ATR + ADX filter

This commit is contained in:
Bell
2025-12-20 02:45:39 +07:00
parent 63fc3129a9
commit ef307514c8
2 changed files with 291 additions and 111 deletions
-77
View File
@@ -1,77 +0,0 @@
Bước 1: Xác định Daily bias:
- Xét 2 cây nến D1 đã đóng cửa gần nhất (Không tính nến hiện tại)
+ Nếu nến D[1] đóng cửa cao hơn giá đóng cửa cao nhất của nến D[2] ngày hôm trước (D[2] tính cả râu nến) -> xu hướng ngày hôm nay (D[0]) uptrend
+ Nếu nến D[1] đóng cửa thấp hơn giá đóng cửa thấp nhất của nến D[2] ngày hôm trước (D[2] tính cả râu nến) -> xu hướng ngày hôm nay (D[0]) downtrend
+ Nếu nến D[1] đóng cửa bên trong nến D[2], không cao hơn và không thấp hơn -> Bỏ qua D[1], dùng nến D[2] và D[3] để xác định xu hướng ngày hôm nay theo cách tương tự
Bước 2: Xác định FVG của khung H1 thuận chiều với daily bias và đánh dấu lại
- Nếu Daily Bias là uptrend -> Tìm uptrend FVG
- Nếu Daily Bias là downtrend -> Tìm downtrend FVG
Bước 3: Vào lệnh pullback khi có xác nhận tín hiệu tại khung m5 khi giá hồi về H1 FVG ở bước 2 như sau:
- Xu hướng hồi của khung M5 bị phá vỡ (Xuất hiện market structure shift, key level bị phá)
- Khung m5 xuất hiện M5 FVG thuận chiều với H1 trend
- Vào 1 lệnh buy limit hoặc sell limit thuận chiều xu hướng của H1 tại M5 FVG
- Tính toán lot size để đặt SL:
+ Nếu buy limit: Tính toán lot size vào lệnh sao cho khoảng cách từ entry tới đáy thấp nhất của sóng hồi m5 vừa bằng 1% equity
+ Nếu sell limit: Tính toán lot size vào lệnh sao cho khoảng cách từ entry tới đỉnh cao nhất của sóng hồi m5 vừa bằng 1% equity
- R:R = 1:3
======
v2 issues:
1. FindInternalFVG -> đang trả về found (boolean) -> nên trả về list FVGs(struct[])
2. Cách tính sl lại: đặt dưới OB
======
Result test 12/12/25 (Data 2025):
1. Cấu hình 1:
---
htfSwingRange = 2;
mtfSwingRange = 3;
ltfSwingRange = 5;
---
- R:R = 1:3 -> Profit 13.8% | Drawdown 16%
- R:R = 1:4 -> Profit 41.1% | Drawdown 5%
- R:R = 1:5 -> Profit 56.7% | Drawdown 20%
- R:R = 1:6 -> Profit 59.4% | Drawdown 20%
- R:R = 1:7 -> Profit 64.6% | Drawdown 23%
- R:R = 1:8 -> Profit 14.7% | Drawdown 32%
======
Result test 12/12/25 (Data 2025):
1. Cấu hình 2:
---
htfSwingRange = 1;
mtfSwingRange = 3;
ltfSwingRange = 5;
---
- R:R = 1:3 -> Profit 11% | Drawdown 16%
- R:R = 1:4 -> Profit 37% | Drawdown 5%
- R:R = 1:5 -> Profit 53% | Drawdown 22%
- R:R = 1:6 -> Profit 55% | Drawdown 22%
- R:R = 1:7 -> Profit % | Drawdown %
- R:R = 1:8 -> Profit % | Drawdown %
===
Issue:
- WatchingMSSMode bị bật lại khi giá chạm ltf fvg -> tổ chức fvg đang sai, fvg bị find và reset liên tục -> fixed
- Bị miss mss
- Cần fix lại cách chọn FVG vào lệnh (premium, discount)
- có trường hợp xuất hiện mss giảm, MTF sideway mà vẫn vào lệnh (jan 03) -> cần check lại
- DetectMSS -> return luôn nếu trends không thuận
+291 -34
View File
@@ -7,22 +7,41 @@
#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 = 3.0; // tỉ lệ R:R mặc định
input double moveSLRange = 1; // Nomal moving SL: số R cần đạt để dời SL về entry (BE)
input double numberOfRToMoveToEntry = 1; // Nomal moving SL: số R cần đạt để dời SL về entry (BE)
input int MaxLimitOrderTime = 180;
input double moveSLStartR = 3; // Advanced moving SL: bắt đầu kích hoạt trailing
input double trailOffsetR = 3; // Advanced moving SL: khoảng cách SL so với giá hiện tại
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 = 2; // X: số nến trước và sau để xác định 1 đỉnh/đáy
input int mtfSwingRange = 2; // 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 htfSwingRange = 3; // D1/H4: chắc trend
input int mtfSwingRange = 3; // H1: BOS rõ
input int ltfSwingRange = 4; // 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)
@@ -154,10 +173,130 @@ 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 = 23;
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;
@@ -785,7 +924,7 @@ double GetPipSize(string symbol)
// --------------------------------------------------------------
int FindInternalFVG(string symbol, ENUM_TIMEFRAMES timeframe, int lookback,
double &out_top[], double &out_bottom[],
datetime &out_timeA[], datetime &out_timeC[], int &out_type[])
datetime &out_timeA[], datetime &out_timeC[], int &out_type[])
{
// clear output arrays
ArrayFree(out_top);
@@ -835,8 +974,13 @@ int FindInternalFVG(string symbol, ENUM_TIMEFRAMES timeframe, int lookback,
// ------------------------------------------------------
if(lowC > highA + tol)
{
double top = lowC;
double bottom = highA;
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;
@@ -876,8 +1020,14 @@ int FindInternalFVG(string symbol, ENUM_TIMEFRAMES timeframe, int lookback,
// ------------------------------------------------------
if(highC < lowA - tol)
{
double top = lowA;
double bottom = highC;
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++)
@@ -1636,6 +1786,17 @@ void DetectMSSOnTimeframe(
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Á
// ===============================
@@ -1667,6 +1828,23 @@ void DetectMSSOnTimeframe(
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Ũ)
// ===============================
@@ -2117,8 +2295,16 @@ void SetUpPendingEntryForMSS(const MSS &mss, int slot)
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(
(fvgTop + fvgBottom) * 0.5,
fvgBottom + (fvgTop - fvgBottom) * retrace,
digits
);
@@ -2239,15 +2425,15 @@ void MoveStoploss()
bool shouldMove = false;
// ======= ĐIỀU KIỆN BE DỰA TRÊN moveSLRange =======
// ======= ĐIỀU KIỆN BE DỰA TRÊN numberOfRToMoveToEntry =======
if(type == POSITION_TYPE_BUY)
{
if(bid >= entry + moveSLRange * R)
if(bid >= entry + numberOfRToMoveToEntry * R)
shouldMove = true;
}
else if(type == POSITION_TYPE_SELL)
{
if(ask <= entry - moveSLRange * R)
if(ask <= entry - numberOfRToMoveToEntry * R)
shouldMove = true;
}
@@ -2277,7 +2463,7 @@ void MoveStoploss()
{
if(PrintEntryLog)
PrintFormat("MoveStoploss: SL moved to BE at %.5f (%.2fR), ticket=%I64u",
entry, moveSLRange, ticket);
entry, numberOfRToMoveToEntry, ticket);
}
else
{
@@ -2385,7 +2571,7 @@ void MoveStoplossAdvanced()
profitR = (entry - ask) / R;
// ===== CHƯA ĐỦ ĐIỀU KIỆN BE =====
if(profitR < moveSLStartR)
if(profitR < trailingStopStartR)
return;
int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
@@ -2394,7 +2580,7 @@ void MoveStoplossAdvanced()
// =========================================================
// 1️⃣ BE PHASE
// =========================================================
if(profitR >= moveSLStartR && profitR < (moveSLStartR + trailOffsetR))
if(profitR >= trailingStopStartR && profitR < (trailingStopStartR + trailOffsetR))
{
newSL = entry;
}
@@ -2404,7 +2590,7 @@ void MoveStoplossAdvanced()
// 2️⃣ STEP TRAIL PHASE (theo bậc R)
// =========================================================
int step = (int)MathFloor(
(profitR - moveSLStartR) / trailOffsetR
(profitR - trailingStopStartR) / trailOffsetR
);
double targetR = step * trailOffsetR;
@@ -2447,7 +2633,7 @@ void MoveStoplossAdvanced()
PrintFormat(
"MoveSL ICT-C: SL -> %.5f | profitR=%.2fR | step=%d",
newSL, profitR,
(int)MathFloor((profitR - moveSLStartR) / trailOffsetR)
(int)MathFloor((profitR - trailingStopStartR) / trailOffsetR)
);
}
}
@@ -2473,32 +2659,48 @@ bool IsEntryTimeAllowed()
bool IsAllowedToTrade()
{
if(!IsEntryTimeAllowed())
return false;
UpdateDailyEquity();
// if(EnableNewsFilter &&
// IsHighImpactNewsNear(_Symbol,
// NewsBlockBeforeMin,
// NewsBlockAfterMin))
return false;
if(IsDailyLossExceeded())
return false;
return true;
// ===============================
// 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() {
if(!IsAllowedToTrade())
return;
string sym = Symbol();
CancelExpiredLimitOrder();
if (moveSLRange > 0) {
if (numberOfRToMoveToEntry > 0) {
// MoveStoploss();
// MoveStoplossAdvanced();
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);
@@ -2718,3 +2920,58 @@ void OnDeinit(const int reason)
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];
}