Вторая часть
Третья часть
Четвертая часть
Пятая часть
Шестая часть
Седьмая часть
Восьмая часть
Девятая часть
Десятая часть
Из результатов тестирования нашей стратегии можно видеть, что она показывает различную эффективность для разных акций. То же самое будет наблюдаться, если взять разные временный периоды тестирования, разные таймфреймы и т.д.
В общем случаем при тестировании стратегий на исторических данных для решения таких проблем производится оптимизация параметров стратегии. В процессе оптимизации подбирается оптимальное соотношение параметров стратегии, например периодов скользящих средних. Об этом мы поговорим далее.
За основу мы возьмем нашу стратегию пересечения двух скользящих средних для трех активов. Первоначальные настройки не меняются:
# Первоначальные настройки
Sys.setenv(TZ = "UTC")
currency('RUR')
init_date <- "2011-12-31"
start_date <- "2012-01-01"
end_date <- "2016-12-31"
symbols <- c("VTBR", "ROSN", "NLMK")
getSymbols(symbols, from=start_date, to=end_date, src="Finam", period="day")
init_equity <- 1e5
adjustment <- FALSE
stock(symbols, currency = "RUR", multiplier = 1)
Мы присвоим диапазон для каждой из наших SMA двум новым переменным: .fastSMA и .slowSMA. Обе являются простыми целочисленными векторами. Вы можете сделать диапазон настолько узкими или широкими, насколько захотите. Однако необходимо учитывать, что оптимизация - это затратный вычислительный процесс. Чем шире ваши диапазоны параметров, тем дольше они будут рассчитываться.
.fastSMA <- (1:30)
.slowSMA <- (20:80)
.nsamples <- 0
Далее, как обычно, инициализируем объекты нашей стратегии:
# Присвоим имена нашим портфелю, счету и объектам стратегии.
portfolio.st <- "Port.Luxor.MA.Opt"
account.st <- "Acct.Luxor.MA.Opt"
strategy.st <- "Strat.Luxor.MA.Opt"
# Удаляем остатки предыдущих запусков стратегий и очищаем значения нашего портфеля и счета
rm.strat(portfolio.st)
rm.strat(account.st)
# Инициализация портфеля
initPortf(name = portfolio.st, symbols = symbols, initDate = init_date, currency = "RUR")
# Инициализация счета
initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, currency = "RUR", initEq = init_equity)
# Инициализация ордеров
initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date)
# Инициализация стратегии
strategy(strategy.st, store = TRUE)
Добавление индикаторов не меняется:
# Добавление индикаторов
# Быстрая скользящая средняя
add.indicator(strategy = strategy.st,
name = "SMA",
arguments = list(x = quote(Cl(mktdata)), n = 7),
label = "nFast")
# Медленная скользящая средняя
add.indicator(strategy = strategy.st,
name = "SMA",
arguments = list(x = quote(Cl(mktdata)), n = 21),
label = "nSlow")
Добавление сигналов и правил, аналогично, не изменяется:
# Добавление сигналов
# Сигнал на открытие длинной позиции
add.signal(strategy = strategy.st,
name="sigCrossover",
arguments = list(columns = c("nFast", "nSlow"),
relationship = "gte"),
label = "long")
# Сигнал на закрытие длинной позиции
add.signal(strategy = strategy.st,
name="sigCrossover",
arguments = list(columns = c("nFast", "nSlow"),
relationship = "lt"),
label = "short")
# Добавление правил
# Правило открытия длинной позиции
add.rule(strategy = strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long",
sigval = TRUE,
orderqty = 100,
ordertype = "market",
orderside = "long",
prefer = "Open",
TxnFees = -20,
replace = FALSE),
type = "enter",
label = "EnterLONG")
# Правило открытия короткой позиции
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
orderqty = -100,
ordertype = "market",
orderside = "short",
replace = FALSE,
TxnFees = -20,
prefer = "Open"),
type = "enter",
label = "EnterSHORT")
# Правило закрытия длинной позиции
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
orderside = "long",
ordertype = "market",
orderqty = "all",
TxnFees = -20,
prefer = "Open",
replace = TRUE),
type = "exit",
label = "Exit2SHORT")
# Правило закрытия короткой позиции
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long",
sigval = TRUE,
orderside = "short",
ordertype = "market",
orderqty = "all",
TxnFees = -20,
prefer = "Open",
replace = TRUE),
type = "exit",
label = "Exit2LONG")
Далее мы используем функцию add.distribution для добавления нашего диапазона значений по двум индикаторам в стратегию.
# Добавляем распределение для быстрой SMA
add.distribution(strategy.st,
paramset.label = "SMA",
component.type = "indicator",
component.label = "nFast",
variable = list(n = .fastSMA),
label = "nFAST")
# Добавляем распределение для медленной SMA
add.distribution(strategy.st,
paramset.label = "SMA",
component.type = "indicator",
component.label = "nSlow",
variable = list(n = .slowSMA),
label = "nSLOW")
Добавление ограничений распределения
Согласно нашим правилам, период быстрой скользящей средней всегда меньше периода медленной скользящей средней, поэтому нет смысла тратить время на перебор вариантов, где будет наоборот.
Это контролируется с помощью функции add.distribution.constraint. Код:
# Добавляем ограничение для распределений
add.distribution.constraint(strategy.st,
paramset.label = "SMA",
distribution.label.1 = "nFAST",
distribution.label.2 = "nSLOW",
operator = "<",
label = "SMA.Constraint")
Мы передаем ей paramset.label так же, как и в add.distribution. Мы присваиваем значение nFAST параметру distribution.label.1 и nSLOW - distribution.label.2.
Наш оператор будет одним из c ("<", ">", "<=", "> =", "="). Здесь мы вводим ограничение, чтобы всегда поддерживать отношение: nFAST меньше nSLOW.
Назовем это ограничение SMA.Constraint, присвоив его параметру label.
Параллельные вычисления
По умолчанию в наших расчетах используется одно виртуальное ядро процессора, что может увеличить время расчетов. Однако, если вы работаете в системе с более чем одним ядром, вы можете использовать приведенный ниже код, любезно предоставленный Гаем Йоллином, для активации распределения вычислительной нагрузки на несколько ядер. Для этого требуется библиотека parallel, а также doParallel для пользователей Windows, и doMC для пользователей систем, отличных от Windows:
# Активация параллельных вычислений
library(parallel)
if( Sys.info()['sysname'] == "Windows") {
library(doParallel)
registerDoParallel(cores=detectCores())
} else {
library(doMC)
registerDoMC(cores=detectCores())
}
Запуск оптимизации
Когда мы запускали нашу исходную стратегию, мы использовали applyStrategy(). При запуске оптимизации мы используем apply.paramset().
# Запуск оптимизации
results <- apply.paramset(strategy.st,
paramset.label = "SMA",
portfolio.st = portfolio.st,
account.st = account.st,
nsamples = .nsamples)
Здесь strategy.st - название стратегии;
paramset.label - имя paramset.label, которое мы определили в add.distribution() или add.distribution.contraint();
портфолио.st - название портфеля;
account.st - название счета;
nsamples - количество перебираемых вариантов, nsample = 0 означает возврат всех возможных вариантов.
Изучение результатов оптимизации
Итак, результаты нашей оптимизации сохранены в объекте “results”, который является окружением (environment), то есть неким метаобъектом, в котором хранятся другие объекты. Его структуру можно просмотреть в RStudio с помощью команды:
View(results)
Мы можем видеть, что results включает в себя объекты c названиями вида Port.Luxor.MA.Opt.train.xx, каждый из которых содержит данные по тестированию одного из сочетаний .fastSMA и .slowSMA.
Нас здесь интересует список tradeStats, содержащий основные показатели тестирования для одного сочетания значений скользящих средних. Сохраняем их в dataframe и упорядочиваем по значению быстрой скользящей средней:
tS <- results$tradeStats
idx <- order(tS[,1],tS[,2])
tS <- tS[idx,]
В результате у нас теперь есть dataframe, содержащий результаты тестирования стратегии на исторических данных:
> str(tS)
'data.frame': 1764 obs. of 34 variables:
$ nFAST : int 1 1 1 1 1 1 1 1 1 1 ...
$ nSLOW : int 20 21 22 23 24 25 26 27 28 29 ...
$ Portfolio : chr "Port.Luxor.MA.Opt.train.1" "Port.Luxor.MA.Opt.train.20" "Port.Luxor.MA.Opt.train.40" "Port.Luxor.MA.Opt.train.61" ...
$ Symbol : chr "ROSN" "ROSN" "ROSN" "ROSN" ...
$ Num.Txns : num 309 305 291 295 291 287 289 277 257 249 ...
$ Num.Trades : int 154 152 145 147 145 143 144 138 128 124 ...
$ Net.Trading.PL : num -6036 -1994 295 -2541 -279 ...
$ Avg.Trade.PL : num -51.9 -26.2 -12.7 -31.5 -16.6 ...
$ Med.Trade.PL : num -266 -232 -230 -225 -220 ...
$ Largest.Winner : num 5055 5055 4685 4685 5675 ...
$ Largest.Loser : num -1815 -1815 -1815 -1815 -1645 ...
$ Gross.Profits : num 43245 44754 44623 42232 43653 ...
$ Gross.Losses : num -51236 -48743 -46463 -46868 -46067 ...
$ Std.Dev.Trade.PL : num 973 1005 1011 967 1015 ...
$ Std.Err.Trade.PL : num 78.4 81.5 84 79.7 84.3 ...
$ Percent.Positive : num 27.9 28.3 26.9 25.2 26.9 ...
$ Percent.Negative : num 72.1 71.7 73.1 74.8 73.1 ...
$ Profit.Factor : num 0.844 0.918 0.96 0.901 0.948 ...
$ Avg.Win.Trade : num 1006 1041 1144 1141 1119 ...
$ Med.Win.Trade : num 395 519 796 820 796 ...
$ Avg.Losing.Trade : num -462 -447 -438 -426 -435 ...
$ Med.Losing.Trade : num -385 -356 -331 -328 -337 ...
$ Avg.Daily.PL : num -51.9 -26.2 -12.7 -31.5 -16.6 ...
$ Med.Daily.PL : num -266 -232 -230 -225 -220 ...
$ Std.Dev.Daily.PL : num 973 1005 1011 967 1015 ...
$ Std.Err.Daily.PL : num 78.4 81.5 84 79.7 84.3 ...
$ Ann.Sharpe : num -0.846 -0.415 -0.199 -0.518 -0.26 ...
$ Max.Drawdown : num -25875 -23235 -19825 -18155 -18205 ...
$ Profit.To.Max.Draw: num -0.2333 -0.0858 0.0149 -0.14 -0.0153 ...
$ Avg.WinLoss.Ratio : num 2.18 2.33 2.61 2.68 2.58 ...
$ Med.WinLoss.Ratio : num 1.03 1.46 2.41 2.5 2.36 ...
$ Max.Equity : num 14804 16206 15085 10579 12891 ...
$ Min.Equity : num -11071 -7029 -4740 -7576 -5314 ...
$ End.Equity : num -6036 -1994 295 -2541 -279 ...
Можно построить трехмерный график, отображающий прибыльность стратегии в зависимости от значений быстрой и медленной скользящих средних:
tradeGraphs(stats = tS, free.params = c("nFAST", "nSLOW"),
statistics = c("Net.Trading.PL", ""))
Однако такой график может помочь лишь в общих чертах понять распределение прибылей и убытков. Для того, чтобы вникнуть в детали, необходимы более тонкие инструменты.
Можно визуализировать результаты оптимизации в виде тепловых карт интересующих нас параметров, что позволит наглядно увидеть их распределение. Начнем с чистой прибыли/убытков:
# чистая прибыль/убыток
z <- tapply(X = tS[,"End.Equity"], INDEX = list(Fast=tS[,1], Slow = tS[,2]), FUN = sum)
x <- as.numeric(rownames(z))
y <- as.numeric(colnames(z))
filled.contour(x = x, y = y, z = z, color = heat.colors, xlab="Fast MA", ylab = "Slow MA")
title("Чистая прибыль")
Далее посмотрим на максимальную просадку:
# максимальная просадка
z <- tapply(X = tS[,"Max.Drawdown"], INDEX = list(Fast=tS[,1], Slow=tS[,2]), FUN = sum)
x <- as.numeric(rownames(z))
y <- as.numeric(colnames(z))
filled.contour(x = x, y = y, z = z, color = heat.colors, xlab = "Fast MA", ylab = "Slow MA")
title("Максимальная просадка")
Profit Factor:
# profit factor
z <- tapply(X = tS[,"Profit.Factor"], INDEX = list(Fast = tS[,1], Slow = tS[,2]), FUN = sum)
x <- as.numeric(rownames(z))
y <- as.numeric(colnames(z))
filled.contour(x = x, y = y, z = z, color = heat.colors, xlab= "Fast MA", ylab = "Slow MA")
title("Profit Factor")
Из вышеприведенных графиков можно понять, что на большинстве комбинаций наша стратегия приносит убытки. Положительные значения наблюдаются в узком диапазоне значений переменных.
Еще один быстрый способ оценить эффективность стратегии - построить гистограмму распределения прибылей.
# Построение гистограммы распределения прибылей
profit_gr <- ggplot(tS, aes(x=End.Equity)) + geom_density(fill = "lightblue",color="white")
profit_gr + xlab("Общая прибыль") + ggtitle("Распределение прибылей стратегии пересечения двух скользящих средних")
Аналогичным образом можно построить гистограммы распределения и других показателей эффективности.
Комментариев нет:
Отправить комментарий