//+------------------------------------------------------------------+ //| DrawdownControl.mqh | //| Implements account drawdown-based risk controls (for prop-firms) | //| | //| 2025 xMattC (github.com/xMattC) | //+------------------------------------------------------------------+ #property copyright "2025 xMattC (github.com/xMattC)" #property link "https://github.com/xMattC" #property version "1.00" #property library #include #include class DrawdownControl : public CObject { protected: CTrade trade; MarketDataUtils m_utils; string data_file; double daily_max_dd_per; string daily_reset_time; bool print_statments; double acc_max_dd_per; double equaty_control_high; double equaty_control_low; double daily_equity_start; double daily_max_dd_target; bool daily_dd_limit_reached; bool write_global_var_data(); bool print_messages(); public: void init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true); bool determine_daily_dd_limit(); double lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20); double lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor); }; // --------------------------------------------------------------------- // Initializes the drawdown control with input parameters and reads // or resets persistent drawdown state. // // Parameters: // - inp_data_file : Filename for drawdown data storage. // - inp_acc_max_dd_per : Max absolute drawdown percentage. // - inp_daily_max_dd_per : Max daily drawdown percentage. // - inp_daily_reset_time : Reset time (e.g., "00:00"). // - inp_print_statments : Optional flag to print debug info. // --------------------------------------------------------------------- void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true) { data_file = inp_data_file; acc_max_dd_per = inp_acc_max_dd_per; daily_max_dd_per = inp_daily_max_dd_per; daily_reset_time = inp_daily_reset_time; print_statments = inp_print_statments; // If no data file exisits, create one and set global vairiables: if(FileIsExist(data_file) == false) { daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)); daily_dd_limit_reached = false; equaty_control_high = 9999999; equaty_control_low = 0; write_global_var_data(); } // If file exisits read file: if(FileIsExist(data_file) == true) { int file_handle = FileOpen(data_file, FILE_READ | FILE_ANSI | FILE_TXT); if(file_handle == INVALID_HANDLE) { Print("Error opening file: ", data_file); } // If data file is older than 24h 10min create a new file and reset global vars: long modifided_date = FileGetInteger(file_handle, FILE_MODIFY_DATE); long time_delta = ((long)TimeCurrent() - modifided_date) / 60; if(time_delta >= 1450) { daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)); daily_dd_limit_reached = false; equaty_control_high = equaty_control_high; equaty_control_low = equaty_control_low; write_global_var_data(); Print(data_file, " is older than 24h and 10min; global vars reset!"); } // If data file is younger than 24h+10 min read data and set global vars: else { daily_equity_start = (double)FileReadString(file_handle, 0); daily_max_dd_target = (double)FileReadString(file_handle, 1); daily_dd_limit_reached = FileReadBool(file_handle); equaty_control_high = (double)FileReadString(file_handle, 3); equaty_control_low = (double)FileReadString(file_handle, 4);; } FileClose(file_handle); } print_messages(); } // --------------------------------------------------------------------- // Checks if current equity has breached the daily drawdown threshold. // If so, closes all trades and cancels orders. // // Returns: // - true if daily drawdown limit has been reached. // --------------------------------------------------------------------- bool DrawdownControl::determine_daily_dd_limit() { // Reset max equity at the start of each day: string ct = TimeToString(TimeCurrent(), TIME_MINUTES); if(ct == daily_reset_time) { daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); daily_max_dd_target = (daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100))); daily_dd_limit_reached = false; write_global_var_data(); print_messages(); } // If in drawdown close all positions and delete orders if(daily_dd_limit_reached || AccountInfoDouble(ACCOUNT_EQUITY) <= daily_max_dd_target) { if(daily_dd_limit_reached == false) { daily_dd_limit_reached = true; write_global_var_data(); print_messages(); } for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); trade.PositionClose(ticket); } for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); trade.OrderDelete(ticket); } } return daily_dd_limit_reached; } // --------------------------------------------------------------------- // Calculates a corrected lot multiplier based on account drawdown. // // Parameters: // - acc_equity_start : Starting equity reference. // - min_lot_factor : Minimum lot scaling factor. // - max_lot_factor : Maximum lot scaling factor. // - dynm_lot_factor : Enable trailing dynamic lot logic. // - dlf_trail_per : Percent buffer for dynamic trail. // // Returns: // - Scaled lot factor between min and max bounds. // --------------------------------------------------------------------- double DrawdownControl::lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20) { double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); double lot_factor; // Interpolate to find lot factor between given min and max values. if (account_value < acc_equity_start){ double acc_equity_min = acc_equity_start - (acc_equity_start * (acc_max_dd_per / 100)); double y1 = min_lot_factor; double y2 = max_lot_factor; double x1 = acc_equity_min; double x = account_value; double x2 = acc_equity_start; lot_factor = y1 + (x - x1) * ((y2 - y1) / (x2 - x1)); } else if(account_value >= acc_equity_start) { if(dynm_lot_factor=true){ lot_factor = lot_correction_dynamic(dlf_trail_per, min_lot_factor, max_lot_factor); } else { lot_factor = max_lot_factor; } } return max_lot_factor; } // --------------------------------------------------------------------- // Computes dynamic lot factor based on trailing equity bounds. // // Parameters: // - acc_dd_percent : Dynamic trailing buffer in percent. // - min_lot_factor : Minimum lot factor. // - max_lot_factor : Maximum lot factor. // // Returns: // - Interpolated lot factor. // --------------------------------------------------------------------- double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor) { double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); double trail_point = account_value - (account_value * (acc_dd_percent / 100)); if(equaty_control_low < trail_point){ equaty_control_low = trail_point; } if(equaty_control_high < account_value){ equaty_control_high = account_value; } if(account_value < equaty_control_low){ equaty_control_low = account_value; equaty_control_high = account_value + (account_value * (acc_dd_percent / 100)); } // back-up to file every hour: if(m_utils.is_new_bar(_Symbol, PERIOD_H1) == true){ write_global_var_data(); } // Linear interpolation: double y1 = min_lot_factor; double y2 = max_lot_factor; double x1 = equaty_control_low; double x = account_value; double x2 = equaty_control_high; double y = y1 + (x - x1) * ((y2 - y1) / (x2 - x1)); return y; } bool DrawdownControl::write_global_var_data() { int file_handle = FileOpen(data_file, FILE_WRITE | FILE_ANSI | FILE_TXT); FileWrite(file_handle, daily_equity_start); FileWrite(file_handle, daily_max_dd_target); FileWrite(file_handle, daily_dd_limit_reached); FileClose(file_handle); Print(data_file, " written"); return true; } bool DrawdownControl::print_messages() { if(print_statments == true) { Print("TimeCurrent(): ", TimeToString(TimeCurrent())); Print("Daily Equity Start: ", (int)daily_equity_start); Print("Current Equity: ", (int)AccountInfoDouble(ACCOUNT_EQUITY)); Print("Daily Drawdown Limit: ", (int)daily_max_dd_target, " (", daily_max_dd_per, "%) of DES"); Print("Daily Drawdown Limit Hit: ", daily_dd_limit_reached); } return true; }