Skip to content

风险控制

一、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);
}