Azure 数据资源管理器 (Kusto/KQL) 金融资产回测跟踪停止

Azure Data Explorer (Kusto/KQL) Financial Asset Backtesting Trail Stop

我有一个金融资产价格随时间变化的数据集,我想模拟跟踪止损以针对该数据集进行回溯测试策略。

追踪止损是一些在线经纪商支持的一种交易订单类型,用于开仓时的止损或利润保护,设置追踪止损以在满足价格条件时自动止损。

跟踪止损单会随着资产价格的上涨而增加,并在开仓期间保持在最大值,一旦资产价格跌破跟踪止损最大值,经纪商将平仓.

在这种情况下,跟踪止损是资产价格的百分比。即资产价格减去 3%。

我尝试了多种方法,包括汇总和扫描运算符,但似乎无法找到工作原型。

下面是价格随时间变化的资产的示例数据 table。

//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

如果追踪止损正常工作,并且有开仓和平仓列指示追踪止损发生的时间,导致平仓,结果集将类似于以下数据的输出table .

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

最终结果将是两个开仓和平仓。

如有任何帮助、想法或指导,我们将不胜感激。

Investopedia 关于追踪止损的很好解释

  • 按位置和时间戳排序
  • 使用 prev() 确定新位置的起点。
  • 使用 scan() 计算 运行 CallPremium 最大值(总是上升,为新位置重置)。
  • 将每个 CallPremium 与 运行 最大值进行比较,并检查是否达到追踪止损。

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
Timestamp Symbol StrikePrice CallPremium PositionId PositionId_start CallPremium_running_max TrailStop PositionOpen PositionClose
2022-03-16T13:57:55.815Z SPY 432 2.46 1 true 2.46 2.39 0 1
2022-03-16T14:00:55.698Z SPY 432 2.48 1 false 2.48 2.41 0 1
2022-03-16T14:01:15.876Z SPY 432 2.49 1 false 2.49 2.42 0 1
2022-03-16T14:08:25.536Z SPY 431 2.45 1 false 2.49 2.42 0 1
2022-03-16T14:18:25.675Z SPY 434 2.4 1 false 2.49 2.42 1 0
2022-03-16T14:21:50.887Z SPY 434 2.4 2 true 2.4 2.33 0 1
2022-03-16T14:35:00.835Z SPY 434 2.33 2 false 2.4 2.33 1 0

Fiddle