diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..7b5efd6 Binary files /dev/null and b/.DS_Store differ diff --git a/OI EA/.DS_Store b/OI EA/.DS_Store new file mode 100644 index 0000000..5aa50d8 Binary files /dev/null and b/OI EA/.DS_Store differ diff --git a/OI EA/AGENTS.md b/OI EA/AGENTS.md new file mode 100644 index 0000000..598c717 --- /dev/null +++ b/OI EA/AGENTS.md @@ -0,0 +1,147 @@ +# MeanRevisionEA - Agent Guide + +This repository contains a MetaTrader 5 Expert Advisor (EA) for XAUUSD trading using mean reversion strategies with Open Interest levels. + +## Build / Lint / Test Commands + +MQL5 files are compiled via MetaEditor IDE, not command line. Use MetaEditor (F4 in MT5) → Open OI_MeanReversion_Pro_XAUUSD_A.mq5 → Press F7 to compile. + +**File Encoding:** UTF-16LE with CRLF line endings. MetaEditor handles this automatically. + +**Testing:** Use MetaTrader 5 Strategy Tester. No automated test framework. + +## Code Style Guidelines + +### Naming Conventions + +**Input Parameters:** `Inp` prefix + PascalCase +```mql5 +input double InpLotSize = 0.1; +input bool InpUseStopLoss = true; +input ENUM_OI_SOURCE InpOISource = OI_SOURCE_MANUAL; +``` + +**Global Variables:** PascalCase (no prefix) +```mql5 +double DeltaPrice = 0.0; +double SpotPrice = 0.0; +int DailyTradeCount = 0; +``` + +**Functions:** PascalCase with descriptive names +```mql5 +void InitializeOILevels() +bool CheckGlobalConditions() +double CalculateLotSize(ENUM_POSITION_TYPE tradeType) +``` + +**Local Variables:** camelCase +```mql5 +double lotSize = NormalizeLot(ManualLotSize); +double riskAmount = balance * (InpRiskPercent / 100.0); +``` + +**Object Names:** Descriptive names with prefixes (e.g., "CP_" for control panel) +```mql5 +CreateButton("CP_Sell", x, y, width, height, "Sell Now", clrRed, clrWhite); +``` + +### Enums and Constants + +**Enums:** `ENUM_` prefix, uppercase values +```mql5 +enum ENUM_MARKET_PHASE { + PHASE_BULLISH, + PHASE_BEARISH, + PHASE_SIDEWAYS, + PHASE_NEUTRAL +}; +``` + +### Imports and Includes + +Use angle brackets with backslashes: +```mql5 +#include +#include +``` + +### Function Documentation + +Use MetaTrader's standard format: +```mql5 +//+------------------------------------------------------------------+ +//| Calculate Lot Size | +//+------------------------------------------------------------------+ +double CalculateLotSize(ENUM_POSITION_TYPE tradeType) { } +``` + +### Error Handling + +Check return values and validate inputs: +```mql5 +if(Trade.Buy(lotSize, _Symbol, SpotPrice, sl, tp, "BUY")) { + Print("BUY executed"); +} else { + Print("BUY failed. Error: ", Trade.ResultRetcodeDescription()); +} +if(PendingPriceValue <= 0) { + Print("Invalid price"); + return; +} +``` + +### Conditionals and Loops + +No space after `if`/`for`, space after condition: +```mql5 +if(DeltaDeviation <= threshold) + return false; +for(int i = PositionsTotal() - 1; i >= 0; i--) { } +``` + +### Arrays and Strings + +Use MQL5 functions for arrays and strings: +```mql5 +ArrayResize(result, count + 1); +ArraySort(CallLevels); +StringToDouble(), StringToInteger(), DoubleToString(value, _Digits) +``` + +### Color and Object Management + +Use MQL5 color constants and check object existence: +```mql5 +color PanelColor = C'30,30,30'; +color TextColor = clrWhite; +if(ObjectFind(chart_id, name) < 0) + ObjectCreate(chart_id, name, OBJ_LABEL, 0, 0, 0); +ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1001); +``` + +### Trade Operations + +Normalize values and use CTrade library: +```mql5 +NormalizeDouble(price, _Digits) +NormalizeLot(lotSize) +CTrade Trade; +Trade.SetExpertMagicNumber(InpMagicNumber); +Trade.Buy(lotSize, _Symbol, price, sl, tp, comment); +``` + +### Event Handlers + +Implement required MQL5 handlers: +```mql5 +int OnInit() // Initialization +void OnDeinit() // Cleanup +void OnTick() // Price tick +void OnTimer() // Timer events +void OnChartEvent() // UI interactions +``` + +### Comments + +Use `//` for single-line, `/* */` for multi-line. Thai comments acceptable for user messages. \ No newline at end of file diff --git a/OI EA/OI_MeanReversion_Pro_XAUUSD.mq5 b/OI EA/OI_MeanReversion_Pro_XAUUSD.mq5 new file mode 100644 index 0000000..976c613 --- /dev/null +++ b/OI EA/OI_MeanReversion_Pro_XAUUSD.mq5 @@ -0,0 +1,2290 @@ +//+------------------------------------------------------------------+ +//| OI_MeanReversion_Pro_XAUUSD_V2.mq5 | +//| Professional OI Mean Reversion System | +//| Version 2.0 | +//+------------------------------------------------------------------+ +#property copyright "Professional OI Mean Reversion System" +#property link "" +#property version "2.00" +#property description "Advanced Mean Reversion with OI Levels & Multi-Timeframe Filtering" +#property strict + +//--- Includes +#include +#include +#include +#include +#include +#include +#include +#include + +//--- Market Phase Enum (Moved to global scope) +enum ENUM_MARKET_PHASE +{ + PHASE_BULLISH, + PHASE_BEARISH, + PHASE_SIDEWAYS, + PHASE_NEUTRAL +}; + +//--- Input Parameters (จัดกลุ่มตามสเปก) +// Group A: OI & Delta Configuration +input group "=== OI & DELTA SETTINGS ===" +enum ENUM_OI_SOURCE { + OI_SOURCE_MANUAL, // Manual Input + OI_SOURCE_CSV_FILE, // CSV File + OI_SOURCE_AUTO_SYNC // Auto Sync (Future) +}; +input ENUM_OI_SOURCE InpOISource = OI_SOURCE_CSV_FILE; +input string InpOICsvPath = "oi_data.csv"; // Path to CSV file +input int InpCSVReloadInterval = 60; // CSV reload interval (minutes, 0=disabled) +input double InpManualFuturePrice = 0.0; // Manual future price input + +// OI Levels (Manual Input - สามารถใส่ได้ถึง 3 ระดับ) +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; + +// Delta Calculation +input int InpDeltaEmaPeriod = 20; // EMA period for Delta average +input double InpDeviationThreshold = 20.0; // Deviation threshold in points +input double InpDeviationMultiplier = 1.5; // Deviation multiplier for entry + +// Group B: Trading Configuration +input group "=== TRADING SETTINGS ===" +input double InpLotSize = 0.1; // Fixed lot size +input bool InpUseMoneyManagement = true; // Use money management +input double InpRiskPercent = 1.0; // Risk per trade (%) +input bool InpUseStopLoss = true; // Use stop loss +input bool InpUseTakeProfit = true; // Use take profit +input int InpStopLossPoints = 300; // SL in points +input int InpTakeProfitPoints = 500; // TP in points +input int InpMaxSpread = 200; // Max spread in points +input int InpMaxSlippage = 10; // Max slippage in points +input int InpMagicNumber = 202412; // Magic number +input bool InpCloseOnZeroDeviation = true; // Close when deviation crosses zero + +// Group C: Market Filters +input group "=== MARKET FILTERS ===" +input bool InpUseMarketPhaseFilter = true; // Filter by market phase +input ENUM_TIMEFRAMES InpTrendTF = PERIOD_H4; // Trend timeframe +input int InpTrendMAPeriod1 = 50; // Fast MA period +input int InpTrendMAPeriod2 = 200; // Slow MA period +input bool InpUseATRFilter = true; // Use ATR volatility filter +input double InpMaxATRPercent = 2.0; // Max ATR% for trading +input bool InpUseSessionFilter = true; // Filter by trading session +input int InpSessionStartHour = 8; // Session start (GMT) +input int InpSessionEndHour = 22; // Session end (GMT) +input bool InpAvoidHighImpactNews = true; // Avoid high impact news + +// Group D: Risk Management +input group "=== RISK MANAGEMENT ===" +input double InpMaxDailyLossPercent = 3.0; // Max daily loss % +input double InpMaxDailyProfitPercent = 5.0; // Max daily profit % +input int InpMaxConsecutiveLosses = 3; // Max consecutive losses +input bool InpDisableAfterMaxLoss = true; // Disable after max loss +input double InpEquityProtectionPercent = 10.0; // Equity protection % +input bool InpCloseAllOnReverse = false; // Close all on market reversal +input int InpMaxDailyTrades = 10; // Maximum trades per day + +// Group E: Advanced Features +input group "=== ADVANCED FEATURES ===" +input bool InpUseTrailingStop = false; // Use trailing stop +input int InpTrailingStartPoints = 150; // Trailing start in points +input int InpTrailingStepPoints = 50; // Trailing step in points +input bool InpUseSoftStopLoss = true; // Use soft stop loss (close manually) +input bool InpUseAutoRecovery = true; // Auto recover after restart +input bool InpEnableDashboard = true; // Enable dashboard display + +//--- Global Objects +CTrade Trade; +CSymbolInfo SymbolInfo; +CAccountInfo AccountInfo; +CPositionInfo PositionInfo; +COrderInfo OrderInfo; +CHistoryOrderInfo HistoryOrderInfo; + +//--- Global Variables +double DeltaPrice = 0.0; +double DeltaEMA = 0.0; +double DeltaDeviation = 0.0; +double FuturePrice = 0.0; +double SpotPrice = 0.0; + +// OI Levels Arrays +double CallLevels[3]; +double PutLevels[3]; +int CallOI[3]; +int PutOI[3]; + +// Dynamic OI Data Variables (modifiable) +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; + +// Key Levels from Dashboard +double LevelUpper1 = 0.0; +double LevelUpper2 = 0.0; +double LevelMid = 0.0; +double LevelLower1 = 0.0; +double LevelLower2 = 0.0; + +// Trading Statistics +int DailyTradeCount = 0; +double DailyPnL = 0.0; +double DailyProfit = 0.0; +double DailyLoss = 0.0; +int ConsecutiveLosses = 0; +datetime LastTradeTime = 0; +datetime LastResetDate = 0; + +// Risk Management +bool TradingEnabled = true; +double EquityHigh = 0.0; +double EquityLow = 0.0; + +// Indicator Handles +int ATRHandle = INVALID_HANDLE; +int MAFastHandle = INVALID_HANDLE; +int MASlowHandle = INVALID_HANDLE; +int RSIMainHandle = INVALID_HANDLE; + +// Dashboard +long chart_id = 0; // Chart window ID +int DashboardSubWindow = -1; +color PanelColor = C'30,30,30'; +color TextColor = clrWhite; +color ProfitColor = clrLime; +color LossColor = clrRed; +color WarningColor = clrOrange; + +// News Filter +bool NewsBlockActive = false; +datetime NewsBlockStart = 0; +datetime NewsBlockEnd = 0; + +// CSV Cache Variables +double CachedFuturePrice = -1; +string LoadedCSVPath = ""; +bool CSVLoadLogged = false; +datetime LastCSVReloadTime = 0; + +//=== NEW: Control Panel Variables === +// Control Panel Position - Moved below dashboard +int ControlPanelX = 10; +int ControlPanelY = 210; + +// Input Values for Control Panel +double PendingPriceValue = 0.0; +double ManualLotSize = 0.5; +int ManualSLPoints = 0; +int ManualTPPoints = 300; + +// OI Data Input Values +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; + +// Control Panel Object Names +string ControlPanelObjects[] = { + // Top Panel Objects + "CP_TopPanel", + "CP_CloseAll", + + // OI Data Panel Objects + "CP_OIDataPanel", + "CP_FuturePrice", + "CP_CallStrike1", + "CP_CallStrike2", + "CP_CallStrike3", + "CP_PutStrike1", + "CP_PutStrike2", + "CP_PutStrike3", + "CP_UpdateOI", + + // Trading Panel Objects + "CP_TradingPanel", + "CP_PendingPrice", + "CP_LotSize", + "CP_SLPoints", + "CP_TPPoints", + "CP_Sell", + "CP_Buy", + "CP_PendingSell", + "CP_PendingBuy", + "CP_CloseAllPending", + "CP_CloseSell", + "CP_CloseBuy" +}; + +//--- Global Variables for Line Management +string DrawnLines[]; // Array to store drawn line names +int MaxLines = 10; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + //--- Initialize trading objects + Trade.SetExpertMagicNumber(InpMagicNumber); + Trade.SetDeviationInPoints(InpMaxSlippage); + Trade.SetTypeFilling(ORDER_FILLING_IOC); + + //--- Initialize symbol info + SymbolInfo.Name(_Symbol); + SymbolInfo.RefreshRates(); + + //--- Initialize Dynamic OI Variables with input parameters + DynamicFuturePrice = InpManualFuturePrice; + DynamicCallStrike1 = InpCallStrike1; + DynamicCallStrike2 = InpCallStrike2; + DynamicCallStrike3 = InpCallStrike3; + DynamicPutStrike1 = InpPutStrike1; + DynamicPutStrike2 = InpPutStrike2; + DynamicPutStrike3 = InpPutStrike3; + + //--- Initialize OI Levels + InitializeOILevels(); + + //--- Initialize Key Levels + InitializeKeyLevels(); + + //--- Initialize indicators + if(!InitializeIndicators()) + { + Print("Error initializing indicators"); + return INIT_FAILED; + } + + //--- Initialize risk management + EquityHigh = AccountInfo.Equity(); + EquityLow = AccountInfo.Equity(); + + //--- Initialize dashboard + if(InpEnableDashboard) + { + chart_id = ChartID(); // Get current chart ID + CreateDashboard(); + CreateControlPanel(); // Create new control panel + } + + //--- Auto-recovery: Check for existing positions + if(InpUseAutoRecovery) + { + CheckExistingPositions(); + } + + //--- Set timer for updates (every 1 second) + EventSetTimer(1); + + //--- Initialize lines array + ArrayResize(DrawnLines, MaxLines); + for(int i = 0; i < MaxLines; i++) + { + DrawnLines[i] = ""; + } + + Print("EA Initialized Successfully"); + Print("Symbol: ", _Symbol); + Print("Account Balance: ", AccountInfo.Balance()); + Print("Trading Enabled: ", TradingEnabled); + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + //--- Clean up dashboard and control panel + if(InpEnableDashboard) + { + ObjectsDeleteAll(chart_id, 0, -1); + } + + //--- Release indicator handles + if(ATRHandle != INVALID_HANDLE) IndicatorRelease(ATRHandle); + if(MAFastHandle != INVALID_HANDLE) IndicatorRelease(MAFastHandle); + if(MASlowHandle != INVALID_HANDLE) IndicatorRelease(MASlowHandle); + if(RSIMainHandle != INVALID_HANDLE) IndicatorRelease(RSIMainHandle); + + //--- Kill timer + EventKillTimer(); + + Print("EA Deinitialized"); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + //--- Skip if not enough bars + if(Bars(_Symbol, _Period) < 100) + return; + + //--- Update market data + if(!UpdateMarketData()) + return; + + //--- Check global conditions + if(!CheckGlobalConditions()) + return; + + //--- Check for trade signals + CheckTradingSignals(); + + //--- Manage existing positions + ManagePositions(); + + //--- Update dashboard and control panel + if(InpEnableDashboard) + { + UpdateDashboard(); + UpdateControlPanel(); + } +} + +//+------------------------------------------------------------------+ +//| Timer function | +//+------------------------------------------------------------------+ +void OnTimer() +{ + //--- Update dashboard periodically + if(InpEnableDashboard) + { + UpdateDashboard(); + UpdateControlPanel(); + } + + //--- Check news events + if(InpAvoidHighImpactNews) + { + CheckNewsEvents(); + } + + //--- Reset daily statistics if new day + CheckDailyReset(); + + //--- CSV reload check + if(InpOISource == OI_SOURCE_CSV_FILE && InpCSVReloadInterval > 0) { + if(TimeCurrent() - LastCSVReloadTime >= InpCSVReloadInterval * 60) { + Print("CSV Reload: Scheduled reload triggered"); + LastCSVReloadTime = TimeCurrent(); + CachedFuturePrice = -1; + LoadOIFromCSV(); + } + } +} + +//+------------------------------------------------------------------+ +//| Chart Event Handler | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + //--- Handle control panel button clicks + if(id == CHARTEVENT_OBJECT_CLICK) + { + HandleControlPanelClick(sparam); + } + + //--- Handle edit field changes (สำหรับรับค่าจาก Input Fields) + if(id == CHARTEVENT_OBJECT_ENDEDIT) + { + // อัพเดทค่าทันทีเมื่อมีการแก้ไขข้อมูลใน Edit Fields + UpdateInputValues(); + Print("Edit field updated: ", sparam, " Value: ", ObjectGetString(chart_id, sparam, OBJPROP_TEXT)); + } +} + +//+------------------------------------------------------------------+ +//| Create Control Panel | +//+------------------------------------------------------------------+ +void CreateControlPanel() +{ + //--- Create top control panel + CreateControlPanelUI(); + + //--- Set initial values + UpdateControlPanelValues(); +} + +//+------------------------------------------------------------------+ +//| Create Control Panel UI | +//+------------------------------------------------------------------+ +void CreateControlPanelUI() +{ + int panelWidth = 850; // Match dashboard width + int topPanelHeight = 50; + int oiPanelHeight = 120; + int tradingPanelHeight = 180; + + //--- Top Panel (Quick Tools) + CreatePanel("CP_TopPanel", ControlPanelX, ControlPanelY, panelWidth, topPanelHeight, C'45,45,45', BORDER_FLAT); + + //--- Close All Button + CreateButton("CP_CloseAll", ControlPanelX + 10, ControlPanelY + 10, 830, 30, "Close All Positions", clrRed, clrWhite); + + //--- OI Data Panel (New Section) + CreatePanel("CP_OIDataPanel", ControlPanelX, ControlPanelY + topPanelHeight + 10, panelWidth, oiPanelHeight, C'45,45,45', BORDER_FLAT); + + //--- OI Data Labels Row 1 - ปรับจัดเรียงให้เป็น Grid Layout + 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); + + //--- OI Data Fields Row 1 - จัดเรียงแบบ Grid + 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); + + //--- OI Data Fields Row 2 - จัดเรียงให้เสมอกันกับ Call Strikes + 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); + + //--- Update OI Button + CreateButton("CP_UpdateOI", ControlPanelX + 750, ControlPanelY + topPanelHeight + 25, 90, 35, "Update OI Data", clrCyan, clrBlack); + + //--- Trading Panel (Main Trading Controls) + CreatePanel("CP_TradingPanel", ControlPanelX, ControlPanelY + topPanelHeight + oiPanelHeight + 20, panelWidth, tradingPanelHeight, C'45,45,45', BORDER_FLAT); + + //--- Trading Input Fields Labels + CreateLabel("CP_PendingPriceLabel", ControlPanelX + 10, ControlPanelY + topPanelHeight + oiPanelHeight + 35, "Price:", clrYellow, 8); + CreateLabel("CP_LotSizeLabel", ControlPanelX + 10, ControlPanelY + topPanelHeight + oiPanelHeight + 60, "Lot Size:", clrYellow, 8); + CreateLabel("CP_SLPointsLabel", ControlPanelX + 10, ControlPanelY + topPanelHeight + oiPanelHeight + 85, "SL (Point):", clrYellow, 8); + CreateLabel("CP_TPPointsLabel", ControlPanelX + 10, ControlPanelY + topPanelHeight + oiPanelHeight + 110, "TP (Point):", clrYellow, 8); + + //--- Trading Input Fields + CreateEditField("CP_PendingPrice", ControlPanelX + 80, ControlPanelY + topPanelHeight + oiPanelHeight + 30, 80, 20, "0", clrWhite, clrBlack); + CreateEditField("CP_LotSize", ControlPanelX + 80, ControlPanelY + topPanelHeight + oiPanelHeight + 55, 80, 20, "0.5", clrWhite, clrBlack); + CreateEditField("CP_SLPoints", ControlPanelX + 80, ControlPanelY + topPanelHeight + oiPanelHeight + 80, 80, 20, "0", clrWhite, clrBlack); + CreateEditField("CP_TPPoints", ControlPanelX + 80, ControlPanelY + topPanelHeight + oiPanelHeight + 105, 80, 20, "300", clrWhite, clrBlack); + + //--- Immediate Action Buttons Row 1 + CreateButton("CP_Sell", ControlPanelX + 180, ControlPanelY + topPanelHeight + oiPanelHeight + 30, 100, 25, "Sell Now", clrRed, clrWhite); + CreateButton("CP_Buy", ControlPanelX + 290, ControlPanelY + topPanelHeight + oiPanelHeight + 30, 100, 25, "Buy Now", clrGreen, clrWhite); + + //--- Pending Action Buttons Row 2 + CreateButton("CP_PendingSell", ControlPanelX + 180, ControlPanelY + topPanelHeight + oiPanelHeight + 65, 100, 25, "Pending Sell", clrOrange, clrBlack); + CreateButton("CP_PendingBuy", ControlPanelX + 290, ControlPanelY + topPanelHeight + oiPanelHeight + 65, 100, 25, "Pending Buy", clrDarkGreen, clrWhite); + + //--- Management Buttons Row 3 + CreateButton("CP_CloseAllPending", ControlPanelX + 180, ControlPanelY + topPanelHeight + oiPanelHeight + 100, 210, 25, "Close All Pending Orders", clrPurple, clrWhite); + + //--- Management Buttons Row 4 + CreateButton("CP_CloseSell", ControlPanelX + 180, ControlPanelY + topPanelHeight + oiPanelHeight + 135, 100, 25, "Close All Sell", clrPurple, clrWhite); + CreateButton("CP_CloseBuy", ControlPanelX + 290, ControlPanelY + topPanelHeight + oiPanelHeight + 135, 100, 25, "Close All Buy", clrPurple, clrWhite); +} + +//+------------------------------------------------------------------+ +//| Create Panel | +//+------------------------------------------------------------------+ +void CreatePanel(string name, int x, int y, int width, int height, color bgColor, ENUM_BORDER_TYPE border) +{ + if(ObjectFind(chart_id, name) < 0) + { + ObjectCreate(chart_id, name, OBJ_RECTANGLE_LABEL, 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, border); + ObjectSetInteger(chart_id, name, OBJPROP_BORDER_COLOR, clrGray); + ObjectSetInteger(chart_id, name, OBJPROP_BACK, false); // ปรับจาก true เป็น false เพื่อให้อยู่หน้ากราฟ + ObjectSetInteger(chart_id, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chart_id, name, OBJPROP_HIDDEN, true); + // ตั้งค่า Z-Order ให้สูงเพื่อให้อยู่เหนือเส้นกราฟ + ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1000); + } +} + +//+------------------------------------------------------------------+ +//| Create Button | +//+------------------------------------------------------------------+ +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); + 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, 8); + ObjectSetString(chart_id, name, OBJPROP_FONT, "Arial"); + ObjectSetInteger(chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chart_id, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); + ObjectSetInteger(chart_id, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chart_id, name, OBJPROP_HIDDEN, true); + ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1001); + } +} + +//+------------------------------------------------------------------+ +//| Create Edit Field | +//+------------------------------------------------------------------+ +void CreateEditField(string name, int x, int y, int width, int height, string defaultText, color textColor, color bgColor) +{ + if(ObjectFind(chart_id, name) < 0) + { + ObjectCreate(chart_id, name, OBJ_EDIT, 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, 8); + ObjectSetString(chart_id, name, OBJPROP_FONT, "Arial"); + ObjectSetInteger(chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chart_id, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); + ObjectSetInteger(chart_id, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chart_id, name, OBJPROP_HIDDEN, true); + ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1001); + } +} + +//+------------------------------------------------------------------+ +//| Create Label | +//+------------------------------------------------------------------+ +void CreateLabel(string name, int x, int y, string text, color clr, int fontSize = 8) +{ + if(ObjectFind(chart_id, name) < 0) + { + ObjectCreate(chart_id, name, OBJ_LABEL, 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, clr); + ObjectSetInteger(chart_id, name, OBJPROP_FONTSIZE, fontSize); + ObjectSetString(chart_id, name, OBJPROP_FONT, "Arial"); + ObjectSetInteger(chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chart_id, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); + ObjectSetInteger(chart_id, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chart_id, name, OBJPROP_HIDDEN, true); + ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1002); + } +} + +//+------------------------------------------------------------------+ +//| Handle Control Panel Click | +//+------------------------------------------------------------------+ +void HandleControlPanelClick(string objectName) +{ + //--- Get current values from input fields + UpdateInputValues(); + + if(objectName == "CP_CloseAll") + { + CloseAllPositions(); + } + else if(objectName == "CP_UpdateOI") // New OI Data Update Handler + { + UpdateOIData(); + } + else if(objectName == "CP_Sell") + { + ExecuteManualSell(); + } + else if(objectName == "CP_Buy") + { + ExecuteManualBuy(); + } + else if(objectName == "CP_PendingSell") + { + ExecutePendingSell(); + } + else if(objectName == "CP_PendingBuy") + { + ExecutePendingBuy(); + } + else if(objectName == "CP_CloseAllPending") + { + CancelAllPendingOrders(); + } + else if(objectName == "CP_CloseSell") + { + CloseAllSellPositions(); + } + else if(objectName == "CP_CloseBuy") + { + CloseAllBuyPositions(); + } +} + +//+------------------------------------------------------------------+ +//| Update Input Values from Control Panel | +//+------------------------------------------------------------------+ +void UpdateInputValues() +{ + // Update trading input values + PendingPriceValue = StringToDouble(ObjectGetString(chart_id, "CP_PendingPrice", OBJPROP_TEXT)); + ManualLotSize = StringToDouble(ObjectGetString(chart_id, "CP_LotSize", OBJPROP_TEXT)); + ManualSLPoints = (int)StringToInteger(ObjectGetString(chart_id, "CP_SLPoints", OBJPROP_TEXT)); + ManualTPPoints = (int)StringToInteger(ObjectGetString(chart_id, "CP_TPPoints", OBJPROP_TEXT)); + + // Update OI data input values + ManualFuturePriceValue = StringToDouble(ObjectGetString(chart_id, "CP_FuturePrice", OBJPROP_TEXT)); + CallStrike1Value = StringToDouble(ObjectGetString(chart_id, "CP_CallStrike1", OBJPROP_TEXT)); + CallStrike2Value = StringToDouble(ObjectGetString(chart_id, "CP_CallStrike2", OBJPROP_TEXT)); + CallStrike3Value = StringToDouble(ObjectGetString(chart_id, "CP_CallStrike3", OBJPROP_TEXT)); + PutStrike1Value = StringToDouble(ObjectGetString(chart_id, "CP_PutStrike1", OBJPROP_TEXT)); + PutStrike2Value = StringToDouble(ObjectGetString(chart_id, "CP_PutStrike2", OBJPROP_TEXT)); + PutStrike3Value = StringToDouble(ObjectGetString(chart_id, "CP_PutStrike3", OBJPROP_TEXT)); + + Print("Pending Price: ", PendingPriceValue); + Print("Lot Size: ", ManualLotSize); + Print("SL Points: ", ManualSLPoints); + Print("TP Points: ", ManualTPPoints); + Print("Manual Future Price: ", ManualFuturePriceValue); + Print("Call Strikes: ", CallStrike1Value, ", ", CallStrike2Value, ", ", CallStrike3Value); + Print("Put Strikes: ", PutStrike1Value, ", ", PutStrike2Value, ", ", PutStrike3Value); +} + +//+------------------------------------------------------------------+ +//| Update Control Panel Values | +//+------------------------------------------------------------------+ +void UpdateControlPanelValues() +{ + // Update price display + double currentBid = SymbolInfo.Bid(); + ObjectSetString(chart_id, "CP_PendingPrice", OBJPROP_TEXT, DoubleToString(currentBid, _Digits)); + + // Initialize OI data fields with current input values (dynamic first, then input) + ObjectSetString(chart_id, "CP_FuturePrice", OBJPROP_TEXT, + DoubleToString((DynamicFuturePrice > 0) ? DynamicFuturePrice : InpManualFuturePrice, _Digits)); + ObjectSetString(chart_id, "CP_CallStrike1", OBJPROP_TEXT, + DoubleToString((DynamicCallStrike1 > 0) ? DynamicCallStrike1 : InpCallStrike1, _Digits)); + ObjectSetString(chart_id, "CP_CallStrike2", OBJPROP_TEXT, + DoubleToString((DynamicCallStrike2 > 0) ? DynamicCallStrike2 : InpCallStrike2, _Digits)); + ObjectSetString(chart_id, "CP_CallStrike3", OBJPROP_TEXT, + DoubleToString((DynamicCallStrike3 > 0) ? DynamicCallStrike3 : InpCallStrike3, _Digits)); + ObjectSetString(chart_id, "CP_PutStrike1", OBJPROP_TEXT, + DoubleToString((DynamicPutStrike1 > 0) ? DynamicPutStrike1 : InpPutStrike1, _Digits)); + ObjectSetString(chart_id, "CP_PutStrike2", OBJPROP_TEXT, + DoubleToString((DynamicPutStrike2 > 0) ? DynamicPutStrike2 : InpPutStrike2, _Digits)); + ObjectSetString(chart_id, "CP_PutStrike3", OBJPROP_TEXT, + DoubleToString((DynamicPutStrike3 > 0) ? DynamicPutStrike3 : InpPutStrike3, _Digits)); +} + +//+------------------------------------------------------------------+ +//| Update Control Panel | +//+------------------------------------------------------------------+ +void UpdateControlPanel() +{ + // Update pending price with current market price periodically + static datetime lastUpdate = 0; + if(TimeCurrent() - lastUpdate > 5) // Update every 5 seconds + { + UpdateControlPanelValues(); + lastUpdate = TimeCurrent(); + } +} + +//+------------------------------------------------------------------+ +//| Execute Manual SELL | +//+------------------------------------------------------------------+ +void ExecuteManualSell() +{ + double lotSize = NormalizeLot(ManualLotSize); + if(lotSize <= 0) return; + + double sl = (ManualSLPoints > 0) ? SymbolInfo.Bid() + ManualSLPoints * _Point : 0.0; + double tp = (ManualTPPoints > 0) ? SymbolInfo.Bid() - ManualTPPoints * _Point : 0.0; + + if(Trade.Sell(lotSize, _Symbol, SymbolInfo.Bid(), sl, tp, "Manual SELL")) + { + Print("Manual SELL executed. Lot: ", lotSize, " Price: ", SymbolInfo.Bid()); + DailyTradeCount++; + } + else + { + Print("Manual SELL failed. Error: ", Trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Execute Manual BUY | +//+------------------------------------------------------------------+ +void ExecuteManualBuy() +{ + double lotSize = NormalizeLot(ManualLotSize); + if(lotSize <= 0) return; + + double sl = (ManualSLPoints > 0) ? SymbolInfo.Ask() - ManualSLPoints * _Point : 0.0; + double tp = (ManualTPPoints > 0) ? SymbolInfo.Ask() + ManualTPPoints * _Point : 0.0; + + if(Trade.Buy(lotSize, _Symbol, SymbolInfo.Ask(), sl, tp, "Manual BUY")) + { + Print("Manual BUY executed. Lot: ", lotSize, " Price: ", SymbolInfo.Ask()); + DailyTradeCount++; + } + else + { + Print("Manual BUY failed. Error: ", Trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Execute Pending SELL | +//+------------------------------------------------------------------+ +void ExecutePendingSell() +{ + // 1. อัพเดทค่าจาก Control Panel + UpdateInputValues(); + + // 2. Refresh rates ก่อนใช้งาน + SymbolInfo.RefreshRates(); + + // 3. ตรวจสอบค่าที่ได้ + if(PendingPriceValue <= 0) + { + Print("Invalid pending price for SELL: ", PendingPriceValue); + Alert("กรุณากรอกราคาที่ต้องการสำหรับ Pending SELL"); + return; + } + + double lotSize = NormalizeLot(ManualLotSize); + if(lotSize <= 0) + { + Print("Invalid lot size for Pending SELL: ", ManualLotSize); + Alert("กรุณากรอกขนาด Lot ที่ถูกต้อง"); + return; + } + + // 4. คำนวณ SL และ TP สำหรับ SELL orders + // SL ต้องต่ำกว่าราคา entry สำหรับ SELL orders + double sl = (ManualSLPoints > 0) ? PendingPriceValue - ManualSLPoints * _Point : 0.0; + double tp = (ManualTPPoints > 0) ? PendingPriceValue - ManualTPPoints * _Point : 0.0; + + Print("SELL Order - Entry: ", PendingPriceValue, " | SL: ", sl, " | TP: ", tp); + + // 5. กำหนดประเภท Order - SELL logic ต้องถูกต้อง + ENUM_ORDER_TYPE orderType; + double currentBid = SymbolInfo.Bid(); // ใช้ Bid สำหรับ SELL orders + + Print("Current Bid: ", currentBid, " | Pending Price: ", PendingPriceValue); + + // SELL_LIMIT: ขายที่ราคาสูงกว่าตลาดปัจจุบัน (รอราคาขึ้นมาแล้วขาย) + // SELL_STOP: ขายเมื่อราคาลดลงถึงระดับที่กำหนด (Stop loss) + if(PendingPriceValue > currentBid) + { + orderType = ORDER_TYPE_SELL_LIMIT; // Sell limit if price is above current Bid + Print("Using SELL_LIMIT order type (sell at higher price)"); + } + else + { + orderType = ORDER_TYPE_SELL_STOP; // Sell stop if price is below current Bid + Print("Using SELL_STOP order type (sell when price drops)"); + } + + // 6. ใช้ OrderSend() ตรงๆ แทน Trade.OrderSend() + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.action = TRADE_ACTION_PENDING; + request.symbol = _Symbol; + request.type = orderType; + request.volume = lotSize; + request.price = NormalizeDouble(PendingPriceValue, _Digits); + request.sl = (sl > 0) ? NormalizeDouble(sl, _Digits) : 0; + request.tp = (tp > 0) ? NormalizeDouble(tp, _Digits) : 0; + request.comment = "Pending SELL"; + request.type_filling = ORDER_FILLING_IOC; + request.magic = InpMagicNumber; + + // 7. ใช้ Trade.OrderSend() สำหรับการจัดการที่ดีกว่า + if(Trade.OrderSend(request, result)) + { + Print("Pending SELL order placed successfully!"); + Print("Ticket: ", result.order, " | Price: ", result.price); + Alert("Pending SELL order placed successfully!\nTicket: " + IntegerToString(result.order) + "\nPrice: " + DoubleToString(result.price, _Digits)); + } + else + { + Print("Pending SELL failed! Error: ", Trade.ResultRetcodeDescription()); + Alert("Pending SELL failed! Error: " + Trade.ResultRetcodeDescription()); + } +} +//+------------------------------------------------------------------+ +//| Execute Pending BUY | +//+------------------------------------------------------------------+ +void ExecutePendingBuy() +{ + // 1. อัพเดทค่าจาก Control Panel + UpdateInputValues(); + + // 2. Refresh rates ก่อนใช้งาน + SymbolInfo.RefreshRates(); + + // 3. ตรวจสอบค่าที่ได้ + if(PendingPriceValue <= 0) + { + Print("Invalid pending price for BUY: ", PendingPriceValue); + Alert("กรุณากรอกราคาที่ต้องการสำหรับ Pending BUY"); + return; + } + + double lotSize = NormalizeLot(ManualLotSize); + if(lotSize <= 0) + { + Print("Invalid lot size for Pending BUY: ", ManualLotSize); + Alert("กรุณากรอกขนาด Lot ที่ถูกต้อง"); + return; + } + + // 4. คำนวณ SL และ TP สำหรับ BUY orders + // SL ต้องต่ำกว่าราคา entry สำหรับ BUY orders + double sl = (ManualSLPoints > 0) ? PendingPriceValue - ManualSLPoints * _Point : 0.0; + double tp = (ManualTPPoints > 0) ? PendingPriceValue + ManualTPPoints * _Point : 0.0; + + Print("BUY Order - Entry: ", PendingPriceValue, " | SL: ", sl, " | TP: ", tp); + + // 5. กำหนดประเภท Order - BUY logic + ENUM_ORDER_TYPE orderType; + double currentAsk = SymbolInfo.Ask(); // ใช้ Ask สำหรับ BUY orders + + Print("Current Ask: ", currentAsk, " | Pending Price: ", PendingPriceValue); + + // BUY_LIMIT: ซื้อที่ราคาต่ำกว่าตลาดปัจจุบัน (รอราคาลงมาแล้วซื้อ) + // BUY_STOP: ซื้อเมื่อราคาขึ้นไปถึงระดับที่กำหนด (Breakout) + if(PendingPriceValue < currentAsk) + { + orderType = ORDER_TYPE_BUY_LIMIT; // Buy limit if price is below current Ask + Print("Using BUY_LIMIT order type (buy at lower price)"); + } + else + { + orderType = ORDER_TYPE_BUY_STOP; // Buy stop if price is above current Ask + Print("Using BUY_STOP order type (buy when price breaks higher)"); + } + + // 6. เตรียม Trade Request + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.action = TRADE_ACTION_PENDING; + request.symbol = _Symbol; + request.type = orderType; + request.volume = lotSize; + request.price = NormalizeDouble(PendingPriceValue, _Digits); + request.sl = (sl > 0) ? NormalizeDouble(sl, _Digits) : 0; + request.tp = (tp > 0) ? NormalizeDouble(tp, _Digits) : 0; + request.comment = "Pending BUY"; + request.type_filling = ORDER_FILLING_IOC; + request.magic = InpMagicNumber; + + // 7. ส่ง Order + if(Trade.OrderSend(request, result)) + { + Print("Pending BUY order placed successfully!"); + Print("Ticket: ", result.order, " | Price: ", result.price); + Alert("Pending BUY order placed successfully!\nTicket: " + IntegerToString(result.order) + "\nPrice: " + DoubleToString(result.price, _Digits)); + } + else + { + Print("Pending BUY failed! Error: ", Trade.ResultRetcodeDescription()); + Alert("Pending BUY failed! Error: " + Trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Cancel All Pending Orders | +//+------------------------------------------------------------------+ +void CancelAllPendingOrders() +{ + int totalOrders = OrdersTotal(); + int canceledCount = 0; + + for(int i = totalOrders - 1; i >= 0; i--) + { + if(OrderInfo.SelectByIndex(i)) + { + if(OrderInfo.Symbol() == _Symbol && OrderInfo.Magic() == InpMagicNumber) + { + ENUM_ORDER_TYPE orderType = OrderInfo.OrderType(); + + // Check if it's a pending order + if(orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_BUY_STOP || + orderType == ORDER_TYPE_SELL_LIMIT || orderType == ORDER_TYPE_SELL_STOP) + { + if(Trade.OrderDelete(OrderInfo.Ticket())) + { + Print("Pending order deleted: ", OrderInfo.Ticket()); + canceledCount++; + } + } + } + } + } + + if(canceledCount > 0) + { + Print("Total pending orders canceled: ", canceledCount); + } + else + { + Print("No pending orders found to cancel"); + } +} + +//+------------------------------------------------------------------+ +//| Close All Sell Positions | +//+------------------------------------------------------------------+ +void CloseAllSellPositions() +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionInfo.SelectByIndex(i)) + { + if(PositionInfo.Symbol() == _Symbol && + PositionInfo.Magic() == InpMagicNumber && + PositionInfo.PositionType() == POSITION_TYPE_SELL) + { + Trade.PositionClose(PositionInfo.Ticket()); + Print("SELL position closed: ", PositionInfo.Ticket()); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Close All Buy Positions | +//+------------------------------------------------------------------+ +void CloseAllBuyPositions() +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionInfo.SelectByIndex(i)) + { + if(PositionInfo.Symbol() == _Symbol && + PositionInfo.Magic() == InpMagicNumber && + PositionInfo.PositionType() == POSITION_TYPE_BUY) + { + Trade.PositionClose(PositionInfo.Ticket()); + Print("BUY position closed: ", PositionInfo.Ticket()); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Normalize Lot Size | +//+------------------------------------------------------------------+ +double NormalizeLot(double lot) +{ + double minLot = SymbolInfo.LotsMin(); + double maxLot = SymbolInfo.LotsMax(); + double lotStep = SymbolInfo.LotsStep(); + + lot = MathMax(lot, minLot); + lot = MathMin(lot, maxLot); + + if(lotStep > 0) + lot = MathRound(lot / lotStep) * lotStep; + + return NormalizeDouble(lot, 2); +} + +//+------------------------------------------------------------------+ +//| Update OI Data from Control Panel | +//+------------------------------------------------------------------+ +void UpdateOIData() +{ + // Get current values from control panel + UpdateInputValues(); + + // แสดงข้อมูลที่ได้รับจาก Control Panel + Print("=== OI Data Update Process ==="); + Print("Manual Future Price Value: ", ManualFuturePriceValue); + Print("Call Strike Values: ", CallStrike1Value, ", ", CallStrike2Value, ", ", CallStrike3Value); + Print("Put Strike Values: ", PutStrike1Value, ", ", PutStrike2Value, ", ", PutStrike3Value); + + // Validate and update dynamic variables + if(ManualFuturePriceValue > 0) + { + DynamicFuturePrice = ManualFuturePriceValue; + Print("Updated Dynamic Future Price: ", DynamicFuturePrice); + } + else + { + Print("Warning: Manual Future Price is 0 or invalid, using previous value: ", DynamicFuturePrice); + } + + // Update OI Strike levels (dynamic variables) + if(CallStrike1Value >= 0) DynamicCallStrike1 = CallStrike1Value; + if(CallStrike2Value >= 0) DynamicCallStrike2 = CallStrike2Value; + if(CallStrike3Value >= 0) DynamicCallStrike3 = CallStrike3Value; + if(PutStrike1Value >= 0) DynamicPutStrike1 = PutStrike1Value; + if(PutStrike2Value >= 0) DynamicPutStrike2 = PutStrike2Value; + if(PutStrike3Value >= 0) DynamicPutStrike3 = PutStrike3Value; + + Print("Dynamic Call Strikes: ", DynamicCallStrike1, ", ", DynamicCallStrike2, ", ", DynamicCallStrike3); + Print("Dynamic Put Strikes: ", DynamicPutStrike1, ", ", DynamicPutStrike2, ", ", DynamicPutStrike3); + + // Reinitialize OI levels with new values + InitializeOILevels(); + InitializeKeyLevels(); + + Print("OI Data Updated Successfully!"); + + // Show confirmation + string message = "OI Data Updated Successfully!\n" + + "Future Price: " + DoubleToString(DynamicFuturePrice, _Digits) + "\n" + + "Call Strikes: " + DoubleToString(DynamicCallStrike1, _Digits) + ", " + + DoubleToString(DynamicCallStrike2, _Digits) + ", " + DoubleToString(DynamicCallStrike3, _Digits) + "\n" + + "Put Strikes: " + DoubleToString(DynamicPutStrike1, _Digits) + ", " + + DoubleToString(DynamicPutStrike2, _Digits) + ", " + DoubleToString(DynamicPutStrike3, _Digits); + + Alert(message); +} + +//+------------------------------------------------------------------+ +//| Initialize OI Levels | +//+------------------------------------------------------------------+ +void InitializeOILevels() +{ + //--- Load OI data based on source + switch(InpOISource) + { + case OI_SOURCE_MANUAL: + // Use dynamic variables first, fall back to input parameters + CallLevels[0] = (DynamicCallStrike1 > 0) ? DynamicCallStrike1 : InpCallStrike1; + CallLevels[1] = (DynamicCallStrike2 > 0) ? DynamicCallStrike2 : InpCallStrike2; + CallLevels[2] = (DynamicCallStrike3 > 0) ? DynamicCallStrike3 : InpCallStrike3; + + PutLevels[0] = (DynamicPutStrike1 > 0) ? DynamicPutStrike1 : InpPutStrike1; + PutLevels[1] = (DynamicPutStrike2 > 0) ? DynamicPutStrike2 : InpPutStrike2; + PutLevels[2] = (DynamicPutStrike3 > 0) ? DynamicPutStrike3 : InpPutStrike3; + + // Default OI values + CallOI[0] = 1000; + CallOI[1] = 800; + CallOI[2] = 600; + + PutOI[0] = 1000; + PutOI[1] = 800; + PutOI[2] = 600; + break; + + case OI_SOURCE_CSV_FILE: + LoadOIFromCSV(); + break; + + case OI_SOURCE_AUTO_SYNC: + // Future implementation for auto-sync + break; + } + + //--- Sort levels in ascending order + ArraySort(CallLevels); + ArraySort(PutLevels); + + Print("OI Levels Initialized:"); + Print("Call Levels: ", CallLevels[0], ", ", CallLevels[1], ", ", CallLevels[2]); + Print("Put Levels: ", PutLevels[0], ", ", PutLevels[1], ", ", PutLevels[2]); +} + +//+------------------------------------------------------------------+ +//| Load OI data from CSV file | +//+------------------------------------------------------------------+ +void LoadOIFromCSV() +{ + Print("=== CSV Loading Debug ==="); + Print("Terminal Files dir: ", TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\"); + Print("Attempting to open: ", InpOICsvPath); + + if(!FileIsExist(InpOICsvPath)) { + Print("CSV FILE NOT FOUND - check path or if scraper output exists"); + return; + } + + string filename = InpOICsvPath; + int filehandle = FileOpen(filename, FILE_READ | FILE_ANSI); + + if(filehandle == INVALID_HANDLE) + { + int err = GetLastError(); + Print("CSV OPEN FAILED [Error ", err, "]: ", err); + Print("File exists: ", FileIsExist(InpOICsvPath) ? "YES" : "NO"); + InitializeOILevels(); + return; + } + + long fileSize = FileSize(filehandle); + Print("CSV opened successfully. Size: ", fileSize, " bytes"); + + int callIndex = 0; + int putIndex = 0; + double futurePrice = 0.0; + bool isFirstLine = true; + + while(!FileIsEnding(filehandle)) + { + string row = FileReadString(filehandle); + + Print("DEBUG ROW: [", row, "]"); + + 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; + if(!CSVLoadLogged) { + Print("DEBUG: Parsed Future price: ", futurePrice); + } + } + else if(type == "CALL" && callIndex < 3) + { + CallLevels[callIndex] = strike; + CallOI[callIndex] = oi; + callIndex++; + if(!CSVLoadLogged) { + Print("DEBUG: Found CALL row, strike=", strike, " oi=", oi); + } + } + else if(type == "PUT" && putIndex < 3) + { + PutLevels[putIndex] = strike; + PutOI[putIndex] = oi; + putIndex++; + if(!CSVLoadLogged) { + Print("DEBUG: Found PUT row, strike=", strike, " oi=", oi); + } + } + } + } + + 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; + LoadedCSVPath = filename; + CSVLoadLogged = true; + Print("CSV SUCCESS: FuturePrice=", futurePrice, ", CALL=[", CallLevels[0], ",", CallLevels[1], ",", CallLevels[2], "], PUT=[", PutLevels[0], ",", PutLevels[1], ",", PutLevels[2], "] loaded from ", filename); + } else { + if(!CSVLoadLogged) { + Print("CSV ERROR: No valid price found in ", filename); + CSVLoadLogged = true; + } + CachedFuturePrice = 0; + } +} + +//+------------------------------------------------------------------+ +//| Initialize Key Levels | +//+------------------------------------------------------------------+ +void InitializeKeyLevels() +{ + // Calculate key levels based on OI levels + if(PutLevels[2] > 0 && CallLevels[2] > 0) + { + LevelLower2 = PutLevels[2]; // Lowest put strike + LevelLower1 = PutLevels[1]; // Middle put strike + LevelMid = (PutLevels[0] + CallLevels[0]) / 2; // Mid point + LevelUpper1 = CallLevels[1]; // Middle call strike + LevelUpper2 = CallLevels[2]; // Highest call strike + } + else + { + // Use current price as reference + double currentPrice = SymbolInfo.Bid(); + LevelLower2 = currentPrice - 500 * _Point; + LevelLower1 = currentPrice - 250 * _Point; + LevelMid = currentPrice; + LevelUpper1 = currentPrice + 250 * _Point; + LevelUpper2 = currentPrice + 500 * _Point; + } +} + +//+------------------------------------------------------------------+ +//| Initialize Indicators | +//+------------------------------------------------------------------+ +bool InitializeIndicators() +{ + // ATR for volatility + ATRHandle = iATR(_Symbol, PERIOD_H1, 14); + if(ATRHandle == INVALID_HANDLE) + { + Print("Error creating ATR indicator"); + return false; + } + + // Moving Averages for trend + 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("Error creating MA indicators"); + return false; + } + + // RSI for momentum (optional) + RSIMainHandle = iRSI(_Symbol, PERIOD_H1, 14, PRICE_CLOSE); + + return true; +} + +//+------------------------------------------------------------------+ +//| Update Market Data | +//+------------------------------------------------------------------+ +bool UpdateMarketData() +{ + // Get current prices + SpotPrice = SymbolInfo.Bid(); + SymbolInfo.RefreshRates(); + + // Get future price (CSV cache, manual or from symbol) + if(CachedFuturePrice > 0) + { + FuturePrice = CachedFuturePrice; + } + else if(DynamicFuturePrice > 0) + { + FuturePrice = DynamicFuturePrice; + } + else if(InpManualFuturePrice > 0) + { + FuturePrice = InpManualFuturePrice; + } + else + { + // In real implementation, you might get this from a different symbol + // For now, use spot price with a small offset + FuturePrice = SpotPrice; + } + + // Calculate Delta + double previousDelta = DeltaPrice; + DeltaPrice = FuturePrice - SpotPrice; + + // Update Delta EMA + UpdateDeltaEMA(); + + // Calculate Deviation + DeltaDeviation = DeltaPrice - DeltaEMA; + + // Check for zero cross + if(InpCloseOnZeroDeviation && previousDelta * DeltaPrice < 0) + { + CloseAllPositions(); + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Update Delta EMA | +//+------------------------------------------------------------------+ +void UpdateDeltaEMA() +{ + static double ema = 0; + static bool firstRun = true; + + if(firstRun) + { + ema = DeltaPrice; + firstRun = false; + } + else + { + double alpha = 2.0 / (InpDeltaEmaPeriod + 1.0); + ema = alpha * DeltaPrice + (1 - alpha) * ema; + } + + DeltaEMA = ema; +} + +//+------------------------------------------------------------------+ +//| Check Global Trading Conditions | +//+------------------------------------------------------------------+ +bool CheckGlobalConditions() +{ + //--- Check if trading is enabled + if(!TradingEnabled) + { + return false; + } + + //--- Check terminal connection + if(!TerminalInfoInteger(TERMINAL_CONNECTED)) + { + Print("Terminal not connected"); + return false; + } + + //--- Check trading permission + if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)) + { + Print("Trading not allowed"); + return false; + } + + //--- Check spread limit + long spread = SymbolInfo.Spread(); + if(spread > InpMaxSpread) + { + return false; + } + + //--- Check session time + if(InpUseSessionFilter && !IsTradingSession()) + { + return false; + } + + //--- Check news block + if(NewsBlockActive) + { + return false; + } + + //--- Check volatility filter + if(InpUseATRFilter && !CheckVolatilityFilter()) + { + return false; + } + + //--- Check daily limits + if(!CheckDailyLimits()) + { + TradingEnabled = false; + Print("Daily limit reached. Trading disabled."); + return false; + } + + //--- Check equity protection + if(CheckEquityProtection()) + { + TradingEnabled = false; + Print("Equity protection triggered. Trading disabled."); + return false; + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check Trading Session | +//+------------------------------------------------------------------+ +bool IsTradingSession() +{ + MqlDateTime timeNow; + TimeCurrent(timeNow); + + int hour = timeNow.hour; + + // Check if current hour is within trading session + if(hour >= InpSessionStartHour && hour < InpSessionEndHour) + { + return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Check Volatility Filter | +//+------------------------------------------------------------------+ +bool CheckVolatilityFilter() +{ + double atrValues[]; + ArraySetAsSeries(atrValues, true); + if(CopyBuffer(ATRHandle, 0, 0, 1, atrValues) < 1) + return false; + + double atr = atrValues[0]; + double atrPercent = (atr / SpotPrice) * 100; + + return (atrPercent <= InpMaxATRPercent); +} + +//+------------------------------------------------------------------+ +//| Check Daily Limits | +//+------------------------------------------------------------------+ +bool CheckDailyLimits() +{ + double balance = AccountInfo.Balance(); + double equity = AccountInfo.Equity(); + + // Check daily loss limit + if(DailyLoss > 0) + { + double lossPercent = (DailyLoss / balance) * 100; + if(lossPercent >= InpMaxDailyLossPercent) + { + Print("Daily loss limit reached: ", lossPercent, "%"); + return false; + } + } + + // Check daily profit limit + if(DailyProfit > 0) + { + double profitPercent = (DailyProfit / balance) * 100; + if(profitPercent >= InpMaxDailyProfitPercent) + { + Print("Daily profit limit reached: ", profitPercent, "%"); + return false; + } + } + + // Check consecutive losses + if(ConsecutiveLosses >= InpMaxConsecutiveLosses && InpDisableAfterMaxLoss) + { + Print("Max consecutive losses reached: ", ConsecutiveLosses); + return false; + } + + // Check daily trade count + if(DailyTradeCount >= InpMaxDailyTrades) + { + Print("Max daily trades reached: ", DailyTradeCount); + return false; + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check Equity Protection | +//+------------------------------------------------------------------+ +bool CheckEquityProtection() +{ + double balance = AccountInfo.Balance(); + double equity = AccountInfo.Equity(); + + if(balance <= 0) return false; + + double drawdownPercent = ((balance - equity) / balance) * 100; + + if(drawdownPercent >= InpEquityProtectionPercent) + { + Print("Equity protection triggered: ", drawdownPercent, "% drawdown"); + CloseAllPositions(); + return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Check News Events | +//+------------------------------------------------------------------+ +void CheckNewsEvents() +{ + // Simplified news check - in real implementation, you would use a news API + // or economic calendar data + datetime timeNow = TimeCurrent(); + + // For now, this is a placeholder function + // In production, you would implement actual news checking logic here +} + +//+------------------------------------------------------------------+ +//| Check Trading Signals | +//+------------------------------------------------------------------+ +void CheckTradingSignals() +{ + // Don't open new trades if already have position + if(PositionsTotal() > 0) + { + // Check if we should add to position or close + return; + } + + // Get market phase + ENUM_MARKET_PHASE marketPhase = GetMarketPhase(); + + // Check SELL conditions + if(CheckSellConditions(marketPhase)) + { + ExecuteSellTrade(); + } + // Check BUY conditions + else if(CheckBuyConditions(marketPhase)) + { + ExecuteBuyTrade(); + } +} + +//+------------------------------------------------------------------+ +//| Get Market Phase | +//+------------------------------------------------------------------+ +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]; + + // Calculate slope + 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; +} + +//+------------------------------------------------------------------+ +//| Check SELL Conditions | +//+------------------------------------------------------------------+ +bool CheckSellConditions(ENUM_MARKET_PHASE marketPhase) +{ + // 1. Check Delta Deviation + if(DeltaDeviation <= InpDeviationThreshold * _Point) + return false; + + // 2. Check price near upper OI level + if(!IsPriceNearLevel(SpotPrice, CallLevels, true)) + return false; + + // 3. Check market phase filter + if(marketPhase == PHASE_BULLISH && InpUseMarketPhaseFilter) + { + // Need stronger signal in bullish phase + if(DeltaDeviation < InpDeviationThreshold * _Point * InpDeviationMultiplier) + return false; + } + + // 4. Check overbought condition (optional) + if(!CheckOverbought()) + return false; + + // 5. Check if price is at key resistance + if(!IsAtResistance()) + return false; + + return true; +} + +//+------------------------------------------------------------------+ +//| Check BUY Conditions | +//+------------------------------------------------------------------+ +bool CheckBuyConditions(ENUM_MARKET_PHASE marketPhase) +{ + // 1. Check Delta Deviation + if(DeltaDeviation >= -InpDeviationThreshold * _Point) + return false; + + // 2. Check price near lower OI level + if(!IsPriceNearLevel(SpotPrice, PutLevels, false)) + return false; + + // 3. Check market phase filter + if(marketPhase == PHASE_BEARISH && InpUseMarketPhaseFilter) + { + // Need stronger signal in bearish phase + if(DeltaDeviation > -InpDeviationThreshold * _Point * InpDeviationMultiplier) + return false; + } + + // 4. Check oversold condition (optional) + if(!CheckOversold()) + return false; + + // 5. Check if price is at key support + if(!IsAtSupport()) + return false; + + return true; +} + +//+------------------------------------------------------------------+ +//| Check if price is near OI level | +//+------------------------------------------------------------------+ +bool IsPriceNearLevel(double price, double &levels[], bool isUpper) +{ + double tolerance = 50 * _Point; // 50 points tolerance + + for(int i = 0; i < ArraySize(levels); i++) + { + if(levels[i] <= 0) continue; + + if(isUpper) + { + // For upper levels, price should be at or above the level + if(price >= levels[i] - tolerance && price <= levels[i] + tolerance) + return true; + } + else + { + // For lower levels, price should be at or below the level + if(price >= levels[i] - tolerance && price <= levels[i] + tolerance) + return true; + } + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Check overbought condition | +//+------------------------------------------------------------------+ +bool CheckOverbought() +{ + // Simple RSI check + if(RSIMainHandle != INVALID_HANDLE) + { + double rsiValues[]; + ArraySetAsSeries(rsiValues, true); + if(CopyBuffer(RSIMainHandle, 0, 0, 1, rsiValues) >= 1) + { + if(rsiValues[0] < 70) // Not overbought + return false; + } + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check oversold condition | +//+------------------------------------------------------------------+ +bool CheckOversold() +{ + // Simple RSI check + if(RSIMainHandle != INVALID_HANDLE) + { + double rsiValues[]; + ArraySetAsSeries(rsiValues, true); + if(CopyBuffer(RSIMainHandle, 0, 0, 1, rsiValues) >= 1) + { + if(rsiValues[0] > 30) // Not oversold + return false; + } + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check if price is at resistance | +//+------------------------------------------------------------------+ +bool IsAtResistance() +{ + // Check if price is near upper key levels + double tolerance = 100 * _Point; + + if(MathAbs(SpotPrice - LevelUpper1) <= tolerance || + MathAbs(SpotPrice - LevelUpper2) <= tolerance) + { + return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Check if price is at support | +//+------------------------------------------------------------------+ +bool IsAtSupport() +{ + // Check if price is near lower key levels + double tolerance = 100 * _Point; + + if(MathAbs(SpotPrice - LevelLower1) <= tolerance || + MathAbs(SpotPrice - LevelLower2) <= tolerance) + { + return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Execute SELL Trade | +//+------------------------------------------------------------------+ +void ExecuteSellTrade() +{ + double lotSize = CalculateLotSize(POSITION_TYPE_SELL); + if(lotSize <= 0) + return; + + double sl = CalculateStopLoss(POSITION_TYPE_SELL, SpotPrice); + double tp = CalculateTakeProfit(POSITION_TYPE_SELL, SpotPrice); + + // Send sell order + if(Trade.Sell(lotSize, _Symbol, SpotPrice, sl, tp, "OI Mean Reversion SELL")) + { + DailyTradeCount++; + LastTradeTime = TimeCurrent(); + Print("SELL order executed. Lot: ", lotSize, " Price: ", SpotPrice); + } + else + { + Print("SELL order failed. Error: ", Trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Execute BUY Trade | +//+------------------------------------------------------------------+ +void ExecuteBuyTrade() +{ + double lotSize = CalculateLotSize(POSITION_TYPE_BUY); + if(lotSize <= 0) + return; + + double sl = CalculateStopLoss(POSITION_TYPE_BUY, SpotPrice); + double tp = CalculateTakeProfit(POSITION_TYPE_BUY, SpotPrice); + + // Send buy order + if(Trade.Buy(lotSize, _Symbol, SpotPrice, sl, tp, "OI Mean Reversion BUY")) + { + DailyTradeCount++; + LastTradeTime = TimeCurrent(); + Print("BUY order executed. Lot: ", lotSize, " Price: ", SpotPrice); + } + else + { + Print("BUY order failed. Error: ", Trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Calculate Lot Size | +//+------------------------------------------------------------------+ +double CalculateLotSize(ENUM_POSITION_TYPE tradeType) +{ + if(!InpUseMoneyManagement) + return NormalizeLot(InpLotSize); + + double balance = AccountInfo.Balance(); + double riskAmount = balance * (InpRiskPercent / 100.0); + + double stopLossPrice = CalculateStopLoss(tradeType, SpotPrice); + double stopLossPoints = MathAbs(SpotPrice - stopLossPrice) / _Point; + + double tickValue = SymbolInfo.TickValue(); + double lotSize = riskAmount / (stopLossPoints * tickValue); + + return NormalizeLot(lotSize); +} + +//+------------------------------------------------------------------+ +//| Calculate Stop Loss | +//+------------------------------------------------------------------+ +double CalculateStopLoss(ENUM_POSITION_TYPE tradeType, double entryPrice) +{ + if(!InpUseStopLoss) + return 0.0; + + double slPoints = InpStopLossPoints * _Point; + + if(tradeType == POSITION_TYPE_BUY) + return entryPrice - slPoints; + else + return entryPrice + slPoints; +} + +//+------------------------------------------------------------------+ +//| Calculate Take Profit | +//+------------------------------------------------------------------+ +double CalculateTakeProfit(ENUM_POSITION_TYPE tradeType, double entryPrice) +{ + if(!InpUseTakeProfit) + return 0.0; + + double tpPoints = InpTakeProfitPoints * _Point; + + if(tradeType == POSITION_TYPE_BUY) + return entryPrice + tpPoints; + else + return entryPrice - tpPoints; +} + +//+------------------------------------------------------------------+ +//| Manage Positions | +//+------------------------------------------------------------------+ +void ManagePositions() +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionInfo.SelectByIndex(i)) + { + if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == InpMagicNumber) + { + // Check for trailing stop + if(InpUseTrailingStop) + { + ApplyTrailingStop(i); + } + + // Check for soft stop loss + if(InpUseSoftStopLoss) + { + CheckSoftStopLoss(i); + } + + // Check for take profit at mid level + CheckMidLevelExit(i); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Apply Trailing Stop | +//+------------------------------------------------------------------+ +void ApplyTrailingStop(int positionIndex) +{ + if(!PositionInfo.SelectByIndex(positionIndex)) + return; + + double currentPrice = PositionInfo.PositionType() == POSITION_TYPE_BUY ? + SymbolInfo.Bid() : SymbolInfo.Ask(); + + double openPrice = PositionInfo.PriceOpen(); + double currentSL = PositionInfo.StopLoss(); + double pointsProfit = MathAbs(currentPrice - openPrice) / _Point; + + if(pointsProfit > InpTrailingStartPoints) + { + double newSL = 0; + double trailPoints = InpTrailingStepPoints * _Point; + + if(PositionInfo.PositionType() == POSITION_TYPE_BUY) + { + newSL = currentPrice - trailPoints; + if(newSL > currentSL && newSL > openPrice) + { + Trade.PositionModify(_Symbol, newSL, PositionInfo.TakeProfit()); + } + } + else + { + newSL = currentPrice + trailPoints; + if(newSL < currentSL && newSL < openPrice) + { + Trade.PositionModify(_Symbol, newSL, PositionInfo.TakeProfit()); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Check Soft Stop Loss | +//+------------------------------------------------------------------+ +void CheckSoftStopLoss(int positionIndex) +{ + if(!PositionInfo.SelectByIndex(positionIndex)) + return; + + double currentPrice = PositionInfo.PositionType() == POSITION_TYPE_BUY ? + SymbolInfo.Bid() : SymbolInfo.Ask(); + + double stopLossPrice = CalculateStopLoss(PositionInfo.PositionType(), + PositionInfo.PriceOpen()); + + if((PositionInfo.PositionType() == POSITION_TYPE_BUY && currentPrice <= stopLossPrice) || + (PositionInfo.PositionType() == POSITION_TYPE_SELL && currentPrice >= stopLossPrice)) + { + Trade.PositionClose(_Symbol); + ConsecutiveLosses++; + DailyLoss += PositionInfo.Profit(); + } +} + +//+------------------------------------------------------------------+ +//| Check Mid Level Exit | +//+------------------------------------------------------------------+ +void CheckMidLevelExit(int positionIndex) +{ + if(!PositionInfo.SelectByIndex(positionIndex)) + return; + + double currentPrice = PositionInfo.PositionType() == POSITION_TYPE_BUY ? + SymbolInfo.Bid() : SymbolInfo.Ask(); + + // Close if price reaches mid level + if(MathAbs(currentPrice - LevelMid) <= 10 * _Point) + { + Trade.PositionClosePartial(_Symbol, PositionInfo.Volume() * 0.5); // Close half + } +} + +//+------------------------------------------------------------------+ +//| Close All Positions | +//+------------------------------------------------------------------+ +void CloseAllPositions() +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionInfo.SelectByIndex(i)) + { + if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == InpMagicNumber) + { + Trade.PositionClose(_Symbol); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Check Existing Positions (Auto Recovery) | +//+------------------------------------------------------------------+ +void CheckExistingPositions() +{ + int count = 0; + for(int i = 0; i < PositionsTotal(); i++) + { + if(PositionInfo.SelectByIndex(i)) + { + if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == InpMagicNumber) + { + count++; + Print("Found existing position #", i+1, + " Type: ", EnumToString(PositionInfo.PositionType()), + " Volume: ", PositionInfo.Volume(), + " Profit: ", PositionInfo.Profit()); + } + } + } + + if(count > 0) + { + Print("Auto-recovery: ", count, " positions restored"); + } +} + +//+------------------------------------------------------------------+ +//| Check Daily Reset | +//+------------------------------------------------------------------+ +void CheckDailyReset() +{ + MqlDateTime timeNow, lastReset; + TimeCurrent(timeNow); + TimeToStruct(LastResetDate, lastReset); + + if(timeNow.day != lastReset.day || + timeNow.mon != lastReset.mon || + timeNow.year != lastReset.year) + { + // New day, reset statistics + DailyTradeCount = 0; + DailyPnL = 0.0; + DailyProfit = 0.0; + DailyLoss = 0.0; + ConsecutiveLosses = 0; + LastResetDate = TimeCurrent(); + TradingEnabled = true; + + Print("Daily statistics reset"); + } +} + +//+------------------------------------------------------------------+ +//| Create Dashboard | +//+------------------------------------------------------------------+ +void CreateDashboard() +{ + // Create main panel - ตั้งค่า Z-Order ให้สูงขึ้น + CreatePanel("MainPanel", 10, 20, 850, 180, PanelColor, BORDER_SUNKEN); + + // Create section panels - ตั้งค่า Z-Order ให้สูงขึ้น และ BACK = false + CreatePanel("PricePanel", 20, 30, 260, 70, C'40,40,40', BORDER_FLAT); + CreatePanel("DeltaPanel", 290, 30, 260, 70, C'40,40,40', BORDER_FLAT); + CreatePanel("LevelsPanel", 560, 30, 290, 70, C'40,40,40', BORDER_FLAT); + + CreatePanel("StatsPanel", 20, 110, 410, 80, C'40,40,40', BORDER_FLAT); + CreatePanel("TradingPanel", 440, 110, 410, 80, C'40,40,40', BORDER_FLAT); + + // Initial labels + UpdateDashboard(); +} + +//+------------------------------------------------------------------+ +//| Update Dashboard | +//+------------------------------------------------------------------+ +void UpdateDashboard() +{ + // Calculate metrics + double balance = AccountInfo.Balance(); + double equity = AccountInfo.Equity(); + double dailyGainPercent = balance > 0 ? ((equity - balance) / balance) * 100 : 0; + double maxDD = balance > 0 ? ((balance - equity) / balance) * 100 : 0; + + // Get ATR + double atrValues[]; + ArraySetAsSeries(atrValues, true); + double atr = 0; + double atrPercent = 0; + if(CopyBuffer(ATRHandle, 0, 0, 1, atrValues) >= 1) + { + atr = atrValues[0]; + atrPercent = (atr / SpotPrice) * 100; + } + + // Get market phase + string marketPhase = GetMarketPhaseString(); + color phaseColor = GetMarketPhaseColor(); + + // Calculate distances to key levels + double distToUpper = LevelUpper2 > 0 ? ((LevelUpper2 - SpotPrice) / SpotPrice) * 100 : 0; + double distToLower = LevelLower2 > 0 ? ((SpotPrice - LevelLower2) / SpotPrice) * 100 : 0; + + // Find nearest OI levels + double nearestCall = FindNearestLevel(SpotPrice, CallLevels, true); + double nearestPut = FindNearestLevel(SpotPrice, PutLevels, false); + + // Price Section + UpdateLabel("PriceTitle", 30, 35, "PRICE INFORMATION", clrYellow, 10); + UpdateLabel("SpotPrice", 30, 55, "Spot: " + DoubleToString(SpotPrice, _Digits), TextColor, 9); + UpdateLabel("FuturePrice", 30, 70, "Future: " + DoubleToString(FuturePrice, _Digits), TextColor, 9); + UpdateLabel("Spread", 150, 55, "Spread: " + IntegerToString((int)SymbolInfo.Spread()), TextColor, 9); + + // Delta Section + UpdateLabel("DeltaTitle", 300, 35, "DELTA & DEVIATION", clrYellow, 10); + UpdateLabel("DeltaValue", 300, 55, "ΔPrice: " + DoubleToString(DeltaPrice, 4), + DeltaPrice > 0 ? clrLime : clrRed, 9); + UpdateLabel("DeltaEMA", 300, 70, "ΔEMA: " + DoubleToString(DeltaEMA, 4), TextColor, 9); + UpdateLabel("Deviation", 420, 55, "Deviation: " + DoubleToString(DeltaDeviation/_Point, 1) + " pts", + MathAbs(DeltaDeviation) > InpDeviationThreshold * _Point ? WarningColor : TextColor, 9); + + // Levels Section + UpdateLabel("LevelsTitle", 570, 35, "OI & KEY LEVELS", clrYellow, 10); + UpdateLabel("NearestCALL", 570, 55, "Nearest CALL: " + (nearestCall>0?DoubleToString(nearestCall, _Digits):"N/A"), clrCyan, 9); + UpdateLabel("NearestPUT", 570, 70, "Nearest PUT: " + (nearestPut>0?DoubleToString(nearestPut, _Digits):"N/A"), clrCyan, 9); + UpdateLabel("DistUpper", 725, 55, "%Far Upper: " + DoubleToString(distToUpper, 2), TextColor, 9); + UpdateLabel("DistLower", 725, 70, "%Far Lower: " + DoubleToString(distToLower, 2), TextColor, 9); + + // Statistics Section + UpdateLabel("StatsTitle", 30, 115, "STATISTICS", clrYellow, 10); + UpdateLabel("DailyTrades", 30, 135, "Trades Today: " + IntegerToString(DailyTradeCount), TextColor, 9); + UpdateLabel("DailyGain", 30, 150, "%Gain: " + DoubleToString(dailyGainPercent, 2), + dailyGainPercent >= 0 ? ProfitColor : LossColor, 9); + UpdateLabel("MaxDD", 30, 165, "%Max DD: " + DoubleToString(maxDD, 2), + maxDD > 5 ? WarningColor : TextColor, 9); + UpdateLabel("ConsecLosses", 180, 135, "Consec. Losses: " + IntegerToString(ConsecutiveLosses), + ConsecutiveLosses > 0 ? WarningColor : TextColor, 9); + UpdateLabel("DailyProfit", 180, 150, "Daily P/L: " + DoubleToString(DailyProfit + DailyLoss, 2), + (DailyProfit + DailyLoss) >= 0 ? ProfitColor : LossColor, 9); + + // Trading Section + UpdateLabel("TradingTitle", 450, 115, "TRADING STATUS", clrYellow, 10); + UpdateLabel("MarketPhase", 450, 135, "Market: " + marketPhase, phaseColor, 9); + UpdateLabel("TradingStatus", 450, 150, "Status: " + (TradingEnabled ? "ACTIVE" : "DISABLED"), + TradingEnabled ? clrLime : clrRed, 9); + UpdateLabel("ATRPercent", 450, 165, "ATR%: " + DoubleToString(atrPercent, 2), + atrPercent > InpMaxATRPercent ? WarningColor : TextColor, 9); + + // Active Position + string posInfo = "No Active Position"; + color posColor = clrGray; + double posProfit = 0; + + if(PositionsTotal() > 0) + { + for(int i = 0; i < PositionsTotal(); i++) + { + if(PositionInfo.SelectByIndex(i)) + { + if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == InpMagicNumber) + { + posInfo = PositionInfo.PositionType() == POSITION_TYPE_BUY ? + "LONG (" + DoubleToString(PositionInfo.Volume(), 2) + ")" : + "SHORT (" + DoubleToString(PositionInfo.Volume(), 2) + ")"; + posColor = PositionInfo.PositionType() == POSITION_TYPE_BUY ? clrLime : clrRed; + posProfit = PositionInfo.Profit(); + break; + } + } + } + } + + UpdateLabel("PositionInfo", 600, 135, "Position: " + posInfo, posColor, 9); + UpdateLabel("PositionProfit", 600, 150, "P/L: " + DoubleToString(posProfit, 2), + posProfit >= 0 ? ProfitColor : LossColor, 9); + + // News Block Warning + if(NewsBlockActive) + { + UpdateLabel("NewsWarning", 600, 165, "NEWS BLOCK ACTIVE", clrRed, 9); + } + else + { + ObjectDelete(chart_id, "NewsWarning"); + } +} + +//+------------------------------------------------------------------+ +//| Update Label | +//+------------------------------------------------------------------+ +void UpdateLabel(string name, int x, int y, string text, color clr, int fontSize = 9) +{ + if(ObjectFind(chart_id, name) < 0) + { + ObjectCreate(chart_id, name, OBJ_LABEL, 0, 0, 0); + ObjectSetInteger(chart_id, name, OBJPROP_XDISTANCE, x); + ObjectSetInteger(chart_id, name, OBJPROP_YDISTANCE, y); + ObjectSetInteger(chart_id, name, OBJPROP_COLOR, clr); + ObjectSetInteger(chart_id, name, OBJPROP_FONTSIZE, fontSize); + ObjectSetString(chart_id, name, OBJPROP_FONT, "Consolas"); + ObjectSetInteger(chart_id, name, OBJPROP_BACK, false); + ObjectSetInteger(chart_id, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chart_id, name, OBJPROP_HIDDEN, true); + // ตั้งค่า Z-Order ให้สูง + ObjectSetInteger(chart_id, name, OBJPROP_ZORDER, 1002); + } + + ObjectSetString(chart_id, name, OBJPROP_TEXT, text); + ObjectSetInteger(chart_id, name, OBJPROP_COLOR, clr); +} + +//+------------------------------------------------------------------+ +//| Find Nearest Level | +//+------------------------------------------------------------------+ +double FindNearestLevel(double price, double &levels[], bool findAbove) +{ + double nearest = 0; + double minDist = DBL_MAX; + + for(int i = 0; i < ArraySize(levels); i++) + { + if(levels[i] <= 0) continue; + + double dist = MathAbs(price - levels[i]); + + if(findAbove) + { + if(levels[i] >= price && dist < minDist) + { + minDist = dist; + nearest = levels[i]; + } + } + else + { + if(levels[i] <= price && dist < minDist) + { + minDist = dist; + nearest = levels[i]; + } + } + } + + return nearest; +} + +//+------------------------------------------------------------------+ +//| Get Market Phase String | +//+------------------------------------------------------------------+ +string GetMarketPhaseString() +{ + ENUM_MARKET_PHASE phase = GetMarketPhase(); + + switch(phase) + { + case PHASE_BULLISH: return "Bullish"; + case PHASE_BEARISH: return "Bearish"; + case PHASE_SIDEWAYS: return "Sideways"; + default: return "Neutral"; + } +} + +//+------------------------------------------------------------------+ +//| Get Market Phase Color | +//+------------------------------------------------------------------+ +color GetMarketPhaseColor() +{ + ENUM_MARKET_PHASE phase = GetMarketPhase(); + + switch(phase) + { + case PHASE_BULLISH: return clrLime; + case PHASE_BEARISH: return clrRed; + case PHASE_SIDEWAYS: return clrYellow; + default: return clrGray; + } +} + +//+------------------------------------------------------------------+ +//| String Split Function | +//+------------------------------------------------------------------+ +int StringSplit(string text, string delimiter, string &result[]) +{ + int count = 0; + int pos = 0; + int len = StringLen(delimiter); + + while(pos < StringLen(text)) + { + int nextPos = StringFind(text, delimiter, pos); + if(nextPos == -1) nextPos = StringLen(text); + + string substr = StringSubstr(text, pos, nextPos - pos); + ArrayResize(result, count + 1); + result[count] = substr; + count++; + + pos = nextPos + len; + } + + return count; +} +//+------------------------------------------------------------------+ \ No newline at end of file diff --git a/OI EA/OI_OrderFlow_Absorption_XAUUSD.mq5 b/OI EA/OI_OrderFlow_Absorption_XAUUSD.mq5 new file mode 100644 index 0000000..3a581c6 --- /dev/null +++ b/OI EA/OI_OrderFlow_Absorption_XAUUSD.mq5 @@ -0,0 +1,1381 @@ +//+------------------------------------------------------------------+ +//| 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 = "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() +{ + Print("=== CSV Loading Debug ==="); + Print("Terminal Files dir: ", TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\"); + Print("Attempting to open: ", InpOICsvPath); + + if(!FileIsExist(InpOICsvPath)) { + Print("CSV FILE NOT FOUND - check path or if scraper output exists"); + return; + } + + int filehandle = FileOpen(InpOICsvPath, FILE_READ | FILE_ANSI); + + if(filehandle == INVALID_HANDLE) + { + int err = GetLastError(); + Print("CSV OPEN FAILED [Error ", err, "]: ", err); + Print("File exists: ", FileIsExist(InpOICsvPath) ? "YES" : "NO"); + return; + } + + long fileSize = FileSize(filehandle); + Print("CSV opened successfully. Size: ", fileSize, " bytes"); + + int callIndex = 0; + int putIndex = 0; + double futurePrice = 0.0; + bool isFirstLine = true; + static bool csvParseErrorLogged = false; + + while(!FileIsEnding(filehandle)) + { + string row = FileReadString(filehandle); + + Print("DEBUG ROW: [", row, "]"); + + 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; + Print("DEBUG: Found Future row, price=", futurePrice); + } + else if(type == "CALL" && callIndex < 3) + { + CallLevels[callIndex] = strike; + CallOI[callIndex] = oi; + callIndex++; + Print("DEBUG: Found CALL row, strike=", strike, " oi=", oi); + } + else if(type == "PUT" && putIndex < 3) + { + PutLevels[putIndex] = strike; + PutOI[putIndex] = oi; + putIndex++; + Print("DEBUG: Found PUT row, strike=", strike, " oi=", oi); + } + } + } + + 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); + csvParseErrorLogged = false; + } else { + if(!csvParseErrorLogged) { + Print("CSV ERROR: No valid Future price found in file"); + csvParseErrorLogged = true; + } + } + + 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(); + } +} \ No newline at end of file diff --git a/OI EA/oi_scraper/.env.example b/OI EA/oi_scraper/.env.example new file mode 100644 index 0000000..14658ed --- /dev/null +++ b/OI EA/oi_scraper/.env.example @@ -0,0 +1,22 @@ +# CME Group QuikStrike Login Credentials +CME_USERNAME=your_username_here +CME_PASSWORD=your_password_here +CME_LOGIN_URL=https://login.cmegroup.com/sso/accountstatus/showAuth.action + +# QuikStrike URL (fixed - always same page) +QUIKSTRIKE_URL=https://www.cmegroup.com/tools-information/quikstrike/open-interest-heatmap.html + +# Gold Price Source (investing.com) +INVESTING_URL=https://www.investing.com/commodities/gold + +# Output Settings +CSV_OUTPUT_PATH=./oi_data.csv +TOP_N_STRIKES=3 + +# Scraping Settings +HEADLESS=false # Set to true for production +TIMEOUT_SECONDS=30 +RETRY_ATTEMPTS=3 + +# Logging +LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR \ No newline at end of file diff --git a/OI EA/oi_scraper/.gitignore b/OI EA/oi_scraper/.gitignore new file mode 100644 index 0000000..4b3fd0c --- /dev/null +++ b/OI EA/oi_scraper/.gitignore @@ -0,0 +1,31 @@ +# Python cache +__pycache__/ +*.py[cod] +*$py.class +*.so + +# Virtual environments +venv/ +env/ +ENV/ + +# Environment variables +.env + +# Output files +*.csv +*.png +*.log + +# Session data +cookies.json + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/OI EA/oi_scraper/README.md b/OI EA/oi_scraper/README.md new file mode 100644 index 0000000..06a50bc --- /dev/null +++ b/OI EA/oi_scraper/README.md @@ -0,0 +1,268 @@ +# CME OI Scraper + +Python scraper that extracts Open Interest data from CME Group QuikStrike and current gold price from investing.com. + +## What It Extracts + +1. **OI Levels (from CME QuikStrike):** + - Top 3 CALL strikes by OI volume (unique strikes) + - Top 3 PUT strikes by OI volume (unique strikes) + +2. **Gold Price (from investing.com):** + - Current gold futures price (e.g., 4476.50) + +## Prerequisites + +- Python 3.9 or higher +- CME Group QuikStrike account (free registration at https://www.cmegroup.com) +- Windows 10/11 (for batch files) or Linux/macOS + +## Quick Start + +### Windows + +1. **Run one-time setup:** + ```cmd + cd C:\Path\To\oi_scraper + setup_env.bat + ``` + +2. **Run the scraper:** + ```cmd + run_with_venv.bat + ``` + +### Linux/macOS + +1. **Setup:** + ```bash + cd /path/to/oi_scraper + python3 -m venv venv + source venv/bin/activate + pip install -r requirements.txt + playwright install chromium + ``` + +2. **Run:** + ```bash + source venv/bin/activate + python main.py + ``` + +## Configuration + +### Edit `.env` File + +Copy and edit the environment file: + +```cmd +copy .env.example .env +notepad .env +``` + +Required settings: +```env +CME_USERNAME=your_cme_username +CME_PASSWORD=your_cme_password +``` + +Optional settings: +```env +# Number of top strikes to export (default: 3) +TOP_N_STRIKES=3 + +# Run browser without window (default: false) +HEADLESS=false + +# Page timeout in seconds (default: 30) +TIMEOUT_SECONDS=30 + +# Output CSV path +CSV_OUTPUT_PATH=./oi_data.csv + +# Logging level: DEBUG, INFO, WARNING, ERROR +LOG_LEVEL=INFO +``` + +## Output Format + +The scraper exports to `oi_data.csv`: + +```csv +Type,Strike,OI +CALL,4375.0,147 +CALL,4450.0,173 +CALL,4500.0,176 +PUT,4435.0,49 +PUT,4400.0,102 +PUT,4515.0,150 + +[Price] +FuturePrice,4467.8 +``` + +The `[Price]` section contains the current gold futures price scraped from investing.com. + +## Session Persistence + +The scraper saves login sessions to `cookies.json`: + +- **First run:** Logs in with credentials, saves cookies +- **Subsequent runs:** Uses saved cookies if session is valid +- **Session expired:** Automatically re-logs in and saves new cookies + +This makes scheduled runs faster and reduces login attempts to CME servers. + +To force a fresh login: +```cmd +del cookies.json +``` + +## Integration with EA + +The EA reads OI data from CSV when configured: +```mql5 +input ENUM_OI_SOURCE InpOISource = OI_SOURCE_CSV_FILE; +``` + +Copy `oi_data.csv` to your MT5 `MQL5/Files` directory: +``` +C:\Users\YourUsername\AppData\Roaming\MetaQuotes\Terminal\Common\MQL5\Files\oi_data.csv +``` + +## Automatic Daily Scheduling + +### Windows Task Scheduler + +1. **Create scheduled task:** + - Open Task Scheduler (`taskschd.msc`) + - Click "Create Task" + +2. **Configure General tab:** + - Name: `CME OI Scraper - Daily` + - ✅ Run whether user is logged on or not + - ✅ Run with highest privileges + +3. **Configure Triggers tab:** + - New → On a schedule → Daily + - Start time: 9:00 AM (or your preferred time) + - ✅ Enabled + +4. **Configure Actions tab:** + - Action: Start a program + - Program/script: + ``` + C:\Path\To\oi_scraper\run_scheduled.bat + ``` + - Start in: + ``` + C:\Path\To\oi_scraper + ``` + +5. **Click OK to save** + +### Linux/macOS (cron) + +```bash +# Edit crontab +crontab -e + +# Add line to run every day at 9 AM +0 9 * * * cd /path/to/oi_scraper && /path/to/venv/bin/python main.py +``` + +## Batch Files Reference + +| File | Purpose | +|------|---------| +| `setup_env.bat` | One-time setup (creates virtual environment) | +| `run_with_venv.bat` | Manual run with visible window | +| `run_scheduled.bat` | For Task Scheduler (no window, no pause) | + +## Troubleshooting + +### Module Not Found Errors + +**Error:** `ModuleNotFoundError: No module named 'playwright'` + +**Solution:** +```cmd +run_with_venv.bat +``` + +The virtual environment ensures all dependencies are isolated. + +### Login Fails + +- Verify credentials in `.env` +- Check if CME requires 2FA (manual intervention needed) +- Set `HEADLESS=false` to see browser activity +- Check screenshots: `login_failed.png`, `login_error.png` + +### No Data Extracted + +- Check if CME table structure changed +- Increase `TIMEOUT_SECONDS=60` in `.env` +- Check logs for errors +- Screenshot saved as `login_debug.png` + +### Browser Issues + +```cmd +# Reinstall Chromium +python -m playwright install chromium +``` + +### Session Expires Frequently + +Delete cookies to force fresh login: +```cmd +del cookies.json +``` + +### Check Python Path Issues (Windows) + +```cmd +# Check which Python is being used +where python + +# Use Python launcher +py -3 main.py + +# Or use the virtual environment +run_with_venv.bat +``` + +## Finding Product IDs + +To scrape other instruments (Silver, Crude Oil, etc.): + +1. Visit CME QuikStrike OI Heatmap +2. Login to your CME account +3. Select a product from the dropdown +4. The URL updates with the `pid` parameter +5. Note: This scraper is configured for Gold by default + +## Notes + +- Targets the OI Heatmap table structure +- Exports top N unique strikes by OI volume +- Uses session cookies for faster subsequent runs +- CME sessions typically last several days to weeks +- Virtual environment recommended to avoid Python path conflicts + +## Files + +``` +oi_scraper/ +├── main.py # Main scraper script +├── requirements.txt # Python dependencies +├── .env.example # Environment template +├── .env # Your credentials (create from example) +├── setup_env.bat # Windows: Create virtual environment +├── run_with_venv.bat # Windows: Manual run +├── run_scheduled.bat # Windows: Task Scheduler run +├── oi_data.csv # Output file (generated) +├── cookies.json # Session cookies (generated) +└── scraper.log # Log file (generated) +``` diff --git a/OI EA/oi_scraper/main.py b/OI EA/oi_scraper/main.py new file mode 100644 index 0000000..51af0eb --- /dev/null +++ b/OI EA/oi_scraper/main.py @@ -0,0 +1,376 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +CME OI Scraper - Extracts Open Interest data from CME QuikStrike and gold price from investing.com +Usage: python main.py +Requires: pip install -r requirements.txt +""" + +import os +import logging +import json +from datetime import datetime +from playwright.sync_api import sync_playwright +from dotenv import load_dotenv +import pandas as pd + +load_dotenv() + +# Configuration +CME_USERNAME = os.getenv("CME_USERNAME") +CME_PASSWORD = os.getenv("CME_PASSWORD") +CME_LOGIN_URL = os.getenv( + "CME_LOGIN_URL", "https://login.cmegroup.com/sso/accountstatus/showAuth.action" +) +QUIKSTRIKE_URL = ( + "https://www.cmegroup.com/tools-information/quikstrike/open-interest-heatmap.html" +) +QUIKSTRIKE_URL = ( + "https://www.cmegroup.com/tools-information/quikstrike/open-interest-heatmap.html" +) +INVESTING_URL = os.getenv("INVESTING_URL", "https://www.investing.com/commodities/gold") +CSV_OUTPUT_PATH = os.getenv("CSV_OUTPUT_PATH", "./oi_data.csv") +TOP_N_STRIKES = int(os.getenv("TOP_N_STRIKES", "3")) +HEADLESS = os.getenv("HEADLESS", "false").lower() == "true" +TIMEOUT_SECONDS = int(os.getenv("TIMEOUT_SECONDS", "30")) +RETRY_ATTEMPTS = int(os.getenv("RETRY_ATTEMPTS", "3")) +LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") +COOKIE_FILE = "./cookies.json" + +logging.basicConfig(level=getattr(logging, LOG_LEVEL)) +logger = logging.getLogger(__name__) + + +def save_cookies(context): + cookies = context.cookies() + with open(COOKIE_FILE, "w") as f: + json.dump(cookies, f) + logger.info("Cookies saved to file") + + +def load_cookies(context): + if os.path.exists(COOKIE_FILE): + with open(COOKIE_FILE, "r") as f: + cookies = json.load(f) + context.add_cookies(cookies) + logger.info("Cookies loaded from file") + return True + return False + + +def delete_cookies(): + if os.path.exists(COOKIE_FILE): + os.remove(COOKIE_FILE) + logger.info("Cookies deleted") + + +def are_cookies_valid(page): + logger.info("Checking if cookies are valid...") + page.goto(QUIKSTRIKE_URL, timeout=TIMEOUT_SECONDS * 1000) + page.wait_for_load_state("domcontentloaded", timeout=TIMEOUT_SECONDS * 1000) + page.wait_for_timeout(3000) + + try: + frame = page.frame_locator("iframe.cmeIframe").first + page.wait_for_timeout(5000) + + table_exists = frame.locator("table.grid-thm").count() > 0 + if table_exists: + logger.info("Cookies are valid - OI table found in iframe") + else: + logger.info("Cookies may be expired - no OI table found in iframe") + return table_exists + except Exception as e: + logger.info(f"Cookies expired - error checking iframe: {e}") + return False + + +def login_to_cme(page): + logger.info("Attempting to login to CME QuikStrike...") + + page.goto(CME_LOGIN_URL, timeout=TIMEOUT_SECONDS * 1000) + page.wait_for_load_state("domcontentloaded", timeout=TIMEOUT_SECONDS * 1000) + page.wait_for_timeout(1000) + + try: + page.fill("#user", CME_USERNAME) + page.fill("#pwd", CME_PASSWORD) + page.click("#loginBtn") + + logger.info("Waiting for login redirect...") + page.wait_for_timeout(30000) + + current_url = page.url.lower() + logger.info(f"Current URL after login attempt: {current_url}") + + if "login" in current_url or "sso" in current_url: + logger.error("Login may have failed - still on SSO/login page") + page.screenshot(path="login_failed.png") + return False + + logger.info("Login successful") + page.screenshot(path="login_success.png") + return True + + except Exception as e: + logger.error(f"Login error: {e}") + page.screenshot(path="login_error.png") + return False + + +def select_gold_product(page): + logger.info("Selecting Gold product...") + + logger.info("Switching to iframe context...") + frame = page.frame_locator("iframe.cmeIframe").first + page.wait_for_timeout(5000) + + logger.info("Step 1: Clicking dropdown arrow...") + frame.locator("#ctl11_hlProductArrow").click() + page.wait_for_timeout(1000) + + logger.info("Step 2: Clicking Metals...") + frame.locator('a[groupid="6"]:has-text("Metals")').click() + page.wait_for_timeout(500) + + logger.info("Step 3: Clicking Precious Metals...") + frame.locator('a[familyid="6"]:has-text("Precious Metals")').click() + page.wait_for_timeout(500) + + logger.info("Step 4: Clicking Gold...") + frame.locator('a[title="Gold"]').click() + + logger.info("Waiting for Gold data to load...") + page.wait_for_timeout(10000) + logger.info("Gold product selected") + + +def navigate_to_oi_heatmap(page): + logger.info(f"Navigating to QuikStrike: {QUIKSTRIKE_URL}") + page.goto(QUIKSTRIKE_URL, timeout=TIMEOUT_SECONDS * 1000) + page.wait_for_load_state("domcontentloaded", timeout=TIMEOUT_SECONDS * 1000) + page.wait_for_timeout(5000) + + select_gold_product(page) + + +def extract_oi_data(page): + logger.info("Extracting OI data from Gold matrix table...") + + logger.info("Switching to iframe context...") + frame = page.frame_locator("iframe.cmeIframe").first + page.wait_for_timeout(8000) + + logger.info("Looking for table.grid-thm...") + call_levels = [] + put_levels = [] + + table = frame.locator("table.grid-thm").first + table.wait_for(state="visible", timeout=10000) + logger.info("Table found, waiting for data...") + + rows = table.locator("tbody tr").all() + logger.info(f"Found {len(rows)} rows in table") + + for row in rows: + try: + cells = row.locator("td").all() + if len(cells) < 3: + continue + + strike = None + for cell in cells: + text = cell.text_content().strip() + if text and text.replace(".", "").isdigit(): + strike = float(text) + break + + if strike is None: + continue + + number_cells = row.locator("td.number").all() + logger.debug(f"Strike {strike}: found {len(number_cells)} number cells") + + for i in range(0, len(number_cells), 2): + if i + 1 >= len(number_cells): + break + + call_cell = number_cells[i] + put_cell = number_cells[i + 1] + + call_text = call_cell.text_content().strip() + put_text = put_cell.text_content().strip() + + if call_text and call_text != "-": + call_oi = int(call_text.replace(",", "")) + call_levels.append( + {"Type": "CALL", "Strike": strike, "OI": call_oi} + ) + + if put_text and put_text != "-": + put_oi = int(put_text.replace(",", "")) + put_levels.append({"Type": "PUT", "Strike": strike, "OI": put_oi}) + + except Exception as e: + logger.warning(f"Error parsing row: {e}") + continue + + logger.info( + f"Extracted {len(call_levels)} CALL levels, {len(put_levels)} PUT levels" + ) + + if call_levels: + call_df = pd.DataFrame(call_levels) + call_df = call_df.drop_duplicates(subset="Strike", keep="first") + call_df = call_df.sort_values("OI") + call_df = call_df.tail(TOP_N_STRIKES) + call_df["Type"] = "CALL" + else: + call_df = pd.DataFrame() + + if put_levels: + put_df = pd.DataFrame(put_levels) + put_df = put_df.drop_duplicates(subset="Strike", keep="first") + put_df = put_df.sort_values("OI") + put_df = put_df.tail(TOP_N_STRIKES) + put_df["Type"] = "PUT" + else: + put_df = pd.DataFrame() + + result_df = pd.concat([call_df, put_df]) + result_df = result_df[["Type", "Strike", "OI"]] + + logger.info(f"Final top {TOP_N_STRIKES} unique strikes for CALL and PUT extracted") + return result_df + + +def scrape_investing_gold_price(page): + logger.info(f"Scraping gold price from: {INVESTING_URL}") + + try: + page.goto(INVESTING_URL, timeout=60000, wait_until="domcontentloaded") + logger.info(f"Page loaded, title: {page.title()}") + + page.wait_for_timeout(5000) + logger.info("Waited for JavaScript to render") + + selectors = [ + 'div[data-test="instrument-price-last"]', + ".text-5xl\\/9.font-bold.text-\\[#232526\\]", + '[data-test="instrument-price-last"]', + ".text-5xl\\/9", + ] + + price = 0.0 + for selector in selectors: + try: + locator = page.locator(selector) + if locator.count() > 0: + locator.first.wait_for(state="visible", timeout=10000) + price_text = locator.first.text_content().strip() + if price_text: + price_text = price_text.replace(",", "") + price = float(price_text) + logger.info(f"Extracted gold price ({selector}): {price}") + break + except Exception as e: + logger.debug(f"Selector {selector} failed: {e}") + continue + + if price == 0.0: + logger.warning("Could not extract gold price, all selectors failed") + + return price + + except Exception as e: + logger.error(f"Error scraping gold price: {e}") + return 0.0 + + +def export_to_csv(df, future_price=0.0): + output_path = CSV_OUTPUT_PATH + + with open(output_path, "w", encoding="utf-8") as f: + f.write("Type,Strike,OI\n") + + call_df = df[df["Type"] == "CALL"] if len(df) > 0 else pd.DataFrame() + put_df = df[df["Type"] == "PUT"] if len(df) > 0 else pd.DataFrame() + + if len(call_df) > 0: + for _, row in call_df.iterrows(): + f.write(f"CALL,{row['Strike']:.1f},{row['OI']}\n") + + if len(put_df) > 0: + for _, row in put_df.iterrows(): + f.write(f"PUT,{row['Strike']:.1f},{row['OI']}\n") + + f.write(f"Future,{future_price},0\n") + + logger.info(f"Exported OI data and price to {output_path}") + + +def run_scraper(): + if not CME_USERNAME or not CME_PASSWORD: + logger.error("Missing CME_USERNAME or CME_PASSWORD in .env file") + return + + future_price = 0.0 + + for attempt in range(RETRY_ATTEMPTS): + try: + with sync_playwright() as p: + browser = p.chromium.launch(headless=HEADLESS) + context = browser.new_context() + page = context.new_page() + + cookies_loaded = load_cookies(context) + cookies_valid = False + + if cookies_loaded: + cookies_valid = are_cookies_valid(page) + + if cookies_valid: + logger.info("Using cached session") + else: + if cookies_loaded: + logger.info("Cookies expired, deleting and re-logging in...") + delete_cookies() + + logger.info("Logging in to CME...") + if not login_to_cme(page): + browser.close() + if attempt < RETRY_ATTEMPTS - 1: + logger.info( + f"Retrying... Attempt {attempt + 2}/{RETRY_ATTEMPTS}" + ) + continue + else: + logger.error("All login attempts failed") + return + + navigate_to_oi_heatmap(page) + oi_data = extract_oi_data(page) + save_cookies(context) + + if len(oi_data) > 0: + logger.info("Extracting gold price from investing.com...") + future_price = scrape_investing_gold_price(page) + logger.info(f"Gold price extracted: {future_price}") + + export_to_csv(oi_data, future_price) + else: + logger.warning("No OI data extracted") + + browser.close() + break + + except Exception as e: + logger.error(f"Scraper error (attempt {attempt + 1}): {e}") + if attempt < RETRY_ATTEMPTS - 1: + logger.info(f"Retrying... Attempt {attempt + 2}/{RETRY_ATTEMPTS}") + else: + logger.error("All attempts failed") + + +if __name__ == "__main__": + run_scraper() diff --git a/OI EA/oi_scraper/requirements.txt b/OI EA/oi_scraper/requirements.txt new file mode 100644 index 0000000..d29d1ce --- /dev/null +++ b/OI EA/oi_scraper/requirements.txt @@ -0,0 +1,3 @@ +playwright>=1.40.0 +python-dotenv>=1.0.0 +pandas>=2.2.0 diff --git a/OI EA/oi_scraper/run_scheduled.bat b/OI EA/oi_scraper/run_scheduled.bat new file mode 100644 index 0000000..c3a373e --- /dev/null +++ b/OI EA/oi_scraper/run_scheduled.bat @@ -0,0 +1,13 @@ +@echo off +REM ========================================== +REM CME OI Scraper - Scheduled Task Version +REM For use with Windows Task Scheduler +REM ========================================== + +REM Navigate to script directory +cd /d %~dp0 + +REM Activate virtual environment and run scraper (no pause) +call venv\Scripts\activate.bat +python main.py +exit %ERRORLEVEL% diff --git a/OI EA/oi_scraper/run_scraper.bat b/OI EA/oi_scraper/run_scraper.bat new file mode 100644 index 0000000..a484bcb --- /dev/null +++ b/OI EA/oi_scraper/run_scraper.bat @@ -0,0 +1,21 @@ +@echo off +REM ========================================== +REM CME OI Scraper - Run with Virtual Environment +REM ========================================== + +REM Navigate to script directory +cd /d %~dp0 + +echo ========================================== +echo CME OI Scraper +echo ========================================== + +REM Activate virtual environment +call venv\Scripts\activate.bat + +REM Run Python scraper +python main.py + +REM Pause for 5 seconds if running manually (not scheduled) +if "%1"=="--scheduled" goto :eof +timeout /t 5 diff --git a/OI EA/oi_scraper/run_scraper.ps1 b/OI EA/oi_scraper/run_scraper.ps1 new file mode 100644 index 0000000..28f3e11 --- /dev/null +++ b/OI EA/oi_scraper/run_scraper.ps1 @@ -0,0 +1,77 @@ +# CME OI Scraper - PowerShell Script +# Copy this file to: run_scraper.ps1 + +# ========================================== +# Configuration +# ========================================== +$scriptPath = "C:\Users\YourUsername\Gitea\MeanRevisionEA\oi_scraper" +$logFile = "$scriptPath\scraper.log" +$csvFile = "$scriptPath\oi_data.csv" +$mt5Path = "C:\Users\YourUsername\AppData\Roaming\MetaQuotes\Terminal\[Your_Terminal_ID]\MQL5\Files\oi_data.csv" + +# ========================================== +# Helper Functions +# ========================================== +function Write-Log { + param([string]$message) + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logEntry = "[$timestamp] $message" + Write-Output $logEntry | Add-Content $logFile + Write-Host $logEntry +} + +# ========================================== +# Main Script +# ========================================== + +# Navigate to script directory +cd $scriptPath + +Write-Log "==========================================" +Write-Log "CME OI Scraper - Daily Update" +Write-Log "==========================================" + +try { + # Run Python scraper + Write-Log "Starting Python scraper..." + & python main.py *>> $logFile 2>&1 + $exitCode = $LASTEXITCODE + + if ($exitCode -eq 0) { + Write-Log "Python scraper completed successfully" + + # Check if CSV was created + if (Test-Path $csvFile)) { + $fileInfo = Get-Item $csvFile + Write-Log "CSV file found (Last modified: $($fileInfo.LastWriteTime))" + + # Copy to MT5 directory + Write-Log "Copying CSV to MetaTrader 5 Files directory..." + + try { + Copy-Item -Path $csvFile -Destination $mt5Path -Force + Write-Log "CSV successfully copied to MT5 directory" + + # Verify copy + if (Test-Path $mt5Path)) { + Write-Log "Verified: MT5 CSV file exists" + } else { + Write-Log "ERROR: MT5 CSV file not found after copy" + } + } catch { + Write-Log "ERROR: Failed to copy to MT5 directory - $_" + } + } else { + Write-Log "WARNING: CSV file not found after scraper execution" + } + } else { + Write-Log "ERROR: Python scraper failed with exit code $exitCode" + } +} catch { + Write-Log "ERROR: Script failed - $($_.Exception.Message)" + exit 1 +} + +Write-Log "==========================================" +Write-Log "Script completed" +Write-Log "==========================================" diff --git a/OI EA/oi_scraper/run_with_venv.bat b/OI EA/oi_scraper/run_with_venv.bat new file mode 100644 index 0000000..1aaa2f4 --- /dev/null +++ b/OI EA/oi_scraper/run_with_venv.bat @@ -0,0 +1,24 @@ +@echo off +REM ========================================== +REM CME OI Scraper - Manual Run with Virtual Environment +REM ========================================== + +REM Navigate to script directory +cd /d %~dp0 + +echo ========================================== +echo CME OI Scraper - Manual Run +echo ========================================== + +REM Activate virtual environment +call venv\Scripts\activate.bat + +REM Run Python scraper +python main.py + +MOVE "oi_data.csv" "C:\Users\limitrack\AppData\Roaming\MetaQuotes\Terminal\53785E099C927DB68A545C249CDBCE06\MQL5\Files\" + + +echo. +echo Scraper completed. Check oi_data.csv for results. +timeout /t 5 diff --git a/OI EA/oi_scraper/setup_env.bat b/OI EA/oi_scraper/setup_env.bat new file mode 100644 index 0000000..6c25181 --- /dev/null +++ b/OI EA/oi_scraper/setup_env.bat @@ -0,0 +1,34 @@ +@echo off +REM ========================================== +REM CME OI Scraper - Virtual Environment Setup +REM ========================================== + +echo ========================================== +echo Setting up Python Virtual Environment +echo ========================================== + +REM Navigate to script directory +cd /d %~dp0 + +REM Create virtual environment +echo Creating virtual environment... +py -3 -m venv venv + +REM Activate virtual environment and install dependencies +echo Installing dependencies... +call venv\Scripts\activate.bat +pip install --upgrade pip +pip install -r requirements.txt + +REM Install playwright browser +echo Installing Playwright browser... +python -m playwright install chromium + +echo ========================================== +echo Setup Complete! +echo ========================================== +echo. +echo To run the scraper, use: run_with_venv.bat +echo. + +pause diff --git a/OracleDashboard/The Orc D V1.0.mq5 b/OracleDashboard/The Orc D V1.0.mq5 new file mode 100644 index 0000000..95bbde0 Binary files /dev/null and b/OracleDashboard/The Orc D V1.0.mq5 differ diff --git a/blank b/blank deleted file mode 100644 index e69de29..0000000 diff --git a/opencode.jsonc b/opencode.jsonc new file mode 100644 index 0000000..9c2f7d0 --- /dev/null +++ b/opencode.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "mql5_help": { + "type": "local", + "command": [ + "npx", + "-y", + "github:caoshuo594/mql5-help-mcp" + ] + } + } +}