mql5/Include/Escape/SecurityEnhancements_Impl.mqh

352 lines
11 KiB
MQL5
Raw Permalink Normal View History

2025-08-05 01:57:33 -04:00
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+