MQL5源码:智能交易脚本EA结构解读
2016-04-12 18:29
447 查看
EA是MQL中的自动交易脚本,可以通过编程控制计算机自动进行交易,节省人工盯盘的精力,以及提高执行效率。
本文以经典的均线交易系统解读MQL5交易脚本EA的结构。
真是非常简单的均线交易系统呢。
使用
在
这里有4个参数,分别为:
最大风险比率(默认2%)(用于风控)
减小因子(默认为3)(用于风控)
均线周期(默认为12)
均线位移(默认为6)
具体是什么意思,还要结合下面的逻辑看。
Ext是Expert的意思,就是EA的E。
CTrade对象来自于
Handle 是句柄,把手的意思,在MQL中,通常指指标的引用。
在这里,EA需要使用一个均线的指标,所以需要存储一个指标的对象,指标在MQL中以句柄的形式存储,与操作系统的PID的机制类似,也类似一个地址(指针)。
Hedging是避险的意思,在这里是风险控制,也就是仓位控制。
ExtHedging就是一个开关,是否进行仓位控制,默认是关闭的,但是它会在程序中随时打开(因为如果它并不能随时打开,那么应该设置为 input 属性,由外界给出)。
可以看到,全局变量管理更像是在管理状态机。
在MQL中,使用OnInit函数来控制初始化,它在其他的事件函数开始之前被执行。
我不得不吐槽一下这诡异的MQL自带缩进,简直不忍直视,啊这不是重点。
函数的签名
是不是有点像
在函数的开始,首先对ExtHedging进行了操作。
AccountInfoInteger函数是用于“获取用户账户信息”的,它会根据传入的枚举常量,多态地返回各种值。在这里,是查询用户的保证金模式(Margin:保证金)。当保证金模式为
接下来设置一下 CTrade对象 ExtTrade的一些属性
再是获取一下均线指标的数据,利用iMA函数
iMA函数的参数在官方的手册上都可以查,
这里有很多的常量
在下也不知道为什么要默认延迟6个K线进行交易
当然,这个ExtHandle的赋值是有可能出现失败的,比如当传入的参数不合法的时候(总之就是指标内部出现错误的时候),ExtHandle会被赋值为
这样可以提升代码的健壮性。
如果想使用自定义指标,请参考 iCustom函数
在初始化结束的时候,如果一切正常,应该返回
逻辑很简单,但涉及了三个尚未定义的函数,在这里先从语义上理解一下:
因此这个逻辑非常清晰:如果现在有仓位就看看能不能平仓了;否则看看是不是能开仓了。
我个人对这些函数的命名持保留态度:觉得不是很便于理解。
position有仓位的意思,select有查询的意思。
其中有几个系统的函数需要说明一下:
现在,结合整个逻辑,可以理解
如果不考虑仓位控制,即一次只能下一单,不能加仓或者减仓,那么完全可以直接使用
因此,在遍历仓位列表的时候,需要严格匹配,必须是相同的品种而且魔数是一致的(比起魔数我更喜欢叫它暗号)。尽管是遍历,但其效率在一般的情况下(只交易一种品种),第一次就能够跳出循环,实际上是效率很高的。
如果只是测试交易信号的准确程度,不必大费周章,直接使用
因为在MQL5中没有全局close[]数组了,要接入时间序列的方式就要使用
CopyRates函数获取价格时间序列的函数,参数列表为:品种、周期、开始点、数量、目标。用自然语义解释的话就是,把某品种某周期从某处开始一共某数量的价格序列复制到目标数组中。
开始点的0表示最新的,而不是最早的,即是时间逆序的。
获取两个价格,是因为“上穿”与“下穿”的实现至少需要两个柱。
如果只想用收盘价还可以用
MqlRates中有一个成员 tick_volume 表示订单成交量(跳动量),即该时期内,订单成交造成的价格变动数。如果只想在新柱
只需要屏蔽之后的订单即可。
这里我们只需要均线的一个值即可,之前ExtHandle已经获得过均线句柄了,现在可以直接去拷贝数据了:
在这里,是指将ExtHandle句柄对应的指标的0号缓冲区的数据,从0处(最新的位置),拷贝1个到ma数组里。
处理异常的方式同上
一般的做法是默认为
代码写的很难看,但逻辑非常清晰,表达了上穿均线买入,下穿均线卖出的逻辑。
确定了仓位就可以开始激动人心的下单环节了!
根据之前仓位的方向来下单,如果是
里面还有一些更严格的检查:
(在这里,如果不超过100柱的数据,数据会被认为是不可靠的,跳过交易——当然,不一定100,也可以是50或者其他数字,但应该大于指标的计算周期)
CTrade对象的PositionOpen方法提供了开仓的功能,其参数列表为:品种、方向、数量、索价、止损、止盈,还有一个默认为空的注释。自然语义化为:在某品种的某方向以某索价开一个某数量的仓,设置止损位与止盈位,并注释此订单。
在这里,在当前品种上按照给定方向开一个
当止损/止盈为0时表示不止损/止盈。
到底开多少的仓位呢?用TradeSizeOptimized函数来控制(仓位控制),下文会提到。
平仓的逻辑是,
逻辑还是很简单的……获取完数据,再检测一下现在是什么仓位,对应地检查一下平仓条件,如果符合了并且现在系统允许交易而且柱数够多(这个其实是废话),就平仓。
平仓使用CTrade对象的PositionClose方法,很方便,其参数列表为:品种,滑点。设置滑点是当交易不成功时,可以做一个价格上的小让步,匹配更多的订单,设置一个合适的滑点是比较重要的,太小了有可能会交易失败,太大了有可能会很亏。
就好像一个智者一样,告诉你要控制仓位到哪个位置。
如果不想控制仓位,直接使用一个数字代替TradeSizeOptimized()即可
啊懒得解释了……
本文以经典的均线交易系统解读MQL5交易脚本EA的结构。
策略
当价格上穿均线时做多并在下穿均线时平仓;当价格下穿均线时做空并在上穿均线时平仓。真是非常简单的均线交易系统呢。
代码结构精解
版本声明
在一开始,会有一些跟实际功能无关的代码,大致是一个EA的自我介绍的玩意,并没有什么功能上的用途。//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2009-2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2009-2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00"
使用
#property来声明EA的各种属性,这些部分系统可以自动生成,基本没有必要改动,一笔带过。
代码引用
与C/C++以及绝大多数编程语言一样,MQL也有代码包含的指令,它仿照C/C++使用了#include,其机制是一致的——预编译。更多的资料可以自行查阅“预编译 文本包含”。#include <Trade\Trade.mqh>
在
Trade.mqh头文件内包含了很多跟交易有关的函数。如果将EA的买/卖操作比作输入/输出,那么
Trade.mqh头文件就可以比作
stdio.h,非常重要。
输入参数
MQL中的参数是可以由用户定制的,算是一个由外界输入的接口,Meta Trader还可以在GUI界面对输入参数进行调优。在MQL中,输入参数以input修饰。
input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift
这里有4个参数,分别为:
最大风险比率(默认2%)(用于风控)
减小因子(默认为3)(用于风控)
均线周期(默认为12)
均线位移(默认为6)
具体是什么意思,还要结合下面的逻辑看。
全局量
EA脚本通常是可以持续执行的(守护态运行),存一些全局变/常量是相当有必要的。int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; #define MA_MAGIC 1234501
Ext是Expert的意思,就是EA的E。
CTrade对象来自于
Trade.mqh,封装了很多交易方法。ExtTrade是一个实例。
Handle 是句柄,把手的意思,在MQL中,通常指指标的引用。
在这里,EA需要使用一个均线的指标,所以需要存储一个指标的对象,指标在MQL中以句柄的形式存储,与操作系统的PID的机制类似,也类似一个地址(指针)。
Hedging是避险的意思,在这里是风险控制,也就是仓位控制。
ExtHedging就是一个开关,是否进行仓位控制,默认是关闭的,但是它会在程序中随时打开(因为如果它并不能随时打开,那么应该设置为 input 属性,由外界给出)。
可以看到,全局变量管理更像是在管理状态机。
初始化状态机
全局变量集合就像状态机一样。那么首先开始的就是状态机的起点——初始状态。在MQL中,使用OnInit函数来控制初始化,它在其他的事件函数开始之前被执行。
int OnInit(void) { //--- prepare trade class to control positions if hedging mode is active ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); //--- Moving Average indicator ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); } //--- ok return(INIT_SUCCEEDED); }
我不得不吐槽一下这诡异的MQL自带缩进,简直不忍直视,啊这不是重点。
函数的签名
int OnInit()与
int OnInit(void)是一样的。
是不是有点像
int main()?
在函数的开始,首先对ExtHedging进行了操作。
ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
AccountInfoInteger函数是用于“获取用户账户信息”的,它会根据传入的枚举常量,多态地返回各种值。在这里,是查询用户的保证金模式(Margin:保证金)。当保证金模式为
ACCOUNT_MARGIN_MODE_RETAIL_HEDGING(具体到底是什么我也不是很清楚)时,ExtHedging开关会打开。
接下来设置一下 CTrade对象 ExtTrade的一些属性
ExtTrade.SetExpertMagicNumber(MA_MAGIC) // 之前的那个宏定义的1234501,就是一个Hash值,随便写。 ExtTrade.SetMarginMode(); // 设置保证金模式
再是获取一下均线指标的数据,利用iMA函数
ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
iMA函数的参数在官方的手册上都可以查,
这里有很多的常量
_Symbol代表当前货币,这里也可以用NULL代替;
_Period代表当前周期,即时间周期,如1小时K线;
MovingPeriod代表均线的周期,这里由input给出;
MovingShift代表均线平移量,实际的效果延迟若干根K线处理;
在下也不知道为什么要默认延迟6个K线进行交易
MODE_SMA是使用
Simple Moving Average(简单移动平均线:取算术平均数)
PRICE_CLOSE是应用于收盘价的意思,其他的还可以应用于开盘价。
当然,这个ExtHandle的赋值是有可能出现失败的,比如当传入的参数不合法的时候(总之就是指标内部出现错误的时候),ExtHandle会被赋值为
INVALID_HANDLE(非法句柄),在这个时候要检测一下:
if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); }
这样可以提升代码的健壮性。
如果想使用自定义指标,请参考 iCustom函数
在初始化结束的时候,如果一切正常,应该返回
INIT_SUCCEEDED表示成功初始化了;否则返回
INIT_FAILED告诉 Meta Trader 停止脚本运行。
状态机转换
MQL提供了一个时间事件void OnTick(),一旦任何的变动发生,无论是接受到新的价格、产生订单等都会调用OnTick,所以这是名副其实的状态转换事件。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- }
逻辑很简单,但涉及了三个尚未定义的函数,在这里先从语义上理解一下:
SelectPostion函数返回“目前是否存在仓位”;
CheckForClose函数检查是否满足平仓的条件,如果满足就平仓;
CheckForOpen函数检查是否满足开仓的条件,如果满足就开仓。
因此这个逻辑非常清晰:如果现在有仓位就看看能不能平仓了;否则看看是不是能开仓了。
我个人对这些函数的命名持保留态度:觉得不是很便于理解。
仓位查询
仓位查询,在这里由单独的函数SelectPosition来管理了。
position有仓位的意思,select有查询的意思。
//+------------------------------------------------------------------+ //| Position select depending on netting or hedging | //+------------------------------------------------------------------+ bool SelectPosition() { bool res=false; //--- if(ExtHedging) { uint total=PositionsTotal(); for(uint i=0; i<total; i++) { string position_symbol=PositionGetSymbol(i); if(_Symbol==position_symbol && MA_MAGIC==PositionGetInteger(POSITION_MAGIC)) { res=true; break; } } } else res=PositionSelect(_Symbol); //--- return(res); }
其中有几个系统的函数需要说明一下:
int PositionsTotal()返回现在仓位下的所有交易数(比如你开多仓,分为3单交易成交,分别是1、0.2、0.5手的交易量——此时会返回3——此时仓位列表中有3个单子成交)。
string PositionGetSymbol(int index)通过仓位列表中的序号(索引)查询该单的货币名称。
PositionGetInteger则是返回该单的魔数(Magic Number)。
PositionSelect是MQL提供的查询当前是否有仓位的函数。
现在,结合整个逻辑,可以理解
ExtHedging的作用了:
如果不考虑仓位控制,即一次只能下一单,不能加仓或者减仓,那么完全可以直接使用
PositionSelect(_Symbol)来代替整个
SelectPosition函数。但是,当存在多个订单的时候,甚至是跨品种的交易的时候,检测仓位显然需要更严格的控制,因为对于一个账号,所有的交易都存在同一仓位列表中。
因此,在遍历仓位列表的时候,需要严格匹配,必须是相同的品种而且魔数是一致的(比起魔数我更喜欢叫它暗号)。尽管是遍历,但其效率在一般的情况下(只交易一种品种),第一次就能够跳出循环,实际上是效率很高的。
如果只是测试交易信号的准确程度,不必大费周章,直接使用
PositionSelect函数吧,也不要考虑仓位控制了。
交易信号
开仓
开仓是交易的开始……//+------------------------------------------------------------------+ //| Check for open position conditions | //+------------------------------------------------------------------+ void CheckForOpen(void) { MqlRates rt[2]; //--- go trading only for first ticks of new bar if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRates of ",_Symbol," failed, no history"); return; } if(rt[1].tick_volume>1) return; //--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; } //--- check signals ENUM_ORDER_TYPE signal=WRONG_VALUE; if(rt[0].open>ma[0] && rt[0].close<ma[0]) signal=ORDER_TYPE_SELL; // sell conditions else { if(rt[0].open<ma[0] && rt[0].close>ma[0]) signal=ORDER_TYPE_BUY; // buy conditions } //--- additional checking if(signal!=WRONG_VALUE) { if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100) ExtTrade.PositionOpen(_Symbol,signal,TradeSizeOptimized(), SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK), 0,0); } //--- }
获取价格时间序列
MqlRates对象是存储有关价位、交易量和传递的信息的。因为在MQL5中没有全局close[]数组了,要接入时间序列的方式就要使用
Copy*(以Copy开头的)函数来从不同的Buffer(缓冲区:即数组)中获取数据。MqlRates基本包含了一个Bar的所有信息。
CopyRates函数获取价格时间序列的函数,参数列表为:品种、周期、开始点、数量、目标。用自然语义解释的话就是,把某品种某周期从某处开始一共某数量的价格序列复制到目标数组中。
开始点的0表示最新的,而不是最早的,即是时间逆序的。
Copy*函数会返回成功复制的数据量,一般情况下等于参数中的数量,但是如果异常情况下可以检测出来:
if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRates of ",_Symbol," failed, no history"); return; }
获取两个价格,是因为“上穿”与“下穿”的实现至少需要两个柱。
如果只想用收盘价还可以用
CopyClose来只获取收盘价。
MqlRates中有一个成员 tick_volume 表示订单成交量(跳动量),即该时期内,订单成交造成的价格变动数。如果只想在新柱
rt[1]的第一个价格下单:
if(rt[1].tick_volume>1) return;
只需要屏蔽之后的订单即可。
获取指标数据
获取均线的方法是使用iMA函数//--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; }
这里我们只需要均线的一个值即可,之前ExtHandle已经获得过均线句柄了,现在可以直接去拷贝数据了:
CopyBuffer的参数列表为:指标句柄、指标缓冲区、开始位置、数量、目标。自然语义化为:将某句柄对应的指标的某缓冲区上从某处开始拷贝某数量的值到目标数组上。
在这里,是指将ExtHandle句柄对应的指标的0号缓冲区的数据,从0处(最新的位置),拷贝1个到ma数组里。
处理异常的方式同上
CopyRates。
检查仓位方向
选择做多还是做空的时候来了!ENUM_ORDER_TYPE是订单的类型,可以用
ORDER_TYPE_BUY、
ORDER_TYPE_SELL、
WRONG_VALUE
一般的做法是默认为
WRONG_VALUE,这样默认不开仓。
//--- check signals ENUM_ORDER_TYPE signal=WRONG_VALUE; if(rt[0].open>ma[0] && rt[0].close<ma[0]) signal=ORDER_TYPE_SELL; // sell conditions else { if(rt[0].open<ma[0] && rt[0].close>ma[0]) signal=ORDER_TYPE_BUY; // buy conditions }
代码写的很难看,但逻辑非常清晰,表达了上穿均线买入,下穿均线卖出的逻辑。
确定了仓位就可以开始激动人心的下单环节了!
下单
//--- additional checking if(signal!=WRONG_VALUE) { if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100) ExtTrade.PositionOpen(_Symbol,signal,TradeSizeOptimized(), SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK), 0,0); }
根据之前仓位的方向来下单,如果是
WRONG_VALUE就不开仓,跳过即可。
里面还有一些更严格的检查:
TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)现在系统是否允许交易
Bars(_Symbol,_Period)>100现在系统获取的数据数是否足够多
(在这里,如果不超过100柱的数据,数据会被认为是不可靠的,跳过交易——当然,不一定100,也可以是50或者其他数字,但应该大于指标的计算周期)
CTrade对象的PositionOpen方法提供了开仓的功能,其参数列表为:品种、方向、数量、索价、止损、止盈,还有一个默认为空的注释。自然语义化为:在某品种的某方向以某索价开一个某数量的仓,设置止损位与止盈位,并注释此订单。
在这里,在当前品种上按照给定方向开一个
TradeSizeOptimized()数量的单,索价……如果是空单就按市场卖价;如果是多单就按市场买价……总之是市场价,不止损也不止盈,也不注释。
当止损/止盈为0时表示不止损/止盈。
到底开多少的仓位呢?用TradeSizeOptimized函数来控制(仓位控制),下文会提到。
平仓
平仓函数总体上与开仓函数的结构是相似的,在很多情况下,他们能够写到一起。比如我觉得开头那一段复制数据的过程就可以抽出来,没必要写两次(官方代码也是很蠢的)。平仓的逻辑是,
//+------------------------------------------------------------------+
//| Check for close position conditions |
//+------------------------------------------------------------------+
void CheckForClose(void)
{
MqlRates rt[2];
//--- go trading only for first ticks of new bar
if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
{
Print("CopyRates of ",_Symbol," failed, no history");
return;
}
if(rt[1].tick_volume>1)
return;
//--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; }
//--- positions already selected before
bool signal=false;
long type=PositionGetInteger(POSITION_TYPE);
if(type==(long)POSITION_TYPE_BUY && rt[0].open>ma[0] && rt[0].close<ma[0])
signal=true;
if(type==(long)POSITION_TYPE_SELL && rt[0].open<ma[0] && rt[0].close>ma[0])
signal=true;
//--- additional checking
if(signal)
{
if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
ExtTrade.PositionClose(_Symbol,3);
}
//---
}
逻辑还是很简单的……获取完数据,再检测一下现在是什么仓位,对应地检查一下平仓条件,如果符合了并且现在系统允许交易而且柱数够多(这个其实是废话),就平仓。
平仓使用CTrade对象的PositionClose方法,很方便,其参数列表为:品种,滑点。设置滑点是当交易不成功时,可以做一个价格上的小让步,匹配更多的订单,设置一个合适的滑点是比较重要的,太小了有可能会交易失败,太大了有可能会很亏。
仓位控制
顺风局重仓乘胜追击,逆风局轻仓小心谨慎。TradeSizeOptimized函数返回一个数量,代表应该调整到这个位置。
就好像一个智者一样,告诉你要控制仓位到哪个位置。
如果不想控制仓位,直接使用一个数字代替TradeSizeOptimized()即可
//+------------------------------------------------------------------+ //| Calculate optimal lot size | //+------------------------------------------------------------------+ double TradeSizeOptimized(void) { double price=0.0; double margin=0.0; //--- select lot size if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price)) return(0.0); if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin)) return(0.0); if(margin<=0.0) return(0.0); double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)*MaximumRisk/margin,2); //--- calculate number of losses orders without a break if(DecreaseFactor>0) { //--- select history for access HistorySelect(0,TimeCurrent()); //--- int orders=HistoryDealsTotal(); // total history deals int losses=0; // number of losses orders without a break for(int i=orders-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) { Print("HistoryDealGetTicket failed, no trade history"); break; } //--- check symbol if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue; //--- check profit double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); if(profit>0.0) break; if(profit<0.0) losses++; } //--- if(losses>1) lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1); } //--- normalize and check limits double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); lot=stepvol*NormalizeDouble(lot/stepvol,0); double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); if(lot<minvol) lot=minvol; double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); if(lot>maxvol) lot=maxvol; //--- return trading volume return(lot); }
啊懒得解释了……
附:源码
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2009-2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2009-2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00"
#include <Trade\Trade.mqh>
input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift
//---
int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; #define MA_MAGIC 1234501
//+------------------------------------------------------------------+ //| Calculate optimal lot size | //+------------------------------------------------------------------+ double TradeSizeOptimized(void) { double price=0.0; double margin=0.0; //--- select lot size if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price)) return(0.0); if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin)) return(0.0); if(margin<=0.0) return(0.0); double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)*MaximumRisk/margin,2); //--- calculate number of losses orders without a break if(DecreaseFactor>0) { //--- select history for access HistorySelect(0,TimeCurrent()); //--- int orders=HistoryDealsTotal(); // total history deals int losses=0; // number of losses orders without a break for(int i=orders-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) { Print("HistoryDealGetTicket failed, no trade history"); break; } //--- check symbol if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue; //--- check profit double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); if(profit>0.0) break; if(profit<0.0) losses++; } //--- if(losses>1) lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1); } //--- normalize and check limits double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); lot=stepvol*NormalizeDouble(lot/stepvol,0); double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); if(lot<minvol) lot=minvol; double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); if(lot>maxvol) lot=maxvol; //--- return trading volume return(lot); }
//+------------------------------------------------------------------+ //| Check for open position conditions | //+------------------------------------------------------------------+ void CheckForOpen(void) { MqlRates rt[2]; //--- go trading only for first ticks of new bar if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRates of ",_Symbol," failed, no history"); return; } if(rt[1].tick_volume>1) return; //--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; } //--- check signals ENUM_ORDER_TYPE signal=WRONG_VALUE; if(rt[0].open>ma[0] && rt[0].close<ma[0]) signal=ORDER_TYPE_SELL; // sell conditions else { if(rt[0].open<ma[0] && rt[0].close>ma[0]) signal=ORDER_TYPE_BUY; // buy conditions } //--- additional checking if(signal!=WRONG_VALUE) { if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100) ExtTrade.PositionOpen(_Symbol,signal,TradeSizeOptimized(), SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK), 0,0); } //--- }
//+------------------------------------------------------------------+
//| Check for close position conditions |
//+------------------------------------------------------------------+
void CheckForClose(void)
{
MqlRates rt[2];
//--- go trading only for first ticks of new bar
if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
{
Print("CopyRates of ",_Symbol," failed, no history");
return;
}
if(rt[1].tick_volume>1)
return;
//--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; }
//--- positions already selected before
bool signal=false;
long type=PositionGetInteger(POSITION_TYPE);
if(type==(long)POSITION_TYPE_BUY && rt[0].open>ma[0] && rt[0].close<ma[0])
signal=true;
if(type==(long)POSITION_TYPE_SELL && rt[0].open<ma[0] && rt[0].close>ma[0])
signal=true;
//--- additional checking
if(signal)
{
if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
ExtTrade.PositionClose(_Symbol,3);
}
//---
}
//+------------------------------------------------------------------+ //| Position select depending on netting or hedging | //+------------------------------------------------------------------+ bool SelectPosition() { bool res=false; //--- if(ExtHedging) { uint total=PositionsTotal(); for(uint i=0; i<total; i++) { string position_symbol=PositionGetSymbol(i); if(_Symbol==position_symbol && MA_MAGIC==PositionGetInteger(POSITION_MAGIC)) { res=true; break; } } } else res=PositionSelect(_Symbol); //--- return(res); }
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit(void) { //--- prepare trade class to control positions if hedging mode is active ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); //--- Moving Average indicator ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); } //--- ok return(INIT_SUCCEEDED); }
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- }
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
相关文章推荐
- c语言判断素数和输入一个数n代表有n行数据的问题
- uva 1152 4 values whose sum is zero ——yhx
- viewController 瘦身 -- 通过新建DataSource类来管理tableView的数据源方法
- 关于多线程的一点感想
- 题目1335:闯迷宫( BFS在求解最短路径或者最短步数上有很多的应用)
- Qconf配置管理工具-Java使用文档
- Cocos2dx:用Photoshop自动创建CocosStudio.csd文件——cocos3.10
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- 面试问题及答案小结
- Swift基础类型
- python实现简单爬虫
- 集合类 Contains 方法 深入详解 与接口的实例
- manifest.mf 详解
- 使用memcache 存储session
- Fbric、Ansible、Docker、Chaos Monkey:DevOps工具的年中回顾
- 感兴趣的文章搜集
- EditBox注册返回键和点击空白返回时候输入内容不能写入游戏
- 装系统(1)
- IOS下的 NSTimer与Run loop Modes
- CMake Error: your CXX compiler: "CMAKE_CXX_COMPILER-NOTFOUND" was not found