简介
所有的外汇交易者或多或少都接触过价格行为. 它不仅仅是一项图表分析技术, 而是包含了定义未来价格可能走向的整个系统. 在本文中, 我们将详细研究内含柱模式, 并且会基于该模式开发一个EA交易以跟踪内含柱信息及进行交易.
关于价格行为
价格行为是一种非指标的价格移动侦测方法, 它可以使用简单或者复杂的模式, 也可以使用辅助的图表元件(例如水平线, 垂直线, 趋势线, 斐波那契水平, 支撑/阻力水平等等).
乍一看来, 此方法貌似复杂, 但事实上并非如此. 这种方法现已日益流行, 因为和使用技术指标的方法相比较, 它的优势显而易见.
内含柱
内含柱也就是某个柱的柱体以及引线都包含在其前一个柱(母柱)的范围之内. 内含柱的最高价比其母柱的最高价要低, 而最低价比其母柱最低价要高. 母柱和内柱构成了潜在的进入市场的模式.
这是一个两面性的模式, 因为它可能指出趋势的反转或者持续.
图 1. 内含柱
图 2. 内含柱模式布局
内含柱规则:
内含柱模式在更高时间框架内更有意义, 例如 H4 或 D1.
此模式可能暗示趋势的反转, 也可能暗示趋势的持续.
使用额外的图形分析工具, 包括趋势线, 支撑/阻力水平, 斐波那契水平以及其他的价格行为模式等等, 可以获得更加清晰的进场信号.
使用挂单来避免过早或者错误地进入市场.
不要在平缓的市场中重复使用内含柱作为进场信号.
图 3. 在 GBPUSD D1 图表上定义真正的内含柱
把这些都记住后, 让我们尝试定义一个真正的内含柱. 在上图中, 我们可以看到在价格陡然下降之后出现一个牛势柱形. 但是, 这个柱整个位于前一个柱范围之内. 另外, 此柱也位于支撑水平之上, 进一步确认了该模式. 第三点确认是这并非平缓的市场环境. 因为此模式满足了这些规则, 它可以被确认为真实.
定义入场点并设置止损单
就这样, 我们已经在图表上发现了一个真正的内含柱 (图 3). 我们应该怎样进入市场?还有我们在哪里设置订单的止损位呢?让我们看图4.
图 4. 设置止损买入 订单并设置止损
首先, 我们应该使用上面的例子来考虑止损水平的设置:
设置一个比母柱最高价略高的止损买入挂单(只需要高几个点, 用于确认).
设置一个比支撑位, 也就是母柱的最低价略低的一个止损水平. 这是一个额外的保护, 以防挂单被触发以后, 价格又回到支撑水平而反弹后再向正确方向移动.
再在略低于阻力位的地方设置获利水平.
别忘了, 内含柱可能跟随着趋势的反转或者持续, 所以我们还要设置一个止损卖出订单.
图 5. 设置止损卖出订单及止损
首先, 我们应该使用上面的例子来考虑止损水平的设置:
设置一个比母柱最低价略低的止损卖出订单(只要低几个点, 以作确认).
在母柱最高价上方设置止损水平.
在最近的支撑水平略高处设置获利水平.
基于内含柱交易开发一个EA交易
现在我们已经了解了定义内含柱的所有规则, 进入市场及设置止损单, 我们最终可以使用内含柱模式来实现对应的EA交易了.
从MetaTrader 4终端开启 MetaEditor 并创建一个新的 EA 交易 (相信我不必在此方面涉及过多, 网站上已经有了很多如何创建EA交易的信息了). 在这个阶段把所有的参数设置成空. 您可以按照您的喜好对它们进行命名. 结果代码看起来如下:
//++ //| InsideBar.mq4 | //| Copyright 2015, Iglakov Dmitry. | //| cjdmitri@gmail.com | //++ #property copyright "Copyright 2015, Iglakov Dmitry." #property link "cjdmitri@gmail.com" #property version "1.00" #property strict //++ //| EA 初始化函数 | //++ int OnInit() { //- //- return(INIT_SUCCEEDED); } //++ //| EA 终止化函数 | //++ void OnDeinit(const int reason) { //- } //++ //| EA 订单处理函数 | //++ void OnTick() { //- } //++
把模式转换为MQL4算法
当我们创建了EA之后, 我们需要在柱关闭后定义一个内含柱. 为此, 我们会引入新的变量并为它们赋值. 参照以下代码:
//++ //| InsideBar.mq4 | //| Copyright 2015, Iglakov Dmitry. | //| cjdmitri@gmail.com | //++ #property copyright "Copyright 2015, Iglakov Dmitry." #property link "cjdmitri@gmail.com" #property version "1.00" #property strict double open1,//第一个柱的开盘价 open2, //第二个柱的开盘价 close1, //第一个柱的收盘价 close2, //第二个柱的收盘价 low1, //第一个柱的最低价 low2, //第二个柱的最低价 high1, //第一个柱的最高价 high2; //第二个柱的最高价 //++ //| EA 初始化函数 | //++ int OnInit() { return(INIT_SUCCEEDED); } //++ //| EA 终止化函数 | //++ void OnDeinit(const int reason) { } //++ //| EA 订单处理函数 | //++ void OnTick() { //- 定义所需的柱的价格 open1 = NormalizeDouble(iOpen(Symbol(), Period(), 1), Digits); open2 = NormalizeDouble(iOpen(Symbol(), Period(), 2), Digits); close1 = NormalizeDouble(iClose(Symbol(), Period(), 1), Digits); close2 = NormalizeDouble(iClose(Symbol(), Period(), 2), Digits); low1 = NormalizeDouble(iLow(Symbol(), Period(), 1), Digits); low2 = NormalizeDouble(iLow(Symbol(), Period(), 2), Digits); high1 = NormalizeDouble(iHigh(Symbol(), Period(), 1), Digits); high2 = NormalizeDouble(iHigh(Symbol(), Period(), 2), Digits); } //++
作为例子, 让我们考虑母柱是熊势柱 (柱 2), 而内含柱为牛势柱 (柱 1)的状况. 让我们在 OnTick()函数体中增加一系列条件:
void OnTick() { //- 定义所需的柱的价格 open1 = NormalizeDouble(iOpen(Symbol(), Period(), 1), Digits); open2 = NormalizeDouble(iOpen(Symbol(), Period(), 2), Digits); close1 = NormalizeDouble(iClose(Symbol(), Period(), 1), Digits); close2 = NormalizeDouble(iClose(Symbol(), Period(), 2), Digits); low1 = NormalizeDouble(iLow(Symbol(), Period(), 1), Digits); low2 = NormalizeDouble(iLow(Symbol(), Period(), 2), Digits); high1 = NormalizeDouble(iHigh(Symbol(), Period(), 1), Digits); high2 = NormalizeDouble(iHigh(Symbol(), Period(), 2), Digits); //- 如果第二个柱为熊势而第一个柱为牛势 if(open2>close2 && //第二个柱为牛势 close1>open1 && //第一个柱是熊势 high2>high1 && //第二柱的最高价高于第一个柱的最高价 open2>close1 && //第二柱的开盘价高于第一柱的收盘价 low2<low1) //第二柱的最低价低于第一柱的收盘价 { //- 我们已经列出了第一柱内含于第二柱的所有条件 } }
创建可定制的变量: 止损单, 点差, 订单过期时间, EA 幻数, 交易手数. 止损值可以不用, 因为它决定于内含柱的原则.
为这些变量增加代码中的局部变量.
止损订单由距离柱价位的某个距离设置. 为了实现这一点, 增加Interval变量, 作为止损订单和最高/最低价的距离以及挂单的水平.
增加timeBarInside变量以避免在此模式上重复开启订单.
增加bar2size变量以确认母柱足够大, 是当前市场并不平缓的很好的标记.
结果我们可以获得如下代码:
//++ //| InsideBar.mq4 | //| Copyright 2015, Iglakov Dmitry. | //| cjdmitri@gmail.com | //++ #property copyright "Copyright 2015, Iglakov Dmitry." #property link "cjdmitri@gmail.com" #property version "1.00" #property strict extern int interval = 20; //距离 extern double lot = 0.1; //手数 extern int TP = 300; //获利 extern int magic = 555124; //幻数 extern int slippage = 2; //点差 extern int ExpDate = 48; //订单过期小时数 extern int bar2size = 800; //柱 2 的大小 double buyPrice,//定义止损买入价位 buyTP, //止损买入单的获利价位 buySL, //止损买入单的止损价位 sellPrice, //定义止损卖出价位 sellTP, //止损卖出单的获利价位 sellSL; //止损卖出单的止损价位 double open1,//第一个柱的开盘价 open2, //第二个柱的开盘价 close1, //第一个柱的收盘价 close2, //第二个柱的收盘价 low1, //第一个柱的最低价 low2, //第二个柱的最低价 high1, //第一个柱的最高价 high2; //第二个柱的最高价 datetime _ExpDate=0; //定义订单过期时间的局部变量 double _bar2size; datetime timeBarInside; //内含柱开启的时间, 用于防止重复开单 //++ //| EA 初始化函数 | //++ int OnInit() { return(INIT_SUCCEEDED); } //++ //| EA 终止化函数 | //++ void OnDeinit(const int reason) { } //++ //| EA 订单处理函数 | //++ void OnTick() { double _bid = NormalizeDouble(MarketInfo(Symbol(), MODE_BID), Digits); //定义低价 double _ask = NormalizeDouble(MarketInfo(Symbol(), MODE_ASK), Digits); //定义高价 double _point = MarketInfo(Symbol(), MODE_POINT); //- 定义所需的柱的价格 open1 = NormalizeDouble(iOpen(Symbol(), Period(), 1), Digits); open2 = NormalizeDouble(iOpen(Symbol(), Period(), 2), Digits); close1 = NormalizeDouble(iClose(Symbol(), Period(), 1), Digits); close2 = NormalizeDouble(iClose(Symbol(), Period(), 2), Digits); low1 = NormalizeDouble(iLow(Symbol(), Period(), 1), Digits); low2 = NormalizeDouble(iLow(Symbol(), Period(), 2), Digits); high1 = NormalizeDouble(iHigh(Symbol(), Period(), 1), Digits); high2 = NormalizeDouble(iHigh(Symbol(), Period(), 2), Digits); //- _bar2size=NormalizeDouble(((high2-low2)/_point),0); //- 如果第二个柱为熊势而第一个柱为牛势 if(timeBarInside!=iTime(Symbol(),Period(),1) && //此模式尚未开启订单 _bar2size>bar2size && //第二个柱足够大, 说明市场并不平缓 open2>close2 && //第二个柱为牛势 close1>open1 && //第一个柱是熊势 high2>high1 && //第二柱的最高价高于第一个柱的最高价 open2>close1 && //第二个柱的开盘价高于第一个柱的收盘价 low2<low1) //第二个柱的最低价低于第一个柱的最低价 { //- 我们已经列出了第一柱内含于第二柱的所有条件 timeBarInside=iTime(Symbol(),Period(),1); //表明已经为此模式开启订单 } } //++
定义止损订单水平
现在所有的准备工作都已完成, 我们只需要定义止损订单水平和订单价格了. 并且, 别忘了订单过期时间的计算.
让我们把以下代码加入OnTick()函数体:
buyPrice=NormalizeDouble(high2+interval*_point,Digits); //根据距离定义订单价格 buySL=NormalizeDouble(low2-interval*_point,Digits); //根据距离定义止损 buyTP=NormalizeDouble(buyPrice+TP*_point,Digits); //定义获利价位 _ExpDate=TimeCurrent()+ExpDate*60*60; //计算挂单过期时间 sellPrice=NormalizeDouble(low2-interval*_point,Digits); sellSL=NormalizeDouble(high2+interval*_point,Digits); sellTP=NormalizeDouble(sellPrice-TP*_point,Digits);
执行错误的修正
如果您开发过EA交易, 您也许知道当关闭订单, 设置订单, 包括等待时间, 不正确的止损值等等经常可能引发错误. 为了消除此类错误, 我们应该使用简单的内部基本错误处理写一个独立的函数.
//++ //| 开启和设置订单的函数 | //| symbol - 订单的交易品种 | //| cmd - 交易类型 (可能等于任何交易类型值). | //| volume - 手数. | //| price - 开单价格. | //| slippage - 市场买入或者卖出订单的最大点差. | //| stoploss - 当亏损达到某种水平关闭仓位的价位 (如果不设止损则为0). | //| takeprofit - 当获利达到某种水平关闭仓位的价位 (如果不设获利则为0). | //| comment - 订单注释. 注释的最后部分可能被交易服务器修改. | //| magic - 订单幻数. 可以使用用户自定义的ID. | //| expiration - 挂单的过期时间. | //| arrow_color - 图表上开启订单的箭头颜色. 如果此参数空缺或者等于CLR_NONE, | //| 开单的箭头在图表上则不做显示. | //++ int OrderOpenF(string OO_symbol, int OO_cmd, double OO_volume, double OO_price, int OO_slippage, double OO_stoploss, double OO_takeprofit, string OO_comment, int OO_magic, datetime OO_expiration, color OO_arrow_color) { int result = -1; //开启订单的结果 int Error = 0; //开启订单出错编号 int attempt = 0; //已经进行的尝试次数 int attemptMax = 3; //最大尝试次数 bool exit_loop = false; //退出循环 string lang=TerminalInfoString(TERMINAL_LANGUAGE); //交易终端语言, 为了定义消息的语言 和double stopllvl=NormalizeDouble(MarketInfo(OO_symbol,MODE_STOPLEVEL)*MarketInfo(OO_symbol,MODE_POINT),Digits); //最小止损/获利水平点数 //本模块提供了安全的订单开启功能. //- 检查买入订单 if(OO_cmd==OP_BUY || OO_cmd==OP_BUYLIMIT || OO_cmd==OP_BUYSTOP) { double tp = (OO_takeprofit - OO_price)/MarketInfo(OO_symbol, MODE_POINT); double sl = (OO_price - OO_stoploss)/MarketInfo(OO_symbol, MODE_POINT); if(tp>0 && tp<=stopllvl) { OO_takeprofit=OO_price+stopllvl+2*MarketInfo(OO_symbol,MODE_POINT); } if(sl>0 && sl<=stopllvl) { OO_stoploss=OO_price -(stopllvl+2*MarketInfo(OO_symbol,MODE_POINT)); } } //- 检查卖出订单 if(OO_cmd==OP_SELL || OO_cmd==OP_SELLLIMIT || OO_cmd==OP_SELLSTOP) { double tp = (OO_price - OO_takeprofit)/MarketInfo(OO_symbol, MODE_POINT); double sl = (OO_stoploss - OO_price)/MarketInfo(OO_symbol, MODE_POINT); if(tp>0 && tp<=stopllvl) { OO_takeprofit=OO_price -(stopllvl+2*MarketInfo(OO_symbol,MODE_POINT)); } if(sl>0 && sl<=stopllvl) { OO_stoploss=OO_price+stopllvl+2*MarketInfo(OO_symbol,MODE_POINT); } } //- while 循环 while(!exit_loop) { result=OrderSend(OO_symbol,OO_cmd,OO_volume,OO_price,OO_slippage,OO_stoploss,OO_takeprofit,OO_comment,OO_magic,OO_expiration,OO_arrow_color); //尝试使用指定的参数下单 //- 如果开启订单出错 if(result<0) { Error = GetLastError(); //给错误码赋值 switch(Error) //枚举错误 { //关闭订单出错的枚举, 并尝试修复错误 case 2: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 Sleep(3000); //延迟3秒 RefreshRates(); break; //退出 switch } if(attempt==attemptMax) { attempt=0; //把尝试次数重设为0 exit_loop = true; //退出 while break; //退出 switch } case 3: RefreshRates(); exit_loop = true; //退出 while break; //退出 switch case 4: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 Sleep(3000); //延迟3秒 RefreshRates(); break; //退出 switch } if(attempt==attemptMax) { attempt = 0; //把尝试次数重设为0 exit_loop = true; //退出 while break; //退出 switch } case 5: exit_loop = true; //退出 while break; //退出 switch case 6: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 Sleep(5000); //延迟5秒 break; //退出 switch } if(attempt==attemptMax) { attempt = 0; //把尝试次数重设为0 exit_loop = true; //退出 while break; //退出 switch } case 8: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 Sleep(7000); //延迟7秒 break; //退出 switch } if(attempt==attemptMax) { attempt = 0; //把尝试次数重设为0 exit_loop = true; //退出 while break; //退出 switch } case 64: exit_loop = true; //退出 while break; //退出 switch case 65: exit_loop = true; //退出 while break; //退出 switch case 128: Sleep(3000); RefreshRates(); continue; //退出 switch case 129: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 Sleep(3000); //延迟3秒 RefreshRates(); break; //退出 switch } if(attempt==attemptMax) { attempt = 0; //把尝试次数重设为0 exit_loop = true; //退出 while break; //退出 switch } case 130: exit_loop=true; //退出 while break; case 131: exit_loop = true; //退出 while break; //退出 switch case 132: Sleep(10000); //延迟10秒 RefreshRates(); //更新数据 //exit_loop = true; //退出 while break; //退出 switch case 133: exit_loop=true; //退出 while break; //退出 switch case 134: exit_loop=true; //退出 while break; //退出 switch case 135: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 RefreshRates(); break; //退出 switch } if(attempt==attemptMax) { attempt = 0; //把尝试次数设为0 exit_loop = true; //退出 while break; //退出 switch } case 136: if(attempt<attemptMax) { attempt=attempt+1; //再多尝试一次 RefreshRates(); break; //退出 switch } if(attempt==attemptMax) { attempt = 0; //把尝试次数设为0 exit_loop = true; //退出 while break; //退出 switch } case 137: if(attempt<attemptMax) { attempt=attempt+1; Sleep(2000); RefreshRates(); break; } if(attempt==attemptMax) { attempt=0; exit_loop=true; break; } case 138: if(attempt<attemptMax) { attempt=attempt+1; Sleep(1000); RefreshRates(); break; } if(attempt==attemptMax) { attempt=0; exit_loop=true; break; } case 139: exit_loop=true; break; case 141: Sleep(5000); exit_loop=true; break; case 145: exit_loop=true; break; case 146: if(attempt<attemptMax) { attempt=attempt+1; Sleep(2000); RefreshRates(); break; } if(attempt==attemptMax) { attempt=0; exit_loop=true; break; } case 147: if(attempt<attemptMax) { attempt=attempt+1; OO_expiration=0; break; } if(attempt==attemptMax) { attempt=0; exit_loop=true; break; } case 148: exit_loop=true; break; default: Print("错误: ",Error); exit_loop=true; //退出 while break; //其他选项 } } //- 如果没有发现出错 else { if(lang == "Russian") {Print("Ордер успешно открыт. ", result);} if(lang == "English") {Print("The order is successfully opened.", result);} Error = 0; //把错误码重设为0 break; //退出 while //errorCount =0; //把尝试次数设为0 } } return(result); } //++
结果我们可以获得如下代码:
//++ //| InsideBar.mq4 | //| Copyright 2015, Iglakov Dmitry. | //| cjdmitri@gmail.com | //++ #property copyright "Copyright 2015, Iglakov Dmitry." #property link "cjdmitri@gmail.com" #property version "1.00" #property strict extern int interval = 20; //距离 extern double lot = 0.1; //手数 extern int TP = 300; //获利 extern int magic = 555124; //幻数 extern int slippage = 2; //点差 extern int ExpDate = 48; //订单过期小时数 extern int bar2size = 800; //柱 2 的大小 double buyPrice,//定义止损买入价位 buyTP, //止损买入单的获利价位 buySL, //止损买入单的止损价位 sellPrice, //定义止损卖出价位 sellTP, //止损卖出单的获利价位 sellSL; //止损卖出单的止损价位 double open1,//第一个柱的开盘价 open2, //第二个柱的开盘价 close1, //第一个柱的收盘价 close2, //第二个柱的收盘价 low1, //第一个柱的最低价 low2, //第二个柱的最低价 high1, //第一个柱的最高价 high2; //第二个柱的最高价 datetime _ExpDate=0; //定义订单过期时间的局部变量 double _bar2size; datetime timeBarInside; //内含柱订单开启的柱的时间, 防止重复下单 //++ //| EA 初始化函数 | //++ int OnInit() { return(INIT_SUCCEEDED); } //++ //| EA 终止化函数 | //++ void OnDeinit(const int reason) { } //++ //| EA 订单处理函数 | //++ void OnTick() { double _bid = NormalizeDouble(MarketInfo(Symbol(), MODE_BID), Digits); //定义低价 double _ask = NormalizeDouble(MarketInfo(Symbol(), MODE_ASK), Digits); //定义高价 double _point = MarketInfo(Symbol(), MODE_POINT); //- 定义所需的柱的价格 open1 = NormalizeDouble(iOpen(Symbol(), Period(), 1), Digits); open2 = NormalizeDouble(iOpen(Symbol(), Period(), 2), Digits); close1 = NormalizeDouble(iClose(Symbol(), Period(), 1), Digits); close2 = NormalizeDouble(iClose(Symbol(), Period(), 2), Digits); low1 = NormalizeDouble(iLow(Symbol(), Period(), 1), Digits); low2 = NormalizeDouble(iLow(Symbol(), Period(), 2), Digits); high1 = NormalizeDouble(iHigh(Symbol(), Period(), 1), Digits); high2 = NormalizeDouble(iHigh(Symbol(), Period(), 2), Digits); //- _bar2size=NormalizeDouble(((high2-low2)/_point),0); //- 如果第二个柱为熊势而第一个柱为牛势 if(timeBarInside!=iTime(Symbol(),Period(),1) && //此模式尚未开启订单 _bar2size>bar2size && //第二个柱足够大, 说明市场并不平缓 open2>close2 && //第二个柱为牛势 close1>open1 && //第一个柱是熊势 high2>high1 && //第二柱的最高价高于第一个柱的最高价 open2>close1 && //第二个柱的开盘价高于第一个柱的收盘价 low2<low1) //第二个柱的最低价低于第一个柱的最低价 { buyPrice=NormalizeDouble(high2+interval*_point,Digits); //根据间隔定义的订单价格 buySL=NormalizeDouble(low2-interval*_point,Digits); //根据间隔定义的止损价格 buyTP=NormalizeDouble(buyPrice+TP*_point,Digits); //定义获利价位 _ExpDate=TimeCurrent()+ExpDate*60*60; //挂单过期时间的计算 sellPrice=NormalizeDouble(low2-interval*_point,Digits); sellSL=NormalizeDouble(high2+interval*_point,Digits); sellTP=NormalizeDouble(sellPrice-TP*_point,Digits); OrderOpenF(Symbol(),OP_BUYSTOP,lot,buyPrice,slippage,buySL,buyTP,NULL,magic,_ExpDate,Blue); OrderOpenF(Symbol(),OP_SELLSTOP,lot,sellPrice,slippage,sellSL,sellTP,NULL,magic,_ExpDate,Blue); //- we h*e listed all the conditions defining that the first bar is completely within the second one timeBarInside=iTime(Symbol(),Period(),1); //指出此模式已经下单 } } //++
现在让我们进行编译并在记录中检查错误信息.
测试EA交易
现在是时候测试我们的EA交易了. 让我们运行策略测试期并设置输入参数. 我设置了如下的参数:
图 6. 测试的输入参数
选择一个交易品种 (在我的例子中是 CADJPY ).
请确认使用"每一订单(Every tick)"模式, 并且定义测试将在历史数据上进行. 我已经选择了2014年整年的数据.
设置的时间框架是D1.
运行测试.
测试结束后检查记录. 我们可以看到, 过程中没有错误.
以下是EA交易测试的日志:
图 7. EA 交易测试日志
确认没有出错后优化EA交易.
优化
我选择了如下的参数进行优化:
图 8. 优化参数
图 9. 优化设置
就这样, 我们就拥有了可以使用的EA交易.
优化和测试结果
图 10. 测试结果
图 11. 测试结果图
结论
我们已经开发了可用的基于内含柱交易的 EA 交易.
我们已经确认, 即使不使用额外的进场过滤器价格行为模式也是可行的.
没有使用特殊的技巧(例如马丁格尔或者平均).
通过止损单的正确设置, 回撤已经被减到最小.
没有使用技术指标. 此交易机器人仅仅依赖于读取基本图表.
感谢您的阅读!我希望本文会有所帮助.