Init SimpleFVG stradegy

This commit is contained in:
Bell
2026-03-11 16:06:24 +07:00
parent d971f23f25
commit 123b0f3644
5 changed files with 759 additions and 754 deletions
+44 -754
View File
@@ -1,777 +1,67 @@
//+------------------------------------------------------------------+
//| SimpleFVG EA Pure Fair Value Gap Strategy |
//| |
//| Logic: |
//| 1. Scan 3-candle pattern: gap between High[i+2] and Low[i] |
//| 2. Bullish FVG: High(candle1) < Low(candle3) + strong candle2 |
//| 3. Place Buy Limit at FVG upper edge (Low of candle3) |
//| 4. SL = Low(candle1), TP = entry + 2R |
//| 5. Invalidation: close below FVG or timeout |
//| |
//| Bearish FVG: mirror logic |
//| SimpleFVG.mq5 Fair Value Gap Visual Strategy (Test mode) |
//| |
//| Modules: |
//| Config.mqh → Step 0: Inputs, enums, constants |
//| Trend.mqh → Step 1: EMA34/EMA89 trend detection |
//| FVG.mqh → Step 2: FVG scanning & management |
//| Drawing.mqh → Step 4: Chart visualization |
//+------------------------------------------------------------------+
#property copyright "SimpleFVG"
#property version "1.00"
#property version "2.00"
#property description "FVG strategy visualizer EMA trend + FVG zones on chart"
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1;
input double InpRiskPercent = 1.0;
input double InpRiskReward = 2.0;
input double InpFVGMinBodyPct = 50.0; // body% tối thiểu của nến giữa
input int InpFVGMaxAgeBars = 20; // FVG chưa fill sau N bars → hủy
input int InpSLBufferPts = 20; // buffer thêm cho SL (points)
input double InpEntryDepthPct = 50.0; // entry tại X% chiều sâu FVG (0=edge, 50=mid, 100=đáy)
input int InpEMAPeriod = 50; // EMA trend filter trên TF chính
input ENUM_TIMEFRAMES InpHTF = PERIOD_D1; // Higher TF confirmation
input int InpHTFEMAPeriod = 50; // EMA period trên HTF
input bool InpTrailBE = true; // dời SL về entry khi đạt 1R profit
input long InpMagicNumber = 20260311;
input int InpSlippage = 5;
input bool InpDebugLog = true;
//--- Constants ---
const string PFX = "SFVG_";
const string PFX_ORDER = "SFVG_ORD_";
//--- EMA handles ---
int g_EMAHandle = INVALID_HANDLE;
int g_HTFEMAHandle = INVALID_HANDLE;
string g_EMAShortName = "";
string g_HTFEMAShortName = "";
//--- FVG record ---
struct FVGSlot
{
bool active;
int direction; // 1=bullish, -1=bearish
double high; // cạnh trên FVG
double low; // cạnh dưới FVG
double slPrice; // SL level (Low candle1 cho bull, High candle1 cho bear)
double entryPrice; // giá entry thực tế (midpoint hoặc edge tùy InpEntryDepthPct)
datetime createdTime;
int ageBars;
ulong ticket; // ticket lệnh limit đã đặt (0 = chưa đặt)
};
#define MAX_FVG 10
FVGSlot g_Slots[MAX_FVG];
int g_SlotCount = 0;
datetime g_LastBarTime = 0;
int g_LastDay = -1;
#include "SimpleFVG/Config.mqh"
#include "SimpleFVG/Trend.mqh"
#include "SimpleFVG/FVG.mqh"
#include "SimpleFVG/Drawing.mqh"
//+------------------------------------------------------------------+
//| HELPER FUNCTIONS |
//| OnInit |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime t = iTime(_Symbol, InpTimeframe, 0);
if (t == g_LastBarTime) return false;
g_LastBarTime = t;
return true;
}
double GetEMA(int shift)
{
double buf[1];
if (g_EMAHandle == INVALID_HANDLE) return 0;
if (CopyBuffer(g_EMAHandle, 0, shift, 1, buf) < 1) return 0;
return buf[0];
}
double GetHTFEMA(int shift)
{
double buf[1];
if (g_HTFEMAHandle == INVALID_HANDLE) return 0;
if (CopyBuffer(g_HTFEMAHandle, 0, shift, 1, buf) < 1) return 0;
return buf[0];
}
/** Kiểm tra cả 2 EMA (TF chính + HTF) đều đồng thuận hướng dir. */
int GetTrendDirection()
{
double ema = GetEMA(1);
double htf = GetHTFEMA(1);
double cls = iClose(_Symbol, InpTimeframe, 1);
double hCls = iClose(_Symbol, InpHTF, 1);
if (ema == 0 || htf == 0) return 0;
bool tfBull = cls > ema;
bool htfBull = hCls > htf;
bool tfBear = cls < ema;
bool htfBear = hCls < htf;
if (tfBull && htfBull) return 1;
if (tfBear && htfBear) return -1;
return 0;
}
bool IsCandleStrong(int shift)
{
double h = iHigh (_Symbol, InpTimeframe, shift);
double l = iLow (_Symbol, InpTimeframe, shift);
double o = iOpen (_Symbol, InpTimeframe, shift);
double c = iClose(_Symbol, InpTimeframe, shift);
double range = h - l;
if (range < _Point) return false;
return (MathAbs(c - o) / range * 100.0) >= InpFVGMinBodyPct;
}
double CalcLot(double entry, double sl)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * InpRiskPercent / 100.0;
double slDist = MathAbs(entry - sl) / _Point;
if (slDist < 1) return 0;
double tickVal = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if (tickVal <= 0 || tickSize <= 0) return 0;
double pipVal = tickVal * (_Point / tickSize);
double lot = riskMoney / (slDist * pipVal);
double minL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if (step <= 0) step = 0.01;
lot = MathFloor(lot / step) * step;
lot = MathMax(minL, MathMin(maxL, lot));
return NormalizeDouble(lot, 2);
}
bool HasPendingOrPosition()
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if (ticket > 0
&& OrderGetInteger(ORDER_MAGIC) == InpMagicNumber
&& OrderGetString(ORDER_SYMBOL) == _Symbol)
return true;
}
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if (ticket > 0
&& PositionGetInteger(POSITION_MAGIC) == InpMagicNumber
&& PositionGetString(POSITION_SYMBOL) == _Symbol)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| FVG SCANNING |
//+------------------------------------------------------------------+
void ScanFVGs()
{
int trendDir = GetTrendDirection();
int maxBar = MathMin(InpFVGMaxAgeBars + 5, Bars(_Symbol, InpTimeframe) - 3);
for (int i = 1; i <= maxBar; i++)
{
double c1High = iHigh (_Symbol, InpTimeframe, i + 2);
double c1Low = iLow (_Symbol, InpTimeframe, i + 2);
double c2Open = iOpen (_Symbol, InpTimeframe, i + 1);
double c2Close= iClose(_Symbol, InpTimeframe, i + 1);
double c3High = iHigh (_Symbol, InpTimeframe, i);
double c3Low = iLow (_Symbol, InpTimeframe, i);
datetime fvgTime = iTime(_Symbol, InpTimeframe, i);
bool alreadyExists = false;
for (int j = 0; j < g_SlotCount; j++)
if (g_Slots[j].active && g_Slots[j].createdTime == fvgTime)
{ alreadyExists = true; break; }
if (alreadyExists) continue;
// --- Bullish FVG: chỉ khi trend = +1 ---
if (trendDir >= 1 && c1High < c3Low
&& c2Close > c2Open && IsCandleStrong(i + 1))
{
if (g_SlotCount >= MAX_FVG) continue;
double fvgH = c3Low;
double fvgL = c1High;
double depth = (fvgH - fvgL) * InpEntryDepthPct / 100.0;
double entry = NormalizeDouble(fvgH - depth, _Digits);
ZeroMemory(g_Slots[g_SlotCount]);
g_Slots[g_SlotCount].active = true;
g_Slots[g_SlotCount].direction = 1;
g_Slots[g_SlotCount].high = fvgH;
g_Slots[g_SlotCount].low = fvgL;
g_Slots[g_SlotCount].slPrice = c1Low;
g_Slots[g_SlotCount].entryPrice = entry;
g_Slots[g_SlotCount].createdTime = fvgTime;
g_Slots[g_SlotCount].ageBars = 0;
g_Slots[g_SlotCount].ticket = 0;
g_SlotCount++;
if (InpDebugLog)
PrintFormat("[FVG+] BULL [%.5f%.5f] entry=%.5f SL=%.5f trend=%d | %s",
fvgL, fvgH, entry, c1Low, trendDir, TimeToString(fvgTime));
}
// --- Bearish FVG: chỉ khi trend = -1 ---
if (trendDir <= -1 && c1Low > c3High
&& c2Close < c2Open && IsCandleStrong(i + 1))
{
if (g_SlotCount >= MAX_FVG) continue;
double fvgH = c1Low;
double fvgL = c3High;
double depth = (fvgH - fvgL) * InpEntryDepthPct / 100.0;
double entry = NormalizeDouble(fvgL + depth, _Digits);
ZeroMemory(g_Slots[g_SlotCount]);
g_Slots[g_SlotCount].active = true;
g_Slots[g_SlotCount].direction = -1;
g_Slots[g_SlotCount].high = fvgH;
g_Slots[g_SlotCount].low = fvgL;
g_Slots[g_SlotCount].slPrice = c1High;
g_Slots[g_SlotCount].entryPrice = entry;
g_Slots[g_SlotCount].createdTime = fvgTime;
g_Slots[g_SlotCount].ageBars = 0;
g_Slots[g_SlotCount].ticket = 0;
g_SlotCount++;
if (InpDebugLog)
PrintFormat("[FVG+] BEAR [%.5f%.5f] entry=%.5f SL=%.5f trend=%d | %s",
fvgL, fvgH, entry, c1High, trendDir, TimeToString(fvgTime));
}
}
}
//+------------------------------------------------------------------+
//| ORDER MANAGEMENT |
//+------------------------------------------------------------------+
void PlaceLimitForSlot(int idx)
{
if (g_Slots[idx].ticket != 0) return;
if (HasPendingOrPosition()) return;
double entry, sl, tp;
ENUM_ORDER_TYPE orderType;
if (g_Slots[idx].direction > 0)
{
entry = NormalizeDouble(g_Slots[idx].entryPrice, _Digits);
sl = NormalizeDouble(g_Slots[idx].slPrice - InpSLBufferPts * _Point, _Digits);
double rD = entry - sl;
if (rD <= 0) return;
tp = NormalizeDouble(entry + InpRiskReward * rD, _Digits);
orderType = ORDER_TYPE_BUY_LIMIT;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if (entry >= ask) return;
}
else
{
entry = NormalizeDouble(g_Slots[idx].entryPrice, _Digits);
sl = NormalizeDouble(g_Slots[idx].slPrice + InpSLBufferPts * _Point, _Digits);
double rD = sl - entry;
if (rD <= 0) return;
tp = NormalizeDouble(entry - InpRiskReward * rD, _Digits);
orderType = ORDER_TYPE_SELL_LIMIT;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if (entry <= bid) return;
}
double lot = CalcLot(entry, sl);
if (lot <= 0) return;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_PENDING;
req.symbol = _Symbol;
req.volume = lot;
req.type = orderType;
req.price = entry;
req.sl = sl;
req.tp = tp;
req.deviation = (ulong)InpSlippage;
req.magic = InpMagicNumber;
req.comment = StringFormat("FVG_%s", TimeToString(g_Slots[idx].createdTime, TIME_DATE));
req.type_filling = ORDER_FILLING_RETURN;
req.type_time = ORDER_TIME_GTC;
if (!OrderSend(req, res))
{
PrintFormat("[ORDER] ❌ retcode=%u", res.retcode);
return;
}
if (res.retcode != TRADE_RETCODE_DONE && res.retcode != TRADE_RETCODE_PLACED)
{
PrintFormat("[ORDER] ❌ rejected %u: %s", res.retcode, res.comment);
return;
}
g_Slots[idx].ticket = res.order;
PrintFormat("[ORDER] ✅ %s #%llu | %.2f @ %.5f SL=%.5f TP=%.5f",
(g_Slots[idx].direction > 0) ? "BUY_LIM" : "SELL_LIM",
res.order, lot, entry, sl, tp);
}
bool IsTicketStillPending(ulong ticket)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
if (OrderGetTicket(i) == ticket) return true;
return false;
}
void CancelOrder(ulong ticket)
{
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_REMOVE;
req.order = ticket;
if (OrderSend(req, res))
{
if (InpDebugLog)
PrintFormat("[CANCEL] #%llu removed", ticket);
}
}
//+------------------------------------------------------------------+
//| TRAILING SL (BREAKEVEN) |
//+------------------------------------------------------------------+
/** Dời SL về entry khi profit đạt 1R. Gọi mỗi tick. */
void TrailBreakeven()
{
if (!InpTrailBE) return;
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if (ticket == 0) continue;
if (PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double curSL = PositionGetDouble(POSITION_SL);
double curTP = PositionGetDouble(POSITION_TP);
bool isBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
double slDist = MathAbs(openPrice - curSL);
if (slDist < _Point) continue;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
bool alreadyBE = isBuy ? (curSL >= openPrice) : (curSL <= openPrice);
if (alreadyBE) continue;
double profit1R = isBuy ? (openPrice + slDist) : (openPrice - slDist);
bool reached = isBuy ? (bid >= profit1R) : (ask <= profit1R);
if (!reached) continue;
double newSL = NormalizeDouble(openPrice + (isBuy ? 1 : -1) * _Point, _Digits);
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.symbol = _Symbol;
req.sl = newSL;
req.tp = curTP;
if (OrderSend(req, res))
{
if (InpDebugLog)
PrintFormat("[TRAIL] #%llu SL moved to breakeven %.5f", ticket, newSL);
}
}
}
//+------------------------------------------------------------------+
//| DAILY RESET |
//+------------------------------------------------------------------+
/** Hủy tất cả pending limits + slots khi sang ngày mới. */
void DailyReset()
{
MqlDateTime now;
TimeCurrent(now);
if (now.day == g_LastDay) return;
g_LastDay = now.day;
int cancelled = 0;
for (int i = g_SlotCount - 1; i >= 0; i--)
{
if (!g_Slots[i].active) continue;
if (g_Slots[i].ticket != 0 && IsTicketStillPending(g_Slots[i].ticket))
{
CancelOrder(g_Slots[i].ticket);
cancelled++;
}
g_Slots[i].active = false;
}
int writeIdx = 0;
for (int i = 0; i < g_SlotCount; i++)
{
if (g_Slots[i].active)
{
if (writeIdx != i) g_Slots[writeIdx] = g_Slots[i];
writeIdx++;
}
}
g_SlotCount = writeIdx;
if (InpDebugLog && cancelled > 0)
PrintFormat("[DAILY] New day %04d.%02d.%02d → cancelled %d pending orders, cleared slots",
now.year, now.mon, now.day, cancelled);
}
//+------------------------------------------------------------------+
//| SLOT UPDATE & INVALIDATION |
//+------------------------------------------------------------------+
void UpdateSlots()
{
double lastClose = iClose(_Symbol, InpTimeframe, 1);
for (int i = g_SlotCount - 1; i >= 0; i--)
{
if (!g_Slots[i].active) continue;
g_Slots[i].ageBars++;
// Invalidation 1: close hoàn toàn bên ngoài FVG → hủy
bool invalidated = false;
if (g_Slots[i].direction > 0 && lastClose < g_Slots[i].low)
invalidated = true;
if (g_Slots[i].direction < 0 && lastClose > g_Slots[i].high)
invalidated = true;
// Invalidation 2: timeout
if (g_Slots[i].ageBars > InpFVGMaxAgeBars)
invalidated = true;
// Invalidation 3: limit đã fill → position mở → slot hoàn thành
if (g_Slots[i].ticket != 0 && !IsTicketStillPending(g_Slots[i].ticket))
{
g_Slots[i].active = false;
if (InpDebugLog)
PrintFormat("[SLOT] #%llu filled or gone → deactivate", g_Slots[i].ticket);
continue;
}
if (invalidated)
{
if (g_Slots[i].ticket != 0 && IsTicketStillPending(g_Slots[i].ticket))
CancelOrder(g_Slots[i].ticket);
g_Slots[i].active = false;
if (InpDebugLog)
PrintFormat("[SLOT] %s FVG [%.5f%.5f] invalidated | age=%d",
(g_Slots[i].direction > 0) ? "BULL" : "BEAR",
g_Slots[i].low, g_Slots[i].high, g_Slots[i].ageBars);
continue;
}
// Chưa đặt lệnh → đặt limit
if (g_Slots[i].ticket == 0)
PlaceLimitForSlot(i);
}
// Dọn slots không active
int writeIdx = 0;
for (int i = 0; i < g_SlotCount; i++)
{
if (g_Slots[i].active)
{
if (writeIdx != i) g_Slots[writeIdx] = g_Slots[i];
writeIdx++;
}
}
g_SlotCount = writeIdx;
}
//+------------------------------------------------------------------+
//| DRAWING |
//+------------------------------------------------------------------+
void SetRect(string name, datetime t1, double p1, datetime t2, double p2, color clr)
{
if (ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FILL, true);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
ObjectMove(0, name, 0, t1, p1);
ObjectMove(0, name, 1, t2, p2);
}
void SetHLine(string name, datetime t1, double p, datetime t2, color clr, int style, int width)
{
if (ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_STYLE, style);
ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
ObjectMove(0, name, 0, t1, p);
ObjectMove(0, name, 1, t2, p);
}
void SetLabel(string name, int x, int y, string txt, color clr, int fontSize)
{
if (ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetString (0, name, OBJPROP_TEXT, txt);
}
void SetText(string name, datetime t, double p, string txt, color clr, int fontSize, int anchor)
{
if (ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_TEXT, 0, t, p);
ObjectMove(0, name, 0, t, p);
ObjectSetString (0, name, OBJPROP_TEXT, txt);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
}
void DrawFVGZones()
{
ObjectsDeleteAll(0, PFX + "FVG_");
datetime now = iTime(_Symbol, InpTimeframe, 0);
for (int i = 0; i < g_SlotCount; i++)
{
if (!g_Slots[i].active) continue;
string sid = IntegerToString(i);
bool isBull = (g_Slots[i].direction > 0);
color zClr = isBull ? C'0,60,120' : C'100,25,0';
color bClr = isBull ? C'0,120,200' : C'200,60,0';
SetRect(PFX + "FVG_Z_" + sid,
g_Slots[i].createdTime, g_Slots[i].high,
now, g_Slots[i].low, zClr);
SetHLine(PFX + "FVG_E_" + sid,
g_Slots[i].createdTime, g_Slots[i].entryPrice, now,
C'200,200,0', STYLE_DOT, 1);
SetText(PFX + "FVG_L_" + sid,
g_Slots[i].createdTime, g_Slots[i].high,
StringFormat("FVG %s [%.3f%.3f] ent=%.3f",
isBull ? "▲" : "▼", g_Slots[i].low, g_Slots[i].high, g_Slots[i].entryPrice),
bClr, 7, ANCHOR_LEFT_LOWER);
}
}
void DrawOrderVisual()
{
string entN = PFX_ORDER + "ENT";
string slN = PFX_ORDER + "SLL";
string tpN = PFX_ORDER + "TPL";
string tpZ = PFX_ORDER + "TPZ";
string slZ = PFX_ORDER + "SLZ";
string eL = PFX_ORDER + "ELBL";
string sL = PFX_ORDER + "SLBL";
string tL = PFX_ORDER + "TPLBL";
// Tìm slot có lệnh pending
int activeIdx = -1;
for (int i = 0; i < g_SlotCount; i++)
{
if (g_Slots[i].active && g_Slots[i].ticket != 0)
{ activeIdx = i; break; }
}
if (activeIdx < 0)
{
ObjectDelete(0, entN); ObjectDelete(0, slN); ObjectDelete(0, tpN);
ObjectDelete(0, tpZ); ObjectDelete(0, slZ);
ObjectDelete(0, eL); ObjectDelete(0, sL); ObjectDelete(0, tL);
return;
}
bool isBull = (g_Slots[activeIdx].direction > 0);
double entry, sl, tp;
if (isBull)
{
entry = g_Slots[activeIdx].entryPrice;
sl = NormalizeDouble(g_Slots[activeIdx].slPrice - InpSLBufferPts * _Point, _Digits);
double rD = entry - sl;
if (rD <= 0) return;
tp = NormalizeDouble(entry + InpRiskReward * rD, _Digits);
}
else
{
entry = g_Slots[activeIdx].entryPrice;
sl = NormalizeDouble(g_Slots[activeIdx].slPrice + InpSLBufferPts * _Point, _Digits);
double rD = sl - entry;
if (rD <= 0) return;
tp = NormalizeDouble(entry - InpRiskReward * rD, _Digits);
}
datetime tStart = g_Slots[activeIdx].createdTime;
datetime tEnd = iTime(_Symbol, InpTimeframe, 0) + PeriodSeconds(InpTimeframe) * 8;
color entClr = isBull ? C'33,150,243' : C'255,152,0';
color tpClr = C'38,166,91';
color slClr = C'229,57,53';
double tpTop = isBull ? tp : entry;
double tpBot = isBull ? entry : tp;
SetRect(tpZ, tStart, tpTop, tEnd, tpBot, C'15,60,30');
double slTop = isBull ? entry : sl;
double slBot = isBull ? sl : entry;
SetRect(slZ, tStart, slTop, tEnd, slBot, C'75,12,12');
SetHLine(entN, tStart, entry, tEnd, entClr, STYLE_SOLID, 2);
SetHLine(tpN, tStart, tp, tEnd, tpClr, STYLE_DASH, 1);
SetHLine(slN, tStart, sl, tEnd, slClr, STYLE_DASH, 1);
datetime lblT = tEnd + PeriodSeconds(InpTimeframe);
double slPips = MathAbs(entry - sl) / _Point;
double tpPips = MathAbs(tp - entry) / _Point;
double rr = (slPips > 0) ? tpPips / slPips : 0;
double lot = CalcLot(entry, sl);
SetText(eL, lblT, entry,
StringFormat("%s %.3f | %.2f lot", isBull ? "BUY LIM" : "SELL LIM", entry, lot),
entClr, 8, ANCHOR_LEFT);
SetText(tL, lblT, tp,
StringFormat("TP %.3f | +%.0fp (%.1fR)", tp, tpPips, rr),
tpClr, 7, isBull ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
SetText(sL, lblT, sl,
StringFormat("SL %.3f | -%.0fp", sl, slPips),
slClr, 7, isBull ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER);
}
void DrawDebugPanel()
{
int pending = 0, total = 0;
for (int i = 0; i < g_SlotCount; i++)
if (g_Slots[i].active) { total++; if (g_Slots[i].ticket != 0) pending++; }
bool inPos = false;
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if (t > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber
&& PositionGetString(POSITION_SYMBOL) == _Symbol)
{ inPos = true; break; }
}
int trendDir = GetTrendDirection();
string trendTxt = (trendDir > 0) ? "BULL" : (trendDir < 0) ? "BEAR" : "NEUTRAL";
color trendClr = (trendDir > 0) ? clrLime : (trendDir < 0) ? clrTomato : clrGray;
double ema = GetEMA(1);
double htfEma = GetHTFEMA(1);
SetLabel(PFX + "HDR", 10, 20,
StringFormat("SimpleFVG v2 | %s | Slots=%d Pend=%d | Trail=%s",
EnumToString(InpTimeframe), total, pending, InpTrailBE ? "ON" : "OFF"),
clrSilver, 9);
SetLabel(PFX + "TRD", 10, 44,
StringFormat("Trend: %s | EMA%.0f=%.3f | HTF EMA%.0f=%.3f",
trendTxt, (double)InpEMAPeriod, ema, (double)InpHTFEMAPeriod, htfEma),
trendClr, 9);
color sClr = inPos ? clrLime : (pending > 0) ? clrOrange : clrGray;
string sTxt = inPos ? "IN TRADE" : (pending > 0) ? "LIMIT PLACED" : "SCANNING";
SetLabel(PFX + "ST", 10, 68, StringFormat("State: %s", sTxt), sClr, 9);
}
void DrawAll()
{
DrawFVGZones();
DrawOrderVisual();
DrawDebugPanel();
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| EVENT HANDLERS |
//+------------------------------------------------------------------+
int OnInit()
{
g_EMAHandle = iMA(_Symbol, InpTimeframe, InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if (g_EMAHandle == INVALID_HANDLE)
{
Print("[INIT] Failed to create EMA handle");
return INIT_FAILED;
}
ChartIndicatorAdd(0, 0, g_EMAHandle);
int n1 = ChartIndicatorsTotal(0, 0);
if (n1 > 0) g_EMAShortName = ChartIndicatorName(0, 0, n1 - 1);
//--- Step 1: Initialize trend (EMA handles + chart indicators) ---
if(!TrendInit())
return INIT_FAILED;
g_HTFEMAHandle = iMA(_Symbol, InpHTF, InpHTFEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if (g_HTFEMAHandle == INVALID_HANDLE)
{
Print("[INIT] Failed to create HTF EMA handle");
return INIT_FAILED;
}
//--- Step 2: Initialize FVG array ---
FVGInit();
for (int i = 0; i < MAX_FVG; i++) ZeroMemory(g_Slots[i]);
g_SlotCount = 0;
g_LastBarTime = 0;
//--- Initial scan on existing bars ---
TrendUpdate();
FVGUpdate();
ScanFVGs();
DrawAll();
//--- Step 4: Draw everything ---
DrawAll();
if (InpDebugLog)
PrintFormat("[INIT] SimpleFVG v2 | TF=%s HTF=%s | EMA=%d/%d | R=%.1f%% RR=%.1f | DepthPct=%.0f%% Trail=%s",
EnumToString(InpTimeframe), EnumToString(InpHTF),
InpEMAPeriod, InpHTFEMAPeriod,
InpRiskPercent, InpRiskReward,
InpEntryDepthPct, InpTrailBE ? "ON" : "OFF");
return INIT_SUCCEEDED;
if(InpDebugLog)
PrintFormat("[INIT] SimpleFVG v2.0 | %s %s | EMA%d/%d | FVG lookback=%d maxAge=%d",
GetTradeSymbol(), EnumToString(InpTimeframe),
InpEMAFastPeriod, InpEMASlowPeriod,
InpFVGLookbackBars, InpFVGMaxAgeBars);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if (g_EMAShortName != "")
ChartIndicatorDelete(0, 0, g_EMAShortName);
if (g_EMAHandle != INVALID_HANDLE)
IndicatorRelease(g_EMAHandle);
if (g_HTFEMAHandle != INVALID_HANDLE)
IndicatorRelease(g_HTFEMAHandle);
ObjectsDeleteAll(0, PFX);
ObjectsDeleteAll(0, PFX_ORDER);
ChartRedraw(0);
TrendDeinit();
DrawCleanup();
}
//+------------------------------------------------------------------+
//| OnTick |
//+------------------------------------------------------------------+
void OnTick()
{
DailyReset();
TrailBreakeven();
if(!IsNewBar())
return;
if (IsNewBar())
{
ScanFVGs();
UpdateSlots();
}
DrawAll();
TrendUpdate();
FVGUpdate();
DrawAll();
}
+86
View File
@@ -0,0 +1,86 @@
//+------------------------------------------------------------------+
//| Config.mqh Step 0: Inputs, Enums, Constants |
//| Cho phép cấu hình symbol, timeframe, EMA, FVG, drawing |
//+------------------------------------------------------------------+
#ifndef __SIMPLE_FVG_CONFIG_MQH__
#define __SIMPLE_FVG_CONFIG_MQH__
//--- Step 0: Asset & Timeframe ---
input string InpSymbol = ""; // Symbol (empty = current chart)
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; // Analysis Timeframe
//--- Step 1: Trend (EMA) ---
input int InpEMAFastPeriod = 34; // EMA Fast Period
input int InpEMASlowPeriod = 89; // EMA Slow Period
//--- Step 2: FVG Detection ---
input int InpFVGLookbackBars = 100; // FVG: Lookback range (bars)
input int InpFVGMaxAgeBars = 50; // FVG: Max age before expiry (bars)
input double InpFVGMinBodyPct = 50.0; // FVG: Min body % of impulse candle
input double InpFVGMinSizePoints = 0; // FVG: Min gap size (points, 0=any)
//--- Step 4: Drawing ---
input color InpColorBullFVG = C'30,80,140'; // Color: Bullish FVG zone
input color InpColorBearFVG = C'140,30,20'; // Color: Bearish FVG zone
input color InpColorMitigatedFVG = C'60,60,60'; // Color: Mitigated FVG zone
input color InpColorEMAFast = clrDodgerBlue; // Color: EMA Fast line
input color InpColorEMASlow = clrOrangeRed; // Color: EMA Slow line
input bool InpShowPanel = true; // Draw: Show info panel
input bool InpShowFVGLabels = true; // Draw: Show FVG labels
//--- Debug ---
input bool InpDebugLog = true; // Debug: Enable logging
//+------------------------------------------------------------------+
//| Enums |
//+------------------------------------------------------------------+
enum ENUM_TREND_DIRECTION
{
TREND_BULLISH = 1,
TREND_BEARISH = -1,
TREND_NEUTRAL = 0
};
enum ENUM_FVG_TYPE
{
FVG_BULLISH = 1,
FVG_BEARISH = -1
};
//+------------------------------------------------------------------+
//| Constants |
//+------------------------------------------------------------------+
const string EA_PREFIX = "SFVG_";
const int MAX_FVG_SLOTS = 50;
//+------------------------------------------------------------------+
//| Resolved symbol (cached) |
//+------------------------------------------------------------------+
string g_ResolvedSymbol = "";
string GetTradeSymbol()
{
if(g_ResolvedSymbol != "")
return g_ResolvedSymbol;
g_ResolvedSymbol = (InpSymbol == "" || InpSymbol == "current")
? _Symbol
: InpSymbol;
return g_ResolvedSymbol;
}
//+------------------------------------------------------------------+
//| New-bar detector |
//+------------------------------------------------------------------+
datetime g_PreviousBarTime = 0;
bool IsNewBar()
{
datetime currentBarTime = iTime(GetTradeSymbol(), InpTimeframe, 0);
if(currentBarTime == g_PreviousBarTime)
return false;
g_PreviousBarTime = currentBarTime;
return true;
}
#endif
+205
View File
@@ -0,0 +1,205 @@
//+------------------------------------------------------------------+
//| Drawing.mqh Step 4: Visualize FVG zones, EMA info, trend panel |
//+------------------------------------------------------------------+
#ifndef __SIMPLE_FVG_DRAWING_MQH__
#define __SIMPLE_FVG_DRAWING_MQH__
#include "Config.mqh"
#include "Trend.mqh"
#include "FVG.mqh"
//+------------------------------------------------------------------+
//| Object-creation helpers |
//+------------------------------------------------------------------+
void DrawRectangle(string name,
datetime timeStart, double priceTop,
datetime timeEnd, double priceBot,
color clr, bool filled = true)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_RECTANGLE, 0, timeStart, priceTop, timeEnd, priceBot);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FILL, filled);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectMove(0, name, 0, timeStart, priceTop);
ObjectMove(0, name, 1, timeEnd, priceBot);
}
void DrawTrendLine(string name,
datetime t1, double p1,
datetime t2, double p2,
color clr, int style, int width)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_STYLE, style);
ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectMove(0, name, 0, t1, p1);
ObjectMove(0, name, 1, t2, p2);
}
void DrawLabel(string name, int xDist, int yDist,
string text, color clr, int fontSize)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDist);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDist);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetString (0, name, OBJPROP_FONT, "Consolas");
ObjectSetString (0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}
void DrawTextOnChart(string name, datetime t, double price,
string text, color clr, int fontSize, int anchor)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_TEXT, 0, t, price);
ObjectMove(0, name, 0, t, price);
ObjectSetString (0, name, OBJPROP_TEXT, text);
ObjectSetString (0, name, OBJPROP_FONT, "Arial");
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}
//+------------------------------------------------------------------+
//| Draw all active FVG zones as rectangles on the chart |
//+------------------------------------------------------------------+
void DrawFVGZones()
{
ObjectsDeleteAll(0, EA_PREFIX + "FVG_");
string symbol = GetTradeSymbol();
datetime currentBar = iTime(symbol, InpTimeframe, 0);
for(int i = 0; i < g_FVGCount; i++)
{
if(!g_FVGZones[i].isActive)
continue;
string zoneId = IntegerToString(i);
//--- Zone color depends on type and mitigation status ---
color zoneColor;
if(g_FVGZones[i].isMitigated)
zoneColor = InpColorMitigatedFVG;
else if(g_FVGZones[i].type == FVG_BULLISH)
zoneColor = InpColorBullFVG;
else
zoneColor = InpColorBearFVG;
//--- Draw filled rectangle for the FVG zone ---
DrawRectangle(
EA_PREFIX + "FVG_ZONE_" + zoneId,
g_FVGZones[i].createdTime, g_FVGZones[i].upperEdge,
currentBar, g_FVGZones[i].lowerEdge,
zoneColor, true
);
//--- Draw mid-line (50% of FVG) ---
double midPrice = (g_FVGZones[i].upperEdge + g_FVGZones[i].lowerEdge) / 2.0;
color midColor = g_FVGZones[i].isMitigated ? clrDimGray : clrGold;
DrawTrendLine(
EA_PREFIX + "FVG_MID_" + zoneId,
g_FVGZones[i].createdTime, midPrice,
currentBar, midPrice,
midColor, STYLE_DOT, 1
);
//--- Draw text label ---
if(InpShowFVGLabels)
{
string typeStr = (g_FVGZones[i].type == FVG_BULLISH) ? "BULL" : "BEAR";
string arrow = (g_FVGZones[i].type == FVG_BULLISH) ? "▲" : "▼";
string mitigStr = g_FVGZones[i].isMitigated ? " [MITIGATED]" : "";
color labelColor;
if(g_FVGZones[i].isMitigated)
labelColor = clrDimGray;
else if(g_FVGZones[i].type == FVG_BULLISH)
labelColor = clrDeepSkyBlue;
else
labelColor = clrOrangeRed;
DrawTextOnChart(
EA_PREFIX + "FVG_LBL_" + zoneId,
g_FVGZones[i].createdTime,
g_FVGZones[i].upperEdge,
StringFormat("%s %s [%.2f%.2f]%s",
arrow, typeStr,
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge,
mitigStr),
labelColor, 7, ANCHOR_LEFT_LOWER
);
}
}
}
//+------------------------------------------------------------------+
//| Draw info panel (top-left corner) |
//+------------------------------------------------------------------+
void DrawInfoPanel()
{
if(!InpShowPanel) return;
int bullCount = CountActiveFVGs(FVG_BULLISH);
int bearCount = CountActiveFVGs(FVG_BEARISH);
int mitigCount = CountMitigatedFVGs();
string trendText = TrendToString(g_CurrentTrend);
color trendColor = TrendToColor(g_CurrentTrend);
//--- Row 1: EA header ---
DrawLabel(EA_PREFIX + "PNL_HDR", 12, 22,
StringFormat("SimpleFVG | %s %s",
GetTradeSymbol(), EnumToString(InpTimeframe)),
clrSilver, 10);
//--- Row 2: Trend status ---
DrawLabel(EA_PREFIX + "PNL_TREND", 12, 44,
StringFormat("Trend: %s | EMA%d = %.2f | EMA%d = %.2f",
trendText,
InpEMAFastPeriod, g_EMAFastValue,
InpEMASlowPeriod, g_EMASlowValue),
trendColor, 9);
//--- Row 3: FVG counts ---
DrawLabel(EA_PREFIX + "PNL_FVG", 12, 66,
StringFormat("FVG Active: %d Bull + %d Bear | Mitigated: %d",
bullCount, bearCount, mitigCount),
clrWheat, 9);
}
//+------------------------------------------------------------------+
//| Master draw: call all draw functions |
//+------------------------------------------------------------------+
void DrawAll()
{
DrawFVGZones();
DrawInfoPanel();
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Clean up all drawn objects |
//+------------------------------------------------------------------+
void DrawCleanup()
{
ObjectsDeleteAll(0, EA_PREFIX);
ChartRedraw(0);
}
#endif
+277
View File
@@ -0,0 +1,277 @@
//+------------------------------------------------------------------+
//| FVG.mqh Step 2: Fair Value Gap detection & management |
//| |
//| 3-candle pattern: |
//| CandleA (oldest) ── CandleB (impulse) ── CandleC (newest) |
//| |
//| Bullish FVG: CandleA.High < CandleC.Low → gap UP |
//| zone = [CandleA.High .. CandleC.Low] |
//| CandleB must be bullish with strong body |
//| |
//| Bearish FVG: CandleA.Low > CandleC.High → gap DOWN |
//| zone = [CandleC.High .. CandleA.Low] |
//| CandleB must be bearish with strong body |
//+------------------------------------------------------------------+
#ifndef __SIMPLE_FVG_FVG_MQH__
#define __SIMPLE_FVG_FVG_MQH__
#include "Config.mqh"
//+------------------------------------------------------------------+
//| FVG data structure |
//+------------------------------------------------------------------+
struct FVGZone
{
bool isActive;
ENUM_FVG_TYPE type;
double upperEdge;
double lowerEdge;
datetime createdTime;
int ageInBars;
bool isMitigated;
};
//+------------------------------------------------------------------+
//| Module state |
//+------------------------------------------------------------------+
FVGZone g_FVGZones[];
int g_FVGCount = 0;
//+------------------------------------------------------------------+
//| Init: allocate array |
//+------------------------------------------------------------------+
void FVGInit()
{
ArrayResize(g_FVGZones, MAX_FVG_SLOTS);
for(int i = 0; i < MAX_FVG_SLOTS; i++)
ZeroMemory(g_FVGZones[i]);
g_FVGCount = 0;
}
//+------------------------------------------------------------------+
//| Check if impulse candle has strong body (body/range >= threshold) |
//+------------------------------------------------------------------+
bool IsImpulseCandleStrong(string symbol, ENUM_TIMEFRAMES tf, int shift)
{
double high = iHigh(symbol, tf, shift);
double low = iLow(symbol, tf, shift);
double open = iOpen(symbol, tf, shift);
double close = iClose(symbol, tf, shift);
double totalRange = high - low;
if(totalRange < _Point)
return false;
double bodySize = MathAbs(close - open);
double bodyRatio = bodySize / totalRange * 100.0;
return (bodyRatio >= InpFVGMinBodyPct);
}
//+------------------------------------------------------------------+
//| Check if this FVG timestamp already exists in our array |
//+------------------------------------------------------------------+
bool FVGAlreadyTracked(datetime fvgTime, ENUM_FVG_TYPE fvgType)
{
for(int i = 0; i < g_FVGCount; i++)
{
if(g_FVGZones[i].isActive
&& g_FVGZones[i].createdTime == fvgTime
&& g_FVGZones[i].type == fvgType)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Add a new FVG zone to the array |
//+------------------------------------------------------------------+
bool AddFVGZone(ENUM_FVG_TYPE type, double upper, double lower, datetime time)
{
if(g_FVGCount >= MAX_FVG_SLOTS)
return false;
if(InpFVGMinSizePoints > 0)
{
double gapSizePoints = (upper - lower) / _Point;
if(gapSizePoints < InpFVGMinSizePoints)
return false;
}
ZeroMemory(g_FVGZones[g_FVGCount]);
g_FVGZones[g_FVGCount].isActive = true;
g_FVGZones[g_FVGCount].type = type;
g_FVGZones[g_FVGCount].upperEdge = upper;
g_FVGZones[g_FVGCount].lowerEdge = lower;
g_FVGZones[g_FVGCount].createdTime = time;
g_FVGZones[g_FVGCount].ageInBars = 0;
g_FVGZones[g_FVGCount].isMitigated = false;
g_FVGCount++;
if(InpDebugLog)
PrintFormat("[FVG] NEW %s [%.5f %.5f] at %s",
(type == FVG_BULLISH) ? "BULL" : "BEAR",
lower, upper, TimeToString(time));
return true;
}
//+------------------------------------------------------------------+
//| Scan for new FVG patterns across the lookback range |
//| Finds ALL FVGs regardless of current trend |
//+------------------------------------------------------------------+
void ScanForNewFVGs()
{
string symbol = GetTradeSymbol();
int totalBars = Bars(symbol, InpTimeframe);
int maxShift = MathMin(InpFVGLookbackBars, totalBars - 3);
for(int shift = 1; shift <= maxShift; shift++)
{
int shiftA = shift + 2; // oldest candle
int shiftB = shift + 1; // impulse (middle) candle
int shiftC = shift; // newest candle
double candleA_High = iHigh(symbol, InpTimeframe, shiftA);
double candleA_Low = iLow (symbol, InpTimeframe, shiftA);
double candleB_Open = iOpen (symbol, InpTimeframe, shiftB);
double candleB_Close = iClose(symbol, InpTimeframe, shiftB);
double candleC_High = iHigh(symbol, InpTimeframe, shiftC);
double candleC_Low = iLow (symbol, InpTimeframe, shiftC);
datetime fvgTime = iTime(symbol, InpTimeframe, shiftC);
//--- Bullish FVG: gap between A.High and C.Low ---
if(candleA_High < candleC_Low
&& candleB_Close > candleB_Open
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
{
if(!FVGAlreadyTracked(fvgTime, FVG_BULLISH))
AddFVGZone(FVG_BULLISH, candleC_Low, candleA_High, fvgTime);
}
//--- Bearish FVG: gap between C.High and A.Low ---
if(candleA_Low > candleC_High
&& candleB_Close < candleB_Open
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
{
if(!FVGAlreadyTracked(fvgTime, FVG_BEARISH))
AddFVGZone(FVG_BEARISH, candleA_Low, candleC_High, fvgTime);
}
}
}
//+------------------------------------------------------------------+
//| Check if price has mitigated (filled through) any active FVG |
//+------------------------------------------------------------------+
void CheckMitigationStatus()
{
string symbol = GetTradeSymbol();
for(int i = 0; i < g_FVGCount; i++)
{
if(!g_FVGZones[i].isActive || g_FVGZones[i].isMitigated)
continue;
double lastLow = iLow (symbol, InpTimeframe, 1);
double lastHigh = iHigh(symbol, InpTimeframe, 1);
// Bullish FVG mitigated: price drops through lower edge
if(g_FVGZones[i].type == FVG_BULLISH && lastLow <= g_FVGZones[i].lowerEdge)
{
g_FVGZones[i].isMitigated = true;
if(InpDebugLog)
PrintFormat("[FVG] MITIGATED BULL [%.5f %.5f]",
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
}
// Bearish FVG mitigated: price rises through upper edge
if(g_FVGZones[i].type == FVG_BEARISH && lastHigh >= g_FVGZones[i].upperEdge)
{
g_FVGZones[i].isMitigated = true;
if(InpDebugLog)
PrintFormat("[FVG] MITIGATED BEAR [%.5f %.5f]",
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
}
}
}
//+------------------------------------------------------------------+
//| Age out old FVGs beyond max age |
//+------------------------------------------------------------------+
void ExpireOldFVGs()
{
for(int i = 0; i < g_FVGCount; i++)
{
if(!g_FVGZones[i].isActive) continue;
g_FVGZones[i].ageInBars++;
if(g_FVGZones[i].ageInBars > InpFVGMaxAgeBars)
{
g_FVGZones[i].isActive = false;
if(InpDebugLog)
PrintFormat("[FVG] EXPIRED %s [%.5f %.5f] age=%d bars",
(g_FVGZones[i].type == FVG_BULLISH) ? "BULL" : "BEAR",
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge,
g_FVGZones[i].ageInBars);
}
}
}
//+------------------------------------------------------------------+
//| Remove inactive FVGs from array to free slots |
//+------------------------------------------------------------------+
void CompactFVGArray()
{
int writeIndex = 0;
for(int i = 0; i < g_FVGCount; i++)
{
if(g_FVGZones[i].isActive)
{
if(writeIndex != i)
g_FVGZones[writeIndex] = g_FVGZones[i];
writeIndex++;
}
}
g_FVGCount = writeIndex;
}
//+------------------------------------------------------------------+
//| Main update: mitigation → expiry → scan new → compact |
//+------------------------------------------------------------------+
void FVGUpdate()
{
CheckMitigationStatus();
ExpireOldFVGs();
ScanForNewFVGs();
CompactFVGArray();
}
//+------------------------------------------------------------------+
//| Count helpers for panel display |
//+------------------------------------------------------------------+
int CountActiveFVGs(ENUM_FVG_TYPE type)
{
int count = 0;
for(int i = 0; i < g_FVGCount; i++)
{
if(g_FVGZones[i].isActive && g_FVGZones[i].type == type)
count++;
}
return count;
}
int CountMitigatedFVGs()
{
int count = 0;
for(int i = 0; i < g_FVGCount; i++)
{
if(g_FVGZones[i].isActive && g_FVGZones[i].isMitigated)
count++;
}
return count;
}
#endif
+147
View File
@@ -0,0 +1,147 @@
//+------------------------------------------------------------------+
//| Trend.mqh Step 1: Trend detection via EMA34 & EMA89 |
//| |
//| Logic: |
//| BULLISH: EMA_fast > EMA_slow AND close > EMA_fast |
//| BEARISH: EMA_fast < EMA_slow AND close < EMA_fast |
//| NEUTRAL: otherwise (EMA crossing or price between EMAs) |
//+------------------------------------------------------------------+
#ifndef __SIMPLE_FVG_TREND_MQH__
#define __SIMPLE_FVG_TREND_MQH__
#include "Config.mqh"
//+------------------------------------------------------------------+
//| Module state |
//+------------------------------------------------------------------+
int g_HandleEMAFast = INVALID_HANDLE;
int g_HandleEMASlow = INVALID_HANDLE;
string g_NameEMAFast = "";
string g_NameEMASlow = "";
double g_EMAFastValue = 0.0;
double g_EMASlowValue = 0.0;
ENUM_TREND_DIRECTION g_CurrentTrend = TREND_NEUTRAL;
//+------------------------------------------------------------------+
//| Init: create EMA indicator handles and add to chart |
//+------------------------------------------------------------------+
bool TrendInit()
{
string symbol = GetTradeSymbol();
g_HandleEMAFast = iMA(symbol, InpTimeframe, InpEMAFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_HandleEMAFast == INVALID_HANDLE)
{
PrintFormat("[Trend] FAILED to create EMA%d handle", InpEMAFastPeriod);
return false;
}
g_HandleEMASlow = iMA(symbol, InpTimeframe, InpEMASlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_HandleEMASlow == INVALID_HANDLE)
{
PrintFormat("[Trend] FAILED to create EMA%d handle", InpEMASlowPeriod);
return false;
}
ChartIndicatorAdd(0, 0, g_HandleEMAFast);
int countAfterFast = ChartIndicatorsTotal(0, 0);
if(countAfterFast > 0)
g_NameEMAFast = ChartIndicatorName(0, 0, countAfterFast - 1);
ChartIndicatorAdd(0, 0, g_HandleEMASlow);
int countAfterSlow = ChartIndicatorsTotal(0, 0);
if(countAfterSlow > 0)
g_NameEMASlow = ChartIndicatorName(0, 0, countAfterSlow - 1);
if(InpDebugLog)
PrintFormat("[Trend] OK EMA%d + EMA%d on %s %s",
InpEMAFastPeriod, InpEMASlowPeriod,
symbol, EnumToString(InpTimeframe));
return true;
}
//+------------------------------------------------------------------+
//| Deinit: release handles and remove from chart |
//+------------------------------------------------------------------+
void TrendDeinit()
{
if(g_NameEMAFast != "")
ChartIndicatorDelete(0, 0, g_NameEMAFast);
if(g_NameEMASlow != "")
ChartIndicatorDelete(0, 0, g_NameEMASlow);
if(g_HandleEMAFast != INVALID_HANDLE)
{
IndicatorRelease(g_HandleEMAFast);
g_HandleEMAFast = INVALID_HANDLE;
}
if(g_HandleEMASlow != INVALID_HANDLE)
{
IndicatorRelease(g_HandleEMASlow);
g_HandleEMASlow = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
//| Read one EMA value at a given bar shift |
//+------------------------------------------------------------------+
double ReadEMA(int handle, int shift)
{
double buffer[1];
if(handle == INVALID_HANDLE) return 0.0;
if(CopyBuffer(handle, 0, shift, 1, buffer) < 1) return 0.0;
return buffer[0];
}
//+------------------------------------------------------------------+
//| Update trend direction (call once per new bar) |
//+------------------------------------------------------------------+
ENUM_TREND_DIRECTION TrendUpdate()
{
g_EMAFastValue = ReadEMA(g_HandleEMAFast, 1);
g_EMASlowValue = ReadEMA(g_HandleEMASlow, 1);
if(g_EMAFastValue == 0.0 || g_EMASlowValue == 0.0)
{
g_CurrentTrend = TREND_NEUTRAL;
return g_CurrentTrend;
}
double closePrice = iClose(GetTradeSymbol(), InpTimeframe, 1);
if(g_EMAFastValue > g_EMASlowValue && closePrice > g_EMAFastValue)
g_CurrentTrend = TREND_BULLISH;
else if(g_EMAFastValue < g_EMASlowValue && closePrice < g_EMAFastValue)
g_CurrentTrend = TREND_BEARISH;
else
g_CurrentTrend = TREND_NEUTRAL;
return g_CurrentTrend;
}
//+------------------------------------------------------------------+
//| Utility: convert trend enum to readable string |
//+------------------------------------------------------------------+
string TrendToString(ENUM_TREND_DIRECTION trend)
{
switch(trend)
{
case TREND_BULLISH: return "BULLISH";
case TREND_BEARISH: return "BEARISH";
default: return "NEUTRAL";
}
}
color TrendToColor(ENUM_TREND_DIRECTION trend)
{
switch(trend)
{
case TREND_BULLISH: return clrLime;
case TREND_BEARISH: return clrTomato;
default: return clrGray;
}
}
#endif