From 39ce46877e2c0c96b8c265b593870ae1e8db1e2c Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Mon, 12 Jan 2026 09:14:10 +0700 Subject: [PATCH] Add files --- .DS_Store | Bin 0 -> 10244 bytes OI EA/.DS_Store | Bin 0 -> 6148 bytes OI EA/AGENTS.md | 147 ++ OI EA/OI_MeanReversion_Pro_XAUUSD.mq5 | 2290 ++++++++++++++++++++++ OI EA/OI_OrderFlow_Absorption_XAUUSD.mq5 | 1381 +++++++++++++ OI EA/oi_scraper/.env.example | 22 + OI EA/oi_scraper/.gitignore | 31 + OI EA/oi_scraper/README.md | 268 +++ OI EA/oi_scraper/main.py | 376 ++++ OI EA/oi_scraper/requirements.txt | 3 + OI EA/oi_scraper/run_scheduled.bat | 13 + OI EA/oi_scraper/run_scraper.bat | 21 + OI EA/oi_scraper/run_scraper.ps1 | 77 + OI EA/oi_scraper/run_with_venv.bat | 24 + OI EA/oi_scraper/setup_env.bat | 34 + OracleDashboard/The Orc D V1.0.mq5 | Bin 0 -> 274240 bytes blank | 0 opencode.jsonc | 13 + 18 files changed, 4700 insertions(+) create mode 100644 .DS_Store create mode 100644 OI EA/.DS_Store create mode 100644 OI EA/AGENTS.md create mode 100644 OI EA/OI_MeanReversion_Pro_XAUUSD.mq5 create mode 100644 OI EA/OI_OrderFlow_Absorption_XAUUSD.mq5 create mode 100644 OI EA/oi_scraper/.env.example create mode 100644 OI EA/oi_scraper/.gitignore create mode 100644 OI EA/oi_scraper/README.md create mode 100644 OI EA/oi_scraper/main.py create mode 100644 OI EA/oi_scraper/requirements.txt create mode 100644 OI EA/oi_scraper/run_scheduled.bat create mode 100644 OI EA/oi_scraper/run_scraper.bat create mode 100644 OI EA/oi_scraper/run_scraper.ps1 create mode 100644 OI EA/oi_scraper/run_with_venv.bat create mode 100644 OI EA/oi_scraper/setup_env.bat create mode 100644 OracleDashboard/The Orc D V1.0.mq5 delete mode 100644 blank create mode 100644 opencode.jsonc diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7b5efd67bf8f9b29c44d72d7ff52492240e4ec46 GIT binary patch literal 10244 zcmeHMU2GIp6uxI#;0_}&)ItFncWZ^#vWAwQQbb|9-3AIR1-7L>{H(J(LpxzQv+T@n zsZF&$iGU_PsPX@es4=KfM13;x2Q|b8f<)0ojEV7q7meV9iSgXIvvpgF_+TR@bCY|| z{W<5}Ip2I|C+9991Y$X@o{%shgkoh6Vj9#axy8?PFA=QI0Z^9W zv{$f?d4OjV_GQ$UV_bTsxF^pZz&8ay#Q<)O>tUvjGV04QF1a}YZce~I8TV!X9X$lp(=GN$3Ub#)g&C@GyWa~7RNCAu#$U=Jl+ z)y+9EHM@t6I&Irb75Zvwx1kRu8P@|+psiyC}!s@&GBp#e1VEo zOv)7_BWoJ#BjJX|(MWh?ef{c4xUpf~=%`2+RIS{wD|yfywCqQ@58(d}5avK}If{cK zhcqW`rtkrodeOAmU!O?ZgP9`B;#DW#%x^JD5(N}47ElmpOVS*ryQjBr&)%dg=Nx^P zZmO=4*W;?IJ4tz-gP^pgZEHtX&m$P!Y1_;VTDUSlt(xf^D(g+AnJ^yU+UcxqnmcUA za1Gn)?Q!+PE?Z+8O<3ybbL_0!>}}R#;Qm~zjGfVuVaCpGJ?o$p#glU@LU-2Oy=vXP z(U#Wso#*Gt^UI`iX<>KXaEz3xcMTbuJJ72-Y2DHctG^H5gXJ0r3_UB(hlto|se}6Y zvPFxR+_qFvCTkaV>B*&xLpj48ma8b`XH+UxqI`<()zHayTVp$F-ilJGR8zW#%{OOt zZ27cQt3*UurJWAC%(qx6)r+ztb{<5saMLwX1C=$g%T!ak$*0y!8>qZZ?9OwK`DD4Y ziHbe=UI)<`htI{7IF&!8``N*5s^fNKbgS@4F=Y#-ea?WI)jNiess)QSO1;;J^US10 zGh93Cp|QPEQF_@{O`4`_==73Bi_4UR(naNSXd@d|-oW6%cfezWRuvdl6|Lpnst*ob z@_HV|Rna;=UCzaL%RYz-sUtD6mFy=OGE9zZE}jdLp~;7kPGBn@+0|~{6emf ztK@emh1pOJOJNyQLk-*ot6(*(g$8JbHfV?K&1Kx!9;Y0WcK7liE0ltGv@I73HD{vKl6GWj*SSl#O?LxIsBh(5j zgpI-`VT-U+=o6B{0QW&CMCiV8pS+GRjACN2`B#R*$*F@aw0TSG)@^M!ZLobkmxb4@ zT+?`8(>Mt^XA3kfroYGd331{v-ebjNw(>ZXLR}aV`r+$^qdbd+ELUOE literal 0 HcmV?d00001 diff --git a/OI EA/.DS_Store b/OI EA/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5aa50d855a575765bae15a2fdaca60bdef9d0dd5 GIT binary patch literal 6148 zcmeHKyG{c^3>-s>h%_lF_ZRqsRTRE}4+xM@AVoSvM1NJji%(O`MLYVt}0_hI`7!y9Zz_}3vr$~_XY!AvBT*n|8UwL zpNHW%?;2G}0VyB_q<|EV0y7ox-b-7q5*4L@6p#Yn3i$V-(H(o?m>8c9h8O{e3#P-k zj#+}(JVES*VLuSI;@5dt0!AcC>BrW{VmF2JyB5#NP$ZQ zu5-Kg{(n#ZW&Xb;X(t7wz`s(!7TbsIhObn;b@FoFYa9KM?lqruH?D)i5bc;4?U);H e$G1_Gb +#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 0000000000000000000000000000000000000000..95bbde0e914eb59d8dd2d072c97314f169a206a9 GIT binary patch literal 274240 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≈1 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 SlmC{@>VF#Zi=}tf^8W+LLMA5w literal 0 HcmV?d00001 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" + ] + } + } +}