Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c88216db03 | |||
| fff49ea9da | |||
| 6208bbd9c5 |
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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__
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+43
-49
@@ -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
@@ -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
@@ -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
Binary file not shown.
+56
-19
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user