您好,我的问题有些绕,不知道能不能描述的清楚
我想通过主连合约后复权,得到接近真实的回测数据和实盘的映射关系,如海龟策略通过ATR计算手数,但是如果再启用定期优化。就会出现一个逻辑死循环不知道怎样解决:
我在回测时,主连后复权合约的价格溢价(或者折价)通过计算手数能够比较真实的取得回测结果。
但是放在实盘,手数的计算上需要除以rollover,以获得真实的下单手数。
而当启动定期优化,又是根据策略单元后复权价格溢价的回测,优化的参数,
这就导致通过价格计算手数、主连后复权、定期优化这几个功能相互矛盾。
想请教有什么解决的办法么?
我没有完全看懂你的文字表述。总体感觉,问题与【主力连续合约换月、后复权】【映射真实交易价格】【图表回测与实盘绩效一致】【使用周期连续指标:ATR、唐奇安通道】等多种因素叠加的一个问题。
我的思路是:
(1)订阅数据源的时候采用不复权订阅,这样图表以及回测报告都是一致的真实价格。
(2)周期连续指标:ATR、唐奇安通道的指标计算时,使用自己构造的基于后复权价格(HighBackRollover、LowBackRollover、CloseBackRollover、OpenBackRollover、AvgTR)进行计算得到。
(3)图表交易函数Buy、Sell、SellShort、BuyToCover的报单价格,采用【后复权报价÷后复权系数BackRollover[0][1]】得到真实的盘口价格。
由于海龟交易系统的复杂性在于,价格波动幅度、下单风控手数相互关联影响,修改点不局限于部分代码片段,以下完整代码仅供参考确认,是否满足的需求?
Params
Numeric nEntries(3); // 最大建仓次数
Numeric ATRLength(20);
Numeric RiskRatio(1); // % Risk Per N ( 0 - 100)
Numeric boLength(20); // 短周期 BreakOut Length
Numeric fsLength(55); // 长周期 FailSafe Length
Numeric teLength(10); // 离市周期 Trailing Exit Length
Bool LastProfitableTradeFilter(True); // 使用入市过滤条件
Vars
Dic<Array<Numeric>> BackRollover(\"TB_ROLLOVER_COEF\");
Series<Numeric> HighBackRollover; //用于计算XATR
Series<Numeric> LowBackRollover; //用于计算XATR
Series<Numeric> CloseBackRollover; //用于计算XATR
Series<Numeric> OpenBackRollover; //用于计算XATR
Series<Numeric> AvgTR; //XATR
Plot AtrPlt;
Series<Numeric> preEntryPrice(0); // 前一次开仓的价格
Series<Bool> PreBreakoutFailure(false); // 前一次突破是否失败
Numeric MinPoint; // 最小变动单位
Numeric N; // N 值
Numeric TotalEquity; // 按最新收盘价计算出的总资产
Numeric TurtleUnits; // 交易单位
Series<Numeric> DonchianHi; // 唐奇安通道上轨,延后1个Bar
Series<Numeric> DonchianLo; // 唐奇安通道下轨,延后1个Bar
Series<Numeric> fsDonchianHi; // 唐奇安通道上轨,延后1个Bar,长周期
Series<Numeric> fsDonchianLo; // 唐奇安通道下轨,延后1个Bar,长周期
Numeric ExitHighestPrice; // 离市时判断需要的N周期最高价
Numeric ExitLowestPrice; // 离市时判断需要的N周期最低价
Numeric myEntryPrice; // 开仓价格
Numeric myExitPrice; // 平仓价格
Bool SendOrderThisBar(False); // 当前Bar有过交易
Defs
Numeric TrueLow_BackRollover(){
Numeric TLowValue;
TLowValue = CloseBackRollover[1];
If(LowBackRollover <= CloseBackRollover[1])
TLowValue = LowBackRollover;
Return TLowValue;
}
Numeric TrueHigh_BackRollover(){
Numeric THighValue;
THighValue = CloseBackRollover[1];
If(HighBackRollover >= CloseBackRollover[1])
THighValue = HighBackRollover;
Return THighValue;
}
Numeric TrueRange_BackRollover(){
If(CurrentBar == 0) Return HighBackRollover - LowBackRollover;
Else Return TrueHigh_BackRollover - TrueLow_BackRollover;
}
Numeric XAvgTrueRange_BackRollover(Numeric Length){
Return XAverage(TrueRange_BackRollover, Length);
}
Events
OnInit(){
SubscribeBarCounts(\"m9888.DCE\", \"1d\", 1000);
AddDataFlag(Enum_Data_AutoSwapPosition()); //设置自动换仓
AddDataFlag(Enum_Data_IgnoreSwapSignalCalc()); //设置忽略换仓信号计算
SetOrderMap2MainSymbol;
AddDicFlag(Enum_DicFlag_VisitPubTime);
AtrPlt.figure(0);
}
OnBar(ArrayRef<Integer> indexs){
Numeric TmpMyEntryPrice;
Numeric TmpMyExitPrice;
Numeric TmpBackRollover;
TmpBackRollover = BackRollover[0][1];
HighBackRollover = Round(High * TmpBackRollover, 0);
LowBackRollover = Round(Low * TmpBackRollover, 0);
CloseBackRollover = Round(Close * TmpBackRollover, 0);
OpenBackRollover = Round(Open * TmpBackRollover, 0);
AvgTR = XAvgTrueRange_BackRollover(ATRLength) / TmpBackRollover; //【AvgTR】用于计算风险开仓手数
AtrPlt.line(\"AvgTR\", AvgTR);
If(BarStatus == 0){
preEntryPrice = InvalidNumeric;
PreBreakoutFailure = False;
}
MinPoint = MinMove * PriceScale;
N = AvgTR[1];
TotalEquity = Portfolio_CurrentCapital() + Portfolio_UsedMargin();
TurtleUnits = (TotalEquity * RiskRatio / 100) /(N * ContractUnit() * BigPointValue());
TurtleUnits = IntPart(TurtleUnits); // 对小数取整
DonchianHi = HighestFC(HighBackRollover[1], boLength);
DonchianLo = LowestFC(LowBackRollover[1], boLength);
fsDonchianHi = HighestFC(HighBackRollover[1], fsLength);
fsDonchianLo = LowestFC(LowBackRollover[1], fsLength);
ExitLowestPrice = LowestFC(LowBackRollover[1], teLength);
ExitHighestPrice = HighestFC(HighBackRollover[1], teLength);
Commentary(\"N=\" + Text(N));
Commentary(\"preEntryPrice=\" + Text(preEntryPrice));
Commentary(\"PreBreakoutFailure=\" + IIFString(PreBreakoutFailure, \"True\", \"False\"));
// 当不使用过滤条件,或者使用过滤条件并且条件为PreBreakoutFailure为True进行后续操作
If(MarketPosition == 0 && ((!LastProfitableTradeFilter) || (PreBreakoutFailure))){
// 突破开仓
If(HighBackRollover > DonchianHi && TurtleUnits >= 1){
// 开仓价格取突破上轨+一个价位和最高价之间的较小值,这样能更接近真实情况,并能尽量保证成交
myEntryPrice = Min(HighBackRollover, DonchianHi + MinPoint);
myEntryPrice = IIF(myEntryPrice < OpenBackRollover, OpenBackRollover, myEntryPrice); // 大跳空的时候用开盘价代替
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
Buy(TurtleUnits, TmpMyEntryPrice);
SendOrderThisBar = True;
PreBreakoutFailure = False;
}
If(LowBackRollover < DonchianLo && TurtleUnits >= 1){
// 开仓价格取突破下轨-一个价位和最低价之间的较大值,这样能更接近真实情况,并能尽量保证成交
myEntryPrice = Max(LowBackRollover, DonchianLo - MinPoint);
myEntryPrice = IIF(myEntryPrice > OpenBackRollover, OpenBackRollover, myEntryPrice); // 大跳空的时候用开盘价代替
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
SellShort(TurtleUnits, TmpMyEntryPrice);
SendOrderThisBar = True;
PreBreakoutFailure = False;
}
}
// 长周期突破开仓 Failsafe Breakout point
If(MarketPosition == 0){
Commentary(\"fsDonchianHi=\" + Text(fsDonchianHi));
If(HighBackRollover > fsDonchianHi && TurtleUnits >= 1){
// 开仓价格取突破上轨+一个价位和最高价之间的较小值,这样能更接近真实情况,并能尽量保证成交
myEntryPrice = Min(HighBackRollover, fsDonchianHi + MinPoint);
myEntryPrice = IIF(myEntryPrice < OpenBackRollover, OpenBackRollover, myEntryPrice); // 大跳空的时候用开盘价代替
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
Buy(TurtleUnits, TmpMyEntryPrice);
SendOrderThisBar = True;
PreBreakoutFailure = False;
}
Commentary(\"fsDonchianLo=\" + Text(fsDonchianLo));
If(LowBackRollover < fsDonchianLo && TurtleUnits >= 1){
// 开仓价格取突破下轨-一个价位和最低价之间的较大值,这样能更接近真实情况,并能尽量保证成交
myEntryPrice = Max(LowBackRollover, fsDonchianLo - MinPoint);
myEntryPrice = IIF(myEntryPrice > OpenBackRollover, OpenBackRollover, myEntryPrice); // 大跳空的时候用开盘价代替
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
SellShort(TurtleUnits, TmpMyEntryPrice);
SendOrderThisBar = True;
PreBreakoutFailure = False;
}
}
If(MarketPosition == 1){ // 有多仓的情况
Commentary(\"ExitLowestPrice=\" + Text(ExitLowestPrice));
If(LowBackRollover < ExitLowestPrice){
myExitPrice = Max(LowBackRollover, ExitLowestPrice - MinPoint);
myExitPrice = IIF(myExitPrice > OpenBackRollover, OpenBackRollover, myExitPrice); // 大跳空的时候用开盘价代替
Sell(0, Round(myExitPrice / TmpBackRollover, 0)); // 数量用0的情况下将全部平仓
}Else{
If(preEntryPrice != InvalidNumeric && TurtleUnits >= 1){
If(OpenBackRollover >= preEntryPrice + 0.5 * N && CurrentEntries < nEntries){ // 如果开盘就超过设定的1/2N,则直接用开盘价增仓。
myEntryPrice = OpenBackRollover;
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
Buy(TurtleUnits, TmpMyEntryPrice);
SendOrderThisBar = True;
}
While(HighBackRollover >= preEntryPrice + 0.5 * N && CurrentEntries < nEntries){ // 以最高价为标准,判断能进行几次增仓
myEntryPrice = preEntryPrice + 0.5 * N;
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
If(False == Buy(TurtleUnits, TmpMyEntryPrice)){
Break;
}
SendOrderThisBar = True;
}
}
// 止损指令
If(LowBackRollover <= preEntryPrice - 2 * N && SendOrderThisBar == False){ // 加仓Bar不止损
myExitPrice = preEntryPrice - 2 * N;
myExitPrice = IIF(myExitPrice > OpenBackRollover, OpenBackRollover, myExitPrice); // 大跳空的时候用开盘价代替
TmpMyExitPrice = Round(myExitPrice / TmpBackRollover, 0);
Sell(0, TmpMyExitPrice); // 数量用0的情况下将全部平仓
PreBreakoutFailure = True;
}
}
}Else If(MarketPosition ==-1){ // 有空仓的情况
// 求出持空仓时离市的条件比较值
Commentary(\"ExitHighestPrice=\" + Text(ExitHighestPrice));
If(HighBackRollover > ExitHighestPrice){
myExitPrice = Min(HighBackRollover, ExitHighestPrice + MinPoint);
myExitPrice = IIF(myExitPrice < OpenBackRollover, OpenBackRollover, myExitPrice); // 大跳空的时候用开盘价代替
TmpMyExitPrice = Round(myExitPrice / TmpBackRollover, 0);
BuyToCover(0, TmpMyExitPrice); // 数量用0的情况下将全部平仓
}Else{
If(preEntryPrice != InvalidNumeric && TurtleUnits >= 1){
If(OpenBackRollover <= preEntryPrice - 0.5 * N && CurrentEntries < nEntries){ // 如果开盘就超过设定的1/2N,则直接用开盘价增仓。
myEntryPrice = OpenBackRollover;
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
SellShort(TurtleUnits, TmpMyEntryPrice);
SendOrderThisBar = True;
}
While(LowBackRollover <= preEntryPrice - 0.5 * N && CurrentEntries < nEntries){ // 以最低价为标准,判断能进行几次增仓
myEntryPrice = preEntryPrice - 0.5 * N;
preEntryPrice = myEntryPrice;
TmpMyEntryPrice = Round(myEntryPrice / TmpBackRollover, 0);
If(False == SellShort(TurtleUnits, TmpMyEntryPrice)){
Break;
}
SendOrderThisBar = True;
}
}
// 止损指令
If(HighBackRollover >= preEntryPrice + 2 * N && SendOrderThisBar == False){ // 加仓Bar不止损
myExitPrice = preEntryPrice + 2 * N;
myExitPrice = IIF(myExitPrice < OpenBackRollover, OpenBackRollover, myExitPrice); // 大跳空的时候用开盘价代替
TmpMyExitPrice = Round(myExitPrice / TmpBackRollover, 0);
BuyToCover(0, TmpMyExitPrice); // 数量用0的情况下将全部平仓
PreBreakoutFailure = True;
}
}
}
Commentary(\"CurrentEntries = \" + Text(CurrentEntries));
}
感谢大佬的回复,和这么快修改出来的这么复杂的策略。我还在尽力消化理解。
这里面的逻辑是不是,添加了一组主连合约,然后把添加的这组主连合约后复权,以此作为信号,在不复权的原始数据中发送信号?
这里面我感觉还有一个问题就是,不复权主连合约换月的跳开和跳空缺口,在回测中的影响是不是还是挺大的。
主力合约换月处理的方法,我没有写在代码里面,因为每个人的处理方法可能会不一样,你原先如何处理,现在就移植过来。一般简单的处理方法,就是在【OnInit】设置一下:
OnInit(){
SubscribeBarCounts(\"m9888.DCE\", \"1d\", 1000);
AddDataFlag(Enum_Data_AutoSwapPosition()); //设置自动换仓
AddDataFlag(Enum_Data_IgnoreSwapSignalCalc()); //设置忽略换仓信号计算
SetOrderMap2MainSymbol;
AddDicFlag(Enum_DicFlag_VisitPubTime);
AtrPlt.figure(0);
}
我自己没有实际操作过换月操作,据说到时候还要用头寸监控器同步一下图表虚拟头寸与资金账户头寸的仓差来完成换月,这里你最好自己实盘用模拟账户测试操作确认一下。
我原来就是在策略研究里面跑优化,手动把参数倒到策略交易中,然后定期同步头寸。
我的意思是说如果在回测中没办法通过自动换月来计算优化参数的话,放在实盘定期优化也就没有意义了。
这段自动换月的程序我加进去学习下。
再次非常感谢大佬倾情指教!!
比如豆粕ATR的计算,主连后复权合约ATR也是原本合约的5倍左右,比如海龟策略用权益除以ATR计算手数,就会导致手数减少5倍。
在回测时候其实这样正好,价格多5倍,手数少5倍,策略单元金额足够大,回测是接近准确的。
但是实盘,下单的价格是映射的,而手数少了5倍,就需要通过rollover来找齐,但是这样回测又不准确了。固定手数也是这个问题。
atr应该是关于bar数据的线性函数,所以也可以直接用rollover系数进行乘除的复权加权处理的
所以你在计算真实手数的时候,如果atr是用复权数据计算出来的,直接除以rollover就能得到当前真实价格水平的数据,然后再计算手数就行了
我的问题就是我想使用定期优化的功能,这样通过不复权ATR计算手数,回测的结果对应给出的参数就又有误差
难道你是不用映射真实价格的么?
策略交易中肯定会开启主力映射啊,但是用于定期优化(或者回测)时的策略单元就没法映射了呢,这也是出现差异的原因吧
你理解错了。
后复权是有一个设置,叫映射真实价格。
整个映射真实价格指的是你在测试报告里的信号价格,会自动除权,以真实价格去计算,而不是以复权后的点价计算。
请问一下映射真实价格是指这么个功能么?这个功能在测试报告中是按照复权后的价格下单的。
还是有另外别的功能呢?
不是这个。这个是指实盘的时候,报单的合约要映射到另外一个合约。
建议你把后复权的内容好好了解一下,后复权里默认四个命令,设置后复权,设置换月信号,设置映射真实价格,设置忽略换仓信号计算,先了解一下。
我记得我所有的课程里,都会系统性地把四个函数一起说明了。
我回去有学习了老师的视频,是我原来学习的不够认真。
自己纠结很久的问题,居然就这样解决了。前来道谢!!
老师,关于这四个函数,另外有2个小问题,还想再耽误您一下:
1、设置后复权,设置映射真实价格,这两个函数写在OnInit里,在实盘的策略单元是否还要设置:后复权和委托映射主力合约呢?
2、设置换月信号,设置忽略换仓信号计算,这两个函数同时使用,我看在k线交易中有发单信号,而在回测的报告中没有,在回测的交易记录中有。想请问一下,在实盘中这两个函数,是不是不会自动触发换月,还是需要在监控器中同步头寸,以起到移仓换月的作用?
确实有点不太明白
那你回测时候,手数也按照除权后的价格点位计算手数不行吗?
是这样,比如豆粕主力合约的价格是3500,主连复权价格就变成了15000。
如果是做趋势策略想拿200点的行情,复权后对应的就变成了1000多点,而越早时间的复权价格就越接近真实价格。固定手数或者计算手数乘以rollover回测,主连复权都有很大的差异。
另外导致如果用主连复权根据和价格相关的指标去计算手数,实盘中就少了rollover倍数