风险控制
一、EA功能与风险监控背景
这个EA没有科学的进出场逻辑,完全通过抛硬币模拟交易。讲解它的目的是介绍日常使用的风控手段——这类手段并非传统的资金管理或止损管理,而是针对整个账户或资金曲线的风险控制。
例如:
- 参加交易比赛时,主办方可能限制做单规则(如总体回撤≤10%、单日回撤≤5%、每日亏损笔数≤N笔)。
- 申请平台风险资金时,平台会通过苛刻规则控制风险,同时筛选交易者。
本次重点:实现这类风险监控体系,包括总体账户风控、每日回撤控制、每日交易笔数限制等。
二、核心风控规则解析
1. 总体账户回撤控制
- 规则:设定账户允许的最大回撤(如40%),当利润回撤达到该阈值时,暂停交易。
- 实现逻辑:实时监控账户净值与初始余额的比例,超过阈值则禁止开仓。
2. 每日回撤控制
- 规则:以每日凌晨的账户余额为基准,设定单日最大回撤比例(如5%)。
- 当日允许最大亏损金额 = 当日凌晨余额 × 单日回撤比例(例:凌晨余额100,000美元,5%对应5,000美元)。
- 数据统计:
- 今日已结算利润:当日所有平仓订单的盈亏(含手续费、隔夜费)。
- 实时计算:通过今日凌晨余额与当前余额对比,判断是否触发回撤限制。
3. 每日交易笔数控制
- 规则:限定单日最大交易笔数(如10笔),超过则禁止开仓。
- 课后作业:将“每日交易笔数”改为“每日亏损笔数”控制(如允许单日最多14笔亏损,第15笔触发禁止交易)。
三、代码实现
cpp
#property strict
#include <Util/Util.mqh>
Util util;
input int MagicNum = 123;
input int accountMaxDD = 10; // 总账户允许的最大回撤(%)
input int dayMaxDD = 5; // 单日允许的最大回撤(%)
input int dayMaxPositionsTotal = 50 ; // 单日允许的最大交易笔数
input bool hideChart = true ; // 隐藏图表便于监控
int barsTotal;
double initialBalance = 0 ;
double dayBalance = 0;
datetime dayTime = 0;
bool isTradeAllowed = true;
double point;
MqlTick currentTick;
double acc_balance(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double acc_equity(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string acc_crrency(){{return AccountInfoString(ACCOUNT_CURRENCY);}}
int OnInit()
{
initialBalance = acc_balance();
createText("0","***总账户&每日-风险监控***",150,20,clrAqua,13);
createText("00","_______________________________________________________",40,25,clrAqua,13);
createText("1","已启用回撤控制",70,50,clrWhite,10);
createText("2","每日凌晨重置次数",70,65,clrWhite,10);
createText("3","开始:",70,80,clrWhite,10);
createText("4","今日开始时间",120,80,clrGray,10);
createText("5","结束:",70,95,clrWhite,10);
createText("6","今日结束时间",120,95,clrGray,10);
createText("7","现在:",70,110,clrWhite,10);
createText("8","当前时间",120,110,clrGray,10);
createText("9","====================总账户回撤控制====================",70,130,clrWhite,10);
createText("10","账户初始余额:",70,145,clrWhite,10);
createText("11",DoubleToString(initialBalance,2)+ " "+ acc_crrency(),250,145,clrWhite,10);
createText("12","允许账户最大回撤:",70,160,clrWhite,10);
createText("13",DoubleToString(accountMaxDD,2) + " %",250,160,clrAqua,10);
createText("14","当前账户净值:",70,175,clrWhite,10);
createText("15",DoubleToString(acc_equity(),2) + " "+ acc_crrency(),250,175,clrWhite,10);
createText("16","净值浮亏浮盈:",70,190,clrWhite,10);
createText("17",DoubleToString((acc_equity()-acc_balance())/acc_balance()*100,2) + " %",250,190,clrGray,10);
createText("18","==================== 每日回撤控制====================",70,210,clrPeru,11);
createText("19","账户初始余额:",70,225,clrWhite,10);
createText("20",DoubleToString(acc_balance(),2)+ " "+ acc_crrency(),270,225,clrWhite,10);
createText("21","允许账户最大回撤:",70,240,clrWhite,10);
createText("22",DoubleToString(dayMaxDD,2) + " %",270,240,clrAqua,10);
createText("23","账户最大回撤金额:",70,255,clrWhite,10);
createText("24",DoubleToString(acc_balance()*dayMaxDD/100,2)+ " "+ acc_crrency(),270,255,clrYellow,10);
createText("25","今日已结算利润:",70,270,clrWhite,10);
createText("26","0.00"+" "+acc_crrency(),270,270,clrWhite,10);
createText("27","当日最大回撤:",70,285,clrWhite,10);
createText("28","0.00"+" %",270,285,clrGray,10);
createText("29",">>> 总账户&每日-风险监控 初始化完毕:",70,300,clrYellow,10);
createText("29.","==================== 每日交易笔数控制====================",70,320,clrPeru,11);
createText("30","单日允许最大交易笔数:",70,335,clrWhite,10);
createText("31",DoubleToString(dayMaxPositionsTotal,2)+" 笔",270,335,clrWhite,10);
createText("32","今日交易笔数:",70,350,clrWhite,10);
createText("33","0 "+" 笔",270,350,clrWhite,10);
createText("34",">>> 交易笔数监控 初始化完毕.",70,365,clrYellow,10);
createText("000","_______________________________________________________",40,380,clrAqua,13);
if(hideChart){
ChartSetInteger(0,CHART_SHOW,false);
ChartSetInteger(0,CHART_SHOW_GRID,false);
ChartSetInteger(0,CHART_COLOR_BACKGROUND,clrBlack);
ChartSetInteger(0,CHART_COLOR_CHART_UP,clrBlack);
ChartSetInteger(0,CHART_COLOR_CHART_DOWN,clrBlack);
ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,clrBlack);
ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,clrBlack);
ChartSetInteger(0,CHART_COLOR_FOREGROUND,clrBlack);
}
// EventSetTimer(60);
return INIT_SUCCEEDED;
}
// 节省内存后 买VPS就可以买小一点
void OnDeinit(const int reason)
{
// EventKillTimer();
ObjectsDeleteAll(0,0,-1);
}
// void OnTimer(){
// }
void OnTick()
{
checkDailyProfit();
// isTradeAllowed 变量 其它 EA 无法访问到
// if(!isTradeAllowed){
// return;
// }
// 使用 全局变量 其它EA 也能访问
double GlobalIsTradeAllowed = GlobalVariableGet("GlobalIsTradeAllowed");
if(GlobalIsTradeAllowed==-1) return ;
if (!util.isNewBar(_Symbol, _Period, barsTotal))
return;
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(PositionsTotal()>1) return ;
double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK),_Digits);
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID),_Digits);
int number = MathRand()%2;
if(number == 0){
double et = ask;
double sl = et -100*_Point;
double tp = et + 100*_Point;
double lotSize = util.calcLots(_Symbol, et, sl, 2, 1);
trade.Buy(lotSize,_Symbol,et,sl,tp);
}
else if(number == 1){
double et = bid;
double sl = et +100*_Point;
double tp = et - 100*_Point;
double lotSize = util.calcLots(_Symbol, et, sl, 2, 1);
trade.Sell(lotSize,_Symbol,et,sl,tp);
}
}
bool createText(string objName, string text, int x ,int y , color clrTxt, int fontSize){
ResetLastError();
if(!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
Print(__FUNCTION__,":OBJ_LABEL 创建失败:",GetLastError());
return false;
}
ObjectSetInteger(0,objName,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,objName,OBJPROP_YDISTANCE,y);
// 显示在左上角
ObjectSetInteger(0,objName,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetString(0,objName,OBJPROP_TEXT,text);
ObjectSetInteger(0,objName,OBJPROP_COLOR,clrTxt);
ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,fontSize);
ChartRedraw(0);
return true;
}
void checkDailyProfit(){
double totalDayProfit =0 ; // 今日已结算利润
int todayPositionsTotal = 0;
string sdate = TimeToString(TimeCurrent(),TIME_DATE); // 今日日期
datetime start = StringToTime(sdate) ; // 今日凌晨时间点
datetime to = start + (1*24 * 60*60) ; // 明日凌晨时间点
// 确保在每天的开始,我们会有一个新的其实余额(dayBalance) 去计算和比较新一天的交易变动情况
if(dayTime<to){
dayTime = to; // 设为明天凌晨的时间
dayBalance = acc_balance(); // 重置当日账户余额
}
HistorySelect( start, TimeCurrent());
int totalDeals = HistoryDealsTotal();
for (int i = 0; i < totalDeals; i++)
{
ulong ticket = HistoryDealGetTicket(i);
if(HistoryDealGetInteger(ticket,DEAL_ENTRY)== DEAL_ENTRY_OUT){
double latestDayProfit = HistoryDealGetDouble(ticket, DEAL_PROFIT)
+ 2*HistoryDealGetDouble(ticket, DEAL_COMMISSION)
+ HistoryDealGetDouble(ticket, DEAL_SWAP);
totalDayProfit += latestDayProfit;
todayPositionsTotal++;
}
}
double startingBalance = acc_balance() - totalDayProfit; // 今日开始时的账户余额
double dailyProfitOrDrawDown = NormalizeDouble((totalDayProfit*100/startingBalance),2); // 今日利润百分比
string dailyProfitInTextFormat = DoubleToString(dailyProfitOrDrawDown,2)+" %";
createText("4",TimeToString(start),120,80,clrGray,10);// 今日开始时间
createText("6",TimeToString(to),120,95,clrGray,10);// 今日结束时间
createText("8",TimeToString(TimeCurrent()),120,110,clrGray,10);// 当前时间
// 总账户的回撤控制
if(acc_equity()>initialBalance){ // 如果账户净值大于账户初始余额
createText("15",DoubleToString(acc_equity(),2) + " "+ acc_crrency(),250,175,clrGreen,10); // 创建绿色的 当前账户净值
createText("17",DoubleToString((acc_equity()-initialBalance)/initialBalance*100,2) + " %",250,190,clrGreen,10); // 创建绿色的 净值浮亏浮盈
}
else if(acc_equity()<initialBalance){ // 如果账户净值小于账户初始余额
createText("15",DoubleToString(acc_equity(),2) + " "+ acc_crrency(),250,175,clrRed,10); // 创建红色的 当前账户净值
createText("17",DoubleToString((acc_equity()-initialBalance)/initialBalance*100,2) + " %",250,190,clrRed,10); // 创建红色的 净值浮亏浮盈
}else if(acc_equity()==initialBalance){ // 如果账户净值等于账户初始余额
createText("15",DoubleToString(acc_equity(),2) + " "+ acc_crrency(),250,175,clrWhite,10); // 创建白色的 当前账户净值
createText("17",DoubleToString((acc_equity()-initialBalance)/initialBalance*100,2) + " %",250,190,clrWhite,10); // 创建白色的 净值浮亏浮盈
}
createText("20",DoubleToString(dayBalance,2)+ " "+ acc_crrency(),270,225,clrWhite,10); // 今日账户初始余额
createText("24",DoubleToString(dayBalance*dayMaxDD/100,2)+ " "+ acc_crrency(),270,255,clrYellow,10);// 今日账户允许最大回撤金额
// 今日的回撤控制
if(acc_balance()>dayBalance){ // 如果账户余额大于当日账户余额
createText("26",DoubleToString(totalDayProfit,2)+ " "+ acc_crrency(),270,270,clrGreen,10); // 创建绿色的 今日已结算利润
createText("28",dailyProfitInTextFormat,270,285,clrGreen,10); // 创建绿色的 当日最大回撤或者盈利
}else if(acc_balance()<dayBalance){ // 如果账户余额小于当日账户余额
createText("26",DoubleToString(totalDayProfit,2)+ " "+ acc_crrency(),270,270,clrRed,10); // 创建红色的 今日已结算利润
createText("28",dailyProfitInTextFormat,270,285,clrRed,10); // 创建红色的 当日最大回撤或者盈利
}else if(acc_balance()==dayBalance){ // 如果账户余额等于当日账户余额
createText("26",DoubleToString(totalDayProfit,2)+ " "+ acc_crrency(),270,270,clrWhite,10); // 创建白色的 今日已结算利润
createText("28",dailyProfitInTextFormat,270,285,clrWhite,10); // 创建白色的 当日最大回撤或者盈利
}
bool mddAllowed = true;
if(dailyProfitOrDrawDown <= -dayMaxDD){
createText("29",">>>达到当日最大回撤"+string(dayMaxDD) + "% 禁止交易",70,300,clrRed,10);
mddAllowed = false;
}else if( ((acc_equity()-initialBalance) / initialBalance) < -accountMaxDD){
createText("29",">>>达到账户最大回撤"+string(accountMaxDD) + "% 禁止交易",70,300,clrRed,10);
mddAllowed = false;
}else{
createText("29",">>>总账户回撤和单日回撤再正常范围之内 允许交易",70,300,clrLime,10);
mddAllowed = true;
}
bool positionTotalAllowed = true;
if(todayPositionsTotal >= dayMaxPositionsTotal){ // 如果今日交易笔数大于单日最大交易笔数
createText("33",string(todayPositionsTotal) + " 笔",270,350,clrRed,10);
createText("34",">>>达到单日最大交易笔数"+string(dayMaxPositionsTotal) + " 禁止交易",70,365,clrRed,10);
positionTotalAllowed = false;
}else if(todayPositionsTotal <dayMaxPositionsTotal){ // 如果今日交易笔数小于单日最大交易笔数
createText("33",string(todayPositionsTotal) + " 笔",270,350,clrWhite,10);
createText("34",">>>单日交易笔数再正常范围之内 允许交易",70,365,clrLime,10);
positionTotalAllowed = true;
}
isTradeAllowed = mddAllowed && positionTotalAllowed; // 交易是否允许
GlobalVariableSet("GlobalIsTradeAllowed",isTradeAllowed?1:-1);
}