feat(oi): improve csv loading with caching

Add price caching to prevent repeated file reads and improve performance.
Implement multi-path search for CSV files with fallback options. Add
comprehensive logging for CSV load success/failure states. Update dashboard
to display CSV loading status. Simplify scraper CSV output format and automate
file transfer to terminal MQL5 Files directory.
This commit is contained in:
Kunthawat Greethong
2026-01-08 11:49:48 +07:00
parent b7c0e68fa8
commit e7487af624
4 changed files with 159 additions and 90 deletions

Binary file not shown.

View File

@@ -194,6 +194,10 @@ double CurrentBarHigh = 0.0;
double CurrentBarLow = 0.0;
double LastPrice = 0.0;
double CachedFuturePrice = -1; // -1 = not loaded, 0 = loaded but failed, >0 = success
string LoadedCSVPath = ""; // Path from which CSV was successfully loaded
bool CSVLoadLogged = false; // Track if we've logged the result
int OnInit() {
Trade.SetExpertMagicNumber(InpMagicNumber);
Trade.SetDeviationInPoints(InpMaxSlippage);
@@ -432,16 +436,7 @@ void UpdateMarketData() {
SpotPrice = SymbolInfo.Bid();
SymbolInfo.RefreshRates();
double csvFuturePrice = LoadFuturePriceFromCSV();
if(csvFuturePrice > 0) {
FuturePrice = csvFuturePrice;
} else if(DynamicFuturePrice > 0) {
FuturePrice = DynamicFuturePrice;
} else if(InpManualFuturePrice > 0) {
FuturePrice = InpManualFuturePrice;
} else {
FuturePrice = SpotPrice;
}
FuturePrice = LoadFuturePriceFromCSV();
}
bool IsPriceNearPutStrike() {
@@ -485,6 +480,13 @@ bool IsInMiddleOfRange() {
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;
@@ -983,33 +985,83 @@ bool InitializeIndicators() {
}
double LoadFuturePriceFromCSV() {
string path = InpOICsvPath;
int filehandle = FileOpen(path, FILE_READ | FILE_CSV, ',');
if(CachedFuturePrice >= 0) {
return CachedFuturePrice;
}
string paths[];
int pathCount = 0;
ArrayResize(paths, 5);
paths[pathCount++] = InpOICsvPath;
paths[pathCount++] = "oi_data.csv";
paths[pathCount++] = "\\Files\\oi_data.csv";
paths[pathCount++] = "..\\oi_scraper\\oi_data.csv";
paths[pathCount++] = "../oi_scraper/oi_data.csv";
int filehandle = INVALID_HANDLE;
string foundPath = "";
for(int i = 0; i < pathCount; i++) {
filehandle = FileOpen(paths[i], FILE_READ | FILE_CSV, ',');
if(filehandle != INVALID_HANDLE) {
foundPath = paths[i];
break;
}
}
if(filehandle == INVALID_HANDLE) {
if(!CSVLoadLogged) {
Print("CSV ERROR: File not found. Searched paths:");
for(int i = 0; i < pathCount; i++) {
Print(" - ", paths[i]);
}
CSVLoadLogged = true;
}
CachedFuturePrice = 0;
return 0.0;
}
double futurePrice = 0.0;
int dataLineCount = 0;
while(!FileIsEnding(filehandle)) {
string line = FileReadString(filehandle);
dataLineCount++;
if(line == "") continue;
string parts[];
int split = StringSplit(line, ',', parts);
if(split >= 2) {
string dateStr = parts[0];
double future = StringToDouble(parts[1]);
if(future > 0) {
futurePrice = future;
double price = StringToDouble(parts[1]);
if(price > 0) {
futurePrice = price;
break;
}
}
}
FileClose(filehandle);
return futurePrice;
if(futurePrice > 0) {
CachedFuturePrice = futurePrice;
LoadedCSVPath = foundPath;
if(!CSVLoadLogged) {
Print("CSV SUCCESS: FuturePrice=", futurePrice, " loaded from ", foundPath);
CSVLoadLogged = true;
}
} else {
if(!CSVLoadLogged) {
Print("CSV ERROR: No valid price found in ", foundPath);
Print(" - File exists but contains no parseable price data");
CSVLoadLogged = true;
}
CachedFuturePrice = 0;
}
return CachedFuturePrice;
}
void CheckExistingPositions() {
@@ -1028,74 +1080,88 @@ void CheckExistingPositions() {
}
void CreateDashboard() {
int panelWidth = 280;
int panelHeight = 280;
int panelWidth = 280;
int panelHeight = 300;
CreatePanel("DashboardPanel", 10, 10, panelWidth, panelHeight, C'25,25,35', BORDER_FLAT);
CreatePanel("DashboardPanel", 10, 10, panelWidth, panelHeight, C'25,25,35', BORDER_FLAT);
CreateLabel("DB_Title", 20, 20, "ORDER FLOW ABSORPTION EA", clrYellow, 10);
CreateLabel("DB_Title", 20, 20, "ORDER FLOW ABSORPTION EA", clrYellow, 10);
UpdateDashboard();
UpdateDashboard();
}
void UpdateDashboard() {
MqlRates rates[];
int copied = CopyRates(_Symbol, PERIOD_M1, 0, 1, rates);
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);
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 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;
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;
}
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);
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);
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 driftText = DoubleToString(PriceDrift / _Point, 1) + " pts";
color driftColor = PriceDrift < InpMaxPriceDriftPoints * _Point ? clrLime : clrOrange;
UpdateLabel("DB_PriceDrift", 20, 145, "Drift: " + driftText, driftColor, 8);
UpdateLabel("DB_Trades", 20, 170, "Daily: " + IntegerToString(DailyTradeCount) +
"/" + IntegerToString(InpMaxDailyTrades), clrWhite, 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_PnL", 20, 190, "Daily PnL: " + DoubleToString(DailyPnL, 2),
DailyPnL >= 0 ? clrLime : clrRed, 8);
UpdateLabel("DB_Trades", 20, 180, "Daily: " + IntegerToString(DailyTradeCount) +
"/" + IntegerToString(InpMaxDailyTrades), clrWhite, 8);
string tradingStatus = TradingEnabled ? "ENABLED" : "DISABLED";
color statusColor = TradingEnabled ? clrLime : clrRed;
UpdateLabel("DB_Status", 20, 215, "Trading: " + tradingStatus, statusColor, 8);
UpdateLabel("DB_PnL", 20, 200, "Daily PnL: " + DoubleToString(DailyPnL, 2),
DailyPnL >= 0 ? clrLime : clrRed, 8);
string nearZone = "";
if(IsPriceNearPutStrike()) nearZone = "NEAR PUT";
else if(IsPriceNearCallStrike()) nearZone = "NEAR CALL";
else nearZone = "MIDDLE";
string tradingStatus = TradingEnabled ? "ENABLED" : "DISABLED";
color statusColor = TradingEnabled ? clrLime : clrRed;
UpdateLabel("DB_Status", 20, 220, "Trading: " + tradingStatus, statusColor, 8);
color zoneColor = (nearZone == "MIDDLE") ? clrOrange : clrCyan;
UpdateLabel("DB_Zone", 20, 235, "Zone: " + nearZone, zoneColor, 8);
string nearZone = "";
if(IsPriceNearPutStrike()) nearZone = "NEAR PUT";
else if(IsPriceNearCallStrike()) nearZone = "NEAR CALL";
else nearZone = "MIDDLE";
string ticksText = "Ticks: " + IntegerToString(CurrentBarUpTicks) + "/" + IntegerToString(CurrentBarDownTicks);
UpdateLabel("DB_Ticks", 20, 255, ticksText, clrWhite, 8);
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() {

View File

@@ -9,6 +9,7 @@ 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
@@ -290,9 +291,8 @@ def export_to_csv(df, future_price=0.0):
output_path = CSV_OUTPUT_PATH
with open(output_path, "w") as f:
df.to_csv(f, index=False)
f.write("\n[Price]\n")
f.write(f"FuturePrice,{future_price}\n")
f.write("date,future_price\n")
f.write(f"{datetime.now().strftime('%Y-%m-%d')},{future_price}\n")
logger.info(f"Exported OI data and price to {output_path}")

View File

@@ -16,6 +16,9 @@ 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