Files
Bell-PriceActionWithEma-EA/Experts/ICT.mq5
T
2025-12-21 05:19:14 +07:00

2978 lines
88 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| EA: ICT v2 |
//| Mục đích: Setup ICT đồng thuận trend 3 timeframes |
//| Người tạo: Bell CW |
//+------------------------------------------------------------------+
#property copyright "Bell CW"
#property version "2.00"
#property strict
// ------------------ Các input về định lượng ------------------
input int LS_LookbackBars = 5; // số nến nhìn lại
input double LS_Wick_ATR_Ratio = 0.1; // wick ≥ 10% ATR
input double MSS_Break_ATR_Ratio = 0.1; // phá ≥ 10% ATR
input double MSS_Body_ATR_Ratio = 0.1; // body ≥ 10% ATR
input double FVG_Min_ATR_Ratio = 0.25;
input int ADX_Min = 10;
// -------------------------------------------------------------
double DailyEquityStart = 0.0;
datetime DailyEquityDate = 0;
bool DailyLossBlocked = false;
input double EntryFvgRetrace = 0.3; // 0.5 = 50%, 0.618 = aggressive
input double MaxDailyLossPercent = 3.0;
// --- Only Entry logs (user requested) ---
bool PrintEntryLog = true; // nếu true -> in log chỉ liên quan tới entry
input double RishPercent = 1.0; // % vốn rủi ro cho mỗi lệnh
input double RiskRewardRatio = 6.0; // tỉ lệ R:R mặc định
input double numberOfRToMoveToEntry = 1; // Nomal moving SL: số R cần đạt để dời SL về entry (BE)
input int MaxLimitOrderTime = 180;
input double trailingStopStartR = 2; // Advanced moving SL: bắt đầu kích hoạt trailing
input double trailOffsetR = 1.5; // Advanced moving SL: khoảng cách SL so với giá hiện tại
// Cấu hình Swing
input int htfSwingRange = 1; // D1/H4: chắc trend
input int mtfSwingRange = 2; // H1: BOS rõ
input int ltfSwingRange = 2; // M5: giảm fake MSS
input int MaxSwingKeep = 2; // Số đỉnh/đáy gần nhất cần lưu (bạn yêu cầu 2)
// Struct pending entry (single slot)
struct PendingEntry
{
bool active; // đang có pending entry
int direction; // 1 = buy (bull), -1 = sell (bear)
double price; // entry price (edge của LTF internal FVG)
int fvgIndex; // index của MTF FVG (watched) hoặc -1 nếu none
string compositeName; // tên object composite vẽ (line|label)
datetime created_time; // thời điểm tạo
int source_slot; // slot nơi phát hiện MSS (HTF/MTF/LTF)
double sl_price; // giá SL (dựa trên swing gần nhất)
double tp_price; // giá TP (dựa trên swing gần nhất)
double lotSize; // kích thước lot tính toán
ulong orderTicket; // ticket lệnh đã mở (0 = chưa mở)
};
// global pending entry variable
PendingEntry pendingEntry;
// --- Watch MSS mode (user requested) ---
bool watchingMSSMode = false; // mặc định false
int watchingFVGIndex = -1; // index trong FVG_* arrays (khi watchingMSSMode==true)
int watchingFVGDir = 0; // 1 = bullish FVG, -1 = bearish FVG
// --- Mảng trạng thái: MTF FVG đã bị chạm bởi LTF hay chưa ---
bool MTF_FVG_Touched[]; // true = MTF FVG k đã bị LTF chạm
datetime MTF_FVG_TouchTime[]; // thời điểm bar LTF đầu tiên chạm zone (datetime)
double MTF_FVG_TouchPrice[]; // giá chạm đại diện (low khi bullish, high khi bearish)
// Cấu hình vẽ Swing trên chart
input bool ShowSwingMarkers = true; // Hiển thị các marker swing trên chart
input string SwingObjPrefix = "Swing_"; // Tiền tố tên object (dễ xóa/điều chỉnh)
input int SwingMarkerFontSize = 10; // Kích thước font cho label
input int FVGLookback = 200; // Số nến lookback khi tính FVG (dùng ở EnsureFVGUpToDate)
input bool PrintToExperts = false; // không dùng — logs đã được chọn lại
// --- lưu trend đã tính cho mỗi slot ---
// slot 0 = HTF, 1 = MTF, 2 = LTF
int TrendTF[3]; // 1 = up, -1 = down, 0 = sideway / unknown
input ENUM_TIMEFRAMES HighTF = PERIOD_W1; // Khung thời gian cao
input ENUM_TIMEFRAMES MiddleTF = PERIOD_H4; // Khung thời gian trung bình (dùng để tìm FVG)
input ENUM_TIMEFRAMES LowTF = PERIOD_M15; // Khung thời gian thấp (Tìm điểm vào lệnh)
// --- multi-TF swing storage (slots) ---
// slot 0 = HighTF, slot 1 = MiddleTF, slot 2 = LowTF
// dimension: [slot][index] (index 0 = nearest, 1 = older)
double SwingHighPriceTF[3][2];
datetime SwingHighTimeTF[3][2];
int SwingHighCountTF[3];
double SwingLowPriceTF[3][2];
datetime SwingLowTimeTF[3][2];
int SwingLowCountTF[3];
// --- cấu hình cho trend provisional (phát hiện sớm) ---
input int ProvisionalBreakPips = 5; // số pips để coi là phá sớm (change-as-you-like)
input int ProvisionalConsecCloses = 1; // số nến đóng liên tiếp vượt level để xác nhận provisional (thường 1 hoặc 2)
// --- FVG struct & storage (replace old separate arrays) ---
struct FVG
{
int type; // 1 = bullish (gap below price), -1 = bearish (gap above price)
double topPrice; // top edge (higher price)
double bottomPrice; // bottom edge (lower price)
datetime timebarA; // time of bar A (older)
datetime timebarC; // time of bar C (newer)
bool touched; // whether LTF touched this MTF FVG yet (first-touch)
bool used; // whether this FVG has been used for entry already
datetime touchTime; // time of first touch (on LowTF)
double touchPrice; // representative touch price (edge)
};
FVG MTF_FVGs[];
int MTF_FVG_count = 0; // number of FVGs found (kept for compatibility)
// last closed bar time per slot (index 0=HighTF,1=MiddleTF,2=LowTF)
datetime lastClosedBarTime[3] = {0,0,0};
// cache last FVG compute time (bar C or bar index 1 time used to detect change)
datetime lastFVGBarTime = 0;
struct MSS {
bool found;
int direction; // 1 = bearish → bullish | -1 = bullish → bearish
datetime sweep_time; // time của nến quét liquidity
double sweep_extreme_price; // GIÁ QUÉT THỰC TẾ
datetime swept_swing_time; // time của swing bị quét
double swept_swing_price; // GIÁ swing bị quét (liquidity pool)
datetime break_time; // time nến BOS
double bos_close_price; // GIÁ ĐÓNG nến BOS
datetime broken_swing_time; // time swing bị phá
double broken_swing_price; // GIÁ swing bị phá (key level)
};
// Kết quả BOS
struct BOSInfo
{
bool found;
bool isReal;
int direction; // 1 = up, -1 = down
datetime break_time; // time của bar xác nhận (bar đóng)
double break_price; // close của bar xác nhận
double broken_sw_price; // giá swing bị phá (sh0 hoặc sl0)
datetime broken_sw_time; // time của swing bị phá
int confirm_bar_index; // index (iBarShift) của bar xác nhận
int consec_closes;
};
// Bật/tắt BOS detection cho từng TF
input bool DetectBOS_HTF = false;
input bool DetectBOS_MTF = false;
input bool DetectBOS_LTF = true;
// --- BOS storage: lưu tối đa 2 BOS cho mỗi slot (slot 0=HTF,1=MTF,2=LTF)
// Reuse struct BOSInfo (đã khai báo trước đó)
BOSInfo BOSStore[3][2]; // BOSStore[slot][index] : index 0 = older, 1 = newer
string BOS_Names[3][2]; // tên objects (composite) cho mỗi store entry
int BOS_Count[3] = {0,0,0}; // số BOS hiện có cho mỗi slot (0..2)
bool BOS_HaveZone[3][2]; // flag có zone hay không
input int NYStartHourVN = 19;
input int NYEndHourVN = 22;
input int LondonStartHourVN = 14;
input int LondonEndHourVN = 18;
// ===== NEWS FILTER =====
input bool EnableNewsFilter = true;
// Block entry trước/sau tin (phút)
input int NewsBlockBeforeMin = 30;
input int NewsBlockAfterMin = 30;
bool IsValidMSS(
string sym,
ENUM_TIMEFRAMES tf,
int shift,
double swingLevel,
bool bullish
)
{
double atr = GetATR(sym, tf, 14, shift);
double close = iClose(sym, tf, shift);
double open = iOpen(sym, tf, shift);
double body = MathAbs(close - open);
if(bullish)
{
if(close <= swingLevel) return false;
if(close - swingLevel < atr * MSS_Break_ATR_Ratio) return false;
}
else
{
if(close >= swingLevel) return false;
if(swingLevel - close < atr * MSS_Break_ATR_Ratio) return false;
}
if(body < atr * MSS_Body_ATR_Ratio)
return false;
return true;
}
bool IsValidLiquiditySweep(
string sym,
ENUM_TIMEFRAMES tf,
int shift,
bool isHighSweep // true = sweep high, false = sweep low
) {
double atr = GetATR(sym, tf, 14, shift);
double wick;
if(isHighSweep)
wick = iHigh(sym, tf, shift) - MathMax(iOpen(sym, tf, shift), iClose(sym, tf, shift));
else
wick = MathMin(iOpen(sym, tf, shift), iClose(sym, tf, shift)) - iLow(sym, tf, shift);
if(wick < atr * LS_Wick_ATR_Ratio)
return false;
// check phá high/low N bars trước
for(int i=shift+1; i<=shift+LS_LookbackBars; i++)
{
if(isHighSweep && iHigh(sym, tf, shift) <= iHigh(sym, tf, i))
return false;
if(!isHighSweep && iLow(sym, tf, shift) >= iLow(sym, tf, i))
return false;
}
return true;
}
bool IsHighImpactNewsNear(string symbol, int minutesBefore, int minutesAfter)
{
// TODO: implement real news filter later
return false;
}
void UpdateDailyEquity()
{
datetime now = TimeCurrent();
MqlDateTime t;
TimeToStruct(now, t);
// YYYYMMDD
int todayKey = t.year * 10000 + t.mon * 100 + t.day;
if(DailyEquityDate != todayKey)
{
DailyEquityDate = todayKey;
DailyEquityStart = AccountInfoDouble(ACCOUNT_EQUITY);
DailyLossBlocked = false;
if(PrintEntryLog)
PrintFormat("DAILY RESET | EquityStart=%.2f", DailyEquityStart);
}
}
bool IsDailyLossExceeded()
{
if(DailyEquityStart <= 0.0)
return false;
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double maxLoss = DailyEquityStart * (MaxDailyLossPercent / 100.0);
double lossNow = DailyEquityStart - equity;
if(lossNow >= maxLoss)
{
if(!DailyLossBlocked && PrintEntryLog)
{
PrintFormat(
"MAX DAILY LOSS HIT | Loss=%.2f (%.2f%%) | Trading BLOCKED",
lossNow,
(lossNow / DailyEquityStart) * 100.0
);
}
DailyLossBlocked = true;
return true;
}
return false;
}
void UpdateMTFFVGTouched(string symbol)
{
if(MTF_FVG_count <= 0) return;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
// scan each stored FVG
for(int fvgIndex = 0; fvgIndex < MTF_FVG_count; fvgIndex++)
{
// nếu đã touched trước đó -> bỏ qua luôn (không in gì cả)
if(MTF_FVGs[fvgIndex].touched) continue;
datetime mtfC = MTF_FVGs[fvgIndex].timebarC;
if(mtfC == 0) continue;
double top = MTF_FVGs[fvgIndex].topPrice;
double bottom = MTF_FVGs[fvgIndex].bottomPrice;
int dir = MTF_FVGs[fvgIndex].type; // 1=bull, -1=bear
// find index of mtfC on LowTF
int idxC_on_LTF = iBarShift(symbol, LowTF, mtfC, false);
if(idxC_on_LTF == -1) idxC_on_LTF = 0;
bool touchedNow = false;
datetime touch_time = 0;
double touch_price = 0.0;
// scan forward on LowTF (bars newer than mtfC): idx = idxC_on_LTF-1 ... 0
int barsAvail = iBars(symbol, LowTF);
for(int idx = idxC_on_LTF - 1; idx >= 0; idx--)
{
double hh = iHigh(symbol, LowTF, idx);
double ll = iLow(symbol, LowTF, idx);
if(hh == 0 || ll == 0) continue;
if(dir == 1) // bullish FVG -> touch when LTF high >= top
{
if(hh >= top - tol)
{
touchedNow = true;
touch_time = iTime(symbol, LowTF, idx);
touch_price = top; // representative edge price
break;
}
}
else // dir == -1 -> bearish FVG -> touch when LTF low <= bottom
{
if(ll <= bottom + tol)
{
touchedNow = true;
touch_time = iTime(symbol, LowTF, idx);
touch_price = bottom;
break;
}
}
}
if(touchedNow)
{
// chỉ set + print khi trước đó chưa touched (điều kiện đã đảm bảo vì ta continue ở trên nếu touched==true)
MTF_FVGs[fvgIndex].touched = true;
MTF_FVGs[fvgIndex].touchTime = touch_time;
MTF_FVGs[fvgIndex].touchPrice = touch_price;
// enable watchingMSSMode when any MTF FVG is first touched
if(!watchingMSSMode && !MTF_FVGs[fvgIndex].used)
{
ToggleWatchingMSSMode(true, fvgIndex, dir);
if(PrintEntryLog)
PrintFormat("watchingMSSMode ENABLED for FVG idx=%d dir=%d", watchingFVGIndex, watchingFVGDir);
}
}
}
}
void ToggleWatchingMSSMode(bool enable, int fvgIndex = -1, int fvgDir = 0)
{
watchingMSSMode = enable;
watchingFVGIndex = fvgIndex;
watchingFVGDir = fvgDir;
}
// Kiểm tra điều kiện vô hiệu hoá watchingMSSMode:
// 1) last closed candle trên LowTF đóng dưới cạnh dưới của bullish FVG
// 2) last closed candle trên LowTF đóng trên cạnh trên của bearish FVG
void CheckWatchMSSInvalidation(string symbol)
{
if(!watchingMSSMode || watchingFVGIndex < 0 || watchingFVGIndex >= MTF_FVG_count) return;
// Lấy last closed close trên LowTF (index 1)
double lastClose = iClose(symbol, LowTF, 1);
if(lastClose == 0.0) return;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
int idx = watchingFVGIndex;
int dir = watchingFVGDir;
// bảo đảm FVG arrays còn hợp lệ
if(idx < 0 || idx >= MTF_FVG_count) return;
double top = MTF_FVGs[idx].topPrice;
double bottom = MTF_FVGs[idx].bottomPrice;
// Nếu bullish FVG (dir == 1): invalid khi đóng dưới bottom
if(dir == 1)
{
if(lastClose < bottom - tol)
{
if(PrintEntryLog) PrintFormat("watchingMSSMode DISABLED: bullish FVG idx=%d invalidated by close=%.5f < bottom=%.5f", idx, lastClose, bottom);
ToggleWatchingMSSMode(false);
}
}
else if(dir == -1) // bearish: invalid khi đóng trên top
{
if(lastClose > top + tol)
{
if(PrintEntryLog) PrintFormat("watchingMSSMode DISABLED: bearish FVG idx=%d invalidated by close=%.5f > top=%.5f", idx, lastClose, top);
// clear
ToggleWatchingMSSMode(false);
}
}
}
void StoreNewBOS(int slot, const BOSInfo &bos)
{
// Nếu chưa có BOS nào
if(BOS_Count[slot] == 0)
{
BOSStore[slot][0] = bos;
// Clear name => DrawBOS sẽ tạo object và lưu tên
BOS_Names[slot][0] = "";
BOS_HaveZone[slot][0] = true;
BOS_Count[slot] = 1;
}
else if(BOS_Count[slot] == 1)
{
// shift index0 -> index1 (data only), but DO NOT copy names
BOSStore[slot][1] = BOSStore[slot][0];
BOS_HaveZone[slot][1] = false; // will be created by DrawBOS
BOS_Names[slot][1] = "";
// store new into index0
BOSStore[slot][0] = bos;
BOS_Names[slot][0] = "";
BOS_HaveZone[slot][0] = true;
BOS_Count[slot] = 2;
}
else // BOS_Count == 2
{
// xóa oldest (index1) trước nếu có object name lưu
if(StringLen(BOS_Names[slot][1]) > 0)
{
DeleteCompositeBOS(BOS_Names[slot][1]);
}
// shift data index0 -> index1 (but clear names so Draw creates fresh objects)
BOSStore[slot][1] = BOSStore[slot][0];
BOS_Names[slot][1] = "";
BOS_HaveZone[slot][1] = false;
// store new into index0
BOSStore[slot][0] = bos;
BOS_Names[slot][0] = "";
BOS_HaveZone[slot][0] = true;
BOS_Count[slot] = 2;
}
// Vẽ lại tất cả BOS cho slot (DrawAllBOSForSlot sẽ tạo lại objects và ghi BOS_Names)
// DrawAllBOSForSlot(slot);
}
// Vẽ tất cả BOS đã lưu cho 1 slot (0=HTF,1=MTF,2=LTF)
// SỬA: không xóa theo prefix chung nữa, chỉ xóa các composite names đã lưu trong BOS_Names
void DrawAllBOSForSlot(int slot)
{
if(!ShowSwingMarkers) return;
int cnt = BOS_Count[slot];
if(cnt <= 0) return;
string sym = Symbol();
// --- SAFE CLEANUP: xóa composite object cũ mà chúng ta từng lưu tên trong BOS_Names ---
for(int j = 0; j < 2; j++)
{
if(StringLen(BOS_Names[slot][j]) > 0)
{
// DeleteCompositeBOS sẽ kiểm tra tồn tại từng object con trước khi xóa
DeleteCompositeBOS(BOS_Names[slot][j]);
BOS_Names[slot][j] = ""; // clear saved composite name so DrawBOS will recreate
BOS_HaveZone[slot][j] = false;
}
}
// draw older first (index1) then newer (index0)
// Note: DrawBOS sẽ set BOS_Names[slot][storeIndex] after drawing, so we pass storeIndex to persist names
if(cnt == 2)
{
if(BOSStore[slot][1].found)
{
DrawBOS(sym, slot, BOSStore[slot][1], 1);
if(StringLen(BOS_Names[slot][1]) == 0)
{
// do nothing special
}
}
if(BOSStore[slot][0].found)
{
DrawBOS(sym, slot, BOSStore[slot][0], 0);
if(StringLen(BOS_Names[slot][0]) == 0)
{
// do nothing special
}
}
}
else // cnt == 1
{
if(BOSStore[slot][0].found)
{
DrawBOS(sym, slot, BOSStore[slot][0], 0);
}
}
}
void HandleBOSDetections(string symbol, ENUM_TIMEFRAMES timeframe, int slot)
{
// Tôn trọng input bật/tắt cho từng slot (slot 0=HTF,1=MTF,2=LTF)
bool enabled = false;
if(slot == 0) enabled = DetectBOS_HTF;
else if(slot == 1) enabled = DetectBOS_MTF;
else if(slot == 2) enabled = DetectBOS_LTF;
if(!enabled) return;
// Phát hiện BOS trên slot (giữ nguyên tham số bạn muốn)
BOSInfo bos = DetectBOSOnSlot(symbol, timeframe, slot,
ProvisionalBreakPips,
ProvisionalConsecCloses,
true,
5);
if(bos.found) StoreNewBOS(slot, bos);
}
// DetectBOSOnSlot: detect Break Of Structure based on nearest swing (index 0)
// symbol, timeframe, slot: như EA của bạn
// minBreakPips: số pips tối thiểu vượt swing để gọi là BOS (ví dụ 1..10 tuỳ TF)
// consecRequired: số nến đóng liên tiếp xác nhận (1 = 1 bar close đủ)
// useBodyCheck: nếu true thì yêu cầu bar xác nhận là nến thuận (close>open cho bullish, close<open cho bearish)
// lookForwardBars: số bars mới hơn để scan tìm confirm (thường small: 1..5)
// Trả về BOSInfo
BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot,
double minBreakPips = 5.0,
int consecRequired = 1,
bool useBodyCheck = true,
int lookForwardBars = 5)
{
BOSInfo out;
out.found = false;
out.direction = 0;
out.break_time = 0;
out.break_price = 0.0;
out.broken_sw_price = 0.0;
out.broken_sw_time = 0;
out.confirm_bar_index = -1;
out.consec_closes = 0;
out.isReal = false; // default
// Cần ít nhất 1 swing high hoặc low (ở slot)
double sh0 = (SwingHighCountTF[slot] >= 1) ? SwingHighPriceTF[slot][0] : 0.0;
double sl0 = (SwingLowCountTF[slot] >= 1) ? SwingLowPriceTF[slot][0] : 0.0;
if(sh0 == 0.0 && sl0 == 0.0) return out;
double pip = GetPipSize(symbol);
double minBreakPoints = minBreakPips * pip;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
int barsAvailable = iBars(symbol, timeframe);
if(barsAvailable < 2) return out;
// --- BULLISH BOS: first try REAL (close > sh0 + threshold) ---
if(sh0 != 0.0)
{
for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++)
{
double c = iClose(symbol, timeframe, idx);
double o = iOpen(symbol, timeframe, idx);
double h = iHigh(symbol, timeframe, idx);
datetime t = iTime(symbol, timeframe, idx);
if(c == 0 || h == 0) continue;
// REAL: require close strictly greater than sh0 + minBreakPoints
if(c > sh0 + minBreakPoints)
{
int consec = 0;
for(int k = idx; k >= 1 && k >= idx - (consecRequired - 1); k--)
{
double ck = iClose(symbol, timeframe, k);
double ok = iOpen(symbol, timeframe, k);
if(ck == 0) break;
if(useBodyCheck)
{
if(ck > ok && ck > sh0 + minBreakPoints) consec++;
else break;
}
else
{
if(ck > sh0 + minBreakPoints) consec++;
else break;
}
}
if(consec >= consecRequired)
{
out.found = true;
out.direction = 1;
out.break_time = t;
out.break_price = c;
out.broken_sw_price = sh0;
out.broken_sw_time = SwingHighTimeTF[slot][0];
out.confirm_bar_index = idx;
out.consec_closes = consec;
out.isReal = true;
return out;
}
}
}
// If no REAL bullish BOS found, check for FAKE bullish BOS:
for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++)
{
double c = iClose(symbol, timeframe, idx);
double h = iHigh(symbol, timeframe, idx);
datetime t = iTime(symbol, timeframe, idx);
if(h == 0) continue;
if(h > sh0 + minBreakPoints && c <= sh0 + minBreakPoints)
{
// classify as fake BOS
out.found = true;
out.direction = 1;
out.break_time = t;
out.break_price = c;
out.broken_sw_price = sh0;
out.broken_sw_time = SwingHighTimeTF[slot][0];
out.confirm_bar_index = idx;
out.consec_closes = 0;
out.isReal = false;
return out;
}
}
}
// --- BEARISH BOS: first try REAL (close < sl0 - threshold) ---
if(sl0 != 0.0)
{
for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++)
{
double c = iClose(symbol, timeframe, idx);
double o = iOpen(symbol, timeframe, idx);
double l = iLow(symbol, timeframe, idx);
datetime t = iTime(symbol, timeframe, idx);
if(c == 0 || l == 0) continue;
if(c < sl0 - minBreakPoints)
{
int consec = 0;
for(int k = idx; k >= 1 && k >= idx - (consecRequired - 1); k--)
{
double ck = iClose(symbol, timeframe, k);
double ok = iOpen(symbol, timeframe, k);
if(ck == 0) break;
if(useBodyCheck)
{
if(ck < ok && ck < sl0 - minBreakPoints) consec++;
else break;
}
else
{
if(ck < sl0 - minBreakPoints) consec++;
else break;
}
}
if(consec >= consecRequired)
{
out.found = true;
out.direction = -1;
out.break_time = t;
out.break_price = c;
out.broken_sw_price = sl0;
out.broken_sw_time = SwingLowTimeTF[slot][0];
out.confirm_bar_index = idx;
out.consec_closes = consec;
out.isReal = true;
return out;
}
}
}
// If no REAL bearish BOS found, check for FAKE bearish BOS:
for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++)
{
double c = iClose(symbol, timeframe, idx);
double l = iLow(symbol, timeframe, idx);
datetime t = iTime(symbol, timeframe, idx);
if(l == 0) continue;
if(l < sl0 - minBreakPoints && c >= sl0 - minBreakPoints)
{
// classify as fake BOS
out.found = true;
out.direction = -1;
out.break_time = t;
out.break_price = c;
out.broken_sw_price = sl0;
out.broken_sw_time = SwingLowTimeTF[slot][0];
out.confirm_bar_index = idx;
out.consec_closes = 0;
out.isReal = false;
return out;
}
}
}
return out;
}
// DrawBOS: vẽ 1 BOS (không shift/xóa store) — parameters: symbol, slot, bos info, storeIndex (0/1)
// Nếu bạn gọi DrawBOS với storeIndex = -1 thì hàm sẽ chỉ tạo objects tạm (không lưu tên).
void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1)
{
if(!info.found) return;
string basePrefix = SwingObjPrefix + "BOS_" + IntegerToString(slot) + "_";
ENUM_TIMEFRAMES tf = (slot==0 ? HighTF : (slot==1 ? MiddleTF : LowTF));
// get start time/price (swing broken)
datetime t_start = info.broken_sw_time;
double p_start = info.broken_sw_price;
if(t_start == 0 || p_start == 0.0)
{
if(info.direction == 1) { t_start = SwingHighTimeTF[slot][0]; p_start = SwingHighPriceTF[slot][0]; }
else { t_start = SwingLowTimeTF[slot][0]; p_start = SwingLowPriceTF[slot][0]; }
}
if(t_start == 0 || p_start == 0.0)
{
return;
}
datetime bar_secs = (datetime)PeriodSeconds(tf);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
datetime touch_time = FindTouchTime(symbol, tf, t_start, p_start, tol, bar_secs);
if(touch_time <= t_start)
{
if(info.break_time != 0 && info.break_time > t_start) touch_time = info.break_time;
else
{
datetime t0 = iTime(symbol, tf, 0);
touch_time = (t0 != 0) ? (datetime)((long)t0 + (long)bar_secs) : (datetime)TimeCurrent();
}
}
datetime t_h_end = touch_time;
double p_h = p_start;
if(t_h_end == t_start) t_h_end = (datetime)((long)t_start + (long)bar_secs);
// --- TẠO TÊN UNIQUE: bao gồm slot + storeIndex + thời điểm để tránh trùng ---
int idxPart = (storeIndex >= 0) ? storeIndex : -1;
int timeStampInt = (info.broken_sw_time != 0) ? (int)info.broken_sw_time : (int)t_start;
string stamp = StringFormat("%d_%d_%d", slot, idxPart, timeStampInt);
string nm_line = basePrefix + "HLINE_" + stamp;
string nm_arrow = basePrefix + "ARW_" + stamp;
string nm_txt = basePrefix + "LBL_" + stamp;
// Nếu caller truyền storeIndex >=0 và đã có composite name lưu trước đó:
if(storeIndex >= 0)
{
if(StringLen(BOS_Names[slot][storeIndex]) > 0)
{
string parts[];
int n = StringSplit(BOS_Names[slot][storeIndex], '|', parts);
bool all_exist = true;
for(int p = 0; p < n; p++)
{
if(StringLen(parts[p]) == 0) continue;
if(ObjectFind(0, parts[p]) < 0) { all_exist = false; break; }
}
if(all_exist)
{
return;
}
else
{
// object cũ bị xóa một phần nào đó -> xóa compositeName cũ khỏi bộ nhớ trước khi vẽ mới
DeleteCompositeBOS(BOS_Names[slot][storeIndex]);
BOS_Names[slot][storeIndex] = "";
BOS_HaveZone[slot][storeIndex] = false;
}
}
}
// Create horizontal line (trend object used as line)
if(!ObjectCreate(0, nm_line, OBJ_TREND, 0, t_start, p_h, t_h_end, p_h))
; // skip silent
else
{
int lineColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow;
ObjectSetInteger(0, nm_line, OBJPROP_COLOR, lineColor);
ObjectSetInteger(0, nm_line, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, nm_line, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, nm_line, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, nm_line, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm_line, OBJPROP_BACK, true);
}
// Arrow (or fallback text arrow) at t_h_end,p_h
if(ObjectCreate(0, nm_arrow, OBJ_ARROW, 0, t_h_end, p_h))
{
int code = (info.direction==1) ? 233 : 234;
#ifdef __MQL5__
ObjectSetInteger(0, nm_arrow, OBJPROP_ARROWCODE, code);
#endif
int arrowColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow;
ObjectSetInteger(0, nm_arrow, OBJPROP_COLOR, arrowColor);
ObjectSetInteger(0, nm_arrow, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm_arrow, OBJPROP_BACK, true);
}
else
{
string nm_fallback = basePrefix + "ARW_T_" + stamp;
if(ObjectCreate(0, nm_fallback, OBJ_TEXT, 0, t_h_end, p_h))
{
string arrowTxt = (info.direction==1) ? "▲" : "▼";
ObjectSetString(0, nm_fallback, OBJPROP_TEXT, arrowTxt);
int arrowColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow;
ObjectSetInteger(0, nm_fallback, OBJPROP_COLOR, arrowColor);
ObjectSetInteger(0, nm_fallback, OBJPROP_FONTSIZE, SwingMarkerFontSize+2);
ObjectSetInteger(0, nm_fallback, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm_fallback, OBJPROP_BACK, true);
nm_arrow = nm_fallback;
}
}
double y_offset = SymbolInfoDouble(symbol, SYMBOL_POINT) * 4.0;
double label_price = (info.direction==1) ? (p_h + y_offset) : (p_h - y_offset);
// Label text and color depending on real/fake
string labelText = info.isReal ? "BOS" : "FAKE";
int labelColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow;
if(!ObjectCreate(0, nm_txt, OBJ_TEXT, 0, t_h_end, label_price))
;
else
{
ObjectSetString(0, nm_txt, OBJPROP_TEXT, labelText);
ObjectSetInteger(0, nm_txt, OBJPROP_COLOR, labelColor);
ObjectSetInteger(0, nm_txt, OBJPROP_FONTSIZE, SwingMarkerFontSize + 1);
ObjectSetInteger(0, nm_txt, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm_txt, OBJPROP_BACK, true);
}
// Nếu caller truyền storeIndex >=0 thì lưu composite name để quản lý (xóa sau)
string compositeName = nm_line + "|" + nm_arrow + "|" + nm_txt;
if(storeIndex >= 0)
{
if(StringLen(BOS_Names[slot][storeIndex]) > 0) DeleteCompositeBOS(BOS_Names[slot][storeIndex]);
BOS_Names[slot][storeIndex] = compositeName;
BOS_HaveZone[slot][storeIndex] = true;
}
}
// xóa compositeName dạng "lineName|arrowName|labelName"
void DeleteCompositeBOS(string compositeName)
{
if(StringLen(compositeName) == 0) return;
string parts[];
int n = StringSplit(compositeName, '|', parts);
for(int i=0; i<n; i++)
{
string nm = parts[i];
if(StringLen(nm) > 0 && ObjectFind(0, nm) >= 0) ObjectDelete(0, nm);
}
}
// ---------------------- Helper: pip size ----------------------
// Trả về 1 "pip" cho symbol (không phải point).
double GetPipSize(string symbol)
{
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
if(digits==5 || digits==3) return point * 10.0;
return point;
}
// Tìm FVG nội bộ (3-candle gap: A = i+2, B = i+1, C = i)
//
// Điều kiện FVG:
// • Bullish: low(C) > high(A)
// • Bearish: high(C) < low(A)
//
// Trả về:
// out_top[] cạnh trên của FVG
// out_bottom[] cạnh dưới của FVG
// out_timeA[] thời gian bar A
// out_timeC[] thời gian bar C
// out_type[] 1 = bullish, -1 = bearish
//
// Lý do dùng total - 3:
// Vì index A = i+2 → cần đảm bảo i+2 <= total-1
// → i <= total - 3
//
// --------------------------------------------------------------
int FindInternalFVG(string symbol, ENUM_TIMEFRAMES timeframe, int lookback,
double &out_top[], double &out_bottom[],
datetime &out_timeA[], datetime &out_timeC[], int &out_type[])
{
// clear output arrays
ArrayFree(out_top);
ArrayFree(out_bottom);
ArrayFree(out_timeA);
ArrayFree(out_timeC);
ArrayFree(out_type);
int found = 0;
int total = iBars(symbol, timeframe);
// cần tối thiểu 5 nến để tránh lỗi/truy cập thiếu (an toàn)
if(total < 5) return 0;
// maxScan = nhỏ nhất giữa lookback và total - 3 (đảm bảo idxA không vượt array)
int maxScan = MathMin(lookback, total - 3);
if(maxScan <= 0) return 0;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
// ----------------------------------------------------------
// Loop qua từng cấu trúc ABC (C = i, B = i+1, A = i+2)
// ----------------------------------------------------------
for(int i = 1; i <= maxScan; i++)
{
int idxA = i + 2;
int idxB = i + 1;
int idxC = i;
// kiểm tra A không vượt tổng số bar
if(idxA > total - 1)
break;
// lấy giá 3 nến A và C
double highA = iHigh(symbol, timeframe, idxA);
double lowA = iLow(symbol, timeframe, idxA);
double highC = iHigh(symbol, timeframe, idxC);
double lowC = iLow(symbol, timeframe, idxC);
if(highA == 0 || lowA == 0 || highC == 0 || lowC == 0)
continue;
// ------------------------------------------------------
// ❶ Kiểm tra Bullish Internal FVG
// low(C) > high(A)
// ------------------------------------------------------
if(lowC > highA + tol)
{
double top = lowC;
double bottom = highA;
// ===== FVG SIZE FILTER (ATR-based) =====
double atr = GetATR(symbol, timeframe, 14, idxC);
if(MathAbs(top - bottom) < atr * FVG_Min_ATR_Ratio)
continue;
// kiểm tra tránh duplicate FVG
bool dup = false;
for(int k = 0; k < found; k++)
{
if(MathAbs(out_top[k] - top) <= tol &&
MathAbs(out_bottom[k] - bottom) <= tol)
{
dup = true;
break;
}
}
if(!dup)
{
ArrayResize(out_top, found+1);
ArrayResize(out_bottom, found+1);
ArrayResize(out_timeA, found+1);
ArrayResize(out_timeC, found+1);
ArrayResize(out_type, found+1);
out_top[found] = top;
out_bottom[found] = bottom;
out_timeA[found] = iTime(symbol, timeframe, idxA);
out_timeC[found] = iTime(symbol, timeframe, idxC);
out_type[found] = 1; // bullish
found++;
}
continue;
}
// ------------------------------------------------------
// ❷ Kiểm tra Bearish Internal FVG
// high(C) < low(A)
// ------------------------------------------------------
if(highC < lowA - tol)
{
double top = lowA;
double bottom = highC;
// ===== FVG SIZE FILTER (ATR-based) =====
double atr = GetATR(symbol, timeframe, 14, idxC);
if(MathAbs(top - bottom) < atr * FVG_Min_ATR_Ratio)
continue;
bool dup = false;
for(int k = 0; k < found; k++)
{
if(MathAbs(out_top[k] - top) <= tol &&
MathAbs(out_bottom[k] - bottom) <= tol)
{
dup = true;
break;
}
}
if(!dup)
{
ArrayResize(out_top, found+1);
ArrayResize(out_bottom, found+1);
ArrayResize(out_timeA, found+1);
ArrayResize(out_timeC, found+1);
ArrayResize(out_type, found+1);
out_top[found] = top;
out_bottom[found] = bottom;
out_timeA[found] = iTime(symbol, timeframe, idxA);
out_timeC[found] = iTime(symbol, timeframe, idxC);
out_type[found] = -1; // bearish
found++;
}
}
}
return found;
}
// ---------- HasBullFVGBetween & HasBearFVGBetween (gọi FindInternalFVG) ----------
bool HasBullFVGBetween(string symbol, ENUM_TIMEFRAMES timeframe, datetime t_start, datetime t_end, int fvgLookback)
{
if(t_start == 0 || t_end == 0 || t_end <= t_start) return false;
double tops[]; double bottoms[];
datetime timeA[]; datetime timeC[];
int types[];
int cnt = FindInternalFVG(symbol, timeframe, fvgLookback, tops, bottoms, timeA, timeC, types);
if(cnt <= 0) return false;
for(int i = 0; i < cnt; i++)
{
if(types[i] == 1)
{
datetime tc = timeC[i];
if(tc > t_start && tc < t_end) return true;
}
}
return false;
}
bool HasBearFVGBetween(string symbol, ENUM_TIMEFRAMES timeframe, datetime t_start, datetime t_end, int fvgLookback)
{
if(t_start == 0 || t_end == 0 || t_end <= t_start) return false;
double tops[]; double bottoms[];
datetime timeA[]; datetime timeC[];
int types[];
int cnt = FindInternalFVG(symbol, timeframe, fvgLookback, tops, bottoms, timeA, timeC, types);
if(cnt <= 0) return false;
for(int i = 0; i < cnt; i++)
{
if(types[i] == -1)
{
datetime tc = timeC[i];
if(tc > t_start && tc < t_end) return true;
}
}
return false;
}
// Kiểm tra 1 bar tại index i có phải SwingHigh không (xét râu - High)
bool IsSwingHigh(string symbol, ENUM_TIMEFRAMES timeframe, int candleIndex, int swingRange)
{
double hi = iHigh(symbol, timeframe, candleIndex);
if(hi == 0.0) return false;
for(int i = 1; i <= swingRange; i++)
{
int idxPrev = candleIndex - i;
if(idxPrev < 0) return false;
double hprev = iHigh(symbol, timeframe, idxPrev);
if(hprev == 0.0) return false;
if(hprev >= hi) return false;
}
for(int k = 1; k <= swingRange; k++)
{
int idxNext = candleIndex + k;
double hnext = iHigh(symbol, timeframe, idxNext);
if(hnext == 0.0) return false;
if(hnext >= hi) return false;
}
return true;
}
// Kiểm tra 1 bar tại index i có phải SwingLow không (xét râu - Low)
bool IsSwingLow(string symbol, ENUM_TIMEFRAMES timeframe, int i, int X)
{
double lo = iLow(symbol, timeframe, i);
if(lo == 0.0) return false;
for(int j = 1; j <= X; j++)
{
int idxPrev = i - j;
if(idxPrev < 0) return false;
double lprev = iLow(symbol, timeframe, idxPrev);
if(lprev == 0.0) return false;
if(lprev <= lo) return false;
}
for(int k = 1; k <= X; k++)
{
int idxNext = i + k;
double lnext = iLow(symbol, timeframe, idxNext);
if(lnext == 0.0) return false;
if(lnext <= lo) return false;
}
return true;
}
// Cập nhật mảng SwingHighPrice/Time và SwingLowPrice/Time (giữ MaxSwingKeep phần tử gần nhất)
void UpdateSwings(string symbol, ENUM_TIMEFRAMES timeframe, int slot, int SwingRange)
{
SwingHighCountTF[slot] = 0;
SwingLowCountTF[slot] = 0;
int total = iBars(symbol, timeframe);
if(total <= SwingRange + 2) return;
int iStart = SwingRange + 1;
int iEnd = total - SwingRange - 1;
for(int i = iStart; i <= iEnd; i++)
{
if(SwingHighCountTF[slot] >= MaxSwingKeep && SwingLowCountTF[slot] >= MaxSwingKeep) break;
if(SwingHighCountTF[slot] < MaxSwingKeep)
{
bool isH = IsSwingHigh(symbol, timeframe, i, SwingRange);
if(isH)
{
double candleI = iHigh(symbol, timeframe, i);
datetime ti = iTime(symbol, timeframe, i);
if(SwingHighCountTF[slot] == 0)
{
SwingHighPriceTF[slot][0] = candleI;
SwingHighTimeTF[slot][0] = ti;
}
else
{
SwingHighPriceTF[slot][1] = candleI;
SwingHighTimeTF[slot][1] = ti;
}
SwingHighCountTF[slot]++;
}
}
if(SwingLowCountTF[slot] < MaxSwingKeep)
{
bool isL = IsSwingLow(symbol, timeframe, i, SwingRange);
if(isL)
{
double candleI = iLow(symbol, timeframe, i);
datetime ti2 = iTime(symbol, timeframe, i);
if(SwingLowCountTF[slot] == 0)
{
SwingLowPriceTF[slot][0] = candleI;
SwingLowTimeTF[slot][0] = ti2;
}
else
{
SwingLowPriceTF[slot][1] = candleI;
SwingLowTimeTF[slot][1] = ti2;
}
SwingLowCountTF[slot]++;
}
}
}
}
//+------------------------------------------------------------------+
//| Vẽ SwingHigh & SwingLow lên chart |
//+------------------------------------------------------------------+
void DrawSwingsOnChart(ENUM_TIMEFRAMES timeframe)
{
if(!ShowSwingMarkers) return;
int slot = 0;
string inChartSwingPrefix = "h";
if(timeframe == MiddleTF) {
slot = 1;
inChartSwingPrefix = "m";
} else if (timeframe == LowTF) {
slot = 2;
inChartSwingPrefix = "l";
}
string prefix = SwingObjPrefix + inChartSwingPrefix;
int total = ObjectsTotal(0);
for(int i = total - 1; i >= 0; i--)
{
string name = ObjectName(0, i);
if(StringLen(name) >= StringLen(prefix))
{
if(StringSubstr(name, 0, StringLen(prefix)) == prefix)
ObjectDelete(0, name);
}
}
datetime bar_secs = (datetime)PeriodSeconds(timeframe);
int maxH = (SwingHighCountTF[slot] < MaxSwingKeep) ? SwingHighCountTF[slot] : MaxSwingKeep;
for(int h = 0; h < maxH; h++)
{
string obj = prefix + "H_" + IntegerToString(h);
datetime t = SwingHighTimeTF[slot][h];
double price = SwingHighPriceTF[slot][h];
datetime display_time_h = (datetime)(t + (bar_secs/2));
if(t == 0 || price == 0.0) continue;
if(!ObjectCreate(0, obj, OBJ_TEXT, 0, display_time_h, price))
{
continue;
}
ObjectSetString(0, obj, OBJPROP_TEXT, "▲ " + inChartSwingPrefix + "H" + IntegerToString(h));
ObjectSetInteger(0, obj, OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, obj, OBJPROP_FONTSIZE, SwingMarkerFontSize);
ObjectSetInteger(0, obj, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
ObjectSetInteger(0, obj, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, obj, OBJPROP_HIDDEN, false);
}
int maxL = (SwingLowCountTF[slot] < MaxSwingKeep) ? SwingLowCountTF[slot] : MaxSwingKeep;
for(int l = 0; l < maxL; l++)
{
string obj = prefix + "L_" + IntegerToString(l);
datetime t = SwingLowTimeTF[slot][l];
double price = SwingLowPriceTF[slot][l];
datetime display_time_l = (datetime)(t + (bar_secs/2));
if(!ObjectCreate(0, obj, OBJ_TEXT, 0, display_time_l, price))
{
continue;
}
ObjectSetString(0, obj, OBJPROP_TEXT, "▼ " + inChartSwingPrefix +"L" + IntegerToString(l));
ObjectSetInteger(0, obj, OBJPROP_COLOR, clrLime);
ObjectSetInteger(0, obj, OBJPROP_FONTSIZE, SwingMarkerFontSize);
ObjectSetInteger(0, obj, OBJPROP_ANCHOR, ANCHOR_TOP);
ObjectSetInteger(0, obj, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, obj, OBJPROP_HIDDEN, false);
}
}
// ---------------------------------------------------------------------
// FindTouchTime(): tìm thời điểm bar đầu tiên sau start_time mà bar chạm price
// ---------------------------------------------------------------------
datetime FindTouchTime(string symbol, ENUM_TIMEFRAMES timeframe,
datetime start_time, double price, double tol, datetime bar_secs)
{
int idxStart = iBarShift(symbol, timeframe, start_time, false);
if(idxStart == -1) idxStart = 0;
for(int idx = idxStart - 1; idx >= 0; idx--)
{
double highM = iHigh(symbol, timeframe, idx);
double lowM = iLow(symbol, timeframe, idx);
if(highM == 0 || lowM == 0) continue;
if(lowM <= price + tol && highM >= price - tol)
return iTime(symbol, timeframe, idx);
}
datetime t0 = iTime(symbol, timeframe, 0);
if(t0 != 0) return (datetime)((long)t0 + (long)bar_secs);
return TimeCurrent();
}
void DrawMss(string symbol, ENUM_TIMEFRAMES tf, const MSS &mss, int slot)
{
if(!ShowSwingMarkers || !mss.found)
return;
string basePrefix = SwingObjPrefix + "MSS_" + IntegerToString(slot) + "_";
// ===== clear old MSS objects for this slot =====
int total = ObjectsTotal(0);
for(int i = total - 1; i >= 0; i--)
{
string nm = ObjectName(0, i);
if(StringFind(nm, basePrefix, 0) == 0)
ObjectDelete(0, nm);
}
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
datetime bar_secs = (datetime)PeriodSeconds(tf);
// =================================================
// 1️⃣ DRAW SWEEP LABEL (wick)
// =================================================
if(mss.sweep_time != 0 && mss.sweep_extreme_price != 0.0)
{
string nmSweep = basePrefix + "SWEEP_" + IntegerToString((int)mss.sweep_time);
if(ObjectCreate(0, nmSweep, OBJ_TEXT, 0,
mss.sweep_time, mss.sweep_extreme_price))
{
ObjectSetString(0, nmSweep, OBJPROP_TEXT, "SWEEP");
ObjectSetInteger(0, nmSweep, OBJPROP_COLOR, clrMagenta);
ObjectSetInteger(0, nmSweep, OBJPROP_FONTSIZE, SwingMarkerFontSize + 2);
ObjectSetInteger(0, nmSweep, OBJPROP_BACK, true);
ObjectSetInteger(0, nmSweep, OBJPROP_SELECTABLE, false);
}
}
// =================================================
// 2️⃣ DRAW MSS LABEL (BOS close)
// =================================================
if(mss.break_time != 0 && mss.bos_close_price != 0.0)
{
string nmMSS = basePrefix + "MSS_" + IntegerToString((int)mss.break_time);
if(ObjectCreate(0, nmMSS, OBJ_TEXT, 0,
mss.break_time, mss.bos_close_price))
{
ObjectSetString(0, nmMSS, OBJPROP_TEXT, "MSS");
ObjectSetInteger(0, nmMSS, OBJPROP_COLOR, clrOrange);
ObjectSetInteger(0, nmMSS, OBJPROP_FONTSIZE, SwingMarkerFontSize + 2);
ObjectSetInteger(0, nmMSS, OBJPROP_BACK, true);
ObjectSetInteger(0, nmMSS, OBJPROP_SELECTABLE, false);
}
}
// =================================================
// 3️⃣ DRAW LIQUIDITY SWEEP GUIDE LINE
// =================================================
if(mss.swept_swing_time != 0 &&
mss.swept_swing_price != 0.0 &&
mss.sweep_time > mss.swept_swing_time)
{
string nmGuide = basePrefix + "GUIDE_" + IntegerToString((int)mss.sweep_time);
if(ObjectCreate(0, nmGuide, OBJ_TREND, 0,
mss.swept_swing_time,
mss.swept_swing_price,
mss.sweep_time,
mss.swept_swing_price))
{
ObjectSetInteger(0, nmGuide, OBJPROP_COLOR, clrMagenta);
ObjectSetInteger(0, nmGuide, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, nmGuide, OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(0, nmGuide, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, nmGuide, OBJPROP_BACK, true);
ObjectSetInteger(0, nmGuide, OBJPROP_SELECTABLE, false);
}
}
// =================================================
// 4️⃣ DRAW MSS BREAK (BOS) GUIDE LINE
// =================================================
if(mss.broken_swing_time != 0 &&
mss.broken_swing_price != 0.0 &&
mss.break_time != 0)
{
datetime t_start = mss.broken_swing_time;
datetime t_end = mss.break_time;
// đảm bảo có độ dài để vẽ
if(t_end == t_start)
t_end = (datetime)((long)t_end + (long)bar_secs);
string nmBreak = basePrefix + "BREAK_" + IntegerToString((int)mss.break_time);
if(ObjectCreate(0, nmBreak, OBJ_TREND, 0,
t_start,
mss.broken_swing_price,
t_end,
mss.broken_swing_price))
{
ObjectSetInteger(0, nmBreak, OBJPROP_COLOR, clrOrange);
ObjectSetInteger(0, nmBreak, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, nmBreak, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, nmBreak, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, nmBreak, OBJPROP_BACK, true);
ObjectSetInteger(0, nmBreak, OBJPROP_SELECTABLE, false);
}
}
}
// Hàm tiện ích chuyển giá trị xu hướng thành chuỗi
string TrendToString(int trend)
{
if(trend==1) return "UPTREND";
if(trend==-1) return "DOWNTREND";
return "SIDEWAY";
}
// Cập nhật TrendTF[slot] dựa trên 2 swing gần nhất (không gọi UpdateSwings bên trong)
void UpdateTrendForSlot(int slot, ENUM_TIMEFRAMES timeframe, string symbol)
{
int oldTrend = TrendTF[slot];
// ===== LTF: KHÔNG TỰ TÍNH TREND =====
if(slot == 2)
{
// mirror MTF hoặc bỏ luôn
TrendTF[slot] = TrendTF[1];
return;
}
double pip = GetPipSize(symbol);
double provisionalThresh = (double)ProvisionalBreakPips * pip;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
bool haveTwoSwings = (SwingHighCountTF[slot] >= 2 && SwingLowCountTF[slot] >= 2);
double sh0 = (SwingHighCountTF[slot] >= 1) ? SwingHighPriceTF[slot][0] : 0.0;
double sh1 = (SwingHighCountTF[slot] >= 2) ? SwingHighPriceTF[slot][1] : 0.0;
double sl0 = (SwingLowCountTF[slot] >= 1) ? SwingLowPriceTF[slot][0] : 0.0;
double sl1 = (SwingLowCountTF[slot] >= 2) ? SwingLowPriceTF[slot][1] : 0.0;
int newTrend = oldTrend;
if(haveTwoSwings)
{
if(sh0 > sh1 && sl0 > sl1) newTrend = 1;
else if(sh0 < sh1 && sl0 < sl1) newTrend = -1;
else newTrend = 0;
}
else
{
newTrend = oldTrend;
}
double lastClosedClose = iClose(symbol, timeframe, 1);
if(lastClosedClose == 0.0) lastClosedClose = iClose(symbol, timeframe, 0);
int provisionalTrend = 0;
if(sh0 != 0.0 && lastClosedClose > sh0 + provisionalThresh)
provisionalTrend = 1;
else if(sl0 != 0.0 && lastClosedClose < sl0 - provisionalThresh)
provisionalTrend = -1;
if(provisionalTrend != 0)
{
int countClosers = 0;
for(int i = 1; i <= ProvisionalConsecCloses; i++)
{
double c = iClose(symbol, timeframe, i);
if(c == 0.0) break;
if(provisionalTrend == 1)
{
if(c > sh0 + provisionalThresh) countClosers++;
else break;
}
else
{
if(c < sl0 - provisionalThresh) countClosers++;
else break;
}
}
if(countClosers >= ProvisionalConsecCloses)
{
if(!haveTwoSwings || newTrend == 0)
{
newTrend = provisionalTrend;
}
}
}
TrendTF[slot] = newTrend;
}
uint MakeARGB(int a, uint clr)
{
if(a < 0) a = 0; if(a > 255) a = 255;
return ((uint)a << 24) | (clr & 0x00FFFFFF);
}
void DrawFVG(string symbol, ENUM_TIMEFRAMES timeframe, bool startFromCBar = true)
{
if(!ShowSwingMarkers) return;
string prefix = SwingObjPrefix + "FVG_";
int tot = ObjectsTotal(0);
for(int i = tot - 1; i >= 0; i--)
{
string nm = ObjectName(0, i);
if(StringFind(nm, prefix, 0) == 0)
ObjectDelete(0, nm);
}
if(MTF_FVG_count <= 0) return;
datetime bar_secs = (datetime)PeriodSeconds(timeframe);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
for(int k = 0; k < MTF_FVG_count; k++)
{
double top = MTF_FVGs[k].topPrice;
double bottom = MTF_FVGs[k].bottomPrice;
datetime timeC = MTF_FVGs[k].timebarC;
datetime timeA = MTF_FVGs[k].timebarA;
datetime start_time = startFromCBar ? timeC : timeA;
int idxC = iBarShift(symbol, timeframe, timeC, false);
if(idxC == -1) idxC = 0;
bool touched = false;
datetime end_time = TimeCurrent();
for(int idx = idxC - 1; idx >= 0; idx--)
{
double highM = iHigh(symbol, timeframe, idx);
double lowM = iLow(symbol, timeframe, idx);
if(highM == 0 || lowM == 0) continue;
if(lowM <= top + tol && highM >= bottom - tol)
{
touched = true;
end_time = iTime(symbol, timeframe, idx);
break;
}
}
if(!touched)
{
datetime t0 = iTime(symbol, timeframe, 0);
if(t0 != 0) end_time = t0 + bar_secs;
else end_time = TimeCurrent();
}
if(end_time <= start_time) end_time = (datetime)( (long)start_time + (long)bar_secs );
string obj = prefix + IntegerToString(k);
if(!ObjectCreate(0, obj, OBJ_RECTANGLE, 0, start_time, top, end_time, bottom))
{
continue;
}
ObjectSetInteger(0, obj, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, obj, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, obj, OBJPROP_BACK, true);
uint fill_alpha = 80;
uint border_alpha = 0;
uint color_fill;
if(MTF_FVGs[k].type == 1) color_fill = MakeARGB((int)fill_alpha, clrDodgerBlue);
else color_fill = MakeARGB((int)fill_alpha, clrCrimson);
uint col_border = MakeARGB((int)border_alpha, clrBlack);
ObjectSetInteger(0, obj, OBJPROP_COLOR, (int)col_border);
ObjectSetInteger(0, obj, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, obj, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, obj, OBJPROP_COLOR, (int)color_fill);
}
}
// ------------------------------------------------------------
// UpdateLabel: vẽ label cho 1 timeframe bất kỳ
// ------------------------------------------------------------
void UpdateLabel(string labelName, ENUM_TIMEFRAMES timeframe, int trend)
{
if(!ShowSwingMarkers) return;
string txt = StringFormat("%s: %s", EnumToString(timeframe), TrendToString(trend));
int corner = CORNER_LEFT_UPPER;
int xdist = 10;
int ydist_default = 20;
int ydist = ydist_default;
int offset_down = 30;
if (timeframe == MiddleTF) ydist = ydist_default + offset_down;
else if(timeframe == LowTF) ydist = ydist_default + offset_down * 2;
if(ObjectFind(0, labelName) >= 0)
{
ObjectSetString(0, labelName, OBJPROP_TEXT, txt);
if(trend == 1) ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrLime);
else if(trend == -1) ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrRed);
else ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrGray);
}
else
{
if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0))
{
return;
}
ObjectSetInteger(0, labelName, OBJPROP_CORNER, corner);
ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, xdist);
ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, ydist);
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, SwingMarkerFontSize);
ObjectSetString(0, labelName, OBJPROP_TEXT, txt);
if(trend == 1) ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrLime);
else if(trend == -1) ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrRed);
else ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrGray);
}
}
// Trả true nếu bar đóng mới xuất hiện (dựa vào iTime(...,1))
bool IsNewClosedBar(string symbol, ENUM_TIMEFRAMES tf, int slot)
{
datetime t = iTime(symbol, tf, 1);
if(t == -1 || t == 0) return false;
if(t != lastClosedBarTime[slot])
{
lastClosedBarTime[slot] = t;
return true;
}
return false;
}
void EnsureFVGUpToDate(string symbol, ENUM_TIMEFRAMES tf, int lookback)
{
if(tf != MiddleTF) return;
datetime closed = iTime(symbol, tf, 1);
if(closed == 0) return;
if(closed != lastFVGBarTime)
{
UpdateMTFFVGs(symbol, tf, lookback);
lastFVGBarTime = closed;
}
}
// VẼ các internal FVG trên timeframe tf nếu timeC nằm giữa sweep_time và break_time
void DrawInternalFVGMatches(string sym, ENUM_TIMEFRAMES tf, datetime sweep_time, datetime break_time, double pmin, double pmax, int lookback)
{
// lấy tất cả internal FVG trên tf
double tops[]; double bottoms[]; datetime timeA[]; datetime timeC[]; int types[];
int cnt = FindInternalFVG(sym, tf, lookback, tops, bottoms, timeA, timeC, types);
if(cnt <= 0) return;
double point = SymbolInfoDouble(sym, SYMBOL_POINT);
double tol = (point > 0.0) ? point * 0.5 : 0.0;
datetime bar_secs = (datetime)PeriodSeconds(tf);
for(int i=0; i<cnt; i++)
{
datetime tc = timeC[i];
if(tc < sweep_time || tc > break_time) continue; // đảm bảo timeC strictly giữa sweep & break
double top = tops[i];
double bottom = bottoms[i];
// kiểm tra overlap phần giá (nếu hoàn toàn ngoài cửa sổ pmin..pmax thì bỏ)
if(bottom > pmax + tol || top < pmin - tol) continue;
// tạo tên object duy nhất dựa trên timeC và index
string nm = SwingObjPrefix + "INTFVG_" + IntegerToString(types[i]) + "_" + IntegerToString(i) + "_" + IntegerToString((int)tc);
// Nếu đã tồn tại thì bỏ qua (tránh duplicate)
if(ObjectFind(0, nm) >= 0) continue;
// start từ bar A tới bar C + 1 bar để hiển thị rõ (tùy bạn có thể dùng timeA[i] hoặc timeC[i])
datetime start_time = timeA[i];
datetime end_time = (datetime)((long)tc + (long)bar_secs); // show zone tới C bar end
// Tạo rectangle
if(!ObjectCreate(0, nm, OBJ_RECTANGLE, 0, start_time, top, end_time, bottom))
continue;
// set style similar DrawFVG
uint fill_alpha = 80;
uint color_fill = (types[i] == 1) ? MakeARGB((int)fill_alpha, C'229,255,30') : MakeARGB((int)fill_alpha, clrCrimson);
uint col_border = MakeARGB(0, clrBlack);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, nm, OBJPROP_BACK, true);
ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, nm, OBJPROP_STYLE, STYLE_SOLID);
// fill color
ObjectSetInteger(0, nm, OBJPROP_COLOR, (int)color_fill); // MQL may reuse OBJPROP_COLOR for fill here like in DrawFVG
}
}
void DetectMSSOnTimeframe(
string sym,
ENUM_TIMEFRAMES tf,
int slot,
bool requireFVG,
double minBreakPips,
int consecRequired,
int lookForwardBars,
int fvgLookback
) {
// ===== REQUIRE WATCHING FVG BEFORE MSS =====
if(!watchingMSSMode) return; // chỉ xét MSS khi đang ở watching mode
if(tf != LowTF) return; // chỉ xét MSS trên LTF
if(BOS_Count[slot] < 2) return;
// ===============================
// 1️⃣ LẤY 2 BOS GẦN NHẤT & SẮP THỨ TỰ THỜI GIAN
// ===============================
BOSInfo b0 = BOSStore[slot][0];
BOSInfo b1 = BOSStore[slot][1];
BOSInfo olderBOS, newerBOS;
if(b0.break_time < b1.break_time)
{
olderBOS = b0;
newerBOS = b1;
}
else
{
olderBOS = b1;
newerBOS = b0;
}
if(!olderBOS.found || !newerBOS.found) return;
if(olderBOS.direction == newerBOS.direction) return;
datetime sweep_time = olderBOS.break_time;
datetime break_time = newerBOS.break_time;
if(sweep_time == 0 || break_time == 0 || break_time <= sweep_time)
return;
// ===============================
// 2️⃣ XÁC ĐỊNH GIÁ QUÉT THỰC (WICK)
// ===============================
int sweepIdx = iBarShift(sym, tf, sweep_time, false);
if(sweepIdx < 0) return;
double sweepExtreme = 0.0;
if(newerBOS.direction == 1)
sweepExtreme = iLow(sym, tf, sweepIdx); // bullish MSS → quét LOW
else
sweepExtreme = iHigh(sym, tf, sweepIdx); // bearish MSS → quét HIGH
if(sweepExtreme == 0.0) return;
// ===============================
// 2️⃣.1 VALIDATE LIQUIDITY SWEEP (ATR-based)
// ===============================
bool isHighSweep = (newerBOS.direction == -1); // bearish MSS quét HIGH
if(!IsValidLiquiditySweep(sym, tf, sweepIdx, isHighSweep))
{
if(PrintEntryLog)
Print("MSS rejected: invalid liquidity sweep (ATR filter)");
return;
}
// ===============================
// 3️⃣ SWING BỊ QUÉT & SWING BỊ PHÁ
// ===============================
double sweptSwingPrice = olderBOS.broken_sw_price;
datetime sweptSwingTime = olderBOS.broken_sw_time;
double brokenSwingPrice = newerBOS.broken_sw_price;
datetime brokenSwingTime = newerBOS.broken_sw_time;
if(sweptSwingPrice == 0.0 || brokenSwingPrice == 0.0)
return;
// ===============================
// 4️⃣ KIỂM TRA MSS HỢP LỆ (ICT CORE)
// ===============================
double tol = GetPipSize(sym) * 0.1;
if(newerBOS.direction == 1)
{
// Bullish MSS:
// - Wick quét sâu hơn swing low
// - Sau đó phá swing high
if(sweepExtreme >= sweptSwingPrice - tol)
return;
}
else
{
// Bearish MSS
if(sweepExtreme <= sweptSwingPrice + tol)
return;
}
// ===============================
// 4️⃣.1 MSS STRENGTH VALIDATION (ATR + BODY)
// ===============================
if(!IsValidMSS(
sym,
tf,
newerBOS.confirm_bar_index,
newerBOS.broken_sw_price,
newerBOS.direction == 1
))
{
if(PrintEntryLog)
Print("MSS rejected: weak break (ATR/body filter)");
return;
}
// ===============================
// 5️⃣ REQUIRE FVG (GIỮ LOGIC CŨ)
// ===============================
if(requireFVG)
{
bool hasFVG = false;
if(newerBOS.direction == 1)
hasFVG = HasBullFVGBetween(sym, tf, sweep_time, break_time, fvgLookback);
else
hasFVG = HasBearFVGBetween(sym, tf, sweep_time, break_time, fvgLookback);
if(!hasFVG)
return;
}
// ===============================
// 6️⃣ TẠO MSS (STRUCT MỚI CLEAN)
// ===============================
MSS mss;
mss.found = true;
mss.direction = newerBOS.direction;
mss.sweep_time = sweep_time;
mss.sweep_extreme_price = sweepExtreme;
mss.swept_swing_time = sweptSwingTime;
mss.swept_swing_price = sweptSwingPrice;
mss.break_time = break_time;
mss.bos_close_price = newerBOS.break_price;
mss.broken_swing_time = brokenSwingTime;
mss.broken_swing_price = brokenSwingPrice;
// ===============================
// 7️⃣ WATCHING MODE → ENTRY CHECK
// ===============================
if(watchingMSSMode && watchingFVGIndex >= 0)
{
if(mss.direction == watchingFVGDir)
{
bool valid = CheckValidEntry(
mss.direction,
TrendTF[0], // HTF
TrendTF[1], // MTF
watchingFVGIndex
);
if(valid)
{
if(PrintEntryLog)
Print(">>> ENTRY CONDITIONS PASSED → SETUP ENTRY. Disable watchingMSSMode.");
SetUpPendingEntryForMSS(mss, slot);
MTF_FVGs[watchingFVGIndex].used = true;
ToggleWatchingMSSMode(false);
}
else
{
if(PrintEntryLog)
Print(">>> ENTRY BLOCKED by CheckValidEntry()");
}
}
}
// ===============================
// 8️⃣ VẼ MSS
// ===============================
DrawMss(sym, tf, mss, slot);
}
bool CheckValidEntry(
int mssDirection,
int htfTrend,
int mtfTrend,
int fvgIndex
)
{
// =============================
// 0. Sanity check
// =============================
if(mssDirection == 0)
return false;
// HTF phải có trend rõ ràng // TEST
// if(htfTrend == 0)
// {
// if(PrintEntryLog)
// Print("CheckValidEntry FAIL: HTF is SIDEWAY");
// return false;
// }
if(fvgIndex < 0 || fvgIndex >= MTF_FVG_count)
{
if(PrintEntryLog)
Print("CheckValidEntry: invalid FVG index");
return false;
}
int fvgDir = MTF_FVGs[fvgIndex].type; // 1 bull, -1 bear
// =============================
// 1. MSS direction == FVG direction
// (thường đã đúng do watchingMSSMode,
// nhưng giữ lại cho chắc)
// =============================
if(mssDirection != fvgDir)
{
if(PrintEntryLog)
PrintFormat(
"CheckValidEntry FAIL: MSS dir=%d != FVG dir=%d",
mssDirection, fvgDir
);
return false;
}
// =============================
// 2. FVG direction thuận MTF trend
// =============================
if(mtfTrend != 0 && fvgDir != mtfTrend)
{
if(PrintEntryLog)
PrintFormat(
"CheckValidEntry FAIL: FVG dir=%d not aligned with MTF trend=%d",
fvgDir, mtfTrend
);
return false;
}
// =============================
// 3. MTF trend thuận HTF trend
// =============================
if(htfTrend != 0 && mtfTrend != 0 && htfTrend != mtfTrend)
{
if(PrintEntryLog)
PrintFormat(
"CheckValidEntry FAIL: MTF trend=%d not aligned with HTF trend=%d",
mtfTrend, htfTrend
);
return false;
}
// =============================
// PASS ALL CONDITIONS
// =============================
if(PrintEntryLog)
{
PrintFormat(
"CheckValidEntry PASS: dir=%d | FVG=%d | MTF trend=%d | HTF trend=%d",
mssDirection, fvgDir, mtfTrend, htfTrend
);
}
return true;
}
void HandleLogicForTimeframe(string sym, ENUM_TIMEFRAMES tf, int slot,
int swingRange, bool requireFVG,
double minBreakPips, int minConsec,
int lookbackBarsForSweep, int fvgLookback)
{
UpdateSwings(sym, tf, slot, swingRange);
UpdateTrendForSlot(slot, tf, sym);
HandleBOSDetections(sym, tf, slot);
if(tf == MiddleTF) EnsureFVGUpToDate(sym, MiddleTF, fvgLookback);
DetectMSSOnTimeframe(sym, tf, slot, requireFVG, minBreakPips, minConsec, lookbackBarsForSweep, fvgLookback);
if(ShowSwingMarkers)
{
if (tf == LowTF) DrawSwingsOnChart(tf);
if(tf == MiddleTF) DrawFVG(sym, MiddleTF, true);
}
UpdateLabel((tf == HighTF) ? "LBL_HTF" : (tf == MiddleTF) ? "LBL_MTF" : "LTF: TRIGGER", tf, TrendTF[slot]);
}
// Clear & reset pending entry (xóa visuals nếu có)
void ClearPendingEntry()
{
if(pendingEntry.fvgIndex >= 0 && pendingEntry.fvgIndex < MTF_FVG_count) {
MTF_FVGs[pendingEntry.fvgIndex].used = true; // FVG đã cho entry nhưng không khớp lệnh
}
if(!pendingEntry.active && StringLen(pendingEntry.compositeName)==0) {
return;
}
if(StringLen(pendingEntry.compositeName) > 0) {
string parts[];
int n = StringSplit(pendingEntry.compositeName, '|', parts);
for(int i=0;i<n;i++)
{
string nm = parts[i];
if(StringLen(nm) > 0 && ObjectFind(0, nm) >= 0) ObjectDelete(0, nm);
}
}
pendingEntry.active = false;
pendingEntry.direction = 0;
pendingEntry.price = 0.0;
pendingEntry.fvgIndex = -1;
pendingEntry.compositeName = "";
pendingEntry.created_time = 0;
pendingEntry.source_slot = -1;
if(PrintEntryLog) Print("PendingEntry CLEARED");
}
// CalculateLotSizeForRisk:
// - symbol: symbol
// - entryPrice, slPrice: điểm entry và sl (giá thực tế)
// - riskPercent: ví dụ 1.0 cho 1% equity
// Trả về lotsize phù hợp (đã được clamp theo min/max/step)
double CalculateLotSizeForRisk(string symbol, double entryPrice, double slPrice, double riskPercent)
{
double pipValue = GetPipSize(symbol); // 1 pip in price units
double distance = MathAbs(entryPrice - slPrice); // in price units
if(distance <= 0.0) return 0.0;
// Use account equity as base
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
if(equity <= 0.0) equity = AccountInfoDouble(ACCOUNT_BALANCE);
if(equity <= 0.0) return 0.0;
double riskAmount = equity * (riskPercent / 100.0);
// Obtain tick size/value info from symbol
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
// Defensive fallback: if tick_size/tick_value unavailable, try point-based approximation
double valuePerPointPerLot = 0.0;
if(tick_size > 0.0 && tick_value > 0.0)
{
valuePerPointPerLot = tick_value / tick_size;
}
else
{
// approximate: use point and contract size
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
// assume valuePerPointPerLot ~ 10 (very rough) -> but better to abort
// safer: abort by returning 0 if we don't have reliable tick info
return 0.0;
}
// value risk per lot = distance (price units) * valuePerPointPerLot
double valueRiskPerLot = distance * valuePerPointPerLot;
if(valueRiskPerLot <= 0.0) return 0.0;
double lots = riskAmount / valueRiskPerLot;
// clamp to symbol lot limits and step
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(minLot <= 0.0 || stepLot <= 0.0)
{
// fallback defaults if broker info missing
minLot = 0.01;
stepLot = 0.01;
maxLot = 100.0;
}
// Normalize lots to nearest step
double stepInv = MathRound(lots / stepLot);
double lotsNorm = stepInv * stepLot;
// ensure within min/max
if(lotsNorm < minLot) lotsNorm = minLot;
if(lotsNorm > maxLot) lotsNorm = maxLot;
// final safety: round to allowed decimals (step determines decimals)
int stepDigits = 0;
{
double tmp = stepLot;
while(tmp < 1.0 && stepDigits < 8)
{
tmp *= 10.0;
stepDigits++;
}
}
lotsNorm = NormalizeDouble(lotsNorm, stepDigits);
return lotsNorm;
}
// PlacePendingOrderFromPendingEntry:
// - Gọi DrawPendingEntryVisuals để vẽ entry/sl/tp
// - Gửi pending order (BUY_LIMIT nếu pendingEntry.direction==1, SELL_LIMIT nếu -1)
// - Lưu ticket vào pendingEntry.order_ticket nếu thành công
// - Trả về true nếu order đặt thành công, false nếu lỗi
bool PlacePendingOrderFromPendingEntry()
{
string sym = Symbol();
if(!pendingEntry.active)
{
if(PrintEntryLog) Print("PlacePendingOrderFromPendingEntry: abort - pendingEntry.active == false");
return false;
}
if(pendingEntry.price <= 0.0 || pendingEntry.lotSize <= 0.0)
{
if(PrintEntryLog) PrintFormat("PlacePendingOrderFromPendingEntry: abort - invalid price/lotsize (price=%.10f lots=%.4f)", pendingEntry.price, pendingEntry.lotSize);
return false;
}
// Chuẩn bị trade request
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_PENDING; // đặt lệnh pending
request.symbol = sym;
request.volume = pendingEntry.lotSize;
request.deviation= 10; // acceptable slippage in points (bạn chỉnh nếu muốn)
request.magic = 123456; // chỉnh magic number nếu bạn dùng khác
request.comment = "PEND_BY_MSS_OB";
// normalize prices
int digs = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
double price = NormalizeDouble(pendingEntry.price, digs);
double sl = (pendingEntry.sl_price != 0.0) ? NormalizeDouble(pendingEntry.sl_price, digs) : 0.0;
double tp = (pendingEntry.tp_price != 0.0) ? NormalizeDouble(pendingEntry.tp_price, digs) : 0.0;
if(pendingEntry.direction == 1)
{
request.type = ORDER_TYPE_BUY_LIMIT;
request.price = price;
}
else
{
request.type = ORDER_TYPE_SELL_LIMIT;
request.price = price;
}
// set stoploss/takeprofit as absolute prices
request.sl = sl;
request.tp = tp;
// optional: set expiration (0 = good till canceled)
request.expiration = 0;
// 3) Send request
if(!OrderSend(request, result))
{
// OrderSend failed to execute (interface error)
if(PrintEntryLog) PrintFormat("PlacePendingOrderFromPendingEntry: OrderSend() returned false. result.retcode=%d retcode_external=%d", result.retcode, result.retcode_external);
return false;
}
// 4) Check result.retcode for success codes (10009 etc.)
// Success for pending order is typically TRADE_RETCODE_DONE (10008) or TRADE_RETCODE_DONE_REMAINDER etc.
if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED || result.retcode == 10006 || result.retcode == 10008 || result.retcode == 10009)
{
// store ticket
pendingEntry.orderTicket = result.order;
if(PrintEntryLog) PrintFormat("PlacePendingOrderFromPendingEntry: SUCCESS ticket=%I64u (retcode=%d) entry=%.10f sl=%.10f tp=%.10f lots=%.4f",
pendingEntry.orderTicket, result.retcode, price, sl, tp, pendingEntry.lotSize);
return true;
}
else
{
// failure; log reason
if(PrintEntryLog)
{
PrintFormat("PlacePendingOrderFromPendingEntry: FAILED retcode=%d retval=%d result_comment=%s",
result.retcode, result.retcode, result.comment);
}
return false;
}
}
void SetUpPendingEntryForMSS(const MSS &mss, int slot)
{
string symbol = Symbol();
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double pip = GetPipSize(symbol);
// ===============================
// 0️⃣ Sanity check
// ===============================
if(!mss.found || mss.sweep_time == 0 || mss.break_time == 0)
return;
// ===============================
// 1️⃣ FIND ALL LTF INTERNAL FVGs
// ===============================
double fvgTopPrices[];
double fvgBottomPrices[];
datetime fvgTimeA[];
datetime fvgTimeC[];
int fvgDirections[];
int totalLtfFVGs = FindInternalFVG(
symbol,
LowTF,
100, // LTF lookback
fvgTopPrices,
fvgBottomPrices,
fvgTimeA,
fvgTimeC,
fvgDirections
);
if(totalLtfFVGs <= 0)
return;
// ===============================
// 2️⃣ SELECT FVG:
// - same direction as MSS
// - timeC strictly between sweep & break
// - closest to break_time
// ===============================
int selectedFvgIndex = -1;
datetime selectedFvgTime = 0;
for(int i = 0; i < totalLtfFVGs; i++)
{
// direction must match MSS
if(fvgDirections[i] != mss.direction)
continue;
// must be between sweep & break
if(fvgTimeC[i] <= mss.sweep_time || fvgTimeC[i] >= mss.break_time)
continue;
// choose the one closest to break_time
if(selectedFvgIndex == -1 || fvgTimeC[i] > selectedFvgTime)
{
selectedFvgIndex = i;
selectedFvgTime = fvgTimeC[i];
}
}
if(selectedFvgIndex == -1)
{
if(PrintEntryLog)
Print("SetUpPendingEntryForMSS: no valid LTF FVG between sweep & break");
return;
}
// ===============================
// 3️⃣ ENTRY = 50% OF SELECTED FVG
// ===============================
double fvgTop = fvgTopPrices[selectedFvgIndex];
double fvgBottom = fvgBottomPrices[selectedFvgIndex];
double retrace = EntryFvgRetrace;
// trend mạnh → entry sớm hơn
if(TrendTF[0] == TrendTF[1] && TrendTF[1] == mss.direction)
retrace = 0.2;
else
retrace = EntryFvgRetrace;
double entryPrice = NormalizeDouble(
fvgBottom + (fvgTop - fvgBottom) * retrace,
digits
);
// ===============================
// 4️⃣ STOP LOSS = LIQUIDITY SWEEP
// ===============================
double stopLoss = mss.sweep_extreme_price;
double slBuffer = 1.0 * pip; // safety buffer
if(mss.direction == 1)
{
stopLoss -= slBuffer;
if(stopLoss >= entryPrice)
return;
}
else
{
stopLoss += slBuffer;
if(stopLoss <= entryPrice)
return;
}
stopLoss = NormalizeDouble(stopLoss, digits);
// ===============================
// 5️⃣ TAKE PROFIT = RR
// ===============================
double riskDistance = MathAbs(entryPrice - stopLoss);
if(riskDistance <= 0.0)
return;
double takeProfit;
if(mss.direction == 1)
takeProfit = entryPrice + RiskRewardRatio * riskDistance;
else
takeProfit = entryPrice - RiskRewardRatio * riskDistance;
takeProfit = NormalizeDouble(takeProfit, digits);
// ===============================
// 6️⃣ LOT SIZE BY RISK
// ===============================
double lotSize = CalculateLotSizeForRisk(
symbol,
entryPrice,
stopLoss,
RishPercent
);
if(lotSize <= 0.0)
return;
// ===============================
// 7️⃣ STORE PENDING ENTRY
// ===============================
ClearPendingEntry();
pendingEntry.active = true;
pendingEntry.direction = mss.direction;
pendingEntry.price = entryPrice;
pendingEntry.sl_price = stopLoss;
pendingEntry.tp_price = takeProfit;
pendingEntry.lotSize = lotSize;
pendingEntry.created_time = TimeCurrent();
pendingEntry.source_slot = slot;
pendingEntry.fvgIndex = -1; // LTF FVG, không phải MTF
pendingEntry.orderTicket = 0;
if(PrintEntryLog)
{
PrintFormat(
"LTF FVG ENTRY SET | dir=%d | entry=%.5f | SL=%.5f | TP=%.5f | FVG time=%s",
mss.direction,
entryPrice,
stopLoss,
takeProfit,
TimeToString(selectedFvgTime, TIME_DATE|TIME_MINUTES)
);
}
// ===============================
// 8️⃣ PLACE LIMIT ORDER
// ===============================
PlacePendingOrderFromPendingEntry();
}
void MoveStoploss()
{
string sym = Symbol();
// EA chỉ quản lý 1 position cho symbol hiện tại
if(!PositionSelect(sym))
return;
ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
if(entry <= 0.0 || sl <= 0.0)
return;
// Nếu SL đã >= entry (BUY) hoặc <= entry (SELL) → đã BE rồi
if(type == POSITION_TYPE_BUY && sl >= entry)
return;
if(type == POSITION_TYPE_SELL && sl <= entry)
return;
double R = MathAbs(entry - sl);
if(R <= 0.0)
return;
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
bool shouldMove = false;
// ======= ĐIỀU KIỆN BE DỰA TRÊN numberOfRToMoveToEntry =======
if(type == POSITION_TYPE_BUY)
{
if(bid >= entry + numberOfRToMoveToEntry * R)
shouldMove = true;
}
else if(type == POSITION_TYPE_SELL)
{
if(ask <= entry - numberOfRToMoveToEntry * R)
shouldMove = true;
}
if(!shouldMove)
return;
// ---- MODIFY POSITION SL -> ENTRY ----
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.symbol = sym;
req.sl = NormalizeDouble(entry, (int)SymbolInfoInteger(sym, SYMBOL_DIGITS));
req.tp = tp; // giữ nguyên TP
if(!OrderSend(req, res))
{
if(PrintEntryLog)
PrintFormat("MoveStoploss: OrderSend failed ticket=%I64u", ticket);
return;
}
if(res.retcode == TRADE_RETCODE_DONE)
{
if(PrintEntryLog)
PrintFormat("MoveStoploss: SL moved to BE at %.5f (%.2fR), ticket=%I64u",
entry, numberOfRToMoveToEntry, ticket);
}
else
{
if(PrintEntryLog)
PrintFormat("MoveStoploss: failed retcode=%d ticket=%I64u",
res.retcode, ticket);
}
}
void CancelExpiredLimitOrder()
{
if(!pendingEntry.active) return;
if(pendingEntry.orderTicket == 0) return;
if(pendingEntry.created_time == 0) return;
ulong ticket = pendingEntry.orderTicket;
// Nếu order không còn tồn tại → đã khớp hoặc bị xóa
if(!OrderSelect(ticket))
{
if(PrintEntryLog)
Print("Pending LIMIT no longer exists → cleared");
ClearPendingEntry();
return;
}
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
// Chỉ xử lý LIMIT
if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT)
return;
datetime now = TimeCurrent();
int aliveSec = (int)(now - pendingEntry.created_time);
int maxSec = MaxLimitOrderTime * 60;
if(aliveSec < maxSec)
return; // chưa quá hạn
// ---- HỦY LỆNH LIMIT ----
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_REMOVE;
req.order = ticket;
if(!OrderSend(req, res))
{
if(PrintEntryLog)
PrintFormat("Cancel LIMIT failed (OrderSend=false), ticket=%I64u", ticket);
return;
}
if(res.retcode == TRADE_RETCODE_DONE)
{
if(PrintEntryLog)
PrintFormat("LIMIT cancelled after %d minutes, ticket=%I64u",
MaxLimitOrderTime, ticket);
ClearPendingEntry();
}
else
{
if(PrintEntryLog)
PrintFormat("Cancel LIMIT failed retcode=%d ticket=%I64u",
res.retcode, ticket);
}
}
void MoveStoplossAdvanced()
{
string sym = Symbol();
// chỉ quản lý 1 position cho symbol
if(!PositionSelect(sym))
return;
ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
ENUM_POSITION_TYPE type =
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
if(entry <= 0.0 || sl <= 0.0)
return;
// ===== R BAN ĐẦU =====
double R = MathAbs(entry - sl);
if(R <= 0.0)
return;
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
// ===== PROFIT HIỆN TẠI (theo R) =====
double profitR = 0.0;
if(type == POSITION_TYPE_BUY)
profitR = (bid - entry) / R;
else
profitR = (entry - ask) / R;
// ===== CHƯA ĐỦ ĐIỀU KIỆN BE =====
if(profitR < trailingStopStartR)
return;
int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
double newSL = sl;
// =========================================================
// 1️⃣ BE PHASE
// =========================================================
if(profitR >= trailingStopStartR && profitR < (trailingStopStartR + trailOffsetR))
{
newSL = entry;
}
else
{
// =========================================================
// 2️⃣ STEP TRAIL PHASE (theo bậc R)
// =========================================================
int step = (int)MathFloor(
(profitR - trailingStopStartR) / trailOffsetR
);
double targetR = step * trailOffsetR;
if(type == POSITION_TYPE_BUY)
newSL = entry + targetR * R;
else
newSL = entry - targetR * R;
}
newSL = NormalizeDouble(newSL, digits);
// ===== KHÔNG CHO SL ĐI LÙI =====
if(type == POSITION_TYPE_BUY && newSL <= sl)
return;
if(type == POSITION_TYPE_SELL && newSL >= sl)
return;
// ===== GỬI MODIFY =====
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.symbol = sym;
req.sl = newSL;
req.tp = tp;
if(!OrderSend(req, res))
{
if(PrintEntryLog)
PrintFormat("MoveSL ICT-C: OrderSend failed ticket=%I64u", ticket);
return;
}
if(res.retcode == TRADE_RETCODE_DONE && PrintEntryLog)
{
PrintFormat(
"MoveSL ICT-C: SL -> %.5f | profitR=%.2fR | step=%d",
newSL, profitR,
(int)MathFloor((profitR - trailingStopStartR) / trailOffsetR)
);
}
}
bool IsEntryTimeAllowed()
{
datetime now = TimeCurrent();
MqlDateTime t;
TimeToStruct(now, t);
int hour = t.hour;
// ===== LONDON =====
if(hour >= LondonStartHourVN && hour < LondonEndHourVN)
return true;
// ===== NEW YORK =====
if(hour >= NYStartHourVN && hour < NYEndHourVN)
return true;
return false;
}
bool IsAllowedToTrade()
{
UpdateDailyEquity();
if(IsDailyLossExceeded())
return false;
// ===============================
// REGIME FILTER TREND REQUIRED
// ===============================
double adx = GetADX(_Symbol, MiddleTF, 14, 1); // dùng bar đã đóng
if(adx < ADX_Min)
{
if(PrintEntryLog)
PrintFormat("Trade blocked: ADX %.2f < %d", adx, ADX_Min);
return false;
}
if(!IsEntryTimeAllowed())
return false;
if(EnableNewsFilter &&
IsHighImpactNewsNear(_Symbol,
NewsBlockBeforeMin,
NewsBlockAfterMin))
return false;
return true;
}
void OnTick() {
string sym = Symbol();
CancelExpiredLimitOrder();
if (numberOfRToMoveToEntry > 0) {
// MoveStoploss();
MoveStoplossAdvanced();
}
if(IsNewClosedBar(sym, LowTF, 2)) {
// if(!IsAllowedToTrade()) //TEST
// return;
HandleLogicForTimeframe(sym, HighTF, 0, htfSwingRange, false, 10.0, 2, 50, FVGLookback);
HandleLogicForTimeframe(sym, MiddleTF, 1, mtfSwingRange, true, 10.0, 2, 50, FVGLookback);
HandleLogicForTimeframe(sym, LowTF, 2, ltfSwingRange, true, 10.0, 2, 50, FVGLookback);
if (watchingMSSMode) {
CheckWatchMSSInvalidation(sym);
}
}
UpdateMTFFVGTouched(sym);
}
void MarkHistoricalTouchedFVGs(string symbol)
{
if(MTF_FVG_count <= 0) return;
for(int i = 0; i < MTF_FVG_count; i++)
{
// reset mặc định
MTF_FVGs[i].touched = false;
MTF_FVGs[i].used = false;
MTF_FVGs[i].touchTime = 0;
MTF_FVGs[i].touchPrice = 0;
}
// Dùng logic đã có, nhưng scan TOÀN BỘ LTF history
for(int i = 0; i < MTF_FVG_count; i++)
{
datetime mtfC = MTF_FVGs[i].timebarC;
if(mtfC == 0) continue;
int idxC = iBarShift(symbol, LowTF, mtfC, false);
if(idxC <= 0) continue;
double top = MTF_FVGs[i].topPrice;
double bottom = MTF_FVGs[i].bottomPrice;
int dir = MTF_FVGs[i].type;
for(int idx = idxC - 1; idx >= 0; idx--)
{
double h = iHigh(symbol, LowTF, idx);
double l = iLow(symbol, LowTF, idx);
if(h == 0 || l == 0) continue;
bool touched =
(dir == 1 && h >= top) ||
(dir == -1 && l <= bottom);
if(touched)
{
MTF_FVGs[i].touched = true;
MTF_FVGs[i].used = true; // 🔥 QUAN TRỌNG
MTF_FVGs[i].touchTime = iTime(symbol, LowTF, idx);
MTF_FVGs[i].touchPrice= (dir == 1 ? top : bottom);
break;
}
}
}
}
void InitMTFFVGs(string symbol, ENUM_TIMEFRAMES tf, int lookback)
{
ArrayFree(MTF_FVGs);
MTF_FVG_count = 0;
int total = iBars(symbol, tf);
if(total < 5) return;
int maxScan = MathMin(lookback, total - 3);
double tol = SymbolInfoDouble(symbol, SYMBOL_POINT) * 0.5;
for(int i = 1; i <= maxScan; i++)
{
int idxA = i + 2;
int idxC = i;
if(idxA > total - 1) break;
double highA = iHigh(symbol, tf, idxA);
double lowA = iLow(symbol, tf, idxA);
double highC = iHigh(symbol, tf, idxC);
double lowC = iLow(symbol, tf, idxC);
if(highA == 0 || lowA == 0 || highC == 0 || lowC == 0) continue;
datetime timeC = iTime(symbol, tf, idxC);
// Bullish FVG
if(lowC > highA + tol)
{
int idx = MTF_FVG_count;
ArrayResize(MTF_FVGs, MTF_FVG_count + 1);
MTF_FVG_count++;
MTF_FVGs[idx].type = 1;
MTF_FVGs[idx].topPrice = lowC;
MTF_FVGs[idx].bottomPrice = highA;
MTF_FVGs[idx].timebarA = iTime(symbol, tf, idxA);
MTF_FVGs[idx].timebarC = timeC;
MTF_FVGs[idx].touched = false;
MTF_FVGs[idx].used = false;
MTF_FVGs[idx].touchTime = 0;
MTF_FVGs[idx].touchPrice = 0;
continue;
}
// Bearish FVG
if(highC < lowA - tol)
{
int idx = MTF_FVG_count;
ArrayResize(MTF_FVGs, MTF_FVG_count + 1);
MTF_FVG_count++;
MTF_FVGs[idx].type = -1;
MTF_FVGs[idx].topPrice = lowA;
MTF_FVGs[idx].bottomPrice = highC;
MTF_FVGs[idx].timebarA = iTime(symbol, tf, idxA);
MTF_FVGs[idx].timebarC = timeC;
MTF_FVGs[idx].touched = false;
MTF_FVGs[idx].used = false;
MTF_FVGs[idx].touchTime = 0;
MTF_FVGs[idx].touchPrice = 0;
}
}
}
void UpdateMTFFVGs(string symbol, ENUM_TIMEFRAMES tf, int lookback)
{
int total = iBars(symbol, tf);
if(total < 5) return;
int maxScan = MathMin(lookback, total - 3);
double tol = SymbolInfoDouble(symbol, SYMBOL_POINT) * 0.5;
for(int i = 1; i <= maxScan; i++)
{
int idxA = i + 2;
int idxC = i;
if(idxA > total - 1) break;
datetime timeC = iTime(symbol, tf, idxC);
if(timeC == 0) continue;
// CHECK DUPLICATE BY timebarC
bool exists = false;
for(int k = 0; k < MTF_FVG_count; k++)
{
if(MTF_FVGs[k].timebarC == timeC)
{
exists = true;
break;
}
}
if(exists) continue;
double highA = iHigh(symbol, tf, idxA);
double lowA = iLow(symbol, tf, idxA);
double highC = iHigh(symbol, tf, idxC);
double lowC = iLow(symbol, tf, idxC);
if(highA == 0 || lowA == 0 || highC == 0 || lowC == 0) continue;
if(lowC > highA + tol || highC < lowA - tol)
{
int idx = MTF_FVG_count;
ArrayResize(MTF_FVGs, MTF_FVG_count + 1);
MTF_FVG_count++;
MTF_FVGs[idx].type = (lowC > highA) ? 1 : -1;
MTF_FVGs[idx].topPrice = (MTF_FVGs[idx].type == 1) ? lowC : lowA;
MTF_FVGs[idx].bottomPrice = (MTF_FVGs[idx].type == 1) ? highA : highC;
MTF_FVGs[idx].timebarA = iTime(symbol, tf, idxA);
MTF_FVGs[idx].timebarC = timeC;
MTF_FVGs[idx].touched = false;
MTF_FVGs[idx].used = false;
MTF_FVGs[idx].touchTime = 0;
MTF_FVGs[idx].touchPrice = 0;
}
}
}
int OnInit()
{
string sym = Symbol();
InitMTFFVGs(sym, MiddleTF, FVGLookback);
MarkHistoricalTouchedFVGs(sym);
HandleLogicForTimeframe(sym, HighTF, 0, htfSwingRange, false, 10.0, 2, 50, FVGLookback);
HandleLogicForTimeframe(sym, MiddleTF, 1, mtfSwingRange, true, 10.0, 2, 50, FVGLookback);
HandleLogicForTimeframe(sym, LowTF, 2, ltfSwingRange, true, 10.0, 2, 50, FVGLookback);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
for(int s=0; s<3; s++)
{
for(int j=0; j<2; j++)
{
if(StringLen(BOS_Names[s][j]) > 0) DeleteCompositeBOS(BOS_Names[s][j]);
BOS_Names[s][j] = "";
BOS_Count[s] = 0;
BOSStore[s][j].found = false;
BOS_HaveZone[s][j] = false;
}
}
if(ObjectFind(0, "LBL_HTF") >= 0) ObjectDelete(0, "LBL_HTF");
if(ObjectFind(0, "LBL_MTF") >= 0) ObjectDelete(0, "LBL_MTF");
if(ObjectFind(0, "LTF: TRIGGER") >= 0) ObjectDelete(0, "LTF: TRIGGER");
int tot = ObjectsTotal(0);
for(int i = tot - 1; i >= 0; i--)
{
string nm = ObjectName(0, i);
if(StringFind(nm, SwingObjPrefix, 0) == 0)
ObjectDelete(0, nm);
}
}
double GetATR(string symbol, ENUM_TIMEFRAMES tf, int period, int shift)
{
static int atrHandle = INVALID_HANDLE;
static ENUM_TIMEFRAMES lastTF = PERIOD_CURRENT;
static int lastPeriod = 0;
// recreate handle if TF or period changed
if(atrHandle == INVALID_HANDLE || tf != lastTF || period != lastPeriod)
{
if(atrHandle != INVALID_HANDLE)
IndicatorRelease(atrHandle);
atrHandle = iATR(symbol, tf, period);
lastTF = tf;
lastPeriod = period;
}
if(atrHandle == INVALID_HANDLE)
return 0.0;
double buffer[];
if(CopyBuffer(atrHandle, 0, shift, 1, buffer) <= 0)
return 0.0;
return buffer[0];
}
double GetADX(string symbol, ENUM_TIMEFRAMES tf, int period, int shift)
{
static int adxHandle = INVALID_HANDLE;
static ENUM_TIMEFRAMES lastTF = PERIOD_CURRENT;
static int lastPeriod = 0;
// recreate handle if TF or period changed
if(adxHandle == INVALID_HANDLE || tf != lastTF || period != lastPeriod)
{
if(adxHandle != INVALID_HANDLE)
IndicatorRelease(adxHandle);
adxHandle = iADX(symbol, tf, period);
lastTF = tf;
lastPeriod = period;
}
if(adxHandle == INVALID_HANDLE)
return 0.0;
double buffer[];
// ADX MAIN = buffer index 0
if(CopyBuffer(adxHandle, 0, shift, 1, buffer) <= 0)
return 0.0;
return buffer[0];
}