Files
xau-ai-trading-bot/ea-research/xaubot-mq5/Experts/XAUBot_Pro_V3.mq5
T
buckybonez c0976c4518 feat: implement Professor AI recommendations v0.2.2 (5 critical fixes)
Exit Strategy v6.6 "Professor AI Validated" - All recommendations implemented

FIX #1: Remove Misleading Debug Code
- Removed manual trajectory calculation (line 1262-1269)
- Trajectory predictor was CORRECT, debug comparison was WRONG
- Cleaned up false "bug found" warnings

FIX #2: Peak Detection Logic (CHECK 0A.4)
- Detects approaching peak (vel > 0, accel < 0)
- Holds position if peak within 30s and 15%+ profit ahead
- Suppresses fuzzy exits during peak approach
- Target: Peak capture 38% -> 70%+
- Added peak_hold_active field to PositionGuard

FIX #3: London False Breakout Filter
- London session + ATR ratio < 1.2 = whipsaw risk
- Requires ML confidence 70% (instead of 60%)
- Prevents false breakouts during low volatility
- Implemented in main_live.py before signal logic

FIX #4: Enhanced Kelly Partial Exit Strategy
- Active for all profits >= tp_min * 0.5 (not just >$8)
- Recommends partial exits for better peak capture
- Full exit when Kelly suggests >70% close
- Note: Actual partial close needs MT5 volume parameter (TODO)

