4586 lines
155 KiB
Plaintext
4586 lines
155 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| Universal Buffer Reader EA v2.0 |
|
|
//| Combined Single File Version |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025"
|
|
#property link ""
|
|
#property version "2.0"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\OrderInfo.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CLoggingManager |
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLoggingManager - Smart logging with error deduplication |
|
|
//+------------------------------------------------------------------+
|
|
class CLoggingManager
|
|
{
|
|
private:
|
|
bool m_enable_debug;
|
|
string m_session_id;
|
|
|
|
// Error tracking for deduplication
|
|
struct ErrorRecord
|
|
{
|
|
int error_code;
|
|
string error_message;
|
|
int count;
|
|
datetime first_seen;
|
|
datetime last_seen;
|
|
};
|
|
|
|
ErrorRecord m_error_records[];
|
|
int m_max_error_records;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CLoggingManager()
|
|
{
|
|
m_enable_debug = false;
|
|
m_session_id = "";
|
|
m_max_error_records = 50;
|
|
ArrayResize(m_error_records, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CLoggingManager()
|
|
{
|
|
ArrayResize(m_error_records, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
m_session_id = GenerateSessionID();
|
|
ClearErrorRecords();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log info message |
|
|
//+------------------------------------------------------------------+
|
|
void LogInfo(string message)
|
|
{
|
|
Print("[INFO] ", message);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log warning message |
|
|
//+------------------------------------------------------------------+
|
|
void LogWarning(string message)
|
|
{
|
|
Print("[WARNING] ", message);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get error description |
|
|
//+------------------------------------------------------------------+
|
|
string ErrorDescription(int error_code)
|
|
{
|
|
switch(error_code)
|
|
{
|
|
case 0: return "No error";
|
|
case 1: return "No error, but result is unknown";
|
|
case 2: return "Common error";
|
|
case 3: return "Invalid parameters";
|
|
case 4: return "Trade server is busy";
|
|
case 5: return "Old version of the client terminal";
|
|
case 6: return "No connection with trade server";
|
|
case 7: return "Not enough rights";
|
|
case 8: return "Too frequent requests";
|
|
case 9: return "Malfunctional trade operation";
|
|
case 64: return "Account disabled";
|
|
case 65: return "Invalid account";
|
|
case 128: return "Trade timeout";
|
|
case 129: return "Invalid price";
|
|
case 130: return "Invalid stops";
|
|
case 131: return "Invalid volume";
|
|
case 132: return "Market is closed";
|
|
case 133: return "Trade is disabled";
|
|
case 134: return "Not enough money";
|
|
case 135: return "Price changed";
|
|
case 136: return "No prices";
|
|
case 137: return "Broker is busy";
|
|
case 138: return "New prices (requote)";
|
|
case 139: return "Order locked";
|
|
case 140: return "Long positions only allowed";
|
|
case 141: return "Too many requests";
|
|
case 145: return "Modification denied because order is too close to market";
|
|
case 146: return "Trade context is busy";
|
|
case 147: return "Expirations are denied by broker";
|
|
case 148: return "Too many orders";
|
|
case 149: return "Hedge is prohibited";
|
|
case 150: return "Prohibited by FIFO rules";
|
|
case 4000: return "No error";
|
|
case 4001: return "Wrong function pointer";
|
|
case 4002: return "Array index is out of range";
|
|
case 4003: return "No memory for function call stack";
|
|
case 4004: return "Recursive stack overflow";
|
|
case 4005: return "Not enough stack for parameter";
|
|
case 4006: return "No memory for parameter string";
|
|
case 4007: return "No memory for temp string";
|
|
case 4008: return "Not initialized string";
|
|
case 4009: return "Not initialized arraystring";
|
|
case 4010: return "No memory for arraystring";
|
|
case 4011: return "Too long string";
|
|
case 4012: return "Remainder from zero divide";
|
|
case 4013: return "Zero divide";
|
|
case 4014: return "Unknown command";
|
|
case 4015: return "Wrong jump (never generated error)";
|
|
case 4016: return "Not initialized array";
|
|
case 4017: return "DLL calls are not allowed";
|
|
case 4018: return "Cannot load library";
|
|
case 4019: return "Cannot call function";
|
|
case 4020: return "External function calls are not allowed";
|
|
case 4021: return "Not enough memory for temp string";
|
|
case 4022: return "System is busy (never generated error)";
|
|
case 4023: return "Internal error";
|
|
case 4024: return "Out of memory";
|
|
case 4025: return "Invalid pointer";
|
|
case 4026: return "Too long string (up to 256 characters)";
|
|
case 4027: return "Structures or classes containing objects are not allowed";
|
|
case 4028: return "Not enough memory for string";
|
|
case 4029: return "Not enough memory for arraystring";
|
|
case 4030: return "Not enough memory for array";
|
|
case 4031: return "Unknown object type";
|
|
case 4032: return "Invalid object type";
|
|
case 4033: return "Object is not initialized";
|
|
case 4034: return "Cannot apply delete operation";
|
|
case 4035: return "Too many objects";
|
|
case 4036: return "Cannot create object";
|
|
case 4037: return "Invalid object pointer";
|
|
case 4038: return "Too many array dimensions";
|
|
case 4039: return "Access to arrayindex is out of range";
|
|
case 4040: return "Custom indicator error";
|
|
case 4041: return "Incorrect series array using";
|
|
case 4042: return "Custom indicator error";
|
|
case 4043: return "Arrays are incompatible";
|
|
case 4044: return "Series array cannot be used as timeseries";
|
|
case 4045: return "Custom indicator error";
|
|
case 4046: return "Internal error";
|
|
case 4047: return "Custom indicator error";
|
|
case 4048: return "Internal error";
|
|
case 4049: return "String error";
|
|
case 4050: return "String error";
|
|
case 4051: return "String error";
|
|
case 4052: return "String error";
|
|
case 4053: return "String error";
|
|
case 4054: return "Too long string";
|
|
case 4055: return "String error";
|
|
case 4056: return "String error";
|
|
case 4057: return "String error";
|
|
case 4058: return "String error";
|
|
case 4059: return "String error";
|
|
case 4060: return "String error";
|
|
case 4061: return "Array error";
|
|
case 4062: return "Array error";
|
|
case 4063: return "Array error";
|
|
case 4064: return "Array error";
|
|
case 4065: return "Array error";
|
|
case 4066: return "Array error";
|
|
case 4067: return "Array error";
|
|
case 4068: return "Array error";
|
|
case 4069: return "String error";
|
|
case 4070: return "String error";
|
|
case 4071: return "String error";
|
|
case 4072: return "String error";
|
|
case 4073: return "String error";
|
|
case 4074: return "String error";
|
|
case 4075: return "String error";
|
|
case 4076: return "String error";
|
|
case 4077: return "String error";
|
|
case 4078: return "String error";
|
|
case 4079: return "String error";
|
|
case 4080: return "String error";
|
|
case 4081: return "Too many array dimensions";
|
|
case 4082: return "Too many array dimensions";
|
|
case 4083: return "Too many array dimensions";
|
|
case 4084: return "Too many array dimensions";
|
|
case 4085: return "Array error";
|
|
case 4086: return "Array error";
|
|
case 4087: return "Array error";
|
|
case 4088: return "Array error";
|
|
case 4089: return "Array error";
|
|
case 4090: return "Array error";
|
|
case 4091: return "Array error";
|
|
case 4092: return "Array error";
|
|
case 4093: return "Array error";
|
|
case 4094: return "Array error";
|
|
case 4095: return "Array error";
|
|
case 4096: return "Array error";
|
|
case 4097: return "Array error";
|
|
case 4098: return "Array error";
|
|
case 4099: return "Array error";
|
|
case 4100: return "Array error";
|
|
case 4101: return "Array error";
|
|
case 4102: return "Array error";
|
|
case 4103: return "Array error";
|
|
case 4104: return "Array error";
|
|
case 4105: return "Array error";
|
|
case 4106: return "Array error";
|
|
case 4107: return "Array error";
|
|
case 4108: return "Array error";
|
|
case 4109: return "Array error";
|
|
case 4110: return "Array error";
|
|
case 4111: return "Array error";
|
|
case 4112: return "Array error";
|
|
case 4113: return "Array error";
|
|
case 4114: return "Array error";
|
|
case 4115: return "Array error";
|
|
case 4116: return "Array error";
|
|
case 4117: return "Array error";
|
|
case 4118: return "Array error";
|
|
case 4119: return "Array error";
|
|
case 4200: return "Object is not exist";
|
|
case 4201: return "Unknown object property";
|
|
case 4202: return "Object is not exist";
|
|
case 4203: return "Unknown object type";
|
|
case 4204: return "No object name";
|
|
case 4205: return "Object coordinates error";
|
|
case 4206: return "No specified subwindow";
|
|
case 4207: return "Some object error";
|
|
default: return "Unknown error code: " + IntegerToString(error_code);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log error with deduplication |
|
|
//+------------------------------------------------------------------+
|
|
void LogError(int error_code, string context)
|
|
{
|
|
string error_message = ErrorDescription(error_code);
|
|
|
|
// Check if this error has been logged before
|
|
if(IsErrorDuplicate(error_code, error_message))
|
|
{
|
|
// Update existing record
|
|
for(int i = 0; i < ArraySize(m_error_records); i++)
|
|
{
|
|
if(m_error_records[i].error_code == error_code &&
|
|
m_error_records[i].error_message == error_message)
|
|
{
|
|
m_error_records[i].count++;
|
|
m_error_records[i].last_seen = TimeCurrent();
|
|
|
|
// Log with repetition count
|
|
Print("[ERROR] ", FormatErrorMessage(error_code, context, m_error_records[i].count));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// New error - add to tracking
|
|
AddErrorRecord(error_code, error_message);
|
|
Print("[ERROR] ", FormatErrorMessage(error_code, context, 1));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log debug message (only if enabled) |
|
|
//+------------------------------------------------------------------+
|
|
void LogDebug(string message)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[DEBUG] ", message);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clear error records |
|
|
//+------------------------------------------------------------------+
|
|
void ClearErrorRecords()
|
|
{
|
|
ArrayResize(m_error_records, 0);
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Check if error has been logged before |
|
|
//+------------------------------------------------------------------+
|
|
bool IsErrorDuplicate(int error_code, string error_message)
|
|
{
|
|
for(int i = 0; i < ArraySize(m_error_records); i++)
|
|
{
|
|
if(m_error_records[i].error_code == error_code &&
|
|
m_error_records[i].error_message == error_message)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Add error to tracking |
|
|
//+------------------------------------------------------------------+
|
|
void AddErrorRecord(int error_code, string error_message)
|
|
{
|
|
int size = ArraySize(m_error_records);
|
|
|
|
// Limit number of tracked errors
|
|
if(size >= m_max_error_records)
|
|
{
|
|
// Remove oldest error by shifting elements
|
|
for(int i = 0; i < size - 1; i++)
|
|
{
|
|
m_error_records[i] = m_error_records[i + 1];
|
|
}
|
|
size = m_max_error_records - 1;
|
|
}
|
|
|
|
ArrayResize(m_error_records, size + 1);
|
|
m_error_records[size].error_code = error_code;
|
|
m_error_records[size].error_message = error_message;
|
|
m_error_records[size].count = 1;
|
|
m_error_records[size].first_seen = TimeCurrent();
|
|
m_error_records[size].last_seen = TimeCurrent();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Format error message for display |
|
|
//+------------------------------------------------------------------+
|
|
string FormatErrorMessage(int error_code, string context, int count)
|
|
{
|
|
string error_msg = ErrorDescription(error_code);
|
|
string result = error_msg + " (Code: " + IntegerToString(error_code) + ")";
|
|
|
|
if(count > 1)
|
|
{
|
|
result += " [REPEATED " + IntegerToString(count) + "x]";
|
|
}
|
|
|
|
result += "\nContext: " + context;
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Generate session ID |
|
|
//+------------------------------------------------------------------+
|
|
string GenerateSessionID()
|
|
{
|
|
datetime now = TimeCurrent();
|
|
return TimeToString(now, TIME_DATE|TIME_MINUTES|TIME_SECONDS);
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CSignalDetector |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CSignalDetector - Reads indicator buffers and detects signals |
|
|
//+------------------------------------------------------------------+
|
|
class CSignalDetector
|
|
{
|
|
private:
|
|
string m_indicator_name;
|
|
int m_indicator_handle;
|
|
|
|
// Signal buffers
|
|
int m_buy_signal_buffer;
|
|
int m_sell_signal_buffer;
|
|
|
|
// SL/TP buffers (separate for BUY/SELL)
|
|
int m_buy_sl_buffer;
|
|
int m_buy_tp_buffers[];
|
|
int m_sell_sl_buffer;
|
|
int m_sell_tp_buffers[];
|
|
|
|
// ATR settings
|
|
int m_sltp_mode;
|
|
int m_atr_period;
|
|
int m_atr_handle;
|
|
double m_sl_atr_multiplier;
|
|
double m_tp_atr_multiplier;
|
|
int m_min_tp_pips;
|
|
|
|
// Symbol info
|
|
string m_symbol;
|
|
double m_pip_value;
|
|
int m_digits;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Signal data structure with multiple TPs |
|
|
//+------------------------------------------------------------------+
|
|
struct SignalData
|
|
{
|
|
bool has_signal;
|
|
bool is_buy;
|
|
double signal_price;
|
|
double sl_price;
|
|
double tp_prices[];
|
|
int tp_count;
|
|
bool used_atr_fallback;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSignalDetector()
|
|
{
|
|
m_indicator_name = "";
|
|
m_indicator_handle = INVALID_HANDLE;
|
|
m_buy_signal_buffer = 0;
|
|
m_sell_signal_buffer = 1;
|
|
m_buy_sl_buffer = 2;
|
|
m_sell_sl_buffer = 6;
|
|
m_sltp_mode = 0;
|
|
m_atr_period = 14;
|
|
m_atr_handle = INVALID_HANDLE;
|
|
m_sl_atr_multiplier = 1.5;
|
|
m_tp_atr_multiplier = 3.0;
|
|
m_min_tp_pips = 300;
|
|
m_symbol = "";
|
|
m_pip_value = 0;
|
|
m_digits = 0;
|
|
m_enable_debug = false;
|
|
|
|
ArrayResize(m_buy_tp_buffers, 0);
|
|
ArrayResize(m_sell_tp_buffers, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CSignalDetector()
|
|
{
|
|
Deinitialize();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize indicator handles |
|
|
//+------------------------------------------------------------------+
|
|
bool Initialize()
|
|
{
|
|
Print("[SignalDetector] Initializing...");
|
|
|
|
// Initialize custom indicator handle
|
|
if(m_indicator_name != "" && m_indicator_name != "My_Indicator")
|
|
{
|
|
m_indicator_handle = iCustom(m_symbol, PERIOD_CURRENT, m_indicator_name);
|
|
if(m_indicator_handle == INVALID_HANDLE)
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to create indicator handle for: ", m_indicator_name,
|
|
" Error code: ", error);
|
|
return false;
|
|
}
|
|
Print("[SignalDetector] Custom indicator handle created: ", m_indicator_name);
|
|
}
|
|
else
|
|
{
|
|
Print("[SignalDetector] Using ATR mode only (no custom indicator)");
|
|
}
|
|
|
|
// Initialize ATR handle
|
|
m_atr_handle = iATR(m_symbol, PERIOD_CURRENT, m_atr_period);
|
|
if(m_atr_handle == INVALID_HANDLE)
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to create ATR handle. Error code: ", error);
|
|
return false;
|
|
}
|
|
Print("[SignalDetector] ATR handle created (Period: ", m_atr_period, ")");
|
|
|
|
Print("[SignalDetector] Initialization complete");
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Deinitialize indicator handles |
|
|
//+------------------------------------------------------------------+
|
|
void Deinitialize()
|
|
{
|
|
Print("[SignalDetector] Deinitializing...");
|
|
|
|
if(m_indicator_handle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(m_indicator_handle);
|
|
m_indicator_handle = INVALID_HANDLE;
|
|
Print("[SignalDetector] Custom indicator handle released");
|
|
}
|
|
|
|
if(m_atr_handle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(m_atr_handle);
|
|
m_atr_handle = INVALID_HANDLE;
|
|
Print("[SignalDetector] ATR handle released");
|
|
}
|
|
|
|
ArrayResize(m_buy_tp_buffers, 0);
|
|
ArrayResize(m_sell_tp_buffers, 0);
|
|
|
|
Print("[SignalDetector] Deinitialization complete");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
string indicator_name,
|
|
int buy_signal_buffer, int sell_signal_buffer,
|
|
int buy_sl_buffer, int sell_sl_buffer,
|
|
int sltp_mode, int atr_period,
|
|
double sl_atr_multiplier, double tp_atr_multiplier,
|
|
int min_tp_pips, double pip_value, int digits,
|
|
string symbol,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
m_indicator_name = indicator_name;
|
|
m_buy_signal_buffer = buy_signal_buffer;
|
|
m_sell_signal_buffer = sell_signal_buffer;
|
|
m_buy_sl_buffer = buy_sl_buffer;
|
|
m_sell_sl_buffer = sell_sl_buffer;
|
|
m_sltp_mode = sltp_mode;
|
|
m_atr_period = atr_period;
|
|
m_sl_atr_multiplier = sl_atr_multiplier;
|
|
m_tp_atr_multiplier = tp_atr_multiplier;
|
|
m_min_tp_pips = min_tp_pips;
|
|
m_pip_value = pip_value;
|
|
m_digits = digits;
|
|
m_symbol = symbol;
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set TP buffers (can have multiple) |
|
|
//+------------------------------------------------------------------+
|
|
void SetBuyTPBuffers(int &buffers[])
|
|
{
|
|
ArrayCopy(m_buy_tp_buffers, buffers);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set TP buffers (can have multiple) |
|
|
//+------------------------------------------------------------------+
|
|
void SetSellTPBuffers(int &buffers[])
|
|
{
|
|
ArrayCopy(m_sell_tp_buffers, buffers);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Detect signal from indicator buffers |
|
|
//+------------------------------------------------------------------+
|
|
SignalData DetectSignal(int bar_shift = 1)
|
|
{
|
|
SignalData result;
|
|
result.has_signal = false;
|
|
result.is_buy = false;
|
|
result.signal_price = 0;
|
|
result.sl_price = 0;
|
|
result.tp_count = 0;
|
|
result.used_atr_fallback = false;
|
|
ArrayResize(result.tp_prices, 0);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] Detecting signal at bar shift: ", bar_shift);
|
|
}
|
|
|
|
// Read buy/sell signal buffers
|
|
double buy_signal = GetIndicatorBufferValue(m_buy_signal_buffer, bar_shift);
|
|
double sell_signal = GetIndicatorBufferValue(m_sell_signal_buffer, bar_shift);
|
|
|
|
// Determine signal type
|
|
bool has_buy = (buy_signal != EMPTY_VALUE && buy_signal != 0);
|
|
bool has_sell = (sell_signal != EMPTY_VALUE && sell_signal != 0);
|
|
|
|
if(!has_buy && !has_sell)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] No signal detected");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Validate: Both buy and sell signals should not be present
|
|
if(has_buy && has_sell)
|
|
{
|
|
Print("[WARNING] Both BUY and SELL signals detected. Using BUY signal.");
|
|
has_sell = false;
|
|
}
|
|
|
|
result.has_signal = true;
|
|
result.is_buy = has_buy;
|
|
result.signal_price = has_buy ? buy_signal : sell_signal;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] ", (has_buy ? "BUY" : "SELL"), " signal detected at: ",
|
|
DoubleToString(result.signal_price, m_digits));
|
|
}
|
|
|
|
// Calculate SL/TP based on mode
|
|
if(m_sltp_mode == 1)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] Using Mode 1: Indicator SL/TP buffers");
|
|
}
|
|
|
|
// Try indicator buffers first
|
|
double sl_buffer = has_buy ?
|
|
GetIndicatorBufferValue(m_buy_sl_buffer, bar_shift) :
|
|
GetIndicatorBufferValue(m_sell_sl_buffer, bar_shift);
|
|
|
|
if(IsValidPrice(sl_buffer))
|
|
{
|
|
result.sl_price = sl_buffer;
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] SL from indicator: ", DoubleToString(result.sl_price, m_digits));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] SL buffer empty, will use ATR fallback");
|
|
}
|
|
}
|
|
|
|
// Read TP buffers
|
|
int tp_count;
|
|
if(has_buy)
|
|
tp_count = ArraySize(m_buy_tp_buffers);
|
|
else
|
|
tp_count = ArraySize(m_sell_tp_buffers);
|
|
|
|
if(tp_count > 0)
|
|
{
|
|
ArrayResize(result.tp_prices, tp_count);
|
|
result.tp_count = 0;
|
|
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
int buffer_index;
|
|
if(has_buy)
|
|
buffer_index = m_buy_tp_buffers[i];
|
|
else
|
|
buffer_index = m_sell_tp_buffers[i];
|
|
|
|
double tp_buffer = GetIndicatorBufferValue(buffer_index, bar_shift);
|
|
if(IsValidPrice(tp_buffer))
|
|
{
|
|
result.tp_prices[result.tp_count] = tp_buffer;
|
|
result.tp_count++;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] TP", (i + 1), " from indicator:",
|
|
DoubleToString(tp_buffer, m_digits));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] Using Mode 0: ATR-based SL/TP");
|
|
}
|
|
}
|
|
|
|
// Fall back to ATR if SL/TP not set
|
|
if(result.sl_price == 0 || result.tp_count == 0)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] SL or TP not set from indicator, using ATR fallback");
|
|
}
|
|
|
|
double atr = GetATRValue(bar_shift);
|
|
if(atr > 0)
|
|
{
|
|
double open_price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
|
|
result.sl_price = CalculateATRSL(has_buy, atr, open_price);
|
|
CalculateATRTPs(has_buy, atr, open_price, result.tp_prices, result.tp_count);
|
|
result.used_atr_fallback = true;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] ATR value: ", DoubleToString(atr, m_digits));
|
|
Print("[SignalDetector] SL from ATR: ", DoubleToString(result.sl_price, m_digits));
|
|
for(int i = 0; i < result.tp_count; i++)
|
|
{
|
|
Print("[SignalDetector] TP", (i + 1), " from ATR: ",
|
|
DoubleToString(result.tp_prices[i], m_digits));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] ATR value is 0, cannot calculate SL/TP");
|
|
}
|
|
}
|
|
|
|
// Apply minimum TP
|
|
ApplyMinimumTP(has_buy, result.tp_prices, result.tp_count, result.signal_price);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] Signal detection complete. SL: ", DoubleToString(result.sl_price, m_digits),
|
|
", TPs: ", result.tp_count, ", ATR fallback: ", (result.used_atr_fallback ? "Yes" : "No"));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get ATR value |
|
|
//+------------------------------------------------------------------+
|
|
double GetATRValue(int bar_shift = 1)
|
|
{
|
|
if(m_atr_handle == INVALID_HANDLE)
|
|
{
|
|
Print("[ERROR] ATR handle is invalid");
|
|
return 0;
|
|
}
|
|
|
|
double atr[];
|
|
ArraySetAsSeries(atr, true);
|
|
|
|
int copied = CopyBuffer(m_atr_handle, 0, bar_shift, 1, atr);
|
|
if(copied <= 0)
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to copy ATR buffer. Error code: ", error);
|
|
return 0;
|
|
}
|
|
|
|
if(atr[0] <= 0)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] ATR value is 0 or negative: ", atr[0]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return atr[0];
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Get indicator buffer value |
|
|
//+------------------------------------------------------------------+
|
|
double GetIndicatorBufferValue(int buffer_index, int bar_shift)
|
|
{
|
|
if(m_indicator_handle == INVALID_HANDLE)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] Indicator handle is invalid");
|
|
}
|
|
return EMPTY_VALUE;
|
|
}
|
|
|
|
if(buffer_index < 0)
|
|
{
|
|
Print("[ERROR] Invalid buffer index: ", buffer_index);
|
|
return EMPTY_VALUE;
|
|
}
|
|
|
|
double buffer[];
|
|
ArraySetAsSeries(buffer, true);
|
|
|
|
int copied = CopyBuffer(m_indicator_handle, buffer_index, bar_shift, 1, buffer);
|
|
if(copied <= 0)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
int error = GetLastError();
|
|
Print("[SignalDetector] Failed to copy buffer ", buffer_index,
|
|
". Error code: ", error);
|
|
}
|
|
return EMPTY_VALUE;
|
|
}
|
|
|
|
return buffer[0];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate ATR-based SL |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateATRSL(bool is_buy, double atr_value, double open_price)
|
|
{
|
|
if(atr_value <= 0) return 0;
|
|
|
|
if(is_buy)
|
|
{
|
|
return NormalizeDouble(open_price - atr_value * m_sl_atr_multiplier, m_digits);
|
|
}
|
|
else
|
|
{
|
|
return NormalizeDouble(open_price + atr_value * m_sl_atr_multiplier, m_digits);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate ATR-based TPs |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateATRTPs(bool is_buy, double atr_value, double open_price, double &tp_prices[], int &tp_count)
|
|
{
|
|
if(atr_value <= 0) return;
|
|
|
|
// Default to 3 TPs if using ATR
|
|
int num_tps = 3;
|
|
ArrayResize(tp_prices, num_tps);
|
|
tp_count = num_tps;
|
|
|
|
for(int i = 0; i < num_tps; i++)
|
|
{
|
|
double multiplier = m_tp_atr_multiplier * (i + 1);
|
|
|
|
if(is_buy)
|
|
{
|
|
tp_prices[i] = NormalizeDouble(open_price + atr_value * multiplier, m_digits);
|
|
}
|
|
else
|
|
{
|
|
tp_prices[i] = NormalizeDouble(open_price - atr_value * multiplier, m_digits);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Apply minimum TP to all TPs |
|
|
//+------------------------------------------------------------------+
|
|
void ApplyMinimumTP(bool is_buy, double &tp_prices[], int tp_count, double open_price)
|
|
{
|
|
if(m_min_tp_pips <= 0)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] Minimum TP disabled (0 pips)");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(tp_count == 0)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] No TPs to apply minimum TP");
|
|
}
|
|
return;
|
|
}
|
|
|
|
int adjusted_count = 0;
|
|
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
if(is_buy)
|
|
{
|
|
double min_tp = NormalizeDouble(open_price + m_min_tp_pips * m_pip_value, m_digits);
|
|
if(tp_prices[i] < min_tp)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] TP", (i + 1), " adjusted to minimum: ",
|
|
DoubleToString(tp_prices[i], m_digits), " -> ",
|
|
DoubleToString(min_tp, m_digits));
|
|
}
|
|
tp_prices[i] = min_tp;
|
|
adjusted_count++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double min_tp = NormalizeDouble(open_price - m_min_tp_pips * m_pip_value, m_digits);
|
|
if(tp_prices[i] > min_tp)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[SignalDetector] TP", (i + 1), " adjusted to minimum: ",
|
|
DoubleToString(tp_prices[i], m_digits), " -> ",
|
|
DoubleToString(min_tp, m_digits));
|
|
}
|
|
tp_prices[i] = min_tp;
|
|
adjusted_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_enable_debug && adjusted_count > 0)
|
|
{
|
|
Print("[SignalDetector] Minimum TP applied to ", adjusted_count, " TP(s)");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if price is valid |
|
|
//+------------------------------------------------------------------+
|
|
bool IsValidPrice(double price)
|
|
{
|
|
return (price != EMPTY_VALUE && price != 0 && price > 0);
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CTimeFilter |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CTimeFilter - Validates day of week and trading session filters |
|
|
//+------------------------------------------------------------------+
|
|
class CTimeFilter
|
|
{
|
|
private:
|
|
bool m_enabled;
|
|
bool m_days_enabled[7]; // 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
bool m_asian_enabled;
|
|
bool m_europe_enabled;
|
|
bool m_america_enabled;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CTimeFilter()
|
|
{
|
|
m_enabled = false;
|
|
m_enable_debug = false;
|
|
|
|
// Default: Mon-Fri enabled
|
|
for(int i = 0; i < 7; i++)
|
|
{
|
|
m_days_enabled[i] = (i >= 1 && i <= 5);
|
|
}
|
|
|
|
m_asian_enabled = true;
|
|
m_europe_enabled = true;
|
|
m_america_enabled = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CTimeFilter()
|
|
{
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
bool enabled,
|
|
bool mon, bool tue, bool wed, bool thu, bool fri, bool sat, bool sun,
|
|
bool asian, bool europe, bool america,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
m_enabled = enabled;
|
|
m_days_enabled[1] = mon; // Monday
|
|
m_days_enabled[2] = tue; // Tuesday
|
|
m_days_enabled[3] = wed; // Wednesday
|
|
m_days_enabled[4] = thu; // Thursday
|
|
m_days_enabled[5] = fri; // Friday
|
|
m_days_enabled[6] = sat; // Saturday
|
|
m_days_enabled[0] = sun; // Sunday
|
|
|
|
m_asian_enabled = asian;
|
|
m_europe_enabled = europe;
|
|
m_america_enabled = america;
|
|
m_enable_debug = enable_debug;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TimeFilter] Parameters set - Enabled: ", m_enabled);
|
|
Print("[TimeFilter] Days: Mon=", mon, " Tue=", tue, " Wed=", wed,
|
|
" Thu=", thu, " Fri=", fri, " Sat=", sat, " Sun=", sun);
|
|
Print("[TimeFilter] Sessions: Asian=", asian, " Europe=", europe, " America=", america);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if current time is allowed for trading |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTimeAllowed()
|
|
{
|
|
if(!m_enabled)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TimeFilter] Time filtering disabled - Trading allowed");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool day_allowed = IsDayOfWeekAllowed();
|
|
bool session_allowed = IsSessionTimeAllowed();
|
|
|
|
bool result = (day_allowed && session_allowed);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TimeFilter] Time check - Day allowed: ", day_allowed,
|
|
", Session allowed: ", session_allowed,
|
|
", Result: ", (result ? "ALLOWED" : "BLOCKED"));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current day of week name |
|
|
//+------------------------------------------------------------------+
|
|
string GetCurrentDayOfWeek()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeGMT(), dt);
|
|
|
|
string day_names[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
|
|
return day_names[dt.day_of_week];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current session name |
|
|
//+------------------------------------------------------------------+
|
|
string GetCurrentSession()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeGMT(), dt);
|
|
int hour = dt.hour;
|
|
|
|
if(IsAsianSession(hour)) return "Asian";
|
|
if(IsEuropeSession(hour)) return "Europe";
|
|
if(IsAmericaSession(hour)) return "America";
|
|
|
|
return "Off-hours";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current GMT time as string |
|
|
//+------------------------------------------------------------------+
|
|
string GetCurrentGMTTime()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeGMT(), dt);
|
|
|
|
return StringFormat("%02d:%02d:%02d GMT", dt.hour, dt.min, dt.sec);
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Check if current day of week is allowed |
|
|
//+------------------------------------------------------------------+
|
|
bool IsDayOfWeekAllowed()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeGMT(), dt);
|
|
|
|
string day_names[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
|
|
bool allowed = m_days_enabled[dt.day_of_week];
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TimeFilter] Day of week: ", day_names[dt.day_of_week],
|
|
" (", dt.day_of_week, ") - ", (allowed ? "ALLOWED" : "BLOCKED"));
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if current session is allowed |
|
|
//+------------------------------------------------------------------+
|
|
bool IsSessionTimeAllowed()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeGMT(), dt);
|
|
int hour = dt.hour;
|
|
|
|
bool in_asian = IsAsianSession(hour);
|
|
bool in_europe = IsEuropeSession(hour);
|
|
bool in_america = IsAmericaSession(hour);
|
|
|
|
bool allowed = false;
|
|
string active_session = "None";
|
|
|
|
if(in_asian && m_asian_enabled)
|
|
{
|
|
allowed = true;
|
|
active_session = "Asian";
|
|
}
|
|
else if(in_europe && m_europe_enabled)
|
|
{
|
|
allowed = true;
|
|
active_session = "Europe";
|
|
}
|
|
else if(in_america && m_america_enabled)
|
|
{
|
|
allowed = true;
|
|
active_session = "America";
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TimeFilter] Current time: ", dt.hour, ":00 GMT");
|
|
Print("[TimeFilter] In Asian session (00-08): ", in_asian, " - Enabled: ", m_asian_enabled);
|
|
Print("[TimeFilter] In Europe session (07-16): ", in_europe, " - Enabled: ", m_europe_enabled);
|
|
Print("[TimeFilter] In America session (13-22): ", in_america, " - Enabled: ", m_america_enabled);
|
|
Print("[TimeFilter] Active session: ", active_session, " - ", (allowed ? "ALLOWED" : "BLOCKED"));
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if Asian session (00:00 - 08:00 GMT) |
|
|
//+------------------------------------------------------------------+
|
|
bool IsAsianSession(int hour)
|
|
{
|
|
return (hour >= 0 && hour < 8);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if Europe session (07:00 - 16:00 GMT) |
|
|
//+------------------------------------------------------------------+
|
|
bool IsEuropeSession(int hour)
|
|
{
|
|
return (hour >= 7 && hour < 16);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if America session (13:00 - 22:00 GMT) |
|
|
//+------------------------------------------------------------------+
|
|
bool IsAmericaSession(int hour)
|
|
{
|
|
return (hour >= 13 && hour < 22);
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CMoneyManager |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CMoneyManager - Calculates lot sizes and manages daily profit |
|
|
//+------------------------------------------------------------------+
|
|
class CMoneyManager
|
|
{
|
|
private:
|
|
double m_base_lot_size;
|
|
bool m_use_percent_balance;
|
|
double m_percent_of_balance_for_profit;
|
|
double m_daily_profit_target_percent;
|
|
|
|
double m_min_lot;
|
|
double m_max_lot;
|
|
double m_lot_step;
|
|
double m_point_value;
|
|
double m_tick_value;
|
|
|
|
// State
|
|
double m_daily_profit_accumulated;
|
|
double m_daily_start_balance;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMoneyManager()
|
|
{
|
|
m_base_lot_size = 0.03;
|
|
m_use_percent_balance = true;
|
|
m_percent_of_balance_for_profit = 1.0;
|
|
m_daily_profit_target_percent = 2.0;
|
|
|
|
m_min_lot = 0.01;
|
|
m_max_lot = 100.0;
|
|
m_lot_step = 0.01;
|
|
m_point_value = 0;
|
|
m_tick_value = 0;
|
|
|
|
m_daily_profit_accumulated = 0;
|
|
m_daily_start_balance = 0;
|
|
m_enable_debug = false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CMoneyManager()
|
|
{
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
double base_lot_size,
|
|
bool use_percent_balance, double percent_of_balance_for_profit,
|
|
double daily_profit_target_percent,
|
|
double min_lot, double max_lot, double lot_step,
|
|
double point_value, double tick_value,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
// Validate parameters
|
|
if(base_lot_size <= 0)
|
|
{
|
|
Print("[ERROR] Invalid base lot size: ", base_lot_size, ". Using default 0.01");
|
|
m_base_lot_size = 0.01;
|
|
}
|
|
else
|
|
{
|
|
m_base_lot_size = base_lot_size;
|
|
}
|
|
|
|
if(percent_of_balance_for_profit <= 0)
|
|
{
|
|
Print("[ERROR] Invalid percent of balance for profit: ", percent_of_balance_for_profit, ". Using default 1.0");
|
|
m_percent_of_balance_for_profit = 1.0;
|
|
}
|
|
else
|
|
{
|
|
m_percent_of_balance_for_profit = percent_of_balance_for_profit;
|
|
}
|
|
|
|
if(daily_profit_target_percent < 0)
|
|
{
|
|
Print("[ERROR] Invalid daily profit target percent: ", daily_profit_target_percent, ". Using default 2.0");
|
|
m_daily_profit_target_percent = 2.0;
|
|
}
|
|
else
|
|
{
|
|
m_daily_profit_target_percent = daily_profit_target_percent;
|
|
}
|
|
|
|
if(min_lot <= 0)
|
|
{
|
|
Print("[ERROR] Invalid min lot: ", min_lot, ". Using default 0.01");
|
|
m_min_lot = 0.01;
|
|
}
|
|
else
|
|
{
|
|
m_min_lot = min_lot;
|
|
}
|
|
|
|
if(max_lot <= 0 || max_lot < min_lot)
|
|
{
|
|
Print("[ERROR] Invalid max lot: ", max_lot, ". Using default 100.0");
|
|
m_max_lot = 100.0;
|
|
}
|
|
else
|
|
{
|
|
m_max_lot = max_lot;
|
|
}
|
|
|
|
if(lot_step <= 0)
|
|
{
|
|
Print("[ERROR] Invalid lot step: ", lot_step, ". Using default 0.01");
|
|
m_lot_step = 0.01;
|
|
}
|
|
else
|
|
{
|
|
m_lot_step = lot_step;
|
|
}
|
|
|
|
if(point_value <= 0)
|
|
{
|
|
Print("[ERROR] Invalid point value: ", point_value);
|
|
}
|
|
m_point_value = point_value;
|
|
|
|
if(tick_value <= 0)
|
|
{
|
|
Print("[ERROR] Invalid tick value: ", tick_value);
|
|
}
|
|
m_tick_value = tick_value;
|
|
|
|
m_use_percent_balance = use_percent_balance;
|
|
m_enable_debug = enable_debug;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Parameters set:");
|
|
Print(" Base lot size: ", m_base_lot_size);
|
|
Print(" Use % balance: ", m_use_percent_balance);
|
|
Print(" % of balance for profit: ", m_percent_of_balance_for_profit);
|
|
Print(" Daily profit target %: ", m_daily_profit_target_percent);
|
|
Print(" Min lot: ", m_min_lot, ", Max lot: ", m_max_lot, ", Lot step: ", m_lot_step);
|
|
Print(" Point value: ", m_point_value, ", Tick value: ", m_tick_value);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate lot size (pure function) |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateLotSize(
|
|
bool is_buy,
|
|
double open_price,
|
|
double tp_price,
|
|
double account_balance
|
|
)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Calculating lot size...");
|
|
Print(" Direction: ", (is_buy ? "BUY" : "SELL"));
|
|
Print(" Open price: ", open_price);
|
|
Print(" TP price: ", tp_price);
|
|
Print(" Account balance: ", account_balance);
|
|
}
|
|
|
|
// Validate inputs
|
|
if(open_price <= 0)
|
|
{
|
|
Print("[ERROR] Invalid open price: ", open_price);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
if(tp_price <= 0)
|
|
{
|
|
Print("[ERROR] Invalid TP price: ", tp_price);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
if(account_balance <= 0)
|
|
{
|
|
Print("[ERROR] Invalid account balance: ", account_balance);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
// Calculate TP points
|
|
double tp_points = 0;
|
|
if(is_buy)
|
|
{
|
|
tp_points = (tp_price - open_price) / m_point_value;
|
|
}
|
|
else
|
|
{
|
|
tp_points = (open_price - tp_price) / m_point_value;
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" TP points: ", tp_points);
|
|
}
|
|
|
|
if(tp_points <= 0)
|
|
{
|
|
Print("[WARNING] TP points <= 0. Using base lot size: ", m_base_lot_size);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
// Calculate base lot
|
|
double base_lot = m_base_lot_size;
|
|
if(m_use_percent_balance)
|
|
{
|
|
base_lot = CalculateBaseLot(tp_points, account_balance);
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Base lot from % balance: ", base_lot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Using fixed base lot: ", base_lot);
|
|
}
|
|
}
|
|
|
|
// Normalize and return
|
|
double normalized_lot = NormalizeLotSize(base_lot);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Normalized lot: ", normalized_lot);
|
|
Print("[MoneyManager] Lot size calculation complete: ", normalized_lot);
|
|
}
|
|
|
|
return normalized_lot;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset daily profit tracking |
|
|
//+------------------------------------------------------------------+
|
|
void ResetDailyProfit(double current_balance)
|
|
{
|
|
if(current_balance <= 0)
|
|
{
|
|
Print("[ERROR] Invalid current balance for daily profit reset: ", current_balance);
|
|
return;
|
|
}
|
|
|
|
m_daily_start_balance = current_balance;
|
|
m_daily_profit_accumulated = 0;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Daily profit tracking reset");
|
|
Print(" Start balance: ", m_daily_start_balance);
|
|
Print(" Target profit: ", GetDailyProfitTarget());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if daily profit target reached |
|
|
//+------------------------------------------------------------------+
|
|
bool IsDailyProfitTargetReached()
|
|
{
|
|
if(m_daily_profit_target_percent <= 0)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Daily profit target disabled (0%)");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
double target_profit = GetDailyProfitTarget();
|
|
bool reached = (m_daily_profit_accumulated >= target_profit);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Daily profit check:");
|
|
Print(" Accumulated: ", m_daily_profit_accumulated);
|
|
Print(" Target: ", target_profit);
|
|
Print(" Reached: ", (reached ? "YES" : "NO"));
|
|
}
|
|
|
|
return reached;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get daily profit target |
|
|
//+------------------------------------------------------------------+
|
|
double GetDailyProfitTarget()
|
|
{
|
|
if(m_daily_start_balance <= 0) return 0;
|
|
return m_daily_start_balance * m_daily_profit_target_percent / 100.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get daily profit accumulated |
|
|
//+------------------------------------------------------------------+
|
|
double GetDailyProfitAccumulated()
|
|
{
|
|
return m_daily_profit_accumulated;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get daily profit percentage |
|
|
//+------------------------------------------------------------------+
|
|
double GetDailyProfitPercent()
|
|
{
|
|
if(m_daily_start_balance <= 0) return 0;
|
|
return (m_daily_profit_accumulated / m_daily_start_balance) * 100.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set daily profit accumulated (for tracking) |
|
|
//+------------------------------------------------------------------+
|
|
void SetDailyProfitAccumulated(double value)
|
|
{
|
|
m_daily_profit_accumulated = value;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Daily profit accumulated set to: ", value);
|
|
}
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate base lot based on % balance |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateBaseLot(double tp_points, double account_balance)
|
|
{
|
|
if(tp_points <= 0)
|
|
{
|
|
Print("[ERROR] Invalid TP points for base lot calculation: ", tp_points);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
if(m_tick_value <= 0)
|
|
{
|
|
Print("[ERROR] Invalid tick value for base lot calculation: ", m_tick_value);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
if(account_balance <= 0)
|
|
{
|
|
Print("[ERROR] Invalid account balance for base lot calculation: ", account_balance);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
double target_profit = account_balance * (m_percent_of_balance_for_profit / 100.0);
|
|
double profit_per_lot = tp_points * m_tick_value;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Base lot calculation:");
|
|
Print(" Target profit: ", target_profit, " (", m_percent_of_balance_for_profit, "% of balance)");
|
|
Print(" Profit per lot: ", profit_per_lot);
|
|
}
|
|
|
|
if(profit_per_lot <= 0)
|
|
{
|
|
Print("[ERROR] Invalid profit per lot: ", profit_per_lot);
|
|
return m_base_lot_size;
|
|
}
|
|
|
|
double lot = target_profit / profit_per_lot;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Calculated lot: ", lot);
|
|
}
|
|
|
|
return lot;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize lot size to broker requirements |
|
|
//+------------------------------------------------------------------+
|
|
double NormalizeLotSize(double lot)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[MoneyManager] Normalizing lot size: ", lot);
|
|
}
|
|
|
|
// Round to lot step
|
|
double rounded_lot = MathFloor(lot / m_lot_step) * m_lot_step;
|
|
|
|
if(m_enable_debug && rounded_lot != lot)
|
|
{
|
|
Print(" Rounded to lot step: ", rounded_lot, " (step: ", m_lot_step, ")");
|
|
}
|
|
|
|
// Ensure within min/max
|
|
double min_adjusted = MathMax(rounded_lot, m_min_lot);
|
|
double max_adjusted = MathMin(min_adjusted, m_max_lot);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
if(min_adjusted != rounded_lot)
|
|
{
|
|
Print(" Adjusted to min lot: ", min_adjusted, " (min: ", m_min_lot, ")");
|
|
}
|
|
if(max_adjusted != min_adjusted)
|
|
{
|
|
Print(" Adjusted to max lot: ", max_adjusted, " (max: ", m_max_lot, ")");
|
|
}
|
|
}
|
|
|
|
double normalized = NormalizeDouble(max_adjusted, 2);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Final normalized lot: ", normalized);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CRiskManager |
|
|
//+------------------------------------------------------------------+
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CRiskManager - Manages breakeven and trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManager
|
|
{
|
|
private:
|
|
bool m_use_trailing_stop;
|
|
int m_trailing_stop_pips;
|
|
bool m_use_breakeven;
|
|
int m_breakeven_pips;
|
|
|
|
double m_pip_value;
|
|
int m_digits;
|
|
double m_stop_level_points;
|
|
int m_magic_number;
|
|
string m_symbol;
|
|
|
|
CTrade *m_trade;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
// Partial close tracking
|
|
bool m_partial_close_enabled;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Validated SL/TP structure |
|
|
//+------------------------------------------------------------------+
|
|
struct ValidatedSLTP
|
|
{
|
|
double sl_price;
|
|
double tp_prices[];
|
|
int tp_count;
|
|
bool is_valid;
|
|
string error_message;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager()
|
|
{
|
|
m_use_trailing_stop = false;
|
|
m_trailing_stop_pips = 300;
|
|
m_use_breakeven = true;
|
|
m_breakeven_pips = 30;
|
|
|
|
m_pip_value = 0;
|
|
m_digits = 0;
|
|
m_stop_level_points = 0;
|
|
m_magic_number = 0;
|
|
m_symbol = "";
|
|
m_enable_debug = false;
|
|
m_partial_close_enabled = false;
|
|
|
|
m_trade = new CTrade();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CRiskManager()
|
|
{
|
|
if(m_trade != NULL)
|
|
{
|
|
delete m_trade;
|
|
m_trade = NULL;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
bool use_trailing_stop,
|
|
int trailing_stop_pips,
|
|
bool use_breakeven,
|
|
int breakeven_pips,
|
|
double pip_value,
|
|
int digits,
|
|
double stop_level_points,
|
|
int magic_number,
|
|
string symbol,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
// Validate parameters
|
|
if(trailing_stop_pips <= 0)
|
|
{
|
|
Print("[ERROR] Invalid trailing stop pips: ", trailing_stop_pips, ". Using default 300");
|
|
m_trailing_stop_pips = 300;
|
|
}
|
|
else
|
|
{
|
|
m_trailing_stop_pips = trailing_stop_pips;
|
|
}
|
|
|
|
if(breakeven_pips <= 0)
|
|
{
|
|
Print("[ERROR] Invalid breakeven pips: ", breakeven_pips, ". Using default 30");
|
|
m_breakeven_pips = 30;
|
|
}
|
|
else
|
|
{
|
|
m_breakeven_pips = breakeven_pips;
|
|
}
|
|
|
|
if(pip_value <= 0)
|
|
{
|
|
Print("[ERROR] Invalid pip value: ", pip_value);
|
|
}
|
|
m_pip_value = pip_value;
|
|
|
|
if(digits <= 0)
|
|
{
|
|
Print("[ERROR] Invalid digits: ", digits);
|
|
}
|
|
m_digits = digits;
|
|
|
|
if(stop_level_points < 0)
|
|
{
|
|
Print("[ERROR] Invalid stop level points: ", stop_level_points);
|
|
}
|
|
m_stop_level_points = stop_level_points;
|
|
|
|
m_use_trailing_stop = use_trailing_stop;
|
|
m_use_breakeven = use_breakeven;
|
|
m_magic_number = magic_number;
|
|
m_symbol = symbol;
|
|
m_enable_debug = enable_debug;
|
|
|
|
m_trade.SetExpertMagicNumber(m_magic_number);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Parameters set:");
|
|
Print(" Use trailing stop: ", m_use_trailing_stop, " (", m_trailing_stop_pips, " pips)");
|
|
Print(" Use breakeven: ", m_use_breakeven, " (", m_breakeven_pips, " pips)");
|
|
Print(" Pip value: ", m_pip_value, ", Digits: ", m_digits);
|
|
Print(" Stop level: ", m_stop_level_points, " points");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Enable partial close (for TP-based trailing) |
|
|
//+------------------------------------------------------------------+
|
|
void EnablePartialClose(bool enable)
|
|
{
|
|
m_partial_close_enabled = enable;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Partial close ", (enable ? "enabled" : "disabled"));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage breakeven and trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
void ManageRiskManagement()
|
|
{
|
|
// Iterate through all positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(!PositionSelectByTicket(PositionGetTicket(i))) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != m_symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue;
|
|
|
|
ulong ticket = PositionGetInteger(POSITION_TICKET);
|
|
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
bool is_buy = (pos_type == POSITION_TYPE_BUY);
|
|
double current_price = is_buy ?
|
|
SymbolInfoDouble(m_symbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(m_symbol, SYMBOL_ASK);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Checking position #", ticket);
|
|
Print(" Type: ", (is_buy ? "BUY" : "SELL"));
|
|
Print(" Open: ", open_price, ", Current: ", current_price, ", SL: ", current_sl);
|
|
}
|
|
|
|
// Check breakeven
|
|
if(m_use_breakeven && current_sl != open_price)
|
|
{
|
|
if(ShouldMoveToBreakeven(ticket, open_price, current_price))
|
|
{
|
|
TryMoveToBreakeven(ticket, open_price);
|
|
}
|
|
}
|
|
|
|
// Check TP-based trailing
|
|
if(m_partial_close_enabled) // Only if partial close is enabled
|
|
{
|
|
ManageTPBasedTrailing(ticket, is_buy, open_price, current_price, current_sl);
|
|
}
|
|
|
|
// Check standard trailing
|
|
if(m_use_trailing_stop)
|
|
{
|
|
ManageStandardTrailing(ticket, is_buy, open_price, current_price, current_sl);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate SL/TP to meet broker requirements |
|
|
//+------------------------------------------------------------------+
|
|
ValidatedSLTP ValidateSLTP(
|
|
bool is_buy,
|
|
double open_price,
|
|
double sl_price,
|
|
double &tp_prices[],
|
|
int tp_count
|
|
)
|
|
{
|
|
ValidatedSLTP result;
|
|
result.sl_price = sl_price;
|
|
result.tp_count = tp_count;
|
|
result.is_valid = true;
|
|
result.error_message = "";
|
|
|
|
ArrayResize(result.tp_prices, tp_count);
|
|
ArrayCopy(result.tp_prices, tp_prices);
|
|
|
|
// Validate SL
|
|
if(sl_price != 0)
|
|
{
|
|
result.sl_price = AdjustToStopLevel(is_buy, sl_price, open_price);
|
|
}
|
|
|
|
// Validate TPs
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
if(result.tp_prices[i] != 0)
|
|
{
|
|
result.tp_prices[i] = AdjustToStopLevel(is_buy, result.tp_prices[i], open_price);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Check if SL should move to breakeven |
|
|
//+------------------------------------------------------------------+
|
|
bool ShouldMoveToBreakeven(ulong ticket, double open_price, double current_price)
|
|
{
|
|
double profit_pips = 0;
|
|
|
|
// Get position type
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
profit_pips = (current_price - open_price) / m_pip_value;
|
|
}
|
|
else
|
|
{
|
|
profit_pips = (open_price - current_price) / m_pip_value;
|
|
}
|
|
|
|
return (profit_pips >= m_breakeven_pips);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Try to move SL to breakeven |
|
|
//+------------------------------------------------------------------+
|
|
bool TryMoveToBreakeven(ulong ticket, double open_price)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
if(current_sl == open_price) return true; // Already at breakeven
|
|
|
|
string comment = PositionGetString(POSITION_COMMENT);
|
|
|
|
if(m_trade.PositionModify(ticket, open_price, PositionGetDouble(POSITION_TP)))
|
|
{
|
|
Print("[RiskManager] Breakeven set for ticket #", ticket, " at ", open_price);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to set breakeven for ticket #", ticket,
|
|
". Error: ", m_trade.ResultRetcodeDescription());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage TP-based trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
void ManageTPBasedTrailing(ulong ticket, bool is_buy, double open_price, double current_price, double current_sl)
|
|
{
|
|
// Get TP prices from comment
|
|
double tp_prices[];
|
|
int tp_count = 0;
|
|
if(!GetTPPricesFromComment(ticket, tp_prices, tp_count)) return;
|
|
|
|
if(tp_count == 0) return;
|
|
|
|
// Check which TP level has been reached
|
|
int reached_tp_level = GetReachedTPLevel(is_buy, current_price, tp_prices, tp_count);
|
|
|
|
if(reached_tp_level == 0) return; // No TP reached yet
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] TP", reached_tp_level, " reached for ticket #", ticket);
|
|
}
|
|
|
|
// Determine new SL based on TP level
|
|
double new_sl = 0;
|
|
|
|
if(reached_tp_level == 1)
|
|
{
|
|
// TP1 reached: Move SL to breakeven
|
|
new_sl = open_price;
|
|
}
|
|
else if(reached_tp_level == 2)
|
|
{
|
|
// TP2 reached: Move SL to TP1
|
|
new_sl = tp_prices[0];
|
|
}
|
|
else if(reached_tp_level >= 3)
|
|
{
|
|
// TP3 reached: Move SL to TP2
|
|
new_sl = tp_prices[1];
|
|
}
|
|
|
|
// Check if SL should be moved
|
|
if(new_sl > 0)
|
|
{
|
|
bool should_move = false;
|
|
if(is_buy)
|
|
{
|
|
should_move = (new_sl > current_sl);
|
|
}
|
|
else
|
|
{
|
|
should_move = (new_sl < current_sl);
|
|
}
|
|
|
|
if(should_move)
|
|
{
|
|
TryMoveSL(ticket, new_sl, "TP" + IntegerToString(reached_tp_level));
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage standard trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
void ManageStandardTrailing(ulong ticket, bool is_buy, double open_price, double current_price, double current_sl)
|
|
{
|
|
// Calculate new SL based on trailing distance
|
|
double new_sl = CalculateNewSL(is_buy, current_price);
|
|
|
|
if(new_sl == 0) return;
|
|
|
|
// Ratchet rule: SL must only move in profit direction
|
|
bool should_move = false;
|
|
if(is_buy)
|
|
{
|
|
should_move = (new_sl > current_sl);
|
|
}
|
|
else
|
|
{
|
|
should_move = (new_sl < current_sl);
|
|
}
|
|
|
|
if(should_move)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Trailing SL for ticket #", ticket);
|
|
Print(" Current SL: ", current_sl, ", New SL: ", new_sl);
|
|
}
|
|
|
|
TryMoveSL(ticket, new_sl, "TRAIL");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get TP prices from position comment |
|
|
//+------------------------------------------------------------------+
|
|
bool GetTPPricesFromComment(ulong ticket, double &tp_prices[], int &tp_count)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
string comment = PositionGetString(POSITION_COMMENT);
|
|
tp_count = 0;
|
|
ArrayResize(tp_prices, 0);
|
|
|
|
// Parse comment for TP values
|
|
// Format: "UnivBufEA_24680_H1;TP1=1.2530;TP2=1.2560;TP3=1.2600;..."
|
|
|
|
int tp_index = 1;
|
|
while(true)
|
|
{
|
|
string search_str = ";TP" + IntegerToString(tp_index) + "=";
|
|
int pos = StringFind(comment, search_str);
|
|
|
|
if(pos == -1) break;
|
|
|
|
// Extract TP value
|
|
int start_pos = pos + StringLen(search_str);
|
|
int end_pos = StringFind(comment, ";", start_pos);
|
|
|
|
if(end_pos == -1) end_pos = StringLen(comment);
|
|
|
|
string tp_str = StringSubstr(comment, start_pos, end_pos - start_pos);
|
|
double tp_value = StringToDouble(tp_str);
|
|
|
|
ArrayResize(tp_prices, tp_count + 1);
|
|
tp_prices[tp_count] = tp_value;
|
|
tp_count++;
|
|
|
|
tp_index++;
|
|
}
|
|
|
|
return (tp_count > 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get reached TP level |
|
|
//+------------------------------------------------------------------+
|
|
int GetReachedTPLevel(bool is_buy, double current_price, double &tp_prices[], int tp_count)
|
|
{
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
if(tp_prices[i] == 0) continue;
|
|
|
|
if(is_buy)
|
|
{
|
|
if(current_price >= tp_prices[i]) return (i + 1);
|
|
}
|
|
else
|
|
{
|
|
if(current_price <= tp_prices[i]) return (i + 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate new SL for trailing |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateNewSL(bool is_buy, double current_price)
|
|
{
|
|
if(m_trailing_stop_pips <= 0) return 0;
|
|
|
|
double trailing_distance = m_trailing_stop_pips * m_pip_value;
|
|
|
|
if(is_buy)
|
|
{
|
|
return NormalizeDouble(current_price - trailing_distance, m_digits);
|
|
}
|
|
else
|
|
{
|
|
return NormalizeDouble(current_price + trailing_distance, m_digits);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Try to move SL |
|
|
//+------------------------------------------------------------------+
|
|
bool TryMoveSL(ulong ticket, double new_sl, string reason)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
double current_tp = PositionGetDouble(POSITION_TP);
|
|
|
|
if(m_trade.PositionModify(ticket, new_sl, current_tp))
|
|
{
|
|
Print("[RiskManager] SL moved for ticket #", ticket, " to ", new_sl, " (", reason, ")");
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to move SL for ticket #", ticket,
|
|
". Error: ", m_trade.ResultRetcodeDescription());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Adjust price to meet stop level requirements |
|
|
//+------------------------------------------------------------------+
|
|
double AdjustToStopLevel(bool is_buy, double price, double open_price)
|
|
{
|
|
double distance = 0;
|
|
|
|
if(is_buy)
|
|
{
|
|
if(price < open_price) // SL
|
|
{
|
|
distance = open_price - price;
|
|
}
|
|
else // TP
|
|
{
|
|
distance = price - open_price;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(price > open_price) // SL
|
|
{
|
|
distance = price - open_price;
|
|
}
|
|
else // TP
|
|
{
|
|
distance = open_price - price;
|
|
}
|
|
}
|
|
|
|
if(distance < m_stop_level_points)
|
|
{
|
|
distance = m_stop_level_points;
|
|
|
|
if(is_buy)
|
|
{
|
|
if(price < open_price) // SL
|
|
{
|
|
price = NormalizeDouble(open_price - distance, m_digits);
|
|
}
|
|
else // TP
|
|
{
|
|
price = NormalizeDouble(open_price + distance, m_digits);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(price > open_price) // SL
|
|
{
|
|
price = NormalizeDouble(open_price + distance, m_digits);
|
|
}
|
|
else // TP
|
|
{
|
|
price = NormalizeDouble(open_price - distance, m_digits);
|
|
}
|
|
}
|
|
}
|
|
|
|
return price;
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CPartialCloseManager |
|
|
//+------------------------------------------------------------------+
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CPartialCloseManager - Manages multiple TPs and partial closes |
|
|
//+------------------------------------------------------------------+
|
|
class CPartialCloseManager
|
|
{
|
|
private:
|
|
bool m_enable_partial_close;
|
|
bool m_use_equal_division;
|
|
double m_partial_close_percentages[];
|
|
|
|
int m_magic_number;
|
|
string m_symbol;
|
|
CTrade *m_trade;
|
|
|
|
// TP tracking to prevent duplicate closes
|
|
struct TPTracking
|
|
{
|
|
ulong ticket;
|
|
int last_closed_tp;
|
|
datetime last_check_time;
|
|
};
|
|
|
|
TPTracking m_tp_tracking[];
|
|
int m_max_tracking_records;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| TP lot allocation structure |
|
|
//+------------------------------------------------------------------+
|
|
struct TPLotAllocation
|
|
{
|
|
double lots;
|
|
double percentage;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CPartialCloseManager()
|
|
{
|
|
m_enable_partial_close = true;
|
|
m_use_equal_division = true;
|
|
|
|
ArrayResize(m_partial_close_percentages, 0);
|
|
|
|
m_magic_number = 0;
|
|
m_symbol = "";
|
|
m_enable_debug = false;
|
|
m_max_tracking_records = 100;
|
|
|
|
ArrayResize(m_tp_tracking, 0);
|
|
|
|
m_trade = new CTrade();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CPartialCloseManager()
|
|
{
|
|
if(m_trade != NULL)
|
|
{
|
|
delete m_trade;
|
|
m_trade = NULL;
|
|
}
|
|
|
|
ArrayResize(m_partial_close_percentages, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
bool enable_partial_close,
|
|
bool use_equal_division,
|
|
double &partial_close_percentages[],
|
|
int magic_number,
|
|
string symbol,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
m_enable_partial_close = enable_partial_close;
|
|
m_use_equal_division = use_equal_division;
|
|
|
|
// Validate and copy percentages
|
|
int pct_count = ArraySize(partial_close_percentages);
|
|
ArrayResize(m_partial_close_percentages, pct_count);
|
|
|
|
double total_pct = 0;
|
|
for(int i = 0; i < pct_count; i++)
|
|
{
|
|
if(partial_close_percentages[i] <= 0)
|
|
{
|
|
Print("[WARNING] Invalid partial close percentage at index ", i, ": ",
|
|
partial_close_percentages[i], ". Using 0%");
|
|
m_partial_close_percentages[i] = 0;
|
|
}
|
|
else if(partial_close_percentages[i] > 100)
|
|
{
|
|
Print("[WARNING] Partial close percentage > 100% at index ", i, ": ",
|
|
partial_close_percentages[i], ". Using 100%");
|
|
m_partial_close_percentages[i] = 100;
|
|
}
|
|
else
|
|
{
|
|
m_partial_close_percentages[i] = partial_close_percentages[i];
|
|
}
|
|
|
|
total_pct += m_partial_close_percentages[i];
|
|
}
|
|
|
|
if(!m_use_equal_division && MathAbs(total_pct - 100.0) > 0.01)
|
|
{
|
|
Print("[WARNING] Partial close percentages don't sum to 100%: ", total_pct, "%");
|
|
}
|
|
|
|
m_magic_number = magic_number;
|
|
m_symbol = symbol;
|
|
m_enable_debug = enable_debug;
|
|
|
|
m_trade.SetExpertMagicNumber(m_magic_number);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] Parameters set:");
|
|
Print(" Enable partial close: ", m_enable_partial_close);
|
|
Print(" Use equal division: ", m_use_equal_division);
|
|
Print(" Partial close percentages: ", pct_count, " levels");
|
|
if(!m_use_equal_division)
|
|
{
|
|
for(int i = 0; i < pct_count; i++)
|
|
{
|
|
Print(" TP", (i + 1), ": ", m_partial_close_percentages[i], "%");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if any TP has been reached and execute partial close |
|
|
//+------------------------------------------------------------------+
|
|
void CheckAndExecutePartialCloses()
|
|
{
|
|
if(!m_enable_partial_close)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] Partial close disabled");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Iterate through all positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(!PositionSelectByTicket(PositionGetTicket(i))) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != m_symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue;
|
|
|
|
ulong ticket = PositionGetInteger(POSITION_TICKET);
|
|
double total_lots = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] Checking position #", ticket, " (", total_lots, " lots)");
|
|
}
|
|
|
|
// Get TP prices from comment
|
|
double tp_prices[];
|
|
int tp_count = 0;
|
|
if(!GetTPPricesFromComment(ticket, tp_prices, tp_count))
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] No TPs found in comment for ticket #", ticket);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] Found ", tp_count, " TP levels for ticket #", ticket);
|
|
}
|
|
|
|
// Get last closed TP for this ticket
|
|
int last_closed_tp = GetLastClosedTP(ticket);
|
|
|
|
// Check each TP level
|
|
for(int tp_index = last_closed_tp; tp_index < tp_count; tp_index++)
|
|
{
|
|
if(IsTPReached(ticket, tp_index, tp_prices))
|
|
{
|
|
// Calculate close lots
|
|
TPLotAllocation allocation = CalculateTPLotAllocation(total_lots, tp_index, tp_count);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] TP", (tp_index + 1), " reached for ticket #", ticket);
|
|
Print(" Close lots: ", allocation.lots, " (", allocation.percentage, "%)");
|
|
}
|
|
|
|
// Execute partial close
|
|
if(allocation.lots > 0)
|
|
{
|
|
if(ExecutePartialClose(ticket, allocation.lots, tp_index))
|
|
{
|
|
// Update tracking
|
|
UpdateTPTracking(ticket, tp_index + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate lot size for each TP level |
|
|
//+------------------------------------------------------------------+
|
|
TPLotAllocation CalculateTPLotAllocation(double total_lots, int tp_index, int total_tp_count)
|
|
{
|
|
TPLotAllocation result;
|
|
result.lots = 0;
|
|
result.percentage = 0;
|
|
|
|
if(m_use_equal_division)
|
|
{
|
|
result.lots = CalculateEqualDivisionLots(total_lots, tp_index, total_tp_count);
|
|
result.percentage = 100.0 / total_tp_count;
|
|
}
|
|
else
|
|
{
|
|
result.lots = CalculateCustomPercentageLots(total_lots, tp_index);
|
|
if(tp_index < ArraySize(m_partial_close_percentages))
|
|
{
|
|
result.percentage = m_partial_close_percentages[tp_index];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Get TP prices from position comment |
|
|
//+------------------------------------------------------------------+
|
|
bool GetTPPricesFromComment(ulong ticket, double &tp_prices[], int &tp_count)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
string comment = PositionGetString(POSITION_COMMENT);
|
|
tp_count = 0;
|
|
ArrayResize(tp_prices, 0);
|
|
|
|
// Parse comment for TP values
|
|
// Format: "UnivBufEA_24680_H1;TP1=1.2530;TP2=1.2560;TP3=1.2600;..."
|
|
|
|
int tp_index = 1;
|
|
while(true)
|
|
{
|
|
string search_str = ";TP" + IntegerToString(tp_index) + "=";
|
|
int pos = StringFind(comment, search_str);
|
|
|
|
if(pos == -1) break;
|
|
|
|
// Extract TP value
|
|
int start_pos = pos + StringLen(search_str);
|
|
int end_pos = StringFind(comment, ";", start_pos);
|
|
|
|
if(end_pos == -1) end_pos = StringLen(comment);
|
|
|
|
string tp_str = StringSubstr(comment, start_pos, end_pos - start_pos);
|
|
double tp_value = StringToDouble(tp_str);
|
|
|
|
ArrayResize(tp_prices, tp_count + 1);
|
|
tp_prices[tp_count] = tp_value;
|
|
tp_count++;
|
|
|
|
tp_index++;
|
|
}
|
|
|
|
return (tp_count > 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if specific TP level has been reached |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTPReached(ulong ticket, int tp_index, double &tp_prices[])
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
if(tp_index >= ArraySize(tp_prices)) return false;
|
|
|
|
double tp_price = tp_prices[tp_index];
|
|
if(tp_price == 0) return false;
|
|
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
double current_price = (pos_type == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(m_symbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(m_symbol, SYMBOL_ASK);
|
|
|
|
// Check if TP has been reached
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
return (current_price >= tp_price);
|
|
}
|
|
else
|
|
{
|
|
return (current_price <= tp_price);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute partial close for specific TP level |
|
|
//+------------------------------------------------------------------+
|
|
bool ExecutePartialClose(ulong ticket, double close_lots, int tp_index)
|
|
{
|
|
if(!PositionSelectByTicket(ticket))
|
|
{
|
|
Print("[ERROR] Failed to select position #", ticket);
|
|
return false;
|
|
}
|
|
|
|
double current_lots = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
if(close_lots <= 0)
|
|
{
|
|
Print("[ERROR] Invalid close lots: ", close_lots, " for ticket #", ticket);
|
|
return false;
|
|
}
|
|
|
|
if(close_lots > current_lots)
|
|
{
|
|
Print("[WARNING] Close lots (", close_lots, ") > current lots (", current_lots,
|
|
") for ticket #", ticket, ". Adjusting to current lots.");
|
|
close_lots = current_lots;
|
|
}
|
|
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
bool result = false;
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
result = m_trade.Sell(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index + 1));
|
|
}
|
|
else
|
|
{
|
|
result = m_trade.Buy(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index + 1));
|
|
}
|
|
|
|
if(result)
|
|
{
|
|
Print("[PartialCloseManager] Partial close executed: ", close_lots, " lots at TP", (tp_index + 1),
|
|
" for ticket #", ticket);
|
|
UpdateCommentAfterPartialClose(ticket, tp_index + 1);
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to execute partial close for ticket #", ticket,
|
|
". Error: ", m_trade.ResultRetcodeDescription());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update position comment after partial close |
|
|
//+------------------------------------------------------------------+
|
|
bool UpdateCommentAfterPartialClose(ulong ticket, int tp_index)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
string comment = PositionGetString(POSITION_COMMENT);
|
|
|
|
// Update BE flag if TP1 was closed
|
|
if(tp_index == 1)
|
|
{
|
|
// Replace BE=0 with BE=1
|
|
string be_old = ";BE=0";
|
|
string be_new = ";BE=1";
|
|
comment = StringReplace(comment, be_old, be_new);
|
|
}
|
|
|
|
// Update TS flag if all TPs closed
|
|
// Check if this was the last TP
|
|
double tp_prices[];
|
|
int tp_count = 0;
|
|
if(GetTPPricesFromComment(ticket, tp_prices, tp_count))
|
|
{
|
|
if(tp_index >= tp_count)
|
|
{
|
|
// All TPs closed, activate trailing
|
|
string ts_old = ";TS=0";
|
|
string ts_new = ";TS=ACTIVE";
|
|
comment = StringReplace(comment, ts_old, ts_new);
|
|
}
|
|
}
|
|
|
|
// Update comment
|
|
if(m_trade.PositionModify(ticket, PositionGetDouble(POSITION_SL), PositionGetDouble(POSITION_TP)))
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] Comment updated for ticket #", ticket, ": ", comment);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to update comment for ticket #", ticket);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get last closed TP for ticket |
|
|
//+------------------------------------------------------------------+
|
|
int GetLastClosedTP(ulong ticket)
|
|
{
|
|
for(int i = 0; i < ArraySize(m_tp_tracking); i++)
|
|
{
|
|
if(m_tp_tracking[i].ticket == ticket)
|
|
{
|
|
return m_tp_tracking[i].last_closed_tp;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update TP tracking |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateTPTracking(ulong ticket, int tp_level)
|
|
{
|
|
// Check if ticket already exists in tracking
|
|
for(int i = 0; i < ArraySize(m_tp_tracking); i++)
|
|
{
|
|
if(m_tp_tracking[i].ticket == ticket)
|
|
{
|
|
m_tp_tracking[i].last_closed_tp = tp_level;
|
|
m_tp_tracking[i].last_check_time = TimeCurrent();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Add new tracking record
|
|
int size = ArraySize(m_tp_tracking);
|
|
if(size >= m_max_tracking_records)
|
|
{
|
|
// Remove oldest record
|
|
ArrayCopy(m_tp_tracking, m_tp_tracking, 0, 1, size - 1);
|
|
size = m_max_tracking_records - 1;
|
|
}
|
|
|
|
ArrayResize(m_tp_tracking, size + 1);
|
|
m_tp_tracking[size].ticket = ticket;
|
|
m_tp_tracking[size].last_closed_tp = tp_level;
|
|
m_tp_tracking[size].last_check_time = TimeCurrent();
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[PartialCloseManager] Added tracking for ticket #", ticket, ", TP level: ", tp_level);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clear TP tracking for ticket |
|
|
//+------------------------------------------------------------------+
|
|
void ClearTPTracking(ulong ticket)
|
|
{
|
|
for(int i = 0; i < ArraySize(m_tp_tracking); i++)
|
|
{
|
|
if(m_tp_tracking[i].ticket == ticket)
|
|
{
|
|
ArrayCopy(m_tp_tracking, m_tp_tracking, i, i + 1, ArraySize(m_tp_tracking) - i - 1);
|
|
ArrayResize(m_tp_tracking, ArraySize(m_tp_tracking) - 1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate equal division lots |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateEqualDivisionLots(double total_lots, int tp_index, int total_tp_count)
|
|
{
|
|
if(total_tp_count == 0) return 0;
|
|
|
|
double equal_lots = total_lots / total_tp_count;
|
|
|
|
// Last TP gets remaining lots
|
|
if(tp_index == total_tp_count - 1)
|
|
{
|
|
return total_lots - (equal_lots * (total_tp_count - 1));
|
|
}
|
|
|
|
return equal_lots;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate custom percentage lots |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateCustomPercentageLots(double total_lots, int tp_index)
|
|
{
|
|
if(tp_index >= ArraySize(m_partial_close_percentages)) return 0;
|
|
|
|
double percentage = m_partial_close_percentages[tp_index];
|
|
return total_lots * (percentage / 100.0);
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CTradeExecutor |
|
|
//+------------------------------------------------------------------+
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CTradeExecutor - Executes trades and manages orders |
|
|
//+------------------------------------------------------------------+
|
|
class CTradeExecutor
|
|
{
|
|
private:
|
|
int m_slippage_points;
|
|
int m_magic_number;
|
|
string m_symbol;
|
|
int m_digits;
|
|
bool m_take_screenshot;
|
|
long m_chart_id;
|
|
string m_indicator_name;
|
|
|
|
CTrade *m_trade;
|
|
CPositionInfo *m_position_info;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Trade result structure |
|
|
//+------------------------------------------------------------------+
|
|
struct TradeResult
|
|
{
|
|
bool success;
|
|
ulong ticket;
|
|
string error_message;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CTradeExecutor()
|
|
{
|
|
m_slippage_points = 30;
|
|
m_magic_number = 24680;
|
|
m_symbol = "";
|
|
m_digits = 0;
|
|
m_take_screenshot = true;
|
|
m_chart_id = 0;
|
|
m_indicator_name = "";
|
|
m_enable_debug = false;
|
|
|
|
m_trade = new CTrade();
|
|
m_position_info = new CPositionInfo();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CTradeExecutor()
|
|
{
|
|
if(m_trade != NULL)
|
|
{
|
|
delete m_trade;
|
|
m_trade = NULL;
|
|
}
|
|
|
|
if(m_position_info != NULL)
|
|
{
|
|
delete m_position_info;
|
|
m_position_info = NULL;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
int slippage_points,
|
|
int magic_number,
|
|
string symbol,
|
|
int digits,
|
|
bool take_screenshot,
|
|
long chart_id,
|
|
string indicator_name,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
// Validate parameters
|
|
if(slippage_points < 0)
|
|
{
|
|
Print("[ERROR] Invalid slippage points: ", slippage_points, ". Using default 30");
|
|
m_slippage_points = 30;
|
|
}
|
|
else
|
|
{
|
|
m_slippage_points = slippage_points;
|
|
}
|
|
|
|
if(magic_number <= 0)
|
|
{
|
|
Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680");
|
|
m_magic_number = 24680;
|
|
}
|
|
else
|
|
{
|
|
m_magic_number = magic_number;
|
|
}
|
|
|
|
if(symbol == "")
|
|
{
|
|
Print("[ERROR] Invalid symbol: empty string");
|
|
}
|
|
m_symbol = symbol;
|
|
|
|
if(digits <= 0)
|
|
{
|
|
Print("[ERROR] Invalid digits: ", digits);
|
|
}
|
|
m_digits = digits;
|
|
|
|
m_take_screenshot = take_screenshot;
|
|
m_chart_id = chart_id;
|
|
m_indicator_name = indicator_name;
|
|
m_enable_debug = enable_debug;
|
|
|
|
m_trade.SetExpertMagicNumber(m_magic_number);
|
|
m_trade.SetDeviationInPoints(m_slippage_points);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Parameters set:");
|
|
Print(" Slippage: ", m_slippage_points, " points");
|
|
Print(" Magic number: ", m_magic_number);
|
|
Print(" Symbol: ", m_symbol);
|
|
Print(" Digits: ", m_digits);
|
|
Print(" Take screenshot: ", m_take_screenshot);
|
|
Print(" Chart ID: ", m_chart_id);
|
|
Print(" Indicator: ", m_indicator_name);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open new trade with multiple TPs |
|
|
//+------------------------------------------------------------------+
|
|
TradeResult ExecuteTrade(
|
|
bool is_buy,
|
|
double lots,
|
|
double open_price,
|
|
double sl_price,
|
|
double &tp_prices[],
|
|
int tp_count
|
|
)
|
|
{
|
|
TradeResult result;
|
|
result.success = false;
|
|
result.ticket = 0;
|
|
result.error_message = "";
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Executing trade...");
|
|
Print(" Direction: ", (is_buy ? "BUY" : "SELL"));
|
|
Print(" Lots: ", lots);
|
|
Print(" SL: ", sl_price);
|
|
Print(" TP count: ", tp_count);
|
|
}
|
|
|
|
// Validate inputs
|
|
if(lots <= 0)
|
|
{
|
|
result.error_message = "Invalid lots: " + DoubleToString(lots, 2);
|
|
Print("[ERROR] ", result.error_message);
|
|
return result;
|
|
}
|
|
|
|
if(m_symbol == "")
|
|
{
|
|
result.error_message = "Symbol not set";
|
|
Print("[ERROR] ", result.error_message);
|
|
return result;
|
|
}
|
|
|
|
// Build order comment with multiple TPs
|
|
string comment = BuildOrderComment(tp_prices, tp_count);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Comment: ", comment);
|
|
}
|
|
|
|
// Get execution price
|
|
double execution_price = GetExecutionPrice(is_buy);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print(" Execution price: ", execution_price);
|
|
}
|
|
|
|
// Execute order
|
|
bool order_result = false;
|
|
if(is_buy)
|
|
{
|
|
order_result = m_trade.Buy(lots, m_symbol, execution_price, sl_price, 0, comment);
|
|
}
|
|
else
|
|
{
|
|
order_result = m_trade.Sell(lots, m_symbol, execution_price, sl_price, 0, comment);
|
|
}
|
|
|
|
if(order_result)
|
|
{
|
|
result.success = true;
|
|
result.ticket = m_trade.ResultOrder();
|
|
|
|
Print("[TradeExecutor] Order opened successfully. Ticket: ", result.ticket);
|
|
|
|
// Take screenshot
|
|
if(m_take_screenshot)
|
|
{
|
|
TakeScreenshot(result.ticket);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int error_code = (int)m_trade.ResultRetcode();
|
|
result.error_message = "OrderSend failed: " + m_trade.ResultRetcodeDescription() +
|
|
" (Code: " + IntegerToString(error_code) + ")";
|
|
Print("[ERROR] ", result.error_message);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Partial close |
|
|
//+------------------------------------------------------------------+
|
|
TradeResult ExecutePartialClose(
|
|
ulong ticket,
|
|
double close_lots,
|
|
int tp_index
|
|
)
|
|
{
|
|
TradeResult result;
|
|
result.success = false;
|
|
result.ticket = 0;
|
|
result.error_message = "";
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Executing partial close...");
|
|
Print(" Ticket: ", ticket);
|
|
Print(" Close lots: ", close_lots);
|
|
Print(" TP index: ", tp_index);
|
|
}
|
|
|
|
// Validate inputs
|
|
if(ticket == 0)
|
|
{
|
|
result.error_message = "Invalid ticket: 0";
|
|
Print("[ERROR] ", result.error_message);
|
|
return result;
|
|
}
|
|
|
|
if(close_lots <= 0)
|
|
{
|
|
result.error_message = "Invalid close lots: " + DoubleToString(close_lots, 2);
|
|
Print("[ERROR] ", result.error_message);
|
|
return result;
|
|
}
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
{
|
|
result.error_message = "Failed to select position #" + IntegerToString(ticket);
|
|
Print("[ERROR] ", result.error_message);
|
|
return result;
|
|
}
|
|
|
|
double current_lots = PositionGetDouble(POSITION_VOLUME);
|
|
if(close_lots > current_lots)
|
|
{
|
|
Print("[WARNING] Close lots (", close_lots, ") > current lots (", current_lots,
|
|
") for ticket #", ticket, ". Adjusting to current lots.");
|
|
close_lots = current_lots;
|
|
}
|
|
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
bool close_result = false;
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
close_result = m_trade.Sell(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index));
|
|
}
|
|
else
|
|
{
|
|
close_result = m_trade.Buy(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index));
|
|
}
|
|
|
|
if(close_result)
|
|
{
|
|
result.success = true;
|
|
result.ticket = m_trade.ResultOrder();
|
|
Print("[TradeExecutor] Partial close executed: ", close_lots, " lots at TP", tp_index,
|
|
" for ticket #", ticket);
|
|
}
|
|
else
|
|
{
|
|
int error_code = (int)m_trade.ResultRetcode();
|
|
result.error_message = "Partial close failed: " + m_trade.ResultRetcodeDescription() +
|
|
" (Code: " + IntegerToString(error_code) + ")";
|
|
Print("[ERROR] ", result.error_message);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if trade is open |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTradeOpen()
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(m_position_info.SelectByIndex(i))
|
|
{
|
|
if(m_position_info.Symbol() == m_symbol &&
|
|
m_position_info.Magic() == m_magic_number)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close opposite trade |
|
|
//+------------------------------------------------------------------+
|
|
bool CloseOppositeTrade(bool is_buy)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Closing opposite trades for ", (is_buy ? "BUY" : "SELL"), " signal");
|
|
}
|
|
|
|
bool closed_any = false;
|
|
|
|
ENUM_POSITION_TYPE opposite_type = is_buy ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(m_position_info.SelectByIndex(i))
|
|
{
|
|
if(m_position_info.Symbol() == m_symbol &&
|
|
m_position_info.Magic() == m_magic_number &&
|
|
m_position_info.PositionType() == opposite_type)
|
|
{
|
|
ulong ticket = m_position_info.Ticket();
|
|
double lots = m_position_info.Volume();
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Found opposite trade #", ticket, " (", lots, " lots)");
|
|
}
|
|
|
|
if(m_trade.PositionClose(ticket))
|
|
{
|
|
Print("[TradeExecutor] Closed opposite trade Ticket#", ticket, " due to new signal.");
|
|
closed_any = true;
|
|
}
|
|
else
|
|
{
|
|
int error_code = (int)m_trade.ResultRetcode();
|
|
Print("[ERROR] Failed to close opposite trade Ticket#", ticket,
|
|
". Error: ", m_trade.ResultRetcodeDescription(), " (", error_code, ")");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_enable_debug && !closed_any)
|
|
{
|
|
Print("[TradeExecutor] No opposite trades found to close");
|
|
}
|
|
|
|
return closed_any;
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Build order comment with multiple TPs |
|
|
//+------------------------------------------------------------------+
|
|
string BuildOrderComment(double &tp_prices[], int tp_count)
|
|
{
|
|
string comment = "UnivBufEA_" + IntegerToString(m_magic_number);
|
|
|
|
// Add TPs to comment
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
comment += ";TP" + IntegerToString(i + 1) + "=" + DoubleToString(tp_prices[i], m_digits);
|
|
}
|
|
|
|
// Add breakeven and trailing flags
|
|
comment += ";BE=0;TS=0";
|
|
|
|
// Truncate to max length (31 chars for some brokers)
|
|
if(StringLen(comment) > 31)
|
|
{
|
|
comment = StringSubstr(comment, 0, 31);
|
|
}
|
|
|
|
return comment;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Take screenshot |
|
|
//+------------------------------------------------------------------+
|
|
bool TakeScreenshot(ulong ticket)
|
|
{
|
|
if(!m_take_screenshot)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Screenshot disabled");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if(m_chart_id == 0)
|
|
{
|
|
Print("[ERROR] Chart ID is 0, cannot take screenshot");
|
|
return false;
|
|
}
|
|
|
|
string time_str = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS);
|
|
StringReplace(time_str, ":", "_");
|
|
StringReplace(time_str, ".", "_");
|
|
|
|
string filename = m_symbol + "_T" + IntegerToString(ticket) + "_" + time_str + ".gif";
|
|
|
|
int width = (int)ChartGetInteger(m_chart_id, CHART_WIDTH_IN_PIXELS);
|
|
int height = (int)ChartGetInteger(m_chart_id, CHART_HEIGHT_IN_PIXELS);
|
|
|
|
if(width <= 0 || height <= 0)
|
|
{
|
|
Print("[ERROR] Invalid chart dimensions: ", width, "x", height);
|
|
return false;
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[TradeExecutor] Taking screenshot: ", filename, " (", width, "x", height, ")");
|
|
}
|
|
|
|
if(ChartScreenShot(m_chart_id, filename, width, height, ALIGN_RIGHT))
|
|
{
|
|
Print("[TradeExecutor] Screenshot saved: ", filename);
|
|
return true;
|
|
}
|
|
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to save screenshot. Error code: ", error);
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get execution price |
|
|
//+------------------------------------------------------------------+
|
|
double GetExecutionPrice(bool is_buy)
|
|
{
|
|
if(is_buy)
|
|
{
|
|
return SymbolInfoDouble(m_symbol, SYMBOL_ASK);
|
|
}
|
|
else
|
|
{
|
|
return SymbolInfoDouble(m_symbol, SYMBOL_BID);
|
|
}
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CStateManager |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CStateManager - Manages persistent state using Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
class CStateManager
|
|
{
|
|
private:
|
|
string m_symbol;
|
|
int m_magic_number;
|
|
string m_prefix;
|
|
|
|
string m_gv_accum_loss;
|
|
string m_gv_consec_loss;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CStateManager()
|
|
{
|
|
m_symbol = "";
|
|
m_magic_number = 0;
|
|
m_prefix = "UnivBufEA";
|
|
m_enable_debug = false;
|
|
|
|
m_gv_accum_loss = "";
|
|
m_gv_consec_loss = "";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CStateManager()
|
|
{
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(string symbol, int magic_number, bool enable_debug = false)
|
|
{
|
|
// Validate parameters
|
|
if(symbol == "")
|
|
{
|
|
Print("[ERROR] Invalid symbol: empty string");
|
|
}
|
|
m_symbol = symbol;
|
|
|
|
if(magic_number <= 0)
|
|
{
|
|
Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680");
|
|
m_magic_number = 24680;
|
|
}
|
|
else
|
|
{
|
|
m_magic_number = magic_number;
|
|
}
|
|
|
|
m_enable_debug = enable_debug;
|
|
|
|
// Build Global Variable names
|
|
m_gv_accum_loss = BuildGVName("_AccumLoss");
|
|
m_gv_consec_loss = BuildGVName("_ConsecLoss");
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[StateManager] Parameters set:");
|
|
Print(" Symbol: ", m_symbol);
|
|
Print(" Magic number: ", m_magic_number);
|
|
Print(" GV Accum Loss: ", m_gv_accum_loss);
|
|
Print(" GV Consec Loss: ", m_gv_consec_loss);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Load state from Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
bool LoadState(double &accumulated_loss, int &consecutive_losses)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[StateManager] Loading state...");
|
|
}
|
|
|
|
accumulated_loss = 0;
|
|
consecutive_losses = 0;
|
|
|
|
// Load accumulated loss
|
|
if(GlobalVariableCheck(m_gv_accum_loss))
|
|
{
|
|
accumulated_loss = GlobalVariableGet(m_gv_accum_loss);
|
|
|
|
// Validate accumulated loss
|
|
if(accumulated_loss < 0)
|
|
{
|
|
Print("[WARNING] Invalid accumulated loss found: ", accumulated_loss, ". Resetting to 0");
|
|
accumulated_loss = 0;
|
|
GlobalVariableSet(m_gv_accum_loss, 0);
|
|
}
|
|
|
|
Print("[StateManager] Loaded accumulated loss: ", DoubleToString(accumulated_loss, 2));
|
|
}
|
|
else
|
|
{
|
|
if(!GlobalVariableSet(m_gv_accum_loss, 0))
|
|
{
|
|
Print("[ERROR] Failed to initialize accumulated loss Global Variable");
|
|
return false;
|
|
}
|
|
Print("[StateManager] Initialized accumulated loss: 0");
|
|
}
|
|
|
|
// Load consecutive losses
|
|
if(GlobalVariableCheck(m_gv_consec_loss))
|
|
{
|
|
consecutive_losses = (int)GlobalVariableGet(m_gv_consec_loss);
|
|
|
|
// Validate consecutive losses
|
|
if(consecutive_losses < 0)
|
|
{
|
|
Print("[WARNING] Invalid consecutive losses found: ", consecutive_losses, ". Resetting to 0");
|
|
consecutive_losses = 0;
|
|
GlobalVariableSet(m_gv_consec_loss, 0);
|
|
}
|
|
|
|
Print("[StateManager] Loaded consecutive losses: ", consecutive_losses);
|
|
}
|
|
else
|
|
{
|
|
if(!GlobalVariableSet(m_gv_consec_loss, 0))
|
|
{
|
|
Print("[ERROR] Failed to initialize consecutive losses Global Variable");
|
|
return false;
|
|
}
|
|
Print("[StateManager] Initialized consecutive losses: 0");
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[StateManager] State loaded successfully");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Save state to Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
bool SaveState(double accumulated_loss, int consecutive_losses)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[StateManager] Saving state...");
|
|
Print(" Accumulated loss: ", DoubleToString(accumulated_loss, 2));
|
|
Print(" Consecutive losses: ", consecutive_losses);
|
|
}
|
|
|
|
// Validate inputs
|
|
if(accumulated_loss < 0)
|
|
{
|
|
Print("[ERROR] Invalid accumulated loss to save: ", accumulated_loss);
|
|
return false;
|
|
}
|
|
|
|
if(consecutive_losses < 0)
|
|
{
|
|
Print("[ERROR] Invalid consecutive losses to save: ", consecutive_losses);
|
|
return false;
|
|
}
|
|
|
|
bool result = true;
|
|
|
|
// Save accumulated loss
|
|
if(!GlobalVariableSet(m_gv_accum_loss, accumulated_loss))
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to save accumulated loss. Error code: ", error);
|
|
result = false;
|
|
}
|
|
|
|
// Save consecutive losses
|
|
if(!GlobalVariableSet(m_gv_consec_loss, consecutive_losses))
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to save consecutive losses. Error code: ", error);
|
|
result = false;
|
|
}
|
|
|
|
if(result && m_enable_debug)
|
|
{
|
|
Print("[StateManager] State saved successfully");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clear state (for backtesting) |
|
|
//+------------------------------------------------------------------+
|
|
bool ClearState()
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[StateManager] Clearing state...");
|
|
}
|
|
|
|
bool result = true;
|
|
|
|
// Delete accumulated loss
|
|
if(GlobalVariableCheck(m_gv_accum_loss))
|
|
{
|
|
if(!GlobalVariableDel(m_gv_accum_loss))
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to delete accumulated loss. Error code: ", error);
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
Print("[StateManager] Deleted accumulated loss Global Variable");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("[StateManager] Accumulated loss Global Variable not found (already cleared)");
|
|
}
|
|
|
|
// Delete consecutive losses
|
|
if(GlobalVariableCheck(m_gv_consec_loss))
|
|
{
|
|
if(!GlobalVariableDel(m_gv_consec_loss))
|
|
{
|
|
int error = GetLastError();
|
|
Print("[ERROR] Failed to delete consecutive losses. Error code: ", error);
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
Print("[StateManager] Deleted consecutive losses Global Variable");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("[StateManager] Consecutive losses Global Variable not found (already cleared)");
|
|
}
|
|
|
|
if(result)
|
|
{
|
|
Print("[StateManager] State cleared successfully");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get Global Variable names |
|
|
//+------------------------------------------------------------------+
|
|
void GetGVNames(string &accum_loss_name, string &consec_loss_name)
|
|
{
|
|
accum_loss_name = m_gv_accum_loss;
|
|
consec_loss_name = m_gv_consec_loss;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if state exists |
|
|
//+------------------------------------------------------------------+
|
|
bool StateExists()
|
|
{
|
|
bool accum_exists = GlobalVariableCheck(m_gv_accum_loss);
|
|
bool consec_exists = GlobalVariableCheck(m_gv_consec_loss);
|
|
|
|
return (accum_exists || consec_exists);
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Build Global Variable name |
|
|
//+------------------------------------------------------------------+
|
|
string BuildGVName(string suffix)
|
|
{
|
|
return m_prefix + "_" + m_symbol + "_" + IntegerToString(m_magic_number) + suffix;
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS: CUIManager |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CUIManager - Manages chart labels and manual mode UI |
|
|
//+------------------------------------------------------------------+
|
|
class CUIManager
|
|
{
|
|
private:
|
|
long m_chart_id;
|
|
bool m_manual_mode;
|
|
int m_magic_number;
|
|
string m_symbol;
|
|
double m_accumulated_loss;
|
|
double m_loss_percent;
|
|
double m_high_loss_threshold;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CUIManager()
|
|
{
|
|
m_chart_id = 0;
|
|
m_manual_mode = false;
|
|
m_magic_number = 0;
|
|
m_symbol = "";
|
|
m_accumulated_loss = 0;
|
|
m_loss_percent = 0;
|
|
m_high_loss_threshold = 0;
|
|
m_enable_debug = false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CUIManager()
|
|
{
|
|
DeleteUI();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
long chart_id,
|
|
bool manual_mode,
|
|
int magic_number,
|
|
string symbol,
|
|
double high_loss_threshold,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
// Validate parameters
|
|
if(chart_id == 0)
|
|
{
|
|
Print("[ERROR] Invalid chart ID: 0");
|
|
}
|
|
m_chart_id = chart_id;
|
|
|
|
if(magic_number <= 0)
|
|
{
|
|
Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680");
|
|
m_magic_number = 24680;
|
|
}
|
|
else
|
|
{
|
|
m_magic_number = magic_number;
|
|
}
|
|
|
|
if(symbol == "")
|
|
{
|
|
Print("[ERROR] Invalid symbol: empty string");
|
|
}
|
|
m_symbol = symbol;
|
|
|
|
if(high_loss_threshold < 0)
|
|
{
|
|
Print("[ERROR] Invalid high loss threshold: ", high_loss_threshold, ". Using 0");
|
|
m_high_loss_threshold = 0;
|
|
}
|
|
else
|
|
{
|
|
m_high_loss_threshold = high_loss_threshold;
|
|
}
|
|
|
|
m_manual_mode = manual_mode;
|
|
m_enable_debug = enable_debug;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Parameters set:");
|
|
Print(" Chart ID: ", m_chart_id);
|
|
Print(" Manual mode: ", m_manual_mode);
|
|
Print(" Magic number: ", m_magic_number);
|
|
Print(" Symbol: ", m_symbol);
|
|
Print(" High loss threshold: ", m_high_loss_threshold, "%");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Create UI elements |
|
|
//+------------------------------------------------------------------+
|
|
void CreateUI()
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Creating UI elements...");
|
|
}
|
|
|
|
if(m_chart_id == 0)
|
|
{
|
|
Print("[ERROR] Cannot create UI: Chart ID is 0");
|
|
return;
|
|
}
|
|
|
|
CreateLossLabels();
|
|
|
|
if(m_manual_mode)
|
|
{
|
|
CreateManualModeControls();
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] UI elements created successfully");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Delete UI elements |
|
|
//+------------------------------------------------------------------+
|
|
void DeleteUI()
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Deleting UI elements...");
|
|
}
|
|
|
|
int deleted_count = 0;
|
|
|
|
if(ObjectDelete(m_chart_id, "AccumLossLabel")) deleted_count++;
|
|
if(ObjectDelete(m_chart_id, "AccumLossPercentLabel")) deleted_count++;
|
|
|
|
if(m_manual_mode)
|
|
{
|
|
if(ObjectDelete(m_chart_id, "ManualTPLabel")) deleted_count++;
|
|
if(ObjectDelete(m_chart_id, "ManualTPEdit")) deleted_count++;
|
|
if(ObjectDelete(m_chart_id, "ManualSLLabel")) deleted_count++;
|
|
if(ObjectDelete(m_chart_id, "ManualSLEdit")) deleted_count++;
|
|
if(ObjectDelete(m_chart_id, "ManualTradeButton")) deleted_count++;
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Deleted ", deleted_count, " UI elements");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update loss display |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateLossDisplay(double accumulated_loss, double loss_percent)
|
|
{
|
|
m_accumulated_loss = accumulated_loss;
|
|
m_loss_percent = loss_percent;
|
|
|
|
// Validate inputs
|
|
if(accumulated_loss < 0)
|
|
{
|
|
Print("[WARNING] Invalid accumulated loss: ", accumulated_loss, ". Using 0");
|
|
accumulated_loss = 0;
|
|
}
|
|
|
|
if(loss_percent < 0)
|
|
{
|
|
Print("[WARNING] Invalid loss percent: ", loss_percent, "%. Using 0%");
|
|
loss_percent = 0;
|
|
}
|
|
|
|
string loss_text = "Accum. Loss: " + DoubleToString(accumulated_loss, 2);
|
|
string percent_text = "Loss % of Bal: " + DoubleToString(loss_percent, 2) + "%";
|
|
|
|
color label_color = clrWhite;
|
|
bool is_high_loss = false;
|
|
|
|
if(m_high_loss_threshold > 0 && loss_percent >= m_high_loss_threshold)
|
|
{
|
|
label_color = clrOrangeRed;
|
|
is_high_loss = true;
|
|
}
|
|
|
|
// Update labels
|
|
ObjectSetString(m_chart_id, "AccumLossLabel", OBJPROP_TEXT, loss_text);
|
|
ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_COLOR, label_color);
|
|
|
|
ObjectSetString(m_chart_id, "AccumLossPercentLabel", OBJPROP_TEXT, percent_text);
|
|
ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_COLOR, label_color);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Loss display updated: ", loss_text, ", ", percent_text,
|
|
(is_high_loss ? " [HIGH LOSS WARNING]" : ""));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update manual mode UI |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateManualModeUI()
|
|
{
|
|
// Update any dynamic manual mode elements if needed
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get manual TP from UI |
|
|
//+------------------------------------------------------------------+
|
|
double GetManualTP()
|
|
{
|
|
if(!m_manual_mode)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Manual mode not enabled, returning TP = 0");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
string tp_str = ObjectGetString(m_chart_id, "ManualTPEdit", OBJPROP_TEXT);
|
|
double tp_value = StringToDouble(tp_str);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Manual TP: ", tp_value);
|
|
}
|
|
|
|
return tp_value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get manual SL from UI |
|
|
//+------------------------------------------------------------------+
|
|
double GetManualSL()
|
|
{
|
|
if(!m_manual_mode)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Manual mode not enabled, returning SL = 0");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
string sl_str = ObjectGetString(m_chart_id, "ManualSLEdit", OBJPROP_TEXT);
|
|
double sl_value = StringToDouble(sl_str);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Manual SL: ", sl_value);
|
|
}
|
|
|
|
return sl_value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if manual trade button was clicked |
|
|
//+------------------------------------------------------------------+
|
|
bool IsManualTradeButtonClicked()
|
|
{
|
|
if(!m_manual_mode) return false;
|
|
|
|
// This will be checked in OnChartEvent
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Create loss display labels |
|
|
//+------------------------------------------------------------------+
|
|
void CreateLossLabels()
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Creating loss display labels...");
|
|
}
|
|
|
|
// Accumulated Loss Label
|
|
if(!ObjectCreate(m_chart_id, "AccumLossLabel", OBJ_LABEL, 0, 0, 0))
|
|
{
|
|
Print("[ERROR] Failed to create AccumLossLabel. Error: ", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
ObjectSetString(m_chart_id, "AccumLossLabel", OBJPROP_TEXT, "Accum. Loss: Loading...");
|
|
ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER);
|
|
ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_XDISTANCE, 10);
|
|
ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_YDISTANCE, 40);
|
|
ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_FONTSIZE, 10);
|
|
ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_COLOR, clrWhite);
|
|
}
|
|
|
|
// Loss Percent Label
|
|
if(!ObjectCreate(m_chart_id, "AccumLossPercentLabel", OBJ_LABEL, 0, 0, 0))
|
|
{
|
|
Print("[ERROR] Failed to create AccumLossPercentLabel. Error: ", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
ObjectSetString(m_chart_id, "AccumLossPercentLabel", OBJPROP_TEXT, "Loss % of Bal: Loading...");
|
|
ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER);
|
|
ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_XDISTANCE, 10);
|
|
ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_YDISTANCE, 20);
|
|
ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_FONTSIZE, 10);
|
|
ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_COLOR, clrWhite);
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Loss display labels created");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Create manual mode controls |
|
|
//+------------------------------------------------------------------+
|
|
void CreateManualModeControls()
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Creating manual mode controls...");
|
|
}
|
|
|
|
int created_count = 0;
|
|
|
|
// Manual TP Label
|
|
if(ObjectCreate(m_chart_id, "ManualTPLabel", OBJ_LABEL, 0, 0, 0))
|
|
{
|
|
ObjectSetString(m_chart_id, "ManualTPLabel", OBJPROP_TEXT, "Take Profit:");
|
|
ObjectSetInteger(m_chart_id, "ManualTPLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
|
|
ObjectSetInteger(m_chart_id, "ManualTPLabel", OBJPROP_XDISTANCE, 180);
|
|
ObjectSetInteger(m_chart_id, "ManualTPLabel", OBJPROP_YDISTANCE, 50);
|
|
created_count++;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to create ManualTPLabel. Error: ", GetLastError());
|
|
}
|
|
|
|
// Manual TP Edit
|
|
if(ObjectCreate(m_chart_id, "ManualTPEdit", OBJ_EDIT, 0, 0, 0))
|
|
{
|
|
ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
|
|
ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_XDISTANCE, 110);
|
|
ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_YDISTANCE, 50);
|
|
ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_XSIZE, 60);
|
|
created_count++;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to create ManualTPEdit. Error: ", GetLastError());
|
|
}
|
|
|
|
// Manual SL Label
|
|
if(ObjectCreate(m_chart_id, "ManualSLLabel", OBJ_LABEL, 0, 0, 0))
|
|
{
|
|
ObjectSetString(m_chart_id, "ManualSLLabel", OBJPROP_TEXT, "Stop Loss:");
|
|
ObjectSetInteger(m_chart_id, "ManualSLLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
|
|
ObjectSetInteger(m_chart_id, "ManualSLLabel", OBJPROP_XDISTANCE, 180);
|
|
ObjectSetInteger(m_chart_id, "ManualSLLabel", OBJPROP_YDISTANCE, 70);
|
|
created_count++;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to create ManualSLLabel. Error: ", GetLastError());
|
|
}
|
|
|
|
// Manual SL Edit
|
|
if(ObjectCreate(m_chart_id, "ManualSLEdit", OBJ_EDIT, 0, 0, 0))
|
|
{
|
|
ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
|
|
ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_XDISTANCE, 110);
|
|
ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_YDISTANCE, 70);
|
|
ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_XSIZE, 60);
|
|
created_count++;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to create ManualSLEdit. Error: ", GetLastError());
|
|
}
|
|
|
|
// Manual Trade Button
|
|
if(ObjectCreate(m_chart_id, "ManualTradeButton", OBJ_BUTTON, 0, 0, 0))
|
|
{
|
|
ObjectSetString(m_chart_id, "ManualTradeButton", OBJPROP_TEXT, "Open Market Order");
|
|
ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
|
|
ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_XDISTANCE, 110);
|
|
ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_YDISTANCE, 95);
|
|
ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_XSIZE, 130);
|
|
ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_YSIZE, 25);
|
|
created_count++;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to create ManualTradeButton. Error: ", GetLastError());
|
|
}
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[UIManager] Created ", created_count, " manual mode controls");
|
|
}
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| MAIN EA CODE |
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Trade Mode Enum |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_TRADE_MODE
|
|
{
|
|
MODE_INDICATOR,
|
|
MODE_MANUAL
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Input Parameters |
|
|
//+------------------------------------------------------------------+
|
|
|
|
// --- General Settings ---
|
|
input string General_Settings = "--- General Settings ---";
|
|
input ENUM_TRADE_MODE Trade_Mode = MODE_INDICATOR;
|
|
input double LotSize = 0.03;
|
|
input int Slippage = 3;
|
|
input int MagicNumber = 24680;
|
|
input bool TakeScreenshotOnOpen = true;
|
|
input bool EnableDebugPrints = true;
|
|
input bool ExitOnOppositeSignal = false;
|
|
|
|
// --- Money Management Settings ---
|
|
input string Money_Management_Settings = "--- Money Management Settings ---";
|
|
input bool UsePercentBalanceLot = true;
|
|
input double PercentOfBalanceForProfit = 1.0;
|
|
input double DailyProfitTargetPercent = 2.0;
|
|
|
|
// --- Time Filtering Settings ---
|
|
input string Time_Filter_Settings = "--- Time Filtering Settings ---";
|
|
input bool EnableTimeFiltering = false;
|
|
input string Day_Filter_Header = "--- Day of Week Filter ---";
|
|
input bool TradeMondayEnabled = true;
|
|
input bool TradeTuesdayEnabled = true;
|
|
input bool TradeWednesdayEnabled = true;
|
|
input bool TradeThursdayEnabled = true;
|
|
input bool TradeFridayEnabled = true;
|
|
input bool TradeSaturdayEnabled = false;
|
|
input bool TradeSundayEnabled = false;
|
|
input string Session_Filter_Header = "--- Trading Session Filter ---";
|
|
input bool TradeAsianSessionEnabled = true;
|
|
input bool TradeEuropeSessionEnabled = true;
|
|
input bool TradeAmericaSessionEnabled = true;
|
|
|
|
// --- Risk Management Settings ---
|
|
input string Risk_Management_Settings = "--- Risk Management Settings ---";
|
|
input bool UseBreakeven = true;
|
|
input int BreakevenPips = 30;
|
|
input bool UseTrailingStop = false;
|
|
input int TrailingStopPips = 300;
|
|
input int MinimumTakeProfitPips = 300;
|
|
|
|
// --- Indicator Settings ---
|
|
input string Indicator_Settings = "--- Indicator Settings ---";
|
|
input string IndicatorFileName = "My_Indicator";
|
|
input int BuySignalBuffer = 0;
|
|
input int SellSignalBuffer = 1;
|
|
|
|
// --- SL/TP Mode Settings ---
|
|
input string SLTP_Settings = "--- SL/TP Mode Settings ---";
|
|
input int SLTP_Mode = 0; // 0=ATR, 1=Indicator
|
|
|
|
// --- ATR Settings (Mode 0) ---
|
|
input string ATR_Settings = "--- (Mode 0) ATR Settings ---";
|
|
input int AtrPeriod = 14;
|
|
input double StopLossAtrMultiplier = 1.5;
|
|
input double TakeProfitAtrMultiplier = 3.0;
|
|
|
|
// --- Indicator SL/TP Buffers (Mode 1) ---
|
|
input string Indicator_SLTP_Settings = "--- (Mode 1) Indicator SL/TP ---";
|
|
input int BuyStopLossBuffer = 2;
|
|
input int BuyTP1Buffer = 3;
|
|
input int BuyTP2Buffer = 4;
|
|
input int BuyTP3Buffer = 5;
|
|
input int SellStopLossBuffer = 6;
|
|
input int SellTP1Buffer = 7;
|
|
input int SellTP2Buffer = 8;
|
|
input int SellTP3Buffer = 9;
|
|
|
|
// --- Partial Close Settings ---
|
|
input string Partial_Close_Settings = "--- Partial Close Settings ---";
|
|
input bool EnablePartialClose = true;
|
|
input bool UseEqualDivision = true;
|
|
input double TP1_ClosePercent = 50.0;
|
|
input double TP2_ClosePercent = 30.0;
|
|
input double TP3_ClosePercent = 20.0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
|
|
// Component instances
|
|
CSignalDetector *g_signal_detector;
|
|
CTimeFilter *g_time_filter;
|
|
CMoneyManager *g_money_manager;
|
|
CRiskManager *g_risk_manager;
|
|
CPartialCloseManager *g_partial_close_manager;
|
|
CTradeExecutor *g_trade_executor;
|
|
CStateManager *g_state_manager;
|
|
CLoggingManager *g_logging_manager;
|
|
CUIManager *g_ui_manager;
|
|
|
|
// Symbol info
|
|
string g_symbol;
|
|
int g_digits;
|
|
double g_point_value;
|
|
double g_pip_value;
|
|
double m_tick_value;
|
|
double m_stop_level_points;
|
|
|
|
// Lot info
|
|
double g_min_lot;
|
|
double g_max_lot;
|
|
double g_lot_step;
|
|
|
|
// Time tracking
|
|
datetime g_last_bar_time;
|
|
datetime g_last_daily_reset_time;
|
|
|
|
// State
|
|
double g_accumulated_loss;
|
|
int g_consecutive_losses;
|
|
double g_daily_profit_accumulated;
|
|
double g_daily_start_balance;
|
|
|
|
// Chart
|
|
long g_chart_id;
|
|
|
|
// Manual mode
|
|
bool g_manual_trade_button_pressed;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Initialize logging first
|
|
g_logging_manager = new CLoggingManager();
|
|
g_logging_manager.SetParameters(EnableDebugPrints);
|
|
g_logging_manager.LogInfo("========== EA Initializing ==========");
|
|
|
|
// Get symbol info
|
|
g_symbol = _Symbol;
|
|
g_digits = (int)SymbolInfoInteger(g_symbol, SYMBOL_DIGITS);
|
|
g_point_value = SymbolInfoDouble(g_symbol, SYMBOL_POINT);
|
|
g_pip_value = (g_digits == 3 || g_digits == 5) ? g_point_value * 10 : g_point_value;
|
|
m_tick_value = SymbolInfoDouble(g_symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
m_stop_level_points = (double)SymbolInfoInteger(g_symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
|
|
// Get lot info
|
|
g_min_lot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MIN);
|
|
g_max_lot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MAX);
|
|
g_lot_step = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
g_chart_id = ChartID();
|
|
|
|
// Initialize state manager
|
|
g_state_manager = new CStateManager();
|
|
g_state_manager.SetParameters(g_symbol, MagicNumber, EnableDebugPrints);
|
|
g_state_manager.LoadState(g_accumulated_loss, g_consecutive_losses);
|
|
|
|
// Initialize time filter
|
|
g_time_filter = new CTimeFilter();
|
|
g_time_filter.SetParameters(
|
|
EnableTimeFiltering,
|
|
TradeMondayEnabled, TradeTuesdayEnabled, TradeWednesdayEnabled,
|
|
TradeThursdayEnabled, TradeFridayEnabled, TradeSaturdayEnabled, TradeSundayEnabled,
|
|
TradeAsianSessionEnabled, TradeEuropeSessionEnabled, TradeAmericaSessionEnabled,
|
|
EnableDebugPrints
|
|
);
|
|
|
|
// Initialize money manager
|
|
g_money_manager = new CMoneyManager();
|
|
g_money_manager.SetParameters(
|
|
LotSize,
|
|
UsePercentBalanceLot, PercentOfBalanceForProfit,
|
|
DailyProfitTargetPercent,
|
|
g_min_lot, g_max_lot, g_lot_step,
|
|
g_point_value, m_tick_value,
|
|
EnableDebugPrints
|
|
);
|
|
|
|
// Initialize signal detector
|
|
g_signal_detector = new CSignalDetector();
|
|
g_signal_detector.SetParameters(
|
|
IndicatorFileName,
|
|
BuySignalBuffer, SellSignalBuffer,
|
|
BuyStopLossBuffer, SellStopLossBuffer,
|
|
SLTP_Mode, AtrPeriod,
|
|
StopLossAtrMultiplier, TakeProfitAtrMultiplier,
|
|
MinimumTakeProfitPips, g_pip_value, g_digits,
|
|
g_symbol,
|
|
EnableDebugPrints
|
|
);
|
|
|
|
// Set TP buffers
|
|
int buy_tp_buffers[] = {BuyTP1Buffer, BuyTP2Buffer, BuyTP3Buffer};
|
|
int sell_tp_buffers[] = {SellTP1Buffer, SellTP2Buffer, SellTP3Buffer};
|
|
g_signal_detector.SetBuyTPBuffers(buy_tp_buffers);
|
|
g_signal_detector.SetSellTPBuffers(sell_tp_buffers);
|
|
|
|
if(!g_signal_detector.Initialize())
|
|
{
|
|
g_logging_manager.LogError(GetLastError(), "OnInit - SignalDetector initialization");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Initialize risk manager
|
|
g_risk_manager = new CRiskManager();
|
|
g_risk_manager.SetParameters(
|
|
UseTrailingStop,
|
|
TrailingStopPips,
|
|
UseBreakeven,
|
|
BreakevenPips,
|
|
g_pip_value,
|
|
g_digits,
|
|
m_stop_level_points,
|
|
MagicNumber,
|
|
g_symbol,
|
|
EnableDebugPrints
|
|
);
|
|
g_risk_manager.EnablePartialClose(EnablePartialClose);
|
|
|
|
// Initialize partial close manager
|
|
g_partial_close_manager = new CPartialCloseManager();
|
|
double partial_close_percentages[] = {TP1_ClosePercent, TP2_ClosePercent, TP3_ClosePercent};
|
|
g_partial_close_manager.SetParameters(
|
|
EnablePartialClose,
|
|
UseEqualDivision,
|
|
partial_close_percentages,
|
|
MagicNumber,
|
|
g_symbol,
|
|
EnableDebugPrints
|
|
);
|
|
|
|
// Initialize trade executor
|
|
g_trade_executor = new CTradeExecutor();
|
|
g_trade_executor.SetParameters(
|
|
Slippage,
|
|
MagicNumber,
|
|
g_symbol,
|
|
g_digits,
|
|
TakeScreenshotOnOpen,
|
|
g_chart_id,
|
|
IndicatorFileName,
|
|
EnableDebugPrints
|
|
);
|
|
|
|
// Initialize UI manager
|
|
g_ui_manager = new CUIManager();
|
|
g_ui_manager.SetParameters(
|
|
g_chart_id,
|
|
(Trade_Mode == MODE_MANUAL),
|
|
MagicNumber,
|
|
g_symbol,
|
|
0, // High loss threshold (not used in v2.0)
|
|
EnableDebugPrints
|
|
);
|
|
g_ui_manager.CreateUI();
|
|
|
|
// Initialize time tracking
|
|
g_last_bar_time = 0;
|
|
g_last_daily_reset_time = 0;
|
|
g_manual_trade_button_pressed = false;
|
|
|
|
// Reset daily profit if needed
|
|
ResetDailyProfitIfNeeded();
|
|
|
|
g_logging_manager.LogInfo("========== EA Initialized Successfully ==========");
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
g_logging_manager.LogInfo("========== EA Deinitializing ==========");
|
|
|
|
// Save state
|
|
if(g_state_manager != NULL)
|
|
{
|
|
g_state_manager.SaveState(g_accumulated_loss, g_consecutive_losses);
|
|
}
|
|
|
|
// Delete UI
|
|
if(g_ui_manager != NULL)
|
|
{
|
|
g_ui_manager.DeleteUI();
|
|
}
|
|
|
|
// Delete components
|
|
if(g_signal_detector != NULL)
|
|
{
|
|
delete g_signal_detector;
|
|
g_signal_detector = NULL;
|
|
}
|
|
|
|
if(g_time_filter != NULL)
|
|
{
|
|
delete g_time_filter;
|
|
g_time_filter = NULL;
|
|
}
|
|
|
|
if(g_money_manager != NULL)
|
|
{
|
|
delete g_money_manager;
|
|
g_money_manager = NULL;
|
|
}
|
|
|
|
if(g_risk_manager != NULL)
|
|
{
|
|
delete g_risk_manager;
|
|
g_risk_manager = NULL;
|
|
}
|
|
|
|
if(g_partial_close_manager != NULL)
|
|
{
|
|
delete g_partial_close_manager;
|
|
g_partial_close_manager = NULL;
|
|
}
|
|
|
|
if(g_trade_executor != NULL)
|
|
{
|
|
delete g_trade_executor;
|
|
g_trade_executor = NULL;
|
|
}
|
|
|
|
if(g_state_manager != NULL)
|
|
{
|
|
delete g_state_manager;
|
|
g_state_manager = NULL;
|
|
}
|
|
|
|
if(g_ui_manager != NULL)
|
|
{
|
|
delete g_ui_manager;
|
|
g_ui_manager = NULL;
|
|
}
|
|
|
|
if(g_logging_manager != NULL)
|
|
{
|
|
delete g_logging_manager;
|
|
g_logging_manager = NULL;
|
|
}
|
|
|
|
g_logging_manager.LogInfo("========== EA Deinitialized ==========");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Update UI
|
|
UpdateUI();
|
|
|
|
// Manage risk (breakeven, trailing)
|
|
g_risk_manager.ManageRiskManagement();
|
|
|
|
// Check partial closes
|
|
g_partial_close_manager.CheckAndExecutePartialCloses();
|
|
|
|
// Check new bar
|
|
if(!IsNewBar()) return;
|
|
|
|
g_logging_manager.LogInfo("========== New Bar Detected ==========");
|
|
|
|
// Update daily profit tracking
|
|
UpdateDailyProfitTracking();
|
|
|
|
// Process based on trade mode
|
|
if(Trade_Mode == MODE_MANUAL)
|
|
{
|
|
ProcessManualTrade();
|
|
}
|
|
else if(Trade_Mode == MODE_INDICATOR)
|
|
{
|
|
ProcessIndicatorTrade();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Chart event function |
|
|
//+------------------------------------------------------------------+
|
|
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
|
{
|
|
if(id == CHARTEVENT_OBJECT_CLICK && Trade_Mode == MODE_MANUAL && sparam == "ManualTradeButton")
|
|
{
|
|
g_manual_trade_button_pressed = true;
|
|
g_logging_manager.LogDebug("Manual trade button clicked");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if new bar |
|
|
//+------------------------------------------------------------------+
|
|
bool IsNewBar()
|
|
{
|
|
datetime current_bar_time = iTime(g_symbol, PERIOD_CURRENT, 0);
|
|
|
|
if(current_bar_time != g_last_bar_time)
|
|
{
|
|
g_last_bar_time = current_bar_time;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update UI |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateUI()
|
|
{
|
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double loss_percent = 0;
|
|
|
|
if(account_balance > 0)
|
|
{
|
|
loss_percent = (g_accumulated_loss / account_balance) * 100.0;
|
|
}
|
|
|
|
g_ui_manager.UpdateLossDisplay(g_accumulated_loss, loss_percent);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset daily profit if needed |
|
|
//+------------------------------------------------------------------+
|
|
void ResetDailyProfitIfNeeded()
|
|
{
|
|
datetime now = TimeCurrent();
|
|
datetime midnight = now - (now % 86400);
|
|
datetime reset_time = midnight + 3600; // 01:00 GMT
|
|
|
|
if(now >= reset_time &&
|
|
(g_last_daily_reset_time < reset_time ||
|
|
TimeToString(now, TIME_DATE) != TimeToString(g_last_daily_reset_time, TIME_DATE)))
|
|
{
|
|
double current_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
g_money_manager.ResetDailyProfit(current_balance);
|
|
g_last_daily_reset_time = now;
|
|
g_logging_manager.LogInfo("Daily Profit Tracking Reset at 01:00. Start Balance: " + DoubleToString(current_balance, 2));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update daily profit tracking |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateDailyProfitTracking()
|
|
{
|
|
ResetDailyProfitIfNeeded();
|
|
|
|
// Calculate daily profit from history
|
|
double daily_profit = 0;
|
|
|
|
for(int i = HistoryDealsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(HistoryDealSelect(i))
|
|
{
|
|
string symbol;
|
|
long magic, entry_type, deal_time;
|
|
double profit, commission, swap;
|
|
|
|
if(HistoryDealGetString(i, DEAL_SYMBOL, symbol) &&
|
|
HistoryDealGetInteger(i, DEAL_MAGIC, magic) &&
|
|
HistoryDealGetInteger(i, DEAL_ENTRY, entry_type) &&
|
|
HistoryDealGetInteger(i, DEAL_TIME, deal_time) &&
|
|
HistoryDealGetDouble(i, DEAL_PROFIT, profit) &&
|
|
HistoryDealGetDouble(i, DEAL_COMMISSION, commission) &&
|
|
HistoryDealGetDouble(i, DEAL_SWAP, swap))
|
|
{
|
|
if(symbol == g_symbol &&
|
|
magic == MagicNumber &&
|
|
entry_type == DEAL_ENTRY_OUT &&
|
|
deal_time > g_last_daily_reset_time)
|
|
{
|
|
daily_profit += profit + commission + swap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g_money_manager.SetDailyProfitAccumulated(daily_profit);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Process manual trade |
|
|
//+------------------------------------------------------------------+
|
|
void ProcessManualTrade()
|
|
{
|
|
if(!g_manual_trade_button_pressed) return;
|
|
|
|
g_manual_trade_button_pressed = false;
|
|
|
|
double manual_tp = g_ui_manager.GetManualTP();
|
|
double manual_sl = g_ui_manager.GetManualSL();
|
|
|
|
if(manual_tp == 0 || manual_sl == 0)
|
|
{
|
|
g_logging_manager.LogWarning("Please fill both Stop Loss and Take Profit values for manual trade.");
|
|
return;
|
|
}
|
|
|
|
double bid = SymbolInfoDouble(g_symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(g_symbol, SYMBOL_ASK);
|
|
|
|
bool is_buy = (manual_tp > bid && manual_sl < bid);
|
|
bool is_sell = (manual_tp < bid && manual_sl > bid);
|
|
|
|
if(!is_buy && !is_sell)
|
|
{
|
|
g_logging_manager.LogWarning("Invalid SL/TP values for a market order.");
|
|
return;
|
|
}
|
|
|
|
// Calculate lot size
|
|
double lot_size = g_money_manager.CalculateLotSize(is_buy, is_buy ? ask : bid, manual_tp, AccountInfoDouble(ACCOUNT_BALANCE));
|
|
|
|
// Validate SL/TP
|
|
double tp_prices[] = {manual_tp};
|
|
CRiskManager::ValidatedSLTP validated = g_risk_manager.ValidateSLTP(is_buy, is_buy ? ask : bid, manual_sl, tp_prices, 1);
|
|
|
|
if(!validated.is_valid)
|
|
{
|
|
g_logging_manager.LogWarning("SL/TP validation failed: " + validated.error_message);
|
|
return;
|
|
}
|
|
|
|
// Execute trade
|
|
CTradeExecutor::TradeResult result = g_trade_executor.ExecuteTrade(
|
|
is_buy,
|
|
lot_size,
|
|
is_buy ? ask : bid,
|
|
validated.sl_price,
|
|
validated.tp_prices,
|
|
validated.tp_count
|
|
);
|
|
|
|
if(!result.success)
|
|
{
|
|
g_logging_manager.LogError(GetLastError(), "ProcessManualTrade - ExecuteTrade");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Process indicator trade |
|
|
//+------------------------------------------------------------------+
|
|
void ProcessIndicatorTrade()
|
|
{
|
|
// Check daily profit target
|
|
if(g_money_manager.IsDailyProfitTargetReached())
|
|
{
|
|
g_logging_manager.LogInfo("Daily profit target reached. Skipping trade.");
|
|
return;
|
|
}
|
|
|
|
// Check time filter
|
|
if(!g_time_filter.IsTimeAllowed())
|
|
{
|
|
g_logging_manager.LogDebug("Time filter not allowed. Skipping trade.");
|
|
return;
|
|
}
|
|
|
|
// Check if trade is already open
|
|
if(g_trade_executor.IsTradeOpen())
|
|
{
|
|
g_logging_manager.LogDebug("Trade already open. Skipping new signal.");
|
|
return;
|
|
}
|
|
|
|
// Detect signal
|
|
CSignalDetector::SignalData signal = g_signal_detector.DetectSignal(1);
|
|
|
|
if(!signal.has_signal)
|
|
{
|
|
g_logging_manager.LogDebug("No signal detected.");
|
|
return;
|
|
}
|
|
|
|
g_logging_manager.LogInfo(signal.is_buy ? "BUY signal detected" : "SELL signal detected");
|
|
g_logging_manager.LogInfo("Signal Price: " + DoubleToString(signal.signal_price, g_digits));
|
|
g_logging_manager.LogInfo("SL: " + DoubleToString(signal.sl_price, g_digits));
|
|
|
|
for(int i = 0; i < signal.tp_count; i++)
|
|
{
|
|
g_logging_manager.LogInfo("TP" + IntegerToString(i + 1) + ": " + DoubleToString(signal.tp_prices[i], g_digits));
|
|
}
|
|
|
|
if(signal.used_atr_fallback)
|
|
{
|
|
g_logging_manager.LogInfo("ATR fallback used for SL/TP");
|
|
}
|
|
|
|
// Exit opposite trade if enabled
|
|
if(ExitOnOppositeSignal)
|
|
{
|
|
g_trade_executor.CloseOppositeTrade(signal.is_buy);
|
|
}
|
|
|
|
// Calculate lot size
|
|
double open_price = SymbolInfoDouble(g_symbol, SYMBOL_BID);
|
|
double lot_size = g_money_manager.CalculateLotSize(
|
|
signal.is_buy,
|
|
open_price,
|
|
signal.tp_prices[0], // Use first TP for lot calculation
|
|
AccountInfoDouble(ACCOUNT_BALANCE)
|
|
);
|
|
|
|
g_logging_manager.LogInfo("Lot size: " + DoubleToString(lot_size, 2));
|
|
|
|
// Validate SL/TP
|
|
CRiskManager::ValidatedSLTP validated = g_risk_manager.ValidateSLTP(
|
|
signal.is_buy,
|
|
open_price,
|
|
signal.sl_price,
|
|
signal.tp_prices,
|
|
signal.tp_count
|
|
);
|
|
|
|
if(!validated.is_valid)
|
|
{
|
|
g_logging_manager.LogWarning("SL/TP validation failed: " + validated.error_message);
|
|
return;
|
|
}
|
|
|
|
// Execute trade
|
|
CTradeExecutor::TradeResult result = g_trade_executor.ExecuteTrade(
|
|
signal.is_buy,
|
|
lot_size,
|
|
open_price,
|
|
validated.sl_price,
|
|
validated.tp_prices,
|
|
validated.tp_count
|
|
);
|
|
|
|
if(!result.success)
|
|
{
|
|
g_logging_manager.LogError(GetLastError(), "ProcessIndicatorTrade - ExecuteTrade");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+ |