//+------------------------------------------------------------------+ //| LiquiditySweepEA_Pro_v2.mq5 | //| Full rewrite — all weaknesses addressed | //| Version 2.00 — Finished Product | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Expert Assistant" #property link "" #property version "2.00" #property description "Liquidity Sweep + BOS + Order Block EA with full trade management" #include #include #include //+------------------------------------------------------------------+ //| INPUT PARAMETERS | //+------------------------------------------------------------------+ sinput group "========== 1. Strategy Settings ==========" input ENUM_TIMEFRAMES InpHTF = PERIOD_H1; // Higher Timeframe (H1 or H4) input ENUM_TIMEFRAMES InpEntryTF = PERIOD_M15; // Entry Timeframe (M15) input int InpATRPeriod = 14; // ATR Period input int InpRSIPeriod = 14; // RSI Period *** WAS MISSING — NOW ADDED *** input double InpImpulseATRMult = 1.5; // Impulse candle must be X times ATR input double InpOBEntryZone = 0.5; // Order Block entry zone (0.5 = midpoint) sinput group "========== 2. Setup Quality Filters ==========" input bool InpRequireRSIDivergence = true; // Require RSI divergence confirmation input bool InpRequireHTFAlignment = true; // Require Higher Timeframe EMA alignment input double InpMinQualityScore = 70; // Minimum quality score to trade (0-100) sinput group "========== 3. Spread & Volatility Filters ==========" input int InpMaxSpread = 30; // Maximum allowed spread (points) *** WAS MISSING *** input double InpMinATR = 0.0003; // Minimum ATR to confirm volatility *** WAS MISSING *** sinput group "========== 4. Multi-Entry Settings ==========" input bool InpAllowMultipleEntries = true; // Allow scaling into the same signal input int InpMaxEntriesPerSignal = 3; // Maximum entries per signal direction input double InpEntrySpacingPips = 20; // Minimum pips between scale-in entries sinput group "========== 5. Risk Management ==========" input double InpRiskPercent = 1.0; // Risk per trade (% of equity) input double InpMinRR = 2.0; // Minimum Risk:Reward ratio input bool InpUseATRTP = false; // Use ATR-based Take Profit instead of RR input double InpATRTPMult = 2.0; // ATR multiplier for Take Profit input bool InpCompounding = true; // Use equity (true) or balance (false) for sizing input int InpMaxDailyLossPercent = 5; // Daily loss limit (%) — stops trading if hit sinput group "========== 6. Trade Management (NEW) ==========" input bool InpUseTrailingStop = true; // Enable trailing stop loss input double InpTrailingATRMult = 1.5; // Trail stop at X * ATR behind price input bool InpUseBreakeven = true; // Move stop to breakeven after reward hit input double InpBreakevenRR = 1.0; // Move to breakeven after 1:1 reward reached input bool InpUsePartialClose = true; // Take partial profit at first target input double InpPartialClosePercent = 50.0; // Percentage of position to close at TP1 input double InpPartialCloseRR = 1.0; // RR level to trigger partial close sinput group "========== 7. Session Filter ==========" input bool InpUseSessionFilter = true; // Enable trading session filter input int InpLondonStart = 8; // London session start (UTC hour) input int InpLondonEnd = 11; // London session end (UTC hour) input int InpNewYorkStart = 13; // New York session start (UTC hour) input int InpNewYorkEnd = 16; // New York session end (UTC hour) sinput group "========== 8. Execution ==========" input int InpMagicNumber = 234567; // EA magic number (unique ID) input int InpSlippage = 10; // Allowed slippage (points) input double InpMinLot = 0.01; // Minimum lot size input int InpCooldownSeconds = 60; // Seconds to wait between trades input bool InpLogToFile = true; // Write trade log to file input bool InpShowDashboard = true; // Show dashboard on chart //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ CTrade Trade; CPositionInfo PosInfo; COrderInfo OrdInfo; string g_symbol = ""; double g_point = 0; double g_pipValue = 0; int g_digits = 0; // Indicator handles int g_fractalHandle = INVALID_HANDLE; int g_rsiHandle = INVALID_HANDLE; int g_atrHandle = INVALID_HANDLE; // State tracking datetime g_lastBarTime = 0; datetime g_lastTradeTime = 0; datetime g_lastResetDate = 0; // Daily loss tracking (tracks ACTUAL closed P&L, not just risked amount) double g_dailyStartBalance = 0; // Balance at start of day — FIXED BUG // Signal entry tracking struct SignalEntry { datetime signalTime; int direction; int count; }; SignalEntry g_currentSignal; // Dashboard label names string DASH_LABEL = "LSEA_Dashboard"; //+------------------------------------------------------------------+ //| INITIALISATION | //+------------------------------------------------------------------+ int OnInit() { g_symbol = Symbol(); g_point = SymbolInfoDouble(g_symbol, SYMBOL_POINT); g_digits = (int)SymbolInfoInteger(g_symbol, SYMBOL_DIGITS); g_pipValue = g_point * 10.0; // Create indicator handles g_fractalHandle = iFractals(g_symbol, InpEntryTF); if(g_fractalHandle == INVALID_HANDLE) { Print("ERROR: Could not create Fractals indicator"); return INIT_FAILED; } g_rsiHandle = iRSI(g_symbol, InpEntryTF, InpRSIPeriod, PRICE_CLOSE); if(g_rsiHandle == INVALID_HANDLE) { Print("ERROR: Could not create RSI indicator"); return INIT_FAILED; } g_atrHandle = iATR(g_symbol, InpEntryTF, InpATRPeriod); if(g_atrHandle == INVALID_HANDLE) { Print("ERROR: Could not create ATR indicator"); return INIT_FAILED; } Trade.SetExpertMagicNumber(InpMagicNumber); Trade.SetDeviationInPoints(InpSlippage); Trade.SetTypeFilling(ORDER_FILLING_FOK); // Initialise daily tracking properly ResetDailyCounters(); ZeroMemory(g_currentSignal); if(InpShowDashboard) CreateDashboard(); Print("LiquiditySweepEA_Pro v2.00 initialised on ", g_symbol); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| DEINITIALISATION | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(g_fractalHandle != INVALID_HANDLE) IndicatorRelease(g_fractalHandle); if(g_rsiHandle != INVALID_HANDLE) IndicatorRelease(g_rsiHandle); if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle); if(InpShowDashboard) ObjectsDeleteAll(0, "LSEA_"); Print("LiquiditySweepEA_Pro v2.00 stopped. Reason: ", reason); } //+------------------------------------------------------------------+ //| MAIN TICK FUNCTION | //+------------------------------------------------------------------+ void OnTick() { // 1. Manage open trades EVERY tick (trailing stop, breakeven, partial close) ManageOpenTrades(); // 2. Only look for NEW signals on a new bar datetime currentBarTime = iTime(g_symbol, InpEntryTF, 0); if(currentBarTime == g_lastBarTime) return; g_lastBarTime = currentBarTime; // 3. Daily reset & loss check UpdateDailyCounters(); if(!CanTrade()) { if(InpShowDashboard) UpdateDashboard(0, false, 0); return; } // 4. Higher timeframe trend bias int htfBias = DetectTrend(); if(htfBias == 0) { if(InpShowDashboard) UpdateDashboard(0, false, 0); return; } // 5. Session filter if(InpUseSessionFilter && !IsInSession()) { if(InpShowDashboard) UpdateDashboard(htfBias, false, 0); return; } // 6. Spread check if(!CheckSpread()) return; // 7. Volatility check double atrValue = GetATR(); if(atrValue < InpMinATR) return; // 8. Detect setup bool buySetup = false, sellSetup = false; double sweepLevel = 0, bosLevel = 0, obMidpoint = 0; datetime obTime = 0; int obIndex = 0; double qualityScore = 0; if(htfBias == 1) // Bullish bias { if(DetectLiquiditySweep(true, sweepLevel) && DetectBOS(true, bosLevel) && IdentifyOrderBlock(true, obMidpoint, obTime, obIndex)) { double price = SymbolInfoDouble(g_symbol, SYMBOL_BID); if(price <= obMidpoint + g_point && price >= obMidpoint - g_point) { qualityScore = EvaluateSetupQuality(true, atrValue); if(qualityScore >= InpMinQualityScore) buySetup = true; } } } else if(htfBias == -1) // Bearish bias { if(DetectLiquiditySweep(false, sweepLevel) && DetectBOS(false, bosLevel) && IdentifyOrderBlock(false, obMidpoint, obTime, obIndex)) { double price = SymbolInfoDouble(g_symbol, SYMBOL_ASK); if(price >= obMidpoint - g_point && price <= obMidpoint + g_point) { qualityScore = EvaluateSetupQuality(false, atrValue); if(qualityScore >= InpMinQualityScore) sellSetup = true; } } } // 9. Cooldown check if(TimeCurrent() - g_lastTradeTime < InpCooldownSeconds) { if(InpShowDashboard) UpdateDashboard(htfBias, buySetup || sellSetup, qualityScore); return; } // 10. Execute trades if(buySetup && CanEnterNewSignal(1, obTime)) { if(ExecuteTrade(true, sweepLevel, obMidpoint, obTime, obIndex, atrValue)) { g_lastTradeTime = TimeCurrent(); UpdateSignalEntry(1, obTime); if(InpLogToFile) LogToFile("BUY LIMIT placed", obMidpoint, sweepLevel, qualityScore); } } else if(sellSetup && CanEnterNewSignal(-1, obTime)) { if(ExecuteTrade(false, sweepLevel, obMidpoint, obTime, obIndex, atrValue)) { g_lastTradeTime = TimeCurrent(); UpdateSignalEntry(-1, obTime); if(InpLogToFile) LogToFile("SELL LIMIT placed", obMidpoint, sweepLevel, qualityScore); } } if(InpShowDashboard) UpdateDashboard(htfBias, buySetup || sellSetup, qualityScore); } //+------------------------------------------------------------------+ //| TRADE MANAGEMENT — runs every tick (NEW in v2) | //+------------------------------------------------------------------+ void ManageOpenTrades() { double atr = GetATR(); if(atr <= 0) return; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; if(PositionGetString(POSITION_SYMBOL) != g_symbol) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); double lotSize = PositionGetDouble(POSITION_VOLUME); double bid = SymbolInfoDouble(g_symbol, SYMBOL_BID); double ask = SymbolInfoDouble(g_symbol, SYMBOL_ASK); ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // ── Partial close at TP1 if(InpUsePartialClose) { double slDist = MathAbs(openPrice - currentSL); double tp1 = (posType == POSITION_TYPE_BUY) ? openPrice + slDist * InpPartialCloseRR : openPrice - slDist * InpPartialCloseRR; bool tp1Hit = (posType == POSITION_TYPE_BUY && bid >= tp1) || (posType == POSITION_TYPE_SELL && ask <= tp1); string comment = PositionGetString(POSITION_COMMENT); bool alreadyDone = (StringFind(comment, "_P1") >= 0); if(tp1Hit && !alreadyDone && lotSize > InpMinLot) { double closeLots = NormalizeLot(lotSize * InpPartialClosePercent / 100.0); if(closeLots >= InpMinLot) { if(Trade.PositionClosePartial(ticket, closeLots)) if(InpLogToFile) LogToFile("PARTIAL CLOSE TP1", tp1, 0, 0); } } } // ── Move stop to breakeven if(InpUseBreakeven) { double slDist = MathAbs(openPrice - currentSL); double beHit = (posType == POSITION_TYPE_BUY) ? openPrice + slDist * InpBreakevenRR : openPrice - slDist * InpBreakevenRR; double newBE = NormalizeDouble(openPrice + (posType == POSITION_TYPE_BUY ? g_point : -g_point), g_digits); bool triggered = (posType == POSITION_TYPE_BUY && bid >= beHit) || (posType == POSITION_TYPE_SELL && ask <= beHit); bool slNotYetBE = (posType == POSITION_TYPE_BUY && currentSL < newBE) || (posType == POSITION_TYPE_SELL && currentSL > newBE); if(triggered && slNotYetBE) { if(Trade.PositionModify(ticket, newBE, currentTP)) if(InpLogToFile) LogToFile("BREAKEVEN set", newBE, 0, 0); } } // ── Trailing stop (only trails in profit) if(InpUseTrailingStop) { double trail = InpTrailingATRMult * atr; double newSL = 0; if(posType == POSITION_TYPE_BUY) { newSL = NormalizeDouble(bid - trail, g_digits); if(newSL > currentSL && bid > openPrice) Trade.PositionModify(ticket, newSL, currentTP); } else { newSL = NormalizeDouble(ask + trail, g_digits); if(newSL < currentSL && ask < openPrice) Trade.PositionModify(ticket, newSL, currentTP); } } } } //+------------------------------------------------------------------+ //| SETUP QUALITY SCORE (0-100) | //+------------------------------------------------------------------+ double EvaluateSetupQuality(bool isBuy, double atrValue) { double score = 0.0; // Component 1: HTF alignment (40 points) if(InpRequireHTFAlignment) { int trend = DetectTrend(); if((isBuy && trend == 1) || (!isBuy && trend == -1)) score += 40.0; else return 0; // Hard fail } else score += 40.0; // Component 2: RSI divergence (30 points) if(InpRequireRSIDivergence) { if(CheckRSIDivergence(isBuy)) score += 30.0; else return 0; // Hard fail } else score += 30.0; // Component 3: Impulse candle strength (20 points) double candleRange = iHigh(g_symbol, InpEntryTF, 1) - iLow(g_symbol, InpEntryTF, 1); if(candleRange > InpImpulseATRMult * atrValue) score += 20.0; else if(candleRange > atrValue) score += 10.0; // Component 4: Order block size (10 points) double obRange = iHigh(g_symbol, InpEntryTF, 2) - iLow(g_symbol, InpEntryTF, 2); if(obRange > atrValue) score += 10.0; else if(obRange > atrValue * 0.5) score += 5.0; return score; } //+------------------------------------------------------------------+ //| MULTI-ENTRY GATE | //+------------------------------------------------------------------+ bool CanEnterNewSignal(int direction, datetime signalTime) { if(!InpAllowMultipleEntries) return (g_currentSignal.signalTime != signalTime || g_currentSignal.count == 0); if(g_currentSignal.signalTime != signalTime || g_currentSignal.direction != direction) return true; if(g_currentSignal.count >= InpMaxEntriesPerSignal) return false; double lastPrice = GetLastEntryPrice(direction); double currentPrice = (direction == 1) ? SymbolInfoDouble(g_symbol, SYMBOL_BID) : SymbolInfoDouble(g_symbol, SYMBOL_ASK); return (MathAbs(lastPrice - currentPrice) / g_pipValue >= InpEntrySpacingPips); } void UpdateSignalEntry(int direction, datetime signalTime) { if(g_currentSignal.signalTime == signalTime && g_currentSignal.direction == direction) g_currentSignal.count++; else { g_currentSignal.signalTime = signalTime; g_currentSignal.direction = direction; g_currentSignal.count = 1; } } double GetLastEntryPrice(int direction) { for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; ENUM_POSITION_TYPE type = (direction == 1) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL; if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type) return PositionGetDouble(POSITION_PRICE_OPEN); } return 0; } //+------------------------------------------------------------------+ //| TREND DETECTION — HTF EMA cross | //+------------------------------------------------------------------+ int DetectTrend() { int ema50h = iMA(g_symbol, InpHTF, 50, 0, MODE_EMA, PRICE_CLOSE); int ema200h = iMA(g_symbol, InpHTF, 200, 0, MODE_EMA, PRICE_CLOSE); if(ema50h == INVALID_HANDLE || ema200h == INVALID_HANDLE) return 0; double ema50[], ema200[], close[]; ArraySetAsSeries(ema50, true); ArraySetAsSeries(ema200, true); ArraySetAsSeries(close, true); bool ok = (CopyBuffer(ema50h, 0, 0, 3, ema50) >= 3) && (CopyBuffer(ema200h, 0, 0, 3, ema200) >= 3) && (CopyClose(g_symbol, InpHTF, 0, 3, close) >= 3); IndicatorRelease(ema50h); IndicatorRelease(ema200h); if(!ok) return 0; if(ema50[0] > ema200[0] && close[0] > ema50[0]) return 1; if(ema50[0] < ema200[0] && close[0] < ema50[0]) return -1; return 0; } //+------------------------------------------------------------------+ //| LIQUIDITY SWEEP DETECTION | //+------------------------------------------------------------------+ bool DetectLiquiditySweep(bool isBuy, double &sweepLevel) { double fractalUp[], fractalDown[]; ArraySetAsSeries(fractalUp, true); ArraySetAsSeries(fractalDown, true); if(CopyBuffer(g_fractalHandle, 0, 0, 10, fractalUp) < 10) return false; if(CopyBuffer(g_fractalHandle, 1, 0, 10, fractalDown) < 10) return false; if(isBuy) { double lastLow = 0; for(int i = 1; i < 10; i++) if(fractalDown[i] != 0) { lastLow = fractalDown[i]; break; } if(lastLow == 0) return false; double low[], close[]; ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); if(CopyLow(g_symbol, InpEntryTF, 0, 3, low) < 3) return false; if(CopyClose(g_symbol, InpEntryTF, 0, 3, close) < 3) return false; if(low[1] < lastLow && close[1] > lastLow) { sweepLevel = lastLow; return true; } } else { double lastHigh = 0; for(int i = 1; i < 10; i++) if(fractalUp[i] != 0) { lastHigh = fractalUp[i]; break; } if(lastHigh == 0) return false; double high[], close[]; ArraySetAsSeries(high, true); ArraySetAsSeries(close, true); if(CopyHigh(g_symbol, InpEntryTF, 0, 3, high) < 3) return false; if(CopyClose(g_symbol, InpEntryTF, 0, 3, close) < 3) return false; if(high[1] > lastHigh && close[1] < lastHigh) { sweepLevel = lastHigh; return true; } } return false; } //+------------------------------------------------------------------+ //| BREAK OF STRUCTURE DETECTION | //+------------------------------------------------------------------+ bool DetectBOS(bool isBuy, double &bosLevel) { double fractalUp[], fractalDown[]; ArraySetAsSeries(fractalUp, true); ArraySetAsSeries(fractalDown, true); if(CopyBuffer(g_fractalHandle, 0, 0, 20, fractalUp) < 20) return false; if(CopyBuffer(g_fractalHandle, 1, 0, 20, fractalDown) < 20) return false; if(isBuy) { double h1 = 0, h2 = 0; for(int i = 1; i < 20; i++) if(fractalUp[i] != 0) { if(h1 == 0) h1 = fractalUp[i]; else { h2 = fractalUp[i]; break; } } if(h1 == 0 || h2 == 0 || h2 >= h1) return false; double high[], close[]; ArraySetAsSeries(high, true); ArraySetAsSeries(close, true); if(CopyHigh(g_symbol, InpEntryTF, 0, 3, high) < 3) return false; if(CopyClose(g_symbol, InpEntryTF, 0, 3, close) < 3) return false; if(high[1] > h2 && close[1] > h2) { bosLevel = h2; return true; } } else { double l1 = 0, l2 = 0; for(int i = 1; i < 20; i++) if(fractalDown[i] != 0) { if(l1 == 0) l1 = fractalDown[i]; else { l2 = fractalDown[i]; break; } } if(l1 == 0 || l2 == 0 || l2 <= l1) return false; double low[], close[]; ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); if(CopyLow(g_symbol, InpEntryTF, 0, 3, low) < 3) return false; if(CopyClose(g_symbol, InpEntryTF, 0, 3, close) < 3) return false; if(low[1] < l2 && close[1] < l2) { bosLevel = l2; return true; } } return false; } //+------------------------------------------------------------------+ //| ORDER BLOCK IDENTIFICATION | //+------------------------------------------------------------------+ bool IdentifyOrderBlock(bool isBuy, double &obMidpoint, datetime &obTime, int &obIndex) { double atrValue = GetATR(); if(atrValue <= 0) return false; double open[], high[], low[], close[]; ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); int bars = 20; if(CopyOpen(g_symbol, InpEntryTF, 0, bars, open) < bars) return false; if(CopyHigh(g_symbol, InpEntryTF, 0, bars, high) < bars) return false; if(CopyLow(g_symbol, InpEntryTF, 0, bars, low) < bars) return false; if(CopyClose(g_symbol, InpEntryTF, 0, bars, close) < bars) return false; for(int i = 1; i < bars - 1; i++) { if((high[i] - low[i]) > InpImpulseATRMult * atrValue) { int obIdx = i + 1; if(obIdx >= bars) continue; if(isBuy && close[obIdx] < open[obIdx] && close[i] > open[i]) { obMidpoint = (high[obIdx] + low[obIdx]) / 2.0; obTime = iTime(g_symbol, InpEntryTF, obIdx); obIndex = obIdx; return true; } if(!isBuy && close[obIdx] > open[obIdx] && close[i] < open[i]) { obMidpoint = (high[obIdx] + low[obIdx]) / 2.0; obTime = iTime(g_symbol, InpEntryTF, obIdx); obIndex = obIdx; return true; } } } return false; } //+------------------------------------------------------------------+ //| RSI DIVERGENCE — improved swing point detection | //+------------------------------------------------------------------+ bool CheckRSIDivergence(bool isBuy) { double rsi[], low[], high[]; ArraySetAsSeries(rsi, true); ArraySetAsSeries(low, true); ArraySetAsSeries(high, true); int bars = 60; int lookback = 5; if(CopyBuffer(g_rsiHandle, 0, 0, bars, rsi) < bars) return false; if(CopyLow(g_symbol, InpEntryTF, 0, bars, low) < bars) return false; if(CopyHigh(g_symbol, InpEntryTF, 0, bars, high) < bars) return false; if(isBuy) { double p1=0, p2=0, r1=0, r2=0; for(int i = lookback; i < bars - lookback; i++) { bool swing = true; for(int j = 1; j <= lookback; j++) if(low[i] >= low[i-j] || low[i] >= low[i+j]) { swing=false; break; } if(swing) { if(p1==0) { p1=low[i]; r1=rsi[i]; } else { p2=low[i]; r2=rsi[i]; break; } } } if(p1==0 || p2==0) return false; return (p2 < p1 && r2 > r1); // Lower low, higher RSI = bullish divergence } else { double p1=0, p2=0, r1=0, r2=0; for(int i = lookback; i < bars - lookback; i++) { bool swing = true; for(int j = 1; j <= lookback; j++) if(high[i] <= high[i-j] || high[i] <= high[i+j]) { swing=false; break; } if(swing) { if(p1==0) { p1=high[i]; r1=rsi[i]; } else { p2=high[i]; r2=rsi[i]; break; } } } if(p1==0 || p2==0) return false; return (p2 > p1 && r2 < r1); // Higher high, lower RSI = bearish divergence } } //+------------------------------------------------------------------+ //| TRADE EXECUTION | //+------------------------------------------------------------------+ bool ExecuteTrade(bool isBuy, double sweepLevel, double obMidpoint, datetime obTime, int obIndex, double atrValue) { double capital = InpCompounding ? AccountInfoDouble(ACCOUNT_EQUITY) : AccountInfoDouble(ACCOUNT_BALANCE); double riskMoney = capital * InpRiskPercent / 100.0; double slPrice = isBuy ? NormalizeDouble(sweepLevel - 2 * g_point, g_digits) : NormalizeDouble(sweepLevel + 2 * g_point, g_digits); double slPoints = MathAbs(obMidpoint - slPrice) / g_point; if(slPoints < 10) { Print("SL too tight. Skip."); return false; } // Correct lot calculation using tick value and tick size double tickValue = SymbolInfoDouble(g_symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(g_symbol, SYMBOL_TRADE_TICK_SIZE); double lot = riskMoney / ((slPoints * g_point / tickSize) * tickValue); lot = NormalizeLot(lot); double minLot = MathMax(SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MIN), InpMinLot); double maxLot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MAX); lot = MathMax(lot, minLot); lot = MathMin(lot, maxLot); // Take profit double tpPrice; if(InpUseATRTP) { double tpPts = InpATRTPMult * atrValue / g_point; tpPrice = isBuy ? NormalizeDouble(obMidpoint + tpPts * g_point, g_digits) : NormalizeDouble(obMidpoint - tpPts * g_point, g_digits); } else { double rrPts = InpMinRR * slPoints; tpPrice = isBuy ? NormalizeDouble(obMidpoint + rrPts * g_point, g_digits) : NormalizeDouble(obMidpoint - rrPts * g_point, g_digits); } // Validate actual RR double actualRR = MathAbs(tpPrice - obMidpoint) / MathAbs(obMidpoint - slPrice); if(actualRR < InpMinRR) { Print("RR too low (", DoubleToString(actualRR,2), "). Skip."); return false; } bool placed = false; string cmt = "LSEA_v2_" + (isBuy ? "BUY" : "SELL"); if(isBuy) { if(SymbolInfoDouble(g_symbol, SYMBOL_ASK) <= obMidpoint + g_point) placed = Trade.BuyLimit(lot, obMidpoint, g_symbol, slPrice, tpPrice, ORDER_TIME_GTC, 0, cmt); else Print("BUY: ask above OB midpoint. Skip."); } else { if(SymbolInfoDouble(g_symbol, SYMBOL_BID) >= obMidpoint - g_point) placed = Trade.SellLimit(lot, obMidpoint, g_symbol, slPrice, tpPrice, ORDER_TIME_GTC, 0, cmt); else Print("SELL: bid below OB midpoint. Skip."); } if(placed && Trade.ResultRetcode() == TRADE_RETCODE_DONE) { Print("Order OK: ", (isBuy?"BUY":"SELL"), " lots=", lot, " entry=", obMidpoint, " SL=", slPrice, " TP=", tpPrice, " RR=", DoubleToString(actualRR,2)); return true; } Print("Order FAILED: ", Trade.ResultRetcodeDescription()); return false; } //+------------------------------------------------------------------+ //| HELPERS | //+------------------------------------------------------------------+ double GetATR() { double atr[]; ArraySetAsSeries(atr, true); if(CopyBuffer(g_atrHandle, 0, 0, 1, atr) < 1) return 0; return atr[0]; } bool CheckSpread() { return (SymbolInfoInteger(g_symbol, SYMBOL_SPREAD) <= InpMaxSpread); } bool IsInSession() { MqlDateTime t; TimeToStruct(TimeCurrent(), t); return ((t.hour >= InpLondonStart && t.hour < InpLondonEnd) || (t.hour >= InpNewYorkStart && t.hour < InpNewYorkEnd)); } bool CanTrade() { // FIXED: measure actual balance drop vs start of day, not risked money double loss = (g_dailyStartBalance - AccountInfoDouble(ACCOUNT_BALANCE)) / g_dailyStartBalance * 100.0; if(loss >= InpMaxDailyLossPercent) { Print("Daily loss limit hit (", DoubleToString(loss,2), "%). No more trades today."); return false; } return true; } void ResetDailyCounters() { g_dailyStartBalance = AccountInfoDouble(ACCOUNT_BALANCE); g_lastResetDate = TimeCurrent(); Print("Daily reset. Start balance: ", g_dailyStartBalance); } void UpdateDailyCounters() { MqlDateTime now, last; TimeToStruct(TimeCurrent(), now); TimeToStruct(g_lastResetDate, last); if(now.day != last.day || now.mon != last.mon || now.year != last.year) ResetDailyCounters(); } double NormalizeLot(double lot) { double step = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_STEP); if(step <= 0) step = 0.01; return NormalizeDouble(MathFloor(lot / step) * step, 2); } //+------------------------------------------------------------------+ //| DASHBOARD | //+------------------------------------------------------------------+ void CreateDashboard() { ObjectsDeleteAll(0, "LSEA_"); ObjectCreate(0, DASH_LABEL, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, DASH_LABEL, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, DASH_LABEL, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, DASH_LABEL, OBJPROP_YDISTANCE, 10); ObjectSetInteger(0, DASH_LABEL, OBJPROP_XSIZE, 230); ObjectSetInteger(0, DASH_LABEL, OBJPROP_YSIZE, 155); ObjectSetInteger(0, DASH_LABEL, OBJPROP_BGCOLOR, C'18,18,18'); ObjectSetInteger(0, DASH_LABEL, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, DASH_LABEL, OBJPROP_COLOR, clrDimGray); string txt = DASH_LABEL + "_txt"; ObjectCreate(0, txt, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, txt, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, txt, OBJPROP_XDISTANCE, 18); ObjectSetInteger(0, txt, OBJPROP_YDISTANCE, 18); ObjectSetInteger(0, txt, OBJPROP_FONTSIZE, 9); ObjectSetString( 0, txt, OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, txt, OBJPROP_COLOR, clrWhite); } void UpdateDashboard(int trend, bool signal, double quality) { string trendStr = (trend==1)?"BULLISH":(trend==-1)?"BEARISH":"NEUTRAL"; double loss = (g_dailyStartBalance > 0) ? MathMax((g_dailyStartBalance - AccountInfoDouble(ACCOUNT_BALANCE)) / g_dailyStartBalance * 100.0, 0) : 0; string text = " LSEA Pro v2.00\n"; text += " --------------------\n"; text += " Trend : " + trendStr + "\n"; text += " Session : " + (IsInSession() ? "ACTIVE" : "CLOSED") + "\n"; text += " Quality : " + DoubleToString(quality,0) + " / 100\n"; text += " Loss/Day: " + DoubleToString(loss,2) + "% / " + IntegerToString(InpMaxDailyLossPercent) + "%\n"; text += " --------------------\n"; text += (signal ? " *** SIGNAL READY ***" : " Scanning..."); ObjectSetString(0, DASH_LABEL + "_txt", OBJPROP_TEXT, text); ObjectSetInteger(0, DASH_LABEL + "_txt", OBJPROP_COLOR, signal ? clrLime : clrSilver); ChartRedraw(0); } //+------------------------------------------------------------------+ //| FILE LOGGING | //+------------------------------------------------------------------+ void LogToFile(string action, double price, double sl, double quality) { string fn = "LSEA_Pro_v2_" + g_symbol + ".log"; int h = FileOpen(fn, FILE_READ|FILE_WRITE|FILE_TXT|FILE_SHARE_READ); if(h != INVALID_HANDLE) { FileSeek(h, 0, SEEK_END); FileWrite(h, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), " | ", action, " | Price:", DoubleToString(price, g_digits), " | SL:", DoubleToString(sl, g_digits), " | Score:", DoubleToString(quality,1), " | Bal:", DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE),2)); FileClose(h); } } //+------------------------------------------------------------------+