| //+------------------------------------------------------------------+
|
| //| SecurityEnhancements_Impl.mqh |
|
| //| Copyright 2025, EscapeEA |
|
| //| https://www.escapeea.com |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2025, EscapeEA"
|
| #property link "https://www.escapeea.com"
|
| #property version "1.00"
|
| #property strict
|
|
|
| // Include necessary headers
|
| #include <Trade\Trade.mqh>
|
| #include <Trade\PositionInfo.mqh>
|
| #include <Trade\DealInfo.mqh>
|
| #include <Trade\OrderInfo.mqh>
|
| #include <Trade\HistoryOrderInfo.mqh>
|
| #include <Arrays\ArrayObj.mqh>
|
| #include <Arrays\ArrayString.mqh>
|
|
|
| // Include the header file to get class declaration
|
| #include "SecurityEnhancements.mqh"
|
|
|
| //+------------------------------------------------------------------+
|
| //| Constructor |
|
| //+------------------------------------------------------------------+
|
| CSecurityEnhancements::CSecurityEnhancements(void) :
|
| m_riskManager(NULL),
|
| m_trade(),
|
| m_position(),
|
| m_deal(),
|
| m_lastTradeTime(0),
|
| m_tradeCount(0),
|
| m_avgTradeTime(0.0),
|
| m_anomalyCount(0),
|
| m_maxSlippage(10) // 10 points default slippage
|
| {
|
| ZeroMemory(m_lastTick);
|
| m_lastError = "";
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Destructor |
|
| //+------------------------------------------------------------------+
|
| CSecurityEnhancements::~CSecurityEnhancements(void)
|
| {
|
| Deinitialize();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Initialization |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::Initialize(void)
|
| {
|
| // Initialize trade object
|
| m_trade.SetExpertMagicNumber(0);
|
| m_trade.SetDeviationInPoints(m_maxSlippage);
|
| m_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
|
| // Reset counters
|
| m_tradeCount = 0;
|
| m_anomalyCount = 0;
|
| m_avgTradeTime = 0.0;
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Deinitialization |
|
| //+------------------------------------------------------------------+
|
| void CSecurityEnhancements::Deinitialize(void)
|
| {
|
| // Clean up resources
|
| m_tradeCount = 0;
|
| m_anomalyCount = 0;
|
| m_avgTradeTime = 0.0;
|
| m_lastError = "";
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Check price deviation from current market price |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::CheckPriceDeviation(const string symbol, const double price)
|
| {
|
| if(!SymbolInfoTick(symbol, m_lastTick))
|
| {
|
| m_lastError = "Failed to get tick data for " + symbol;
|
| return false;
|
| }
|
|
|
| double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
| if(point == 0)
|
| point = 0.00001; // Prevent division by zero
|
|
|
| long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
| double spread_pips = spread * point;
|
| double max_deviation = spread_pips * 5.0; // Allow up to 5x spread deviation
|
|
|
| if(MathAbs(price - m_lastTick.ask) > max_deviation &&
|
| MathAbs(price - m_lastTick.bid) > max_deviation)
|
| {
|
| m_lastError = StringFormat("Suspicious price deviation: %G vs [%G, %G]",
|
| price, m_lastTick.bid, m_lastTick.ask);
|
| return false;
|
| }
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Check and enforce rate limiting |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::CheckRateLimit(void)
|
| {
|
| datetime current_time = TimeCurrent();
|
|
|
| // Reset counter if minute, hour, or day has changed
|
| MqlDateTime time_struct;
|
| TimeToStruct(current_time, time_struct);
|
| MqlDateTime last_trade_time_struct;
|
| TimeToStruct(m_lastTradeTime, last_trade_time_struct);
|
|
|
| if(time_struct.min != last_trade_time_struct.min ||
|
| time_struct.hour != last_trade_time_struct.hour ||
|
| time_struct.day != last_trade_time_struct.day)
|
| {
|
| m_tradeCount = 0;
|
| }
|
|
|
| // Check if rate limit is exceeded
|
| if(m_tradeCount >= 10) // 10 trades per minute limit
|
| {
|
| m_lastError = StringFormat("Rate limit exceeded: %d trades in last minute", m_tradeCount);
|
| return false;
|
| }
|
|
|
| // Update counters
|
| m_lastTradeTime = current_time;
|
| m_tradeCount++;
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Execute trade with enhanced security checks |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::ExecuteTrade(MqlTradeRequest &request, MqlTradeResult &result)
|
| {
|
| // Initialize result structure
|
| ZeroMemory(result);
|
|
|
| // Validate the trade request first
|
| if(!ValidateTradeRequest(request))
|
| {
|
| result.retcode = TRADE_RETCODE_ERROR;
|
| return false;
|
| }
|
|
|
| bool success = false;
|
| uint start_time = GetTickCount();
|
|
|
| // Process different types of trade actions
|
| switch(request.action)
|
| {
|
| case TRADE_ACTION_DEAL:
|
| if(request.type == ORDER_TYPE_BUY || request.type == ORDER_TYPE_SELL)
|
| {
|
| success = m_trade.PositionOpen(request.symbol, request.type, request.volume,
|
| request.price, request.sl, request.tp, request.comment);
|
| }
|
| else
|
| {
|
| success = m_trade.OrderSend(request, result);
|
| }
|
| break;
|
|
|
| case TRADE_ACTION_MODIFY:
|
| success = m_trade.OrderModify(request.order, request.price, request.sl,
|
| request.tp, ORDER_TIME_GTC, 0, 0);
|
| break;
|
|
|
| case TRADE_ACTION_REMOVE:
|
| success = m_trade.OrderDelete(request.order);
|
| break;
|
|
|
| default:
|
| m_lastError = "Unsupported trade action: " + IntegerToString(request.action);
|
| result.retcode = TRADE_RETCODE_REJECT;
|
| return false;
|
| }
|
|
|
| // Update performance metrics
|
| uint execution_time = GetTickCount() - start_time;
|
| if(m_tradeCount > 0)
|
| m_avgTradeTime = (m_avgTradeTime * m_tradeCount + execution_time) / (m_tradeCount + 1);
|
| else
|
| m_avgTradeTime = execution_time;
|
|
|
| // Verify order fill if required
|
| if(success && request.action == TRADE_ACTION_DEAL)
|
| {
|
| if(!VerifyOrderFill(result.order))
|
| {
|
| m_lastError = "Order fill verification failed";
|
| success = false;
|
| }
|
| }
|
|
|
| return success;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Verify order fill details |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::VerifyOrderFill(const ulong ticket)
|
| {
|
| if(!m_deal.SelectByIndex(0)) // Select the most recent deal
|
| {
|
| m_lastError = "Failed to select deal for ticket " + IntegerToString(ticket);
|
| return false;
|
| }
|
|
|
| double volume = m_deal.Volume();
|
| double volume_initial = m_deal.Volume(); // In MQL5, Volume() returns initial volume
|
|
|
| if(volume < volume_initial)
|
| {
|
| PrintFormat("Partial fill detected: %G/%G lots", volume, volume_initial);
|
| // Not necessarily an error, just log it
|
| }
|
|
|
| double point = SymbolInfoDouble(m_deal.Symbol(), SYMBOL_POINT);
|
| if(point == 0)
|
| point = 0.00001; // Prevent division by zero
|
|
|
| double max_slippage = m_maxSlippage * point;
|
| double actual_slippage = MathAbs(m_deal.Price() - m_deal.Price());
|
|
|
| if(actual_slippage > max_slippage)
|
| {
|
| m_lastError = "Excessive slippage detected: " + DoubleToString(actual_slippage, 5);
|
| return false;
|
| }
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Detect market anomalies |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::DetectAnomalies(const MqlTradeRequest &request)
|
| {
|
| if(request.price <= 0.0)
|
| {
|
| m_lastError = "Invalid price in trade request";
|
| return false;
|
| }
|
|
|
| double point = SymbolInfoDouble(request.symbol, SYMBOL_POINT);
|
| if(point == 0)
|
| point = 0.00001; // Prevent division by zero
|
|
|
| long stop_level = SymbolInfoInteger(request.symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
| double ask = SymbolInfoDouble(request.symbol, SYMBOL_ASK);
|
|
|
| if(stop_level == 0 || ask == 0)
|
| {
|
| m_lastError = "Failed to get symbol information for " + request.symbol;
|
| return false;
|
| }
|
|
|
| double min_price = stop_level * point;
|
| double max_price = (min_price > 0) ? ask * 2.0 : 1e10;
|
|
|
| if(request.price < min_price || request.price > max_price)
|
| {
|
| m_lastError = StringFormat("Invalid price: %G (valid range: %G - %G)",
|
| request.price, min_price, max_price);
|
| return false;
|
| }
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Validate trade request parameters |
|
| //+------------------------------------------------------------------+
|
| bool CSecurityEnhancements::ValidateTradeRequest(MqlTradeRequest &request)
|
| {
|
| // Basic validation
|
| if(request.symbol == "")
|
| {
|
| m_lastError = "Empty symbol in trade request";
|
| return false;
|
| }
|
|
|
| if(request.volume <= 0)
|
| {
|
| m_lastError = "Invalid volume in trade request: " + DoubleToString(request.volume);
|
| return false;
|
| }
|
|
|
| // Check for news events if enabled
|
| if(m_newsFilterEnabled && IsNewsEvent(request.symbol))
|
| {
|
| m_lastError = "Trading is restricted during news events for " + request.symbol;
|
| return false;
|
| }
|
|
|
| // Check price anomalies
|
| if(!DetectAnomalies(request))
|
| {
|
| return false;
|
| }
|
|
|
| // Check rate limits
|
| if(!CheckRateLimit())
|
| {
|
| return false;
|
| }
|
|
|
| // Additional validation based on order type
|
| switch(request.type)
|
| {
|
| case ORDER_TYPE_BUY:
|
| case ORDER_TYPE_SELL:
|
| // Market order - check price deviation
|
| if(!CheckPriceDeviation(request.symbol, request.price))
|
| {
|
| return false;
|
| }
|
| break;
|
|
|
| case ORDER_TYPE_BUY_LIMIT:
|
| case ORDER_TYPE_SELL_LIMIT:
|
| case ORDER_TYPE_BUY_STOP:
|
| case ORDER_TYPE_SELL_STOP:
|
| // Pending order - check price is valid
|
| if(request.price <= 0)
|
| {
|
| m_lastError = "Invalid price in pending order: " + DoubleToString(request.price);
|
| return false;
|
| }
|
| break;
|
|
|
| default:
|
| m_lastError = "Unsupported order type: " + IntegerToString(request.type);
|
| return false;
|
| }
|
|
|
| return true;
|
| }
|
| //+------------------------------------------------------------------+
|