Investigate FindInternalFvg
This commit is contained in:
+331
-105
@@ -31,9 +31,9 @@ struct PendingEntry
|
||||
PendingEntry pendingEntry;
|
||||
|
||||
// --- Watch MSS mode (user requested) ---
|
||||
bool watchMSSMode = false; // mặc định false
|
||||
int watchedFVGIndex = -1; // index trong FVG_* arrays (khi watchMSSMode==true)
|
||||
int watchedFVGDir = 0; // 1 = bullish FVG, -1 = bearish FVG
|
||||
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
|
||||
@@ -48,7 +48,7 @@ input bool DetectMSS_LTF = true;
|
||||
// Cấu hình Swing
|
||||
input int htfSwingRange = 2; // X: số nến trước và sau để xác định 1 đỉnh/đáy
|
||||
input int mtfSwingRange = 3; // X: số nến trước và sau để xác định 1 đỉnh/đáy
|
||||
input int ltfSwingRange = 2; // X: số nến trước và sau để xác định 1 đỉnh/đáy
|
||||
input int ltfSwingRange = 5; // X: số nến trước và sau để xác định 1 đỉnh/đáy
|
||||
input int MaxSwingKeep = 2; // Số đỉnh/đáy gần nhất cần lưu (bạn yêu cầu 2)
|
||||
|
||||
// Cấu hình vẽ Swing trên chart
|
||||
@@ -159,23 +159,23 @@ void UpdateMTFFVGTouched(string symbol)
|
||||
double tol = (point > 0.0) ? point * 0.5 : 0.0;
|
||||
|
||||
// scan each stored FVG
|
||||
for(int k = 0; k < FVG_count; k++)
|
||||
for(int fvgIndex = 0; fvgIndex < FVG_count; fvgIndex++)
|
||||
{
|
||||
// if already touched, skip (keeps first-touch)
|
||||
if(FVGs[k].touched) continue;
|
||||
// nếu đã touched trước đó -> bỏ qua luôn (không in gì cả)
|
||||
if(FVGs[fvgIndex].touched) continue;
|
||||
|
||||
datetime mtfC = FVGs[k].timebarC;
|
||||
datetime mtfC = FVGs[fvgIndex].timebarC;
|
||||
if(mtfC == 0) continue;
|
||||
|
||||
double top = FVGs[k].topPrice;
|
||||
double bottom = FVGs[k].bottomPrice;
|
||||
int dir = FVGs[k].type; // 1=bull, -1=bear
|
||||
double top = FVGs[fvgIndex].topPrice;
|
||||
double bottom = FVGs[fvgIndex].bottomPrice;
|
||||
int dir = 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 touched = false;
|
||||
bool touchedNow = false;
|
||||
datetime touch_time = 0;
|
||||
double touch_price = 0.0;
|
||||
|
||||
@@ -191,7 +191,7 @@ void UpdateMTFFVGTouched(string symbol)
|
||||
{
|
||||
if(hh >= top - tol)
|
||||
{
|
||||
touched = true;
|
||||
touchedNow = true;
|
||||
touch_time = iTime(symbol, LowTF, idx);
|
||||
touch_price = top; // representative edge price
|
||||
break;
|
||||
@@ -201,7 +201,7 @@ void UpdateMTFFVGTouched(string symbol)
|
||||
{
|
||||
if(ll <= bottom + tol)
|
||||
{
|
||||
touched = true;
|
||||
touchedNow = true;
|
||||
touch_time = iTime(symbol, LowTF, idx);
|
||||
touch_price = bottom;
|
||||
break;
|
||||
@@ -209,34 +209,32 @@ void UpdateMTFFVGTouched(string symbol)
|
||||
}
|
||||
}
|
||||
|
||||
if(touched)
|
||||
if(touchedNow)
|
||||
{
|
||||
FVGs[k].touched = true;
|
||||
FVGs[k].touchTime = touch_time;
|
||||
FVGs[k].touchPrice = touch_price;
|
||||
// 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)
|
||||
FVGs[fvgIndex].touched = true;
|
||||
FVGs[fvgIndex].touchTime = touch_time;
|
||||
FVGs[fvgIndex].touchPrice = touch_price;
|
||||
|
||||
if(PrintEntryLog)
|
||||
PrintFormat("MTF FVG touched (struct): idx=%d dir=%d at %s price=%.5f", k, dir, TimeToString(touch_time, TIME_DATE|TIME_MINUTES), touch_price);
|
||||
|
||||
// enable watchMSSMode when any MTF FVG is first touched
|
||||
if(!watchMSSMode)
|
||||
// enable watchingMSSMode when any MTF FVG is first touched
|
||||
if(!watchingMSSMode)
|
||||
{
|
||||
watchMSSMode = true;
|
||||
watchedFVGIndex = k;
|
||||
watchedFVGDir = dir;
|
||||
watchingMSSMode = true;
|
||||
watchingFVGIndex = fvgIndex;
|
||||
watchingFVGDir = dir;
|
||||
if(PrintEntryLog)
|
||||
PrintFormat("watchMSSMode ENABLED for FVG idx=%d dir=%d", watchedFVGIndex, watchedFVGDir);
|
||||
PrintFormat("watchingMSSMode ENABLED for FVG idx=%d dir=%d", watchingFVGIndex, watchingFVGDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kiểm tra điều kiện vô hiệu hoá watchMSSMode:
|
||||
// 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(!watchMSSMode || watchedFVGIndex < 0 || watchedFVGIndex >= FVG_count) return;
|
||||
if(!watchingMSSMode || watchingFVGIndex < 0 || watchingFVGIndex >= FVG_count) return;
|
||||
|
||||
// Lấy last closed close trên LowTF (index 1)
|
||||
double lastClose = iClose(symbol, LowTF, 1);
|
||||
@@ -245,8 +243,8 @@ void CheckWatchMSSInvalidation(string symbol)
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
double tol = (point > 0.0) ? point * 0.5 : 0.0;
|
||||
|
||||
int idx = watchedFVGIndex;
|
||||
int dir = watchedFVGDir;
|
||||
int idx = watchingFVGIndex;
|
||||
int dir = watchingFVGDir;
|
||||
|
||||
// bảo đảm FVG arrays còn hợp lệ
|
||||
if(idx < 0 || idx >= FVG_count) return;
|
||||
@@ -259,22 +257,22 @@ void CheckWatchMSSInvalidation(string symbol)
|
||||
{
|
||||
if(lastClose < bottom - tol)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat("watchMSSMode DISABLED: bullish FVG idx=%d invalidated by close=%.5f < bottom=%.5f", idx, lastClose, bottom);
|
||||
if(PrintEntryLog) PrintFormat("watchingMSSMode DISABLED: bullish FVG idx=%d invalidated by close=%.5f < bottom=%.5f", idx, lastClose, bottom);
|
||||
// clear
|
||||
watchMSSMode = false;
|
||||
watchedFVGIndex = -1;
|
||||
watchedFVGDir = 0;
|
||||
watchingMSSMode = false;
|
||||
watchingFVGIndex = -1;
|
||||
watchingFVGDir = 0;
|
||||
}
|
||||
}
|
||||
else if(dir == -1) // bearish: invalid khi đóng trên top
|
||||
{
|
||||
if(lastClose > top + tol)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat("watchMSSMode DISABLED: bearish FVG idx=%d invalidated by close=%.5f > top=%.5f", idx, lastClose, top);
|
||||
if(PrintEntryLog) PrintFormat("watchingMSSMode DISABLED: bearish FVG idx=%d invalidated by close=%.5f > top=%.5f", idx, lastClose, top);
|
||||
// clear
|
||||
watchMSSMode = false;
|
||||
watchedFVGIndex = -1;
|
||||
watchedFVGDir = 0;
|
||||
watchingMSSMode = false;
|
||||
watchingFVGIndex = -1;
|
||||
watchingFVGDir = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -760,47 +758,91 @@ double GetPipSize(string symbol)
|
||||
return point;
|
||||
}
|
||||
|
||||
// ---------- FindInternalFVG ----------
|
||||
// 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[])
|
||||
{
|
||||
ArrayFree(out_top); ArrayFree(out_bottom); ArrayFree(out_timeA); ArrayFree(out_timeC); ArrayFree(out_type);
|
||||
int found = 0;
|
||||
// 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 A–B–C (C = i, B = i+1, A = i+2)
|
||||
// ----------------------------------------------------------
|
||||
for(int i = 1; i <= maxScan; i++)
|
||||
{
|
||||
int idxA = i + 2;
|
||||
int idxB = i + 1;
|
||||
int idxB = i + 1; // thực ra không cần B trong phép tính nhưng vẫn để đúng mô hình
|
||||
int idxC = i;
|
||||
|
||||
if(idxA > total - 1) break;
|
||||
// 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;
|
||||
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;
|
||||
|
||||
// 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(MathAbs(out_top[k] - top) <= tol &&
|
||||
MathAbs(out_bottom[k] - bottom) <= tol)
|
||||
{
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!dup)
|
||||
{
|
||||
ArrayResize(out_top, found+1);
|
||||
@@ -813,21 +855,34 @@ int FindInternalFVG(string symbol, ENUM_TIMEFRAMES timeframe, int lookback,
|
||||
out_bottom[found] = bottom;
|
||||
out_timeA[found] = iTime(symbol, timeframe, idxA);
|
||||
out_timeC[found] = iTime(symbol, timeframe, idxC);
|
||||
out_type[found] = 1;
|
||||
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;
|
||||
|
||||
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(MathAbs(out_top[k] - top) <= tol &&
|
||||
MathAbs(out_bottom[k] - bottom) <= tol)
|
||||
{
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!dup)
|
||||
{
|
||||
ArrayResize(out_top, found+1);
|
||||
@@ -840,7 +895,8 @@ int FindInternalFVG(string symbol, ENUM_TIMEFRAMES timeframe, int lookback,
|
||||
out_bottom[found] = bottom;
|
||||
out_timeA[found] = iTime(symbol, timeframe, idxA);
|
||||
out_timeC[found] = iTime(symbol, timeframe, idxC);
|
||||
out_type[found] = -1;
|
||||
out_type[found] = -1; // bearish
|
||||
|
||||
found++;
|
||||
}
|
||||
}
|
||||
@@ -1528,7 +1584,58 @@ void EnsureFVGUpToDate(string symbol, ENUM_TIMEFRAMES tf, int lookback)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- DetectMSSOnTimeframe (fixed) ----------
|
||||
// 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, clrDodgerBlue) : 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 enabled,
|
||||
bool requireFVG,
|
||||
double minBreakPips,
|
||||
@@ -1556,8 +1663,13 @@ void DetectMSSOnTimeframe(string sym, ENUM_TIMEFRAMES tf, int slot, bool enabled
|
||||
if(!older.found || !newer.found) return;
|
||||
if(older.direction == newer.direction) return;
|
||||
|
||||
// Lấy sweep/break price sớm (phải có trước khi dùng pmin/pmax)
|
||||
double sweep_price = older.broken_sw_price;
|
||||
double break_price = newer.broken_sw_price;
|
||||
|
||||
if(requireFVG)
|
||||
{
|
||||
// nếu TF là MiddleTF thì đảm bảo danh sách MTF FVG mới nhất
|
||||
if(tf == MiddleTF) EnsureFVGUpToDate(sym, tf, fvgLookback);
|
||||
|
||||
datetime sweep_time = older.break_time;
|
||||
@@ -1568,19 +1680,31 @@ void DetectMSSOnTimeframe(string sym, ENUM_TIMEFRAMES tf, int slot, bool enabled
|
||||
return;
|
||||
}
|
||||
|
||||
// price window: cần sweep_price/break_price phải đã có
|
||||
if(sweep_price == 0.0 || break_price == 0.0) return;
|
||||
double pmin = MathMin(sweep_price, break_price);
|
||||
double pmax = MathMax(sweep_price, break_price);
|
||||
|
||||
// Kiểm tra tồn tại FVG trên MTF (giữ logic cũ)
|
||||
bool ok = false;
|
||||
if(newer.direction == 1)
|
||||
ok = HasBullFVGBetween(sym, tf, sweep_time, break_time, fvgLookback);
|
||||
else
|
||||
ok = HasBearFVGBetween(sym, tf, sweep_time, break_time, fvgLookback);
|
||||
|
||||
if(!ok) return;
|
||||
if(!ok)
|
||||
{
|
||||
// nếu requireFVG yêu cầu MTF FVG bắt buộc thì abort
|
||||
return;
|
||||
}
|
||||
|
||||
// --- VẼ các internal FVG trên LTF nằm giữa sweep_time & break_time ---
|
||||
DrawInternalFVGMatches(sym, LowTF, sweep_time, break_time, pmin, pmax, FVGLookback);
|
||||
}
|
||||
|
||||
// tiếp tục các kiểm tra MSS như trước
|
||||
double pip = GetPipSize(sym);
|
||||
double tol = pip * 0.1;
|
||||
double sweep_price = older.broken_sw_price;
|
||||
double break_price = newer.broken_sw_price;
|
||||
double low_sweap_price = sweep_price;
|
||||
|
||||
if(sweep_price == 0.0 || break_price == 0.0) return;
|
||||
@@ -1608,16 +1732,16 @@ void DetectMSSOnTimeframe(string sym, ENUM_TIMEFRAMES tf, int slot, bool enabled
|
||||
mss.key_level = newer.broken_sw_price;
|
||||
|
||||
// Nếu đang watch và MSS direction trùng với FVG direction -> xử lý pending
|
||||
if(watchMSSMode && watchedFVGIndex >= 0)
|
||||
if(watchingMSSMode && watchingFVGIndex >= 0)
|
||||
{
|
||||
if(mss.found && mss.direction == watchedFVGDir)
|
||||
if(mss.found && mss.direction == watchingFVGDir)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat("MSS matched watched FVG dir=%d -> creating pending", mss.direction);
|
||||
watchMSSMode = false;
|
||||
watchedFVGIndex = -1;
|
||||
watchedFVGDir = 0;
|
||||
watchingMSSMode = false;
|
||||
watchingFVGIndex = -1;
|
||||
watchingFVGDir = 0;
|
||||
|
||||
SetUpPendingEntryForMSS(mss, slot);
|
||||
// SetUpPendingEntryForMSS(mss, slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1694,8 +1818,12 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
pendingEntry.compositeName = "";
|
||||
}
|
||||
|
||||
// chuẩn bị tên unique dựa trên thời gian
|
||||
int timeStamp = (int)TimeCurrent();
|
||||
// use created_time as anchor (fallback to TimeCurrent if not set)
|
||||
datetime created = pendingEntry.created_time;
|
||||
if(created == 0) created = TimeCurrent();
|
||||
|
||||
// chuẩn bị tên unique dựa trên created_time (ổn định khi redraw)
|
||||
int timeStamp = (int)created;
|
||||
string base = SwingObjPrefix + "PEND_";
|
||||
string lineEntry = base + "LINE_E_" + IntegerToString(timeStamp);
|
||||
string lblEntry = base + "LBL_E_" + IntegerToString(timeStamp);
|
||||
@@ -1704,13 +1832,13 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
string lineTP = base + "LINE_TP_" + IntegerToString(timeStamp);
|
||||
string lblTP = base + "LBL_TP_" + IntegerToString(timeStamp);
|
||||
|
||||
// time để vẽ xu hướng ngang: vẽ từ now -> next bar time
|
||||
datetime t0 = iTime(symbol, LowTF, 0);
|
||||
// time để vẽ tia ngang: bắt đầu tại created, tia hướng sang phải => sử dụng OBJPROP_RAY_RIGHT
|
||||
datetime barSecs = (datetime)PeriodSeconds(LowTF);
|
||||
datetime start_time = (t0 != 0) ? (datetime)((long)t0 + (long)barSecs) : (datetime)TimeCurrent();
|
||||
datetime left_time = created;
|
||||
datetime right_time = (datetime)((long)created + (long)barSecs); // điểm phụ (không quan trọng vì ray_right=true)
|
||||
|
||||
// ENTRY line
|
||||
if(ObjectCreate(0, lineEntry, OBJ_TREND, 0, TimeCurrent(), pendingEntry.price, start_time, pendingEntry.price))
|
||||
// ENTRY line (trend object as horizontal ray)
|
||||
if(ObjectCreate(0, lineEntry, OBJ_TREND, 0, left_time, pendingEntry.price, right_time, pendingEntry.price))
|
||||
{
|
||||
int col = (pendingEntry.direction == 1) ? clrLime : clrRed;
|
||||
ObjectSetInteger(0, lineEntry, OBJPROP_COLOR, col);
|
||||
@@ -1718,13 +1846,15 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
ObjectSetInteger(0, lineEntry, OBJPROP_STYLE, STYLE_DASH);
|
||||
ObjectSetInteger(0, lineEntry, OBJPROP_SELECTABLE, false);
|
||||
ObjectSetInteger(0, lineEntry, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, lineEntry, OBJPROP_RAY_RIGHT, true); // <-- make it a ray to the right
|
||||
ObjectSetInteger(0, lineEntry, OBJPROP_RAY_LEFT, false);
|
||||
}
|
||||
|
||||
// ENTRY label
|
||||
// ENTRY label (đặt tại created_time để hiện đúng khi pending tạo)
|
||||
double yoffset = SymbolInfoDouble(symbol, SYMBOL_POINT) * 6.0;
|
||||
int digs = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
double entryLabelPrice = (pendingEntry.direction == 1) ? (pendingEntry.price + yoffset) : (pendingEntry.price - yoffset);
|
||||
if(ObjectCreate(0, lblEntry, OBJ_TEXT, 0, TimeCurrent(), entryLabelPrice))
|
||||
if(ObjectCreate(0, lblEntry, OBJ_TEXT, 0, left_time, entryLabelPrice))
|
||||
{
|
||||
string txtE = "PEND " + ((pendingEntry.direction==1) ? "BUY" : "SELL") + " " + DoubleToString(pendingEntry.price, digs);
|
||||
ObjectSetString(0, lblEntry, OBJPROP_TEXT, txtE);
|
||||
@@ -1737,17 +1867,19 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
// SL line + label (nếu có)
|
||||
if(pendingEntry.sl_price != 0.0)
|
||||
{
|
||||
if(ObjectCreate(0, lineSL, OBJ_TREND, 0, TimeCurrent(), pendingEntry.sl_price, start_time, pendingEntry.sl_price))
|
||||
if(ObjectCreate(0, lineSL, OBJ_TREND, 0, left_time, pendingEntry.sl_price, right_time, pendingEntry.sl_price))
|
||||
{
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_COLOR, clrRed);
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_STYLE, STYLE_DOT);
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_SELECTABLE, false);
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, lineSL, OBJPROP_RAY_LEFT, false);
|
||||
}
|
||||
|
||||
double slLabelPrice = (pendingEntry.sl_price - yoffset);
|
||||
if(ObjectCreate(0, lblSL, OBJ_TEXT, 0, TimeCurrent(), slLabelPrice))
|
||||
if(ObjectCreate(0, lblSL, OBJ_TEXT, 0, left_time, slLabelPrice))
|
||||
{
|
||||
string txtSL = "SL " + DoubleToString(pendingEntry.sl_price, digs);
|
||||
ObjectSetString(0, lblSL, OBJPROP_TEXT, txtSL);
|
||||
@@ -1766,17 +1898,19 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
// TP line + label (nếu có)
|
||||
if(pendingEntry.tp_price != 0.0)
|
||||
{
|
||||
if(ObjectCreate(0, lineTP, OBJ_TREND, 0, TimeCurrent(), pendingEntry.tp_price, start_time, pendingEntry.tp_price))
|
||||
if(ObjectCreate(0, lineTP, OBJ_TREND, 0, left_time, pendingEntry.tp_price, right_time, pendingEntry.tp_price))
|
||||
{
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_COLOR, clrLime);
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_STYLE, STYLE_DOT);
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_SELECTABLE, false);
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, lineTP, OBJPROP_RAY_LEFT, false);
|
||||
}
|
||||
|
||||
double tpLabelPrice = (pendingEntry.tp_price + yoffset);
|
||||
if(ObjectCreate(0, lblTP, OBJ_TEXT, 0, TimeCurrent(), tpLabelPrice))
|
||||
if(ObjectCreate(0, lblTP, OBJ_TEXT, 0, left_time, tpLabelPrice))
|
||||
{
|
||||
string txtTP = "TP " + DoubleToString(pendingEntry.tp_price, digs);
|
||||
ObjectSetString(0, lblTP, OBJPROP_TEXT, txtTP);
|
||||
@@ -1792,13 +1926,13 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
}
|
||||
|
||||
// Lưu compositeName theo thứ tự: entryLine|entryLbl|slLine|slLbl|tpLine|tpLbl
|
||||
// các phần rỗng sẽ bị loại khi xóa
|
||||
string comp = lineEntry + "|" + lblEntry + "|" + lineSL + "|" + lblSL + "|" + lineTP + "|" + lblTP;
|
||||
pendingEntry.compositeName = comp;
|
||||
|
||||
if(PrintEntryLog)
|
||||
{
|
||||
PrintFormat("PendingEntry VISUAL DRAWN: dir=%d entry=%.5f sl=%.5f tp=%.5f composite=(%s)",
|
||||
PrintFormat("PendingEntry VISUAL DRAWN: created=%s dir=%d entry=%.5f sl=%.5f tp=%.5f composite=(%s)",
|
||||
TimeToString(created, TIME_DATE|TIME_MINUTES),
|
||||
pendingEntry.direction, pendingEntry.price, pendingEntry.sl_price, pendingEntry.tp_price, pendingEntry.compositeName);
|
||||
}
|
||||
}
|
||||
@@ -1808,44 +1942,90 @@ void DrawPendingEntryVisuals(string symbol)
|
||||
// - slot: slot where MSS was detected
|
||||
void SetUpPendingEntryForMSS(const MSSInfo &mss, int slot)
|
||||
{
|
||||
|
||||
PrintFormat("@@@@@@ mss: found=%d dir=%d sweep_time=%d sweep_price=%.5f break_time=%d break_price=%.5f",
|
||||
mss.found ? 1 : 0, mss.direction, (int)mss.sweep_time, mss.sweep_price, (int)mss.break_time, mss.break_price);
|
||||
string sym = Symbol();
|
||||
|
||||
if(!mss.found) return;
|
||||
if(mss.sweep_time == 0 || mss.break_time == 0 || mss.sweep_time >= mss.break_time) return;
|
||||
if(PrintEntryLog)
|
||||
PrintFormat("==> SetUpPendingEntryForMSS START: mss.found=%d dir=%d sweep_time=%d sweep_price=%.10f break_time=%d break_price=%.10f",
|
||||
mss.found ? 1 : 0, mss.direction, (int)mss.sweep_time, mss.sweep_price, (int)mss.break_time, mss.break_price);
|
||||
|
||||
if(!mss.found)
|
||||
{
|
||||
if(PrintEntryLog) Print("-> abort: mss.found == false");
|
||||
return;
|
||||
}
|
||||
if(mss.sweep_time == 0 || mss.break_time == 0 || mss.sweep_time >= mss.break_time)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat("-> abort: invalid times (sweep_time=%d break_time=%d)", (int)mss.sweep_time, (int)mss.break_time);
|
||||
return;
|
||||
}
|
||||
|
||||
double lfgtop[]; double lfgbottom[];
|
||||
datetime lfgA[]; datetime lfgC[];
|
||||
int lfgtype[];
|
||||
|
||||
int cnt = FindInternalFVG(sym, LowTF, FVGLookback, lfgtop, lfgbottom, lfgA, lfgC, lfgtype);
|
||||
if(cnt <= 0) return;
|
||||
if(PrintEntryLog) PrintFormat("-> FindInternalFVG returned cnt=%d (FVGLookback=%d)", cnt, FVGLookback);
|
||||
|
||||
if(cnt <= 0)
|
||||
{
|
||||
if(PrintEntryLog) Print("-> abort: no internal LTF FVG found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Log all FVGs found for debug
|
||||
double point = SymbolInfoDouble(sym, SYMBOL_POINT);
|
||||
double tol = (point > 0.0) ? point * 0.5 : 0.0;
|
||||
for(int i=0; i<cnt; i++)
|
||||
{
|
||||
string timestrA = TimeToString(lfgA[i], TIME_DATE|TIME_MINUTES);
|
||||
string timestrC = TimeToString(lfgC[i], TIME_DATE|TIME_MINUTES);
|
||||
if(PrintEntryLog)
|
||||
PrintFormat(" FVG[%d]: type=%d top=%.10f bottom=%.10f timeA=%s timeC=%s", i, lfgtype[i], lfgtop[i], lfgbottom[i], timestrA, timestrC);
|
||||
}
|
||||
|
||||
double pmin = MathMin(mss.sweep_price, mss.break_price);
|
||||
double pmax = MathMax(mss.sweep_price, mss.break_price);
|
||||
|
||||
double point = SymbolInfoDouble(sym, SYMBOL_POINT);
|
||||
double tol = (point > 0.0) ? point * 0.5 : 0.0;
|
||||
if(PrintEntryLog) PrintFormat("-> Price window between sweep & break: pmin=%.10f pmax=%.10f tol=%.10g", pmin, pmax, tol);
|
||||
|
||||
int chosenIdx = -1;
|
||||
for(int i=0;i<cnt;i++)
|
||||
for(int i=0; i<cnt; i++)
|
||||
{
|
||||
datetime tc = lfgC[i];
|
||||
if(tc <= mss.sweep_time || tc >= mss.break_time) continue;
|
||||
// ensure candidate FVG C bar is strictly between sweep_time and break_time
|
||||
if(tc <= mss.sweep_time)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat(" skip FVG[%d] because timeC(%s) <= sweep_time(%s)", i, TimeToString(tc, TIME_DATE|TIME_MINUTES), TimeToString(mss.sweep_time, TIME_DATE|TIME_MINUTES));
|
||||
continue;
|
||||
}
|
||||
if(tc >= mss.break_time)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat(" skip FVG[%d] because timeC(%s) >= break_time(%s)", i, TimeToString(tc, TIME_DATE|TIME_MINUTES), TimeToString(mss.break_time, TIME_DATE|TIME_MINUTES));
|
||||
continue;
|
||||
}
|
||||
|
||||
double top = lfgtop[i];
|
||||
double bottom = lfgbottom[i];
|
||||
|
||||
// check overlap: bottom..top overlap with sweep..break price window
|
||||
if(bottom + tol >= pmin && top - tol <= pmax)
|
||||
{
|
||||
chosenIdx = i;
|
||||
if(PrintEntryLog) PrintFormat("-> chosen FVG index=%d (type=%d top=%.10f bottom=%.10f)", chosenIdx, lfgtype[i], top, bottom);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat(" FVG[%d] does not overlap window: bottom+tol=%.10f top-tol=%.10f (need bottom+tol >= pmin && top-tol <= pmax)", i, bottom+tol, top-tol);
|
||||
}
|
||||
}
|
||||
|
||||
if(chosenIdx == -1) return;
|
||||
if(chosenIdx == -1)
|
||||
{
|
||||
if(PrintEntryLog) Print("-> abort: no matching LTF internal FVG found between sweep and break (chosenIdx == -1)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine entry price from chosen FVG
|
||||
double entPrice = 0.0;
|
||||
int fvgDir = lfgtype[chosenIdx];
|
||||
if(fvgDir == 1)
|
||||
@@ -1853,52 +2033,98 @@ void SetUpPendingEntryForMSS(const MSSInfo &mss, int slot)
|
||||
else
|
||||
entPrice = lfgtop[chosenIdx];
|
||||
|
||||
// Normalize entry to symbol digits
|
||||
int digs = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
|
||||
double entPriceNorm = NormalizeDouble(entPrice, digs);
|
||||
|
||||
if(PrintEntryLog)
|
||||
{
|
||||
PrintFormat("-> entry raw=%.10f normalized=%.10f digits=%d fvgDir=%d", entPrice, entPriceNorm, digs, fvgDir);
|
||||
}
|
||||
|
||||
// Clear previous pending
|
||||
ClearPendingEntry();
|
||||
|
||||
// Fill pendingEntry fields
|
||||
pendingEntry.active = true;
|
||||
pendingEntry.direction = (mss.direction == 1) ? 1 : -1;
|
||||
pendingEntry.price = entPrice;
|
||||
pendingEntry.fvgIndex = watchedFVGIndex;
|
||||
pendingEntry.price = entPriceNorm;
|
||||
pendingEntry.fvgIndex = chosenIdx;
|
||||
pendingEntry.created_time = TimeCurrent();
|
||||
pendingEntry.compositeName = "";
|
||||
pendingEntry.source_slot = slot;
|
||||
// SL được lấy từ MSS (hoặc bạn có thể thay đổi nếu muốn buffer)
|
||||
|
||||
// Use MSS sweep price as SL (as before)
|
||||
pendingEntry.sl_price = mss.sweep_price;
|
||||
|
||||
// --- TÍNH TP theo RiskRewardRatio (TP = entry ± RiskRewardRatio * |entry - SL|) ---
|
||||
double entry = entPrice;
|
||||
// compute TP using RiskRewardRatio
|
||||
double entry = pendingEntry.price;
|
||||
double sl = pendingEntry.sl_price;
|
||||
double tp = 0.0;
|
||||
pendingEntry.tp_price = 0.0;
|
||||
|
||||
if(sl == 0.0)
|
||||
{
|
||||
pendingEntry.tp_price = 0.0;
|
||||
if(PrintEntryLog) PrintFormat("PendingEntry TP NOT SET because SL==0 (entry=%.5f)", entry);
|
||||
if(PrintEntryLog) PrintFormat("-> warning: SL==0. TP not set (entry=%.10f)", entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
double diff = MathAbs(entry - sl);
|
||||
if(diff <= 0.0)
|
||||
{
|
||||
pendingEntry.tp_price = 0.0;
|
||||
if(PrintEntryLog) PrintFormat("PendingEntry TP NOT SET because diff==0 (entry=%.5f sl=%.5f)", entry, sl);
|
||||
if(PrintEntryLog) PrintFormat("-> warning: diff==0 (entry=%.10f sl=%.10f) TP not set", entry, sl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// dùng biến RiskRewardRatio để tính TP
|
||||
tp = (pendingEntry.direction == 1) ? (entry + RiskRewardRatio * diff)
|
||||
: (entry - RiskRewardRatio * diff);
|
||||
|
||||
// làm tròn theo digits của symbol
|
||||
int digs = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
|
||||
double tp = (pendingEntry.direction == 1) ? (entry + RiskRewardRatio * diff)
|
||||
: (entry - RiskRewardRatio * diff);
|
||||
pendingEntry.tp_price = NormalizeDouble(tp, digs);
|
||||
|
||||
if(PrintEntryLog)
|
||||
PrintFormat("PendingEntry CREATED: dir=%d entry=%.5f sl=%.5f tp=%.5f (R:R=%.2f)", pendingEntry.direction, entry, sl, pendingEntry.tp_price, RiskRewardRatio);
|
||||
PrintFormat("-> computed TP: entry=%.10f sl=%.10f diff=%.10f R:R=%.2f tp=%.10f", entry, sl, diff, RiskRewardRatio, pendingEntry.tp_price);
|
||||
}
|
||||
}
|
||||
|
||||
if(PrintEntryLog) PrintFormat("-> PendingEntry populated: dir=%d entry=%.10f sl=%.10f tp=%.10f fvgIndex=%d created=%s",
|
||||
pendingEntry.direction, pendingEntry.price, pendingEntry.sl_price, pendingEntry.tp_price, pendingEntry.fvgIndex, TimeToString(pendingEntry.created_time, TIME_DATE|TIME_SECONDS));
|
||||
|
||||
// Draw visuals
|
||||
DrawPendingEntryVisuals(sym);
|
||||
|
||||
// After drawing, log compositeName and check each object exists
|
||||
if(StringLen(pendingEntry.compositeName) > 0)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat("-> DrawPendingEntryVisuals set compositeName=(%s)", pendingEntry.compositeName);
|
||||
|
||||
string parts[];
|
||||
int n = StringSplit(pendingEntry.compositeName, '|', parts);
|
||||
for(int i=0; i<n; i++)
|
||||
{
|
||||
string nm = parts[i];
|
||||
if(StringLen(nm) == 0)
|
||||
{
|
||||
if(PrintEntryLog) PrintFormat(" part[%d] is empty", i);
|
||||
continue;
|
||||
}
|
||||
int found = ObjectFind(0, nm);
|
||||
if(found >= 0)
|
||||
PrintFormat(" OBJECT FOUND: part[%d] name='%s' ObjectFind returned=%d", i, nm, found);
|
||||
else
|
||||
PrintFormat(" OBJECT MISSING: part[%d] name='%s' ObjectFind returned=%d", i, nm, found);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(PrintEntryLog) Print("-> WARNING: pendingEntry.compositeName is empty AFTER DrawPendingEntryVisuals()");
|
||||
// As extra debug, attempt to reconstruct expected entry object names using created_time
|
||||
int timeStamp = (int)pendingEntry.created_time;
|
||||
string base = SwingObjPrefix + "PEND_";
|
||||
string expect_line = base + "LINE_E_" + IntegerToString(timeStamp);
|
||||
string expect_lbl = base + "LBL_E_" + IntegerToString(timeStamp);
|
||||
int found_line = ObjectFind(0, expect_line);
|
||||
int found_lbl = ObjectFind(0, expect_lbl);
|
||||
if(PrintEntryLog) PrintFormat("-> Reconstructed expected names: %s(found=%d), %s(found=%d)", expect_line, found_line, expect_lbl, found_lbl);
|
||||
}
|
||||
|
||||
if(PrintEntryLog) Print("==> SetUpPendingEntryForMSS END");
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
@@ -1915,7 +2141,7 @@ void OnTick()
|
||||
|
||||
if(IsNewClosedBar(sym, LowTF, 2)) {
|
||||
HandleLogicForTimeframe(sym, LowTF, 2, DetectMSS_LTF, ltfSwingRange, true, 10.0, 2, 50, FVGLookback);
|
||||
if (watchMSSMode) {
|
||||
if (watchingMSSMode) {
|
||||
CheckWatchMSSInvalidation(sym);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user