Третья часть
Четвертая часть
Пятая часть
Шестая часть
Седьмая часть
Восьмая часть
Девятая часть
Десятая часть
Одиннадцатая часть
К настоящему времени мы уже умеем собирать наши стратегии для тестирования из набора индикаторов, сигналов и правил, а также производить тестирование на исторических данных и выводить его результаты.
Однако реальную торговую стратегию невозможно представить без стоп-лоссов.
Стоп-лосс (англ. stop loss — «остановить потери») — биржевая заявка, выставленная в торговом терминале трейдером с целью ограничить свои убытки при достижении ценой заранее определенного уровня. Стоп-лосс представляет собой защитный ордер. Без него при движении рынка против вашей позиции вы можете понести большие убытки, либо, при использовании плечей, обнулить свой счет.
Рассмотрим, как выставить стоп-лоссы в нашей, уже обкатанной стратегии пересечения двух скользящих средних. Будем рассматривать вариант стратегии для одного актива (для упрощения), в качестве которого возьмем акции НЛМК.
Первоначальные настройки не отличаются от того, что мы делали раньше. Сначала загружаем необходимые нам библиотеки:
# Загружаем необходимые библиотеки
library("blotter")
library("quantstrat")
library("rusquant")
library("knitr")
library("kableExtra")
library("dplyr")
library("lattice")
Далее задаем все первоначальные настройки стратегии:
# код для очистки всех компонентов портфеля и стратегии перед запуском
.blotter <- new.env()
.strategy <- new.env()
# Первоначальные настройки
# Установка часового пояса
Sys.setenv(TZ = "UTC")
# Задаем валюту
currency('RUR')
# Дата инициализации портфеля
init_date <- "2011-12-31"
# Дата начала торговли
start_date <- "2012-01-01"
# Дата окончания торговли
end_date <- "2016-12-31"
# Наши активы, которыми мы торгуем
symbols <- c("NLMK")
# Скачиваем исторические данные
getSymbols(symbols, from=start_date, to=end_date, src="Finam", period="day")
# Начальный депозит (100 000)
init_equity <- 1e5
# Учет дивидендов при расчете доходности
adjustment <- FALSE
# Задаем валюту в RUR с мультипликатором 1.
stock(symbols, currency = "RUR", multiplier = 1)
А теперь небольшое изменение. Перед инициализацией объектов нашей стратегии сохраним все наши настройки в переменных, чтобы упростить работу с кодом в дальнейшем.
# Сохраняем настройки в переменных
.fast <- 7
.slow <- 21
.orderqty <- 100
.txnfees <- -20
.stoploss <- 1e-2 # 0.01 или 1.0%
portfolio.st <- "Port.Luxor.Stop.Loss"
account.st <- "Acct.Luxor.Stop.Loss"
strategy.st <- "Strat.Luxor.Stop.Loss"
Инициализация счета, портфеля и стратегии:
# Удаляем остатки предыдущих запусков стратегий и очищаем значения нашего портфеля и счета
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 = .fast),
label = "nFast")
# Медленная скользящая средняя
add.indicator(strategy = strategy.st,
name = "SMA",
arguments = list(x = quote(Cl(mktdata)), n = .slow),
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.st,
name = "ruleSignal",
arguments = list(sigcol = "long" ,
sigval = TRUE,
replace = FALSE,
orderside = "long",
ordertype = "market",
prefer = "Open",
TxnFees = .txnfees,
orderqty = +.orderqty,
osFUN = osMaxPos,
orderset = "ocolong"),
type = "enter",
label = "EnterLONG")
# Правило открытия короткой позиции
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
replace = FALSE,
orderside = "short",
ordertype = "market",
prefer = "Open",
TxnFees = .txnfees,
orderqty = -.orderqty,
osFUN = osMaxPos,
orderset = "ocoshort"),
type = "enter",
label = "EnterSHORT")
# Правило закрытия длинной позиции
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
replace = TRUE,
orderside = "long" ,
ordertype = "market",
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocolong"),
type = "exit",
label = "Exit2SHORT")
# Правило закрытия короткой позиции
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long",
sigval = TRUE,
replace = TRUE,
orderside = "short",
ordertype = "market",
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocoshort"),
type = "exit",
label = "Exit2LONG")
До этого момента наша стратегия Luxor.Stop.Loss была практически такой же, как и наша оригинальная стратегия. Пока наши правила не сильно отличаются от предыдущих правил, которые мы добавляли в прошлый раз. Многие параметры похожи. Хотя есть и новые.
Мы используем функцию osFUN, о которой уже упоминали ранее. Это функция, используемая для определения размера ордера. Значение по умолчанию для нее - osNoOp, это функция ордеров, которая не выполняет никаких операций. Другими словами, если вы передадите 100 в качестве orderqty, это будет покупка 100 ценных бумаг.
В данном случае в наших правилах открытия позиций мы передаем функцию osMaxPos(). Она совместно с функцией addPosLimit() (которую мы приведем ниже), используется, чтобы задать максимальную позицию для каждого тикера. Это предотвратит повторное выполнение одних и тех же ордеров.
Мы также включили параметр orderset со значениями ocolong и ocoshort. Он поможет сгруппировать наши длинные и короткие ордера.
А теперь введем правила для стоп-лоссов:
# Правила для стоп-лоссов
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long",
sigval = TRUE,
replace = FALSE,
orderside = "long",
ordertype = "stoplimit",
tmult = TRUE,
threshold = quote(.stoploss),
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocolong"),
type = "chain",
parent = "EnterLONG",
label = "StopLossLONG",
enabled = FALSE)
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
replace = FALSE,
orderside = "short",
ordertype = "stoplimit",
tmult = TRUE,
threshold = quote(.stoploss),
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocoshort"),
type = "chain",
parent = "EnterSHORT",
label = "StopLossSHORT",
enabled = FALSE)
До этого момента наша стратегия Luxor.Stop.Loss была такой же, как и наша оригинальная стратегия. Когда мы открывали длинную позицию, мы оставались в ней, пока не получали короткий сигнал. Однако теперь мы поставили стопы.
Во-первых, мы создали правило StopLossLONG как дочернее правило родительского правила EnterLONG, являющегося частью набора ордеров ocolong. Пока оно не активировано (enabled = FALSE).
Важнейшей частью StopLossLONG являются параметры tmult и threshold. Когда длинный ордер выполнен, threshold и tmult используются, чтобы определить цену стоп-ордера (ordertype). При этом .stoploss умножается (tmult) на цену выполненного длинного ордера. Эта цена служит ценой стоп-лосса.
Например, пусть цена купленной акции составляет 134,39 руб, .stoploss = 0,01. Тогда цена стоп-лосса будет равна:
StopLossLONG = 134,39−(0.01∗134.39) = 133,05 руб.
Если рыночная цена опускается ниже 133,05 руб, ордер StopLossLONG становится рыночным ордером, а ордер Exit2SHORT отменяется (OCO).
То же самое относится к StopLossSHORT, который является дочерним по отношению к EnterSHORT, за исключением того, что к цене исполнения добавляется .stoploss.
Добавление лимита позиции
Как упоминалось ранее, при использовании osMaxPos() мы должны указать лимит позиции для каждого тикера, с которым работает наша стратегия. Мы делаем это с помощью addPosLimit. На данный момент мы применяем только параметр maxpos, который мы задали в .orderqty.
# Добавление лимита позиции
for(symbol in symbols){
addPosLimit(portfolio = portfolio.st,
symbol = symbol,
timestamp = init_date,
maxpos = .orderqty)
}
Активация правил
Когда мы писали правила StopLossLONG и StopLossSHORT, мы отключили их, присвоив параметру enabled значение FALSE. Теперь мы включаем оба набора правил. Такая возможность очень полезна, если вы хотите протестировать стратегию с разными наборами правил (чтобы не переписывать код).
# Активация правил
enable.rule(strategy.st,
type = "chain",
label = "StopLoss")
Параметр label может применяться к определенному правилу или путем сопоставления значения для всех правил с аналогичным значением. Поставляя «StopLoss» для label, мы даем команду Quantstrat активировать все наши правила со строкой «StopLoss» в label, то есть StopLossLONG и StopLossSHORT.
Запускаем тестирование стратегии два раза: первый раз не активируем правила для стоп-лоссов, затем активируем их и смотрим на разницу в реузльтатах.
# Запускаем стратегию
applyStrategy(strategy = strategy.st, portfolios = portfolio.st)
# Обновляем объекты стратегии
updatePortf(portfolio.st)
updateAcct(account.st)
updateEndEq(account.st)
# Статистика торговли
tstats <- tradeStats(portfolio.st)
kable(t(tstats), format = "simple", caption = "Стратегия пересечения двух скользящих средних + Stop")
Давайте сведем в одну таблицу основные показатели нашей стратегии без стоп-лоссов, и с ними, чтобы понять, что изменилось.
Можно видеть, что результаты не слишком впечатляющие. Количество сделок осталось практически неизменным, что неудивительно, однако доля прибыльных сделок резко упала. Хотя при этом также уменьшился средний убыток, это не компенсировало падение доли доходных сделок. Давайте также взглянем на графики:
# Визуализация результатов тестирования
for(symbol in symbols) {
chart.Posn(portfolio.st, Symbol = symbol,
TA = "add_SMA(n = 7, col = 4); add_SMA(n = 21, col = 2)")
Сначала без стопов:
Теперь со стопами:
Из графиков можно видеть, что основной причиной снижение доходности является уменьшение времени жизни открытых позиций. То есть, при небольшом движении рынка в противоположную сторону позиция закрывается по стоп-лоссу. Следовательно, в данном случае нами задано слишком маленькое значение для стоп-лосса.
Комментариев нет:
Отправить комментарий