//+------------------------------------------------------------------+ //| OI_OrderFlow_Absorption_XAUUSD.mq5 | //| Order Flow Absorption Mean Reversion System | //| Version 2.0 | //+------------------------------------------------------------------+ #property copyright "Order Flow Absorption Mean Reversion System" #property link "" #property version "2.00" #property description "XAUUSD Mean Reversion with OI Levels & Order Flow Absorption" #property strict #include #include #include #include #include #include #include #include enum ENUM_MARKET_PHASE { PHASE_BULLISH, PHASE_BEARISH, PHASE_SIDEWAYS, PHASE_NEUTRAL }; enum ENUM_ABSORPTION_STATE { ABSORPTION_NONE, ABSORPTION_BUY, ABSORPTION_SELL }; enum ENUM_OI_SOURCE { OI_SOURCE_MANUAL, OI_SOURCE_CSV_FILE, OI_SOURCE_AUTO_SYNC }; input group "=== OI & DELTA SETTINGS ===" input ENUM_OI_SOURCE InpOISource = OI_SOURCE_CSV_FILE; input string InpOICsvPath = "\\Files\\oi_data.csv"; input double InpManualFuturePrice = 0.0; input double InpCallStrike1 = 0.0; input double InpCallStrike2 = 0.0; input double InpCallStrike3 = 0.0; input double InpPutStrike1 = 0.0; input double InpPutStrike2 = 0.0; input double InpPutStrike3 = 0.0; input group "=== ORDER FLOW & ABSORPTION ===" input bool InpUseAbsorptionFilter = true; input int InpAbsorptionBars = 3; input int InpOIZonePoints = 100; input double InpMinVolumeMultiplier = 1.5; input int InpVolumeEmaPeriod = 20; input int InpMaxPriceDriftPoints = 50; input int InpSLBufferPoints = 200; input group "=== TRADING SETTINGS ===" input double InpLotSize = 0.1; input bool InpUseMoneyManagement = true; input double InpRiskPercent = 1.0; input bool InpUseStopLoss = true; input bool InpUseTakeProfit = true; input int InpStopLossPoints = 300; input int InpTakeProfitPoints = 500; input int InpMaxSpread = 200; input int InpMaxSlippage = 10; input int InpMagicNumber = 202501; input group "=== MARKET FILTERS ===" input bool InpUseMarketPhaseFilter = true; input ENUM_TIMEFRAMES InpTrendTF = PERIOD_H4; input int InpTrendMAPeriod1 = 50; input int InpTrendMAPeriod2 = 200; input bool InpUseATRFilter = true; input double InpMaxATRPercent = 2.0; input bool InpUseSessionFilter = true; input int InpSessionStartHour = 8; input int InpSessionEndHour = 22; input group "=== RISK MANAGEMENT ===" input double InpMaxDailyLossPercent = 3.0; input double InpMaxDailyProfitPercent = 5.0; input int InpMaxConsecutiveLosses = 3; input bool InpDisableAfterMaxLoss = true; input double InpEquityProtectionPercent = 10.0; input bool InpCloseAllOnReverse = false; input int InpMaxDailyTrades = 10; input group "=== ADVANCED FEATURES ===" input bool InpUseTrailingStop = false; input int InpTrailingStartPoints = 150; input int InpTrailingStepPoints = 50; input bool InpUseSoftStopLoss = true; input bool InpUseAutoRecovery = true; input bool InpEnableDashboard = true; input int InpCSVReloadInterval = 60; CTrade Trade; CSymbolInfo SymbolInfo; CAccountInfo AccountInfo; CPositionInfo PositionInfo; COrderInfo OrderInfo; CHistoryOrderInfo HistoryOrderInfo; double FuturePrice = 0.0; double SpotPrice = 0.0; double CallLevels[3]; double PutLevels[3]; int CallOI[3]; int PutOI[3]; double DynamicFuturePrice = 0.0; double DynamicCallStrike1 = 0.0; double DynamicCallStrike2 = 0.0; double DynamicCallStrike3 = 0.0; double DynamicPutStrike1 = 0.0; double DynamicPutStrike2 = 0.0; double DynamicPutStrike3 = 0.0; double LevelUpper1 = 0.0; double LevelUpper2 = 0.0; double LevelMid = 0.0; double LevelLower1 = 0.0; double LevelLower2 = 0.0; int DailyTradeCount = 0; double DailyPnL = 0.0; double DailyProfit = 0.0; double DailyLoss = 0.0; int ConsecutiveLosses = 0; datetime LastTradeTime = 0; datetime LastResetDate = 0; bool TradingEnabled = true; double EquityHigh = 0.0; double EquityLow = 0.0; int ATRHandle = INVALID_HANDLE; int MAFastHandle = INVALID_HANDLE; int MASlowHandle = INVALID_HANDLE; int RSIMainHandle = INVALID_HANDLE; long chart_id = 0; int DashboardSubWindow = -1; color PanelColor = C'30,30,30'; color TextColor = clrWhite; color ProfitColor = clrLime; color LossColor = clrRed; color WarningColor = clrOrange; int ControlPanelX = 10; int ControlPanelY = 210; double ManualFuturePriceValue = 0.0; double CallStrike1Value = 0.0; double CallStrike2Value = 0.0; double CallStrike3Value = 0.0; double PutStrike1Value = 0.0; double PutStrike2Value = 0.0; double PutStrike3Value = 0.0; string ControlPanelObjects[] = { "CP_TopPanel", "CP_CloseAll", "CP_OIDataPanel", "CP_FuturePrice", "CP_CallStrike1", "CP_CallStrike2", "CP_CallStrike3", "CP_PutStrike1", "CP_PutStrike2", "CP_PutStrike3", "CP_UpdateOI" }; string DrawnLines[]; int MaxLines = 10; double OrderFlowDeltaPercent = 0.0; double VolumeEmaValue = 0.0; double PriceDrift = 0.0; ENUM_ABSORPTION_STATE CurrentAbsorptionState = ABSORPTION_NONE; datetime LastM1BarTime = 0; datetime LastDashboardUpdate = 0; int CurrentBarUpTicks = 0; int CurrentBarDownTicks = 0; int CurrentBarVolume = 0; double CurrentBarHigh = 0.0; double CurrentBarLow = 0.0; double LastPrice = 0.0; double CachedFuturePrice = -1; string LoadedCSVPath = ""; bool CSVLoadLogged = false; datetime LastCSVReloadTime = 0; int OnInit() { Trade.SetExpertMagicNumber(InpMagicNumber); Trade.SetDeviationInPoints(InpMaxSlippage); Trade.SetTypeFilling(ORDER_FILLING_IOC); SymbolInfo.Name(_Symbol); SymbolInfo.RefreshRates(); DynamicFuturePrice = InpManualFuturePrice; DynamicCallStrike1 = InpCallStrike1; DynamicCallStrike2 = InpCallStrike2; DynamicCallStrike3 = InpCallStrike3; DynamicPutStrike1 = InpPutStrike1; DynamicPutStrike2 = InpPutStrike2; DynamicPutStrike3 = InpPutStrike3; InitializeOILevels(); InitializeKeyLevels(); if(InpOISource == OI_SOURCE_CSV_FILE) { LoadOIFromCSV(); } if(!InitializeIndicators()) { Print("Error initializing indicators"); return INIT_FAILED; } EquityHigh = AccountInfo.Equity(); EquityLow = AccountInfo.Equity(); if(InpEnableDashboard) { chart_id = ChartID(); CreateDashboard(); CreateControlPanel(); } if(InpUseAutoRecovery) { CheckExistingPositions(); } EventSetTimer(1); ArrayResize(DrawnLines, MaxLines); for(int i = 0; i < MaxLines; i++) { DrawnLines[i] = ""; } LastPrice = SymbolInfo.Bid(); CurrentBarHigh = LastPrice; CurrentBarLow = LastPrice; LastM1BarTime = iTime(_Symbol, PERIOD_M1, 0); Print("EA Initialized Successfully: Order Flow Absorption Strategy"); Print("Symbol: ", _Symbol); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(InpEnableDashboard) { ObjectsDeleteAll(chart_id, 0, -1); } if(ATRHandle != INVALID_HANDLE) IndicatorRelease(ATRHandle); if(MAFastHandle != INVALID_HANDLE) IndicatorRelease(MAFastHandle); if(MASlowHandle != INVALID_HANDLE) IndicatorRelease(MASlowHandle); if(RSIMainHandle != INVALID_HANDLE) IndicatorRelease(RSIMainHandle); EventKillTimer(); Print("EA Deinitialized"); } void OnTick() { if(Bars(_Symbol, _Period) < 100) return; UpdateMarketData(); CountTicks(); UpdatePriceDrift(); datetime currentM1BarTime = iTime(_Symbol, PERIOD_M1, 0); if(currentM1BarTime != LastM1BarTime) { OnNewM1Bar(currentM1BarTime); } if(!CheckGlobalConditions()) return; CheckTradingSignals(); ManagePositions(); if(InpEnableDashboard && TimeCurrent() - LastDashboardUpdate >= 1) { UpdateDashboard(); UpdateControlPanel(); LastDashboardUpdate = TimeCurrent(); } } void OnTimer() { if(InpEnableDashboard && TimeCurrent() - LastDashboardUpdate >= 1) { UpdateDashboard(); UpdateControlPanel(); LastDashboardUpdate = TimeCurrent(); } CheckDailyReset(); if(InpOISource == OI_SOURCE_CSV_FILE && InpCSVReloadInterval > 0) { if(TimeCurrent() - LastCSVReloadTime >= InpCSVReloadInterval * 60) { Print("CSV Reload: Scheduled reload triggered"); LastCSVReloadTime = TimeCurrent(); CachedFuturePrice = -1; LoadOIFromCSV(); } } } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { HandleControlPanelClick(sparam); } if(id == CHARTEVENT_OBJECT_ENDEDIT) { UpdateInputValues(); Print("Edit field updated: ", sparam); } } void CountTicks() { double currentPrice = SymbolInfo.Bid(); if(LastPrice == 0) { LastPrice = currentPrice; CurrentBarHigh = currentPrice; CurrentBarLow = currentPrice; return; } if(currentPrice > LastPrice) { CurrentBarUpTicks++; } else if(currentPrice < LastPrice) { CurrentBarDownTicks++; } CurrentBarVolume++; LastPrice = currentPrice; } void UpdatePriceDrift() { CurrentBarHigh = MathMax(CurrentBarHigh, SpotPrice); CurrentBarLow = MathMin(CurrentBarLow, SpotPrice); PriceDrift = CurrentBarHigh - CurrentBarLow; } void OnNewM1Bar(datetime newBarTime) { int totalTicks = CurrentBarUpTicks + CurrentBarDownTicks; if(totalTicks > 0) { OrderFlowDeltaPercent = ((double)CurrentBarUpTicks - (double)CurrentBarDownTicks) / totalTicks * 100.0; } else { OrderFlowDeltaPercent = 0; } CalculateVolumeEMAFromHistory(); DetectAbsorptionFromHistory(); CurrentBarUpTicks = 0; CurrentBarDownTicks = 0; CurrentBarVolume = 0; CurrentBarHigh = SpotPrice; CurrentBarLow = SpotPrice; LastM1BarTime = newBarTime; } void CalculateVolumeEMAFromHistory() { MqlRates rates[]; ArraySetAsSeries(rates, true); int copied = CopyRates(_Symbol, PERIOD_M1, 1, InpVolumeEmaPeriod + 1, rates); if(copied < InpVolumeEmaPeriod + 1) return; double emaAlpha = 2.0 / (InpVolumeEmaPeriod + 1); double sum = 0; for(int i = 0; i < InpVolumeEmaPeriod; i++) { sum += (double)rates[i].tick_volume; } double avgVolume = sum / InpVolumeEmaPeriod; if(VolumeEmaValue == 0) { VolumeEmaValue = avgVolume; } else { VolumeEmaValue = emaAlpha * avgVolume + (1 - emaAlpha) * VolumeEmaValue; } } void DetectAbsorptionFromHistory() { MqlRates rates[]; ArraySetAsSeries(rates, true); int copied = CopyRates(_Symbol, PERIOD_M1, 1, InpAbsorptionBars + 1, rates); if(copied < InpAbsorptionBars + 1) return; double avgVolume = 0; for(int i = 0; i < InpAbsorptionBars; i++) { avgVolume += (double)rates[i].tick_volume; } avgVolume /= InpAbsorptionBars; double volumeThreshold = avgVolume * InpMinVolumeMultiplier; int sellAbsorptionCount = 0; int buyAbsorptionCount = 0; for(int i = 1; i <= InpAbsorptionBars; i++) { double high = rates[i].high; double low = rates[i].low; double close = rates[i].close; double open = rates[i].open; int barVolume = (int)rates[i].tick_volume; double barRange = high - low; double barDrift = close - open; bool highVolume = barVolume > volumeThreshold; bool lowDrift = barRange < InpMaxPriceDriftPoints * _Point; if(highVolume && lowDrift && barDrift < 0) { sellAbsorptionCount++; } if(highVolume && lowDrift && barDrift > 0) { buyAbsorptionCount++; } } int requiredBars = (int)MathCeil(InpAbsorptionBars / 2.0); if(sellAbsorptionCount >= requiredBars && IsPriceNearPutStrike()) { CurrentAbsorptionState = ABSORPTION_SELL; } else if(buyAbsorptionCount >= requiredBars && IsPriceNearCallStrike()) { CurrentAbsorptionState = ABSORPTION_BUY; } else { CurrentAbsorptionState = ABSORPTION_NONE; } } void UpdateMarketData() { SpotPrice = SymbolInfo.Bid(); SymbolInfo.RefreshRates(); if(InpOISource == OI_SOURCE_CSV_FILE) { if(CachedFuturePrice < 0) { LoadOIFromCSV(); } FuturePrice = CachedFuturePrice; } else { FuturePrice = 0; } } bool IsPriceNearPutStrike() { double tolerance = InpOIZonePoints * _Point; for(int i = 0; i < 3; i++) { double strike = (i == 0) ? DynamicPutStrike1 : (i == 1) ? DynamicPutStrike2 : DynamicPutStrike3; if(strike > 0 && MathAbs(SpotPrice - strike) <= tolerance) { return true; } } return false; } bool IsPriceNearCallStrike() { double tolerance = InpOIZonePoints * _Point; for(int i = 0; i < 3; i++) { double strike = (i == 0) ? DynamicCallStrike1 : (i == 1) ? DynamicCallStrike2 : DynamicCallStrike3; if(strike > 0 && MathAbs(SpotPrice - strike) <= tolerance) { return true; } } return false; } bool IsInMiddleOfRange() { double tolerance = InpOIZonePoints * _Point * 2; for(int i = 0; i < 3; i++) { double putStrike = (i == 0) ? DynamicPutStrike1 : (i == 1) ? DynamicPutStrike2 : DynamicPutStrike3; double callStrike = (i == 0) ? DynamicCallStrike1 : (i == 1) ? DynamicCallStrike2 : DynamicCallStrike3; if(putStrike > 0 && MathAbs(SpotPrice - putStrike) <= tolerance) return false; if(callStrike > 0 && MathAbs(SpotPrice - callStrike) <= tolerance) return false; } return true; } bool CheckGlobalConditions() { if(!TradingEnabled) return false; if(CachedFuturePrice < 0) { return false; } if(CachedFuturePrice == 0) { return false; } if(!TerminalInfoInteger(TERMINAL_CONNECTED)) return false; if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)) return false; long spread = SymbolInfo.Spread(); if(spread > InpMaxSpread) return false; if(InpUseSessionFilter && !IsTradingSession()) return false; if(InpUseATRFilter && !CheckVolatilityFilter()) return false; if(!CheckDailyLimits()) { TradingEnabled = false; return false; } if(CheckEquityProtection()) { TradingEnabled = false; return false; } return true; } bool IsTradingSession() { MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); return dt.hour >= InpSessionStartHour && dt.hour <= InpSessionEndHour; } bool CheckVolatilityFilter() { if(ATRHandle == INVALID_HANDLE) return true; double atrValues[]; ArraySetAsSeries(atrValues, true); if(CopyBuffer(ATRHandle, 0, 0, 1, atrValues) < 1) return true; double atrValue = atrValues[0]; double atrPercent = (atrValue / SpotPrice) * 100.0; return atrPercent <= InpMaxATRPercent; } bool CheckDailyLimits() { if(DailyTradeCount >= InpMaxDailyTrades) return false; if(DailyLoss >= AccountInfo.Balance() * (InpMaxDailyLossPercent / 100.0)) return false; if(DailyProfit >= AccountInfo.Balance() * (InpMaxDailyProfitPercent / 100.0)) return false; return true; } bool CheckEquityProtection() { double currentEquity = AccountInfo.Equity(); if(EquityHigh == 0) EquityHigh = currentEquity; if(currentEquity > EquityHigh) EquityHigh = currentEquity; double dropPercent = (EquityHigh - currentEquity) / EquityHigh * 100.0; if(dropPercent >= InpEquityProtectionPercent) { EquityLow = currentEquity; return true; } return false; } void CheckDailyReset() { datetime now = TimeCurrent(); MqlDateTime dtNow, dtLast; TimeToStruct(now, dtNow); TimeToStruct(LastResetDate, dtLast); if(LastResetDate == 0 || dtLast.day_of_week != dtNow.day_of_week) { if(LastResetDate != 0 && dtLast.day != dtNow.day) { DailyTradeCount = 0; DailyPnL = 0; DailyProfit = 0; DailyLoss = 0; ConsecutiveLosses = 0; LastResetDate = now; TradingEnabled = true; Print("Daily statistics reset"); } else { LastResetDate = now; } } } void CheckTradingSignals() { if(PositionsTotal() > 0) return; if(InpUseAbsorptionFilter && IsInMiddleOfRange()) return; ENUM_MARKET_PHASE marketPhase = GetMarketPhase(); if(CheckSellConditions(marketPhase)) { ExecuteSellTrade(); } else if(CheckBuyConditions(marketPhase)) { ExecuteBuyTrade(); } } ENUM_MARKET_PHASE GetMarketPhase() { if(!InpUseMarketPhaseFilter) return PHASE_NEUTRAL; double maFast[], maSlow[]; ArraySetAsSeries(maFast, true); ArraySetAsSeries(maSlow, true); if(CopyBuffer(MAFastHandle, 0, 0, 2, maFast) < 2) return PHASE_NEUTRAL; if(CopyBuffer(MASlowHandle, 0, 0, 1, maSlow) < 1) return PHASE_NEUTRAL; double maFastCurrent = maFast[0]; double maFastPrevious = maFast[1]; double maSlowCurrent = maSlow[0]; double slope = maFastCurrent - maFastPrevious; if(maFastCurrent > maSlowCurrent && slope > 0) return PHASE_BULLISH; else if(maFastCurrent < maSlowCurrent && slope < 0) return PHASE_BEARISH; else if(MathAbs(slope) < 0.0001) return PHASE_SIDEWAYS; else return PHASE_NEUTRAL; } bool CheckSellConditions(ENUM_MARKET_PHASE marketPhase) { if(!IsPriceNearCallStrike()) return false; if(InpUseAbsorptionFilter) { if(CurrentAbsorptionState != ABSORPTION_BUY) return false; } if(!CheckOverbought()) return false; if(!IsAtResistance()) return false; return true; } bool CheckBuyConditions(ENUM_MARKET_PHASE marketPhase) { if(!IsPriceNearPutStrike()) return false; if(InpUseAbsorptionFilter) { if(CurrentAbsorptionState != ABSORPTION_SELL) return false; } if(!CheckOversold()) return false; if(!IsAtSupport()) return false; return true; } bool CheckOverbought() { if(RSIMainHandle != INVALID_HANDLE) { double rsiValues[]; ArraySetAsSeries(rsiValues, true); if(CopyBuffer(RSIMainHandle, 0, 0, 1, rsiValues) >= 1) { if(rsiValues[0] < 70) return false; } } return true; } bool CheckOversold() { if(RSIMainHandle != INVALID_HANDLE) { double rsiValues[]; ArraySetAsSeries(rsiValues, true); if(CopyBuffer(RSIMainHandle, 0, 0, 1, rsiValues) >= 1) { if(rsiValues[0] > 30) return false; } } return true; } bool IsAtResistance() { double tolerance = 100 * _Point; if(MathAbs(SpotPrice - LevelUpper1) <= tolerance || MathAbs(SpotPrice - LevelUpper2) <= tolerance) { return true; } return false; } bool IsAtSupport() { double tolerance = 100 * _Point; if(MathAbs(SpotPrice - LevelLower1) <= tolerance || MathAbs(SpotPrice - LevelLower2) <= tolerance) { return true; } return false; } void ExecuteBuyTrade() { double lotSize = CalculateLotSize(POSITION_TYPE_BUY); if(lotSize <= 0) return; double sl = 0, tp = 0; double nearestPutStrike = GetNearestPutStrike(); if(InpUseStopLoss && nearestPutStrike > 0) { sl = nearestPutStrike - InpSLBufferPoints * _Point; } else if(InpUseStopLoss) { sl = SpotPrice - InpStopLossPoints * _Point; } if(InpUseTakeProfit) { double nearestCallStrike = GetNearestCallStrike(); if(nearestCallStrike > 0) { tp = nearestCallStrike; } else { tp = SpotPrice + InpTakeProfitPoints * _Point; } } sl = NormalizeDouble(sl, _Digits); tp = NormalizeDouble(tp, _Digits); string comment = "OF_ABSORPTION_BUY"; if(Trade.Buy(lotSize, _Symbol, SpotPrice, sl, tp, comment)) { Print("Buy order executed. Lot: ", lotSize, " SL: ", sl, " TP: ", tp); DailyTradeCount++; LastTradeTime = TimeCurrent(); } else { Print("Buy order failed: ", Trade.ResultRetcodeDescription()); } } void ExecuteSellTrade() { double lotSize = CalculateLotSize(POSITION_TYPE_SELL); if(lotSize <= 0) return; double sl = 0, tp = 0; double nearestCallStrike = GetNearestCallStrike(); if(InpUseStopLoss && nearestCallStrike > 0) { sl = nearestCallStrike + InpSLBufferPoints * _Point; } else if(InpUseStopLoss) { sl = SpotPrice + InpStopLossPoints * _Point; } if(InpUseTakeProfit) { double nearestPutStrike = GetNearestPutStrike(); if(nearestPutStrike > 0) { tp = nearestPutStrike; } else { tp = SpotPrice - InpTakeProfitPoints * _Point; } } sl = NormalizeDouble(sl, _Digits); tp = NormalizeDouble(tp, _Digits); string comment = "OF_ABSORPTION_SELL"; if(Trade.Sell(lotSize, _Symbol, SpotPrice, sl, tp, comment)) { Print("Sell order executed. Lot: ", lotSize, " SL: ", sl, " TP: ", tp); DailyTradeCount++; LastTradeTime = TimeCurrent(); } else { Print("Sell order failed: ", Trade.ResultRetcodeDescription()); } } double GetNearestPutStrike() { double nearest = 0; double minDistance = DBL_MAX; double strikes[3] = {DynamicPutStrike1, DynamicPutStrike2, DynamicPutStrike3}; for(int i = 0; i < 3; i++) { if(strikes[i] > 0) { double distance = MathAbs(SpotPrice - strikes[i]); if(distance < minDistance) { minDistance = distance; nearest = strikes[i]; } } } return nearest; } double GetNearestCallStrike() { double nearest = 0; double minDistance = DBL_MAX; double strikes[3] = {DynamicCallStrike1, DynamicCallStrike2, DynamicCallStrike3}; for(int i = 0; i < 3; i++) { if(strikes[i] > 0) { double distance = MathAbs(SpotPrice - strikes[i]); if(distance < minDistance) { minDistance = distance; nearest = strikes[i]; } } } return nearest; } double CalculateLotSize(ENUM_POSITION_TYPE tradeType) { if(!InpUseMoneyManagement) { return NormalizeLot(InpLotSize); } double balance = AccountInfo.Balance(); double riskAmount = balance * (InpRiskPercent / 100.0); double slDistance; if(InpUseStopLoss) { slDistance = InpStopLossPoints * _Point; } else { slDistance = 500 * _Point; } double tickValue = SymbolInfo.TickValue(); double tickSize = SymbolInfo.TickSize(); double lotSize = riskAmount / (slDistance * tickValue / tickSize); lotSize = NormalizeLot(lotSize); double maxLot = SymbolInfo.LotsMax(); double minLot = SymbolInfo.LotsMin(); if(lotSize > maxLot) lotSize = maxLot; if(lotSize < minLot) lotSize = 0; return lotSize; } double NormalizeLot(double lot) { double step = SymbolInfo.LotsStep(); return MathFloor(lot / step) * step; } void ManagePositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!PositionSelectByTicket(PositionGetTicket(i))) continue; string symbol = PositionGetString(POSITION_SYMBOL); if(symbol != _Symbol) continue; int magic = (int)PositionGetInteger(POSITION_MAGIC); if(magic != InpMagicNumber) continue; double currentProfit = PositionGetDouble(POSITION_PROFIT); DailyPnL += currentProfit; if(currentProfit > 0) DailyProfit += currentProfit; else DailyLoss += currentProfit; if(currentProfit < 0) { ConsecutiveLosses++; } else { ConsecutiveLosses = 0; } if(InpCloseAllOnReverse && ConsecutiveLosses >= InpMaxConsecutiveLosses) { Trade.PositionClose(PositionGetTicket(i)); TradingEnabled = false; continue; } if(InpUseTrailingStop) { ManageTrailingStop(); } if(InpUseSoftStopLoss && currentProfit > 0) { double sl = PositionGetDouble(POSITION_SL); double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); double distanceToEntry = MathAbs(SpotPrice - entryPrice); if(distanceToEntry > InpTrailingStartPoints * _Point) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { double newSl = SpotPrice - InpTrailingStepPoints * _Point; if(newSl > sl) { Trade.PositionModify(PositionGetTicket(i), newSl, PositionGetDouble(POSITION_TP)); } } else { double newSl = SpotPrice + InpTrailingStepPoints * _Point; if(newSl < sl || sl == 0) { Trade.PositionModify(PositionGetTicket(i), newSl, PositionGetDouble(POSITION_TP)); } } } } } } void ManageTrailingStop() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!PositionSelectByTicket(PositionGetTicket(i))) continue; string symbol = PositionGetString(POSITION_SYMBOL); if(symbol != _Symbol) continue; int magic = (int)PositionGetInteger(POSITION_MAGIC); if(magic != InpMagicNumber) continue; double currentSl = PositionGetDouble(POSITION_SL); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { double newSl = SpotPrice - InpTrailingStepPoints * _Point; if(newSl > currentSl) { Trade.PositionModify(PositionGetTicket(i), newSl, PositionGetDouble(POSITION_TP)); } } else { double newSl = SpotPrice + InpTrailingStepPoints * _Point; if(newSl < currentSl || currentSl == 0) { Trade.PositionModify(PositionGetTicket(i), newSl, PositionGetDouble(POSITION_TP)); } } } } void CloseAllPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!PositionSelectByTicket(PositionGetTicket(i))) continue; string symbol = PositionGetString(POSITION_SYMBOL); if(symbol != _Symbol) continue; int magic = (int)PositionGetInteger(POSITION_MAGIC); if(magic == InpMagicNumber) { Trade.PositionClose(PositionGetTicket(i)); } } } void InitializeOILevels() { CallLevels[0] = DynamicCallStrike1; CallLevels[1] = DynamicCallStrike2; CallLevels[2] = DynamicCallStrike3; PutLevels[0] = DynamicPutStrike1; PutLevels[1] = DynamicPutStrike2; PutLevels[2] = DynamicPutStrike3; CallOI[0] = 0; CallOI[1] = 0; CallOI[2] = 0; PutOI[0] = 0; PutOI[1] = 0; PutOI[2] = 0; } void InitializeKeyLevels() { double allLevels[]; int count = 0; for(int i = 0; i < 3; i++) { if(CallLevels[i] > 0) { ArrayResize(allLevels, count + 1); allLevels[count] = CallLevels[i]; count++; } if(PutLevels[i] > 0) { ArrayResize(allLevels, count + 1); allLevels[count] = PutLevels[i]; count++; } } if(count > 0) { ArraySort(allLevels); LevelLower1 = allLevels[0]; LevelLower2 = (count > 1) ? allLevels[1] : allLevels[0]; LevelMid = (count > 2) ? allLevels[count/2] : allLevels[0]; LevelUpper2 = (count > 1) ? allLevels[count-1] : allLevels[0]; LevelUpper1 = (count > 2) ? allLevels[count-2] : allLevels[0]; } } bool InitializeIndicators() { ATRHandle = iATR(_Symbol, PERIOD_H1, 14); if(ATRHandle == INVALID_HANDLE) { Print("Failed to create ATR indicator"); return false; } MAFastHandle = iMA(_Symbol, InpTrendTF, InpTrendMAPeriod1, 0, MODE_EMA, PRICE_CLOSE); MASlowHandle = iMA(_Symbol, InpTrendTF, InpTrendMAPeriod2, 0, MODE_SMA, PRICE_CLOSE); if(MAFastHandle == INVALID_HANDLE || MASlowHandle == INVALID_HANDLE) { Print("Failed to create MA indicators"); return false; } RSIMainHandle = iRSI(_Symbol, PERIOD_H1, 14, PRICE_CLOSE); if(RSIMainHandle == INVALID_HANDLE) { Print("Failed to create RSI indicator"); return false; } return true; } void LoadOIFromCSV() { int filehandle = FileOpen(InpOICsvPath, FILE_READ | FILE_CSV | FILE_ANSI, ','); if(filehandle == INVALID_HANDLE) { Print("CSV ERROR: Cannot open ", InpOICsvPath); return; } int callIndex = 0; int putIndex = 0; double futurePrice = 0.0; bool isFirstLine = true; while(!FileIsEnding(filehandle) && (callIndex < 3 || putIndex < 3)) { string row = FileReadString(filehandle); if(isFirstLine) { isFirstLine = false; if(StringFind(row, "Type") >= 0 || StringFind(row, "Strike") >= 0 || StringFind(row, "OI") >= 0) { continue; } } string data[]; int count = StringSplit(row, ',', data); if(count >= 3) { string type = data[0]; double strike = StringToDouble(data[1]); int oi = (int)StringToInteger(data[2]); if(StringFind(type, "Future") >= 0) { futurePrice = strike; } else if(type == "CALL" && callIndex < 3) { CallLevels[callIndex] = strike; CallOI[callIndex] = oi; callIndex++; } else if(type == "PUT" && putIndex < 3) { PutLevels[putIndex] = strike; PutOI[putIndex] = oi; putIndex++; } } } FileClose(filehandle); for(int i = callIndex; i < 3; i++) { CallLevels[i] = 0; CallOI[i] = 0; } for(int i = putIndex; i < 3; i++) { PutLevels[i] = 0; PutOI[i] = 0; } if(futurePrice > 0) { CachedFuturePrice = futurePrice; DynamicFuturePrice = futurePrice; FuturePrice = futurePrice; Print("CSV SUCCESS: FuturePrice=", futurePrice); } InitializeKeyLevels(); } void CheckExistingPositions() { for(int i = 0; i < PositionsTotal(); i++) { if(PositionSelectByTicket(PositionGetTicket(i))) { string symbol = PositionGetString(POSITION_SYMBOL); if(symbol == _Symbol) { int magic = (int)PositionGetInteger(POSITION_MAGIC); if(magic == InpMagicNumber) { Print("Existing position found: ", PositionGetInteger(POSITION_TYPE)); DailyTradeCount++; } } } } } void CreateDashboard() { int panelWidth = 280; int panelHeight = 300; CreatePanel("DashboardPanel", 10, 10, panelWidth, panelHeight, C'25,25,35', BORDER_FLAT); CreateLabel("DB_Title", 20, 20, "ORDER FLOW ABSORPTION EA", clrYellow, 10); UpdateDashboard(); } void UpdateDashboard() { MqlRates rates[]; int copied = CopyRates(_Symbol, PERIOD_M1, 0, 1, rates); UpdateLabel("DB_Symbol", 20, 45, "Symbol: " + _Symbol, clrWhite, 8); UpdateLabel("DB_Price", 20, 65, "Price: " + DoubleToString(SpotPrice, 2), clrCyan, 8); string deltaText = DoubleToString(OrderFlowDeltaPercent, 1) + "%"; color deltaColor = OrderFlowDeltaPercent > 0 ? clrLime : (OrderFlowDeltaPercent < 0 ? clrRed : clrGray); UpdateLabel("DB_Delta", 20, 85, "Delta: " + deltaText, deltaColor, 8); string absorptionText = ""; color absorptionColor = clrGray; switch(CurrentAbsorptionState) { case ABSORPTION_BUY: absorptionText = "BUY ABSORPTION"; absorptionColor = clrLime; break; case ABSORPTION_SELL: absorptionText = "SELL ABSORPTION"; absorptionColor = clrRed; break; default: absorptionText = "NONE"; break; } UpdateLabel("DB_Absorption", 20, 105, "Absorption: " + absorptionText, absorptionColor, 8); int currentVol = CurrentBarVolume > 0 ? CurrentBarVolume : (copied > 0 ? (int)rates[0].tick_volume : 0); UpdateLabel("DB_Volume", 20, 125, "Volume: " + IntegerToString(currentVol) + " (Avg: " + IntegerToString((int)VolumeEmaValue) + ")", clrWhite, 8); string driftText = DoubleToString(PriceDrift / _Point, 1) + " pts"; color driftColor = PriceDrift < InpMaxPriceDriftPoints * _Point ? clrLime : clrOrange; UpdateLabel("DB_PriceDrift", 20, 145, "Drift: " + driftText, driftColor, 8); string csvText = ""; color csvColor = clrGray; if(CachedFuturePrice < 0) { csvText = "CSV: LOADING..."; csvColor = clrYellow; } else if(CachedFuturePrice > 0) { csvText = "CSV: OK (" + DoubleToString(CachedFuturePrice, 2) + ")"; csvColor = clrLime; } else { csvText = "CSV: FAILED"; csvColor = clrRed; } UpdateLabel("DB_CSVStatus", 20, 160, csvText, csvColor, 8); UpdateLabel("DB_Trades", 20, 180, "Daily: " + IntegerToString(DailyTradeCount) + "/" + IntegerToString(InpMaxDailyTrades), clrWhite, 8); UpdateLabel("DB_PnL", 20, 200, "Daily PnL: " + DoubleToString(DailyPnL, 2), DailyPnL >= 0 ? clrLime : clrRed, 8); string tradingStatus = TradingEnabled ? "ENABLED" : "DISABLED"; color statusColor = TradingEnabled ? clrLime : clrRed; UpdateLabel("DB_Status", 20, 220, "Trading: " + tradingStatus, statusColor, 8); string nearZone = ""; if(IsPriceNearPutStrike()) nearZone = "NEAR PUT"; else if(IsPriceNearCallStrike()) nearZone = "NEAR CALL"; else nearZone = "MIDDLE"; color zoneColor = (nearZone == "MIDDLE") ? clrOrange : clrCyan; UpdateLabel("DB_Zone", 20, 240, "Zone: " + nearZone, zoneColor, 8); string ticksText = "Ticks: " + IntegerToString(CurrentBarUpTicks) + "/" + IntegerToString(CurrentBarDownTicks); UpdateLabel("DB_Ticks", 20, 260, ticksText, clrWhite, 8); } void CreateControlPanel() { CreateControlPanelUI(); UpdateControlPanelValues(); } void CreateControlPanelUI() { int panelWidth = 850; int topPanelHeight = 50; int oiPanelHeight = 120; CreatePanel("CP_TopPanel", ControlPanelX, ControlPanelY, panelWidth, topPanelHeight, C'45,45,45', BORDER_FLAT); CreateButton("CP_CloseAll", ControlPanelX + 10, ControlPanelY + 10, 830, 30, "Close All Positions", clrRed, clrWhite); CreatePanel("CP_OIDataPanel", ControlPanelX, ControlPanelY + topPanelHeight + 10, panelWidth, oiPanelHeight, C'45,45,45', BORDER_FLAT); CreateLabel("CP_FuturePriceLabel", ControlPanelX + 10, ControlPanelY + topPanelHeight + 20, "Future Price:", clrYellow, 8); CreateLabel("CP_CallStrikesLabel", ControlPanelX + 200, ControlPanelY + topPanelHeight + 20, "Call Strikes:", clrYellow, 8); CreateLabel("CP_PutStrikesLabel", ControlPanelX + 200, ControlPanelY + topPanelHeight + 50, "Put Strikes:", clrYellow, 8); CreateEditField("CP_FuturePrice", ControlPanelX + 90, ControlPanelY + topPanelHeight + 15, 80, 20, "0", clrWhite, clrBlack); CreateEditField("CP_CallStrike1", ControlPanelX + 280, ControlPanelY + topPanelHeight + 15, 80, 20, "0", clrWhite, clrBlack); CreateEditField("CP_CallStrike2", ControlPanelX + 370, ControlPanelY + topPanelHeight + 15, 80, 20, "0", clrWhite, clrBlack); CreateEditField("CP_CallStrike3", ControlPanelX + 460, ControlPanelY + topPanelHeight + 15, 80, 20, "0", clrWhite, clrBlack); CreateEditField("CP_PutStrike1", ControlPanelX + 280, ControlPanelY + topPanelHeight + 45, 80, 20, "0", clrWhite, clrBlack); CreateEditField("CP_PutStrike2", ControlPanelX + 370, ControlPanelY + topPanelHeight + 45, 80, 20, "0", clrWhite, clrBlack); CreateEditField("CP_PutStrike3", ControlPanelX + 460, ControlPanelY + topPanelHeight + 45, 80, 20, "0", clrWhite, clrBlack); CreateButton("CP_UpdateOI", ControlPanelX + 750, ControlPanelY + topPanelHeight + 25, 90, 35, "Update OI Data", clrCyan, clrBlack); } void CreatePanel(string name, int x, int y, int width, int height, color bgColor, int borderType) { if(ObjectFind(chart_id, name) < 0) { ObjectCreate(chart_id, name, OBJ_RECTANGLE_LABEL, 0, 0, 0, 0, 0); } ObjectSetInteger(chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(chart_id, name, OBJPROP_XSIZE, width); ObjectSetInteger(chart_id, name, OBJPROP_YSIZE, height); ObjectSetInteger(chart_id, name, OBJPROP_BGCOLOR, bgColor); ObjectSetInteger(chart_id, name, OBJPROP_BORDER_TYPE, borderType); ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1000); } void CreateButton(string name, int x, int y, int width, int height, string text, color bgColor, color textColor) { if(ObjectFind(chart_id, name) < 0) { ObjectCreate(chart_id, name, OBJ_BUTTON, 0, 0, 0, 0, 0); } ObjectSetInteger(chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(chart_id, name, OBJPROP_XSIZE, width); ObjectSetInteger(chart_id, name, OBJPROP_YSIZE, height); ObjectSetString(chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(chart_id, name, OBJPROP_BGCOLOR, bgColor); ObjectSetInteger(chart_id, name, OBJPROP_COLOR, textColor); ObjectSetInteger(chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1001); } void CreateLabel(string name, int x, int y, string text, color textColor, int fontSize) { if(ObjectFind(chart_id, name) < 0) { ObjectCreate(chart_id, name, OBJ_LABEL, 0, 0, 0, 0, 0); } ObjectSetInteger(chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetString(chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(chart_id, name, OBJPROP_COLOR, textColor); ObjectSetInteger(chart_id, name, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1002); } void CreateEditField(string name, int x, int y, int width, int height, string defaultText, color bgColor, color textColor) { if(ObjectFind(chart_id, name) < 0) { ObjectCreate(chart_id, name, OBJ_EDIT, 0, 0, 0, 0, 0); } ObjectSetInteger(chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(chart_id, name, OBJPROP_XSIZE, width); ObjectSetInteger(chart_id, name, OBJPROP_YSIZE, height); ObjectSetString(chart_id, name, OBJPROP_TEXT, defaultText); ObjectSetInteger(chart_id, name, OBJPROP_BGCOLOR, bgColor); ObjectSetInteger(chart_id, name, OBJPROP_COLOR, textColor); ObjectSetInteger(chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1003); } void UpdateLabel(string name, int x, int y, string text, color textColor, int fontSize) { if(ObjectFind(chart_id, name) < 0) { CreateLabel(name, x, y, text, textColor, fontSize); } else { ObjectSetInteger(chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetString(chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(chart_id, name, OBJPROP_COLOR, textColor); ObjectSetInteger(chart_id, name, OBJPROP_FONTSIZE, fontSize); } } void UpdateControlPanel() { UpdateEditField("CP_FuturePrice", DoubleToString(DynamicFuturePrice, 2)); UpdateEditField("CP_CallStrike1", DoubleToString(DynamicCallStrike1, 2)); UpdateEditField("CP_CallStrike2", DoubleToString(DynamicCallStrike2, 2)); UpdateEditField("CP_CallStrike3", DoubleToString(DynamicCallStrike3, 2)); UpdateEditField("CP_PutStrike1", DoubleToString(DynamicPutStrike1, 2)); UpdateEditField("CP_PutStrike2", DoubleToString(DynamicPutStrike2, 2)); UpdateEditField("CP_PutStrike3", DoubleToString(DynamicPutStrike3, 2)); } void UpdateEditField(string name, string value) { if(ObjectFind(chart_id, name) >= 0) { ObjectSetString(chart_id, name, OBJPROP_TEXT, value); } } void UpdateControlPanelValues() { UpdateEditField("CP_FuturePrice", DoubleToString(DynamicFuturePrice, 2)); UpdateEditField("CP_CallStrike1", DoubleToString(DynamicCallStrike1, 2)); UpdateEditField("CP_CallStrike2", DoubleToString(DynamicCallStrike2, 2)); UpdateEditField("CP_CallStrike3", DoubleToString(DynamicCallStrike3, 2)); UpdateEditField("CP_PutStrike1", DoubleToString(DynamicPutStrike1, 2)); UpdateEditField("CP_PutStrike2", DoubleToString(DynamicPutStrike2, 2)); UpdateEditField("CP_PutStrike3", DoubleToString(DynamicPutStrike3, 2)); } void HandleControlPanelClick(string name) { if(name == "CP_CloseAll") { CloseAllPositions(); } else if(name == "CP_UpdateOI") { UpdateInputValues(); InitializeOILevels(); InitializeKeyLevels(); Print("OI Levels Updated"); } } void UpdateInputValues() { string value; if(ObjectGetString(chart_id, "CP_FuturePrice", OBJPROP_TEXT, 0, value)) { DynamicFuturePrice = StringToDouble(value); } if(ObjectGetString(chart_id, "CP_CallStrike1", OBJPROP_TEXT, 0, value)) { DynamicCallStrike1 = StringToDouble(value); } if(ObjectGetString(chart_id, "CP_CallStrike2", OBJPROP_TEXT, 0, value)) { DynamicCallStrike2 = StringToDouble(value); } if(ObjectGetString(chart_id, "CP_CallStrike3", OBJPROP_TEXT, 0, value)) { DynamicCallStrike3 = StringToDouble(value); } if(ObjectGetString(chart_id, "CP_PutStrike1", OBJPROP_TEXT, 0, value)) { DynamicPutStrike1 = StringToDouble(value); } if(ObjectGetString(chart_id, "CP_PutStrike2", OBJPROP_TEXT, 0, value)) { DynamicPutStrike2 = StringToDouble(value); } if(ObjectGetString(chart_id, "CP_PutStrike3", OBJPROP_TEXT, 0, value)) { DynamicPutStrike3 = StringToDouble(value); } InitializeOILevels(); InitializeKeyLevels(); if(InpOISource == OI_SOURCE_CSV_FILE) { CachedFuturePrice = -1; LoadOIFromCSV(); } }