FIX #5: Unicode Encoding Fixes
- Added UTF-8 encoding to file logger
- Replaced all emoji (⚠️ -> [WARNING]) and arrows (-> -> ->)
- No more UnicodeEncodeError on Windows console
- Fixed in 11 src/*.py files

Expected Performance:
- Peak Capture: 38% -> 70%+ (+84%)
- Avg Profit: $2.00 -> $4.50 (+125%)
- Risk/Reward: 0.49 -> 1.2+ (+145%)
- Win Rate: Maintain 76%

Files Modified:
- src/smart_risk_manager.py (peak detection, Kelly, unicode)
- src/trajectory_predictor.py (unicode arrows)
- main_live.py (London filter, UTF-8 encoding)
- src/*.py (unicode cleanup: 11 files)
- VERSION (0.2.1 -> 0.2.2)
- CHANGELOG.md (comprehensive v0.2.2 docs)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 18:16:34 +07:00

1271 lines
42 KiB
Plaintext

//+------------------------------------------------------------------+
//| XAUBot_Pro_V3.mq5 |
//| Advanced M15 Gold Trading EA with Multi-Layer Quality Filtering |
//| Design: Capital Preservation Through Extreme Selectivity |
//| Brand: suriota |
//+------------------------------------------------------------------+
#property copyright "XAUBot Pro - suriota"
#property version "3.00"
#property description "4-Layer Quality + H1 Bias + ATR Adaptive + Patient Exits"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
//===========================================
// SECTION 1: INPUTS (1-150)
//===========================================
input group "=== Risk Management ==="
input double RiskPercent = 1.0; // Base risk per trade (%)
input double MinRiskPercent = 0.5; // Minimum risk after losses
input double MaxLot = 0.02; // Maximum lot size (safety cap)
input double MinLot = 0.01; // Minimum lot size
input double ATR_SL_Multiplier = 1.0; // ATR multiplier for SL
input double DailyLossLimit = 5.0; // Daily loss limit (%)
input double MonthlyLossLimit = 10.0; // Monthly loss limit (%)
input int MaxConsecutiveLosses = 3; // Max consecutive losses before halt
input group "=== Entry Filters ==="
input int EMA_Fast_M15 = 50; // M15 Fast EMA period
input int EMA_Slow_M15 = 200; // M15 Slow EMA period
input int EMA_Fast_H1 = 50; // H1 Fast EMA period
input int EMA_Slow_H1 = 200; // H1 Slow EMA period
input int ADX_Period = 14; // ADX period
input double ADX_Threshold = 25.0; // Minimum ADX for entry
input int RSI_Period = 14; // RSI period
input double MaxSpread = 20.0; // Maximum spread (points)
input int CooldownMinutes = 15; // Cooldown between trades
input int MaxPositions = 2; // Maximum concurrent positions
input int MaxTradesPerDay = 10; // Maximum trades per day
input double MinQualityScore = 60.0; // Minimum quality score (0-100)
input group "=== Exit Management ==="
input double TP_Hard_ATR = 2.0; // Hard TP (ATR multiplier)
input double BE_Trigger_ATR = 0.5; // Breakeven trigger (ATR)
input double BE_Lock_USD = 2.0; // Breakeven lock profit ($)
input double Trail_Trigger_ATR = 0.6; // Trailing stop trigger (ATR)
input double Trail_Distance_ATR = 0.3; // Trailing distance (ATR)
input double Hard_Stop_ATR = 0.6; // Hard stop loss (ATR)
input int MinTradeAgeMinutes = 5; // Minimum trade age for stops
input int TimeExit_Hour = 3; // Time exit if not profitable (hours)
input int AbsoluteExit_Hour = 5; // Absolute exit time (hours)
input group "=== Panel & Logging ==="
input bool ShowPanel = true; // Show info panel
input ENUM_BASE_CORNER PanelCorner = CORNER_LEFT_UPPER;
input int PanelOffsetX = 380;
input int PanelOffsetY = 10;
input bool EnableFileLog = true; // Enable file logging
input bool LogFilterRejects = false; // Log filter rejections
input int Magic = 202603; // Magic number
//===========================================
// SECTION 2: GLOBAL VARIABLES (151-250)
//===========================================
// Trading objects
CTrade trade;
CPositionInfo position;
CSymbolInfo symbolInfo;
// M15 Indicators
int handleEMAFast_M15, handleEMASlow_M15, handleADX_M15, handleRSI_M15, handleMACD_M15, handleATR_M15;
double emaFast_M15, emaSlow_M15, adxValue_M15, rsiValue_M15, macdMain_M15, macdSignal_M15, atrValue_M15;
// H1 Indicators
int handleEMAFast_H1, handleEMASlow_H1, handleRSI_H1, handleMACD_H1;
double emaFast_H1, emaSlow_H1, rsiValue_H1, macdMain_H1, macdSignal_H1;
// H1 Bias
int h1_bias = 0; // -1=bearish, 0=neutral, +1=bullish
string h1_bias_str = "NEUTRAL";
int h1_bull_count = 0;
int h1_bear_count = 0;
// Risk state
double currentRisk = 1.0;
int consecutiveWins = 0;
int consecutiveLosses = 0;
double dailyProfit = 0;
double dailyLoss = 0;
int dailyTrades = 0;
double monthlyProfit = 0;
double monthlyLoss = 0;
bool canTrade = true;
string stopReason = "";
// Position tracking
datetime lastTradeTime = 0;
datetime lastBarTime = 0;
double peakProfit = 0;
datetime positionOpenTime = 0;
bool hasRecovered = false;
// Quality scoring
double technicalQuality = 0;
double monthlyRiskMult = 1.0;
double intraRiskMult = 1.0;
int patternWinRate = 50;
// Logging
int logFileHandle = INVALID_HANDLE;
string currentLogFile = "";
datetime lastLogDate = 0;
int currentDay = 0;
int currentMonth = 0;
// Circuit breakers
bool dailyLimitReached = false;
bool monthlyLimitReached = false;
bool consecutiveLossHalt = false;
//===========================================
// SECTION 3: STRUCTS (251-400)
//===========================================
struct SessionInfo
{
bool isSydney;
bool isLondon;
bool isNewYork;
double riskMultiplier;
string name;
};
struct QualityScore
{
double atrStability; // 0-20
double priceEfficiency; // 0-20
double trendStrength; // 0-20
double spreadQuality; // 0-20
double h1Alignment; // 0-20
double total; // 0-100
bool passed;
};
//===========================================
// SECTION 4: INITIALIZATION (401-550)
//===========================================
int OnInit()
{
Print("╔════════════════════════════════════════╗");
Print("║ XAUBot Pro V3 - suriota ║");
Print("║ Advanced Multi-Layer Quality System ║");
Print("╚════════════════════════════════════════╝");
// Check timeframe
if(Period() != PERIOD_M15)
{
Alert("⚠️ WARNING: EA designed for M15 timeframe! Current: ", EnumToString(Period()));
}
// Initialize symbol
if(!symbolInfo.Name(_Symbol))
{
Print("ERROR: Failed to set symbol");
return INIT_FAILED;
}
trade.SetExpertMagicNumber(Magic);
// Create M15 indicators
handleEMAFast_M15 = iMA(_Symbol, PERIOD_M15, EMA_Fast_M15, 0, MODE_EMA, PRICE_CLOSE);
handleEMASlow_M15 = iMA(_Symbol, PERIOD_M15, EMA_Slow_M15, 0, MODE_EMA, PRICE_CLOSE);
handleADX_M15 = iADX(_Symbol, PERIOD_M15, ADX_Period);
handleRSI_M15 = iRSI(_Symbol, PERIOD_M15, RSI_Period, PRICE_CLOSE);
handleMACD_M15 = iMACD(_Symbol, PERIOD_M15, 12, 26, 9, PRICE_CLOSE);
handleATR_M15 = iATR(_Symbol, PERIOD_M15, 14);
// Create H1 indicators
handleEMAFast_H1 = iMA(_Symbol, PERIOD_H1, EMA_Fast_H1, 0, MODE_EMA, PRICE_CLOSE);
handleEMASlow_H1 = iMA(_Symbol, PERIOD_H1, EMA_Slow_H1, 0, MODE_EMA, PRICE_CLOSE);
handleRSI_H1 = iRSI(_Symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE);
handleMACD_H1 = iMACD(_Symbol, PERIOD_H1, 12, 26, 9, PRICE_CLOSE);
// Check handles
if(handleEMAFast_M15 == INVALID_HANDLE || handleEMASlow_M15 == INVALID_HANDLE ||
handleADX_M15 == INVALID_HANDLE || handleRSI_M15 == INVALID_HANDLE ||
handleMACD_M15 == INVALID_HANDLE || handleATR_M15 == INVALID_HANDLE ||
handleEMAFast_H1 == INVALID_HANDLE || handleEMASlow_H1 == INVALID_HANDLE ||
handleRSI_H1 == INVALID_HANDLE || handleMACD_H1 == INVALID_HANDLE)
{
Print("ERROR: Failed to create indicators");
return INIT_FAILED;
}
// Initialize risk
currentRisk = RiskPercent;
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
currentDay = dt.day;
currentMonth = dt.mon;
// Create panel
if(ShowPanel)
CreatePanel();
// Open log file
if(EnableFileLog)
OpenLogFile();
WriteLog("XAUBot Pro V3 - Initialization Complete");
WriteLog(StringFormat("Config: Risk=%.1f%% | ADX≥%.1f | Quality≥%.0f | MaxLot=%.2f",
RiskPercent, ADX_Threshold, MinQualityScore, MaxLot));
Print("✓ XAUBot Pro V3 initialized successfully");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Release indicators
IndicatorRelease(handleEMAFast_M15);
IndicatorRelease(handleEMASlow_M15);
IndicatorRelease(handleADX_M15);
IndicatorRelease(handleRSI_M15);
IndicatorRelease(handleMACD_M15);
IndicatorRelease(handleATR_M15);
IndicatorRelease(handleEMAFast_H1);
IndicatorRelease(handleEMASlow_H1);
IndicatorRelease(handleRSI_H1);
IndicatorRelease(handleMACD_H1);
if(ShowPanel)
DeletePanel();
if(EnableFileLog)
CloseLogFile();
Comment("");
Print("XAUBot Pro V3 stopped. Reason: ", reason);
}
//===========================================
// SECTION 5: MAIN TICK HANDLER (551-650)
//===========================================
void OnTick()
{
// New bar detection
datetime currentBarTime = iTime(_Symbol, PERIOD_M15, 0);
bool newBar = (currentBarTime != lastBarTime);
// Always manage positions
ManagePosition();
// Update panel every 5 seconds (not every tick)
static datetime lastPanelUpdate = 0;
if(ShowPanel && TimeCurrent() - lastPanelUpdate >= 5)
{
UpdatePanel();
lastPanelUpdate = TimeCurrent();
}
if(!newBar)
return;
lastBarTime = currentBarTime;
// Check day/month rollover
CheckDayRollover();
// Update all data
if(!UpdateAllData())
return;
// Calculate H1 bias
CalculateH1Bias();
// Calculate quality score
CalculateQualityScore();
// Check entry if no position
if(CountOpenPositions() < MaxPositions)
CheckEntry();
}
//===========================================
// SECTION 6: H1 BIAS CALCULATION (651-800)
//===========================================
void CalculateH1Bias()
{
// Reset counters
h1_bull_count = 0;
h1_bear_count = 0;
// Indicator 1: EMA Trend (50 > 200 = bull)
if(emaFast_H1 > emaSlow_H1)
h1_bull_count++;
else if(emaFast_H1 < emaSlow_H1)
h1_bear_count++;
// Indicator 2: Price position relative to EMAs
double close_H1 = iClose(_Symbol, PERIOD_H1, 1);
if(close_H1 > emaFast_H1 && close_H1 > emaSlow_H1)
h1_bull_count++;
else if(close_H1 < emaFast_H1 && close_H1 < emaSlow_H1)
h1_bear_count++;
// Indicator 3: RSI
if(rsiValue_H1 > 55.0)
h1_bull_count++;
else if(rsiValue_H1 < 45.0)
h1_bear_count++;
// Indicator 4: MACD
if(macdMain_H1 > macdSignal_H1 && macdMain_H1 > 0)
h1_bull_count++;
else if(macdMain_H1 < macdSignal_H1 && macdMain_H1 < 0)
h1_bear_count++;
// Indicator 5: Candle structure (last 3 H1 candles)
int bullCandles = 0;
int bearCandles = 0;
for(int i = 1; i <= 3; i++)
{
double open = iOpen(_Symbol, PERIOD_H1, i);
double close = iClose(_Symbol, PERIOD_H1, i);
if(close > open)
bullCandles++;
else if(close < open)
bearCandles++;
}
if(bullCandles >= 2)
h1_bull_count++;
else if(bearCandles >= 2)
h1_bear_count++;
// Determine bias (need 3+ indicators)
if(h1_bull_count >= 3)
{
h1_bias = 1;
h1_bias_str = "▲ BULL";
}
else if(h1_bear_count >= 3)
{
h1_bias = -1;
h1_bias_str = "▼ BEAR";
}
else
{
h1_bias = 0;
h1_bias_str = "━ NEUTRAL";
}
}
//===========================================
// SECTION 7: M15 SIGNAL DETECTION (801-950)
//===========================================
bool CheckM15BuySignal()
{
// H1 must be bullish or neutral
if(h1_bias < 0)
return false;
// M15 EMA trend must be bullish
if(emaFast_M15 <= emaSlow_M15)
return false;
// Price near EMA50 (pullback)
double close = iClose(_Symbol, PERIOD_M15, 1);
double distanceToEMA = MathAbs(close - emaFast_M15) / atrValue_M15;
if(distanceToEMA > 0.3) // Too far from EMA
return false;
// RSI in acceptable range
if(rsiValue_M15 < 40.0 || rsiValue_M15 > 70.0)
return false;
// ADX shows trend
if(adxValue_M15 < ADX_Threshold)
return false;
// MACD bullish
if(macdMain_M15 <= macdSignal_M15)
return false;
return true;
}
bool CheckM15SellSignal()
{
// H1 must be bearish or neutral
if(h1_bias > 0)
return false;
// M15 EMA trend must be bearish
if(emaFast_M15 >= emaSlow_M15)
return false;
// Price near EMA50 (pullback)
double close = iClose(_Symbol, PERIOD_M15, 1);
double distanceToEMA = MathAbs(close - emaFast_M15) / atrValue_M15;
if(distanceToEMA > 0.3)
return false;
// RSI in acceptable range
if(rsiValue_M15 > 60.0 || rsiValue_M15 < 30.0)
return false;
// ADX shows trend
if(adxValue_M15 < ADX_Threshold)
return false;
// MACD bearish
if(macdMain_M15 >= macdSignal_M15)
return false;
return true;
}
//===========================================
// SECTION 8: QUALITY SCORING (951-1150)
//===========================================
double GetMonthlyRiskMultiplier()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int month = dt.mon;
// Historical volatility patterns
if(month == 2 || month == 10) // Feb, Oct - risk-off
return 0.6;
else if(month == 9) // Sep - high activity
return 1.1;
else if(month == 3 || month == 5 || month == 7 || month == 11) // Normal
return 1.0;
else
return 0.8; // Other months - cautious
}
void CalculateQualityScore()
{
QualityScore qs;
// Layer 1: Monthly Risk Multiplier
monthlyRiskMult = GetMonthlyRiskMultiplier();
// Layer 2: Technical Quality (0-100)
// 1. ATR Stability (20 pts)
double atr_24h_arr[];
ArraySetAsSeries(atr_24h_arr, true);
CopyBuffer(handleATR_M15, 0, 0, 96, atr_24h_arr); // 96 bars = 24h
double atr_avg = 0;
for(int i = 0; i < 96; i++)
atr_avg += atr_24h_arr[i];
atr_avg /= 96;
double atr_deviation = MathAbs(atrValue_M15 - atr_avg) / atr_avg;
if(atr_deviation < 0.1)
qs.atrStability = 20;
else if(atr_deviation < 0.2)
qs.atrStability = 15;
else if(atr_deviation < 0.3)
qs.atrStability = 10;
else
qs.atrStability = 5;
// 2. Price Efficiency (20 pts) - EMA separation in ATR units
double ema_separation = MathAbs(emaFast_M15 - emaSlow_M15) / atrValue_M15;
if(ema_separation > 2.0)
qs.priceEfficiency = 20;
else if(ema_separation > 1.5)
qs.priceEfficiency = 15;
else if(ema_separation > 1.0)
qs.priceEfficiency = 10;
else
qs.priceEfficiency = 5;
// 3. Trend Strength ADX (20 pts)
if(adxValue_M15 >= 40.0)
qs.trendStrength = 20;
else if(adxValue_M15 >= 30.0)
qs.trendStrength = 15;
else if(adxValue_M15 >= 25.0)
qs.trendStrength = 10;
else
qs.trendStrength = 0;
// 4. Spread Quality (20 pts)
double spread = symbolInfo.Spread();
if(spread <= 10.0)
qs.spreadQuality = 20;
else if(spread <= 15.0)
qs.spreadQuality = 15;
else if(spread <= 20.0)
qs.spreadQuality = 10;
else
qs.spreadQuality = 0;
// 5. H1-M15 Alignment (20 pts)
bool m15_bull = (emaFast_M15 > emaSlow_M15);
bool m15_bear = (emaFast_M15 < emaSlow_M15);
if((h1_bias == 1 && m15_bull) || (h1_bias == -1 && m15_bear))
qs.h1Alignment = 20; // Perfect alignment
else if(h1_bias == 0)
qs.h1Alignment = 10; // Neutral H1
else
qs.h1Alignment = 0; // Conflict
qs.total = qs.atrStability + qs.priceEfficiency + qs.trendStrength + qs.spreadQuality + qs.h1Alignment;
qs.passed = (qs.total >= MinQualityScore);
technicalQuality = qs.total;
// Layer 3: Intra-Period Risk Manager
intraRiskMult = 1.0;
if(consecutiveLosses >= 2)
intraRiskMult = 0.5;
else if(consecutiveLosses == 1)
intraRiskMult = 0.75;
// Layer 4: Pattern Filter (rolling win rate)
// Simplified - use consecutive wins/losses as proxy
if(consecutiveWins >= 2)
patternWinRate = 70;
else if(consecutiveWins == 1)
patternWinRate = 60;
else if(consecutiveLosses == 0)
patternWinRate = 50;
else if(consecutiveLosses == 1)
patternWinRate = 40;
else
patternWinRate = 30;
}
//===========================================
// SECTION 9: ENTRY FILTERS (1151-1300)
//===========================================
bool CheckAllEntryFilters()
{
// Filter 1: Quality Check
if(technicalQuality < MinQualityScore)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: Low quality score (%.0f < %.0f)", technicalQuality, MinQualityScore), "FILTER");
return false;
}
// Filter 2: Circuit Breakers
if(!canTrade)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: Circuit breaker active (%s)", stopReason), "FILTER");
return false;
}
// Filter 3: Spread
if(symbolInfo.Spread() > MaxSpread)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: Spread too high (%.0f > %.0f)", symbolInfo.Spread(), MaxSpread), "FILTER");
return false;
}
// Filter 4: ADX
if(adxValue_M15 < ADX_Threshold)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: Weak trend (ADX %.1f < %.1f)", adxValue_M15, ADX_Threshold), "FILTER");
return false;
}
// Filter 5: Session (preferably London/NY)
SessionInfo session = GetCurrentSession();
// Allow all sessions but with different risk multipliers (already factored into lot calculation)
// Filter 6: Cooldown
if(TimeCurrent() - lastTradeTime < CooldownMinutes * 60)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: Cooldown period (%d min)", CooldownMinutes), "FILTER");
return false;
}
// Filter 7: Max Positions
if(CountOpenPositions() >= MaxPositions)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: Max positions reached (%d)", MaxPositions), "FILTER");
return false;
}
// Filter 8: ATR Volatility Range
if(atrValue_M15 < 5.0 || atrValue_M15 > 25.0)
{
if(LogFilterRejects)
WriteLog(StringFormat("SKIP: ATR out of range (%.2f not in 5-25)", atrValue_M15), "FILTER");
return false;
}
// Filter 9: Time-of-Hour (skip 30 min before H1 close)
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.min >= 30) // Between :30 and :59
{
if(LogFilterRejects)
WriteLog("SKIP: Near H1 close (avoiding instability)", "FILTER");
return false;
}
return true;
}
void CheckEntry()
{
if(!CheckAllEntryFilters())
return;
// Check signals
bool buySignal = CheckM15BuySignal();
bool sellSignal = CheckM15SellSignal();
if(buySignal)
{
WriteLog(StringFormat("SIGNAL: BUY | H1:%s(%d/%d) | Q:%.0f | ADX:%.1f | RSI:%.1f",
h1_bias_str, h1_bull_count, h1_bear_count, technicalQuality, adxValue_M15, rsiValue_M15), "SIGNAL");
OpenTrade(ORDER_TYPE_BUY);
}
else if(sellSignal)
{
WriteLog(StringFormat("SIGNAL: SELL | H1:%s(%d/%d) | Q:%.0f | ADX:%.1f | RSI:%.1f",
h1_bias_str, h1_bull_count, h1_bear_count, technicalQuality, adxValue_M15, rsiValue_M15), "SIGNAL");
OpenTrade(ORDER_TYPE_SELL);
}
}
//===========================================
// SECTION 10: POSITION MANAGEMENT (1301-1500)
//===========================================
void ManagePosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!position.SelectByIndex(i))
continue;
if(position.Symbol() != _Symbol || position.Magic() != Magic)
continue;
double currentPrice = (position.Type() == POSITION_TYPE_BUY) ? symbolInfo.Bid() : symbolInfo.Ask();
double openPrice = position.PriceOpen();
double currentProfit = position.Profit();
double profitDistance = (position.Type() == POSITION_TYPE_BUY) ? (currentPrice - openPrice) : (openPrice - currentPrice);
double profitInATR = profitDistance / atrValue_M15;
// Update peak profit
if(currentProfit > peakProfit)
peakProfit = currentProfit;
// Calculate trade age
datetime tradeAge = TimeCurrent() - positionOpenTime;
int ageMinutes = (int)(tradeAge / 60);
// PRIORITY 1: Hard Take Profit
if(profitInATR >= TP_Hard_ATR)
{
WriteLog(StringFormat("EXIT: Hard TP reached (%.2f ATR)", profitInATR), "EXIT");
ClosePosition(position.Ticket(), "Hard TP");
continue;
}
// PRIORITY 2: Breakeven Shield
if(peakProfit >= BE_Trigger_ATR * atrValue_M15)
{
if(currentProfit < BE_Lock_USD)
{
WriteLog(StringFormat("EXIT: Breakeven shield (peak $%.2f, now $%.2f)", peakProfit, currentProfit), "EXIT");
ClosePosition(position.Ticket(), "BE Shield");
continue;
}
}
// PRIORITY 3: ATR Trailing Stop
if(peakProfit >= Trail_Trigger_ATR * atrValue_M15 && ageMinutes >= MinTradeAgeMinutes)
{
double trailFloor = peakProfit - (Trail_Distance_ATR * atrValue_M15);
if(currentProfit < trailFloor)
{
WriteLog(StringFormat("EXIT: ATR trailing (peak $%.2f, floor $%.2f)", peakProfit, trailFloor), "EXIT");
ClosePosition(position.Ticket(), "ATR Trail");
continue;
}
}
// PRIORITY 4: ATR Hard Stop
if(profitInATR <= -Hard_Stop_ATR && ageMinutes >= MinTradeAgeMinutes)
{
WriteLog(StringFormat("EXIT: ATR hard stop (%.2f ATR loss)", profitInATR), "EXIT");
ClosePosition(position.Ticket(), "ATR Stop");
continue;
}
// PRIORITY 5: Momentum Reversal
bool ema_cross_against = false;
if(position.Type() == POSITION_TYPE_BUY && emaFast_M15 < emaSlow_M15)
ema_cross_against = true;
else if(position.Type() == POSITION_TYPE_SELL && emaFast_M15 > emaSlow_M15)
ema_cross_against = true;
if(ema_cross_against && profitInATR < 0.3)
{
WriteLog(StringFormat("EXIT: EMA reversal (profit %.2f ATR < 0.3)", profitInATR), "EXIT");
ClosePosition(position.Ticket(), "Momentum Reversal");
continue;
}
// PRIORITY 6: Time-Based Exit
if(ageMinutes >= TimeExit_Hour * 60 && currentProfit <= 0)
{
WriteLog(StringFormat("EXIT: Time exit (%d min, not profitable)", ageMinutes), "EXIT");
ClosePosition(position.Ticket(), "Time Exit");
continue;
}
if(ageMinutes >= AbsoluteExit_Hour * 60)
{
WriteLog(StringFormat("EXIT: Absolute time limit (%d min)", ageMinutes), "EXIT");
ClosePosition(position.Ticket(), "Absolute Exit");
continue;
}
// PRIORITY 7: Weekend Close
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if((dt.day_of_week == 5 && dt.hour >= 22) || (dt.day_of_week == 6 && dt.hour < 5))
{
if(currentProfit > 0 || profitInATR > -0.3)
{
WriteLog(StringFormat("EXIT: Weekend close (profit $%.2f)", currentProfit), "EXIT");
ClosePosition(position.Ticket(), "Weekend");
continue;
}
}
}
}
void ClosePosition(ulong ticket, string reason)
{
if(trade.PositionClose(ticket))
{
WriteLog(StringFormat("POSITION CLOSED: %s", reason), "TRADE");
peakProfit = 0;
hasRecovered = false;
}
else
{
WriteLog(StringFormat("CLOSE FAILED: %s | Error: %s", reason, trade.ResultRetcodeDescription()), "ERROR");
}
}
//===========================================
// SECTION 11: RISK CALCULATIONS (1501-1650)
//===========================================
void OpenTrade(ENUM_ORDER_TYPE orderType)
{
// Check daily trade limit
if(dailyTrades >= MaxTradesPerDay)
{
WriteLog(StringFormat("SKIP: Daily trade limit reached (%d)", MaxTradesPerDay), "FILTER");
return;
}
double price = (orderType == ORDER_TYPE_BUY) ? symbolInfo.Ask() : symbolInfo.Bid();
// Calculate SL/TP
double slDistance = atrValue_M15 * ATR_SL_Multiplier;
double tpDistance = atrValue_M15 * TP_Hard_ATR;
double sl = NormalizeDouble((orderType == ORDER_TYPE_BUY) ? (price - slDistance) : (price + slDistance), _Digits);
double tp = NormalizeDouble((orderType == ORDER_TYPE_BUY) ? (price + tpDistance) : (price - tpDistance), _Digits);
// Calculate lot size with all multipliers
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
SessionInfo session = GetCurrentSession();
double effectiveRisk = currentRisk * monthlyRiskMult * intraRiskMult * session.riskMultiplier;
double riskMoney = balance * (effectiveRisk / 100.0);
double tickValue = symbolInfo.TickValue();
double tickSize = symbolInfo.TickSize();
double slInTicks = MathAbs(price - sl) / tickSize;
double lotSize = riskMoney / (slInTicks * tickValue);
lotSize = NormalizeDouble(lotSize, 2);
lotSize = MathMax(MinLot, MathMin(MaxLot, lotSize));
// Open position
if(trade.PositionOpen(_Symbol, orderType, lotSize, price, sl, tp, "XAUBot-V3"))
{
string tradeType = (orderType == ORDER_TYPE_BUY ? "BUY" : "SELL");
Print("✓ ", tradeType, " opened: Lot=", lotSize, " Price=", price);
WriteLog(StringFormat("TRADE OPEN: %s | Lot:%.2f | Price:%.5f | SL:%.5f | TP:%.5f | ATR:%.2f | Risk:%.2f%% | Q:%.0f",
tradeType, lotSize, price, sl, tp, atrValue_M15, effectiveRisk, technicalQuality), "TRADE");
lastTradeTime = TimeCurrent();
positionOpenTime = TimeCurrent();
peakProfit = 0;
hasRecovered = false;
dailyTrades++;
}
else
{
WriteLog(StringFormat("TRADE FAILED: %s | Error: %s", (orderType == ORDER_TYPE_BUY ? "BUY" : "SELL"), trade.ResultRetcodeDescription()), "ERROR");
}
}
SessionInfo GetCurrentSession()
{
SessionInfo session;
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int hour = dt.hour;
// Sydney: 22:00-06:00 GMT
session.isSydney = (hour >= 22 || hour < 6);
// London: 07:00-16:00 GMT
session.isLondon = (hour >= 7 && hour < 16);
// New York: 13:00-22:00 GMT
session.isNewYork = (hour >= 13 && hour < 22);
if(session.isSydney)
{
session.name = "SYDNEY";
session.riskMultiplier = 0.5; // Lower liquidity
}
else if(session.isLondon || session.isNewYork)
{
session.name = session.isLondon ? "LONDON" : "NEW YORK";
session.riskMultiplier = 1.0; // Normal
}
else
{
session.name = "OFF-HOURS";
session.riskMultiplier = 0.7;
}
return session;
}
int CountOpenPositions()
{
int count = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
if(position.SelectByIndex(i))
{
if(position.Symbol() == _Symbol && position.Magic() == Magic)
count++;
}
}
return count;
}
//===========================================
// SECTION 12: PANEL UI (1651-1800)
//===========================================
void CreatePanel()
{
string prefix = "XAU_V3_";
color bgColor = C'20,20,30';
// Background (larger for more info)
ObjectCreate(0, prefix+"BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, prefix+"BG", OBJPROP_CORNER, PanelCorner);
ObjectSetInteger(0, prefix+"BG", OBJPROP_XDISTANCE, PanelOffsetX);
ObjectSetInteger(0, prefix+"BG", OBJPROP_YDISTANCE, PanelOffsetY);
ObjectSetInteger(0, prefix+"BG", OBJPROP_XSIZE, 280);
ObjectSetInteger(0, prefix+"BG", OBJPROP_YSIZE, 260);
ObjectSetInteger(0, prefix+"BG", OBJPROP_BGCOLOR, bgColor);
ObjectSetInteger(0, prefix+"BG", OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, prefix+"BG", OBJPROP_COLOR, C'40,40,50');
ObjectSetInteger(0, prefix+"BG", OBJPROP_SELECTABLE, false);
// Text labels (24 lines)
string labels[] = {
"Title", "Balance", "Equity", "Profit", "Sep1",
"Status", "H1Bias", "M15Trend", "Session", "Sep2",
"Position", "PosProfit", "PosPeak", "Sep3",
"Risk", "Daily", "Monthly", "Spread", "Sep4",
"Circuit1", "Circuit2", "Circuit3", "Sep5",
"Layers"
};
for(int i=0; i<ArraySize(labels); i++)
{
string objName = prefix + labels[i];
ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, objName, OBJPROP_CORNER, PanelCorner);
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, PanelOffsetX + 5);
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, PanelOffsetY + 5 + (i * 11));
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 8);
ObjectSetString(0, objName, OBJPROP_FONT, "Consolas");
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
}
}
void UpdatePanel()
{
if(!ShowPanel) return;
string prefix = "XAU_V3_";
// Title with branding
ObjectSetString(0, prefix+"Title", OBJPROP_TEXT, "══ XAUBot Pro V3 - suriota ══");
ObjectSetInteger(0, prefix+"Title", OBJPROP_COLOR, clrGold);
// Account info
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double profit = AccountInfoDouble(ACCOUNT_PROFIT);
ObjectSetString(0, prefix+"Balance", OBJPROP_TEXT, "Balance: $"+DoubleToString(balance,2));
ObjectSetString(0, prefix+"Equity", OBJPROP_TEXT, "Equity: $"+DoubleToString(equity,2));
color profitColor = (profit>=0) ? clrLimeGreen : clrRed;
string profitSign = (profit>=0) ? "+" : "";
ObjectSetString(0, prefix+"Profit", OBJPROP_TEXT, "Profit: "+profitSign+"$"+DoubleToString(profit,2));
ObjectSetInteger(0, prefix+"Profit", OBJPROP_COLOR, profitColor);
ObjectSetString(0, prefix+"Sep1", OBJPROP_TEXT, "─────────────────────────");
ObjectSetInteger(0, prefix+"Sep1", OBJPROP_COLOR, C'60,60,80');
// Trading status
string statusText = canTrade ? StringFormat("Status: ✓ READY (Q: %.0f/100)", technicalQuality) : StringFormat("Status: ⏸ %s", stopReason);
color statusColor = canTrade ? clrLimeGreen : clrRed;
ObjectSetString(0, prefix+"Status", OBJPROP_TEXT, statusText);
ObjectSetInteger(0, prefix+"Status", OBJPROP_COLOR, statusColor);
// H1 Bias
color biasColor = (h1_bias > 0) ? clrLimeGreen : (h1_bias < 0) ? clrRed : clrGray;
ObjectSetString(0, prefix+"H1Bias", OBJPROP_TEXT, StringFormat("H1 Bias: %s (%d/%d)", h1_bias_str, h1_bull_count, h1_bear_count));
ObjectSetInteger(0, prefix+"H1Bias", OBJPROP_COLOR, biasColor);
// M15 Trend
string m15_dir = (emaFast_M15 > emaSlow_M15) ? "▲ BULL" : "▼ BEAR";
color m15Color = (emaFast_M15 > emaSlow_M15) ? clrLimeGreen : clrRed;
ObjectSetString(0, prefix+"M15Trend", OBJPROP_TEXT, StringFormat("M15: %s | ADX: %.1f", m15_dir, adxValue_M15));
ObjectSetInteger(0, prefix+"M15Trend", OBJPROP_COLOR, m15Color);
// Session
SessionInfo session = GetCurrentSession();
ObjectSetString(0, prefix+"Session", OBJPROP_TEXT, StringFormat("Session: %s (%.1fx)", session.name, session.riskMultiplier));
ObjectSetString(0, prefix+"Sep2", OBJPROP_TEXT, "─────────────────────────");
ObjectSetInteger(0, prefix+"Sep2", OBJPROP_COLOR, C'60,60,80');
// Position info
if(position.Select(_Symbol))
{
string posType = (position.Type()==POSITION_TYPE_BUY) ? "BUY" : "SELL";
color posColor = (position.Type()==POSITION_TYPE_BUY) ? clrDodgerBlue : clrOrangeRed;
ObjectSetString(0, prefix+"Position", OBJPROP_TEXT, StringFormat("● %s | %.2f lot", posType, position.Volume()));
ObjectSetInteger(0, prefix+"Position", OBJPROP_COLOR, posColor);
double posProfit = position.Profit();
int ageMin = (int)((TimeCurrent() - positionOpenTime) / 60);
color pColor = (posProfit>=0) ? clrLimeGreen : clrRed;
ObjectSetString(0, prefix+"PosProfit", OBJPROP_TEXT, StringFormat("P/L: $%.2f | Age: %dmin", posProfit, ageMin));
ObjectSetInteger(0, prefix+"PosProfit", OBJPROP_COLOR, pColor);
ObjectSetString(0, prefix+"PosPeak", OBJPROP_TEXT, StringFormat("Peak: $%.2f | ATR: $%.2f", peakProfit, atrValue_M15));
}
else
{
ObjectSetString(0, prefix+"Position", OBJPROP_TEXT, "● No Position");
ObjectSetInteger(0, prefix+"Position", OBJPROP_COLOR, clrGray);
ObjectSetString(0, prefix+"PosProfit", OBJPROP_TEXT, "");
ObjectSetString(0, prefix+"PosPeak", OBJPROP_TEXT, "");
}
ObjectSetString(0, prefix+"Sep3", OBJPROP_TEXT, "─────────────────────────");
ObjectSetInteger(0, prefix+"Sep3", OBJPROP_COLOR, C'60,60,80');
// Risk info
double effectiveRisk = currentRisk * monthlyRiskMult * intraRiskMult;
string riskMode = (consecutiveLosses > 0) ? "Recovery" : "Normal";
ObjectSetString(0, prefix+"Risk", OBJPROP_TEXT, StringFormat("Risk: %.1f%% (%s)", effectiveRisk, riskMode));
// Daily stats
double dailyLimit = balance * (DailyLossLimit / 100.0);
ObjectSetString(0, prefix+"Daily", OBJPROP_TEXT, StringFormat("Daily: $%.0f / -$%.0f (%.0f%%)", dailyProfit-dailyLoss, dailyLimit, DailyLossLimit));
// Monthly stats
double monthlyLimit = balance * (MonthlyLossLimit / 100.0);
ObjectSetString(0, prefix+"Monthly", OBJPROP_TEXT, StringFormat("Month: $%.0f / -$%.0f (%.0f%%)", monthlyProfit-monthlyLoss, monthlyLimit, MonthlyLossLimit));
// Spread & Trades
ObjectSetString(0, prefix+"Spread", OBJPROP_TEXT, StringFormat("Spread: %.0f/%.0f | Trades: %d/%d", symbolInfo.Spread(), MaxSpread, dailyTrades, MaxTradesPerDay));
ObjectSetString(0, prefix+"Sep4", OBJPROP_TEXT, "─────────────────────────");
ObjectSetInteger(0, prefix+"Sep4", OBJPROP_COLOR, C'60,60,80');
// Circuit breakers
string cb1 = dailyLimitReached ? "[HALT]" : "[ OK ]";
string cb2 = monthlyLimitReached ? "[HALT]" : "[ OK ]";
string cb3 = consecutiveLossHalt ? "[HALT]" : "[ OK ]";
color cb1_col = dailyLimitReached ? clrRed : clrLimeGreen;
color cb2_col = monthlyLimitReached ? clrRed : clrLimeGreen;
color cb3_col = consecutiveLossHalt ? clrRed : clrLimeGreen;
ObjectSetString(0, prefix+"Circuit1", OBJPROP_TEXT, StringFormat("Daily: %s", cb1));
ObjectSetInteger(0, prefix+"Circuit1", OBJPROP_COLOR, cb1_col);
ObjectSetString(0, prefix+"Circuit2", OBJPROP_TEXT, StringFormat("Month: %s", cb2));
ObjectSetInteger(0, prefix+"Circuit2", OBJPROP_COLOR, cb2_col);
ObjectSetString(0, prefix+"Circuit3", OBJPROP_TEXT, StringFormat("Losses: %s (C:%d)", cb3, consecutiveLosses));
ObjectSetInteger(0, prefix+"Circuit3", OBJPROP_COLOR, cb3_col);
ObjectSetString(0, prefix+"Sep5", OBJPROP_TEXT, "─────────────────────────");
ObjectSetInteger(0, prefix+"Sep5", OBJPROP_COLOR, C'60,60,80');
// Layer summary
ObjectSetString(0, prefix+"Layers", OBJPROP_TEXT, StringFormat("L1:%.1f L2:%.0f L3:%.1f L4:%d%%", monthlyRiskMult, technicalQuality, intraRiskMult, patternWinRate));
}
void DeletePanel()
{
string prefix = "XAU_V3_";
ObjectDelete(0, prefix+"BG");
string labels[] = {
"Title", "Balance", "Equity", "Profit", "Sep1",
"Status", "H1Bias", "M15Trend", "Session", "Sep2",
"Position", "PosProfit", "PosPeak", "Sep3",
"Risk", "Daily", "Monthly", "Spread", "Sep4",
"Circuit1", "Circuit2", "Circuit3", "Sep5",
"Layers"
};
for(int i=0; i<ArraySize(labels); i++)
ObjectDelete(0, prefix+labels[i]);
}
//===========================================
// SECTION 13: UTILITIES (1801-1900)
//===========================================
bool UpdateAllData()
{
// Update M15 indicators
double arr[];
ArraySetAsSeries(arr, true);
if(CopyBuffer(handleEMAFast_M15, 0, 0, 2, arr) <= 0) return false;
emaFast_M15 = arr[1];
if(CopyBuffer(handleEMASlow_M15, 0, 0, 2, arr) <= 0) return false;
emaSlow_M15 = arr[1];
if(CopyBuffer(handleADX_M15, 0, 0, 2, arr) <= 0) return false;
adxValue_M15 = arr[1];
if(CopyBuffer(handleRSI_M15, 0, 0, 2, arr) <= 0) return false;
rsiValue_M15 = arr[1];
if(CopyBuffer(handleMACD_M15, 0, 0, 2, arr) <= 0) return false;
macdMain_M15 = arr[1];
if(CopyBuffer(handleMACD_M15, 1, 0, 2, arr) <= 0) return false;
macdSignal_M15 = arr[1];
if(CopyBuffer(handleATR_M15, 0, 0, 2, arr) <= 0) return false;
atrValue_M15 = arr[1];
// Update H1 indicators
if(CopyBuffer(handleEMAFast_H1, 0, 0, 2, arr) <= 0) return false;
emaFast_H1 = arr[1];
if(CopyBuffer(handleEMASlow_H1, 0, 0, 2, arr) <= 0) return false;
emaSlow_H1 = arr[1];
if(CopyBuffer(handleRSI_H1, 0, 0, 2, arr) <= 0) return false;
rsiValue_H1 = arr[1];
if(CopyBuffer(handleMACD_H1, 0, 0, 2, arr) <= 0) return false;
macdMain_H1 = arr[1];
if(CopyBuffer(handleMACD_H1, 1, 0, 2, arr) <= 0) return false;
macdSignal_H1 = arr[1];
return true;
}
void CheckDayRollover()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
// Day rollover
if(dt.day != currentDay)
{
WriteLog(StringFormat("DAY ROLLOVER | Daily: $%.2f | Trades: %d", dailyProfit-dailyLoss, dailyTrades), "SYSTEM");
dailyProfit = 0;
dailyLoss = 0;
dailyTrades = 0;
dailyLimitReached = false;
currentDay = dt.day;
// Reset log file
if(EnableFileLog)
{
CloseLogFile();
OpenLogFile();
}
}
// Month rollover
if(dt.mon != currentMonth)
{
WriteLog(StringFormat("MONTH ROLLOVER | Monthly: $%.2f", monthlyProfit-monthlyLoss), "SYSTEM");
monthlyProfit = 0;
monthlyLoss = 0;
monthlyLimitReached = false;
currentMonth = dt.mon;
}
}
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result)
{
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
ulong dealTicket = trans.deal;
if(dealTicket > 0 && HistoryDealSelect(dealTicket))
{
long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
if(dealMagic == Magic)
{
double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
long dealEntry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(dealEntry == DEAL_ENTRY_OUT)
{
bool isWin = (dealProfit > 0);
if(isWin)
{
consecutiveWins++;
consecutiveLosses = 0;
consecutiveLossHalt = false;
dailyProfit += dealProfit;
monthlyProfit += dealProfit;
if(consecutiveWins >= 2)
currentRisk = RiskPercent;
WriteLog(StringFormat("WIN | Profit: $%.2f | Consecutive: %d | Risk: %.1f%%", dealProfit, consecutiveWins, currentRisk), "WIN");
}
else
{
consecutiveLosses++;
consecutiveWins = 0;
dailyLoss += MathAbs(dealProfit);
monthlyLoss += MathAbs(dealProfit);
currentRisk = MinRiskPercent;
// Check circuit breakers
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
if(dailyLoss >= balance * (DailyLossLimit / 100.0))
{
dailyLimitReached = true;
canTrade = false;
stopReason = "Daily Loss Limit";
WriteLog(StringFormat("CIRCUIT BREAKER: Daily loss limit reached ($%.2f)", dailyLoss), "ALERT");
}
if(monthlyLoss >= balance * (MonthlyLossLimit / 100.0))
{
monthlyLimitReached = true;
canTrade = false;
stopReason = "Monthly Loss Limit";
WriteLog(StringFormat("CIRCUIT BREAKER: Monthly loss limit reached ($%.2f)", monthlyLoss), "ALERT");
}
if(consecutiveLosses >= MaxConsecutiveLosses)
{
consecutiveLossHalt = true;
canTrade = false;
stopReason = StringFormat("%d Consecutive Losses", MaxConsecutiveLosses);
WriteLog(StringFormat("CIRCUIT BREAKER: %d consecutive losses - trading halted", MaxConsecutiveLosses), "ALERT");
}
WriteLog(StringFormat("LOSS | Loss: $%.2f | Consecutive: %d | Risk: %.1f%% | Daily: $%.2f",
MathAbs(dealProfit), consecutiveLosses, currentRisk, dailyLoss), "LOSS");
}
// Re-enable trading if recovered from consecutive losses (after 1 win)
if(isWin && consecutiveLossHalt)
{
consecutiveLossHalt = false;
canTrade = true;
WriteLog("RECOVERY: Consecutive loss halt cleared after win", "SYSTEM");
}
}
}
}
}
}
// Logging functions
bool OpenLogFile()
{
if(!EnableFileLog) return true;
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
string filename = StringFormat("XAUBot_V3_%04d-%02d-%02d.log", dt.year, dt.mon, dt.day);
currentLogFile = filename;
lastLogDate = TimeCurrent();
logFileHandle = FileOpen(filename, FILE_WRITE|FILE_READ|FILE_TXT|FILE_ANSI);
if(logFileHandle == INVALID_HANDLE)
{
Print("ERROR: Failed to open log file: ", filename);
return false;
}
FileSeek(logFileHandle, 0, SEEK_END);
string marker = StringFormat("\n╔════════════════════════════════════════╗\n║ XAUBot Pro V3 - SESSION START ║\n║ %s ║\n╚════════════════════════════════════════╝\n", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS));
FileWriteString(logFileHandle, marker);
FileFlush(logFileHandle);
return true;
}
void WriteLog(string message, string level="INFO")
{
if(!EnableFileLog || logFileHandle == INVALID_HANDLE) return;
string logLine = StringFormat("[%s] [%s] %s\n", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), level, message);
FileWriteString(logFileHandle, logLine);
FileFlush(logFileHandle);
}
void CloseLogFile()
{
if(logFileHandle != INVALID_HANDLE)
{
string marker = StringFormat("\n[%s] ══════════ SESSION END ══════════\n\n", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS));
FileWriteString(logFileHandle, marker);
FileFlush(logFileHandle);
FileClose(logFileHandle);
logFileHandle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+