2025-08-02 22:17:29 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| escape.mq5 |
|
|
|
|
//| Copyright 2025, EscapeEA Team |
|
|
|
|
//| es_cape77@hotmail.com |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
#property copyright "Copyright 2025, EscapeEA Team"
|
|
|
|
#property link "https://www.escapeea.com"
|
|
|
|
#property version "1.00"
|
|
|
|
|
|
|
|
// Include necessary files
|
|
|
|
#include <Trade\Trade.mqh> // For CTrade and error handling
|
|
|
|
#include <Trade\PositionInfo.mqh> // For position information
|
|
|
|
#include <Trade\SymbolInfo.mqh> // For symbol information
|
|
|
|
#include <Trade\AccountInfo.mqh>
|
|
|
|
#include <Trade\DealInfo.mqh>
|
|
|
|
#include <Trade\OrderInfo.mqh>
|
|
|
|
|
|
|
|
//--- Input Parameters ---
|
|
|
|
// General Settings
|
|
|
|
input group "=== General Settings ==="
|
|
|
|
input double InpLotSize = 0.1; // Lot size
|
|
|
|
input int InpStopLoss = 100; // Stop Loss in points
|
|
|
|
input int InpTakeProfit = 200; // Take Profit in points
|
|
|
|
input bool InpUseTrailingStop = false; // Use Trailing Stop
|
|
|
|
input int InpTrailingStop = 50; // Trailing Stop in points
|
|
|
|
input int InpTrailingStep = 10; // Trailing Step in points
|
|
|
|
input int InpMagicNumber = 123456; // Magic Number
|
|
|
|
input string InpTradeComment = "Escape EA"; // Trade Comment
|
|
|
|
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15; // Timeframe
|
|
|
|
input bool InpEnableLogging = true; // Enable Logging
|
|
|
|
input bool InpEnableEmailAlerts = false; // Enable Email Alerts
|
|
|
|
input bool InpEnablePushAlerts = false; // Enable Push Notifications
|
|
|
|
|
|
|
|
// Risk Management
|
|
|
|
input group "=== Risk Management ==="
|
|
|
|
input double InpRiskPercent = 1.0; // Risk per trade (%)
|
|
|
|
input int InpMaxOpenTrades = 5; // Max Open Trades
|
|
|
|
input bool InpHideStopLoss = false; // Hide Stop Loss (for brokers that don't support SL/TP)
|
|
|
|
input bool InpUseDynamicLots = true; // Use Dynamic Lot Sizing
|
|
|
|
input double InpMinLotSize = 0.01; // Minimum Lot Size
|
|
|
|
input double InpMaxLotSize = 10.0; // Maximum Lot Size
|
|
|
|
input int InpSlippage = 30; // Slippage in points
|
|
|
|
input bool InpCloseOnOpposite = true; // Close on Opposite Signal
|
|
|
|
input int InpMaxSpread = 50; // Maximum Spread in points
|
|
|
|
|
|
|
|
// Trading Hours
|
|
|
|
input group "=== Trading Hours ==="
|
2025-08-16 11:53:49 -04:00
|
|
|
input bool InpTradeOnFriday = true; // Trade on Friday
|
2025-08-02 22:17:29 -04:00
|
|
|
input int InpHourStart = 0; // Trading Start Hour (0-23)
|
|
|
|
input int InpHourEnd = 24; // Trading End Hour (0-24)
|
|
|
|
|
|
|
|
//--- Strategy 1: Moving Average Crossover ---
|
|
|
|
input group "=== MA Crossover Strategy ==="
|
|
|
|
input bool InpEnableStrategy1 = true; // Enable MA Crossover Strategy
|
|
|
|
input int InpMAFastPeriod = 5; // MA Fast Period
|
|
|
|
input int InpMASlowPeriod = 20; // MA Slow Period
|
|
|
|
input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA Method
|
|
|
|
input ENUM_APPLIED_PRICE InpMAPrice = PRICE_CLOSE; // MA Applied Price
|
|
|
|
input ENUM_TIMEFRAMES InpHigherTimeframe = PERIOD_H1; // Higher Timeframe for trend confirmation
|
|
|
|
|
|
|
|
//--- Strategy 2: RSI ---
|
|
|
|
input group "=== RSI Strategy ==="
|
|
|
|
input bool InpEnableStrategy2 = true; // Enable RSI Strategy
|
|
|
|
input int InpRSIPeriod = 14; // RSI Period
|
|
|
|
input double InpRSIOverbought = 70.0; // RSI Overbought Level
|
|
|
|
input double InpRSIOversold = 30.0; // RSI Oversold Level
|
|
|
|
input ENUM_APPLIED_PRICE InpRSIPrice = PRICE_CLOSE; // RSI Applied Price
|
|
|
|
|
|
|
|
//--- Strategy 3: MACD ---
|
|
|
|
input group "=== MACD Strategy ==="
|
|
|
|
input bool InpEnableStrategy3 = true; // Enable MACD Strategy
|
|
|
|
input int InpMACDFastEMA = 12; // MACD Fast EMA
|
|
|
|
input int InpMACDSlowEMA = 26; // MACD Slow EMA
|
|
|
|
input int InpMACDSignalPeriod = 9; // MACD Signal Period
|
|
|
|
input ENUM_APPLIED_PRICE InpMACDPrice = PRICE_CLOSE; // MACD Applied Price
|
|
|
|
|
|
|
|
//--- Strategy 4: Bollinger Bands ---
|
|
|
|
input group "=== Bollinger Bands Strategy ==="
|
|
|
|
input bool InpEnableStrategy4 = true; // Enable Bollinger Bands Strategy
|
|
|
|
input int InpBBPeriod = 20; // Bollinger Bands Period
|
|
|
|
input double InpBBDeviation = 2.0; // Bollinger Bands Deviation
|
|
|
|
input int InpBBShift = 0; // Bollinger Bands Shift
|
|
|
|
input ENUM_APPLIED_PRICE InpBBPrice = PRICE_CLOSE; // Bollinger Bands Applied Price
|
|
|
|
|
|
|
|
//--- Strategy Risk Management ---
|
|
|
|
input group "=== Strategy Risk Management ==="
|
|
|
|
input double InpDefaultStopLossPips = 20.0; // Default Stop Loss (pips)
|
|
|
|
input double InpDefaultTakeProfitPips = 40.0; // Default Take Profit (pips)
|
|
|
|
input double InpMaxRiskPerTrade = 1.0; // Max Risk per Trade (%)
|
|
|
|
input bool InpUseATRForSL = true; // Use ATR for Stop Loss
|
|
|
|
input int InpATRPeriod = 14; // ATR Period for Stop Loss
|
|
|
|
input double InpATRMultiplier = 2.0; // ATR Multiplier for Stop Loss
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
//--- Logging Configuration ---
|
|
|
|
input group "=== Logging ==="
|
|
|
|
input int MaxLogSizeKB = 1024; // Rotate when log reaches this size (KB)
|
|
|
|
input int MaxLogFiles = 5; // Number of rotated log files to keep
|
|
|
|
|
|
|
|
//--- Multi-Symbol & Runtime Controls ---
|
|
|
|
input group "=== Multi-Symbol & Controls ==="
|
|
|
|
input bool EnableMultiSymbol = true; // Enable multi-symbol scanning/management
|
|
|
|
input int ScanIntervalSeconds = 10; // OnTimer scan interval (seconds)
|
|
|
|
input string ExtraSymbolsCSV = ""; // Extra symbols (CSV) to include
|
|
|
|
input bool UseSymbolPathDiscovery = true; // Use SYMBOL_PATH to classify and discover
|
|
|
|
input bool MasterPause = false; // Pause all trading activity
|
|
|
|
input bool PauseOpenNew = false; // Pause opening new trades (management still runs)
|
|
|
|
input bool EmergencyHalt = false; // Emergency halt (no new trades, mgmt only)
|
|
|
|
|
|
|
|
//--- News Filter (hooks only for Phase 1) ---
|
|
|
|
input group "=== News Filter (Hooks) ==="
|
|
|
|
input bool UseNewsFilter = false; // Enable calendar-based news filter
|
|
|
|
input bool NewsHighImpact = true; // Consider high impact
|
|
|
|
input bool NewsMediumImpact = false; // Consider medium impact
|
|
|
|
input bool NewsLowImpact = false; // Consider low impact
|
|
|
|
input int NewsPreEventMin = 30; // Minutes before event to block
|
|
|
|
input int NewsPostEventMin = 30; // Minutes after event to block
|
|
|
|
|
|
|
|
//--- Asset Class Controls & Risk ---
|
|
|
|
input group "=== Asset Class Controls & Risk ==="
|
|
|
|
input bool EnableFX = true; // Trade FX pairs
|
|
|
|
input bool EnableMetals = true; // Trade Metals (XAU, XAG)
|
|
|
|
input bool EnableIndices = true; // Trade Indices (US500, GER40, etc.)
|
|
|
|
input bool EnableEnergies = true; // Trade Energies (XTI, XBR, etc.)
|
|
|
|
input bool EnableCrypto = false; // Trade Crypto (BTCUSD, ETHUSD)
|
|
|
|
input double RiskMultFX = 1.0; // Risk multiplier for FX
|
|
|
|
input double RiskMultMetals = 1.0; // Risk multiplier for Metals
|
|
|
|
input double RiskMultIndices = 1.0; // Risk multiplier for Indices
|
|
|
|
input double RiskMultEnergies = 1.0; // Risk multiplier for Energies
|
|
|
|
input double RiskMultCrypto = 0.5; // Risk multiplier for Crypto
|
|
|
|
input int FX_SessionStart = 0; // Hour (server time) trading start for FX
|
|
|
|
input int FX_SessionEnd = 24; // Hour (server time) trading end for FX
|
|
|
|
input int Metals_SessionStart = 0; // Hour trading start for Metals
|
|
|
|
input int Metals_SessionEnd = 24; // Hour trading end for Metals
|
|
|
|
input int Indices_SessionStart = 0; // Hour trading start for Indices
|
|
|
|
input int Indices_SessionEnd = 24; // Hour trading end for Indices
|
|
|
|
input int Energies_SessionStart= 0; // Hour trading start for Energies
|
|
|
|
input int Energies_SessionEnd = 24; // Hour trading end for Energies
|
|
|
|
input int Crypto_SessionStart = 0; // Hour trading start for Crypto
|
|
|
|
input int Crypto_SessionEnd = 24; // Hour trading end for Crypto
|
|
|
|
|
|
|
|
//--- Position Manager (Phase 1) ---
|
|
|
|
input group "=== Position Manager (Phase 1) ==="
|
|
|
|
input bool PM_EnableBreakEven = true; // Enable break-even move
|
|
|
|
input int PM_BETriggerPips = 15; // Profit in pips to trigger BE
|
|
|
|
input int PM_BEOffsetPips = 2; // Offset beyond BE in pips
|
|
|
|
input bool PM_EnableTrailing = true; // Enable trailing stop
|
|
|
|
input int PM_TrailStartPips = 20; // Start trailing at profit (pips)
|
|
|
|
input int PM_TrailStepPips = 10; // Trail step (pips)
|
|
|
|
input bool PM_EnableTimeExit = true; // Enable max holding time exit
|
|
|
|
input int PM_MaxPositionMinutes = 240; // Max minutes to hold a position
|
|
|
|
input int PM_ATRPeriod = 14; // ATR period for future exits
|
|
|
|
input double PM_ATRExitMultiplier = 2.0; // ATR multiplier for future exits
|
|
|
|
|
2025-08-02 22:17:29 -04:00
|
|
|
// Constants
|
|
|
|
const int MIN_PAPER_TRADES = 10; // Minimum number of paper trades before considering live trading
|
|
|
|
const double PAPER_TRADING_TARGET = 70.0; // Target win rate percentage for paper trading
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Trading Modes |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
enum ENUM_TRADING_MODE
|
|
|
|
{
|
|
|
|
MODE_PAPER, // Paper trading only
|
|
|
|
MODE_HYBRID, // Paper trading with live execution (when conditions met)
|
|
|
|
MODE_LIVE // Live trading (when consistently profitable)
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Market Condition Analysis |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
enum ENUM_MARKET_CONDITION
|
|
|
|
{
|
|
|
|
MARKET_RANGING = 0, // Sideways market
|
|
|
|
MARKET_TREND_UP = 1, // Uptrend
|
|
|
|
MARKET_TREND_DOWN = 2, // Downtrend
|
|
|
|
MARKET_VOLATILE = 3, // High volatility
|
|
|
|
MARKET_HIGH_SPREAD = 4, // High spread
|
|
|
|
MARKET_NORMAL = 5 // Normal conditions
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Trade Record Structure |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
struct STradeRecord
|
|
|
|
{
|
|
|
|
ulong ticket; // Trade ticket
|
|
|
|
string symbol; // Symbol
|
|
|
|
ENUM_ORDER_TYPE type; // Order type
|
|
|
|
double volume; // Volume in lots
|
|
|
|
double openPrice; // Open price
|
|
|
|
double stopLoss; // Stop loss level
|
|
|
|
double takeProfit; // Take profit level
|
|
|
|
datetime openTime; // Open time
|
|
|
|
datetime closeTime; // Close time
|
|
|
|
double closePrice; // Close price
|
|
|
|
double commission; // Commission
|
|
|
|
double swap; // Swap
|
|
|
|
double profit; // Profit/Loss
|
|
|
|
string comment; // Comment
|
|
|
|
bool isLive; // True if live trade, false if paper trade
|
|
|
|
|
|
|
|
// Initialize the structure
|
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
ticket = 0;
|
|
|
|
symbol = "";
|
|
|
|
type = WRONG_VALUE;
|
|
|
|
volume = 0.0;
|
|
|
|
openPrice = 0.0;
|
|
|
|
stopLoss = 0.0;
|
|
|
|
takeProfit = 0.0;
|
|
|
|
openTime = 0;
|
|
|
|
closeTime = 0;
|
|
|
|
closePrice = 0.0;
|
|
|
|
commission = 0.0;
|
|
|
|
swap = 0.0;
|
|
|
|
profit = 0.0;
|
|
|
|
comment = "";
|
|
|
|
isLive = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Trading Statistics Structure |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
struct STradingStats
|
|
|
|
{
|
|
|
|
int totalTrades; // Total number of trades
|
|
|
|
int winningTrades; // Number of winning trades
|
|
|
|
double totalProfit; // Total profit
|
|
|
|
double totalLoss; // Total loss
|
|
|
|
double maxDrawdown; // Maximum drawdown
|
|
|
|
double maxProfit; // Maximum profit
|
|
|
|
double winRate; // Win rate in percentage
|
|
|
|
double profitFactor; // Profit factor (gross profit / gross loss)
|
|
|
|
|
|
|
|
// Initialize the structure
|
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
totalTrades = 0;
|
|
|
|
winningTrades = 0;
|
|
|
|
totalProfit = 0.0;
|
|
|
|
totalLoss = 0.0;
|
|
|
|
maxDrawdown = 0.0;
|
|
|
|
maxProfit = 0.0;
|
|
|
|
winRate = 0.0;
|
|
|
|
profitFactor = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update statistics with a new trade
|
|
|
|
void Update(const STradeRecord &trade)
|
|
|
|
{
|
|
|
|
if(trade.profit > 0)
|
|
|
|
{
|
|
|
|
winningTrades++;
|
|
|
|
totalProfit += trade.profit;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
totalLoss += MathAbs(trade.profit);
|
|
|
|
}
|
|
|
|
|
|
|
|
totalTrades++;
|
|
|
|
|
|
|
|
// Update win rate
|
|
|
|
if(totalTrades > 0)
|
|
|
|
{
|
|
|
|
winRate = (double)winningTrades / totalTrades * 100.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update profit factor
|
|
|
|
if(totalLoss > 0)
|
|
|
|
{
|
|
|
|
profitFactor = totalProfit / totalLoss;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if(totalProfit > 0)
|
|
|
|
{
|
|
|
|
profitFactor = DBL_MAX; // Perfect profit factor (no losses)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update max profit/drawdown
|
|
|
|
if(trade.profit > maxProfit)
|
|
|
|
{
|
|
|
|
maxProfit = trade.profit;
|
|
|
|
}
|
|
|
|
|
|
|
|
double currentDrawdown = (totalLoss > 0) ? (totalLoss / (totalProfit + totalLoss)) * 100.0 : 0.0;
|
|
|
|
if(currentDrawdown > maxDrawdown)
|
|
|
|
{
|
|
|
|
maxDrawdown = currentDrawdown;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset all statistics
|
|
|
|
void Reset()
|
|
|
|
{
|
|
|
|
Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate maximum drawdown for the trading stats
|
|
|
|
double CalculateDrawdown()
|
|
|
|
{
|
|
|
|
if(totalProfit <= 0)
|
|
|
|
return 0.0;
|
|
|
|
double total = totalProfit + MathAbs(totalLoss);
|
|
|
|
return total > 0 ? (MathAbs(totalLoss) / total) * 100.0 : 0.0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Timer handler for multi-symbol scan and position management |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void OnTimer()
|
|
|
|
{
|
|
|
|
if(!EnableMultiSymbol)
|
|
|
|
return;
|
|
|
|
if(g_inTimer)
|
|
|
|
return; // prevent re-entrancy
|
|
|
|
g_inTimer = true;
|
|
|
|
|
|
|
|
static int timerCount = 0; timerCount++;
|
|
|
|
// Optional: portfolio level guards can be added here later
|
|
|
|
int n = ArraySize(g_symbols);
|
|
|
|
for(int i=0; i<n; i++)
|
|
|
|
{
|
|
|
|
string sym = g_symbols[i];
|
|
|
|
if(sym == NULL || sym == "")
|
|
|
|
continue;
|
|
|
|
// Ensure symbol is selected in MarketWatch
|
|
|
|
SymbolSelect(sym, true);
|
|
|
|
// Manage existing positions for this symbol (works for both hedging & netting)
|
|
|
|
ManagePositionsForSymbol(sym);
|
|
|
|
|
|
|
|
// Attempt new entries per symbol when allowed
|
|
|
|
if(!EmergencyHalt && !MasterPause && !PauseOpenNew)
|
|
|
|
{
|
|
|
|
CheckSignalsForSymbol(sym, timerCount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_inTimer = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Build symbol universe (best-effort discovery) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void BuildSymbolUniverse()
|
|
|
|
{
|
|
|
|
// Start with a preset cross-asset core set
|
|
|
|
string bases[] = {
|
|
|
|
"EURUSD","GBPUSD","USDJPY","AUDUSD","USDCAD",
|
|
|
|
"XAUUSD","XAGUSD",
|
|
|
|
"XTIUSD","XBRUSD","WTICOUSD", // energies variants
|
|
|
|
"US500","US30","US100","GER40","UK100","JPN225",
|
|
|
|
"BTCUSD","ETHUSD"
|
|
|
|
};
|
|
|
|
|
|
|
|
// Append extra symbols from CSV
|
|
|
|
string extra[]; ParseCSVToArray(ExtraSymbolsCSV, extra);
|
|
|
|
|
|
|
|
// Build candidate list
|
|
|
|
string candidates[];
|
|
|
|
int count = 0;
|
|
|
|
for(int i=0;i<ArraySize(bases);i++)
|
|
|
|
{
|
|
|
|
ArrayResize(candidates, ++count);
|
|
|
|
candidates[count-1] = bases[i];
|
|
|
|
}
|
|
|
|
for(int j=0;j<ArraySize(extra);j++)
|
|
|
|
{
|
|
|
|
if(StringLen(extra[j])==0) continue;
|
|
|
|
ArrayResize(candidates, ++count);
|
|
|
|
candidates[count-1] = StringTrim(extra[j]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map candidates to actual broker symbols using best-effort matching
|
|
|
|
ArrayResize(g_symbols, 0);
|
|
|
|
int selected_total = SymbolsTotal(true);
|
|
|
|
int total = (selected_total>0 ? selected_total : SymbolsTotal(false));
|
|
|
|
for(int k=0;k<count;k++)
|
|
|
|
{
|
|
|
|
string base = candidates[k];
|
|
|
|
string matched = FindBestSymbolMatch(base, total, selected_total>0);
|
|
|
|
if(matched != "")
|
|
|
|
{
|
|
|
|
int newSize = ArraySize(g_symbols) + 1;
|
|
|
|
ArrayResize(g_symbols, newSize);
|
|
|
|
g_symbols[newSize-1] = matched;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deduplicate
|
|
|
|
UniqueStringsInPlace(g_symbols);
|
|
|
|
|
|
|
|
// Log results
|
|
|
|
Print("BuildSymbolUniverse: ", ArraySize(g_symbols), " symbols prepared.");
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Manage positions for a specific symbol |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void ManagePositionsForSymbol(const string symbol)
|
|
|
|
{
|
|
|
|
// Iterate all positions and apply PM rules to those matching 'symbol'
|
|
|
|
int totalPos = (int)PositionsTotal();
|
|
|
|
for(int i=totalPos-1; i>=0; --i)
|
|
|
|
{
|
|
|
|
if(!m_position.SelectByIndex(i))
|
|
|
|
continue;
|
|
|
|
if(m_position.Symbol() != symbol)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ulong ticket = m_position.Ticket();
|
|
|
|
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)m_position.PositionType();
|
|
|
|
double openPrice = m_position.PriceOpen();
|
|
|
|
double current = m_position.PriceCurrent();
|
|
|
|
double sl = m_position.StopLoss();
|
|
|
|
double tp = m_position.TakeProfit();
|
|
|
|
datetime openTime = (datetime)m_position.Time();
|
|
|
|
|
|
|
|
// Time-based exit
|
|
|
|
if(PM_EnableTimeExit && PM_MaxPositionMinutes > 0)
|
|
|
|
{
|
|
|
|
if((int)((TimeCurrent() - openTime)/60) >= PM_MaxPositionMinutes)
|
|
|
|
{
|
|
|
|
if(m_trade.PositionClose(ticket))
|
|
|
|
Print("PM: Time-exit closed position ", ticket, " on ", symbol);
|
|
|
|
else
|
|
|
|
Print("PM: Failed to close (time-exit) ", ticket, " ret=", (int)m_trade.ResultRetcode(), " (", m_trade.ResultRetcodeDescription(), ")");
|
|
|
|
continue; // move next
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Break-even & Trailing
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
int ppp = PointsPerPip(symbol);
|
|
|
|
double profitPips = 0.0;
|
|
|
|
if(type == POSITION_TYPE_BUY)
|
|
|
|
profitPips = (current - openPrice) / point / ppp;
|
|
|
|
else if(type == POSITION_TYPE_SELL)
|
|
|
|
profitPips = (openPrice - current) / point / ppp;
|
|
|
|
|
|
|
|
bool modified = false;
|
|
|
|
double newSL = sl;
|
|
|
|
|
|
|
|
// Break-even
|
|
|
|
if(PM_EnableBreakEven && profitPips >= PM_BETriggerPips)
|
|
|
|
{
|
|
|
|
if(type == POSITION_TYPE_BUY)
|
|
|
|
{
|
|
|
|
double be = openPrice + (PM_BEOffsetPips * ppp * point);
|
|
|
|
if(sl < be)
|
|
|
|
{
|
|
|
|
newSL = MathMax(newSL, be);
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(type == POSITION_TYPE_SELL)
|
|
|
|
{
|
|
|
|
double be = openPrice - (PM_BEOffsetPips * ppp * point);
|
|
|
|
if(sl == 0.0 || sl > be)
|
|
|
|
{
|
|
|
|
newSL = MathMin((sl==0.0?be:sl), be);
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trailing
|
|
|
|
if(PM_EnableTrailing && profitPips >= PM_TrailStartPips)
|
|
|
|
{
|
|
|
|
if(type == POSITION_TYPE_BUY)
|
|
|
|
{
|
|
|
|
double trailSL = current - (PM_TrailStepPips * ppp * point);
|
|
|
|
if(trailSL > newSL && trailSL < current)
|
|
|
|
{
|
|
|
|
newSL = trailSL;
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(type == POSITION_TYPE_SELL)
|
|
|
|
{
|
|
|
|
double trailSL = current + (PM_TrailStepPips * ppp * point);
|
|
|
|
if((newSL==0.0 || trailSL < newSL) && trailSL > current)
|
|
|
|
{
|
|
|
|
newSL = trailSL;
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(modified)
|
|
|
|
{
|
|
|
|
if(ModifyPositionSLTP(ticket, symbol, newSL, tp))
|
|
|
|
Print("PM: Modified SL for ", symbol, " ticket=", ticket, " SL=", newSL, " TP=", tp);
|
|
|
|
else
|
|
|
|
Print("PM: Failed SL modify for ", symbol, " ticket=", ticket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: modify position SL/TP by ticket (hedging & netting) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool ModifyPositionSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
|
|
|
|
{
|
|
|
|
MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res);
|
|
|
|
req.action = TRADE_ACTION_SLTP;
|
|
|
|
req.position = ticket;
|
|
|
|
req.symbol = symbol;
|
|
|
|
req.sl = sl;
|
|
|
|
req.tp = tp;
|
|
|
|
bool ok = OrderSend(req, res);
|
|
|
|
if(!ok)
|
|
|
|
{
|
|
|
|
Print("OrderSend SLTP failed for ", symbol, " ticket=", ticket, " ret=", (int)res.retcode);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_DONE_PARTIAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: determine points per pip for a symbol |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
int PointsPerPip(const string symbol)
|
|
|
|
{
|
|
|
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
|
|
// Common heuristic: 5-digit (or 3-digit JPY) symbols have 10 points per pip
|
|
|
|
if(digits == 3 || digits == 5)
|
|
|
|
return 10;
|
|
|
|
return 1; // otherwise treat 1 point as 1 pip-equivalent
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: CSV parser |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void ParseCSVToArray(const string csv, string &out[])
|
|
|
|
{
|
|
|
|
ArrayResize(out, 0);
|
|
|
|
if(StringLen(csv) == 0) return;
|
|
|
|
int start = 0; int len = StringLen(csv);
|
|
|
|
while(start < len)
|
|
|
|
{
|
|
|
|
int comma = StringFind(csv, ",", start);
|
|
|
|
if(comma == -1) comma = len;
|
|
|
|
string token = StringSubstr(csv, start, comma - start);
|
|
|
|
token = StringTrim(token);
|
|
|
|
int n = ArraySize(out) + 1; ArrayResize(out, n); out[n-1] = token;
|
|
|
|
start = comma + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: trim spaces |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string StringTrim(const string s)
|
|
|
|
{
|
|
|
|
string r = s;
|
|
|
|
// left trim
|
|
|
|
while(StringLen(r)>0 && StringGetCharacter(r,0)==' ') r = StringSubstr(r,1);
|
|
|
|
// right trim
|
|
|
|
while(StringLen(r)>0 && StringGetCharacter(r,StringLen(r)-1)==' ') r = StringSubstr(r,0,StringLen(r)-1);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: best-effort symbol match |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string FindBestSymbolMatch(const string base, int total, bool onlySelected)
|
|
|
|
{
|
|
|
|
// 1) Exact match among selected symbols
|
|
|
|
int n = SymbolsTotal(onlySelected);
|
|
|
|
for(int i=0; i<n; i++)
|
|
|
|
{
|
|
|
|
string s = SymbolName(i, onlySelected);
|
|
|
|
if(s == base) return s;
|
|
|
|
}
|
|
|
|
// 2) Substring contains (suffix/prefix tolerant)
|
|
|
|
for(int i=0; i<n; i++)
|
|
|
|
{
|
|
|
|
string s = SymbolName(i, onlySelected);
|
|
|
|
if(StringFind(s, base) >= 0) return s;
|
|
|
|
}
|
|
|
|
// 3) Fallback: search all if onlySelected was used
|
|
|
|
if(onlySelected)
|
|
|
|
{
|
|
|
|
int m = SymbolsTotal(false);
|
|
|
|
for(int j=0; j<m; j++)
|
|
|
|
{
|
|
|
|
string s2 = SymbolName(j, false);
|
|
|
|
if(s2 == base) return s2;
|
|
|
|
}
|
|
|
|
for(int j=0; j<m; j++)
|
|
|
|
{
|
|
|
|
string s2 = SymbolName(j, false);
|
|
|
|
if(StringFind(s2, base) >= 0) return s2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: deduplicate string array in-place |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void UniqueStringsInPlace(string &arr[])
|
|
|
|
{
|
|
|
|
int n = ArraySize(arr);
|
|
|
|
for(int i=0; i<n; i++)
|
|
|
|
{
|
|
|
|
for(int j=i+1; j<n; j++)
|
|
|
|
{
|
|
|
|
if(arr[i] == arr[j])
|
|
|
|
{
|
|
|
|
// shift left
|
|
|
|
for(int k=j; k<n-1; k++) arr[k] = arr[k+1];
|
|
|
|
n--; ArrayResize(arr, n); j--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| News filter stub (Phase 1) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool IsNewsWindow(const string symbol)
|
|
|
|
{
|
|
|
|
// Phase 1: placeholder returns false. Will integrate Economic Calendar later.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: count open positions for a symbol |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
int CountOpenPositions(const string symbol)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
int total = (int)PositionsTotal();
|
|
|
|
for(int i=0;i<total;i++)
|
|
|
|
{
|
|
|
|
if(!m_position.SelectByIndex(i)) continue;
|
|
|
|
if(m_position.Symbol() == symbol) count++;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Helper: asset class detection from name/path |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string DetectAssetClass(const string symbol)
|
|
|
|
{
|
|
|
|
string path;
|
|
|
|
if(SymbolInfoString(symbol, SYMBOL_PATH, path))
|
|
|
|
{
|
|
|
|
// Normalize path to upper for matching
|
|
|
|
StringToUpper(path);
|
|
|
|
if(StringFind(path, "FOREX")>=0 || StringFind(path, "FX")>=0) return "FX";
|
|
|
|
if(StringFind(path, "METAL")>=0) return "METAL";
|
|
|
|
if(StringFind(path, "INDEX")>=0 || StringFind(path, "INDICES")>=0) return "INDEX";
|
|
|
|
if(StringFind(path, "ENERGY")>=0 || StringFind(path, "OIL")>=0) return "ENERGY";
|
|
|
|
if(StringFind(path, "CRYPTO")>=0 || StringFind(path, "DIGITAL")>=0) return "CRYPTO";
|
|
|
|
}
|
|
|
|
// Heuristic fallback by symbol
|
|
|
|
string s = symbol;
|
|
|
|
StringToUpper(s);
|
|
|
|
if(StringFind(s, "XAU")==0 || StringFind(s, "XAG")==0) return "METAL";
|
|
|
|
if(StringFind(s, "US500")==0 || StringFind(s, "US30")==0 || StringFind(s, "US100")==0 || StringFind(s, "GER")==0 || StringFind(s, "UK")==0 || StringFind(s, "JPN")==0) return "INDEX";
|
|
|
|
if(StringFind(s, "XTI")==0 || StringFind(s, "XBR")==0 || StringFind(s, "WTI")>=0 || StringFind(s, "BRENT")>=0) return "ENERGY";
|
|
|
|
if(StringFind(s, "BTC")==0 || StringFind(s, "ETH")==0) return "CRYPTO";
|
|
|
|
// Default FX if looks like 6-letter pair (alphabetic at 0 and 5)
|
|
|
|
if(StringLen(s)>=6)
|
|
|
|
{
|
|
|
|
int c0 = StringGetCharacter(s,0);
|
|
|
|
int c5 = StringGetCharacter(s,5);
|
|
|
|
bool a0 = ((c0>='A' && c0<='Z'));
|
|
|
|
bool a5 = ((c5>='A' && c5<='Z'));
|
|
|
|
if(a0 && a5) return "FX";
|
|
|
|
}
|
|
|
|
return "OTHER";
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsClassEnabled(const string assetClass)
|
|
|
|
{
|
|
|
|
if(assetClass=="FX") return EnableFX;
|
|
|
|
if(assetClass=="METAL") return EnableMetals;
|
|
|
|
if(assetClass=="INDEX") return EnableIndices;
|
|
|
|
if(assetClass=="ENERGY") return EnableEnergies;
|
|
|
|
if(assetClass=="CRYPTO") return EnableCrypto;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
double ClassRiskMult(const string assetClass)
|
|
|
|
{
|
|
|
|
if(assetClass=="FX") return RiskMultFX;
|
|
|
|
if(assetClass=="METAL") return RiskMultMetals;
|
|
|
|
if(assetClass=="INDEX") return RiskMultIndices;
|
|
|
|
if(assetClass=="ENERGY") return RiskMultEnergies;
|
|
|
|
if(assetClass=="CRYPTO") return RiskMultCrypto;
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsWithinClassSession(const string assetClass, datetime t)
|
|
|
|
{
|
|
|
|
MqlDateTime dt; TimeToStruct(t, dt);
|
|
|
|
int hour = (int)dt.hour;
|
|
|
|
if(assetClass=="FX") return (hour>=FX_SessionStart && hour<FX_SessionEnd);
|
|
|
|
if(assetClass=="METAL") return (hour>=Metals_SessionStart && hour<Metals_SessionEnd);
|
|
|
|
if(assetClass=="INDEX") return (hour>=Indices_SessionStart && hour<Indices_SessionEnd);
|
|
|
|
if(assetClass=="ENERGY") return (hour>=Energies_SessionStart && hour<Energies_SessionEnd);
|
|
|
|
if(assetClass=="CRYPTO") return (hour>=Crypto_SessionStart && hour<Crypto_SessionEnd);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Symbol-aware indicator checks (simple MA cross, like existing) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CheckBuySignalForSymbol(const string symbol)
|
|
|
|
{
|
|
|
|
// Use same periods as existing handles represent (fast=4, slow=10)
|
|
|
|
int fastPeriod = 4;
|
|
|
|
int slowPeriod = 10;
|
|
|
|
int handleFast = iMA(symbol, PERIOD_CURRENT, fastPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
int handleSlow = iMA(symbol, PERIOD_CURRENT, slowPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
if(handleFast==INVALID_HANDLE || handleSlow==INVALID_HANDLE) return false;
|
|
|
|
double fast[]; double slow[]; ArraySetAsSeries(fast,true); ArraySetAsSeries(slow,true); ArrayResize(fast,3); ArrayResize(slow,3);
|
|
|
|
bool ok = (CopyBuffer(handleFast,0,0,3,fast)==3 && CopyBuffer(handleSlow,0,0,3,slow)==3);
|
|
|
|
IndicatorRelease(handleFast); IndicatorRelease(handleSlow);
|
|
|
|
if(!ok) return false;
|
|
|
|
if(fast[1] <= slow[1] && fast[0] > slow[0]) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckSellSignalForSymbol(const string symbol)
|
|
|
|
{
|
|
|
|
int fastPeriod = 4;
|
|
|
|
int slowPeriod = 10;
|
|
|
|
int handleFast = iMA(symbol, PERIOD_CURRENT, fastPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
int handleSlow = iMA(symbol, PERIOD_CURRENT, slowPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
if(handleFast==INVALID_HANDLE || handleSlow==INVALID_HANDLE) return false;
|
|
|
|
double fast[]; double slow[]; ArraySetAsSeries(fast,true); ArraySetAsSeries(slow,true); ArrayResize(fast,3); ArrayResize(slow,3);
|
|
|
|
bool ok = (CopyBuffer(handleFast,0,0,3,fast)==3 && CopyBuffer(handleSlow,0,0,3,slow)==3);
|
|
|
|
IndicatorRelease(handleFast); IndicatorRelease(handleSlow);
|
|
|
|
if(!ok) return false;
|
|
|
|
if(fast[1] >= slow[1] && fast[0] < slow[0]) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Position sizing for a symbol (risk % uses class multiplier) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CalculatePositionSizeForSymbol(const string symbol, double stopLossPips, double riskPercent)
|
|
|
|
{
|
|
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
string cls = DetectAssetClass(symbol);
|
|
|
|
double rp = riskPercent * ClassRiskMult(cls);
|
|
|
|
rp = MathMax(0.01, rp); // at least 0.01%
|
|
|
|
double riskAmount = balance * (rp/100.0);
|
|
|
|
|
|
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
int ppp = PointsPerPip(symbol);
|
|
|
|
double slPoints = stopLossPips * ppp * point;
|
|
|
|
if(slPoints <= 0.0 || tickValue<=0.0) return 0.0;
|
|
|
|
// Approximate: cost per lot at SL
|
|
|
|
double ticksAtSL = slPoints / tickSize;
|
|
|
|
double costPerLot = ticksAtSL * tickValue;
|
|
|
|
if(costPerLot <= 0.0) return 0.0;
|
|
|
|
|
|
|
|
double lot = riskAmount / costPerLot;
|
|
|
|
// Normalize to symbol lot step/min/max
|
|
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
lot = MathFloor(lot/lotStep)*lotStep;
|
|
|
|
lot = MathMax(minLot, MathMin(maxLot, lot));
|
|
|
|
return lot;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Place market order for a specific symbol |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool ExecuteMarketOrderForSymbol(const string symbol, ENUM_ORDER_TYPE orderType, double lots, double stopLossPips, double takeProfitPips, string comment)
|
|
|
|
{
|
|
|
|
if(!g_tradingEnabled) return false;
|
|
|
|
bool isLive = (g_tradingMode == MODE_LIVE || (g_tradingMode == MODE_HYBRID && MathRand() % 2 == 0));
|
|
|
|
if(!isLive)
|
|
|
|
{
|
|
|
|
MqlTick t; if(!SymbolInfoTick(symbol, t)) return false;
|
|
|
|
// record paper trade
|
|
|
|
STradeRecord trade={}; trade.Init();
|
|
|
|
trade.openTime = TimeCurrent(); trade.symbol = symbol; trade.type = orderType; trade.volume = lots; trade.isLive = false;
|
|
|
|
int ppp = PointsPerPip(symbol);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
if(orderType==ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
trade.openPrice=t.ask; trade.stopLoss = trade.openPrice - stopLossPips * ppp * point; trade.takeProfit = trade.openPrice + takeProfitPips * ppp * point;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trade.openPrice=t.bid; trade.stopLoss = trade.openPrice + stopLossPips * ppp * point; trade.takeProfit = trade.openPrice - takeProfitPips * ppp * point;
|
|
|
|
}
|
|
|
|
RecordTrade(trade);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
double price = (orderType==ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
int ppp = PointsPerPip(symbol);
|
|
|
|
double sl = (orderType==ORDER_TYPE_BUY) ? price - stopLossPips * ppp * point : price + stopLossPips * ppp * point;
|
|
|
|
double tp = (orderType==ORDER_TYPE_BUY) ? price + takeProfitPips * ppp * point : price - takeProfitPips * ppp * point;
|
|
|
|
bool ok = (orderType==ORDER_TYPE_BUY) ? m_trade.Buy(lots, symbol, price, sl, tp, comment) : m_trade.Sell(lots, symbol, price, sl, tp, comment);
|
|
|
|
if(ok)
|
|
|
|
{
|
|
|
|
STradeRecord tr={}; tr.Init(); tr.ticket=m_trade.ResultOrder(); tr.openTime=TimeCurrent(); tr.type=orderType; tr.symbol=symbol; tr.volume=lots; tr.openPrice=price; tr.stopLoss=sl; tr.takeProfit=tp; tr.isLive=true; RecordTrade(tr);
|
|
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Symbol-aware signal + entry orchestrator |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CheckSignalsForSymbol(const string symbol, int tickOrTimerCount)
|
|
|
|
{
|
|
|
|
// Class gating
|
|
|
|
string cls = DetectAssetClass(symbol);
|
|
|
|
if(!IsClassEnabled(cls))
|
|
|
|
{
|
|
|
|
if(tickOrTimerCount % 60 == 0) Print("Class disabled for ", symbol, " (", cls, ")");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(!IsWithinClassSession(cls, TimeCurrent()))
|
|
|
|
{
|
|
|
|
if(tickOrTimerCount % 60 == 0) Print("Session gate for ", symbol, " (", cls, ")");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// News gating
|
|
|
|
if(UseNewsFilter && IsNewsWindow(symbol))
|
|
|
|
{
|
|
|
|
if(tickOrTimerCount % 60 == 0) Print("News gate for ", symbol);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Per-symbol open cap
|
|
|
|
int openCount = CountOpenPositions(symbol);
|
|
|
|
if(openCount >= MAX_OPEN_TRADES)
|
|
|
|
{
|
|
|
|
if(tickOrTimerCount % 60 == 0) Print("Open trades cap reached for ", symbol, " (", openCount, "/", MAX_OPEN_TRADES, ")");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool longSignal = CheckBuySignalForSymbol(symbol);
|
|
|
|
bool shortSignal = CheckSellSignalForSymbol(symbol);
|
|
|
|
if(tickOrTimerCount % 50 == 0)
|
|
|
|
Print("[", symbol, "] Signals - Long:", (longSignal?"YES":"no"), " Short:", (shortSignal?"YES":"no"));
|
|
|
|
|
|
|
|
if(longSignal)
|
|
|
|
{
|
|
|
|
double stopLoss = g_strategyManager.GetStopLossPips(true);
|
|
|
|
double takeProfit = g_strategyManager.GetTakeProfitPips(true);
|
|
|
|
double lotSize = CalculatePositionSizeForSymbol(symbol, stopLoss, InpMaxRiskPerTrade);
|
|
|
|
if(lotSize > 0)
|
|
|
|
{
|
|
|
|
if(ExecuteMarketOrderForSymbol(symbol, ORDER_TYPE_BUY, lotSize, stopLoss, takeProfit, "Strategy Buy"))
|
|
|
|
Print("[", symbol, "] BUY executed, lot=", lotSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(shortSignal && !longSignal)
|
|
|
|
{
|
|
|
|
double stopLoss = g_strategyManager.GetStopLossPips(false);
|
|
|
|
double takeProfit = g_strategyManager.GetTakeProfitPips(false);
|
|
|
|
double lotSize = CalculatePositionSizeForSymbol(symbol, stopLoss, InpMaxRiskPerTrade);
|
|
|
|
if(lotSize > 0)
|
|
|
|
{
|
|
|
|
if(ExecuteMarketOrderForSymbol(symbol, ORDER_TYPE_SELL, lotSize, stopLoss, takeProfit, "Strategy Sell"))
|
|
|
|
Print("[", symbol, "] SELL executed, lot=", lotSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-02 22:17:29 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Learning Parameters Structure |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
struct SLearningParams
|
|
|
|
{
|
|
|
|
int maFastPeriod; // Fast MA period
|
|
|
|
int maSlowPeriod; // Slow MA period
|
|
|
|
double stopLossPips; // Stop loss in pips
|
|
|
|
double takeProfitPips; // Take profit in pips
|
|
|
|
double riskPerTrade; // Risk per trade as percentage of balance
|
|
|
|
|
|
|
|
// Initialize the structure
|
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
maFastPeriod = 10;
|
|
|
|
maSlowPeriod = 20;
|
|
|
|
stopLossPips = 30.0;
|
|
|
|
takeProfitPips = 60.0;
|
|
|
|
riskPerTrade = 1.0; // 1% risk per trade by default
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Market Condition Structure |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
struct SMarketCondition
|
|
|
|
{
|
|
|
|
ENUM_MARKET_CONDITION condition; // Current market condition
|
|
|
|
double strength; // 0-100% strength of the condition
|
|
|
|
double volatility; // Current market volatility (ATR based)
|
|
|
|
double trend; // Current trend strength (-1 to 1)
|
|
|
|
double volume; // Volume indicator value
|
|
|
|
double spread; // Current spread in points
|
|
|
|
datetime lastUpdate; // Timestamp of last update
|
|
|
|
|
|
|
|
// Default constructor
|
|
|
|
SMarketCondition()
|
|
|
|
{
|
|
|
|
Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy constructor
|
|
|
|
SMarketCondition(const SMarketCondition &other)
|
|
|
|
{
|
|
|
|
condition = other.condition;
|
|
|
|
strength = other.strength;
|
|
|
|
volatility = other.volatility;
|
|
|
|
trend = other.trend;
|
|
|
|
volume = other.volume;
|
|
|
|
spread = other.spread;
|
|
|
|
lastUpdate = other.lastUpdate;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assignment operator
|
|
|
|
void operator=(const SMarketCondition &other)
|
|
|
|
{
|
|
|
|
// No need for self-assignment check in MQL5 structs
|
|
|
|
condition = other.condition;
|
|
|
|
strength = other.strength;
|
|
|
|
volatility = other.volatility;
|
|
|
|
trend = other.trend;
|
|
|
|
volume = other.volume;
|
|
|
|
spread = other.spread;
|
|
|
|
lastUpdate = other.lastUpdate;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the structure
|
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
condition = MARKET_NORMAL;
|
|
|
|
strength = 0;
|
|
|
|
volatility = 0;
|
|
|
|
trend = 0;
|
|
|
|
volume = 0;
|
|
|
|
spread = 0;
|
|
|
|
lastUpdate = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
ENUM_TRADING_MODE g_tradingMode = MODE_PAPER; // Current trading mode
|
|
|
|
STradeRecord g_tradeHistory[]; // Dynamic array for trade history
|
|
|
|
STradingStats g_paperStats; // Paper trading statistics
|
|
|
|
STradingStats g_liveStats; // Live trading statistics
|
|
|
|
SLearningParams g_learningParams; // Learning parameters
|
|
|
|
int g_currentOpenTrades = 0; // Current number of open trades
|
|
|
|
bool g_tradingEnabled = true; // Global trading flag
|
|
|
|
CTrade m_trade; // Trade object for order execution
|
2025-08-16 11:53:49 -04:00
|
|
|
CPositionInfo m_position; // Position info helper (from PositionInfo.mqh)
|
2025-08-02 22:17:29 -04:00
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
//--- Multi-Symbol State ---
|
|
|
|
string g_symbols[]; // Discovered/selected symbols to manage
|
|
|
|
bool g_inTimer = false; // Reentrancy guard for OnTimer
|
2025-08-02 22:17:29 -04:00
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// Indicator handles
|
|
|
|
int handle_iMA_4 = INVALID_HANDLE; // Handle for 4-period MA
|
|
|
|
int handle_iMA_5 = INVALID_HANDLE; // Handle for 10-period MA
|
2025-08-02 22:17:29 -04:00
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// Input parameters
|
|
|
|
input int LearningWindow = 20; // Number of trades to analyze for learning
|
|
|
|
input double MinWinRateForLive = 80.0; // Minimum win rate % to enable live trading
|
|
|
|
input double MinLiveWinRate = 70.0; // Minimum win rate to stay in live mode
|
|
|
|
input double MaxDrawdownPct = 20.0; // Maximum drawdown % before disabling live trading
|
|
|
|
input int MAX_OPEN_TRADES = 5; // Maximum number of open trades allowed
|
2025-08-02 22:17:29 -04:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Global variables for paper trading timing and state |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
datetime g_lastSignalCheck = 0; // Last time signals were checked (1-minute interval)
|
|
|
|
datetime g_lastLogUpdate = 0; // Last time logs were updated (15-minute interval)
|
|
|
|
int g_signalsSentThisMinute = 0; // Number of signals sent in current minute
|
|
|
|
const int MAX_SIGNALS_PER_MINUTE = 10; // Maximum signals to send per minute
|
|
|
|
const int SIGNAL_CHECK_INTERVAL = 60; // Check signals every 60 seconds
|
|
|
|
const int LOG_UPDATE_INTERVAL = 900; // Update logs every 15 minutes (900 seconds)
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Draw a buy signal arrow on the chart |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void DrawBuySignal()
|
|
|
|
{
|
|
|
|
string objName = "BuySignal_" + IntegerToString(TimeCurrent());
|
|
|
|
|
|
|
|
// Create an up arrow for buy signals
|
|
|
|
if(ObjectCreate(0, objName, OBJ_ARROW_UP, 0, TimeCurrent(), iLow(_Symbol, _Period, 0)))
|
|
|
|
{
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrLime);
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
|
|
|
|
ObjectSetString(0, objName, OBJPROP_TOOLTIP, "Paper Buy Signal\n" + TimeToString(TimeCurrent()));
|
|
|
|
|
|
|
|
// Set the arrow to be visible on all timeframes
|
|
|
|
long chartId = ChartID();
|
|
|
|
ChartRedraw(chartId);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Print("Failed to create buy signal arrow. Error: ", GetLastError());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Draw a sell signal arrow on the chart |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void DrawSellSignal()
|
|
|
|
{
|
|
|
|
string objName = "SellSignal_" + IntegerToString(TimeCurrent());
|
|
|
|
|
|
|
|
// Create a down arrow for sell signals
|
|
|
|
if(ObjectCreate(0, objName, OBJ_ARROW_DOWN, 0, TimeCurrent(), iHigh(_Symbol, _Period, 0)))
|
|
|
|
{
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
|
|
|
|
ObjectSetString(0, objName, OBJPROP_TOOLTIP, "Paper Sell Signal\n" + TimeToString(TimeCurrent()));
|
|
|
|
|
|
|
|
// Set the arrow to be visible on all timeframes
|
|
|
|
long chartId = ChartID();
|
|
|
|
ChartRedraw(chartId);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Print("Failed to create sell signal arrow. Error: ", GetLastError());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Clean up old signal objects to prevent chart clutter |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CleanupOldSignals()
|
|
|
|
{
|
|
|
|
datetime oneWeekAgo = TimeCurrent() - 7 * 24 * 60 * 60; // One week ago
|
|
|
|
|
|
|
|
for(int i = ObjectsTotal(0, 0, -1) - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
string name = ObjectName(0, i, 0, -1);
|
|
|
|
|
|
|
|
// Check if this is one of our signal objects
|
|
|
|
if(StringFind(name, "BuySignal_") == 0 || StringFind(name, "SellSignal_") == 0)
|
|
|
|
{
|
|
|
|
// Get the time from the object name
|
|
|
|
string timeStr = "";
|
|
|
|
if(StringFind(name, "BuySignal_") == 0)
|
|
|
|
timeStr = StringSubstr(name, 10);
|
|
|
|
else if(StringFind(name, "SellSignal_") == 0)
|
|
|
|
timeStr = StringSubstr(name, 11);
|
|
|
|
|
|
|
|
// Convert to datetime and check if it's older than one week
|
|
|
|
datetime signalTime = (datetime)StringToInteger(timeStr);
|
|
|
|
if(signalTime < oneWeekAgo)
|
|
|
|
{
|
|
|
|
ObjectDelete(0, name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Redraw the chart to update the display
|
|
|
|
ChartRedraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Rotate log files to prevent them from growing too large |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void RotateLogs()
|
|
|
|
{
|
|
|
|
string log_path = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Logs\\";
|
|
|
|
string log_file = log_path + "escape.log";
|
|
|
|
|
|
|
|
// Check if log file exists and is too large
|
|
|
|
int handle = FileOpen(log_file, FILE_READ|FILE_BIN|FILE_COMMON);
|
|
|
|
if(handle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
ulong file_size = FileSize(handle) / 1024; // Size in KB
|
|
|
|
FileClose(handle);
|
|
|
|
|
|
|
|
if(file_size >= (ulong)MaxLogSizeKB)
|
|
|
|
{
|
|
|
|
// Delete oldest log file if we've reached max number of logs
|
|
|
|
string oldest_log = log_path + "escape_" + IntegerToString(MaxLogFiles-1) + ".log";
|
|
|
|
if(FileIsExist(oldest_log, FILE_COMMON))
|
|
|
|
{
|
|
|
|
FileDelete(oldest_log, FILE_COMMON);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rotate existing log files
|
|
|
|
for(int i = MaxLogFiles-2; i >= 0; i--)
|
|
|
|
{
|
|
|
|
string src = (i == 0) ? log_file : log_path + "escape_" + IntegerToString(i) + ".log";
|
|
|
|
string dst = log_path + "escape_" + IntegerToString(i+1) + ".log";
|
|
|
|
|
|
|
|
if(FileIsExist(src, FILE_COMMON))
|
|
|
|
{
|
|
|
|
if(FileIsExist(dst, FILE_COMMON))
|
|
|
|
{
|
|
|
|
FileDelete(dst, FILE_COMMON);
|
|
|
|
}
|
|
|
|
FileMove(src, 0, dst, FILE_COMMON);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new empty log file
|
|
|
|
int handle = FileOpen("escape.log", FILE_WRITE|FILE_TXT|FILE_COMMON);
|
|
|
|
if(handle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
FileWriteString(handle, "Log file rotated at " + TimeToString(TimeCurrent()) + "\r\n");
|
|
|
|
FileClose(handle);
|
|
|
|
}
|
|
|
|
Print("Log file rotated. Current size: ", file_size, "KB");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Print(string message) {
|
|
|
|
// Rotate logs if needed (check every 100 prints to avoid performance hit)
|
|
|
|
static int printCount = 0;
|
|
|
|
if(++printCount % 100 == 0) {
|
|
|
|
RotateLogs();
|
|
|
|
}
|
|
|
|
|
|
|
|
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS);
|
|
|
|
printf("%s: %s", timestamp, message);
|
|
|
|
|
|
|
|
// Also write to log file
|
|
|
|
int handle = FileOpen("escape.log", FILE_READ|FILE_WRITE|FILE_TXT|FILE_COMMON);
|
|
|
|
if(handle != INVALID_HANDLE) {
|
|
|
|
FileSeek(handle, 0, SEEK_END);
|
|
|
|
FileWriteString(handle, timestamp + ": " + message + "\r\n");
|
|
|
|
FileClose(handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Expert initialization function |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
int OnInit()
|
|
|
|
{
|
|
|
|
// Rotate logs if needed
|
|
|
|
RotateLogs();
|
|
|
|
|
|
|
|
// Initialize trading objects
|
|
|
|
m_trade.SetExpertMagicNumber(123456);
|
|
|
|
m_trade.SetDeviationInPoints(10);
|
|
|
|
m_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
|
|
|
|
|
|
// Initialize learning parameters with default values if needed
|
|
|
|
g_learningParams.Init();
|
|
|
|
|
|
|
|
// Set default MA periods if not already set
|
|
|
|
if(g_learningParams.maFastPeriod < 1) g_learningParams.maFastPeriod = 5;
|
|
|
|
if(g_learningParams.maSlowPeriod <= g_learningParams.maFastPeriod)
|
|
|
|
g_learningParams.maSlowPeriod = g_learningParams.maFastPeriod * 2;
|
|
|
|
|
|
|
|
Print("Initializing indicators with MA periods: Fast=", g_learningParams.maFastPeriod,
|
|
|
|
", Slow=", g_learningParams.maSlowPeriod);
|
|
|
|
|
|
|
|
// Initialize indicator handles with validated periods
|
|
|
|
handle_iMA_4 = iMA(Symbol(), 0, g_learningParams.maFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
handle_iMA_5 = iMA(Symbol(), 0, g_learningParams.maSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
|
|
|
|
// Check if indicators were created successfully and have enough data
|
|
|
|
if(handle_iMA_4 == INVALID_HANDLE || handle_iMA_5 == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
int error = GetLastError();
|
|
|
|
Print("Error creating indicators. Error code: ", error);
|
|
|
|
if(error == 4002)
|
|
|
|
{
|
|
|
|
Print("Error 4002: Invalid parameters for iMA. Please check the MA periods.");
|
|
|
|
}
|
|
|
|
return(INIT_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for indicators to calculate
|
|
|
|
int retries = 0;
|
|
|
|
while(BarsCalculated(handle_iMA_4) == 0 || BarsCalculated(handle_iMA_5) == 0)
|
|
|
|
{
|
|
|
|
retries++;
|
|
|
|
if(retries > 10)
|
|
|
|
{
|
|
|
|
Print("Error: Indicators not ready after 10 retries");
|
|
|
|
return(INIT_FAILED);
|
|
|
|
}
|
|
|
|
Sleep(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
Print("Indicators initialized successfully");
|
|
|
|
|
|
|
|
// Initialize and add trading strategies
|
|
|
|
if(!AddStrategies())
|
|
|
|
{
|
|
|
|
Print("Failed to initialize trading strategies");
|
|
|
|
return(INIT_FAILED);
|
|
|
|
}
|
|
|
|
Print("Trading strategies initialized successfully");
|
|
|
|
Print("Total strategies loaded: ", g_strategyManager.GetTotalStrategies());
|
|
|
|
|
|
|
|
// Initialize trading statistics
|
|
|
|
g_paperStats.Init();
|
|
|
|
g_liveStats.Init();
|
|
|
|
|
|
|
|
// Load any saved state
|
|
|
|
LoadTradingState();
|
|
|
|
|
|
|
|
Print("EA initialized in ", EnumToString(g_tradingMode), " mode");
|
|
|
|
Print("Learning Window: ", LearningWindow, " trades");
|
|
|
|
Print("Minimum Win Rate for Live: ", MinWinRateForLive, "%");
|
|
|
|
Print("Minimum Live Win Rate: ", MinLiveWinRate, "%");
|
|
|
|
Print("Maximum Drawdown: ", MaxDrawdownPct, "%");
|
2025-08-16 11:53:49 -04:00
|
|
|
Print("Max Open Trades (per symbol): ", MAX_OPEN_TRADES);
|
|
|
|
|
|
|
|
// Build symbol universe and start timer (Phase 1)
|
|
|
|
if(EnableMultiSymbol)
|
|
|
|
{
|
|
|
|
BuildSymbolUniverse();
|
|
|
|
int sec = (ScanIntervalSeconds < 1 ? 10 : ScanIntervalSeconds);
|
|
|
|
EventSetTimer(sec);
|
|
|
|
Print("Multi-Symbol enabled. Symbols discovered: ", ArraySize(g_symbols));
|
|
|
|
}
|
2025-08-02 22:17:29 -04:00
|
|
|
|
|
|
|
return(INIT_SUCCEEDED);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Expert deinitialization function |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void OnDeinit(const int reason)
|
|
|
|
{
|
|
|
|
// Release indicator handles
|
|
|
|
if(handle_iMA_4 != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle_iMA_4);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(handle_iMA_5 != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle_iMA_5);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save trading state
|
|
|
|
SaveTradingState();
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// Kill timer if used
|
|
|
|
if(EnableMultiSymbol)
|
|
|
|
EventKillTimer();
|
|
|
|
|
2025-08-02 22:17:29 -04:00
|
|
|
Print("EA deinitialized. Reason: ", GetUninitReasonText(reason));
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get uninitialization reason text |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string GetUninitReasonText(int reasonCode)
|
|
|
|
{
|
|
|
|
string text="";
|
|
|
|
switch(reasonCode)
|
|
|
|
{
|
|
|
|
case REASON_ACCOUNT:
|
|
|
|
text = "Account was changed";
|
|
|
|
break;
|
|
|
|
case REASON_CHARTCHANGE:
|
|
|
|
text = "Symbol or timeframe was changed";
|
|
|
|
break;
|
|
|
|
case REASON_CHARTCLOSE:
|
|
|
|
text = "Chart was closed";
|
|
|
|
break;
|
|
|
|
case REASON_PARAMETERS:
|
|
|
|
text = "Input-parameter was changed";
|
|
|
|
break;
|
|
|
|
case REASON_RECOMPILE:
|
|
|
|
text = "Program was recompiled";
|
|
|
|
break;
|
|
|
|
case REASON_REMOVE:
|
|
|
|
text = "Program was removed from chart";
|
|
|
|
break;
|
|
|
|
case REASON_TEMPLATE:
|
|
|
|
text = "New template was applied to chart";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
text = "Unknown reason";
|
|
|
|
}
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Reset trading statistics |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void ResetTradingStats()
|
|
|
|
{
|
|
|
|
g_paperStats.Init();
|
|
|
|
g_liveStats.Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Update trading statistics with a new trade |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void UpdateTradingStats(const STradeRecord &trade)
|
|
|
|
{
|
|
|
|
if(trade.isLive)
|
|
|
|
{
|
|
|
|
g_liveStats.Update(trade);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_paperStats.Update(trade);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Record a new trade |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void RecordTrade(const STradeRecord &trade)
|
|
|
|
{
|
|
|
|
int size = ArraySize(g_tradeHistory);
|
|
|
|
ArrayResize(g_tradeHistory, size + 1);
|
|
|
|
g_tradeHistory[size] = trade;
|
|
|
|
|
|
|
|
// Update statistics
|
|
|
|
UpdateTradingStats(trade);
|
|
|
|
|
|
|
|
// Check if it's time to learn
|
2025-08-16 11:53:49 -04:00
|
|
|
// Trigger learning based on combined paper + live trades
|
|
|
|
int totalTradesCombined = g_paperStats.totalTrades + g_liveStats.totalTrades;
|
|
|
|
if(totalTradesCombined > 0 && (totalTradesCombined % LearningWindow) == 0)
|
2025-08-02 22:17:29 -04:00
|
|
|
{
|
|
|
|
LearnFromTrades();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we should switch modes
|
|
|
|
CheckTradingMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get market condition |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
SMarketCondition GetMarketCondition()
|
|
|
|
{
|
|
|
|
SMarketCondition condition;
|
|
|
|
condition.Init();
|
|
|
|
|
|
|
|
// Get technical indicators using helper functions
|
|
|
|
double rsi = GetRSI(Symbol(), PERIOD_CURRENT, 14, 0);
|
|
|
|
double atr = GetATR(Symbol(), PERIOD_CURRENT, 14, 0);
|
|
|
|
double spread = (double)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) * _Point;
|
|
|
|
|
|
|
|
// Calculate trend and volatility
|
|
|
|
condition.trend = (rsi - 50.0) * 2.0; // Convert RSI to -100 to +100 range
|
|
|
|
condition.volatility = MathMin(atr / _Point / 10.0, 100.0); // Scale ATR to 0-100 range
|
|
|
|
condition.volume = 50.0; // Placeholder for volume analysis
|
|
|
|
condition.spread = spread / _Point;
|
|
|
|
|
|
|
|
// Determine market state based on conditions
|
|
|
|
if(spread > 30 * _Point)
|
|
|
|
{
|
|
|
|
condition.condition = MARKET_HIGH_SPREAD;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if(condition.volatility > 70)
|
|
|
|
{
|
|
|
|
condition.condition = MARKET_VOLATILE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if(condition.trend > 70)
|
|
|
|
{
|
|
|
|
condition.condition = MARKET_TREND_UP;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if(condition.trend < 30)
|
|
|
|
{
|
|
|
|
condition.condition = MARKET_TREND_DOWN;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
condition.condition = MARKET_NORMAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
condition.lastUpdate = TimeCurrent();
|
|
|
|
return condition;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get RSI value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double GetRSI(string symbol, ENUM_TIMEFRAMES timeframe, int period, int shift = 0)
|
|
|
|
{
|
|
|
|
double buffer[];
|
|
|
|
ArraySetAsSeries(buffer, true);
|
|
|
|
int handle = iRSI(symbol, timeframe, period, PRICE_CLOSE);
|
|
|
|
if(handle == INVALID_HANDLE)
|
|
|
|
return 0;
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// wait for indicator data readiness
|
|
|
|
int tries = 0;
|
|
|
|
while(BarsCalculated(handle) <= shift && tries++ < 10) Sleep(50);
|
2025-08-02 22:17:29 -04:00
|
|
|
if(CopyBuffer(handle, 0, shift, 1, buffer) <= 0)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
double value = buffer[0];
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get ATR value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period, int shift = 0)
|
|
|
|
{
|
|
|
|
double buffer[];
|
|
|
|
ArraySetAsSeries(buffer, true);
|
|
|
|
int handle = iATR(symbol, timeframe, period);
|
|
|
|
if(handle == INVALID_HANDLE)
|
|
|
|
return 0;
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// wait for indicator data readiness
|
|
|
|
int tries = 0;
|
|
|
|
while(BarsCalculated(handle) <= shift && tries++ < 10) Sleep(50);
|
2025-08-02 22:17:29 -04:00
|
|
|
if(CopyBuffer(handle, 0, shift, 1, buffer) <= 0)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
double value = buffer[0];
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Market Condition Analysis |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
enum ENUM_MARKET_STATE
|
|
|
|
{
|
|
|
|
MARKET_STATE_NORMAL = 0, // Normal market conditions
|
|
|
|
MARKET_STATE_HIGH_VOLATILITY = 1, // High volatility detected
|
|
|
|
MARKET_STATE_LOW_VOLATILITY = 2, // Low volatility detected
|
|
|
|
MARKET_STATE_STRONG_TREND_UP = 3, // Strong uptrend
|
|
|
|
MARKET_STATE_STRONG_TREND_DOWN = 4, // Strong downtrend
|
|
|
|
MARKET_STATE_RANGING = 5, // Sideways market
|
|
|
|
MARKET_STATE_BREAKOUT = 6, // Breakout detected
|
|
|
|
MARKET_STATE_HIGH_SPREAD = 7, // High spread detected
|
|
|
|
MARKET_STATE_NEWS_EVENT = 8, // News event detected
|
|
|
|
MARKET_STATE_OPENING = 9, // Market opening
|
|
|
|
MARKET_STATE_CLOSING = 10, // Market closing
|
|
|
|
MARKET_STATE_THIN = 11, // Thin market (low liquidity)
|
|
|
|
MARKET_STATE_GAP = 12 // Price gap detected
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double MarketInfo(string symbol, int type)
|
|
|
|
{
|
|
|
|
switch(type)
|
|
|
|
{
|
|
|
|
case 6: // MODE_MARGINREQUIRED
|
|
|
|
return SymbolInfoDouble(symbol, SYMBOL_MARGIN_INITIAL);
|
|
|
|
// Add other cases as needed
|
|
|
|
default:
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Using MathAbs() instead of redefining abs()
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Execute a market order |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool ExecuteMarketOrder(ENUM_ORDER_TYPE orderType, double lots, double stopLossPips, double takeProfitPips, string comment = "")
|
|
|
|
{
|
|
|
|
if(!g_tradingEnabled)
|
|
|
|
{
|
|
|
|
Print("Trading is currently disabled");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isLive = (g_tradingMode == MODE_LIVE || (g_tradingMode == MODE_HYBRID && MathRand() % 2 == 0));
|
|
|
|
|
|
|
|
// For paper trades, just record the trade without executing
|
|
|
|
if(!isLive)
|
|
|
|
{
|
|
|
|
MqlTick last_tick;
|
|
|
|
if(!SymbolInfoTick(Symbol(), last_tick))
|
|
|
|
{
|
|
|
|
Print("Error getting tick data for paper trading");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
STradeRecord trade = {};
|
|
|
|
trade.Init();
|
|
|
|
trade.openTime = TimeCurrent();
|
|
|
|
trade.symbol = Symbol();
|
|
|
|
trade.type = orderType;
|
|
|
|
trade.volume = lots;
|
|
|
|
trade.isLive = false;
|
|
|
|
|
|
|
|
// Log paper trade details
|
|
|
|
string orderTypeStr = (orderType == ORDER_TYPE_BUY) ? "BUY" : "SELL";
|
|
|
|
Print("Paper Trade Signal: ", orderTypeStr, " ", DoubleToString(lots, 2), " lots");
|
|
|
|
Print("Current Ask: ", last_tick.ask, " Bid: ", last_tick.bid);
|
|
|
|
|
|
|
|
// Set prices based on order type
|
|
|
|
if(orderType == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
trade.openPrice = last_tick.ask;
|
|
|
|
trade.stopLoss = trade.openPrice - stopLossPips * _Point * 10;
|
|
|
|
trade.takeProfit = trade.openPrice + takeProfitPips * _Point * 10;
|
|
|
|
Print("BUY Order - Price: ", trade.openPrice, " SL: ", trade.stopLoss, " TP: ", trade.takeProfit);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trade.openPrice = last_tick.bid;
|
|
|
|
trade.stopLoss = trade.openPrice + stopLossPips * _Point * 10;
|
|
|
|
trade.takeProfit = trade.openPrice - takeProfitPips * _Point * 10;
|
|
|
|
Print("SELL Order - Price: ", trade.openPrice, " SL: ", trade.stopLoss, " TP: ", trade.takeProfit);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Log the paper trade execution
|
|
|
|
Print("Paper Trade Executed - ", orderTypeStr, " ", DoubleToString(lots, 2), " lots at ",
|
|
|
|
trade.openPrice, " (SL: ", trade.stopLoss, " TP: ", trade.takeProfit, ")");
|
|
|
|
|
|
|
|
// Simulate trade close after a random period
|
|
|
|
trade.closeTime = trade.openTime + (MathRand() % 86400 + 3600);
|
|
|
|
|
|
|
|
// Simulate random win/loss for paper trading
|
|
|
|
bool isWin = (MathRand() % 100) < (g_learningParams.maFastPeriod > g_learningParams.maSlowPeriod ? 60 : 40);
|
|
|
|
if(isWin)
|
|
|
|
{
|
|
|
|
trade.closePrice = orderType == ORDER_TYPE_BUY ?
|
|
|
|
trade.openPrice + (MathRand() % (int)(takeProfitPips * 10) + 10) * _Point :
|
|
|
|
trade.openPrice - (MathRand() % (int)(takeProfitPips * 10) + 10) * _Point;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trade.closePrice = orderType == ORDER_TYPE_BUY ?
|
|
|
|
trade.openPrice - (MathRand() % (int)(stopLossPips * 10) + 5) * _Point :
|
|
|
|
trade.openPrice + (MathRand() % (int)(stopLossPips * 10) + 5) * _Point;
|
|
|
|
}
|
|
|
|
|
|
|
|
trade.profit = (orderType == ORDER_TYPE_BUY) ?
|
|
|
|
(trade.closePrice - trade.openPrice) * trade.volume * 100000 :
|
|
|
|
(trade.openPrice - trade.closePrice) * trade.volume * 100000;
|
|
|
|
|
|
|
|
// Store trade parameters in the comment field since we don't have separate fields anymore
|
|
|
|
trade.comment = StringFormat("MA_Fast=%.0f,MA_Slow=%.0f,SL=%.1f,TP=%.1f,Win=%d",
|
|
|
|
g_learningParams.maFastPeriod,
|
|
|
|
g_learningParams.maSlowPeriod,
|
|
|
|
stopLossPips,
|
|
|
|
takeProfitPips,
|
|
|
|
(int)isWin);
|
|
|
|
|
|
|
|
RecordTrade(trade);
|
|
|
|
|
|
|
|
// Update statistics
|
|
|
|
if(trade.profit > 0)
|
|
|
|
Print("Paper Trade Result: PROFIT ", trade.profit);
|
|
|
|
else if(trade.profit < 0)
|
|
|
|
Print("Paper Trade Result: LOSS ", trade.profit);
|
|
|
|
else
|
|
|
|
Print("Paper Trade Result: BREAKEVEN");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Live trade execution
|
|
|
|
double price = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(Symbol(), SYMBOL_ASK) :
|
|
|
|
SymbolInfoDouble(Symbol(), SYMBOL_BID);
|
|
|
|
|
|
|
|
double sl = (orderType == ORDER_TYPE_BUY) ? price - stopLossPips * _Point * 10 :
|
|
|
|
price + stopLossPips * _Point * 10;
|
|
|
|
|
|
|
|
double tp = (orderType == ORDER_TYPE_BUY) ? price + takeProfitPips * _Point * 10 :
|
|
|
|
price - takeProfitPips * _Point * 10;
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
if(orderType == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
result = m_trade.Buy(lots, Symbol(), price, sl, tp, comment);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = m_trade.Sell(lots, Symbol(), price, sl, tp, comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(result)
|
|
|
|
{
|
|
|
|
STradeRecord tradeRecord = {};
|
|
|
|
tradeRecord.Init();
|
|
|
|
tradeRecord.ticket = m_trade.ResultOrder();
|
|
|
|
tradeRecord.openTime = TimeCurrent();
|
|
|
|
tradeRecord.type = orderType;
|
|
|
|
tradeRecord.symbol = Symbol();
|
|
|
|
tradeRecord.volume = lots;
|
|
|
|
tradeRecord.openPrice = price;
|
|
|
|
tradeRecord.stopLoss = sl;
|
|
|
|
tradeRecord.takeProfit = tp;
|
|
|
|
tradeRecord.isLive = true;
|
|
|
|
|
|
|
|
// Store trade parameters in the comment field
|
|
|
|
tradeRecord.comment = StringFormat("MA_Fast=%.0f,MA_Slow=%.0f,SL=%.1f,TP=%.1f",
|
|
|
|
g_learningParams.maFastPeriod,
|
|
|
|
g_learningParams.maSlowPeriod,
|
|
|
|
stopLossPips,
|
|
|
|
takeProfitPips);
|
|
|
|
|
|
|
|
RecordTrade(tradeRecord);
|
|
|
|
Print("Live trade executed: ", EnumToString(orderType), " ", tradeRecord.volume, " lots at ", price);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Print("Failed to execute live trade. Error: ", GetLastError());
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Strategy Interface |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
interface IStrategy
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual bool Init() = 0;
|
|
|
|
virtual void Deinit() = 0;
|
|
|
|
virtual bool CheckLongSignal() = 0;
|
|
|
|
virtual bool CheckShortSignal() = 0;
|
|
|
|
virtual double GetStopLossPips(bool isLong) = 0;
|
|
|
|
virtual double GetTakeProfitPips(bool isLong) = 0;
|
|
|
|
virtual string GetName() = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Strategy Manager |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CStrategyManager
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
IStrategy* m_strategies[];
|
|
|
|
int m_totalStrategies;
|
|
|
|
|
|
|
|
public:
|
|
|
|
CStrategyManager() : m_totalStrategies(0) {}
|
|
|
|
|
|
|
|
~CStrategyManager()
|
|
|
|
{
|
|
|
|
for(int i = 0; i < m_totalStrategies; i++)
|
|
|
|
delete m_strategies[i];
|
|
|
|
ArrayFree(m_strategies);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AddStrategy(IStrategy* strategy)
|
|
|
|
{
|
|
|
|
if(!strategy.Init())
|
|
|
|
{
|
|
|
|
Print("Failed to initialize strategy: ", strategy.GetName());
|
|
|
|
delete strategy;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int size = ArraySize(m_strategies);
|
|
|
|
if(ArrayResize(m_strategies, size + 1) == -1)
|
|
|
|
{
|
|
|
|
Print("Failed to resize strategies array");
|
|
|
|
delete strategy;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_strategies[size] = strategy;
|
|
|
|
m_totalStrategies++;
|
|
|
|
Print("Strategy added: ", strategy.GetName());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckLongSignal()
|
|
|
|
{
|
|
|
|
for(int i = 0; i < m_totalStrategies; i++)
|
|
|
|
{
|
|
|
|
if(m_strategies[i].CheckLongSignal())
|
|
|
|
{
|
|
|
|
Print("Long signal from strategy: ", m_strategies[i].GetName());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckShortSignal()
|
|
|
|
{
|
|
|
|
for(int i = 0; i < m_totalStrategies; i++)
|
|
|
|
{
|
|
|
|
if(m_strategies[i].CheckShortSignal())
|
|
|
|
{
|
|
|
|
Print("Short signal from strategy: ", m_strategies[i].GetName());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetStopLossPips(bool isLong)
|
|
|
|
{
|
|
|
|
// Default values
|
|
|
|
double sl = isLong ? 20.0 : 20.0; // Default 20 pips
|
|
|
|
|
|
|
|
// Get the most conservative (largest) stop loss from all strategies
|
|
|
|
for(int i = 0; i < m_totalStrategies; i++)
|
|
|
|
{
|
|
|
|
double strategySL = m_strategies[i].GetStopLossPips(isLong);
|
|
|
|
if(strategySL > sl)
|
|
|
|
sl = strategySL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sl;
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetTakeProfitPips(bool isLong)
|
|
|
|
{
|
|
|
|
// Default values
|
|
|
|
double tp = isLong ? 40.0 : 40.0; // Default 40 pips
|
|
|
|
|
|
|
|
// Get the most conservative (smallest) take profit from all strategies
|
|
|
|
for(int i = 0; i < m_totalStrategies; i++)
|
|
|
|
{
|
|
|
|
double strategyTP = m_strategies[i].GetTakeProfitPips(isLong);
|
|
|
|
if(strategyTP > 0 && (strategyTP < tp || tp == 0))
|
|
|
|
tp = strategyTP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return tp;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the total number of strategies currently loaded
|
|
|
|
int GetTotalStrategies() const { return m_totalStrategies; }
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Bollinger Bands Strategy |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CBollingerBandsStrategy : public IStrategy
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
int m_bbHandle;
|
|
|
|
int m_bbPeriod;
|
|
|
|
double m_bbDeviation;
|
|
|
|
int m_atrHandle;
|
|
|
|
|
|
|
|
public:
|
|
|
|
CBollingerBandsStrategy(int period = 20, double deviation = 2.0) :
|
|
|
|
m_bbHandle(INVALID_HANDLE),
|
|
|
|
m_bbPeriod(period),
|
|
|
|
m_bbDeviation(deviation),
|
|
|
|
m_atrHandle(INVALID_HANDLE) {}
|
|
|
|
|
|
|
|
~CBollingerBandsStrategy()
|
|
|
|
{
|
|
|
|
if(m_bbHandle != INVALID_HANDLE) IndicatorRelease(m_bbHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Init() override
|
|
|
|
{
|
|
|
|
m_bbHandle = iBands(_Symbol, _Period, m_bbPeriod, 0, m_bbDeviation, PRICE_CLOSE);
|
|
|
|
m_atrHandle = iATR(_Symbol, _Period, 14);
|
|
|
|
|
|
|
|
if(m_bbHandle == INVALID_HANDLE || m_atrHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Failed to create indicators for Bollinger Bands Strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Deinit() override
|
|
|
|
{
|
|
|
|
if(m_bbHandle != INVALID_HANDLE) IndicatorRelease(m_bbHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
|
|
|
|
m_bbHandle = INVALID_HANDLE;
|
|
|
|
m_atrHandle = INVALID_HANDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckLongSignal() override
|
|
|
|
{
|
|
|
|
double upperBand[2], middleBand[2], lowerBand[2], close[2];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_bbHandle, 1, 0, 2, upperBand) != 2 ||
|
|
|
|
CopyBuffer(m_bbHandle, 0, 0, 2, middleBand) != 2 ||
|
|
|
|
CopyBuffer(m_bbHandle, 2, 0, 2, lowerBand) != 2)
|
|
|
|
{
|
|
|
|
Print("Error getting Bollinger Bands values");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get close prices
|
|
|
|
if(CopyClose(_Symbol, _Period, 0, 2, close) != 2)
|
|
|
|
{
|
|
|
|
Print("Error getting close prices");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Buy when price bounces off the lower band
|
|
|
|
return (close[1] <= lowerBand[1] && close[0] > lowerBand[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckShortSignal() override
|
|
|
|
{
|
|
|
|
double upperBand[2], middleBand[2], lowerBand[2], close[2];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_bbHandle, 1, 0, 2, upperBand) != 2 ||
|
|
|
|
CopyBuffer(m_bbHandle, 0, 0, 2, middleBand) != 2 ||
|
|
|
|
CopyBuffer(m_bbHandle, 2, 0, 2, lowerBand) != 2)
|
|
|
|
{
|
|
|
|
Print("Error getting Bollinger Bands values");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get close prices
|
|
|
|
if(CopyClose(_Symbol, _Period, 0, 2, close) != 2)
|
|
|
|
{
|
|
|
|
Print("Error getting close prices");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sell when price bounces off the upper band
|
|
|
|
return (close[1] >= upperBand[1] && close[0] < upperBand[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetStopLossPips(bool isLong) override
|
|
|
|
{
|
|
|
|
double atr[1];
|
|
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, atr) != 1)
|
|
|
|
return isLong ? 20.0 : 20.0; // Default 20 pips
|
|
|
|
|
|
|
|
// Use 1.8 * ATR for dynamic stop loss
|
|
|
|
double atrPips = atr[0] / _Point;
|
|
|
|
return MathMax(atrPips * 1.8, 10.0); // Minimum 10 pips
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetTakeProfitPips(bool isLong) override
|
|
|
|
{
|
|
|
|
// 2:1 risk-reward ratio
|
|
|
|
return GetStopLossPips(isLong) * 2.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
string GetName() override
|
|
|
|
{
|
|
|
|
return StringFormat("Bollinger Bands(%d,%.1f) Strategy", m_bbPeriod, m_bbDeviation);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| MACD Strategy |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CMACDStrategy : public IStrategy
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
int m_macdHandle;
|
|
|
|
int m_fastEMA;
|
|
|
|
int m_slowEMA;
|
|
|
|
int m_signalPeriod;
|
|
|
|
int m_atrHandle;
|
|
|
|
|
|
|
|
public:
|
|
|
|
CMACDStrategy(int fastEMA = 12, int slowEMA = 26, int signalPeriod = 9) :
|
|
|
|
m_macdHandle(INVALID_HANDLE),
|
|
|
|
m_fastEMA(fastEMA),
|
|
|
|
m_slowEMA(slowEMA),
|
|
|
|
m_signalPeriod(signalPeriod),
|
|
|
|
m_atrHandle(INVALID_HANDLE) {}
|
|
|
|
|
|
|
|
~CMACDStrategy()
|
|
|
|
{
|
|
|
|
if(m_macdHandle != INVALID_HANDLE) IndicatorRelease(m_macdHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Init() override
|
|
|
|
{
|
|
|
|
m_macdHandle = iMACD(_Symbol, _Period, m_fastEMA, m_slowEMA, m_signalPeriod, PRICE_CLOSE);
|
|
|
|
m_atrHandle = iATR(_Symbol, _Period, 14);
|
|
|
|
|
|
|
|
if(m_macdHandle == INVALID_HANDLE || m_atrHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Failed to create indicators for MACD Strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Deinit() override
|
|
|
|
{
|
|
|
|
if(m_macdHandle != INVALID_HANDLE) IndicatorRelease(m_macdHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
|
|
|
|
m_macdHandle = INVALID_HANDLE;
|
|
|
|
m_atrHandle = INVALID_HANDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckLongSignal() override
|
|
|
|
{
|
|
|
|
double macdMain[3], macdSignal[3];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_macdHandle, 0, 0, 3, macdMain) != 3 ||
|
|
|
|
CopyBuffer(m_macdHandle, 1, 0, 3, macdSignal) != 3)
|
|
|
|
{
|
|
|
|
Print("Error getting MACD values");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Buy when MACD crosses above signal line
|
|
|
|
return (macdMain[1] <= macdSignal[1] && macdMain[0] > macdSignal[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckShortSignal() override
|
|
|
|
{
|
|
|
|
double macdMain[3], macdSignal[3];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_macdHandle, 0, 0, 3, macdMain) != 3 ||
|
|
|
|
CopyBuffer(m_macdHandle, 1, 0, 3, macdSignal) != 3)
|
|
|
|
{
|
|
|
|
Print("Error getting MACD values");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sell when MACD crosses below signal line
|
|
|
|
return (macdMain[1] >= macdSignal[1] && macdMain[0] < macdSignal[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetStopLossPips(bool isLong) override
|
|
|
|
{
|
|
|
|
double atr[1];
|
|
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, atr) != 1)
|
|
|
|
return isLong ? 20.0 : 20.0; // Default 20 pips
|
|
|
|
|
|
|
|
// Use 2.0 * ATR for dynamic stop loss
|
|
|
|
double atrPips = atr[0] / _Point;
|
|
|
|
return MathMax(atrPips * 2.0, 10.0); // Minimum 10 pips
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetTakeProfitPips(bool isLong) override
|
|
|
|
{
|
|
|
|
// 3:1 risk-reward ratio for MACD strategy
|
|
|
|
return GetStopLossPips(isLong) * 3.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
string GetName() override
|
|
|
|
{
|
|
|
|
return StringFormat("MACD(%d,%d,%d) Strategy", m_fastEMA, m_slowEMA, m_signalPeriod);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| RSI Strategy |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CRSIStrategy : public IStrategy
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
int m_rsiHandle;
|
|
|
|
int m_rsiPeriod;
|
|
|
|
double m_overbought;
|
|
|
|
double m_oversold;
|
|
|
|
int m_maHandle;
|
|
|
|
int m_atrHandle;
|
|
|
|
|
|
|
|
bool CheckDivergence(bool isBullish)
|
|
|
|
{
|
|
|
|
// Simple divergence detection
|
|
|
|
double rsi[5], low[5], high[5];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_rsiHandle, 0, 0, 5, rsi) != 5 ||
|
|
|
|
CopyLow(_Symbol, _Period, 0, 5, low) != 5 ||
|
|
|
|
CopyHigh(_Symbol, _Period, 0, 5, high) != 5)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(isBullish)
|
|
|
|
{
|
|
|
|
// Bullish divergence: price makes lower low but RSI makes higher low
|
|
|
|
return (low[0] < low[2] && rsi[0] > rsi[2]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Bearish divergence: price makes higher high but RSI makes lower high
|
|
|
|
return (high[0] > high[2] && rsi[0] < rsi[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckMAConfirmation()
|
|
|
|
{
|
|
|
|
double rsi[2], ma[2];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_rsiHandle, 0, 0, 2, rsi) != 2 ||
|
|
|
|
CopyBuffer(m_maHandle, 0, 0, 2, ma) != 2)
|
|
|
|
{
|
|
|
|
return true; // Default to true if we can't get the data
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if RSI is above its MA (bullish) or below (bearish)
|
|
|
|
return (rsi[0] > ma[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
CRSIStrategy(int period = 14, double overbought = 70.0, double oversold = 30.0) :
|
|
|
|
m_rsiHandle(INVALID_HANDLE),
|
|
|
|
m_rsiPeriod(period),
|
|
|
|
m_overbought(overbought),
|
|
|
|
m_oversold(oversold),
|
|
|
|
m_maHandle(INVALID_HANDLE),
|
|
|
|
m_atrHandle(INVALID_HANDLE) {}
|
|
|
|
|
|
|
|
~CRSIStrategy()
|
|
|
|
{
|
|
|
|
if(m_rsiHandle != INVALID_HANDLE) IndicatorRelease(m_rsiHandle);
|
|
|
|
if(m_maHandle != INVALID_HANDLE) IndicatorRelease(m_maHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Init() override
|
|
|
|
{
|
|
|
|
// Initialize RSI indicator
|
|
|
|
m_rsiHandle = iRSI(_Symbol, _Period, m_rsiPeriod, PRICE_CLOSE);
|
|
|
|
if(m_rsiHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Failed to create RSI indicator. Error: ", GetLastError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize Moving Average indicator
|
|
|
|
m_maHandle = iMA(_Symbol, _Period, 14, 0, MODE_SMA, PRICE_CLOSE);
|
|
|
|
if(m_maHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Failed to create MA indicator. Error: ", GetLastError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize ATR indicator for dynamic stop loss
|
|
|
|
m_atrHandle = iATR(_Symbol, _Period, 14);
|
|
|
|
if(m_atrHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Failed to create ATR indicator. Error: ", GetLastError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for indicators to calculate
|
|
|
|
int retries = 0;
|
|
|
|
while(BarsCalculated(m_rsiHandle) == 0 || BarsCalculated(m_maHandle) == 0 || BarsCalculated(m_atrHandle) == 0)
|
|
|
|
{
|
|
|
|
retries++;
|
|
|
|
if(retries > 10)
|
|
|
|
{
|
|
|
|
Print("Error: Indicators not ready after 10 retries");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Sleep(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Deinit() override
|
|
|
|
{
|
|
|
|
if(m_rsiHandle != INVALID_HANDLE) IndicatorRelease(m_rsiHandle);
|
|
|
|
if(m_maHandle != INVALID_HANDLE) IndicatorRelease(m_maHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
|
|
|
|
m_rsiHandle = INVALID_HANDLE;
|
|
|
|
m_maHandle = INVALID_HANDLE;
|
|
|
|
m_atrHandle = INVALID_HANDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckLongSignal() override
|
|
|
|
{
|
|
|
|
// Check if indicator handle is valid
|
|
|
|
if(m_rsiHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("RSI handle not valid in CheckLongSignal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get RSI values with error checking
|
|
|
|
double rsi[3] = {0};
|
2025-08-16 11:53:49 -04:00
|
|
|
int needed = 3;
|
|
|
|
if(BarsCalculated(m_rsiHandle) < needed)
|
2025-08-02 22:17:29 -04:00
|
|
|
return false;
|
2025-08-16 11:53:49 -04:00
|
|
|
int copied = CopyBuffer(m_rsiHandle, 0, 0, needed, rsi);
|
|
|
|
if(copied != needed)
|
|
|
|
{
|
|
|
|
int err = GetLastError();
|
|
|
|
if(err == ERR_INDICATOR_WRONG_HANDLE || m_rsiHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
ResetLastError();
|
|
|
|
if(m_rsiHandle != INVALID_HANDLE) IndicatorRelease(m_rsiHandle);
|
|
|
|
m_rsiHandle = iRSI(_Symbol, _Period, m_rsiPeriod, PRICE_CLOSE);
|
|
|
|
if(m_rsiHandle == INVALID_HANDLE)
|
|
|
|
return false;
|
|
|
|
int retries = 0;
|
|
|
|
while(BarsCalculated(m_rsiHandle) == 0 && retries++ < 10) Sleep(50);
|
|
|
|
ArrayInitialize(rsi, 0.0);
|
|
|
|
copied = CopyBuffer(m_rsiHandle, 0, 0, needed, rsi);
|
|
|
|
}
|
|
|
|
if(copied != needed)
|
|
|
|
{
|
|
|
|
Print("Error getting RSI values. Copied: ", copied, ", Error: ", err);
|
|
|
|
return false;
|
|
|
|
}
|
2025-08-02 22:17:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check for valid RSI values
|
|
|
|
if(rsi[0] == 0 || rsi[1] == 0 || rsi[2] == 0)
|
|
|
|
{
|
|
|
|
Print("Invalid RSI values: ", rsi[0], ", ", rsi[1], ", ", rsi[2]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for oversold condition
|
|
|
|
bool isOversold = (rsi[1] <= m_oversold && rsi[0] > m_oversold);
|
|
|
|
|
|
|
|
// Check for bullish divergence
|
|
|
|
bool hasBullishDivergence = CheckDivergence(true);
|
|
|
|
|
|
|
|
// Check MA confirmation
|
|
|
|
bool maConfirmation = CheckMAConfirmation();
|
|
|
|
|
|
|
|
// Buy when RSI crosses above oversold level with confirmation
|
|
|
|
return (isOversold || hasBullishDivergence) && maConfirmation;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckShortSignal() override
|
|
|
|
{
|
|
|
|
// Check if indicator handle is valid
|
|
|
|
if(m_rsiHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("RSI handle not valid in CheckShortSignal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get RSI values with error checking
|
|
|
|
double rsi[3] = {0};
|
2025-08-16 11:53:49 -04:00
|
|
|
int needed = 3;
|
|
|
|
if(BarsCalculated(m_rsiHandle) < needed)
|
2025-08-02 22:17:29 -04:00
|
|
|
return false;
|
2025-08-16 11:53:49 -04:00
|
|
|
int copied = CopyBuffer(m_rsiHandle, 0, 0, needed, rsi);
|
|
|
|
if(copied != needed)
|
|
|
|
{
|
|
|
|
int err = GetLastError();
|
|
|
|
if(err == ERR_INDICATOR_WRONG_HANDLE || m_rsiHandle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
ResetLastError();
|
|
|
|
if(m_rsiHandle != INVALID_HANDLE) IndicatorRelease(m_rsiHandle);
|
|
|
|
m_rsiHandle = iRSI(_Symbol, _Period, m_rsiPeriod, PRICE_CLOSE);
|
|
|
|
if(m_rsiHandle == INVALID_HANDLE)
|
|
|
|
return false;
|
|
|
|
int retries = 0;
|
|
|
|
while(BarsCalculated(m_rsiHandle) == 0 && retries++ < 10) Sleep(50);
|
|
|
|
ArrayInitialize(rsi, 0.0);
|
|
|
|
copied = CopyBuffer(m_rsiHandle, 0, 0, needed, rsi);
|
|
|
|
}
|
|
|
|
if(copied != needed)
|
|
|
|
{
|
|
|
|
Print("Error getting RSI values. Copied: ", copied, ", Error: ", err);
|
|
|
|
return false;
|
|
|
|
}
|
2025-08-02 22:17:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check for valid RSI values
|
|
|
|
if(rsi[0] == 0 || rsi[1] == 0 || rsi[2] == 0)
|
|
|
|
{
|
|
|
|
Print("Invalid RSI values: ", rsi[0], ", ", rsi[1], ", ", rsi[2]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for overbought condition
|
|
|
|
bool isOverbought = (rsi[1] >= m_overbought && rsi[0] < m_overbought);
|
|
|
|
|
|
|
|
// Check for bearish divergence
|
|
|
|
bool hasBearishDivergence = CheckDivergence(false);
|
|
|
|
|
|
|
|
// Check MA confirmation (inverse for short)
|
|
|
|
bool maConfirmation = !CheckMAConfirmation();
|
|
|
|
|
|
|
|
// Sell when RSI crosses below overbought level with confirmation
|
|
|
|
return (isOverbought || hasBearishDivergence) && maConfirmation;
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetStopLossPips(bool isLong) override
|
|
|
|
{
|
|
|
|
double atr[1];
|
|
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, atr) != 1)
|
|
|
|
return isLong ? 20.0 : 20.0; // Default 20 pips
|
|
|
|
|
|
|
|
// Use 1.5 * ATR for dynamic stop loss
|
|
|
|
double atrPips = atr[0] / _Point;
|
|
|
|
return MathMax(atrPips * 1.5, 10.0); // Minimum 10 pips
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetTakeProfitPips(bool isLong) override
|
|
|
|
{
|
|
|
|
// 2.5:1 risk-reward ratio for RSI strategy
|
|
|
|
return GetStopLossPips(isLong) * 2.5;
|
|
|
|
}
|
|
|
|
|
|
|
|
string GetName() override
|
|
|
|
{
|
|
|
|
return StringFormat("RSI(%d) Strategy", m_rsiPeriod);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Moving Average Crossover Strategy |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CMAStrategy : public IStrategy
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
int m_maFastHandle;
|
|
|
|
int m_maSlowHandle;
|
|
|
|
int m_volumeHandle;
|
|
|
|
int m_atrHandle;
|
|
|
|
int m_maFastPeriod;
|
|
|
|
int m_maSlowPeriod;
|
|
|
|
ENUM_TIMEFRAMES m_higherTimeframe;
|
|
|
|
int m_maFastHandleHTF;
|
|
|
|
int m_maSlowHandleHTF;
|
|
|
|
|
|
|
|
bool CheckVolumeConfirmation()
|
|
|
|
{
|
|
|
|
double volume[2];
|
|
|
|
if(CopyBuffer(m_volumeHandle, 0, 0, 2, volume) != 2)
|
|
|
|
return true; // Default to true if we can't get volume data
|
|
|
|
|
|
|
|
// Get more volume data for the moving average calculation
|
|
|
|
double volumeData[20];
|
|
|
|
if(CopyBuffer(m_volumeHandle, 0, 0, 20, volumeData) != 20)
|
|
|
|
return true; // Default to true if we can't get enough volume data
|
|
|
|
|
|
|
|
// Calculate 20-period SMA of volume using iMAOnArray
|
|
|
|
double avgVolume = 0;
|
|
|
|
for(int i = 0; i < 20; i++)
|
|
|
|
avgVolume += volumeData[i];
|
|
|
|
avgVolume /= 20.0;
|
|
|
|
|
|
|
|
return (volume[1] > avgVolume);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckVolatility()
|
|
|
|
{
|
|
|
|
double atr[1];
|
|
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, atr) != 1)
|
|
|
|
return true; // Default to true if we can't get ATR data
|
|
|
|
|
|
|
|
// Only trade if volatility is above minimum threshold
|
|
|
|
return (atr[0] > (10 * _Point));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckHigherTimeframeTrend()
|
|
|
|
{
|
|
|
|
double maFastHTF[2], maSlowHTF[2];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_maFastHandleHTF, 0, 0, 2, maFastHTF) != 2 ||
|
|
|
|
CopyBuffer(m_maSlowHandleHTF, 0, 0, 2, maSlowHTF) != 2)
|
|
|
|
return true; // Default to true if we can't get HTF data
|
|
|
|
|
|
|
|
// Check if higher timeframe trend is up
|
|
|
|
return (maFastHTF[0] > maSlowHTF[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
CMAStrategy(int fastPeriod = 10, int slowPeriod = 20, ENUM_TIMEFRAMES higherTimeframe = PERIOD_H1) :
|
|
|
|
m_maFastHandle(INVALID_HANDLE),
|
|
|
|
m_maSlowHandle(INVALID_HANDLE),
|
|
|
|
m_volumeHandle(INVALID_HANDLE),
|
|
|
|
m_atrHandle(INVALID_HANDLE),
|
|
|
|
m_maFastPeriod(fastPeriod),
|
|
|
|
m_maSlowPeriod(slowPeriod),
|
|
|
|
m_higherTimeframe(higherTimeframe),
|
|
|
|
m_maFastHandleHTF(INVALID_HANDLE),
|
|
|
|
m_maSlowHandleHTF(INVALID_HANDLE) {}
|
|
|
|
|
|
|
|
~CMAStrategy()
|
|
|
|
{
|
|
|
|
if(m_maFastHandle != INVALID_HANDLE) IndicatorRelease(m_maFastHandle);
|
|
|
|
if(m_maSlowHandle != INVALID_HANDLE) IndicatorRelease(m_maSlowHandle);
|
|
|
|
if(m_volumeHandle != INVALID_HANDLE) IndicatorRelease(m_volumeHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
if(m_maFastHandleHTF != INVALID_HANDLE) IndicatorRelease(m_maFastHandleHTF);
|
|
|
|
if(m_maSlowHandleHTF != INVALID_HANDLE) IndicatorRelease(m_maSlowHandleHTF);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Init() override
|
|
|
|
{
|
|
|
|
// Initialize indicators for current timeframe
|
|
|
|
m_maFastHandle = iMA(_Symbol, _Period, m_maFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
m_maSlowHandle = iMA(_Symbol, _Period, m_maSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
m_volumeHandle = iVolumes(_Symbol, _Period, VOLUME_TICK);
|
|
|
|
m_atrHandle = iATR(_Symbol, _Period, 14);
|
|
|
|
|
|
|
|
// Initialize indicators for higher timeframe
|
|
|
|
m_maFastHandleHTF = iMA(_Symbol, m_higherTimeframe, m_maFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
m_maSlowHandleHTF = iMA(_Symbol, m_higherTimeframe, m_maSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
|
|
|
|
if(m_maFastHandle == INVALID_HANDLE || m_maSlowHandle == INVALID_HANDLE ||
|
|
|
|
m_volumeHandle == INVALID_HANDLE || m_atrHandle == INVALID_HANDLE ||
|
|
|
|
m_maFastHandleHTF == INVALID_HANDLE || m_maSlowHandleHTF == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Failed to create indicators for MA Crossover Strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Deinit() override
|
|
|
|
{
|
|
|
|
if(m_maFastHandle != INVALID_HANDLE) IndicatorRelease(m_maFastHandle);
|
|
|
|
if(m_maSlowHandle != INVALID_HANDLE) IndicatorRelease(m_maSlowHandle);
|
|
|
|
if(m_volumeHandle != INVALID_HANDLE) IndicatorRelease(m_volumeHandle);
|
|
|
|
if(m_atrHandle != INVALID_HANDLE) IndicatorRelease(m_atrHandle);
|
|
|
|
if(m_maFastHandleHTF != INVALID_HANDLE) IndicatorRelease(m_maFastHandleHTF);
|
|
|
|
if(m_maSlowHandleHTF != INVALID_HANDLE) IndicatorRelease(m_maSlowHandleHTF);
|
|
|
|
|
|
|
|
m_maFastHandle = INVALID_HANDLE;
|
|
|
|
m_maSlowHandle = INVALID_HANDLE;
|
|
|
|
m_volumeHandle = INVALID_HANDLE;
|
|
|
|
m_atrHandle = INVALID_HANDLE;
|
|
|
|
m_maFastHandleHTF = INVALID_HANDLE;
|
|
|
|
m_maSlowHandleHTF = INVALID_HANDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckLongSignal() override
|
|
|
|
{
|
|
|
|
double maFast[3], maSlow[3];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_maFastHandle, 0, 0, 3, maFast) != 3 ||
|
|
|
|
CopyBuffer(m_maSlowHandle, 0, 0, 3, maSlow) != 3)
|
|
|
|
{
|
|
|
|
Print("Error getting MA values");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for MA crossover
|
|
|
|
bool maCrossover = (maFast[1] <= maSlow[1] && maFast[0] > maSlow[0]);
|
|
|
|
|
|
|
|
// Additional confirmations
|
|
|
|
bool volumeConfirm = CheckVolumeConfirmation();
|
|
|
|
bool volatilityConfirm = CheckVolatility();
|
|
|
|
bool trendConfirm = CheckHigherTimeframeTrend();
|
|
|
|
|
|
|
|
// All conditions must be true for a valid signal
|
|
|
|
return (maCrossover && volumeConfirm && volatilityConfirm && trendConfirm);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckShortSignal() override
|
|
|
|
{
|
|
|
|
double maFast[3], maSlow[3];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_maFastHandle, 0, 0, 3, maFast) != 3 ||
|
|
|
|
CopyBuffer(m_maSlowHandle, 0, 0, 3, maSlow) != 3)
|
|
|
|
{
|
|
|
|
Print("Error getting MA values");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for MA crossunder
|
|
|
|
bool maCrossunder = (maFast[1] >= maSlow[1] && maFast[0] < maSlow[0]);
|
|
|
|
|
|
|
|
// Additional confirmations
|
|
|
|
bool volumeConfirm = CheckVolumeConfirmation();
|
|
|
|
bool volatilityConfirm = CheckVolatility();
|
|
|
|
bool trendConfirm = !CheckHigherTimeframeTrend(); // Inverse for short
|
|
|
|
|
|
|
|
// All conditions must be true for a valid signal
|
|
|
|
return (maCrossunder && volumeConfirm && volatilityConfirm && trendConfirm);
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetStopLossPips(bool isLong) override
|
|
|
|
{
|
|
|
|
double atr[1];
|
|
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, atr) != 1)
|
|
|
|
return isLong ? 20.0 : 20.0; // Default 20 pips
|
|
|
|
|
|
|
|
// Use 1.5 * ATR for dynamic stop loss
|
|
|
|
double atrPips = atr[0] / _Point;
|
|
|
|
return MathMax(atrPips * 1.5, 10.0); // Minimum 10 pips
|
|
|
|
}
|
|
|
|
|
|
|
|
double GetTakeProfitPips(bool isLong) override
|
|
|
|
{
|
|
|
|
// 2:1 risk-reward ratio
|
|
|
|
return GetStopLossPips(isLong) * 2.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
string GetName() override
|
|
|
|
{
|
|
|
|
return StringFormat("MA Crossover(%d,%d) Strategy", m_maFastPeriod, m_maSlowPeriod);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Global strategy manager
|
|
|
|
CStrategyManager g_strategyManager;
|
|
|
|
|
|
|
|
// In OnInit() - Add this after other initializations
|
|
|
|
bool AddStrategies()
|
|
|
|
{
|
|
|
|
// Add MA Crossover Strategy
|
|
|
|
if(InpEnableStrategy1)
|
|
|
|
{
|
|
|
|
if(!g_strategyManager.AddStrategy(new CMAStrategy(InpMAFastPeriod, InpMASlowPeriod, InpHigherTimeframe)))
|
|
|
|
{
|
|
|
|
Print("Failed to add MA Crossover strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Print("MA Crossover Strategy enabled (Fast:", InpMAFastPeriod, ", Slow:", InpMASlowPeriod, ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add RSI Strategy
|
|
|
|
if(InpEnableStrategy2)
|
|
|
|
{
|
|
|
|
if(!g_strategyManager.AddStrategy(new CRSIStrategy(InpRSIPeriod, InpRSIOverbought, InpRSIOversold)))
|
|
|
|
{
|
|
|
|
Print("Failed to add RSI strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Print("RSI Strategy enabled (Period:", InpRSIPeriod, ", Overbought:", InpRSIOverbought, ", Oversold:", InpRSIOversold, ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add MACD Strategy
|
|
|
|
if(InpEnableStrategy3)
|
|
|
|
{
|
|
|
|
if(!g_strategyManager.AddStrategy(new CMACDStrategy(InpMACDFastEMA, InpMACDSlowEMA, InpMACDSignalPeriod)))
|
|
|
|
{
|
|
|
|
Print("Failed to add MACD strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Print("MACD Strategy enabled (Fast:", InpMACDFastEMA, ", Slow:", InpMACDSlowEMA, ", Signal:", InpMACDSignalPeriod, ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add Bollinger Bands Strategy
|
|
|
|
if(InpEnableStrategy4)
|
|
|
|
{
|
|
|
|
if(!g_strategyManager.AddStrategy(new CBollingerBandsStrategy(InpBBPeriod, InpBBDeviation)))
|
|
|
|
{
|
|
|
|
Print("Failed to add Bollinger Bands strategy");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Print("Bollinger Bands Strategy enabled (Period:", InpBBPeriod, ", Deviation:", InpBBDeviation, ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if at least one strategy is enabled
|
|
|
|
if(g_strategyManager.GetTotalStrategies() == 0)
|
|
|
|
{
|
|
|
|
Print("Error: No trading strategies are enabled!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Print("Total strategies enabled: ", g_strategyManager.GetTotalStrategies());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check trading signals and execute trades if conditions are met
|
|
|
|
void CheckSignals(int tickCount)
|
|
|
|
{
|
2025-08-16 11:53:49 -04:00
|
|
|
// Runtime gates for new entries
|
|
|
|
if(MasterPause || PauseOpenNew || EmergencyHalt)
|
|
|
|
{
|
|
|
|
if(tickCount % 50 == 0)
|
|
|
|
Print("Entry gated by runtime controls. MasterPause=", MasterPause,
|
|
|
|
", PauseOpenNew=", PauseOpenNew, ", EmergencyHalt=", EmergencyHalt);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(UseNewsFilter && IsNewsWindow(_Symbol))
|
|
|
|
{
|
|
|
|
if(tickCount % 50 == 0)
|
|
|
|
Print("Entry gated by NewsFilter for symbol ", _Symbol);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-08-02 22:17:29 -04:00
|
|
|
// Check if we can enter new trades
|
|
|
|
if(g_currentOpenTrades >= MAX_OPEN_TRADES)
|
|
|
|
{
|
|
|
|
if(tickCount % 100 == 0)
|
2025-08-16 11:53:49 -04:00
|
|
|
Print("Maximum number of open trades per symbol reached (", g_currentOpenTrades, "/", MAX_OPEN_TRADES, ")");
|
2025-08-02 22:17:29 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for signals from all strategies
|
|
|
|
bool longSignal = g_strategyManager.CheckLongSignal();
|
|
|
|
bool shortSignal = g_strategyManager.CheckShortSignal();
|
|
|
|
|
|
|
|
if(tickCount % 50 == 0) // Log signal status every 50 ticks
|
|
|
|
{
|
|
|
|
Print("Signal Status - Long: ", (longSignal ? "YES" : "no"), ", Short: ", (shortSignal ? "YES" : "no"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if(longSignal)
|
|
|
|
{
|
|
|
|
Print("Processing BUY signal...");
|
|
|
|
double stopLoss = g_strategyManager.GetStopLossPips(true);
|
|
|
|
double takeProfit = g_strategyManager.GetTakeProfitPips(true);
|
|
|
|
double lotSize = CalculatePositionSize(stopLoss);
|
|
|
|
|
|
|
|
Print(" Stop Loss: ", stopLoss, " pips, Take Profit: ", takeProfit, " pips, Lot Size: ", lotSize);
|
|
|
|
|
|
|
|
if(lotSize > 0)
|
|
|
|
{
|
|
|
|
// Draw buy signal on chart
|
|
|
|
DrawBuySignal();
|
|
|
|
|
|
|
|
if(ExecuteMarketOrder(ORDER_TYPE_BUY, lotSize, stopLoss, takeProfit, "Strategy Buy"))
|
|
|
|
{
|
|
|
|
Print("Successfully executed BUY order");
|
|
|
|
g_currentOpenTrades++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Print("Failed to execute BUY order");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(shortSignal && !longSignal) // Only process short if not already processing long
|
|
|
|
{
|
|
|
|
Print("Processing SELL signal...");
|
|
|
|
double stopLoss = g_strategyManager.GetStopLossPips(false);
|
|
|
|
double takeProfit = g_strategyManager.GetTakeProfitPips(false);
|
|
|
|
double lotSize = CalculatePositionSize(stopLoss);
|
|
|
|
|
|
|
|
Print(" Stop Loss: ", stopLoss, " pips, Take Profit: ", takeProfit, " pips, Lot Size: ", lotSize);
|
|
|
|
|
|
|
|
if(lotSize > 0)
|
|
|
|
{
|
|
|
|
// Draw sell signal on chart
|
|
|
|
DrawSellSignal();
|
|
|
|
|
|
|
|
if(ExecuteMarketOrder(ORDER_TYPE_SELL, lotSize, stopLoss, takeProfit, "Strategy Sell"))
|
|
|
|
{
|
|
|
|
Print("Successfully executed SELL order");
|
|
|
|
g_currentOpenTrades++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Print("Failed to execute SELL order");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Expert tick function |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void OnTick()
|
|
|
|
{
|
|
|
|
// Log tick processing
|
|
|
|
static int tickCount = 0;
|
|
|
|
tickCount++;
|
|
|
|
if(tickCount % 100 == 0) // Log every 100 ticks to avoid flooding
|
2025-08-16 11:53:49 -04:00
|
|
|
Print("OnTick: Open trades (per symbol): ", g_currentOpenTrades, "/", MAX_OPEN_TRADES);
|
2025-08-02 22:17:29 -04:00
|
|
|
|
|
|
|
// Check for trading signals
|
|
|
|
if(tickCount % 50 == 0) // Log status every 50 ticks
|
|
|
|
{
|
|
|
|
Print("=== Trading Status ===");
|
|
|
|
Print("Current Time: ", TimeToString(TimeCurrent()));
|
|
|
|
Print("Trading Allowed: ", (TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) ? "Yes" : "No"));
|
|
|
|
Print("Expert Enabled: ", (MQLInfoInteger(MQL_TRADE_ALLOWED) ? "Yes" : "No"));
|
2025-08-16 11:53:49 -04:00
|
|
|
Print("Current Open Trades (per symbol): ", g_currentOpenTrades, "/", MAX_OPEN_TRADES);
|
2025-08-02 22:17:29 -04:00
|
|
|
Print("Last Signal Check: ", (g_lastSignalCheck == 0 ? "Never" : TimeToString(g_lastSignalCheck)));
|
|
|
|
Print("Last Log Update: ", (g_lastLogUpdate == 0 ? "Never" : TimeToString(g_lastLogUpdate)));
|
|
|
|
Print("Signals This Minute: ", g_signalsSentThisMinute, "/", MAX_SIGNALS_PER_MINUTE);
|
|
|
|
}
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// Note: CheckSignals is invoked later after updating per-symbol open trade count.
|
2025-08-02 22:17:29 -04:00
|
|
|
|
|
|
|
// Get current price and time
|
|
|
|
MqlTick last_tick;
|
|
|
|
double current_price = 0.0;
|
|
|
|
datetime current_time = TimeCurrent();
|
|
|
|
|
|
|
|
if(SymbolInfoTick(_Symbol, last_tick))
|
|
|
|
{
|
|
|
|
current_price = last_tick.last;
|
|
|
|
if(tickCount % 100 == 0)
|
|
|
|
Print("Current price: ", current_price, " (Ask: ", last_tick.ask, ", Bid: ", last_tick.bid, ")");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Print("Error getting tick data");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if trading is allowed
|
|
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED))
|
|
|
|
{
|
|
|
|
Print("Trading is not allowed. Check AutoTrading button and Expert Advisors settings.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implement your position closing logic here
|
|
|
|
// For now, return false to indicate no action needed
|
|
|
|
// Update market condition
|
|
|
|
SMarketCondition condition = GetMarketCondition();
|
|
|
|
|
2025-08-16 11:53:49 -04:00
|
|
|
// Get current price and time for position management
|
|
|
|
// Update current open trades count
|
2025-08-02 22:17:29 -04:00
|
|
|
g_currentOpenTrades = 0;
|
2025-08-16 11:53:49 -04:00
|
|
|
for(int i = 0; i < (int)PositionsTotal(); i++)
|
2025-08-02 22:17:29 -04:00
|
|
|
{
|
2025-08-16 11:53:49 -04:00
|
|
|
if(m_position.SelectByIndex(i) && m_position.Symbol() == _Symbol)
|
2025-08-02 22:17:29 -04:00
|
|
|
g_currentOpenTrades++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up old signals periodically
|
|
|
|
static datetime lastCleanup = 0;
|
|
|
|
if(TimeCurrent() - lastCleanup > 3600) // Clean up every hour
|
|
|
|
{
|
|
|
|
CleanupOldSignals();
|
|
|
|
lastCleanup = TimeCurrent();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we should close any positions
|
|
|
|
if(PositionsTotal() > 0)
|
|
|
|
{
|
2025-08-16 11:53:49 -04:00
|
|
|
for(int i = (int)PositionsTotal() - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if(m_position.SelectByIndex(i))
|
|
|
|
{
|
|
|
|
ulong ticket = m_position.Ticket();
|
|
|
|
string pos_symbol = m_position.Symbol();
|
|
|
|
if(ShouldClosePosition(ticket))
|
|
|
|
{
|
|
|
|
// Close the position by ticket using CTrade (works for netting & hedging)
|
2025-08-02 22:17:29 -04:00
|
|
|
if(m_trade.PositionClose(ticket))
|
|
|
|
{
|
|
|
|
Print("Position closed: ", ticket);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2025-08-16 11:53:49 -04:00
|
|
|
Print(
|
|
|
|
"Failed to close position ", ticket,
|
|
|
|
". Retcode=", (int)m_trade.ResultRetcode(),
|
|
|
|
" (", m_trade.ResultRetcodeDescription(), ")"
|
|
|
|
);
|
2025-08-02 22:17:29 -04:00
|
|
|
}
|
2025-08-16 11:53:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-08-02 22:17:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we can open new positions
|
|
|
|
if(g_currentOpenTrades < MAX_OPEN_TRADES)
|
|
|
|
{
|
2025-08-16 11:53:49 -04:00
|
|
|
if(!EnableMultiSymbol)
|
|
|
|
CheckSignals(tickCount); // In multi-symbol mode, entries are handled by OnTimer per symbol
|
2025-08-02 22:17:29 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(tickCount % 100 == 0)
|
2025-08-16 11:53:49 -04:00
|
|
|
Print("Maximum number of open trades per symbol reached (", g_currentOpenTrades, "/", MAX_OPEN_TRADES, ")");
|
2025-08-02 22:17:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update trading statistics and check trading modes once per 15 minutes
|
|
|
|
static datetime lastUpdateTime = 0;
|
|
|
|
datetime currentTime = TimeCurrent();
|
|
|
|
|
|
|
|
if(currentTime - lastUpdateTime >= 900) // Only update once every 15 minutes
|
|
|
|
{
|
|
|
|
// Create a string with all the information
|
|
|
|
string info = "=== 15-Minute Update ===\n";
|
|
|
|
info += "Time: " + TimeToString(currentTime) + "\n";
|
|
|
|
info += "Open Positions: " + IntegerToString(PositionsTotal()) + "\n";
|
|
|
|
info += "Trading Mode: " + EnumToString(g_tradingMode) + "\n\n";
|
|
|
|
info += "=== Trading Stats ===\n";
|
|
|
|
info += "Paper Trades: " + IntegerToString(g_paperStats.totalTrades) + "\n";
|
|
|
|
info += "Winners: " + IntegerToString(g_paperStats.winningTrades) + "\n";
|
|
|
|
info += "Win Rate: " + DoubleToString(g_paperStats.winRate, 2) + "%\n\n";
|
|
|
|
info += "Live Trades: " + IntegerToString(g_liveStats.totalTrades) + "\n";
|
|
|
|
info += "Winners: " + IntegerToString(g_liveStats.winningTrades) + "\n";
|
|
|
|
info += "Win Rate: " + DoubleToString(g_liveStats.winRate, 2) + "%";
|
|
|
|
|
|
|
|
// Display on chart
|
|
|
|
Comment(info);
|
|
|
|
|
|
|
|
// Also print to log
|
|
|
|
Print("=== 15-Minute Update ===");
|
|
|
|
Print("Current time: ", TimeToString(currentTime));
|
|
|
|
Print("Open positions: ", PositionsTotal());
|
|
|
|
Print("Current trading mode: ", EnumToString(g_tradingMode));
|
|
|
|
Print("Paper trades: ", g_paperStats.totalTrades, " (Winners: ", g_paperStats.winningTrades, ", Win rate: ", g_paperStats.winRate, "%)");
|
|
|
|
Print("Live trades: ", g_liveStats.totalTrades, " (Winners: ", g_liveStats.winningTrades, ", Win rate: ", g_liveStats.winRate, "%)");
|
|
|
|
|
|
|
|
UpdateTradingStats();
|
|
|
|
CheckTradingMode();
|
|
|
|
lastUpdateTime = currentTime;
|
|
|
|
|
|
|
|
// Log current indicator values for debugging
|
|
|
|
double maFast[3], maSlow[3];
|
|
|
|
if(CopyBuffer(handle_iMA_4, 0, 0, 3, maFast) == 3 &&
|
|
|
|
CopyBuffer(handle_iMA_5, 0, 0, 3, maSlow) == 3)
|
|
|
|
{
|
|
|
|
string maInfo = "\n\n=== Current MA Values ===\n";
|
|
|
|
maInfo += "Fast MA: " + DoubleToString(maFast[0], 6) + ", " + DoubleToString(maFast[1], 6) + ", " + DoubleToString(maFast[2], 6) + "\n";
|
|
|
|
maInfo += "Slow MA: " + DoubleToString(maSlow[0], 6) + ", " + DoubleToString(maSlow[1], 6) + ", " + DoubleToString(maSlow[2], 6);
|
|
|
|
|
|
|
|
// Add MA info to chart
|
|
|
|
Comment(info + maInfo);
|
|
|
|
|
|
|
|
// Also print to log
|
|
|
|
Print("Current MA Values - Fast: ", maFast[0], ", ", maFast[1], ", ", maFast[2]);
|
|
|
|
Print("Current MA Values - Slow: ", maSlow[0], ", ", maSlow[1], ", ", maSlow[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check for buy signal based on MA crossover |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CheckBuySignal()
|
|
|
|
{
|
|
|
|
// Check if indicators are valid
|
|
|
|
if(handle_iMA_4 == INVALID_HANDLE || handle_iMA_5 == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Error: Indicator handles not valid in CheckBuySignal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use dynamic arrays for indicator values
|
|
|
|
double maFast[];
|
|
|
|
double maSlow[];
|
|
|
|
|
|
|
|
// Set as series and resize arrays
|
|
|
|
ArraySetAsSeries(maFast, true);
|
|
|
|
ArraySetAsSeries(maSlow, true);
|
|
|
|
|
|
|
|
// Resize arrays to hold 3 values (current and 2 previous)
|
|
|
|
ArrayResize(maFast, 3);
|
|
|
|
ArrayResize(maSlow, 3);
|
|
|
|
|
|
|
|
// Copy more bars to ensure we have enough data
|
|
|
|
if(CopyBuffer(handle_iMA_4, 0, 0, 3, maFast) != 3 ||
|
|
|
|
CopyBuffer(handle_iMA_5, 0, 0, 3, maSlow) != 3)
|
|
|
|
{
|
|
|
|
Print("Error copying indicator buffers in CheckBuySignal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for valid values
|
|
|
|
if(maFast[0] == 0 || maFast[1] == 0 || maSlow[0] == 0 || maSlow[1] == 0)
|
|
|
|
{
|
|
|
|
Print("Warning: Indicator values not ready");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for buy signal (fast MA crosses above slow MA)
|
|
|
|
bool signal = (maFast[1] <= maSlow[1] && maFast[0] > maSlow[0]);
|
|
|
|
|
|
|
|
if(signal)
|
|
|
|
{
|
|
|
|
Print("BUY Signal: Fast MA(", maFast[0], ") crossed above Slow MA(", maSlow[0], ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
return signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check for sell signal based on MA crossover |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CheckSellSignal()
|
|
|
|
{
|
|
|
|
// Check if indicators are valid
|
|
|
|
if(handle_iMA_4 == INVALID_HANDLE || handle_iMA_5 == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Error: Indicator handles not valid in CheckSellSignal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use dynamic arrays for indicator values
|
|
|
|
double maFast[];
|
|
|
|
double maSlow[];
|
|
|
|
|
|
|
|
// Set as series and resize arrays
|
|
|
|
ArraySetAsSeries(maFast, true);
|
|
|
|
ArraySetAsSeries(maSlow, true);
|
|
|
|
|
|
|
|
// Resize arrays to hold 3 values (current and 2 previous)
|
|
|
|
ArrayResize(maFast, 3);
|
|
|
|
ArrayResize(maSlow, 3);
|
|
|
|
|
|
|
|
// Copy more bars to ensure we have enough data
|
|
|
|
if(CopyBuffer(handle_iMA_4, 0, 0, 3, maFast) != 3 ||
|
|
|
|
CopyBuffer(handle_iMA_5, 0, 0, 3, maSlow) != 3)
|
|
|
|
{
|
|
|
|
Print("Error copying indicator buffers in CheckSellSignal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for valid values
|
|
|
|
if(maFast[0] == 0 || maFast[1] == 0 || maSlow[0] == 0 || maSlow[1] == 0)
|
|
|
|
{
|
|
|
|
Print("Warning: Indicator values not ready");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for sell signal (fast MA crosses below slow MA)
|
|
|
|
bool signal = (maFast[1] >= maSlow[1] && maFast[0] < maSlow[0]);
|
|
|
|
|
|
|
|
if(signal)
|
|
|
|
{
|
|
|
|
Print("SELL Signal: Fast MA(", maFast[0], ") crossed below Slow MA(", maSlow[0], ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
return signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CheckTradingMode()
|
|
|
|
{
|
|
|
|
// Check if we should switch to live mode
|
|
|
|
if(g_tradingMode == MODE_PAPER &&
|
|
|
|
g_paperStats.totalTrades >= MIN_PAPER_TRADES &&
|
|
|
|
g_paperStats.winRate >= MinWinRateForLive)
|
|
|
|
{
|
|
|
|
g_tradingMode = MODE_HYBRID;
|
|
|
|
Print("Switching to HYBRID trading mode. Paper performance: ", g_paperStats.winRate, "% win rate");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we should switch back to paper mode
|
|
|
|
if((g_tradingMode == MODE_HYBRID || g_tradingMode == MODE_LIVE) &&
|
|
|
|
(g_paperStats.winRate < (MinWinRateForLive * 0.8) || g_paperStats.profitFactor < 1.2))
|
|
|
|
{
|
|
|
|
g_tradingMode = MODE_PAPER;
|
|
|
|
Print("Switching back to PAPER trading mode due to performance drop");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we should switch to full live mode
|
|
|
|
if(g_tradingMode == MODE_HYBRID &&
|
|
|
|
g_paperStats.totalTrades >= MIN_PAPER_TRADES * 2 &&
|
|
|
|
g_paperStats.winRate >= MinLiveWinRate)
|
|
|
|
{
|
|
|
|
g_tradingMode = MODE_LIVE;
|
|
|
|
Print("Switching to FULL LIVE trading mode. Performance: ", g_paperStats.winRate, "% win rate");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void LearnFromTrades()
|
|
|
|
{
|
|
|
|
// Simple learning logic - can be enhanced with more sophisticated algorithms
|
2025-08-16 11:53:49 -04:00
|
|
|
// Use combined statistics from paper + live trades
|
|
|
|
int totalTrades = g_paperStats.totalTrades + g_liveStats.totalTrades;
|
|
|
|
int winningTrades = g_paperStats.winningTrades + g_liveStats.winningTrades;
|
|
|
|
double combinedWinRate = (totalTrades > 0) ? ((double)winningTrades / (double)totalTrades) * 100.0 : 0.0;
|
|
|
|
|
|
|
|
if(combinedWinRate > 70.0)
|
2025-08-02 22:17:29 -04:00
|
|
|
{
|
|
|
|
// If winning, be more aggressive
|
|
|
|
g_learningParams.riskPerTrade = MathMin(2.0, g_learningParams.riskPerTrade * 1.1);
|
|
|
|
g_learningParams.takeProfitPips = MathMin(100.0, g_learningParams.takeProfitPips * 1.05);
|
|
|
|
g_learningParams.stopLossPips = MathMax(10.0, g_learningParams.stopLossPips * 0.95);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If losing, be more conservative
|
|
|
|
g_learningParams.riskPerTrade = MathMax(0.1, g_learningParams.riskPerTrade * 0.9);
|
|
|
|
g_learningParams.takeProfitPips = MathMax(20.0, g_learningParams.takeProfitPips * 0.95);
|
|
|
|
g_learningParams.stopLossPips = MathMin(50.0, g_learningParams.stopLossPips * 1.05);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adjust MA periods based on volatility
|
|
|
|
SMarketCondition condition = GetMarketCondition();
|
|
|
|
if(condition.volatility > 70)
|
|
|
|
{
|
|
|
|
// In high volatility, use slower MAs
|
|
|
|
g_learningParams.maFastPeriod = MathMin(14, g_learningParams.maFastPeriod + 1);
|
|
|
|
g_learningParams.maSlowPeriod = MathMin(28, g_learningParams.maSlowPeriod + 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if(condition.volatility < 30)
|
|
|
|
{
|
|
|
|
// In low volatility, use faster MAs
|
|
|
|
g_learningParams.maFastPeriod = MathMax(5, g_learningParams.maFastPeriod - 1);
|
|
|
|
g_learningParams.maSlowPeriod = MathMax(10, g_learningParams.maSlowPeriod - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure fast MA is always faster than slow MA
|
|
|
|
if(g_learningParams.maFastPeriod >= g_learningParams.maSlowPeriod)
|
|
|
|
{
|
|
|
|
g_learningParams.maSlowPeriod = g_learningParams.maFastPeriod + 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
Print("Learning complete. New parameters: ",
|
|
|
|
"Risk: ", g_learningParams.riskPerTrade,
|
|
|
|
", TP: ", g_learningParams.takeProfitPips,
|
|
|
|
", SL: ", g_learningParams.stopLossPips,
|
|
|
|
", MA Fast: ", g_learningParams.maFastPeriod,
|
|
|
|
", MA Slow: ", g_learningParams.maSlowPeriod);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void SaveTradingState()
|
|
|
|
{
|
|
|
|
// Implementation to save trading state to a file
|
|
|
|
// This is a placeholder - implement actual file I/O as needed
|
|
|
|
Print("Trading state saved");
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void LoadTradingState()
|
|
|
|
{
|
|
|
|
// Implementation to load trading state from a file
|
|
|
|
// This is a placeholder - implement actual file I/O as needed
|
|
|
|
Print("Trading state loaded");
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate position size based on risk percentage |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CalculatePositionSize(double stopLossPips, double riskPercent = 1.0)
|
|
|
|
{
|
|
|
|
// Get symbol and account information
|
|
|
|
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
|
|
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
|
|
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
|
|
|
|
// Log input parameters
|
|
|
|
Print("Calculating position size:");
|
|
|
|
Print(" Stop Loss: ", stopLossPips, " pips");
|
|
|
|
Print(" Risk %: ", riskPercent);
|
|
|
|
Print(" Account Balance: $", balance);
|
|
|
|
Print(" Symbol Info - Tick Size: ", tickSize, ", Tick Value: $", tickValue, ", Lot Step: ", lotStep);
|
|
|
|
|
|
|
|
// Validate inputs
|
|
|
|
if(stopLossPips <= 0)
|
|
|
|
{
|
|
|
|
Print("Error: Invalid stop loss pips: ", stopLossPips);
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(tickSize <= 0 || tickValue <= 0 || lotStep <= 0)
|
|
|
|
{
|
|
|
|
Print("Error: Invalid symbol parameters - TickSize: ", tickSize, ", TickValue: ", tickValue, ", LotStep: ", lotStep);
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate risk amount in account currency
|
|
|
|
double riskAmount = balance * (riskPercent / 100.0);
|
|
|
|
Print(" Risk Amount: $", riskAmount);
|
|
|
|
|
|
|
|
// Calculate value per pip
|
|
|
|
double pipValue = tickValue / (tickSize / _Point);
|
|
|
|
Print(" Pip Value: $", pipValue);
|
|
|
|
|
|
|
|
// Calculate money at risk per lot
|
|
|
|
double moneyRiskPerLot = stopLossPips * pipValue;
|
|
|
|
Print(" Money Risk per Lot: $", moneyRiskPerLot);
|
|
|
|
|
|
|
|
if(moneyRiskPerLot <= 0)
|
|
|
|
{
|
|
|
|
Print("Error: Invalid money risk per lot calculation: ", moneyRiskPerLot);
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate position size in lots
|
|
|
|
double lots = riskAmount / moneyRiskPerLot;
|
|
|
|
Print(" Raw Lots: ", lots);
|
|
|
|
|
|
|
|
// Round to nearest lot step
|
|
|
|
lots = MathFloor(lots / lotStep) * lotStep;
|
|
|
|
Print(" Rounded Lots: ", lots);
|
|
|
|
|
|
|
|
// Ensure position size is within allowed range
|
|
|
|
lots = MathMax(minLot, MathMin(lots, maxLot));
|
|
|
|
Print(" Final Lots (after min/max check): ", lots);
|
|
|
|
|
|
|
|
if(lots < minLot)
|
|
|
|
{
|
|
|
|
Print("Error: Calculated lot size (", lots, ") is below minimum allowed (", minLot, ")");
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(lots > maxLot)
|
|
|
|
{
|
|
|
|
Print("Warning: Calculated lot size (", lots, ") exceeds maximum allowed (", maxLot, "). Using maximum.");
|
|
|
|
lots = maxLot;
|
|
|
|
}
|
|
|
|
|
|
|
|
Print(" Final Position Size: ", lots, " lots");
|
|
|
|
return lots;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if a position should be closed |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool ShouldClosePosition(ulong ticket)
|
|
|
|
{
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
{
|
|
|
|
Print("Failed to select position ", ticket);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get position details
|
|
|
|
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
double profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
double stopLoss = PositionGetDouble(POSITION_SL);
|
|
|
|
double takeProfit = PositionGetDouble(POSITION_TP);
|
|
|
|
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
|
|
|
|
// Check if stop loss or take profit was hit
|
|
|
|
if((posType == POSITION_TYPE_BUY && (currentPrice <= stopLoss || currentPrice >= takeProfit)) ||
|
|
|
|
(posType == POSITION_TYPE_SELL && (currentPrice >= stopLoss || currentPrice <= takeProfit)))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for reversal signals
|
|
|
|
bool isBuy = (posType == POSITION_TYPE_BUY);
|
|
|
|
bool reversalSignal = isBuy ? CheckSellSignal() : CheckBuySignal();
|
|
|
|
|
|
|
|
// Consider taking profits if we have a reversal signal
|
|
|
|
if(reversalSignal && profit > 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for extreme market conditions
|
|
|
|
SMarketCondition condition = GetMarketCondition();
|
|
|
|
if(condition.volatility > 80)
|
|
|
|
{
|
|
|
|
// Close position if volatility is too high and we have a decent profit
|
|
|
|
if(profit > 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Update trading statistics |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void UpdateTradingStats()
|
|
|
|
{
|
|
|
|
// Update live trading statistics
|
|
|
|
g_liveStats.Init();
|
|
|
|
|
|
|
|
// Get all closed positions from history
|
|
|
|
HistorySelect(0, TimeCurrent());
|
|
|
|
int totalDeals = HistoryDealsTotal();
|
|
|
|
|
|
|
|
for(int i = 0; i < totalDeals; i++)
|
|
|
|
{
|
|
|
|
ulong ticket = HistoryDealGetTicket(i);
|
|
|
|
if(ticket > 0)
|
|
|
|
{
|
|
|
|
// Process deal information
|
|
|
|
STradeRecord trade = {};
|
|
|
|
trade.Init();
|
|
|
|
trade.ticket = ticket;
|
|
|
|
trade.symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
|
|
|
|
trade.type = (ENUM_ORDER_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
|
|
|
|
trade.volume = HistoryDealGetDouble(ticket, DEAL_VOLUME);
|
|
|
|
trade.openPrice = HistoryDealGetDouble(ticket, DEAL_PRICE);
|
|
|
|
trade.profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
|
|
|
|
trade.commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
|
|
|
|
trade.swap = HistoryDealGetDouble(ticket, DEAL_SWAP);
|
|
|
|
trade.openTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
|
|
|
|
trade.isLive = true;
|
|
|
|
|
|
|
|
// Update statistics (isWin is calculated in STradingStats.Update)
|
|
|
|
g_liveStats.Update(trade);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Log current status
|
|
|
|
Print("=== Trading Status ===");
|
|
|
|
Print("Mode: ", EnumToString(g_tradingMode));
|
|
|
|
Print("Paper Trades: ", g_paperStats.totalTrades, ", Win Rate: ", g_paperStats.winRate, "%");
|
|
|
|
Print("Live Trades: ", g_liveStats.totalTrades, ", Win Rate: ", g_liveStats.winRate, "%");
|
|
|
|
Print("Current Open Trades: ", g_currentOpenTrades, " (Max: ", MAX_OPEN_TRADES, ")");
|
|
|
|
Print("Current Balance: ", AccountInfoDouble(ACCOUNT_BALANCE));
|
|
|
|
Print("Current Equity: ", AccountInfoDouble(ACCOUNT_EQUITY));
|
|
|
|
Print("Free Margin: ", AccountInfoDouble(ACCOUNT_MARGIN_FREE));
|
|
|
|
Print("Max Drawdown: ", g_liveStats.maxDrawdown, "%");
|
|
|
|
Print("====================");
|
|
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|