В последней статье по серии Event-Driven Backtester мы рассмотрели основную иерархию ExecutionHandler. В этой статье мы обсудим, как оценить производительность стратегии после обратного теста с использованием ранее построенной кривой собственности DataFrame в объекте Portfolio.
В предыдущей статье мы уже рассматривали коэффициент Шарпа.В этой статье я описываю, что (годовой) коэффициент Шарпа рассчитывается с помощью:
где Ra - поток доходности кривой собственного капитала, а Rb - эталон, например, соответствующая процентная ставка или индекс собственного капитала.
Максимальное снижение и продолжительность снижения - это две дополнительные меры, которые инвесторы часто используют для оценки риска в портфеле.
В этой статье мы будем реализовывать коэффициент Шарпа, максимальный вывод и длительность вывода в качестве показателей производительности портфеля для использования в на базе Python Suite Event-Driven Backtesting.
Первая задача - создать новый файлperformance.py, который хранит функции для расчета коэффициента Шарпа и информацию о снижении.
# performance.py
import numpy as np
import pandas as pd
Следует отметить, что коэффициент Шарпа является мерой риска к вознаграждению (на самом деле, это один из многих!).
Обычно это значение устанавливается на 252, что является числом торговых дней в США в год. Однако, если ваша стратегия торгуется в течение часа, вам нужно скорректировать Шарп, чтобы правильно годовать его. Таким образом, вам нужно установить периоды на 252
Функция create_sharpe_ratio работает на объекте Pandas Series под названием returns и просто вычисляет соотношение среднего показателя процентного возврата периода и стандартных отклонений процентного возврата периода, скалированных по фактору периодов:
# performance.py
def create_sharpe_ratio(returns, periods=252):
"""
Create the Sharpe ratio for the strategy, based on a
benchmark of zero (i.e. no risk-free rate information).
Parameters:
returns - A pandas Series representing period percentage returns.
periods - Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc.
"""
return np.sqrt(periods) * (np.mean(returns)) / np.std(returns)
В то время как коэффициент Шарпа характеризует, сколько риска (как определено стандартным отклонением пути активов) принимается на единицу доходности,
Функция create_drawdowns ниже на самом деле предоставляет как максимальный вывод, так и максимальную продолжительность вывода. Первый является вышеупомянутым самым большим падением от пика до дна, в то время как последний определяется как количество периодов, в течение которых происходит это падение.
Некоторые тонкости требуются при интерпретации длительности использования, поскольку она учитывает периоды торговли и, следовательно, не может быть прямо переведена в временную единицу, такую как
Функция начинается с создания двух объектов серии панды, представляющих снижение и длительность на каждой торговле
Если это значение отрицательное, то длительность увеличивается для каждого бара, который происходит до достижения следующего HWM. Функция затем просто возвращает максимум каждого из двух рядов:
# performance.py
def create_drawdowns(equity_curve):
"""
Calculate the largest peak-to-trough drawdown of the PnL curve
as well as the duration of the drawdown. Requires that the
pnl_returns is a pandas Series.
Parameters:
pnl - A pandas Series representing period percentage returns.
Returns:
drawdown, duration - Highest peak-to-trough drawdown and duration.
"""
# Calculate the cumulative returns curve
# and set up the High Water Mark
# Then create the drawdown and duration series
hwm = [0]
eq_idx = equity_curve.index
drawdown = pd.Series(index = eq_idx)
duration = pd.Series(index = eq_idx)
# Loop over the index range
for t in range(1, len(eq_idx)):
cur_hwm = max(hwm[t-1], equity_curve[t])
hwm.append(cur_hwm)
drawdown[t]= hwm[t] - equity_curve[t]
duration[t]= 0 if drawdown[t] == 0 else duration[t-1] + 1
return drawdown.max(), duration.max()
Для использования этих показателей эффективности нам нужен способ их расчета после проведения обратного теста, т.е. когда доступна подходящая кривая собственности!
Учитывая, что показатели эффективности рассчитываются на основе портфеля, имеет смысл привязать расчеты эффективности к методу по иерархии класса портфеля, который мы обсудили в этой статье.
Первая задача - открыться.portfolio.pyкак обсуждалось в предыдущей статье и импортировать функции производительности:
# portfolio.py
.. # Other imports
from performance import create_sharpe_ratio, create_drawdowns
Поскольку Portfolio является абстрактным базовым классом, мы хотим прикрепить метод к одному из его производных классов, который в данном случае будет NaivePortfolio.
Метод прост: он просто использует два показателя производительности и применяет их непосредственно к кривой собственности DataFrame, выводя статистику в виде списка tuples в формате:
# portfolio.py
..
..
class NaivePortfolio(object):
..
..
def output_summary_stats(self):
"""
Creates a list of summary statistics for the portfolio such
as Sharpe Ratio and drawdown information.
"""
total_return = self.equity_curve['equity_curve'][-1]
returns = self.equity_curve['returns']
pnl = self.equity_curve['equity_curve']
sharpe_ratio = create_sharpe_ratio(returns)
max_dd, dd_duration = create_drawdowns(pnl)
stats = [("Total Return", "%0.2f%%" % ((total_return - 1.0) * 100.0)),
("Sharpe Ratio", "%0.2f" % sharpe_ratio),
("Max Drawdown", "%0.2f%%" % (max_dd * 100.0)),
("Drawdown Duration", "%d" % dd_duration)]
return stats
Очевидно, что это очень простой анализ эффективности для портфеля. Он не учитывает анализ на уровне торговли или другие меры риска/вознаграждения.performance.pyи затем включить их в output_summary_stat по мере необходимости.