每次赫兹股票量化在图表上启动智能交易系统时,我们都会面临选择最佳参数以便获得最大盈利能力的问题。 为了找到这些参数,我们会基于历史数据优化交易策略。 然而,如您所知,行情在不断变化。 随着时间的推移,所选参数逐渐失效。
所以,EA 需要重新优化。 这个周期过程持续不断。 每个用户都会自行选择重新优化的时刻。 但是否有可能令该过程自动化? 有哪些可能的解决方案? 也许,您已研究过利用 自定义配置文件 运行终端来对标准策略测试器进行程序化控制的可能性。 我愿意提供一种非常规方法,并将测试器功能分配给一款指标。
1. 思路
当然,指标绝非意指策略测试器。 那么它如何帮助赫兹股票量化优化 EA 呢? 我的思路是在一款指标当中实现 EA 操作的逻辑,并实时跟踪虚拟交易的盈利能力。 在策略测试器中执行优化时,我们会迭代指定参数执行一连串的测试。 类似策略测试器的通关测试,我们将同时启动若干个单一指标的实例,并指派不同的参数值。 当做出决策时,EA 调查所有启动的指标,并从中选择最佳执行参数。
您也许会问,为什么要重新发明轮子。 赫兹股票量化来分析一下这个决策的利弊。 毫无疑问,这种方法的主要优点是在近乎实时的条件下对 EA 进行优化。 第二个优点在于测试是基于您的经纪商的实时逐笔报价。 另一方面,实时测试有一个巨大的缺点,因为您必须要等待收集统计数据。 另一个优点是当行情随时前进时,测试指标只会重新计算当前的逐笔报价而非整个历史记录,而策略测器每次都从历史记录的开头运行。 这种方法可以在适当的时刻提供更快速的优化。 因此,我们几乎可以在每根柱线上进行优化。
这种方法的缺点则包括基于历史数据测试时缺乏逐笔报价。 当然,赫兹股票量化可以利用 CopyTicks 或 CopyTicksRange。 但是下载逐笔报价历史需要时间,且大量数据重新计算也需要计算资源和时间。 我们不要忘记我们用的是指标,在赫兹股票量化中单一品种的所有指标共处一个线程中运行。 因此,此处有另一个局限 — 太多指标可能导致终端减速。
为了最大限度地降低所述缺陷的风险,我们做出以下假设:
初始化测试指标时,历史记录按 М1 OHLC 价格计算。 在计算订单盈利/亏损时,首先检查止损,随后按最高价/最低价(取决于订单类型)检查止盈。
根据第 1 点,订单仅在蜡烛开盘时下单。
要减少测试指标的运行总数,请采用有意义的方法来选择其中使用的参数。 您可以在此处根据指标逻辑添加最小步幅和过滤参数。 例如,在使用 MACD 时,如果快速和慢速均线的参数范围重叠,则测试指标排除某一组参数重叠部分的运行,如慢速均线周期小于或等于快速均线周期的部分,这与 EA 操作逻辑相悖。 您还可以在区间内添加最小差值,初期丢弃大量假信号的选项。
2. 交易策略
为了测试该方法,赫兹股票量化采用基于三个标准指标 WPR、RSI 和 ADX 的简单策略。 当 WPR 向上超过超卖等级时,触发买入信号(等级 -80)。 RSI 不应处于超买区域(高于等级 70)。 由于两个指标都是振荡器,因此在横盘走势中采用它们是合理的。 ADX 指标检测是否存在横盘,等级不应超过 40。
编辑切换为居中
添加图片注释,不超过 140 字(可选)
卖出则是该信号的镜像。 WPR 指标向下超过超买等级 -20,RSI 应超过等级 30 的超卖区域。 ADX 控制横盘的存在,就像买入时一样。
编辑切换为居中
添加图片注释,不超过 140 字(可选)
如前所述,出现信号之后在新蜡烛上执行入场。 离场则是通过固定止损或止盈来执行的。
出于亏损管理,同一时刻场内只保留不多于一笔的持仓。
3. 准备测试器指标
3.1. 虚拟交易类
在定义了交易策略之后,是时候开发一个测试指标了。 首先,赫兹股票量化需要准备在指标中跟踪的虚拟订单。 文章 [1] 已经描述了一个虚拟订单类。 我们可以利用这项工作,并补充一些小模块。 前面描述的类具有 Tick 方法,该方法使用当前的竞卖价和竞买价检查平仓的时刻。 此方法仅适用于在实时状态下工作,不适用于基于历史数据检查。 我们略微改动上述函数,在其中添加价格和点差参数。 执行操作后,该方法返回订单状态。 添加后的结果,该方法将得到以下形式。
bool CDeal::Tick(double price, int spread) { if(d_ClosePrice>0) return true; //--- switch(e_Direct) { case POSITION_TYPE_BUY: if(d_SL_Price>0 && d_SL_Price>=price) { d_ClosePrice=price; i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point); } else { if(d_TP_Price>0 && d_TP_Price<=price) { d_ClosePrice=price; i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point); } } break; case POSITION_TYPE_SELL: price+=spread*d_Point; if(d_SL_Price>0 && d_SL_Price<=price) { d_ClosePrice=price; i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point); } else { if(d_TP_Price>0 && d_TP_Price>=price) { d_ClosePrice=price; i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point); } } break; } return IsClosed(); }
在附件中可找到完整的类代码。
3.2. 指标编程
接下来,赫兹股票量化编写指标本身。 由于我们的测试指标以某种方式扮演 EA 的角色,因此其输入将类似于 EA 参数。 首先,设置测试区间,以及指标参数中的止损和止盈价位。 接着,指定应用指标的参数。 最后,指出统计数据的交易方向和平均周期。 有关每个参数的更多使用方法,会在指标代码中用到的地方详述。
input int HistoryDepth = 500; //历史数据深度(柱线) input int StopLoss = 200; //止损(点数) input int TakeProfit = 600; //止盈(点数) //--- RSI 指标参数 input int RSIPeriod = 28; //RSI 周期 input double RSITradeZone = 30; //超买/超卖区域大小 //--- WPR 指标参数 input int WPRPeriod = 7; // WPR 周期 input double WPRTradeZone = 30; //超买/超卖区域大小 //--- ADX 指标参数 input int ADXPeriod = 11; //ADX 周期 input int ADXLevel = 40; //ADX 横盘等级 //--- input int Direction = -1; //交易方向 "-1"-所有, "0"-买入, "1"-卖出 //--- input int AveragePeriod = 10; //均化周期
为了与 EA 进行计算和数据交换,请创建包含以下数据的九个指标缓冲区:
1. 盈利成交的概率。
double Buffer_Probability[];
2. 测试期间的盈利因子。
double Buffer_ProfitFactor[];
3. 止损和止盈等级。 可以通过创建匹配指标句柄的数组,并在 EA 中指定等级,或者在执行成交时通过其句柄请求指标参数来排除这两个缓冲区。 但是,目前的解决方案对我来说似乎是最简单的解决方案。
double Buffer_TakeProfit[]; double Buffer_StopLoss[];
4. 用于计算测试区间内执行的成交总数及其盈利数量的缓冲区。
double Buffer_ProfitCount[]; double Buffer_DealsCount[];
5. 以下两个缓冲区用于计算先前的数值,并且仅包含当前柱线的类似数据。
double Buffer_ProfitCountCurrent[]; double Buffer_DealsCountCurrent[];
6. 最后但并不仅限于,缓冲区向 EA 发送信号以便执行成交。
double Buffer_TradeSignal[];
除了指定的缓冲区外,还声明一个用于存储开仓成交的数组,一个用于记录最后一比成交时间的变量,用于存储指标句柄的变量,以及从全局变量块中的指标获取信息的数组。
CArrayObj Deals; datetime last_deal; int wpr_handle,rsi_handle,adx_handle; double rsi[],adx[],wpr[];
在 OnInit 函数的开头初始化指标。
int OnInit() { //--- 获取 RSI 指标句柄 rsi_handle=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod,PRICE_CLOSE); if(rsi_handle==INVALID_HANDLE) { Print("Test Indicator",": Failed to get RSI handle"); Print("Handle = ",rsi_handle," error = ",GetLastError()); return(INIT_FAILED); } //--- 获取 WPR 指标句柄 wpr_handle=iWPR(Symbol(),PERIOD_CURRENT,WPRPeriod); if(wpr_handle==INVALID_HANDLE) { Print("Test Indicator",": Failed to get WPR handle"); Print("Handle = ",wpr_handle," error = ",GetLastError()); return(INIT_FAILED); } //--- 获取 ADX 指标句柄 adx_handle=iADX(Symbol(),PERIOD_CURRENT,ADXPeriod); if(adx_handle==INVALID_HANDLE) { Print("Test Indicator",": Failed to get ADX handle"); Print("Handle = ",adx_handle," error = ",GetLastError()); return(INIT_FAILED); }
接下来,将指标缓冲区与动态数组关联起来。
//--- 指标缓存区映射 SetIndexBuffer(0,Buffer_Probability,INDICATOR_CALCULATIONS); SetIndexBuffer(1,Buffer_DealsCount,INDICATOR_CALCULATIONS); SetIndexBuffer(2,Buffer_TradeSignal,INDICATOR_CALCULATIONS); SetIndexBuffer(3,Buffer_ProfitFactor,INDICATOR_CALCULATIONS); SetIndexBuffer(4,Buffer_ProfitCount,INDICATOR_CALCULATIONS); SetIndexBuffer(5,Buffer_TakeProfit,INDICATOR_CALCULATIONS); SetIndexBuffer(6,Buffer_StopLoss,INDICATOR_CALCULATIONS); SetIndexBuffer(7,Buffer_DealsCountCurrent,INDICATOR_CALCULATIONS); SetIndexBuffer(8,Buffer_ProfitCountCurrent,INDICATOR_CALCULATIONS);
将时间序列属性分配给所有数组。
ArraySetAsSeries(Buffer_Probability,true); ArraySetAsSeries(Buffer_ProfitFactor,true); ArraySetAsSeries(Buffer_TradeSignal,true); ArraySetAsSeries(Buffer_DealsCount,true); ArraySetAsSeries(Buffer_ProfitCount,true); ArraySetAsSeries(Buffer_TakeProfit,true); ArraySetAsSeries(Buffer_StopLoss,true); ArraySetAsSeries(Buffer_DealsCountCurrent,true); ArraySetAsSeries(Buffer_ProfitCountCurrent,true); //--- ArraySetAsSeries(rsi,true); ArraySetAsSeries(wpr,true); ArraySetAsSeries(adx,true);
在函数结尾处,重置成交数组和最后一笔成交的日期,并将名称分配给我们的指标。