This repository has been archived on 2026-01-12. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MeanRevisionEA/OI_OrderFlow_Absorption_XAUUSD.mq5
Kunthawat Greethong b7c0e68fa8 refactor(oi): improve data extraction and consolidate documentation
- Fix MQL5 API usage in EA to use correct CopyRates and POSITION_TYPE enums
- Refactor scraper data extraction to use drop_duplicates for unique strikes
- Consolidate Windows setup guide into main README
- Add virtual environment batch files for easier setup and execution
- Simplify run_scraper.bat to focus on core execution
- Normalize lot calculation to use SymbolInfo.LotsStep()
2026-01-06 20:18:12 +07:00

1244 lines
38 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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 <Trade\Trade.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>
#include <Trade\HistoryOrderInfo.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayDouble.mqh>
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_MANUAL;
input string InpOICsvPath = "\\Files\\oi_data.csv";
input double InpManualFuturePrice = 0.0;
input double InpCallStrike1 = 0.0;
input double InpCallStrike2 = 0.0;
input double InpCallStrike3 = 0.0;
input double InpPutStrike1 = 0.0;
input double InpPutStrike2 = 0.0;
input double InpPutStrike3 = 0.0;
input group "=== ORDER FLOW & ABSORPTION ==="
input bool InpUseAbsorptionFilter = true;
input int InpAbsorptionBars = 3;
input int InpOIZonePoints = 100;
input double InpMinVolumeMultiplier = 1.5;
input int InpVolumeEmaPeriod = 20;
input int InpMaxPriceDriftPoints = 50;
input int InpSLBufferPoints = 200;
input group "=== TRADING SETTINGS ==="
input double InpLotSize = 0.1;
input bool InpUseMoneyManagement = true;
input double InpRiskPercent = 1.0;
input bool InpUseStopLoss = true;
input bool InpUseTakeProfit = true;
input int InpStopLossPoints = 300;
input int InpTakeProfitPoints = 500;
input int InpMaxSpread = 200;
input int InpMaxSlippage = 10;
input int InpMagicNumber = 202501;
input group "=== MARKET FILTERS ==="
input bool InpUseMarketPhaseFilter = true;
input ENUM_TIMEFRAMES InpTrendTF = PERIOD_H4;
input int InpTrendMAPeriod1 = 50;
input int InpTrendMAPeriod2 = 200;
input bool InpUseATRFilter = true;
input double InpMaxATRPercent = 2.0;
input bool InpUseSessionFilter = true;
input int InpSessionStartHour = 8;
input int InpSessionEndHour = 22;
input group "=== RISK MANAGEMENT ==="
input double InpMaxDailyLossPercent = 3.0;
input double InpMaxDailyProfitPercent = 5.0;
input int InpMaxConsecutiveLosses = 3;
input bool InpDisableAfterMaxLoss = true;
input double InpEquityProtectionPercent = 10.0;
input bool InpCloseAllOnReverse = false;
input int InpMaxDailyTrades = 10;
input group "=== ADVANCED FEATURES ==="
input bool InpUseTrailingStop = false;
input int InpTrailingStartPoints = 150;
input int InpTrailingStepPoints = 50;
input bool InpUseSoftStopLoss = true;
input bool InpUseAutoRecovery = true;
input bool InpEnableDashboard = true;
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];
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;
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(!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();
}
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();
double csvFuturePrice = LoadFuturePriceFromCSV();
if(csvFuturePrice > 0) {
FuturePrice = csvFuturePrice;
} else if(DynamicFuturePrice > 0) {
FuturePrice = DynamicFuturePrice;
} else if(InpManualFuturePrice > 0) {
FuturePrice = InpManualFuturePrice;
} else {
FuturePrice = SpotPrice;
}
}
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(!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;
}
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;
}
double LoadFuturePriceFromCSV() {
string path = InpOICsvPath;
int filehandle = FileOpen(path, FILE_READ | FILE_CSV, ',');
if(filehandle == INVALID_HANDLE) {
return 0.0;
}
double futurePrice = 0.0;
while(!FileIsEnding(filehandle)) {
string line = FileReadString(filehandle);
string parts[];
int split = StringSplit(line, ',', parts);
if(split >= 2) {
string dateStr = parts[0];
double future = StringToDouble(parts[1]);
if(future > 0) {
futurePrice = future;
break;
}
}
}
FileClose(filehandle);
return futurePrice;
}
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 = 280;
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);
UpdateLabel("DB_Trades", 20, 170, "Daily: " + IntegerToString(DailyTradeCount) +
"/" + IntegerToString(InpMaxDailyTrades), clrWhite, 8);
UpdateLabel("DB_PnL", 20, 190, "Daily PnL: " + DoubleToString(DailyPnL, 2),
DailyPnL >= 0 ? clrLime : clrRed, 8);
string tradingStatus = TradingEnabled ? "ENABLED" : "DISABLED";
color statusColor = TradingEnabled ? clrLime : clrRed;
UpdateLabel("DB_Status", 20, 215, "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, 235, "Zone: " + nearZone, zoneColor, 8);
string ticksText = "Ticks: " + IntegerToString(CurrentBarUpTicks) + "/" + IntegerToString(CurrentBarDownTicks);
UpdateLabel("DB_Ticks", 20, 255, 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() {
DynamicFuturePrice = StringToDouble(ObjectGetString(chart_id, "CP_FuturePrice", OBJPROP_TEXT));
DynamicCallStrike1 = StringToDouble(ObjectGetString(chart_id, "CP_CallStrike1", OBJPROP_TEXT));
DynamicCallStrike2 = StringToDouble(ObjectGetString(chart_id, "CP_CallStrike2", OBJPROP_TEXT));
DynamicCallStrike3 = StringToDouble(ObjectGetString(chart_id, "CP_CallStrike3", OBJPROP_TEXT));
DynamicPutStrike1 = StringToDouble(ObjectGetString(chart_id, "CP_PutStrike1", OBJPROP_TEXT));
DynamicPutStrike2 = StringToDouble(ObjectGetString(chart_id, "CP_PutStrike2", OBJPROP_TEXT));
DynamicPutStrike3 = StringToDouble(ObjectGetString(chart_id, "CP_PutStrike3", OBJPROP_TEXT));
}