Files
EA/Buffer EA/Universal_Buffer_Reader_EA_Combined.mq5
Kunthawat Greethong 04aa2eb2e6 New EA and Indi
2026-01-25 10:34:54 +07:00

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");
}
}
//+------------------------------------------------------------------+