3 Commits

Author SHA1 Message Date
peter c88216db03 Finalize EA architecture before strategy development 2026-06-07 16:32:51 -04:00
peter fff49ea9da Add XAUUSD strategy Adaptation 2026-05-28 19:09:09 -04:00
peter 6208bbd9c5 Fix critical EA architecture issues 2026-05-28 17:34:34 -04:00
11 changed files with 1562 additions and 281 deletions
+50
View File
@@ -0,0 +1,50 @@
\# Codex instructions for MyProEA
This workspace is only for the MyProEA MT5 Expert Advisor project.
Only read, edit, and reason about files inside this folder unless I explicitly provide another file.
Main EA file:
\- MyProEA.mq5
Project include files:
\- Include/
Strategy files:
\- Include/Strategies/
Do not modify files outside this project.
Do not modify global MQL5 Include files.
Do not scan unrelated Experts, Indicators, Scripts, Libraries, Logs, or Profiles.
When changing code:
1\. Keep the existing architecture.
2\. Make the smallest safe change.
3\. Do not rewrite unrelated managers or framework files.
4\. Strategy files should only output buy/sell signals, SL, TP, and metadata expected by the existing project.
5\. Explain which file you changed and why.
+87 -12
View File
@@ -13,23 +13,43 @@ enum E_LOT_MODE
LOT_MODE_RISK = 1 // Use risk percent method
};
input E_LOT_MODE g_lot_mode = LOT_MODE_FIXED; // Lot Sizing Mode
enum E_TREND_BREAK_MODE
{
BREAK_MODE_CANDLE_CLOSE = 0,
BREAK_MODE_BID_ASK_TOUCH = 1
};
enum E_STOP_LOSS_MODE
{
STOP_LOSS_STRUCTURE = 0,
STOP_LOSS_POINTS = 1,
STOP_LOSS_PERCENT = 2
};
enum E_TAKE_PROFIT_MODE
{
TAKE_PROFIT_POINTS = 1,
TAKE_PROFIT_PERCENT = 2,
TAKE_PROFIT_RISK_REWARD = 3
};
input E_LOT_MODE g_lot_mode = LOT_MODE_RISK; // Lot Sizing Mode
input double g_fixed_lot = 0.1; // Fixed Lot Size (if LOT_MODE_FIXED)
input double g_risk_percent = 2.0; // Risk Percent (if LOT_MODE_RISK)
input double g_max_lot = 10.0; // Maximum Lot Size
input double g_min_lot = 0.01; // Minimum Lot Size
// ==================== TRADE MANAGEMENT ====================
input int g_magic_number = 12345; // Magic Number for trades
input int g_stop_loss_points = 100; // Stop Loss in points
input int g_take_profit_points = 200; // Take Profit in points
input int g_max_spread_points = 10; // Max Spread in points
input int g_stop_loss_points = 100; // Deprecated: strategy owns initial SL
input int g_take_profit_points = 200; // Deprecated: strategy owns initial TP
input int g_max_spread_points = 100; // Max Spread in points (1.00 USD for XAUUSD)
input int g_max_positions = 1; // Max positions at once
// ==================== TRADING HOURS ====================
input bool g_use_trading_hours = false; // Enable trading hour filter
input int g_trade_start_hour = 8; // Trading Start Hour (0-23)
input int g_trade_end_hour = 20; // Trading End Hour (0-23)
input int g_trade_start_hour = 8; // Trading Start Hour (0-23), based on broker server time
input int g_trade_end_hour = 20; // Trading End Hour (0-23), based on broker server time
input string g_trading_hours_note = "Broker server time is used; adjust start/end hours for your broker timezone and session preferences"; // informational only
// ==================== TRAILING STOP ====================
input bool g_use_trailing_stop = true; // Use Trailing Stop
@@ -38,12 +58,67 @@ input bool g_use_break_even = true; // Use Break Even
input int g_break_even_profit = 10; // Break Even Trigger profit
input int g_break_even_sl = 2; // Break Even SL distance
// Enhanced break-even / trailing parameters (ATR-aware)
input bool g_use_break_even_atr = true; // Use ATR-based break-even behavior
input double g_break_even_trigger_r = 1.0; // Break-even trigger as multiple of initial risk (ATR or fixed)
input int g_break_even_buffer_points = 20; // Buffer to apply when moving to break-even (points)
input bool g_use_trailing_stop_atr = true; // Use ATR-based trailing stop
input double g_trail_start_r = 1.5; // Start trailing after this multiple of initial risk
input double g_trail_distance_atr_multiplier = 1.0; // Trailing distance = ATR * this multiplier
// ==================== STRATEGY EVALUATION ====================
input bool g_evaluate_on_new_bar = true; // Evaluate strategy on entry timeframe new bar
input bool g_evaluate_every_tick = false; // Evaluate strategy every tick when true
// ==================== STRATEGY PARAMETERS ====================
input int g_ma_fast_period = 10; // Fast MA Period
input int g_ma_slow_period = 20; // Slow MA Period
input int g_ma_shift = 0; // MA Shift
input ENUM_MA_METHOD g_ma_method = MODE_SMA; // MA Method
input ENUM_APPLIED_PRICE g_ma_price = PRICE_CLOSE; // MA Applied Price
input ENUM_TIMEFRAMES g_strategy_entry_timeframe = PERIOD_M15; // Signal/entry timeframe
input ENUM_TIMEFRAMES g_strategy_trend_timeframe = PERIOD_H1; // Filter/trend timeframe
input int g_signal_swing_depth = 3;
input int g_filter_swing_depth = 5;
input int g_signal_trend_confirm_count = 1;
input int g_filter_trend_confirm_count = 2;
input bool g_use_atr_filter = true;
input int g_atr_period = 14;
input double g_min_swing_atr_multiplier = 0.5;
input bool g_use_breakout_entry = true;
input int g_entry_buffer_points = 10;
input int g_signal_expiration_bars = 3;
input E_TREND_BREAK_MODE g_trend_break_mode = BREAK_MODE_CANDLE_CLOSE;
input int g_break_buffer_points = 0;
input int g_cooldown_bars_after_trend_break = 1;
input E_STOP_LOSS_MODE g_stop_loss_mode = STOP_LOSS_STRUCTURE;
input int g_sl_points = 50;
input double g_sl_percent = 0.5;
input int g_sl_buffer_points = 50;
input E_TAKE_PROFIT_MODE g_take_profit_mode = TAKE_PROFIT_RISK_REWARD;
input int g_tp_points = 100;
input double g_tp_percent = 1.0;
input double g_risk_reward_ratio = 2.0;
input int g_max_trades_per_filter_trend = 1;
input int g_strategy_max_open_positions = 1;
input int g_strategy_max_spread_points = 30;
input bool g_show_signal_swings = true;
input bool g_show_filter_swings = true;
input bool g_show_swing_level_lines = true;
input bool g_show_trend_lines = true;
input bool g_show_setup_trigger_lines = true;
input bool g_show_trend_rectangles = false;
// TP-less trades are intentionally not supported yet. Add an explicit
// allowNoTakeProfit field to TradeSetup before allowing takeProfit = 0.
input double g_risk_percent = 1.0; // Risk percent per trade
input double g_max_risk_percent = 2.0; // Maximum allowed risk percent
// ==================== DEBUG ====================
input bool g_debug_mode = true; // Enable Debug Logging
+7 -1
View File
@@ -78,7 +78,13 @@ public:
// Detect new bar on current timeframe
bool IsNewBar()
{
datetime bar_time = iTime(m_symbol, PERIOD_CURRENT, 0);
return IsNewBar(PERIOD_CURRENT);
}
// Detect new bar on a supplied timeframe
bool IsNewBar(ENUM_TIMEFRAMES timeframe)
{
datetime bar_time = iTime(m_symbol, timeframe, 0);
if(m_last_bar_time == 0)
{
+263 -79
View File
@@ -1,6 +1,6 @@
//+------------------------------------------------------------------+
//| RiskManager.mqh - Risk and position sizing management |
//| Calculates lot sizes, validates parameters, checks trading hours |
//| Calculates trade entry levels, stop loss, take profit and lot sizes|
//+------------------------------------------------------------------+
#ifndef __RISKMANAGER_MQH__
@@ -10,6 +10,7 @@
#include "Logger.mqh"
#include "MarketData.mqh"
#include "Utilities.mqh"
#include "Signal.mqh"
class CRiskManager
{
@@ -18,75 +19,194 @@ private:
CLogger *mp_logger;
public:
// Constructor
CRiskManager(CMarketData *market_data, CLogger *logger)
{
mp_market_data = market_data;
mp_logger = logger;
}
// Calculate lot size based on configuration
double CalculateLotSize(int stop_loss_points)
double CalculateLotByRisk(double risk_distance)
{
double lot = 0.0;
string symbol = mp_market_data.GetSymbol();
if(!IsSymbolValid(symbol))
return 0.0;
if(risk_distance <= 0.0)
{
if(mp_logger)
mp_logger.Error("Risk distance must be positive to calculate lot size");
return 0.0;
}
if(g_lot_mode == LOT_MODE_FIXED)
{
lot = g_fixed_lot;
}
else if(g_lot_mode == LOT_MODE_RISK)
{
lot = CalculateLotByRisk(stop_loss_points);
}
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
if(tick_size <= 0.0 || tick_value <= 0.0)
{
if(mp_logger)
mp_logger.Error("Symbol tick size or tick value invalid for lot calculation");
return 0.0;
}
return ValidateLotSize(lot);
double account_equity = AccountInfoDouble(ACCOUNT_EQUITY);
if(account_equity <= 0.0)
{
if(mp_logger)
mp_logger.Error("Account equity is invalid for lot calculation");
return 0.0;
}
double effective_risk_percent = GetEffectiveRiskPercent();
if(effective_risk_percent <= 0.0)
{
if(mp_logger)
mp_logger.Error("Effective risk percent is invalid");
return 0.0;
}
double risk_amount = account_equity * (effective_risk_percent / 100.0);
// Loss per single lot = (risk_distance / tick_size) * tick_value
double loss_per_lot = (risk_distance / tick_size) * tick_value;
if(loss_per_lot <= 0.0)
{
if(mp_logger)
mp_logger.Error("Calculated monetary loss per lot is non-positive");
return 0.0;
}
double raw_lot = risk_amount / loss_per_lot;
double normalized_lot = CUtilities::NormalizeLot(symbol, raw_lot);
if(normalized_lot <= 0.0)
{
if(mp_logger)
mp_logger.Warning(StringFormat("No valid lot size: raw=%.6f normalized=%.6f (min/max/step constraints)", raw_lot, normalized_lot));
return 0.0;
}
if(mp_logger && g_debug_mode)
{
mp_logger.Info(StringFormat("Tick size=%.8f tick value=%.8f risk%%=%.2f rawLot=%.6f finalLot=%.6f",
tick_size, tick_value, effective_risk_percent, raw_lot, normalized_lot));
}
return normalized_lot;
}
// Calculate lot size based on risk percent
double CalculateLotByRisk(int stop_loss_points)
bool ValidateTradeSetup(const TradeSetup &setup, double &lot)
{
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double stop_loss_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), stop_loss_points);
double contract_size = CUtilities::GetContractSize(mp_market_data.GetSymbol());
if(stop_loss_distance == 0 || contract_size == 0)
return g_min_lot;
string symbol = mp_market_data.GetSymbol();
if(!IsSymbolValid(symbol))
{
if(mp_logger)
mp_logger.Error("Invalid symbol for trade setup validation");
return false;
}
if(setup.signal != SIGNAL_BUY && setup.signal != SIGNAL_SELL)
{
if(mp_logger && g_debug_mode)
mp_logger.Info("Trade setup rejected: no valid signal");
return false;
}
// Risk = Account Balance * Risk Percent / 100
double risk_amount = account_balance * (g_risk_percent / 100.0);
// Lot = Risk Amount / (SL Distance * Contract Size * Point)
double point = mp_market_data.GetPoint();
double lot = risk_amount / (stop_loss_distance * contract_size);
return lot;
}
double execution_price = (setup.signal == SIGNAL_BUY) ? mp_market_data.GetAsk() : mp_market_data.GetBid();
double setup_entry_price = setup.entryPrice;
double setup_diff = 0.0;
if(setup_entry_price > 0.0)
{
setup_diff = MathAbs(setup_entry_price - execution_price);
}
// Validate and normalize lot size
double ValidateLotSize(double lot)
{
// Apply global limits first
if(lot < g_min_lot)
lot = g_min_lot;
if(lot > g_max_lot)
lot = g_max_lot;
if(setup.stopLoss <= 0.0 || setup.takeProfit <= 0.0 || setup.riskDistance <= 0.0)
{
if(mp_logger)
mp_logger.Warning(StringFormat("Trade setup rejected: valid initial SL and TP are required (TP-less trades need a future explicit allowNoTakeProfit field). setupEntry=%.5f execEntry=%.5f sl=%.5f tp=%.5f risk=%.5f",
setup_entry_price, execution_price, setup.stopLoss, setup.takeProfit, setup.riskDistance));
return false;
}
// Normalize to broker's lot step
lot = CUtilities::NormalizeLot(mp_market_data.GetSymbol(), lot);
bool valid_side = true;
if(setup.signal == SIGNAL_BUY)
{
valid_side = (setup.stopLoss < execution_price && setup.takeProfit > execution_price);
}
else if(setup.signal == SIGNAL_SELL)
{
valid_side = (setup.stopLoss > execution_price && setup.takeProfit < execution_price);
}
if(!valid_side)
{
if(mp_logger)
mp_logger.Warning(StringFormat("Trade setup rejected: SL/TP invalid for current market entry price (setupEntry=%.5f execEntry=%.5f sl=%.5f tp=%.5f)",
setup_entry_price, execution_price, setup.stopLoss, setup.takeProfit));
return false;
}
// Broker minimal stop distance relative to current market execution price
double point = CUtilities::GetPoint(symbol);
long min_stop_points = (long)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(min_stop_points < 0)
min_stop_points = 0;
double min_stop_distance = min_stop_points * point;
double actual_risk_distance = MathAbs(execution_price - setup.stopLoss);
if(actual_risk_distance <= 0.0)
{
if(mp_logger)
mp_logger.Warning(StringFormat("Trade setup rejected: non-positive risk distance after using execution price (execEntry=%.5f sl=%.5f)",
execution_price, setup.stopLoss));
return false;
}
if(actual_risk_distance < min_stop_distance)
{
if(mp_logger)
mp_logger.Warning(StringFormat("Trade setup rejected: SL too close to current entry price (distance %.5f < broker stop level %.5f)", actual_risk_distance, min_stop_distance));
return false;
}
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Lot size calculated: %.2f", lot));
{
mp_logger.Info(StringFormat("Trade setup details: setupEntry=%.5f execEntry=%.5f diff=%.5f SL=%.5f TP=%.5f setupRisk=%.5f execRisk=%.5f reason=%s",
setup_entry_price, execution_price, setup_diff, setup.stopLoss, setup.takeProfit, setup.riskDistance, actual_risk_distance, setup.reason));
}
return lot;
// Lot sizing: use actual execution entry-to-SL distance in price units
lot = CalculateLotByRisk(actual_risk_distance);
if(lot <= 0.0)
{
if(mp_logger)
mp_logger.Warning("Trade setup rejected: lot sizing failed or below broker minimum after execution price adjustment");
return false;
}
if(mp_logger && g_debug_mode)
{
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
mp_logger.Info(StringFormat("Trade setup validated: signal=%s setupEntry=%.5f execEntry=%.5f diff=%.5f SL=%.5f TP=%.5f execRisk=%.5f tickSize=%.8f tickValue=%.8f riskUsed=%.2f finalLot=%.6f reason=%s",
setup.signal == SIGNAL_BUY ? "BUY" : "SELL",
setup_entry_price, execution_price, setup_diff,
setup.stopLoss, setup.takeProfit, actual_risk_distance,
tick_size, tick_value, GetEffectiveRiskPercent(), lot, setup.reason));
}
return true;
return true;
}
// Check if spread is acceptable
bool IsSpreadAcceptable()
{
return mp_market_data.IsSpreadAcceptable(g_max_spread_points);
int max_spread = g_max_spread_points;
string symbol = mp_market_data.GetSymbol();
if(StringFind(symbol, "XAU") >= 0 || StringFind(symbol, "GOLD") >= 0)
{
max_spread = MathMax(max_spread, 200);
}
return mp_market_data.IsSpreadAcceptable(max_spread);
}
// Check if trading is allowed by time filter
bool IsTradingHourValid()
{
if(!g_use_trading_hours)
@@ -98,23 +218,21 @@ public:
if(g_trade_start_hour <= g_trade_end_hour)
{
// Normal case: e.g., 8:00 to 20:00
if(current_hour < g_trade_start_hour || current_hour >= g_trade_end_hour)
{
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)",
current_hour, g_trade_start_hour, g_trade_end_hour));
mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)",
current_hour, g_trade_start_hour, g_trade_end_hour));
return false;
}
}
else
{
// Overnight case: e.g., 20:00 to 8:00
if(current_hour < g_trade_start_hour && current_hour >= g_trade_end_hour)
{
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)",
current_hour, g_trade_start_hour, g_trade_end_hour));
mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)",
current_hour, g_trade_start_hour, g_trade_end_hour));
return false;
}
}
@@ -122,37 +240,103 @@ public:
return true;
}
// Calculate stop loss price in absolute terms
double CalculateStopLossPrice(bool buy)
{
double bid = mp_market_data.GetBid();
double ask = mp_market_data.GetAsk();
double entry_price = buy ? ask : bid;
double sl_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_stop_loss_points);
double sl_price = buy ? (entry_price - sl_distance) : (entry_price + sl_distance);
return CUtilities::NormalizePrice(mp_market_data.GetSymbol(), sl_price);
}
// Calculate take profit price in absolute terms
double CalculateTakeProfitPrice(bool buy)
{
double bid = mp_market_data.GetBid();
double ask = mp_market_data.GetAsk();
double entry_price = buy ? ask : bid;
double tp_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_take_profit_points);
double tp_price = buy ? (entry_price + tp_distance) : (entry_price - tp_distance);
return CUtilities::NormalizePrice(mp_market_data.GetSymbol(), tp_price);
}
// Get market data reference
CMarketData* GetMarketData()
{
return mp_market_data;
}
private:
double GetEffectiveRiskPercent()
{
double risk_percent = g_risk_percent;
if(risk_percent > g_max_risk_percent)
{
if(mp_logger)
mp_logger.Warning(StringFormat("RiskPercent capped from %.2f%% to %.2f%% (MaxRiskPercent)",
risk_percent, g_max_risk_percent));
risk_percent = g_max_risk_percent;
}
return risk_percent;
}
double GetAtrValue(int shift)
{
string symbol = mp_market_data.GetSymbol();
int atr_handle = iATR(symbol, g_strategy_entry_timeframe, g_atr_period);
if(atr_handle == INVALID_HANDLE)
return 0.0;
double atr_value[];
ArraySetAsSeries(atr_value, true);
ArrayResize(atr_value, 1);
if(CopyBuffer(atr_handle, 0, shift, 1, atr_value) <= 0)
{
IndicatorRelease(atr_handle);
return 0.0;
}
double atr = atr_value[0];
IndicatorRelease(atr_handle);
return atr;
}
double ValidateLotSize(double lot)
{
string symbol = mp_market_data.GetSymbol();
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(min_lot <= 0.0 || max_lot <= 0.0 || lot_step <= 0.0 || max_lot < min_lot)
{
if(mp_logger)
mp_logger.Error("Invalid volume step or limits for symbol");
return 0.0;
}
if(lot <= 0.0)
return 0.0;
double normalized = MathFloor(lot / lot_step) * lot_step;
if(normalized < min_lot || normalized > max_lot)
{
if(mp_logger && g_debug_mode)
mp_logger.Warning(StringFormat("Normalized lot %.2f outside allowed range [%.2f, %.2f]", normalized, min_lot, max_lot));
return 0.0;
}
normalized = NormalizeDouble(normalized, 2);
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Lot size calculated: %.2f", normalized));
return normalized;
}
bool IsSymbolValid(const string symbol)
{
if(!mp_market_data.IsTradingAllowed())
{
if(mp_logger)
mp_logger.Error("Symbol trading is not allowed");
return false;
}
if(mp_market_data.GetPoint() <= 0.0)
{
if(mp_logger)
mp_logger.Error("Symbol point size is invalid");
return false;
}
if(CUtilities::GetContractSize(symbol) <= 0.0)
{
if(mp_logger)
mp_logger.Error("Symbol contract size is invalid");
return false;
}
return true;
}
};
#endif //__RISKMANAGER_MQH__
+11
View File
@@ -13,4 +13,15 @@ enum E_SIGNAL
SIGNAL_SELL = -1 // Sell signal
};
struct TradeSetup
{
E_SIGNAL signal;
double entryPrice;
double stopLoss;
double takeProfit;
double riskDistance;
double atrValue;
string reason;
};
#endif //__SIGNAL_MQH__
+917 -88
View File
File diff suppressed because it is too large Load Diff
+43 -49
View File
@@ -1,6 +1,6 @@
//+------------------------------------------------------------------+
//| TradeManager.mqh - Trade execution using CTrade class |
//| Handles buy/sell orders with SL/TP and magic number |
//| Handles buy/sell orders with explicit SL/TP and risk controls |
//+------------------------------------------------------------------+
#ifndef __TRADEMANAGER_MQH__
@@ -19,100 +19,105 @@ private:
CRiskManager *mp_risk_manager;
public:
// Constructor
CTradeManager(CLogger *logger, CRiskManager *risk_manager)
{
mp_logger = logger;
mp_risk_manager = risk_manager;
// Set magic number
m_trade.SetExpertMagicNumber((ulong)g_magic_number);
// Set async/sync mode
m_trade.SetAsyncMode(false);
// Set slippage
m_trade.SetDeviationInPoints(10);
}
// Destructor
~CTradeManager()
{
}
// Open BUY trade
bool OpenBuyTrade(const string symbol, double lot)
bool OpenBuyTrade(const string symbol, double lot, double sl, double tp, double setupEntryPrice = 0.0)
{
if(lot <= 0)
if(lot <= 0.0)
{
if(mp_logger)
mp_logger.Error(StringFormat("Invalid lot size: %.2f", lot));
return false;
}
// TP-less trades are intentionally unsupported until TradeSetup has an
// explicit allowNoTakeProfit field; do not infer that from tp = 0.
if(sl <= 0.0 || tp <= 0.0)
{
if(mp_logger)
mp_logger.Error("Invalid SL or TP provided for BUY order");
return false;
}
double ask = mp_risk_manager.GetMarketData().GetAsk();
double sl = mp_risk_manager.CalculateStopLossPrice(true);
double tp = mp_risk_manager.CalculateTakeProfitPrice(true);
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Opening BUY: lot=%.2f, SL=%.5f, TP=%.5f", lot, sl, tp));
{
double sl_distance = MathAbs(ask - sl);
double tp_distance = MathAbs(tp - ask);
double setup_diff = (setupEntryPrice > 0.0) ? MathAbs(setupEntryPrice - ask) : 0.0;
string setup_info = (setupEntryPrice > 0.0) ? StringFormat("setupEntry=%.5f, execEntry=%.5f, diff=%.5f, ", setupEntryPrice, ask, setup_diff) : "execEntry=" + DoubleToString(ask, 5) + ", ";
mp_logger.Info(StringFormat("Opening BUY: %s lot=%.2f, SL=%.5f, TP=%.5f, SL dist=%.5f, TP dist=%.5f",
setup_info, lot, sl, tp, sl_distance, tp_distance));
}
if(!m_trade.Buy(lot, symbol, ask, sl, tp))
{
if(mp_logger)
{
mp_logger.Error(StringFormat("Buy trade failed. Result code: %d, Error: %s",
m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription()));
}
return false;
}
if(mp_logger)
{
mp_logger.Info(StringFormat("Buy trade opened. Ticket: %I64d, Volume: %.2f",
m_trade.ResultOrder(), lot));
}
mp_logger.Info(StringFormat("Buy trade opened. Ticket: %I64d, Volume: %.2f", m_trade.ResultOrder(), lot));
return true;
}
// Open SELL trade
bool OpenSellTrade(const string symbol, double lot)
bool OpenSellTrade(const string symbol, double lot, double sl, double tp, double setupEntryPrice = 0.0)
{
if(lot <= 0)
if(lot <= 0.0)
{
if(mp_logger)
mp_logger.Error(StringFormat("Invalid lot size: %.2f", lot));
return false;
}
double bid = mp_risk_manager.GetMarketData().GetBid();
double sl = mp_risk_manager.CalculateStopLossPrice(false);
double tp = mp_risk_manager.CalculateTakeProfitPrice(false);
// TP-less trades are intentionally unsupported until TradeSetup has an
// explicit allowNoTakeProfit field; do not infer that from tp = 0.
if(sl <= 0.0 || tp <= 0.0)
{
if(mp_logger)
mp_logger.Error("Invalid SL or TP provided for SELL order");
return false;
}
double bid = mp_risk_manager.GetMarketData().GetBid();
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Opening SELL: lot=%.2f, SL=%.5f, TP=%.5f", lot, sl, tp));
{
double sl_distance = MathAbs(sl - bid);
double tp_distance = MathAbs(bid - tp);
double setup_diff = (setupEntryPrice > 0.0) ? MathAbs(setupEntryPrice - bid) : 0.0;
string setup_info = (setupEntryPrice > 0.0) ? StringFormat("setupEntry=%.5f, execEntry=%.5f, diff=%.5f, ", setupEntryPrice, bid, setup_diff) : "execEntry=" + DoubleToString(bid, 5) + ", ";
mp_logger.Info(StringFormat("Opening SELL: %s lot=%.2f, SL=%.5f, TP=%.5f, SL dist=%.5f, TP dist=%.5f",
setup_info, lot, sl, tp, sl_distance, tp_distance));
}
if(!m_trade.Sell(lot, symbol, bid, sl, tp))
{
if(mp_logger)
{
mp_logger.Error(StringFormat("Sell trade failed. Result code: %d, Error: %s",
m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription()));
}
return false;
}
if(mp_logger)
{
mp_logger.Info(StringFormat("Sell trade opened. Ticket: %I64d, Volume: %.2f",
m_trade.ResultOrder(), lot));
}
mp_logger.Info(StringFormat("Sell trade opened. Ticket: %I64d, Volume: %.2f", m_trade.ResultOrder(), lot));
return true;
}
// Close position by ticket
bool ClosePosition(ulong ticket)
{
if(ticket == 0)
@@ -122,18 +127,13 @@ public:
return false;
double volume = PositionGetDouble(POSITION_VOLUME);
string symbol = PositionGetString(POSITION_SYMBOL);
if(mp_logger && g_debug_mode)
mp_logger.Info(StringFormat("Closing position ticket %I64d, volume %.2f", ticket, volume));
if(!m_trade.PositionClose(ticket))
{
if(mp_logger)
{
mp_logger.Error(StringFormat("Close position failed. Ticket: %I64d, Result: %d",
ticket, m_trade.ResultRetcode()));
}
mp_logger.Error(StringFormat("Close position failed. Ticket: %I64d, Result: %d", ticket, m_trade.ResultRetcode()));
return false;
}
@@ -143,7 +143,6 @@ public:
return true;
}
// Modify position SL and/or TP
bool ModifyPosition(ulong ticket, double sl, double tp)
{
if(ticket == 0)
@@ -158,10 +157,7 @@ public:
if(!m_trade.PositionModify(ticket, sl, tp))
{
if(mp_logger)
{
mp_logger.Error(StringFormat("Position modify failed. Ticket: %I64d, Result: %d",
ticket, m_trade.ResultRetcode()));
}
mp_logger.Error(StringFormat("Position modify failed. Ticket: %I64d, Result: %d", ticket, m_trade.ResultRetcode()));
return false;
}
@@ -171,13 +167,11 @@ public:
return true;
}
// Get last result code
uint GetResultRetcode() const
{
return m_trade.ResultRetcode();
}
// Get CTrade instance
CTrade* GetTradeObject()
{
return &m_trade;
+94 -23
View File
@@ -31,7 +31,7 @@ public:
// Update trailing stop for all positions
void UpdateAllPositions()
{
if(!g_use_trailing_stop && !g_use_break_even)
if(!g_use_trailing_stop && !g_use_break_even && !g_use_trailing_stop_atr && !g_use_break_even_atr)
return;
for(int i = PositionsTotal() - 1; i >= 0; i--)
@@ -96,20 +96,44 @@ private:
bool ApplyBreakEven(ENUM_POSITION_TYPE pos_type, double open_price,
double current_sl, double profit, double &new_sl)
{
double break_even_trigger = CUtilities::PointsToPrice(mp_market_data.GetSymbol(),
g_break_even_profit);
double break_even_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(),
g_break_even_sl);
string symbol = mp_market_data.GetSymbol();
double current_price = (pos_type == POSITION_TYPE_BUY) ? mp_market_data.GetBid() : mp_market_data.GetAsk();
double price_move = 0.0;
// Break-even only if profit threshold is reached
if(profit < break_even_trigger)
if(pos_type == POSITION_TYPE_BUY)
price_move = current_price - open_price;
else if(pos_type == POSITION_TYPE_SELL)
price_move = open_price - current_price;
else
return false;
// For BUY: move SL to BE (open price + distance)
if(current_sl <= 0.0)
return false;
double initial_risk = (pos_type == POSITION_TYPE_BUY)
? (open_price - current_sl)
: (current_sl - open_price);
if(initial_risk <= 0.0)
return false;
double trigger_distance = g_break_even_trigger_r * initial_risk;
if(price_move < trigger_distance)
return false;
double buffer = CUtilities::PointsToPrice(symbol, g_break_even_buffer_points);
double point = CUtilities::GetPoint(symbol);
long min_stop_points = (long)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
double min_stop_distance = min_stop_points * point;
if(pos_type == POSITION_TYPE_BUY)
{
double be_sl = open_price + break_even_distance;
be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl);
double be_sl = CUtilities::NormalizePrice(symbol, open_price + buffer);
if((mp_market_data.GetBid() - be_sl) < min_stop_distance)
{
if(mp_logger && g_debug_mode)
mp_logger.Info("Break-even skipped: would violate broker minimal stop distance");
return false;
}
if(be_sl > current_sl)
{
@@ -119,11 +143,15 @@ private:
return true;
}
}
// For SELL: move SL to BE (open price - distance)
else if(pos_type == POSITION_TYPE_SELL)
{
double be_sl = open_price - break_even_distance;
be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl);
double be_sl = CUtilities::NormalizePrice(symbol, open_price - buffer);
if((be_sl - mp_market_data.GetAsk()) < min_stop_distance)
{
if(mp_logger && g_debug_mode)
mp_logger.Info("Break-even skipped: would violate broker minimal stop distance");
return false;
}
if(be_sl < current_sl)
{
@@ -140,18 +168,58 @@ private:
// Apply trailing stop logic
bool ApplyTrailingStop(ENUM_POSITION_TYPE pos_type, double current_sl, double &new_sl)
{
string symbol = mp_market_data.GetSymbol();
double bid = mp_market_data.GetBid();
double ask = mp_market_data.GetAsk();
double trail_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(),
g_trailing_stop_points);
// For BUY: trailing stop follows price from below
if(current_sl <= 0.0)
return false;
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double initial_risk = (pos_type == POSITION_TYPE_BUY)
? (open_price - current_sl)
: (current_sl - open_price);
if(initial_risk <= 0.0)
return false;
double price = (pos_type == POSITION_TYPE_BUY) ? bid : ask;
double price_move = (pos_type == POSITION_TYPE_BUY) ? (price - open_price) : (open_price - price);
double trail_start_distance = g_trail_start_r * initial_risk;
if(price_move < trail_start_distance)
return false;
int atr_handle = iATR(symbol, g_strategy_entry_timeframe, g_atr_period);
if(atr_handle == INVALID_HANDLE)
return false;
double atr_value[];
ArraySetAsSeries(atr_value, true);
ArrayResize(atr_value, 1);
if(CopyBuffer(atr_handle, 0, 1, 1, atr_value) <= 0)
{
IndicatorRelease(atr_handle);
return false;
}
IndicatorRelease(atr_handle);
double trail_distance = atr_value[0] * g_trail_distance_atr_multiplier;
if(trail_distance <= 0.0)
return false;
double point = CUtilities::GetPoint(symbol);
long min_stop_points = (long)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
double min_stop_distance = min_stop_points * point;
if(pos_type == POSITION_TYPE_BUY)
{
double candidate_sl = bid - trail_distance;
candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl);
double candidate_sl = CUtilities::NormalizePrice(symbol, bid - trail_distance);
if((bid - candidate_sl) < min_stop_distance)
{
if(mp_logger && g_debug_mode)
mp_logger.Info("Trailing stop skipped: candidate SL too close to price (broker stop level)");
return false;
}
// Only move SL up (never down)
if(candidate_sl > current_sl)
{
new_sl = candidate_sl;
@@ -160,13 +228,16 @@ private:
return true;
}
}
// For SELL: trailing stop follows price from above
else if(pos_type == POSITION_TYPE_SELL)
{
double candidate_sl = ask + trail_distance;
candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl);
double candidate_sl = CUtilities::NormalizePrice(symbol, ask + trail_distance);
if((candidate_sl - ask) < min_stop_distance)
{
if(mp_logger && g_debug_mode)
mp_logger.Info("Trailing stop skipped: candidate SL too close to price (broker stop level)");
return false;
}
// Only move SL down (never up)
if(candidate_sl < current_sl)
{
new_sl = candidate_sl;
+34 -10
View File
@@ -23,17 +23,41 @@ public:
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
// Apply limits
if(lot < min_lot) lot = min_lot;
if(lot > max_lot) lot = max_lot;
// Round to step
lot = MathFloor(lot / lot_step) * lot_step;
return NormalizeDouble(lot, 2);
}
// Guard against invalid broker volume data
if(min_lot <= 0.0 || max_lot <= 0.0 || lot_step <= 0.0 || max_lot < min_lot)
return 0.0;
// Compute precision from volume step
int precision = 0;
double s = lot_step;
const double eps = 1e-9;
while(MathAbs(MathRound(s) - s) > eps && precision < 8)
{
s *= 10.0;
precision++;
}
// Round down to nearest step
double normalized = MathFloor(lot / lot_step) * lot_step;
normalized = NormalizeDouble(normalized, precision);
// If normalized is below minimum, indicate invalid by returning 0.0
if(normalized < min_lot)
return 0.0;
// Clamp to maximum allowed (rounded down to step)
if(normalized > max_lot)
{
normalized = MathFloor(max_lot / lot_step) * lot_step;
normalized = NormalizeDouble(normalized, precision);
if(normalized < min_lot)
return 0.0;
}
return normalized;
}
// Convert points to price distance
static double PointsToPrice(const string symbol, int points)
{
BIN
View File
Binary file not shown.
+56 -19
View File
@@ -34,6 +34,12 @@ CPositionManager *g_position_manager = NULL;
CTradeManager *g_trade_manager = NULL;
CTrailingStop *g_trailing_stop = NULL;
// Diagnostics counters for blocked entries
long g_blocked_existing_position = 0;
long g_blocked_spread = 0;
long g_blocked_hours = 0;
long g_blocked_max_positions = 0;
//+------------------------------------------------------------------+
// EA Initialization
//+------------------------------------------------------------------+
@@ -48,6 +54,10 @@ int OnInit()
if(!g_market_data.IsTradingAllowed())
{
g_logger.Error("Trading not allowed for this symbol");
delete g_market_data;
g_market_data = NULL;
delete g_logger;
g_logger = NULL;
return INIT_FAILED;
}
@@ -56,6 +66,12 @@ int OnInit()
if(!g_strategy.Init())
{
g_logger.Error("Failed to initialize strategy");
delete g_strategy;
g_strategy = NULL;
delete g_market_data;
g_market_data = NULL;
delete g_logger;
g_logger = NULL;
return INIT_FAILED;
}
@@ -74,11 +90,13 @@ int OnInit()
// Log configuration
g_logger.Info(StringFormat("Magic Number: %d", g_magic_number));
g_logger.Info(StringFormat("Symbol: %s", _Symbol));
g_logger.Info(StringFormat("Timeframe: %s", EnumToString(PERIOD_CURRENT)));
g_logger.Info(StringFormat("Chart Timeframe: %s", EnumToString(PERIOD_CURRENT)));
g_logger.Info(StringFormat("Strategy Evaluation: %s on %s",
g_evaluate_every_tick ? "Every tick" : (g_evaluate_on_new_bar ? "New bar" : "Every tick"),
EnumToString(g_strategy_entry_timeframe)));
g_logger.Info(StringFormat("Lot Mode: %s",
g_lot_mode == LOT_MODE_FIXED ? "FIXED" : "RISK"));
g_logger.Info(StringFormat("Stop Loss: %d points, Take Profit: %d points",
g_stop_loss_points, g_take_profit_points));
g_logger.Info("Initial SL/TP source: Strategy TradeSetup");
g_logger.Info(StringFormat("Max Positions: %d, Max Spread: %d points",
g_max_positions, g_max_spread_points));
@@ -167,65 +185,84 @@ void OnTick()
// Step 2: Check spread - if spread is too wide, don't trade
if(!g_risk_manager.IsSpreadAcceptable())
{
g_blocked_spread++;
if(g_debug_mode)
g_logger.Info(StringFormat("Blocked entry due to spread (count=%d)", g_blocked_spread));
return;
}
// Step 3: Check trading hours
if(!g_risk_manager.IsTradingHourValid())
{
g_blocked_hours++;
if(g_debug_mode)
g_logger.Info(StringFormat("Blocked entry due to trading hours (count=%d)", g_blocked_hours));
return;
}
// Step 4: Manage existing positions (trailing stop, break-even)
g_trailing_stop.UpdateAllPositions();
// Step 5: Only process signals on new bar
if(!g_market_data.IsNewBar())
// Step 5: Evaluate strategy on configured entry timeframe unless every-tick mode is enabled
bool should_evaluate_strategy = true;
if(!g_evaluate_every_tick && g_evaluate_on_new_bar)
should_evaluate_strategy = g_market_data.IsNewBar(g_strategy_entry_timeframe);
if(!should_evaluate_strategy)
{
return;
}
// Step 6: Get trading signal from strategy
E_SIGNAL signal = g_strategy.GetSignal();
// Log diagnostics on each new entry bar
if(g_strategy != NULL)
g_strategy.LogDiagnostics();
if(g_logger != NULL && g_debug_mode)
g_logger.Info(StringFormat("Blocked totals: existing=%d spread=%d hours=%d maxpos=%d", g_blocked_existing_position, g_blocked_spread, g_blocked_hours, g_blocked_max_positions));
if(signal == SIGNAL_NONE)
// Step 6: Get the trade setup from strategy
TradeSetup setup = g_strategy.GetTradeSetup();
if(setup.signal == SIGNAL_NONE)
{
if(g_debug_mode && g_logger != NULL)
g_logger.Info(StringFormat("No trade setup: %s", setup.reason));
return;
}
// Step 7: Check if we already have a position
if(g_position_manager.HasOpenPosition(_Symbol))
{
g_blocked_existing_position++;
if(g_debug_mode)
g_logger.Info("Already have open position, skipping entry");
g_logger.Info(StringFormat("Already have open position, skipping entry (count=%d)", g_blocked_existing_position));
return;
}
// Step 8: Check if new position is allowed
if(!g_position_manager.IsNewPositionAllowed(_Symbol))
{
g_logger.Warning("New position not allowed (max positions reached)");
g_blocked_max_positions++;
g_logger.Warning(StringFormat("New position not allowed (max positions reached) (count=%d)", g_blocked_max_positions));
return;
}
// Step 9: Calculate lot size
double lot = g_risk_manager.CalculateLotSize(g_stop_loss_points);
if(lot <= 0)
double lot = 0.0;
if(!g_risk_manager.ValidateTradeSetup(setup, lot))
{
g_logger.Error("Invalid lot size calculated");
if(g_logger)
g_logger.Warning(StringFormat("Trade setup rejected: %s", setup.reason));
return;
}
// Step 10: Execute trade based on signal
bool trade_success = false;
if(signal == SIGNAL_BUY)
if(setup.signal == SIGNAL_BUY)
{
trade_success = g_trade_manager.OpenBuyTrade(_Symbol, lot);
trade_success = g_trade_manager.OpenBuyTrade(_Symbol, lot, setup.stopLoss, setup.takeProfit, setup.entryPrice);
}
else if(signal == SIGNAL_SELL)
else if(setup.signal == SIGNAL_SELL)
{
trade_success = g_trade_manager.OpenSellTrade(_Symbol, lot);
trade_success = g_trade_manager.OpenSellTrade(_Symbol, lot, setup.stopLoss, setup.takeProfit, setup.entryPrice);
}
if(trade_success)