256 lines
9.8 KiB
Plaintext
256 lines
9.8 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| 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 <Trade/Trade.mqh>
|
|
#include <MyLibs/Utils/MarketDataUtils.mqh>
|
|
|
|
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;
|
|
}
|