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;
| |||
}
| |||
//+------------------------------------------------------------------+
|