
利用解析入场点为指标的技术创建新的交易策略
引言
众所周知, 只有 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 软件包创建策略指标时, 交易信号与文献中描述的指标相差甚远。所建议的技术能够创造性地在交易策略中使用指标, 且不受限于所采用的指标。您可以使用任何用户指标和变体来评估其信号的质量。
参考资料
本文中使用的程序:
# |
名称 |
类型 |
描述 |
---|---|---|---|
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 | 测试报告文件 | 策略测试报告 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/4192
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



有趣的文章
谢谢。
文章很有意思,谢谢。
良好的历史价格数据对分析非常重要。您从哪里获得历史价格数据的?
你好、
我非常喜欢这篇文章。我相信回溯测试 非常重要。但有几个因素一直在我脑子里打转。
我看不出建仓时间有多长。初始资本(抽象单位)是多少?设定的头寸大小是多少?
这将增加策略的成熟度,因为我提出的问题会直接影响任何策略的潜力--佣金。
每次当日滚动时,您都要根据头寸大小支付佣金。
1000 或 10000 的利润可能是巨大的,也可能是无法实现的,这取决于起始资金。
请您对此进行反思。也许我在文章中漏掉了。