2025-08-13 19:40:28 -04:00
// TradeManager.mqh
// Handles all trade execution logic.
# property copyright " 2025, Windsurf Engineering "
# property link " https://www.windsurf.ai "
# include <Trade\Trade.mqh>
# include <Trade\PositionInfo.mqh>
2025-09-24 15:10:34 -04:00
# include <Trade\OrderInfo.mqh>
# include <Trade\DealInfo.mqh>
2025-08-13 19:40:28 -04:00
# include <Arrays\ArrayObj.mqh>
# include <Object.mqh>
# include "IStrategy.mqh"
2025-09-24 15:10:34 -04:00
# include "..\Include\Indicators\ATR.mqh"
2025-10-16 18:03:12 -04:00
# include "LogMiddleware.mqh"
2025-09-24 15:10:34 -04:00
// Use TrailingType from IStrategy.mqh instead of defining a duplicate enum
2025-08-13 19:40:28 -04:00
2026-02-24 12:47:37 -05:00
// Execution mode for trade manager
enum ENUM_EXEC_MODE
{
EXEC_DISABLED = 0 , // No trades allowed (log only)
EXEC_ENABLED = 1 , // Normal execution
EXEC_SHADOW = 2 // Log intent but don't send orders
} ;
2025-08-13 19:40:28 -04:00
class CTradeManager
{
private :
2025-09-24 15:10:34 -04:00
// Trailing stop configuration
class CTrailConfig : public CObject
{
public :
string symbol ; // Symbol this config applies to
bool enabled ; // Whether trailing is enabled
TrailingType type ; // Type of trailing (points, ATR, etc.)
int distance_points ; // Distance in points for fixed trailing
int activation_points ; // Activation distance in points
int step_points ; // Minimum step in points for adjustment
int atr_period ; // Period for ATR calculation
double atr_multiplier ; // Multiplier for ATR distance
// Constructor with defaults
CTrailConfig ( ) : enabled ( false ) , type ( TRAIL_NONE ) , distance_points ( 100 ) ,
activation_points ( 50 ) , step_points ( 10 ) , atr_period ( 14 ) ,
atr_multiplier ( 2.0 ) { }
} ;
2025-08-13 19:40:28 -04:00
CTrade m_trade ;
string m_symbol ;
double m_lot_size ;
int m_magic_number ;
2025-09-24 15:10:34 -04:00
CArrayObj m_trails ; // Array of CTrailConfig objects
2026-02-24 12:47:37 -05:00
ENUM_EXEC_MODE m_exec_mode ; // Execution mode (disabled/enabled/shadow)
2025-08-13 19:40:28 -04:00
2025-09-24 15:10:34 -04:00
// Find trail configuration by symbol - implementation moved to public section
2025-08-13 19:40:28 -04:00
// Normalize and validate volume according to symbol constraints
double NormalizeVolume ( double lots )
{
double vmin = 0.0 , vmax = 0.0 , vstep = 0.0 ;
SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_MIN , vmin ) ;
SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_MAX , vmax ) ;
SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_STEP , vstep ) ;
if ( vstep < = 0.0 ) vstep = vmin ; // safety fallback
// Derive volume digits from step (e.g., 0.01 -> 2)
int vdigits = 0 ;
double tmp = vstep ;
for ( int i = 0 ; i < 8 & & ( MathRound ( tmp ) ! = tmp ) ; + + i ) { tmp * = 10.0 ; vdigits + + ; }
// Clamp to range first
double clamped = lots ;
if ( clamped < vmin ) clamped = vmin ;
if ( clamped > vmax ) clamped = vmax ;
// Snap to step grid (floor to avoid exceeding max)
double steps = MathFloor ( ( clamped + 1e-12 ) / vstep ) ;
double snapped = steps * vstep ;
// Ensure not below min after snapping
if ( snapped < vmin ) snapped = vmin ;
// Normalize to derived volume digits
double norm = NormalizeDouble ( snapped , vdigits ) ;
// Final guard
if ( norm < vmin | | norm > vmax )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [VOL] Normalized volume %.4f out of bounds [%.4f..%.4f] step=%.5f for %s, falling back to min " ,
norm , vmin , vmax , vstep , m_symbol ) ) ;
2025-08-13 19:40:28 -04:00
norm = vmin ;
}
return norm ;
}
// --- Price/tick helpers
// default to manager symbol
double TickSize ( ) { double ts = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_TRADE_TICK_SIZE , ts ) ; if ( ts < = 0.0 ) SymbolInfoDouble ( m_symbol , SYMBOL_POINT , ts ) ; return ts ; }
double TickSize ( const string sym ) { double ts = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_TRADE_TICK_SIZE , ts ) ; if ( ts < = 0.0 ) SymbolInfoDouble ( sym , SYMBOL_POINT , ts ) ; return ts ; }
int PriceDigits ( ) { return ( int ) SymbolInfoInteger ( m_symbol , SYMBOL_DIGITS ) ; }
int PriceDigits ( const string sym ) { return ( int ) SymbolInfoInteger ( sym , SYMBOL_DIGITS ) ; }
double RoundToTick ( double price )
{
double ts = TickSize ( ) ; if ( ts < = 0.0 ) return price ; int dg = PriceDigits ( ) ;
// snap to nearest tick
double ticks = MathRound ( price / ts ) ;
return NormalizeDouble ( ticks * ts , dg ) ;
}
double RoundToTick ( double price , const string sym )
{
double ts = TickSize ( sym ) ; if ( ts < = 0.0 ) return price ; int dg = PriceDigits ( sym ) ;
double ticks = MathRound ( price / ts ) ;
return NormalizeDouble ( ticks * ts , dg ) ;
}
double RoundToTickBelow ( double price , const string sym )
{
double ts = TickSize ( sym ) ; if ( ts < = 0.0 ) return price ; int dg = PriceDigits ( sym ) ;
double ticks = MathFloor ( price / ts ) ;
return NormalizeDouble ( ticks * ts , dg ) ;
}
double RoundToTickAbove ( double price , const string sym )
{
double ts = TickSize ( sym ) ; if ( ts < = 0.0 ) return price ; int dg = PriceDigits ( sym ) ;
double ticks = MathCeil ( price / ts ) ;
return NormalizeDouble ( ticks * ts , dg ) ;
}
double MinStopDistance ( )
{
long lvl = 0 ; SymbolInfoInteger ( m_symbol , SYMBOL_TRADE_STOPS_LEVEL , lvl ) ;
double pt = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_POINT , pt ) ;
return ( double ) lvl * pt ; // price distance
}
double MinStopDistance ( const string sym )
{
long lvl = 0 ; SymbolInfoInteger ( sym , SYMBOL_TRADE_STOPS_LEVEL , lvl ) ;
double pt = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_POINT , pt ) ;
return ( double ) lvl * pt ;
}
double FreezeDistance ( )
{
long lvl = 0 ; SymbolInfoInteger ( m_symbol , SYMBOL_TRADE_FREEZE_LEVEL , lvl ) ;
double pt = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_POINT , pt ) ;
return ( double ) lvl * pt ;
}
double FreezeDistance ( const string sym )
{
long lvl = 0 ; SymbolInfoInteger ( sym , SYMBOL_TRADE_FREEZE_LEVEL , lvl ) ;
double pt = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_POINT , pt ) ;
return ( double ) lvl * pt ;
}
// Ensure symbol is selected, tradable, and has valid bid/ask
bool EnsureSymbolReady ( )
{
// Select symbol in Market Watch
if ( ! SymbolSelect ( m_symbol , true ) )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [SYMBOL] Failed to select symbol %s " , m_symbol ) ) ;
2025-08-13 19:40:28 -04:00
return false ;
}
// Check trading mode
long tmode = 0 ; SymbolInfoInteger ( m_symbol , SYMBOL_TRADE_MODE , tmode ) ;
if ( tmode = = SYMBOL_TRADE_MODE_DISABLED )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [SYMBOL] Trading disabled for %s " , m_symbol ) ) ;
2025-08-13 19:40:28 -04:00
return false ;
}
// Get latest tick
MqlTick tick ; if ( ! SymbolInfoTick ( m_symbol , tick ) )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [SYMBOL] No tick for %s " , m_symbol ) ) ;
2025-08-13 19:40:28 -04:00
return false ;
}
if ( tick . bid < = 0.0 | | tick . ask < = 0.0 )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [SYMBOL] Invalid bid/ask for %s (bid=%.5f ask=%.5f) " , m_symbol , tick . bid , tick . ask ) ) ;
2025-08-13 19:40:28 -04:00
return false ;
}
return true ;
}
// Normalize SL/TP relative to current market and stops level. Return via refs.
void NormalizeStops ( const ENUM_ORDER_TYPE otype , double & entry_price , double & sl , double & tp )
{
2026-02-24 12:47:37 -05:00
// Always use a fresh tick snapshot; SymbolInfoDouble can be stale on some brokers/symbols
double bid = 0.0 , ask = 0.0 ;
MqlTick tick ;
if ( SymbolInfoTick ( m_symbol , tick ) ) { bid = tick . bid ; ask = tick . ask ; }
if ( bid < = 0.0 ) SymbolInfoDouble ( m_symbol , SYMBOL_BID , bid ) ;
if ( ask < = 0.0 ) SymbolInfoDouble ( m_symbol , SYMBOL_ASK , ask ) ;
2025-08-13 19:40:28 -04:00
double minDist = MinStopDistance ( ) ; double frz = FreezeDistance ( ) ; double needDist = MathMax ( minDist , frz ) ;
int dg = PriceDigits ( ) ; double ts = TickSize ( ) ;
// Ensure entry price lies on correct side for pending orders and obeys min distance
if ( otype = = ORDER_TYPE_BUY_LIMIT )
{ if ( entry_price > = bid - needDist ) entry_price = RoundToTickBelow ( bid - needDist , m_symbol ) ; }
else if ( otype = = ORDER_TYPE_SELL_LIMIT )
{ if ( entry_price < = ask + needDist ) entry_price = RoundToTickAbove ( ask + needDist , m_symbol ) ; }
else if ( otype = = ORDER_TYPE_BUY_STOP )
{ if ( entry_price < = ask + needDist ) entry_price = RoundToTickAbove ( ask + needDist , m_symbol ) ; }
else if ( otype = = ORDER_TYPE_SELL_STOP )
{ if ( entry_price > = bid - needDist ) entry_price = RoundToTickBelow ( bid - needDist , m_symbol ) ; }
// Market orders: use current market for distance checks
if ( otype = = ORDER_TYPE_BUY | | otype = = ORDER_TYPE_BUY_LIMIT | | otype = = ORDER_TYPE_BUY_STOP )
{
2026-02-24 12:47:37 -05:00
// For market BUY, prefer an entry reference if provided to avoid edge cases when bid/ask shift mid-call
double refBid = ( entry_price > 0.0 ? entry_price : bid ) ;
double refAsk = ( entry_price > 0.0 ? entry_price : ask ) ;
2025-08-13 19:40:28 -04:00
if ( sl > 0.0 )
{
2026-02-24 12:47:37 -05:00
double minSL = refBid - needDist ; // SL must be below market for BUY
if ( sl > = refBid | | ( refBid - sl ) < needDist ) sl = RoundToTickBelow ( minSL , m_symbol ) ;
2025-08-13 19:40:28 -04:00
}
if ( tp > 0.0 )
{
2026-02-24 12:47:37 -05:00
double minTP = refAsk + needDist ; // TP must be above for BUY
if ( tp < = refAsk | | ( tp - refAsk ) < needDist ) tp = RoundToTickAbove ( minTP , m_symbol ) ;
2025-08-13 19:40:28 -04:00
}
}
else if ( otype = = ORDER_TYPE_SELL | | otype = = ORDER_TYPE_SELL_LIMIT | | otype = = ORDER_TYPE_SELL_STOP )
{
2026-02-24 12:47:37 -05:00
double refBid = ( entry_price > 0.0 ? entry_price : bid ) ;
double refAsk = ( entry_price > 0.0 ? entry_price : ask ) ;
2025-08-13 19:40:28 -04:00
if ( sl > 0.0 )
{
2026-02-24 12:47:37 -05:00
double minSL = refAsk + needDist ; // SL must be above market for SELL
if ( sl < = refAsk | | ( sl - refAsk ) < needDist ) sl = RoundToTickAbove ( minSL , m_symbol ) ;
2025-08-13 19:40:28 -04:00
}
if ( tp > 0.0 )
{
2026-02-24 12:47:37 -05:00
double minTP = refBid - needDist ; // TP must be below for SELL
if ( tp > = refBid | | ( refBid - tp ) < needDist ) tp = RoundToTickBelow ( minTP , m_symbol ) ;
2025-08-13 19:40:28 -04:00
}
}
2026-02-24 12:47:37 -05:00
// Final side sanity: if still invalid (crossed), re-adjust to a valid minimal level instead of dropping to zero
2025-08-13 19:40:28 -04:00
if ( otype = = ORDER_TYPE_BUY | | otype = = ORDER_TYPE_BUY_LIMIT | | otype = = ORDER_TYPE_BUY_STOP )
{
2026-02-24 12:47:37 -05:00
if ( sl > 0.0 & & bid > 0.0 & & sl > = bid ) sl = RoundToTickBelow ( bid - needDist , m_symbol ) ;
if ( tp > 0.0 & & ask > 0.0 & & tp < = ask ) tp = RoundToTickAbove ( ask + needDist , m_symbol ) ;
2025-08-13 19:40:28 -04:00
}
else
{
2026-02-24 12:47:37 -05:00
if ( sl > 0.0 & & ask > 0.0 & & sl < = ask ) sl = RoundToTickAbove ( ask + needDist , m_symbol ) ;
if ( tp > 0.0 & & bid > 0.0 & & tp > = bid ) tp = RoundToTickBelow ( bid - needDist , m_symbol ) ;
2025-08-13 19:40:28 -04:00
}
// Normalize to digits
if ( sl > 0.0 ) sl = NormalizeDouble ( RoundToTick ( sl ) , dg ) ;
if ( tp > 0.0 ) tp = NormalizeDouble ( RoundToTick ( tp ) , dg ) ;
if ( entry_price > 0.0 ) entry_price = NormalizeDouble ( RoundToTick ( entry_price ) , dg ) ;
}
2025-10-16 18:03:12 -04:00
// Compute safe default SL/TP using ATR and broker min distances when one or both are missing
void EnsureFallbackStops ( const ENUM_ORDER_TYPE otype , double & entry_price , double & sl , double & tp )
{
if ( sl > 0.0 & & tp > 0.0 ) return ; // already provided
double bid = 0.0 , ask = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_BID , bid ) ; SymbolInfoDouble ( m_symbol , SYMBOL_ASK , ask ) ;
double pt = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_POINT , pt ) ;
if ( pt < = 0.0 ) pt = TickSize ( ) ;
double needDist = MathMax ( MinStopDistance ( ) , FreezeDistance ( ) ) ;
// Try ATR-based distance (14-period on current chart TF)
double atr_dist = 0.0 ;
int h = iATR ( m_symbol , _Period , 14 ) ;
if ( h ! = INVALID_HANDLE )
{
double buf [ ] ; int c = CopyBuffer ( h , 0 , 0 , 1 , buf ) ;
IndicatorRelease ( h ) ;
if ( c = = 1 & & buf [ 0 ] > 0.0 ) atr_dist = buf [ 0 ] * 2.0 ; // 2x ATR for SL/TP baseline
}
if ( atr_dist < = 0.0 )
{
// Fallback to conservative multiple of broker min distance or 100 points
double fallback = 100.0 * pt ;
atr_dist = MathMax ( fallback , needDist * 3.0 ) ;
}
// Use entry price if provided (pending orders), else use current market
if ( otype = = ORDER_TYPE_BUY | | otype = = ORDER_TYPE_BUY_LIMIT | | otype = = ORDER_TYPE_BUY_STOP )
{
double ref_sl = ( entry_price > 0.0 ? entry_price : bid ) ;
double ref_tp = ( entry_price > 0.0 ? entry_price : ask ) ;
if ( sl < = 0.0 ) sl = RoundToTickBelow ( ref_sl - MathMax ( atr_dist , needDist ) , m_symbol ) ;
if ( tp < = 0.0 ) tp = RoundToTickAbove ( ref_tp + MathMax ( atr_dist , needDist ) , m_symbol ) ;
}
else if ( otype = = ORDER_TYPE_SELL | | otype = = ORDER_TYPE_SELL_LIMIT | | otype = = ORDER_TYPE_SELL_STOP )
{
double ref_sl = ( entry_price > 0.0 ? entry_price : ask ) ;
double ref_tp = ( entry_price > 0.0 ? entry_price : bid ) ;
if ( sl < = 0.0 ) sl = RoundToTickAbove ( ref_sl + MathMax ( atr_dist , needDist ) , m_symbol ) ;
if ( tp < = 0.0 ) tp = RoundToTickBelow ( ref_tp - MathMax ( atr_dist , needDist ) , m_symbol ) ;
}
}
2025-09-24 15:10:34 -04:00
// Find trail config by symbol - implementation is in the public section
2025-08-13 19:40:28 -04:00
// Normalize SL for an existing position with given side; return false if cannot make valid
2025-09-24 15:10:34 -04:00
bool NormalizeSLForPosition ( const ENUM_POSITION_TYPE ptype , double & sl )
2025-08-13 19:40:28 -04:00
{
2025-09-24 15:10:34 -04:00
return NormalizeSLForPosition ( m_symbol , ptype , sl ) ;
}
bool NormalizeSLForPosition ( const string sym , const ENUM_POSITION_TYPE ptype , double & sl )
{
double bid = 0.0 , ask = 0.0 ;
if ( ! SymbolInfoDouble ( sym , SYMBOL_BID , bid ) | | ! SymbolInfoDouble ( sym , SYMBOL_ASK , ask ) )
return false ;
double minDist = MinStopDistance ( sym ) ;
double frz = FreezeDistance ( sym ) ;
double needDist = MathMax ( minDist , frz ) ;
int dg = ( int ) SymbolInfoInteger ( sym , SYMBOL_DIGITS ) ;
if ( ptype = = POSITION_TYPE_BUY )
2025-08-13 19:40:28 -04:00
{
2025-09-24 15:10:34 -04:00
if ( sl < = 0.0 ) return false ;
2025-08-13 19:40:28 -04:00
double maxSL = bid - needDist ;
2025-09-24 15:10:34 -04:00
if ( sl > = bid | | ( bid - sl ) < needDist ) sl = RoundToTickBelow ( maxSL , sym ) ;
if ( sl > = bid ) return false ;
2025-08-13 19:40:28 -04:00
}
2025-09-24 15:10:34 -04:00
else if ( ptype = = POSITION_TYPE_SELL )
2025-08-13 19:40:28 -04:00
{
2025-09-24 15:10:34 -04:00
if ( sl < = 0.0 ) return false ;
2025-08-13 19:40:28 -04:00
double minSL = ask + needDist ;
2025-09-24 15:10:34 -04:00
if ( sl < = ask | | ( sl - ask ) < needDist ) sl = RoundToTickAbove ( minSL , sym ) ;
if ( sl < = ask ) return false ;
2025-08-13 19:40:28 -04:00
}
2025-09-24 15:10:34 -04:00
2025-08-13 19:40:28 -04:00
sl = NormalizeDouble ( sl , dg ) ;
return true ;
}
2025-09-24 15:10:34 -04:00
// Find trailing configuration by symbol
CTrailConfig * FindTrailBySymbol ( const string symbol )
2025-08-13 19:40:28 -04:00
{
2025-09-24 15:10:34 -04:00
for ( int i = 0 ; i < m_trails . Total ( ) ; i + + )
2025-08-13 19:40:28 -04:00
{
2025-09-24 15:10:34 -04:00
CTrailConfig * cfg = ( CTrailConfig * ) m_trails . At ( i ) ;
if ( cfg ! = NULL & & cfg . symbol = = symbol )
return cfg ;
2025-08-13 19:40:28 -04:00
}
2025-09-24 15:10:34 -04:00
return NULL ;
2025-08-13 19:40:28 -04:00
}
public :
CTradeManager ( string symbol , double lot_size , int magic_number ) ;
~ CTradeManager ( ) ;
2026-02-24 12:47:37 -05:00
// Execution mode control
void SetExecutionMode ( ENUM_EXEC_MODE mode ) { m_exec_mode = mode ; }
ENUM_EXEC_MODE GetExecutionMode ( ) const { return m_exec_mode ; }
bool IsExecutionEnabled ( ) const { return m_exec_mode = = EXEC_ENABLED ; }
bool IsShadowMode ( ) const { return m_exec_mode = = EXEC_SHADOW ; }
2025-08-13 19:40:28 -04:00
bool ExecuteOrder ( const TradeOrder & order ) ;
// Accessors for last trade results
uint ResultRetcode ( ) ;
ulong ResultDeal ( ) ;
ulong ResultOrder ( ) ;
double ResultPrice ( ) ;
// Trailing management
void ConfigureTrailing ( const TradeOrder & order ) ;
void UpdateTrailingStops ( ) ;
2025-09-24 17:58:57 -04:00
// Find trail configuration by symbol (implementation moved from private section)
CTrailConfig * FindTrailConfig ( const string symbol ) ;
2025-08-13 19:40:28 -04:00
} ;
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTradeManager : : CTradeManager ( string symbol , double lot_size , int magic_number )
{
m_symbol = symbol ;
m_lot_size = lot_size ;
m_magic_number = magic_number ;
2026-02-24 12:47:37 -05:00
m_exec_mode = EXEC_ENABLED ; // Default to enabled
2025-08-13 19:40:28 -04:00
m_trade . SetExpertMagicNumber ( m_magic_number ) ;
m_trade . SetMarginMode ( ) ;
m_trails . Clear ( ) ;
// Ensure symbol is visible/ready upfront
if ( ! SymbolSelect ( m_symbol , true ) )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [SYMBOL] Failed to select on ctor: %s " , m_symbol ) ) ;
2025-08-13 19:40:28 -04:00
}
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CTradeManager : : ~ CTradeManager ( )
{
}
//+------------------------------------------------------------------+
//| Executes any type of trade order based on the TradeOrder struct |
//+------------------------------------------------------------------+
bool CTradeManager : : ExecuteOrder ( const TradeOrder & order )
{
if ( order . action = = ACTION_NONE ) return false ;
2026-02-24 12:47:37 -05:00
// EXECUTION MODE CHECK
if ( m_exec_mode = = EXEC_DISABLED )
{
LOG ( StringFormat ( " [EXEC-DISABLED] Order blocked: %s on %s (execution disabled) " , EnumToString ( order . order_type ) , m_symbol ) ) ;
return false ;
}
if ( m_exec_mode = = EXEC_SHADOW )
{
// Log intent but don't execute
LOG ( StringFormat ( " [SHADOW] Would execute: %s %s vol=%.2f price=%.5f sl=%.5f tp=%.5f strategy=%s " ,
EnumToString ( order . order_type ) , m_symbol , order . lots > 0.0 ? order . lots : m_lot_size ,
order . price , order . stop_loss , order . take_profit , order . strategy_name ) ) ;
return true ; // Shadow mode reports success but doesn't trade
}
2025-08-13 19:40:28 -04:00
bool ok = false ;
if ( ! EnsureSymbolReady ( ) )
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [SYMBOL] Not ready for trading: %s. Skipping order %s " , m_symbol , EnumToString ( order . order_type ) ) ) ;
2025-08-13 19:40:28 -04:00
return false ;
}
2025-08-27 12:44:21 -04:00
double vol_in = ( order . lots > 0.0 ? order . lots : m_lot_size ) ;
bool lots_overridden = ( order . lots > 0.0 ) ;
double vol = NormalizeVolume ( vol_in ) ;
2025-08-13 19:40:28 -04:00
if ( vol < = 0.0 )
{
double vmin = 0 , vmax = 0 , vstep = 0 ; SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_MIN , vmin ) ; SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_MAX , vmax ) ; SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_STEP , vstep ) ;
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [VOL] Invalid normalized volume (%.4f). Symbol %s constraints: min=%.4f max=%.4f step=%.4f " , vol , m_symbol , vmin , vmax , vstep ) ) ;
2025-08-13 19:40:28 -04:00
return false ;
}
double sl = order . stop_loss ;
double tp = order . take_profit ;
double entry_px = order . price ;
NormalizeStops ( order . order_type , entry_px , sl , tp ) ;
2025-10-16 18:03:12 -04:00
// Enforce fallback SL/TP if missing
if ( sl < = 0.0 | | tp < = 0.0 )
{
EnsureFallbackStops ( order . order_type , entry_px , sl , tp ) ;
NormalizeStops ( order . order_type , entry_px , sl , tp ) ;
}
2026-02-24 12:47:37 -05:00
// If NormalizeStops zeroed one side (e.g., TP ended up on wrong side), recompute deterministically
if ( sl < = 0.0 | | tp < = 0.0 )
{
double bid = 0.0 , ask = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_BID , bid ) ; SymbolInfoDouble ( m_symbol , SYMBOL_ASK , ask ) ;
double needDist = MathMax ( MinStopDistance ( ) , FreezeDistance ( ) ) ;
int dg = PriceDigits ( ) ;
if ( order . order_type = = ORDER_TYPE_BUY | | order . order_type = = ORDER_TYPE_BUY_LIMIT | | order . order_type = = ORDER_TYPE_BUY_STOP )
{
// BUY: SL must be < bid, TP must be > ask
if ( sl < = 0.0 )
sl = RoundToTickBelow ( ( bid > 0.0 ? bid : entry_px ) - MathMax ( needDist * 2.0 , needDist ) , m_symbol ) ;
if ( tp < = 0.0 )
tp = RoundToTickAbove ( ( ask > 0.0 ? ask : entry_px ) + MathMax ( needDist * 2.0 , needDist ) , m_symbol ) ;
}
else
{
// SELL: SL must be > ask, TP must be < bid
if ( sl < = 0.0 )
sl = RoundToTickAbove ( ( ask > 0.0 ? ask : entry_px ) + MathMax ( needDist * 2.0 , needDist ) , m_symbol ) ;
if ( tp < = 0.0 )
tp = RoundToTickBelow ( ( bid > 0.0 ? bid : entry_px ) - MathMax ( needDist * 2.0 , needDist ) , m_symbol ) ;
}
NormalizeStops ( order . order_type , entry_px , sl , tp ) ;
if ( sl > 0.0 ) sl = NormalizeDouble ( sl , dg ) ;
if ( tp > 0.0 ) tp = NormalizeDouble ( tp , dg ) ;
}
2025-10-16 18:03:12 -04:00
// Log final stops state
if ( sl = = 0.0 | | tp = = 0.0 )
{
LOG ( StringFormat ( " [STOPS] Warning: SL/TP zero after fallback for %s on %s -> SL=%.5f TP=%.5f (entry=%.5f) " , EnumToString ( order . order_type ) , m_symbol , sl , tp , entry_px ) ) ;
}
else
2025-08-13 19:40:28 -04:00
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [STOPS] Using SL/TP for %s on %s -> SL=%.5f TP=%.5f (entry=%.5f) " , EnumToString ( order . order_type ) , m_symbol , sl , tp , entry_px ) ) ;
}
// CRITICAL SAFETY CHECK: Prevent execution without SL/TP
if ( sl < = 0.0 | | tp < = 0.0 )
{
LOG ( StringFormat ( " [SAFETY] BLOCKING ORDER: Cannot execute %s on %s without valid SL/TP (SL=%.5f TP=%.5f) " ,
EnumToString ( order . order_type ) , m_symbol , sl , tp ) ) ;
LOG ( " [SAFETY] This indicates a critical bug in signal generation or gate processing " ) ;
LOG ( StringFormat ( " [SAFETY] Order details: entry=%.5f volume=%.4f strategy=%s " , entry_px , vol , order . strategy_name ) ) ;
return false ; // BLOCK EXECUTION
2025-08-13 19:40:28 -04:00
}
2026-02-24 12:47:37 -05:00
// Margin-aware downscaling: if requested lots cannot be opened, reduce down to min lot/step
double vmin = 0.0 , vmax = 0.0 , vstep = 0.0 ;
SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_MIN , vmin ) ;
SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_MAX , vmax ) ;
SymbolInfoDouble ( m_symbol , SYMBOL_VOLUME_STEP , vstep ) ;
if ( vstep < = 0.0 ) vstep = ( vmin > 0.0 ? vmin : 0.01 ) ;
double free_margin = AccountInfoDouble ( ACCOUNT_MARGIN_FREE ) ;
if ( free_margin > 0.0 )
{
double margin_req = 0.0 ;
// OrderCalcMargin uses the current market for market orders; entry_px is used for pending
double margin_price = entry_px ;
if ( margin_price < = 0.0 )
{
double bid = 0.0 , ask = 0.0 ; SymbolInfoDouble ( m_symbol , SYMBOL_BID , bid ) ; SymbolInfoDouble ( m_symbol , SYMBOL_ASK , ask ) ;
margin_price = ( order . order_type = = ORDER_TYPE_SELL | | order . order_type = = ORDER_TYPE_SELL_LIMIT | | order . order_type = = ORDER_TYPE_SELL_STOP ) ? bid : ask ;
}
if ( OrderCalcMargin ( order . order_type , m_symbol , vol , margin_price , margin_req ) )
{
if ( margin_req > free_margin )
{
double vol_try = vol ;
// Step down volume until it fits or we hit min lot
while ( vol_try > vmin + 1e-12 )
{
double next = MathMax ( vmin , vol_try - vstep ) ;
next = NormalizeVolume ( next ) ;
double mr = 0.0 ;
if ( ! OrderCalcMargin ( order . order_type , m_symbol , next , margin_price , mr ) )
break ;
vol_try = next ;
if ( mr < = free_margin )
break ;
if ( vol_try < = vmin + 1e-12 )
break ;
}
double mr_final = 0.0 ;
if ( ! OrderCalcMargin ( order . order_type , m_symbol , vol_try , margin_price , mr_final ) | | mr_final > free_margin )
{
LOG ( StringFormat ( " [MARGIN] Cannot open even min lot for %s on %s. free=%.2f required=%.2f lots=%.4f " , EnumToString ( order . order_type ) , m_symbol , free_margin , mr_final , vol_try ) ) ;
return false ;
}
if ( vol_try < vol )
{
LOG ( StringFormat ( " [MARGIN] Downscaling lots due to free margin: %.4f -> %.4f (free=%.2f req=%.2f) " , vol , vol_try , free_margin , mr_final ) ) ;
vol = vol_try ;
}
}
}
else
{
LOG ( StringFormat ( " [MARGIN] OrderCalcMargin failed for %s on %s (lots=%.4f price=%.5f err=%d) " , EnumToString ( order . order_type ) , m_symbol , vol , margin_price , GetLastError ( ) ) ) ;
}
}
// NON-BLOCKING RETRY: Single attempt only to avoid Sleep on tick path
// Retry responsibility moved to caller (OnTimer) or async handling
int max_attempts = 1 ; // Reduced from 3 to avoid blocking
2025-09-10 13:27:03 -04:00
int attempt = 0 ;
ulong t_start = GetTickCount ( ) ;
for ( attempt = 1 ; attempt < = max_attempts ; + + attempt )
{
ok = false ;
switch ( order . order_type )
2025-08-13 19:40:28 -04:00
{
case ORDER_TYPE_BUY :
2026-02-24 12:47:37 -05:00
ok = m_trade . Buy ( vol , m_symbol , 0 , sl , tp , order . strategy_name ) ;
2025-08-13 19:40:28 -04:00
break ;
case ORDER_TYPE_SELL :
2026-02-24 12:47:37 -05:00
ok = m_trade . Sell ( vol , m_symbol , 0 , sl , tp , order . strategy_name ) ;
2025-08-13 19:40:28 -04:00
break ;
case ORDER_TYPE_BUY_STOP :
2026-02-24 12:47:37 -05:00
ok = m_trade . BuyStop ( vol , entry_px , m_symbol , sl , tp , ORDER_TIME_GTC , 0 , order . strategy_name ) ;
2025-08-13 19:40:28 -04:00
break ;
case ORDER_TYPE_SELL_STOP :
2026-02-24 12:47:37 -05:00
ok = m_trade . SellStop ( vol , entry_px , m_symbol , sl , tp , ORDER_TIME_GTC , 0 , order . strategy_name ) ;
2025-08-13 19:40:28 -04:00
break ;
case ORDER_TYPE_BUY_LIMIT :
2026-02-24 12:47:37 -05:00
ok = m_trade . BuyLimit ( vol , entry_px , m_symbol , sl , tp , ORDER_TIME_GTC , 0 , order . strategy_name ) ;
2025-08-13 19:40:28 -04:00
break ;
case ORDER_TYPE_SELL_LIMIT :
2026-02-24 12:47:37 -05:00
ok = m_trade . SellLimit ( vol , entry_px , m_symbol , sl , tp , ORDER_TIME_GTC , 0 , order . strategy_name ) ;
2025-08-13 19:40:28 -04:00
break ;
default :
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " Unsupported order type in TradeManager: %s " , EnumToString ( order . order_type ) ) ) ;
ok = false ;
break ;
2025-08-13 19:40:28 -04:00
}
2025-09-10 13:27:03 -04:00
uint rc = m_trade . ResultRetcode ( ) ;
ulong deal = m_trade . ResultDeal ( ) ;
ulong ord = m_trade . ResultOrder ( ) ;
double px = m_trade . ResultPrice ( ) ;
datetime now = TimeCurrent ( ) ;
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " OrderSend: type=%s symbol=%s lots_req=%.4f lots_used=%.4f override=%s retcode=%u deal=%I64u order=%I64u price=%.5f ok=%s attempt=%d time=%s " ,
EnumToString ( order . order_type ) , m_symbol , vol_in , vol , ( lots_overridden ? " true " : " false " ) , rc , deal , ord , px , ( ok ? " true " : " false " ) , attempt , TimeToString ( now , TIME_DATE | TIME_SECONDS ) ) ) ;
2025-09-10 13:27:03 -04:00
if ( ok ) break ;
2026-02-24 12:47:37 -05:00
// Log transient errors but DO NOT Sleep on tick path
2025-10-16 18:03:12 -04:00
if ( rc = = 10004 | | rc = = 10006 | | rc = = 10007 | | rc = = 10009 | | rc = = 10010 | | rc = = 10013 | | rc = = 10014 )
2025-09-10 13:27:03 -04:00
{
2026-02-24 12:47:37 -05:00
LOG ( StringFormat ( " [RETRY-SKIPPED] Trade failed with transient retcode=%u (attempt %d/%d) at %s. Retry deferred to next timer cycle. " , rc , attempt , max_attempts , TimeToString ( now , TIME_DATE | TIME_SECONDS ) ) ) ;
// Sleep(200); // REMOVED: Do not block tick thread
2025-09-10 13:27:03 -04:00
}
else
{
2026-02-24 12:47:37 -05:00
LOG ( StringFormat ( " [FAIL] Trade failed with non-retryable retcode=%u at %s. " , rc , TimeToString ( now , TIME_DATE | TIME_SECONDS ) ) ) ;
2025-09-10 13:27:03 -04:00
break ;
}
2025-10-16 18:03:12 -04:00
}
ulong t_end = GetTickCount ( ) ;
LOG ( StringFormat ( " OrderSend: total attempts=%d duration_ms=%d final_ok=%s " , attempt , ( int ) ( t_end - t_start ) , ( ok ? " true " : " false " ) ) ) ;
return ok ;
2025-09-10 13:27:03 -04:00
}
2025-08-13 19:40:28 -04:00
//+------------------------------------------------------------------+
//| Accessors for last trade result |
//+------------------------------------------------------------------+
uint CTradeManager : : ResultRetcode ( )
{
return m_trade . ResultRetcode ( ) ;
}
ulong CTradeManager : : ResultDeal ( )
{
return m_trade . ResultDeal ( ) ;
}
ulong CTradeManager : : ResultOrder ( )
{
return m_trade . ResultOrder ( ) ;
}
double CTradeManager : : ResultPrice ( )
{
return m_trade . ResultPrice ( ) ;
}
//+------------------------------------------------------------------+
//| Configure trailing for current symbol from TradeOrder |
//+------------------------------------------------------------------+
void CTradeManager : : ConfigureTrailing ( const TradeOrder & order )
{
CTrailConfig * cfg = FindTrailBySymbol ( m_symbol ) ;
if ( cfg = = NULL )
{
cfg = new CTrailConfig ( ) ;
cfg . symbol = m_symbol ;
m_trails . Add ( cfg ) ;
}
cfg . enabled = order . trailing_enabled ;
cfg . type = order . trailing_type ;
2025-09-24 15:10:34 -04:00
cfg . distance_points = ( int ) order . trail_distance_points ;
cfg . activation_points = ( int ) order . trail_activation_points ;
cfg . step_points = ( int ) order . trail_step_points ;
cfg . atr_period = ( int ) order . atr_period ;
cfg . atr_multiplier = ( int ) order . atr_multiplier ;
2025-08-13 19:40:28 -04:00
}
//+------------------------------------------------------------------+
//| Update trailing stops for all positions with our magic number |
//+------------------------------------------------------------------+
void CTradeManager : : UpdateTrailingStops ( )
{
int total = PositionsTotal ( ) ;
for ( int i = 0 ; i < total ; + + i )
{
2025-09-10 13:27:03 -04:00
// Ensure a valid position context is selected before reading properties
2025-08-27 12:44:21 -04:00
ulong tk = PositionGetTicket ( i ) ;
if ( tk = = 0 )
2025-08-13 19:40:28 -04:00
continue ;
2025-09-10 13:27:03 -04:00
if ( ! PositionSelectByTicket ( tk ) )
2025-08-13 19:40:28 -04:00
continue ;
2025-09-10 13:27:03 -04:00
string sym = PositionGetString ( POSITION_SYMBOL ) ;
if ( sym = = " " )
continue ;
long pos_magic = ( long ) PositionGetInteger ( POSITION_MAGIC ) ;
2025-08-13 19:40:28 -04:00
if ( pos_magic ! = m_magic_number )
continue ;
CTrailConfig * cfg = FindTrailBySymbol ( sym ) ;
if ( cfg = = NULL | | ! cfg . enabled | | cfg . type = = TRAIL_NONE )
continue ;
// read position
ENUM_POSITION_TYPE ptype = ( ENUM_POSITION_TYPE ) PositionGetInteger ( POSITION_TYPE ) ;
double open_price = PositionGetDouble ( POSITION_PRICE_OPEN ) ;
double sl = PositionGetDouble ( POSITION_SL ) ;
double tp = PositionGetDouble ( POSITION_TP ) ;
double point = SymbolInfoDouble ( sym , SYMBOL_POINT ) ;
int digits = ( int ) SymbolInfoInteger ( sym , SYMBOL_DIGITS ) ;
double bid = 0 , ask = 0 ;
SymbolInfoDouble ( sym , SYMBOL_BID , bid ) ;
SymbolInfoDouble ( sym , SYMBOL_ASK , ask ) ;
double activation = cfg . activation_points * point ;
double distance = cfg . distance_points * point ;
double step = cfg . step_points * point ;
if ( cfg . type = = TRAIL_FIXED_POINTS )
{
if ( ptype = = POSITION_TYPE_BUY )
{
// Ensure activation
if ( ( bid - open_price ) < activation )
continue ;
double new_sl = NormalizeDouble ( bid - distance , digits ) ;
// Normalize to broker rules (below Bid by min distance)
double tmp_sl = new_sl ;
if ( ! NormalizeSLForPosition ( sym , ptype , tmp_sl ) )
{ /* too close or invalid, skip */ }
else
{
if ( sl = = 0 | | tmp_sl > sl + step )
{
double fbid = 0.0 , fask = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_BID , fbid ) ; SymbolInfoDouble ( sym , SYMBOL_ASK , fask ) ;
double needDist = MathMax ( MinStopDistance ( sym ) , FreezeDistance ( sym ) ) ;
if ( sl = = 0 | | tmp_sl > sl + step )
{
double fbid2 = 0.0 , fask2 = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_BID , fbid2 ) ; SymbolInfoDouble ( sym , SYMBOL_ASK , fask2 ) ;
double needDist2 = MathMax ( MinStopDistance ( sym ) , FreezeDistance ( sym ) ) ;
double tick = TickSize ( sym ) ;
if ( MathAbs ( tmp_sl - sl ) < tick )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY skip: delta<tick (sl=%.5f tmp=%.5f tick=%.5f) " , sym , sl , tmp_sl , tick ) ) ; }
2025-08-13 19:40:28 -04:00
else if ( tmp_sl < = fbid2 - needDist2 )
{
double new_sl_out = NormalizeDouble ( tmp_sl , digits ) ;
2025-09-10 13:27:03 -04:00
// Extra guard: avoid server-side 10025 (no change) by skipping equal normalized SL
double cur_sl_norm = NormalizeDouble ( sl , digits ) ;
if ( MathAbs ( new_sl_out - cur_sl_norm ) < ( tick * 0.1 ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY skip: no-change after normalize (sl=%.5f new=%.5f) " , sym , sl , new_sl_out ) ) ; }
2025-09-10 13:27:03 -04:00
else
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [TRAIL] %s BUY modify: old_sl=%.5f -> new_sl=%.5f bid=%.5f needDist=%.5f " , sym , sl , new_sl_out , fbid2 , needDist2 ) ) ;
2025-09-10 13:27:03 -04:00
if ( ! m_trade . PositionModify ( sym , new_sl_out , tp ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY modify failed: ret=%d lastErr=%d " , sym , m_trade . ResultRetcode ( ) , GetLastError ( ) ) ) ; }
2025-09-10 13:27:03 -04:00
}
2025-08-13 19:40:28 -04:00
}
else
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY skip: inside needDist (tmp=%.5f bid=%.5f needDist=%.5f) " , sym , tmp_sl , fbid2 , needDist2 ) ) ; }
2025-08-13 19:40:28 -04:00
}
}
}
}
else if ( ptype = = POSITION_TYPE_SELL )
{
if ( ( open_price - ask ) < activation )
continue ;
double new_sl = NormalizeDouble ( ask + distance , digits ) ;
double tmp_sl = new_sl ;
if ( ! NormalizeSLForPosition ( sym , ptype , tmp_sl ) )
{ /* too close or invalid, skip */ }
else
{
if ( sl = = 0 | | tmp_sl < sl - step )
{
double fbid = 0.0 , fask = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_BID , fbid ) ; SymbolInfoDouble ( sym , SYMBOL_ASK , fask ) ;
double needDist = MathMax ( MinStopDistance ( sym ) , FreezeDistance ( sym ) ) ;
if ( sl = = 0 | | tmp_sl < sl - step )
{
double fbid2 = 0.0 , fask2 = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_BID , fbid2 ) ; SymbolInfoDouble ( sym , SYMBOL_ASK , fask2 ) ;
double needDist2 = MathMax ( MinStopDistance ( sym ) , FreezeDistance ( sym ) ) ;
double tick = TickSize ( sym ) ;
if ( MathAbs ( tmp_sl - sl ) < tick )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL skip: delta<tick (sl=%.5f tmp=%.5f tick=%.5f) " , sym , sl , tmp_sl , tick ) ) ; }
2025-08-13 19:40:28 -04:00
else if ( tmp_sl > = fask2 + needDist2 )
{
double new_sl_out = NormalizeDouble ( tmp_sl , digits ) ;
2025-09-10 13:27:03 -04:00
// Extra guard: avoid server-side 10025 (no change) by skipping equal normalized SL
double cur_sl_norm = NormalizeDouble ( sl , digits ) ;
if ( MathAbs ( new_sl_out - cur_sl_norm ) < ( tick * 0.1 ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL skip: no-change after normalize (sl=%.5f new=%.5f) " , sym , sl , new_sl_out ) ) ; }
2025-09-10 13:27:03 -04:00
else
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [TRAIL] %s SELL modify: old_sl=%.5f -> new_sl=%.5f ask=%.5f needDist=%.5f " , sym , sl , new_sl_out , fask2 , needDist2 ) ) ;
2025-09-10 13:27:03 -04:00
if ( ! m_trade . PositionModify ( sym , new_sl_out , tp ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL modify failed: ret=%d lastErr=%d " , sym , m_trade . ResultRetcode ( ) , GetLastError ( ) ) ) ; }
2025-09-10 13:27:03 -04:00
}
2025-08-13 19:40:28 -04:00
}
else
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL skip: inside needDist (tmp=%.5f ask=%.5f needDist=%.5f) " , sym , tmp_sl , fask2 , needDist2 ) ) ; }
2025-08-13 19:40:28 -04:00
}
}
}
}
}
else if ( cfg . type = = TRAIL_ATR )
{
// Compute ATR and convert to price distance
int handle = iATR ( sym , _Period , cfg . atr_period ) ;
if ( handle ! = INVALID_HANDLE )
{
double atr_buf [ ] ;
if ( CopyBuffer ( handle , 0 , 0 , 1 , atr_buf ) = = 1 )
{
double atr = atr_buf [ 0 ] ;
double atr_distance = atr * cfg . atr_multiplier ;
// Activation still based on points; distance from ATR
if ( ptype = = POSITION_TYPE_BUY )
{
if ( ( bid - open_price ) < activation )
{ IndicatorRelease ( handle ) ; continue ; }
double new_sl = NormalizeDouble ( bid - atr_distance , digits ) ;
double tmp_sl = new_sl ;
if ( ! NormalizeSLForPosition ( sym , ptype , tmp_sl ) )
{ /* too close or invalid, skip */ }
else
{
if ( sl = = 0 | | tmp_sl > sl + step )
{
double fbid = 0.0 , fask = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_BID , fbid ) ; SymbolInfoDouble ( sym , SYMBOL_ASK , fask ) ;
double needDist = MathMax ( MinStopDistance ( sym ) , FreezeDistance ( sym ) ) ;
double tick = TickSize ( sym ) ;
if ( MathAbs ( tmp_sl - sl ) < tick )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY(ATR) skip: delta<tick (sl=%.5f tmp=%.5f tick=%.5f) " , sym , sl , tmp_sl , tick ) ) ; }
2025-08-13 19:40:28 -04:00
else if ( tmp_sl < = fbid - needDist )
{
double new_sl_out = NormalizeDouble ( tmp_sl , digits ) ;
2025-09-10 13:27:03 -04:00
// Extra guard: avoid server-side 10025 (no change) by skipping equal normalized SL
double cur_sl_norm = NormalizeDouble ( sl , digits ) ;
if ( MathAbs ( new_sl_out - cur_sl_norm ) < ( tick * 0.1 ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY(ATR) skip: no-change after normalize (sl=%.5f new=%.5f) " , sym , sl , new_sl_out ) ) ; }
2025-09-10 13:27:03 -04:00
else
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [TRAIL] %s BUY(ATR) modify: old_sl=%.5f -> new_sl=%.5f bid=%.5f needDist=%.5f " , sym , sl , new_sl_out , fbid , needDist ) ) ;
2025-08-13 19:40:28 -04:00
if ( ! m_trade . PositionModify ( sym , new_sl_out , tp ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY(ATR) modify failed: ret=%d lastErr=%d " , sym , m_trade . ResultRetcode ( ) , GetLastError ( ) ) ) ; }
2025-09-10 13:27:03 -04:00
}
2025-08-13 19:40:28 -04:00
}
else
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s BUY(ATR) skip: inside needDist (tmp=%.5f bid=%.5f needDist=%.5f) " , sym , tmp_sl , fbid , needDist ) ) ; }
2025-08-13 19:40:28 -04:00
}
}
}
else if ( ptype = = POSITION_TYPE_SELL )
{
if ( ( open_price - ask ) < activation )
{ IndicatorRelease ( handle ) ; continue ; }
double new_sl = NormalizeDouble ( ask + atr_distance , digits ) ;
double tmp_sl = new_sl ;
if ( ! NormalizeSLForPosition ( sym , ptype , tmp_sl ) )
{ /* too close or invalid, skip */ }
else
{
if ( sl = = 0 | | tmp_sl < sl - step )
{
double fbid = 0.0 , fask = 0.0 ; SymbolInfoDouble ( sym , SYMBOL_BID , fbid ) ; SymbolInfoDouble ( sym , SYMBOL_ASK , fask ) ;
double needDist = MathMax ( MinStopDistance ( sym ) , FreezeDistance ( sym ) ) ;
double tick = TickSize ( sym ) ;
if ( MathAbs ( tmp_sl - sl ) < tick )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL(ATR) skip: delta<tick (sl=%.5f tmp=%.5f tick=%.5f) " , sym , sl , tmp_sl , tick ) ) ; }
2025-08-13 19:40:28 -04:00
else if ( tmp_sl > = fask + needDist )
{
double new_sl_out = NormalizeDouble ( tmp_sl , digits ) ;
2025-09-10 13:27:03 -04:00
// Extra guard: avoid server-side 10025 (no change) by skipping equal normalized SL
double cur_sl_norm = NormalizeDouble ( sl , digits ) ;
if ( MathAbs ( new_sl_out - cur_sl_norm ) < ( tick * 0.1 ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL(ATR) skip: no-change after normalize (sl=%.5f new=%.5f) " , sym , sl , new_sl_out ) ) ; }
2025-09-10 13:27:03 -04:00
else
{
2025-10-16 18:03:12 -04:00
LOG ( StringFormat ( " [TRAIL] %s SELL(ATR) modify: old_sl=%.5f -> new_sl=%.5f ask=%.5f needDist=%.5f " , sym , sl , new_sl_out , fask , needDist ) ) ;
2025-08-13 19:40:28 -04:00
if ( ! m_trade . PositionModify ( sym , new_sl_out , tp ) )
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL(ATR) modify failed: ret=%d lastErr=%d " , sym , m_trade . ResultRetcode ( ) , GetLastError ( ) ) ) ; }
2025-09-10 13:27:03 -04:00
}
2025-08-13 19:40:28 -04:00
}
else
2025-10-16 18:03:12 -04:00
{ LOG ( StringFormat ( " [TRAIL] %s SELL(ATR) skip: inside needDist (tmp=%.5f ask=%.5f needDist=%.5f) " , sym , tmp_sl , fask , needDist ) ) ; }
2025-08-13 19:40:28 -04:00
}
}
}
}
IndicatorRelease ( handle ) ;
}
}
}
}
2025-09-24 17:58:57 -04:00
//+------------------------------------------------------------------+
//| Find trail configuration by symbol |
//+------------------------------------------------------------------+
2025-09-24 22:41:55 -04:00
CTradeManager : : CTrailConfig * CTradeManager : : FindTrailConfig ( const string symbol )
2025-09-24 17:58:57 -04:00
{
for ( int i = 0 ; i < m_trails . Total ( ) ; i + + )
{
2025-09-24 22:41:55 -04:00
CTradeManager : : CTrailConfig * config = ( CTradeManager : : CTrailConfig * ) m_trails . At ( i ) ;
2025-09-24 17:58:57 -04:00
if ( config ! = NULL & & config . symbol = = symbol )
return config ;
}
return NULL ;
}