727 lines
24 KiB
MQL5
727 lines
24 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| TradeExecutor.mqh v1.0 |
|
||
|
//| Centralized Trade Execution Engine |
|
||
|
//| Optimized with retry logic and error handling |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#ifndef TRADE_EXECUTOR_MQH
|
||
|
#define TRADE_EXECUTOR_MQH
|
||
|
|
||
|
#include "DataTypes.mqh"
|
||
|
#include "SymbolManager.mqh"
|
||
|
#include <Trade/Trade.mqh>
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Trade execution result structure |
|
||
|
//+------------------------------------------------------------------+
|
||
|
struct ExecutionResult
|
||
|
{
|
||
|
bool success;
|
||
|
ulong ticket;
|
||
|
double executed_price;
|
||
|
double executed_volume;
|
||
|
double slippage;
|
||
|
int error_code;
|
||
|
string error_message;
|
||
|
int retries;
|
||
|
datetime execution_time;
|
||
|
int execution_ms;
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Trade Executor Class |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CTradeExecutor
|
||
|
{
|
||
|
private:
|
||
|
//--- Configuration
|
||
|
int m_magic_number;
|
||
|
int m_max_slippage;
|
||
|
int m_max_retries;
|
||
|
int m_retry_delay_ms;
|
||
|
bool m_async_mode;
|
||
|
|
||
|
//--- Trade object
|
||
|
CTrade m_trade;
|
||
|
|
||
|
//--- Symbol manager reference
|
||
|
CSymbolManager *m_symbol_manager;
|
||
|
|
||
|
//--- Statistics
|
||
|
int m_trades_executed;
|
||
|
int m_trades_failed;
|
||
|
double m_total_slippage;
|
||
|
int m_total_retries;
|
||
|
|
||
|
//--- Error handling
|
||
|
int m_last_error;
|
||
|
string m_last_error_message;
|
||
|
|
||
|
//--- Helper methods
|
||
|
bool PrepareTradeRequest(MqlTradeRequest &request, const TradeRequest &trade_req);
|
||
|
bool ValidateTradeRequest(const MqlTradeRequest &request);
|
||
|
ExecutionResult ExecuteWithRetry(MqlTradeRequest &request);
|
||
|
void HandleTradeError(int error_code, MqlTradeRequest &request);
|
||
|
double CalculateSlippage(double requested_price, double executed_price, string symbol);
|
||
|
void LogExecution(const ExecutionResult &result, const MqlTradeRequest &request);
|
||
|
bool WaitForExecution(ulong order_ticket, int timeout_ms = 5000);
|
||
|
|
||
|
public:
|
||
|
CTradeExecutor();
|
||
|
~CTradeExecutor();
|
||
|
|
||
|
//--- Initialization
|
||
|
bool Initialize(int magic_number, int max_slippage = 10);
|
||
|
void SetSymbolManager(CSymbolManager *manager) { m_symbol_manager = manager; }
|
||
|
|
||
|
//--- Configuration
|
||
|
void SetMaxRetries(int retries) { m_max_retries = retries; }
|
||
|
void SetRetryDelay(int delay_ms) { m_retry_delay_ms = delay_ms; }
|
||
|
void SetAsyncMode(bool async) { m_async_mode = async; }
|
||
|
|
||
|
//--- Trade execution
|
||
|
ExecutionResult OpenPosition(const TradeRequest &request);
|
||
|
ExecutionResult ClosePosition(ulong ticket, double volume = 0, string comment = "");
|
||
|
ExecutionResult ClosePositionBySymbol(string symbol, ENUM_POSITION_TYPE type = -1);
|
||
|
ExecutionResult ModifyPosition(ulong ticket, double sl, double tp);
|
||
|
|
||
|
//--- Batch operations
|
||
|
int CloseAllPositions(int magic = -1);
|
||
|
int CloseAllBySymbol(string symbol);
|
||
|
int CloseAllProfitable();
|
||
|
int CloseAllLosing();
|
||
|
|
||
|
//--- Pending orders
|
||
|
ExecutionResult PlacePendingOrder(const TradeRequest &request);
|
||
|
ExecutionResult DeletePendingOrder(ulong ticket);
|
||
|
ExecutionResult ModifyPendingOrder(ulong ticket, double price, double sl, double tp);
|
||
|
|
||
|
//--- Utility functions
|
||
|
bool IsTradeAllowed();
|
||
|
double GetMaxVolume(string symbol);
|
||
|
double GetMinVolume(string symbol);
|
||
|
bool CheckMarginForTrade(string symbol, ENUM_ORDER_TYPE type, double volume);
|
||
|
|
||
|
//--- Statistics
|
||
|
int GetTradesExecuted() { return m_trades_executed; }
|
||
|
int GetTradesFailed() { return m_trades_failed; }
|
||
|
double GetAverageSlippage() { return (m_trades_executed > 0) ? m_total_slippage / m_trades_executed : 0; }
|
||
|
double GetSuccessRate() { return (m_trades_executed + m_trades_failed > 0) ?
|
||
|
(double)m_trades_executed / (m_trades_executed + m_trades_failed) * 100 : 0; }
|
||
|
|
||
|
//--- Error handling
|
||
|
int GetLastError() { return m_last_error; }
|
||
|
string GetLastErrorMessage() { return m_last_error_message; }
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CTradeExecutor::CTradeExecutor()
|
||
|
{
|
||
|
m_magic_number = 0;
|
||
|
m_max_slippage = 10;
|
||
|
m_max_retries = 3;
|
||
|
m_retry_delay_ms = 500;
|
||
|
m_async_mode = false;
|
||
|
|
||
|
m_trades_executed = 0;
|
||
|
m_trades_failed = 0;
|
||
|
m_total_slippage = 0;
|
||
|
m_total_retries = 0;
|
||
|
|
||
|
m_last_error = 0;
|
||
|
m_last_error_message = "";
|
||
|
|
||
|
m_symbol_manager = NULL;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CTradeExecutor::~CTradeExecutor()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Initialize executor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CTradeExecutor::Initialize(int magic_number, int max_slippage)
|
||
|
{
|
||
|
m_magic_number = magic_number;
|
||
|
m_max_slippage = max_slippage;
|
||
|
|
||
|
//--- Configure trade object
|
||
|
m_trade.SetExpertMagicNumber(magic_number);
|
||
|
m_trade.SetDeviationInPoints(max_slippage);
|
||
|
m_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||
|
m_trade.SetAsyncMode(m_async_mode);
|
||
|
|
||
|
//--- Get symbol manager instance
|
||
|
if(m_symbol_manager == NULL)
|
||
|
m_symbol_manager = CSymbolManager::GetInstance();
|
||
|
|
||
|
Print("TradeExecutor initialized: Magic=", magic_number,
|
||
|
", MaxSlippage=", max_slippage, " points");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Open new position with full error handling |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionResult CTradeExecutor::OpenPosition(const TradeRequest &request)
|
||
|
{
|
||
|
ExecutionResult result;
|
||
|
ZeroMemory(result);
|
||
|
|
||
|
datetime start_time = GetTickCount();
|
||
|
|
||
|
//--- Check if trading allowed
|
||
|
if(!IsTradeAllowed())
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = ERR_TRADE_NOT_ALLOWED;
|
||
|
result.error_message = "Trading not allowed";
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Prepare MQL trade request
|
||
|
MqlTradeRequest mql_request;
|
||
|
if(!PrepareTradeRequest(mql_request, request))
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = m_last_error;
|
||
|
result.error_message = m_last_error_message;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Validate request
|
||
|
if(!ValidateTradeRequest(mql_request))
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = m_last_error;
|
||
|
result.error_message = m_last_error_message;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Execute with retry logic
|
||
|
result = ExecuteWithRetry(mql_request);
|
||
|
|
||
|
//--- Calculate execution time
|
||
|
result.execution_ms = (int)(GetTickCount() - start_time);
|
||
|
|
||
|
//--- Update statistics
|
||
|
if(result.success)
|
||
|
{
|
||
|
m_trades_executed++;
|
||
|
m_total_slippage += result.slippage;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_trades_failed++;
|
||
|
}
|
||
|
|
||
|
m_total_retries += result.retries;
|
||
|
|
||
|
//--- Log execution
|
||
|
LogExecution(result, mql_request);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Prepare trade request |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CTradeExecutor::PrepareTradeRequest(MqlTradeRequest &request, const TradeRequest &trade_req)
|
||
|
{
|
||
|
ZeroMemory(request);
|
||
|
|
||
|
//--- Get symbol info
|
||
|
if(m_symbol_manager == NULL)
|
||
|
{
|
||
|
m_last_error = ERR_INVALID_POINTER;
|
||
|
m_last_error_message = "Symbol manager not initialized";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
SymbolCacheEntry symbol_info;
|
||
|
if(!m_symbol_manager.GetSymbolInfo(trade_req.symbol, symbol_info))
|
||
|
{
|
||
|
m_last_error = ERR_UNKNOWN_SYMBOL;
|
||
|
m_last_error_message = "Invalid symbol: " + trade_req.symbol;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//--- Basic parameters
|
||
|
request.action = TRADE_ACTION_DEAL;
|
||
|
request.symbol = trade_req.symbol;
|
||
|
request.volume = m_symbol_manager.NormalizeLots(trade_req.symbol, trade_req.volume);
|
||
|
request.type = trade_req.type;
|
||
|
request.type_filling = ORDER_FILLING_RETURN;
|
||
|
request.magic = m_magic_number;
|
||
|
request.comment = trade_req.comment;
|
||
|
request.deviation = m_max_slippage;
|
||
|
|
||
|
//--- Get current price
|
||
|
if(trade_req.type == ORDER_TYPE_BUY)
|
||
|
{
|
||
|
request.price = m_symbol_manager.GetCurrentAsk(trade_req.symbol);
|
||
|
|
||
|
if(trade_req.sl_distance > 0)
|
||
|
request.sl = m_symbol_manager.NormalizePrice(trade_req.symbol,
|
||
|
request.price - trade_req.sl_distance);
|
||
|
if(trade_req.tp_distance > 0)
|
||
|
request.tp = m_symbol_manager.NormalizePrice(trade_req.symbol,
|
||
|
request.price + trade_req.tp_distance);
|
||
|
}
|
||
|
else if(trade_req.type == ORDER_TYPE_SELL)
|
||
|
{
|
||
|
request.price = m_symbol_manager.GetCurrentBid(trade_req.symbol);
|
||
|
|
||
|
if(trade_req.sl_distance > 0)
|
||
|
request.sl = m_symbol_manager.NormalizePrice(trade_req.symbol,
|
||
|
request.price + trade_req.sl_distance);
|
||
|
if(trade_req.tp_distance > 0)
|
||
|
request.tp = m_symbol_manager.NormalizePrice(trade_req.symbol,
|
||
|
request.price - trade_req.tp_distance);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_last_error = ERR_INVALID_ORDER_TYPE;
|
||
|
m_last_error_message = "Invalid order type";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Validate trade request |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CTradeExecutor::ValidateTradeRequest(const MqlTradeRequest &request)
|
||
|
{
|
||
|
//--- Check volume
|
||
|
double min_lot = m_symbol_manager.GetMinLot(request.symbol);
|
||
|
double max_lot = m_symbol_manager.GetMaxLot(request.symbol);
|
||
|
|
||
|
if(request.volume < min_lot || request.volume > max_lot)
|
||
|
{
|
||
|
m_last_error = ERR_INVALID_VOLUME;
|
||
|
m_last_error_message = StringFormat("Invalid volume: %.2f (min: %.2f, max: %.2f)",
|
||
|
request.volume, min_lot, max_lot);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//--- Check stop levels
|
||
|
if(!m_symbol_manager.ValidateStopLevels(request.symbol, request.price, request.sl, request.tp))
|
||
|
{
|
||
|
m_last_error = ERR_INVALID_STOPS;
|
||
|
m_last_error_message = "Invalid stop levels";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//--- Check margin
|
||
|
if(!CheckMarginForTrade(request.symbol, request.type, request.volume))
|
||
|
{
|
||
|
m_last_error = ERR_NOT_ENOUGH_MONEY;
|
||
|
m_last_error_message = "Insufficient margin";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute trade with retry logic |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionResult CTradeExecutor::ExecuteWithRetry(MqlTradeRequest &request)
|
||
|
{
|
||
|
ExecutionResult result;
|
||
|
ZeroMemory(result);
|
||
|
|
||
|
MqlTradeResult trade_result;
|
||
|
|
||
|
for(int retry = 0; retry <= m_max_retries; retry++)
|
||
|
{
|
||
|
result.retries = retry;
|
||
|
|
||
|
//--- Refresh price before each attempt
|
||
|
if(retry > 0)
|
||
|
{
|
||
|
if(request.type == ORDER_TYPE_BUY)
|
||
|
request.price = m_symbol_manager.GetCurrentAsk(request.symbol);
|
||
|
else
|
||
|
request.price = m_symbol_manager.GetCurrentBid(request.symbol);
|
||
|
|
||
|
Sleep(m_retry_delay_ms);
|
||
|
}
|
||
|
|
||
|
//--- Send order
|
||
|
ResetLastError();
|
||
|
|
||
|
if(OrderSend(request, trade_result))
|
||
|
{
|
||
|
result.success = true;
|
||
|
result.ticket = trade_result.order;
|
||
|
result.executed_price = trade_result.price;
|
||
|
result.executed_volume = trade_result.volume;
|
||
|
result.execution_time = TimeCurrent();
|
||
|
|
||
|
//--- Calculate slippage
|
||
|
result.slippage = CalculateSlippage(request.price, result.executed_price, request.symbol);
|
||
|
|
||
|
//--- Wait for position to appear (for async mode)
|
||
|
if(m_async_mode)
|
||
|
{
|
||
|
if(!WaitForExecution(result.ticket))
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = ERR_TRADE_TIMEOUT;
|
||
|
result.error_message = "Trade execution timeout";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int error = GetLastError();
|
||
|
result.error_code = error;
|
||
|
result.error_message = trade_result.comment;
|
||
|
|
||
|
//--- Handle specific errors
|
||
|
HandleTradeError(error, request);
|
||
|
|
||
|
//--- Check if should retry
|
||
|
bool should_retry = false;
|
||
|
|
||
|
switch(error)
|
||
|
{
|
||
|
case ERR_REQUOTE:
|
||
|
case ERR_PRICE_CHANGED:
|
||
|
case ERR_OFF_QUOTES:
|
||
|
case ERR_BROKER_BUSY:
|
||
|
case ERR_TRADE_CONTEXT_BUSY:
|
||
|
should_retry = true;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
should_retry = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(!should_retry || retry == m_max_retries)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Handle trade error |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CTradeExecutor::HandleTradeError(int error_code, MqlTradeRequest &request)
|
||
|
{
|
||
|
m_last_error = error_code;
|
||
|
|
||
|
switch(error_code)
|
||
|
{
|
||
|
case ERR_REQUOTE:
|
||
|
m_last_error_message = "Requote - prices changed";
|
||
|
break;
|
||
|
|
||
|
case ERR_INVALID_STOPS:
|
||
|
m_last_error_message = StringFormat("Invalid stops - SL: %.5f, TP: %.5f",
|
||
|
request.sl, request.tp);
|
||
|
break;
|
||
|
|
||
|
case ERR_INVALID_VOLUME:
|
||
|
m_last_error_message = StringFormat("Invalid volume: %.2f lots", request.volume);
|
||
|
break;
|
||
|
|
||
|
case ERR_NOT_ENOUGH_MONEY:
|
||
|
m_last_error_message = "Insufficient margin for trade";
|
||
|
break;
|
||
|
|
||
|
case ERR_TRADE_NOT_ALLOWED:
|
||
|
m_last_error_message = "Trading not allowed - check AutoTrading";
|
||
|
break;
|
||
|
|
||
|
case ERR_MARKET_CLOSED:
|
||
|
m_last_error_message = "Market is closed";
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
m_last_error_message = StringFormat("Trade error %d", error_code);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate slippage in points |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CTradeExecutor::CalculateSlippage(double requested_price, double executed_price, string symbol)
|
||
|
{
|
||
|
double point = m_symbol_manager.GetPoint(symbol);
|
||
|
if(point == 0) return 0;
|
||
|
|
||
|
return MathAbs(executed_price - requested_price) / point;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Log execution result |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CTradeExecutor::LogExecution(const ExecutionResult &result, const MqlTradeRequest &request)
|
||
|
{
|
||
|
if(result.success)
|
||
|
{
|
||
|
Print(StringFormat("TradeExecutor: %s #%d opened - %s %.2f @ %.5f (slippage: %.1f points, %d ms)",
|
||
|
(request.type == ORDER_TYPE_BUY) ? "BUY" : "SELL",
|
||
|
result.ticket,
|
||
|
request.symbol,
|
||
|
result.executed_volume,
|
||
|
result.executed_price,
|
||
|
result.slippage,
|
||
|
result.execution_ms));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Print(StringFormat("TradeExecutor: Failed to open %s %s %.2f - Error %d: %s (retries: %d)",
|
||
|
(request.type == ORDER_TYPE_BUY) ? "BUY" : "SELL",
|
||
|
request.symbol,
|
||
|
request.volume,
|
||
|
result.error_code,
|
||
|
result.error_message,
|
||
|
result.retries));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Close position |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionResult CTradeExecutor::ClosePosition(ulong ticket, double volume, string comment)
|
||
|
{
|
||
|
ExecutionResult result;
|
||
|
ZeroMemory(result);
|
||
|
|
||
|
datetime start_time = GetTickCount();
|
||
|
|
||
|
//--- Select position
|
||
|
if(!PositionSelectByTicket(ticket))
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = ERR_TRADE_POSITION_NOT_FOUND;
|
||
|
result.error_message = "Position not found";
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Get position info
|
||
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
||
|
double position_volume = PositionGetDouble(POSITION_VOLUME);
|
||
|
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||
|
|
||
|
//--- Determine volume to close
|
||
|
if(volume == 0 || volume >= position_volume)
|
||
|
volume = position_volume;
|
||
|
else
|
||
|
volume = m_symbol_manager.NormalizeLots(symbol, volume);
|
||
|
|
||
|
//--- Prepare request
|
||
|
MqlTradeRequest request;
|
||
|
ZeroMemory(request);
|
||
|
|
||
|
request.action = TRADE_ACTION_DEAL;
|
||
|
request.symbol = symbol;
|
||
|
request.volume = volume;
|
||
|
request.type = (type == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
|
||
|
request.position = ticket;
|
||
|
request.deviation = m_max_slippage;
|
||
|
request.magic = m_magic_number;
|
||
|
request.comment = comment;
|
||
|
|
||
|
//--- Get current price
|
||
|
request.price = (type == POSITION_TYPE_BUY) ?
|
||
|
m_symbol_manager.GetCurrentBid(symbol) :
|
||
|
m_symbol_manager.GetCurrentAsk(symbol);
|
||
|
|
||
|
//--- Execute
|
||
|
result = ExecuteWithRetry(request);
|
||
|
|
||
|
//--- Update execution time
|
||
|
result.execution_ms = (int)(GetTickCount() - start_time);
|
||
|
|
||
|
//--- Update statistics
|
||
|
if(result.success)
|
||
|
{
|
||
|
m_trades_executed++;
|
||
|
m_total_slippage += result.slippage;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_trades_failed++;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Modify position stops |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionResult CTradeExecutor::ModifyPosition(ulong ticket, double sl, double tp)
|
||
|
{
|
||
|
ExecutionResult result;
|
||
|
ZeroMemory(result);
|
||
|
|
||
|
//--- Select position
|
||
|
if(!PositionSelectByTicket(ticket))
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = ERR_TRADE_POSITION_NOT_FOUND;
|
||
|
result.error_message = "Position not found";
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Get position info
|
||
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
||
|
double current_sl = PositionGetDouble(POSITION_SL);
|
||
|
double current_tp = PositionGetDouble(POSITION_TP);
|
||
|
|
||
|
//--- Check if modification needed
|
||
|
sl = m_symbol_manager.NormalizePrice(symbol, sl);
|
||
|
tp = m_symbol_manager.NormalizePrice(symbol, tp);
|
||
|
|
||
|
if(sl == current_sl && tp == current_tp)
|
||
|
{
|
||
|
result.success = true;
|
||
|
result.ticket = ticket;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Validate stops
|
||
|
double current_price = m_symbol_manager.GetCurrentPrice(symbol,
|
||
|
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE));
|
||
|
|
||
|
if(!m_symbol_manager.ValidateStopLevels(symbol, current_price, sl, tp))
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = ERR_INVALID_STOPS;
|
||
|
result.error_message = "Invalid stop levels";
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--- Modify position
|
||
|
if(m_trade.PositionModify(ticket, sl, tp))
|
||
|
{
|
||
|
result.success = true;
|
||
|
result.ticket = ticket;
|
||
|
result.execution_time = TimeCurrent();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result.success = false;
|
||
|
result.error_code = GetLastError();
|
||
|
result.error_message = m_trade.ResultComment();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Check if trading is allowed |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CTradeExecutor::IsTradeAllowed()
|
||
|
{
|
||
|
//--- Check terminal
|
||
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
||
|
return false;
|
||
|
|
||
|
//--- Check EA
|
||
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
|
||
|
return false;
|
||
|
|
||
|
//--- Check account
|
||
|
if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
|
||
|
return false;
|
||
|
|
||
|
if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Check margin for trade |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CTradeExecutor::CheckMarginForTrade(string symbol, ENUM_ORDER_TYPE type, double volume)
|
||
|
{
|
||
|
double price = (type == ORDER_TYPE_BUY) ?
|
||
|
m_symbol_manager.GetCurrentAsk(symbol) :
|
||
|
m_symbol_manager.GetCurrentBid(symbol);
|
||
|
|
||
|
double margin = 0;
|
||
|
|
||
|
if(!OrderCalcMargin(type, symbol, volume, price, margin))
|
||
|
return false;
|
||
|
|
||
|
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
||
|
|
||
|
return (margin > 0 && free_margin > margin);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Wait for order execution (async mode) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CTradeExecutor::WaitForExecution(ulong order_ticket, int timeout_ms)
|
||
|
{
|
||
|
datetime start_time = GetTickCount();
|
||
|
|
||
|
while(GetTickCount() - start_time < timeout_ms)
|
||
|
{
|
||
|
//--- Check if position exists
|
||
|
if(PositionSelectByTicket(order_ticket))
|
||
|
return true;
|
||
|
|
||
|
//--- Check if order still pending
|
||
|
if(OrderSelect(order_ticket))
|
||
|
{
|
||
|
Sleep(10);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//--- Order doesn't exist - might be rejected
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Close all positions |
|
||
|
//+------------------------------------------------------------------+
|
||
|
int CTradeExecutor::CloseAllPositions(int magic)
|
||
|
{
|
||
|
int closed = 0;
|
||
|
|
||
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||
|
{
|
||
|
ulong ticket = PositionGetTicket(i);
|
||
|
if(ticket == 0) continue;
|
||
|
|
||
|
if(PositionSelectByTicket(ticket))
|
||
|
{
|
||
|
//--- Check magic number
|
||
|
if(magic >= 0 && PositionGetInteger(POSITION_MAGIC) != magic)
|
||
|
continue;
|
||
|
|
||
|
ExecutionResult result = ClosePosition(ticket, 0, "Close All");
|
||
|
if(result.success) closed++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return closed;
|
||
|
}
|
||
|
|
||
|
#endif // TRADE_EXECUTOR_MQH
|