352 lines
11 KiB
MQL5
352 lines
11 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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;
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|