У меня есть набор данных о ценах на финансовые активы с течением времени, и я хотел бы имитировать трейл-стоп для тестирования стратегий на основе этого набора данных.
Трейл-стоп — это тип торгового ордера, поддерживаемый некоторыми онлайн-брокерами, который используется в качестве стоп-лосса или защиты прибыли при открытии позиции. Трейл-стоп размещается для автоматического стоп-лосса при выполнении ценового условия.
Ордер трейл-стоп будет следовать за ценой актива по мере ее увеличения и оставаться на максимуме в течение всего времени, пока позиция открыта. Как только цена актива упадет ниже макс. трейл-стопа, позиция будет закрыта брокером.
В этом случае трейл-стоп представляет собой процент от цены актива. т.е. цена актива меньше 3%.
Я пробовал несколько подходов, включая суммирование и оператора сканирования, и, похоже, не смог найти работающий прототип.
Ниже приведен пример таблицы данных актива с изменением цены с течением времени.
//Trail Stop Properties:
//Trail stop will follow an asset price as it increases
// and remain at the max of the asset price increase during an open position
// the position will be closed when the price is less than
// or equal to the trail stop value.
//Usually the stop is set with a percentage of loss from the trailing price.
//i.e. in the below example the trailing stop is 0.03 or 3% of the asset price.
let trailstop = double(0.03);
let assets = datatable
(
Timestamp:datetime, Symbol:string, StrikePrice:double, CallPremium:double,
PositionId:int
)
[
datetime(2022-03-16T13:57:55.815Z), 'SPY' ,432, 2.46, 1,
datetime(2022-03-16T14:00:55.698Z), 'SPY' ,432, 2.48, 1,
datetime(2022-03-16T14:01:15.876Z), 'SPY' ,432, 2.49, 1,
datetime(2022-03-16T14:08:25.536Z), 'SPY' ,431, 2.45, 1,
datetime(2022-03-16T14:18:25.675Z), 'SPY' ,434, 2.40, 1,
datetime(2022-03-16T14:21:50.887Z), 'SPY' ,434, 2.40, 2,
datetime(2022-03-16T14:35:00.835Z), 'SPY' ,434, 2.33, 2
]
;
assets
| sort by Timestamp asc
| extend TrailStop = round(CallPremium - (CallPremium * trailstop),2)
| extend rn = row_number()
Выход
2022-03-16T13:57:55.815Z SPY 432 2.46 1 2.39 1
2022-03-16T14:00:55.698Z SPY 432 2.48 1 2.41 2
2022-03-16T14:01:15.876Z SPY 432 2.49 1 2.42 3
2022-03-16T14:08:25.536Z SPY 431 2.45 1 2.38 4
2022-03-16T14:18:25.675Z SPY 434 2.4 1 2.33 5
2022-03-16T14:21:50.887Z SPY 434 2.4 2 2.33 6
2022-03-16T14:35:00.835Z SPY 434 2.33 2 2.26 7
Если бы трейл-стоп работал правильно и имелись столбцы открытия и закрытия позиций, указывающие, когда произошла трейл-стоп, в результате чего позиция была закрыта, набор результатов выглядел бы как вывод следующей таблицы данных.
let outcomes = datatable
(
Timestamp:datetime, Symbol:string, StrikePrice:double, CallPremium:double,
PositionId:int, TrailStop:double, PositionOpen:int, PositionClose:int
)
[
datetime(2022-03-16T13:57:55.815Z), 'SPY', 432, 2.46, 1, 2.39, 1, 0,
datetime(2022-03-16T14:00:55.698Z), 'SPY', 432, 2.48, 1, 2.41, 1, 0,
datetime(2022-03-16T14:01:15.876Z), 'SPY', 432, 2.49, 1, 2.42, 1, 0,
datetime(2022-03-16T14:08:25.536Z), 'SPY', 431, 2.45, 1, 2.42, 1, 0,
datetime(2022-03-16T14:18:25.675Z), 'SPY', 434, 2.40, 1, 2.42, 0, 1,
datetime(2022-03-16T14:21:50.887Z), 'SPY', 434, 2.40, 2, 2.33, 1, 0,
datetime(2022-03-16T14:35:00.835Z), 'SPY', 434, 2.33, 2, 2.26, 0, 1
]
;
outcomes
| sort by Timestamp asc
| extend rn = row_number()
Выход
2022-03-16T13:57:55.815Z SPY 432 2.46 1 2.39 1 0 1
2022-03-16T14:00:55.698Z SPY 432 2.48 1 2.41 1 0 2
2022-03-16T14:01:15.876Z SPY 432 2.49 1 2.42 1 0 3
2022-03-16T14:08:25.536Z SPY 431 2.45 1 2.42 1 0 4
2022-03-16T14:18:25.675Z SPY 434 2.4 1 2.42 0 1 5
2022-03-16T14:21:50.887Z SPY 434 2.4 2 2.33 1 0 6
2022-03-16T14:35:00.835Z SPY 434 2.33 2 2.26 0 1 7
Конечным результатом будут две открытые и закрытые позиции.
Позиция 1 открыто (rn=1) при 2,46 и закрыто (rn=5) при 2,42
Позиция 2 открыта (rn=6) в 2.40 и закрыта (rn=7) в 2.33
Любая помощь, идеи или рекомендации будут высоко оценены.
Инвестопедия за хорошее объяснение трейлинг-стопа
let trailstop = double(0.03);
let assets = datatable
(
Timestamp:datetime, Symbol:string, StrikePrice:double, CallPremium:double,
PositionId:int
)
[
datetime(2022-03-16T13:57:55.815Z), 'SPY' ,432, 2.46, 1,
datetime(2022-03-16T14:00:55.698Z), 'SPY' ,432, 2.48, 1,
datetime(2022-03-16T14:01:15.876Z), 'SPY' ,432, 2.49, 1,
datetime(2022-03-16T14:08:25.536Z), 'SPY' ,431, 2.45, 1,
datetime(2022-03-16T14:18:25.675Z), 'SPY' ,434, 2.40, 1,
datetime(2022-03-16T14:21:50.887Z), 'SPY' ,434, 2.40, 2,
datetime(2022-03-16T14:35:00.835Z), 'SPY' ,434, 2.33, 2
]
;
assets
| sort by PositionId asc, Timestamp asc
| extend PositionId_start = prev(PositionId) != PositionId
| scan declare (CallPremium_running_max:double = double(null))
with
(
step s1 : true => CallPremium_running_max =
max_of(iff(PositionId_start,double(null),s1.CallPremium_running_max),CallPremium);
)
| extend TrailStop = round(CallPremium_running_max*(1-trailstop),2)
| extend PositionOpen = iff(CallPremium <= TrailStop,1,0)
| extend PositionClose = 1 - PositionOpen
Отметка времени | Условное обозначение | СтрайкЦена | CallПремиум | Идентификатор позиции | PositionId_start | CallPremium_running_max | ТрейлСтоп | ПозицияОткрыта | ПозицияЗакрыть |
---|---|---|---|---|---|---|---|---|---|
2022-03-16T13:57:55.815Z | ШПИОН | 432 | 2,46 | 1 | истинный | 2,46 | 2,39 | 0 | 1 |
2022-03-16T14:00:55.698Z | ШПИОН | 432 | 2,48 | 1 | ЛОЖЬ | 2,48 | 2,41 | 0 | 1 |
2022-03-16T14:01:15.876Z | ШПИОН | 432 | 2,49 | 1 | ЛОЖЬ | 2,49 | 2,42 | 0 | 1 |
2022-03-16T14:08:25.536Z | ШПИОН | 431 | 2,45 | 1 | ЛОЖЬ | 2,49 | 2,42 | 0 | 1 |
2022-03-16T14:18:25.675Z | ШПИОН | 434 | 2,4 | 1 | ЛОЖЬ | 2,49 | 2,42 | 1 | 0 |
2022-03-16T14:21:50.887Z | ШПИОН | 434 | 2,4 | 2 | истинный | 2,4 | 2,33 | 0 | 1 |
2022-03-16T14:35:00.835Z | ШПИОН | 434 | 2,33 | 2 | ЛОЖЬ | 2,4 | 2,33 | 1 | 0 |
Tbh, исходные данные, которые я опубликовал, немного надуманы, реальные рыночные данные, используемые для тестирования на истории, не имеют столбца PositionId. Я создал его как часть примера набора данных. На самом деле используемые рыночные данные таковы; на основе времени, активов и цены. В этом случае цель бэк-тестирования состоит в том, чтобы определить, когда позиции будут открываться благодаря индикаторам рыночных условий и закрываться из-за определений ордеров трейл-стоп. Значения PositionId будут дополнительной группировкой, добавленной в процессе открытия позиции. т. е. когда позиция открыта, значение PositionId+1 для всех строк до стоп-ордера.
С удовольствием, Дэмиен. Если необходимо изменить вопрос, я бы предложил сохранить исходную версию, утвердить ответ и открыть новый пост для измененной версии.
Спасибо @David דודו Markovitz, я отметил этот вопрос как ответ и опубликовал новый вопрос в качестве продолжения этого вопроса. Для справки: идентификатор нового вопроса — 71529994, его можно найти по адресу Следовать за.
Спасибо за отличный ответ @David דודו Markovitz. Я сделал одно небольшое изменение, чтобы выровнять значения столбцов Position Open/Close.
| extend PositionOpen = iff(CallPremium > TrailStop,1,0)