引言
众所周知, 只有 5% 的交易者在金融市场上获得了稳定的盈利, 但其实 100% 的人都想实现这一目标。
若要成功的进行交易, 您需要一个可盈利的交易策略。与主题相关的网站和交易文献描述了数以百计的各种交易策略。所有指标都附有详尽的信号解释, 但是统计数据依旧保持不变: 5% 的数字既没有变成 100, 也未能到达至少 10。交易思想家们指责市场不稳定, 这就意味着早期的可盈利策略均会失去效用。
在我 之前的文章 中我已谈过有关解析入场信息为指标, 并展示了一个提升已有策略的示例。现在我提议使用这种特别的技术, "用一张白纸" 创建自定义策略。这令我们能够以 "全新的眼光" 来审视您所知道的指标, 收集自定义指标模板, 并反思它们的信号。所建议技术的应用, 意味着解释指标信号的创造性方法, 令每个用户均能创建自己独具特色的策略。
1. 创建一个测试和分析模型
我们在交易终端看到的第一件事就是持续价格走势。潜意识里, 随时开单交易, 我们都会盈利。但您如何判定下一刻价格将会在哪里以及如何汇集呢?交易者试图在技术和基本面分析中找到这个问题的答案。为了进行技术分析, 各项指标不断被发明并完善。本文的新颖之处在于解释这些指标信号; 它也许与常见的不同。
因此, 将入场信点解析为指标的技术意即将开仓与指标值进行比较。一旦再次出现同等状况, 或许, 那时我们还能盈利。在这些输入数据的基础上, 在每根蜡烛开盘时依据预设参数双向开仓。然后, 分析每笔交易的盈利因子如何依赖指标值。
为了解决这个问题, 需要进行一些少量的准备工作。
1.1. 创建虚拟订单类
我使用净持帐户。因此, 若要双向开单, 我只能创建虚拟订单, 即终端 (根据账户设置) 不会跟踪订单, 而是由智能交易系统跟踪交易。为此目的而创建 CDeal 类。当初始化一个类实例时, 我们将传递给它: 品名, 仓位类型, 开仓时间和价格, 以及止损和止盈。仓量被故意省略, 因为我们对此没有兴趣。对我们来说重要的是价格走势, 因此利润/损失将以点数计算而非币值。
为了检查仓位状态, 类中增加了一些服务函数:
- IsClosed — 返回逻辑值, 仓位是否已平仓;
- Type - 返回仓位类型;
- GetProfit — 返回已平仓的利润 (亏损值将是负数);
- GetTime — 返回开仓时间。
class CDeal : public CObject { private: string s_Symbol; datetime dt_OpenTime; // 开仓时间 double d_OpenPrice; // 开仓价格 double d_SL_Price; // 仓位的止损位 double d_TP_Price; // 仓位的止盈位 ENUM_POSITION_TYPE e_Direct; // 开仓方向 double d_ClosePrice; // 平仓价格 int i_Profit; // 仓位盈利的点值 //--- double d_Point; public: CDeal(string symbol, ENUM_POSITION_TYPE type,datetime time,double open_price,double sl_price, double tp_price); ~CDeal(); //--- 检查状态 bool IsClosed(void); ENUM_POSITION_TYPE Type(void) { return e_Direct; } double GetProfit(void); datetime GetTime(void) { return dt_OpenTime; } //--- void Tick(void); };
到达的分笔报价由 Tick 函数处理, 按照止损位或止盈位检查何处必须平仓, 并保存累计利润。
void CDeal::Tick(void) { if(d_ClosePrice>0) return; double price=0; switch(e_Direct) { case POSITION_TYPE_BUY: price=SymbolInfoDouble(s_Symbol,SYMBOL_BID); 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=SymbolInfoDouble(s_Symbol,SYMBOL_ASK); 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; } }
1.2. 创建处理指标的类
为了保存和分析指标数据, 我利用了 前一篇文章 中详细介绍过的类。还有, 在此我创建了 CDealsToIndicators 类, 囊括了所有的指标类。它将存储指标类数组并分配它们的功能。
class CDealsToIndicators { private: CADX *ADX[]; CAlligator *Alligator[]; COneBufferArray *OneBuffer[]; CMACD *MACD[]; CStaticOneBuffer *OneBufferStatic[]; CStaticMACD *MACD_Static[]; CStaticADX *ADX_Static[]; CStaticAlligator *Alligator_Static[]; template<typename T> void CleareArray(T *&array[]); public: CDealsToIndicators(); ~CDealsToIndicators(); //--- bool AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name); bool AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name, int &handle); bool AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name); bool AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name, int &handle); bool AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name); bool AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name, int &handle); bool AddOneBuffer(int handle, string name); //--- bool SaveNewValues(long ticket); //--- bool Static(CArrayObj *deals); };
1.3. 创建智能交易系统进行测试
所有准备就绪。现在, 开始创建将要在策略测试器中工作的 EA。首先, 定义所应用的指标列表及其参数。为了演示技术, 我选取了以下指标:
- ADX;
- Alligator;
- CCI;
- Chaikin;
- Force Index;
- MACD.
它们当中的每一个, 均要创建三组参数, 在三个时间帧上跟踪数据。
止损和止盈交易必须与 ATR 指标值绑定, 并通过盈利风险比率来设定。
//--- 输入参数 input double Reward_Risk = 1.0; input int ATR_Period = 288; input ENUM_TIMEFRAMES TimeFrame1 = PERIOD_M5; input ENUM_TIMEFRAMES TimeFrame2 = PERIOD_H1; input ENUM_TIMEFRAMES TimeFrame3 = PERIOD_D1; input string s1 = "ADX" ; //--- input uint ADX_Period1 = 14 ; input uint ADX_Period2 = 28 ; input uint ADX_Period3 = 56 ; input string s2 = "Alligator" ; //--- input uint JAW_Period1 = 13 ; input uint JAW_Shift1 = 8 ; input uint TEETH_Period1 = 8 ; input uint TEETH_Shift1 = 5 ; input uint LIPS_Period1 = 5 ; input uint LIPS_Shift1 = 3 ; input uint JAW_Period2 = 26 ; input uint JAW_Shift2 = 16 ; input uint TEETH_Period2 = 16 ; input uint TEETH_Shift2 = 10 ; input uint LIPS_Period2 = 10 ; input uint LIPS_Shift2 = 6 ; input uint JAW_Period3 = 42 ; input uint JAW_Shift3 = 32 ; input uint TEETH_Period3 = 32 ; input uint TEETH_Shift3 = 20 ; input uint LIPS_Period3 = 20 ; input uint LIPS_Shift3 = 12 ; input ENUM_MA_METHOD Alligator_Method = MODE_SMMA ; input ENUM_APPLIED_PRICE Alligator_Price = PRICE_MEDIAN ; input string s5 = "CCI" ; //--- input uint CCI_Period1 = 14 ; input uint CCI_Period2 = 28 ; input uint CCI_Period3 = 56 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input string s6 = "Chaikin" ; //--- input uint Ch_Fast_Period1 = 3 ; input uint Ch_Slow_Period1 = 14 ; input uint Ch_Fast_Period2 = 6 ; input uint Ch_Slow_Period2 = 28 ; input uint Ch_Fast_Period3 = 12 ; input uint Ch_Slow_Period3 = 56 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input string s7 = "Force Index" ; //--- input uint Force_Period1 = 14 ; input uint Force_Period2 = 28 ; input uint Force_Period3 = 56 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input string s8 = "MACD" ; //--- input uint MACD_Fast1 = 12 ; input uint MACD_Slow1 = 26 ; input uint MACD_Signal1 = 9 ; input uint MACD_Fast2 = 24 ; input uint MACD_Slow2 = 52 ; input uint MACD_Signal2 = 18 ; input uint MACD_Fast3 = 48 ; input uint MACD_Slow3 = 104 ; input uint MACD_Signal3 = 36 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ;
在全局变量模块中声明:
- 保存成交类的数组 Deals,
- 处理指标的类实例 IndicatorsStatic,
- 存储 ATR 指标句柄的变量,
- 存储最后已处理柱线 (last_bar) 和最后一笔平仓订单 (last_closed_deal) 时间的两个变量。我们稍后将需要它们, 以便无需在每个分笔报价来临时遍历已平仓位。
在 OnInit 函数中, 执行全局变量和所需指标类的初始化。
int OnInit() { //--- last_bar=0; last_closed_deal=0; //--- Deals = new CArrayObj(); if(CheckPointer(Deals)==POINTER_INVALID) return INIT_FAILED; //--- IndicatorsStatic = new CDealsToIndicators(); if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) return INIT_FAILED; //--- atr=iATR(_Symbol,TimeFrame1,ATR_Period); if(atr==INVALID_HANDLE) return INIT_FAILED; //--- AddIndicators(TimeFrame1); AddIndicators(TimeFrame2); AddIndicators(TimeFrame3); //--- return(INIT_SUCCEEDED); }
我们将在三个不同的时间帧内使用同一套指标。这就是为什么将指标类的初始化放入单独的函数 AddIndicators 是合理的。在其参数中将指定所需的时间帧。
bool AddIndicators(ENUM_TIMEFRAMES timeframe) { if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) { IndicatorsStatic = new CDealsToIndicators(); if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) return false; } string tf_name=StringSubstr(EnumToString(timeframe),7); string name="ADX("+IntegerToString(ADX_Period1)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period1, name)) return false; name="ADX("+IntegerToString(ADX_Period2)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period2, name)) return false; name="ADX("+IntegerToString(ADX_Period3)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period3, name)) return false; name="Alligator("+IntegerToString(JAW_Period1)+","+IntegerToString(TEETH_Period1)+","+IntegerToString(LIPS_Period1)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period1, JAW_Shift1, TEETH_Period1, TEETH_Shift1, LIPS_Period1, LIPS_Shift1, Alligator_Method, Alligator_Price, name)) return false; name="Alligator("+IntegerToString(JAW_Period2)+","+IntegerToString(TEETH_Period2)+","+IntegerToString(LIPS_Period2)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period2, JAW_Shift2, TEETH_Period2, TEETH_Shift2, LIPS_Period2, LIPS_Shift2, Alligator_Method, Alligator_Price, name)) return false; name="Alligator("+IntegerToString(JAW_Period3)+","+IntegerToString(TEETH_Period3)+","+IntegerToString(LIPS_Period3)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period3, JAW_Shift3, TEETH_Period3, TEETH_Shift3, LIPS_Period3, LIPS_Shift3, Alligator_Method, Alligator_Price, name)) return false; name="MACD("+IntegerToString(MACD_Fast1)+","+IntegerToString(MACD_Slow1)+","+IntegerToString(MACD_Signal1)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast1, MACD_Slow1, MACD_Signal1, MACD_Price, name)) return false; name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name)) return false; name="MACD("+IntegerToString(MACD_Fast3)+","+IntegerToString(MACD_Slow3)+","+IntegerToString(MACD_Signal3)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast3, MACD_Slow3, MACD_Signal3, MACD_Price, name)) return false; name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name; int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; name="CCI("+IntegerToString(CCI_Period2)+") "+tf_name; handle = iCCI(_Symbol, timeframe, CCI_Period2, CCI_Price); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iCCI(_Symbol, timeframe, CCI_Period3, CCI_Price); name="CCI("+IntegerToString(CCI_Period3)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iForce(_Symbol, timeframe, Force_Period1, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period1)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iForce(_Symbol, timeframe, Force_Period2, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period2)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period3)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; name="CHO("+IntegerToString(Ch_Slow_Period1)+","+IntegerToString(Ch_Fast_Period1)+") "+tf_name; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period1, Ch_Slow_Period1, Ch_Method, Ch_Volume); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period3, Ch_Slow_Period3, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period3)+","+IntegerToString(Ch_Fast_Period3)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; return true; }
在 OnTick 中执行的操作可分为两个部分: 检查持仓和开新仓。
第一个操作模块在每个分笔报价来临时执行。其中, 所有以前开单的交易可从数组中检索, 并为它们当中的每一笔调用 Tick 函数。它检查持仓是否触发止损和止盈, 必要时按照当前价格平仓并保存产生的利润。为了避免重复检查先前的已平仓交易, 在变量 last_closed_deal 中保存了第一笔未平仓交易之前的交易数量。
void OnTick() { //--- int total=Deals.Total(); CDeal *deal; bool found=false; for(int i=last_closed_deal;i<total;i++) { deal = Deals.At(i); if(CheckPointer(deal)==POINTER_INVALID) continue; if(!found) { if(deal.IsClosed()) { last_closed_deal=i; continue; } else found=true; } deal.Tick(); }
第二个操作模块从检查新柱线出现开始。在每根柱线的开头, 下载最后一个已收盘蜡烛的 ATR 指标值, 根据设定的参数计算止损位和止盈位, 并开虚拟仓位。对于每笔仓位保存指标数据, 并调用我们类中的 SaveNewValues 函数来处理指标。
//--- datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE); datetime cur_time=TimeCurrent(); if(cur_bar==last_bar || (cur_time-cur_bar)>10) return; double atrs[]; if(CopyBuffer(atr,0,1,1,atrs)<=0) return; last_bar=cur_bar; double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); double sl=NormalizeDouble(stops,_Digits); double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); return; }
在 OnTester 中, 收集测试运行的结果并构建用于分析的图表。为此目的, 调用类的静态函数来处理指标。
确保在 OnDeinit 函数中清理内存!
附件中提供了完整的 EA 代码和使用的类。
2. 分析测试结果
那么, 我们已经创建了测试 EA。现在, 我们来分析一下这段区间。当选择一段区间时, 要考虑到它应足够长, 以便提供分析的客观性。另一个对于区间的要求: 不仅要包括单向走势, 还应包括双向趋势走势, 以及盘整 (横盘) 走势。这种方式能够创建一个在任何走势区间均可产生利润的交易策略。我的例子分析了自 2016 年 1 月 1 日至 2017 年 1 月 10 日期间的货币对 EURUSD。
迄今为止, 我们的流程将具有反复特性, 我建议为测试 EA 设置好所有必要参数以后, 保存参数为 set-文件以便未来工作。
每个测试阶段将采用利润/风险比率等于 1/1 和 15/1 运行 2 次。第一次运行, 我们将评估方向性走势的概率, 而第二次 - 运动的力度。
EA 显示了大量的分析图表, 在本文中并未全部提供 - 所有报告均已在附件中提供。在此, 我们只显示图表, 通过这些图表来决定在新策略中将会使用哪个指标。
2.1. 阶段一
正如我们所预期的那样, 第一个测试阶段并没有显示出明确的可盈利区域。但请同时注意力度指数指标。在 M5 时间帧, 交易利润图表与指标值的依赖性在零区域下滑。观察到的这一重要性由这样一个事实所证明, 即这个现象出现在所有参数都参与测试的分析指标图表上。为我们的模板所选择参数, 应具有最明显的表征 (最大回撤)。
我们来放大分析的图表。正如您所看到的, 这个因素的影响在 -0.01 到 0.01 之间。观察到的现象对买入和卖出交易同样适用。
这一观察结果也许是由于在所观测数值范围内缺乏波动性。对于我们的策略, 请确保在此范围内禁止任何开单。
添加这个滤波器到我们的 EA。为此, 首先添加用于存储指标句柄的全局变量。
int force;
至此, 作为滤波器所需的指标已经加入到 EA, 我们就不必再将其挂载到图表上。只需在 AddIndicators 中将其句柄复制到我们的全局变量中即可。但请确认, 不要忘记针对不同时间帧调用这个指标的初始化函数三次。因此, 在复制指标句柄之前, 我们应检查时间帧是否合规。
handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume); if(timeframe==TimeFrame1) force=handle;
现在, 立即将滤波器添加到 OnTick。在建筑图表时请记住, 在分析图表的函数里, 构建数据应被舍入。因此, 当过滤交易的指标值时, 也应初步舍入。
double atrs[]; double force_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0) return; // 加载指标数据时的一些错误 last_bar=cur_bar; double d_Step=_Point*1000; if(MathAbs(NormalizeDouble(force_data[0]/d_Step,0)*d_Step)<=0.01) return; // 力度指标过滤
完整的 EA 代码在本文的附件中提供。
在 EA 中添加滤波器后, 执行第二个测试阶段。在测试之前, 请确保下载先前保存的参数。
2.2. 阶段二
经过多轮 EA 测试, 我关注 MACD 指标。图表上出现了盈利区域。
在盈利/风险比率为 15/1 的图表中, 这些区域更为明确; 这也许能说明这些范围内信号的潜力。
这个滤波器也应被添加到我们的 EA 代码中。添加滤波器的逻辑与阶段一描述中所提供的逻辑类似。
加入全局变量:
int macd;
加入 AddIndicators 函数:
name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name; if(timeframe==TimeFrame1) { if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name, macd)) return false; } else { if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name)) return false; }
加入 OnTick:
double macd_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0) return;
和
double macd_Step=_Point*50; macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step; if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035) { deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035)) { deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); }
将滤波器偏移添加到第三测试阶段之后。
2.3. 阶段三
在阶段三, 我关注 Chaikin 振荡器。在时间帧 D1 上的振荡器分析图表中, 我们看到多头仓位的利润在数值降低的情况下增长, 而在指标值增长的情况下, 空头仓位的利润增长。
在分析盈利/风险比率等于 15/1 的图表时, 我的观测也得到了证实。
将我们的观测添加到 EA 代码中。
加入全局变量:
int cho;
加入 AddIndicators 函数:
handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; if(timeframe==TimeFrame3) cho=handle;
加入 OnTick:
double cho_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0 || CopyBuffer(cho,0,1,2,cho_data)<2) return;
和
if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0) { deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0) { deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); }
继续下面的阶段。
2.4. 阶段四
再次测试 EA 之后, 我的注意力再次被时间帧 D1 所吸引。这一次, 我考察了 CCI 指标。其分析图表显示, 在指标值下降时空头仓位利润增长, 而多头仓位利润增长的情况 - 随着指标值的增长。这一趋势在所有的三个研究期间内都被观察到, 但是当振荡器使用的周期为 14 时, 利润达到了最大值。
在测试中得到的分析图表, 盈利/风险比率等于 15/1 证实了我们的观测。
将这次观测也添加到测试 EA 代码当中。
加入全局变量:
int cci;
至 AddIndicators:
name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name; int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; if(timeframe==TimeFrame3) cci=handle;
至 OnTick:
double cci_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0 || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2) return;
和
if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0) { deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0) { deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); }
所有阶段的完整 EA 代码已在文章的附件中提供。
3. 在选定的信号上创建和测试 EA
完美无极限, 您可以持续分析和增加滤波器来提升策略的盈利因子。但是我相信这四个阶段对技术示范来说足够了。下一步创建的简单 EA, 将在测试器里检验我们的策略。这令我们能够评估自我策略的盈利因子和回撤, 以及余额变化的进度。
在策略中, 我们选用了四个指标制定交易决策, 并利用 ATR 指标设置止损和止盈。因此, 在 EA 输入参数中, 我们应设置指标所需的所有输入信息。在这个阶段, 我们不会创建资金管理, 所有的订单都使用固定交易量。
//--- 输入参数 input double Lot = 0.1 ; input double Reward_Risk = 15.0 ; input ENUM_TIMEFRAMES ATR_TimeFrame = PERIOD_M5 ; input int ATR_Period = 288 ; input string s1 = "CCI" ; //--- input ENUM_TIMEFRAMES CCI_TimeFrame = PERIOD_D1 ; input uint CCI_Period = 14 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input string s2 = "Chaikin" ; //--- input ENUM_TIMEFRAMES Ch_TimeFrame = PERIOD_D1 ; input uint Ch_Fast_Period = 6 ; input uint Ch_Slow_Period = 28 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input string s3 = "Force Index" ; //--- input ENUM_TIMEFRAMES Force_TimeFrame = PERIOD_M5 ; input uint Force_Period = 56 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input string s4 = "MACD" ; //--- input ENUM_TIMEFRAMES MACD_TimeFrame = PERIOD_M5 ; input uint MACD_Fast = 12 ; input uint MACD_Slow = 26 ; input uint MACD_Signal = 9 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ;
在全局变量中声明:
- 执行交易操作的类实例,
- 保存指标句柄的变量,
- 记录最后的处理的柱线和上笔交易日期的辅助变量,
- 存储最大和最小时间帧的变量。
在 OnInit 函数中, 初始化指标并设置变量的初始值。
int OnInit() { //--- last_bar=0; last_deal=0; //--- atr=iATR(_Symbol,ATR_TimeFrame,ATR_Period); if(atr==INVALID_HANDLE) return INIT_FAILED; //--- force=iForce(_Symbol,Force_TimeFrame,Force_Period,Force_Method,Force_Volume); if(force==INVALID_HANDLE) return INIT_FAILED; //--- macd=iMACD(_Symbol,MACD_TimeFrame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price); if(macd==INVALID_HANDLE) return INIT_FAILED; //--- cho=iChaikin(_Symbol,Ch_TimeFrame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume); if(cho==INVALID_HANDLE) return INIT_FAILED; //--- cci=iCCI(_Symbol,CCI_TimeFrame,CCI_Period,CCI_Price); if(cci==INVALID_HANDLE) return INIT_FAILED; //--- MaxPeriod=fmax(Force_TimeFrame,MACD_TimeFrame); MaxPeriod=fmax(MaxPeriod,Ch_TimeFrame); MaxPeriod=fmax(MaxPeriod,CCI_TimeFrame); MinPeriod=fmin(Force_TimeFrame,MACD_TimeFrame); MinPeriod=fmin(MinPeriod,Ch_TimeFrame); MinPeriod=fmin(MinPeriod,CCI_TimeFrame); //--- return(INIT_SUCCEEDED); }
在 OnDeinit 函数中, 关闭使用的指标。
void OnDeinit(const int reason) { //--- if(atr!=INVALID_HANDLE) IndicatorRelease(atr); //--- if(force==INVALID_HANDLE) IndicatorRelease(force); //--- if(macd==INVALID_HANDLE) IndicatorRelease(macd); //--- if(cho==INVALID_HANDLE) IndicatorRelease(cho); //--- if(cci==INVALID_HANDLE) IndicatorRelease(cci); }
主要动作在 OnTick 中执行。在函数的开始, 检查一根新柱线的产生。当新柱线开盘时, 只能在当前柱线的最短时延 (我限制为自开盘 10 秒) , 和最大时延范围内才可开仓。以这种方式, 我限制一个信号只能开一单。
void OnTick() { //--- datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,MinPeriod,SERIES_LASTBAR_DATE); datetime cur_max=(datetime)SeriesInfoInteger(_Symbol,MaxPeriod,SERIES_LASTBAR_DATE); datetime cur_time=TimeCurrent(); if(cur_bar<=last_bar || (cur_time-cur_bar)>10 || cur_max<=last_deal) return;
进而, 获得所用指标的数据。若是指标之一的数据接收错误则退出该函数。
last_bar=cur_bar; double atrs[]; double force_data[]; double macd_data[]; double cho_data[]; double cci_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0 || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2) { return; }
然后, 按照我们的策略检查力度指数值。如果它不能满足我们对滤波器的要求, 退出该函数, 直到下一根柱线开盘。
double force_Step=_Point*1000; if(MathAbs(NormalizeDouble(force_data[0]/force_Step,0)*force_Step)<=0.01) return;
到下一个阶段, 检查多头开仓信号。如果出现正信号, 则检查是否已有持仓。若有且为空头, 平仓。如果有多头持仓且处于亏损状态, 则忽略该信号并退出该函数。
之后, 计算新仓位的参数并发送订单。
空头持仓执行相同的操作。
double macd_Step=_Point*50; macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step; if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0) { if(PositionSelect(_Symbol)) { switch((int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_BUY: if(PositionGetDouble(POSITION_PROFIT)<=0) return; break; case POSITION_TYPE_SELL: Trade.PositionClose(_Symbol); break; } } last_deal=cur_max; double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double sl=NormalizeDouble(stops,_Digits); double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); double SL=NormalizeDouble(bid-sl,_Digits); double TP=NormalizeDouble(ask+tp,_Digits); if(!Trade.Buy(Lot,_Symbol,ask,SL,TP,"New Strategy")) Print("Error of open BUY ORDER "+Trade.ResultComment()); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0) { if(PositionSelect(_Symbol)) { switch((int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_SELL: if(PositionGetDouble(POSITION_PROFIT)<=0) return; break; case POSITION_TYPE_BUY: Trade.PositionClose(_Symbol); break; } } last_deal=cur_max; double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double sl=NormalizeDouble(stops,_Digits); double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); double SL=NormalizeDouble(ask+sl,_Digits); double TP=NormalizeDouble(bid-tp,_Digits); if(!Trade.Sell(Lot,_Symbol,bid,SL,TP,"New Strategy")) Print("Error of open SELL ORDER "+Trade.ResultComment()); } return; }
完整的 EA 代码在附件中提供。
执行 EA 之后, 我们可以测试我们的策略。为了避免 "策略区间匹配", 扩展测试的时间: 策略测试自 2015 年 1 月 1 日到 2017 年 1 月 12 日。测试的初始资本是 10,000 美元, 交易规模 - 1 手。
根据测试结果, EA 在最大余额回撤12.4%, 以及净值回撤 23.8% 情况下获利 74.8%。共完成了 44 笔交易 (空头 22 笔, 多头 22 笔)。获利仓位的份额占 18.2%, 空头和多头相等。如此低的盈利百分比是因为所规定的预期盈利/风险比值较高 (15:1), 这为进一步改善策略留下了空间。
结束语
本文演示利用将入场点解析为指标的方法, "用一张白纸" 创建交易策略的技术。由此产生的策略能够产生长期的利润, 三年来的测试证明了这一点。尽管使用标准 MetaTrader 软件包创建策略指标时, 交易信号与文献中描述的指标相差甚远。所建议的技术能够创造性地在交易策略中使用指标, 且不受限于所采用的指标。您可以使用任何用户指标和变体来评估其信号的质量。
参考资料
- 解析入场点为指标
- HTML 格式的图表和示意图
本文中使用的程序:
# |
名称 |
类型 |
描述 |
---|---|---|---|
New_Strategy_Gizlyk.zip | |||
1 | NewStrategy1.mq5 | EA | 实现第一阶段策略创建的 EA |
2 | NewStrategy2.mq5 | EA | 实现第二阶段策略创建的 EA |
3 | NewStrategy3.mq5 | EA | 实现第三阶段策略创建的 EA |
4 | NewStrategy4.mq5 | EA |
实现第四阶段策略创建的 EA |
5 | NewStrategy_Final.mq5 | EA |
策略测试 EA |
6 | DealsToIndicators.mqh | 类库 | 处理指标类的类 |
7 | Deal.mqh | 类库 | 保存交易信息的类 |
8 | Value.mqh | 类库 | 保存指标缓冲区状态数据的类 |
9 | OneBufferArray.mqh | 类库 | 保存缓冲区区指标历史数据的类 |
10 | StaticOneBuffer.mqh | 类库 | 收集和分析单缓冲区指标统计数据的类 |
11 | ADXValue.mqh | 类库 | 保存 ADX 指标状态数据的类 |
12 | ADX.mqh | 类库 | 保存 ADX 指标历史数据的类 |
13 | StaticADX.mqh | 类库 | 收集和分析 ADX 指标统计数据的类 |
14 | AlligatorValue.mqh | 类库 | 保存 Alligator 指标状态数据的类 |
15 | Alligator.mqh | 类库 | 保存 Alligator 指标历史数据的类 |
16 | StaticAlligator.mqh | 类库 | 收集和分析 Alligator 指标统计数据的类 |
17 | MACDValue.mqh | 类库 | 保存 MACD 指标状态数据的类 |
18 | MACD.mqh | 类库 | 保存 MACD 指标历史数据的类 |
19 | StaticMACD.mqh | 类库 | 收集和分析 MACD 指标统计数据的类 |
Common.zip | |||
20 | NewStrategy1_Report_1to1_2016-17.html | 测试报告文件 | 第一阶段制定的策略分析图表, 盈利/风险 = 1/1 |
21 | NewStrategy1_Report_15to1_2016-17.html | 测试报告文件 | 第一阶段制定的策略分析图表, 盈利/风险 = 15/1 |
22 | NewStrategy2_Report_1to1_2016-17.html | 测试报告文件 | 第二阶段制定的策略分析图表, 盈利/风险 = 1/1 |
23 | NewStrategy2_Report_15to1_2016-17.html | 测试报告文件 | 第二阶段制定的策略分析图表, 盈利/风险 = 15/1 |
24 | NewStrategy3_Report_1to1_2016-17.html | 测试报告文件 | 第三阶段制定的策略分析图表, 盈利/风险 = 1/1 |
25 | NewStrategy3_Report_15to1_2016-17.html | 测试报告文件 | 第三阶段制定的策略分析图表, 盈利/风险 = 15/1 |
26 | NewStrategy4_Report_1to1_2016-17.html | 测试报告文件 | 第四阶段制定的策略分析图表, 盈利/风险 = 1/1 |
27 | NewStrategy4_Report_15to1_2016-17.html | 测试报告文件 | 第四阶段制定的策略分析图表, 盈利/风险 = 15/1 |
28 | NewStrategy_Final_Report.html | 测试报告文件 | 策略测试报告 |