diff --git a/Experts/EmaPullbackBasic.mq5 b/Experts/EmaPullbackBasic.mq5 index b37d433..16bac6d 100644 --- a/Experts/EmaPullbackBasic.mq5 +++ b/Experts/EmaPullbackBasic.mq5 @@ -1,11 +1,12 @@ //+------------------------------------------------------------------+ //| EmaPullbackBasic.mq5 — EMA50 pullback EA (phased build) | //| Phase 4: breakout khỏi nến touch (cửa sổ N nến) | -//| Phase 5: risk 1% R, TP 1.5R | +//| Phase 5: risk & TP theo R | +//| Phase 6: spread/session/DD + chốt 50% @ 2R rồi BE (không cap lệnh/ngày)| //+------------------------------------------------------------------+ #property copyright "EMA Pullback Basic" -#property version "0.52" -#property description "EMA pullback + reject entry, 1% risk, TP 1.5R" +#property version "0.58" +#property description "Phase 6: filters + partial 2R + breakeven" #include @@ -54,10 +55,24 @@ input bool InpRequireBreakoutCandleColor = true; // Buy: bullish, Sel input group "=== Risk (Phase 5) ===" input bool InpTradeEnabled = true; input double InpRiskPercent = 1.0; // R = % balance -input double InpTpRR = 1.5; // TP = InpTpRR × SL distance +input double InpTpRR = 3; // TP = InpTpRR × SL distance input double InpSlBufferAtr = 0.10; // SL dưới đáy/ trên đỉnh + buffer × ATR input int InpMaxBarsInTrade = 0; // 0 = không đóng theo thời gian +input group "=== Phase 6 — An toàn & quản lý lệnh ===" +input bool InpUseSpreadFilter = true; +input int InpMaxSpreadPoints = 50; // Không vào lệnh nếu spread > (points) +input bool InpUseSessionFilter = true; +input int InpSessionStartHour = 8; // Giờ server (bắt đầu) +input int InpSessionEndHour = 22; // Giờ server (kết thúc, có thể qua đêm) +input bool InpUseDdRiskScale = true; +input double InpDdHalveRiskPct = 5.0; // DD từ đỉnh equity → risk × 0.5 +input bool InpUsePartialAt2R = true; // Đạt 2R: chốt 50% + SL → BE +input double InpPartialCloseRR = 2.0; // Ngưỡng R để chốt một phần +input double InpPartialCloseFrac = 0.50; // Phần volume đóng (0.5 = một nửa) +input int InpBeOffsetPoints = 2; // BE = entry ± offset (points) +// Ghi chú: không giới hạn số lệnh / ngày + input group "=== EA ===" input ulong InpMagic = 20260522; input int InpSlippage = 10; @@ -87,6 +102,14 @@ int g_statTotal = 0; int g_statTP = 0; int g_statSL = 0; +double g_equityPeak = 0.0; +double g_riskScale = 1.0; +ulong g_trackTicket = 0; +double g_trackEntry = 0.0; +double g_trackRiskDist = 0.0; +bool g_trackIsBuy = false; +bool g_trackPartialDone = false; + const string OBJ_PREFIX = "EPB_"; string StatPfx() { return "EPB_ST_" + IntegerToString((int)InpMagic) + "_"; } @@ -496,9 +519,125 @@ bool HasOurPosition() { return false; } +ulong FindOurPositionTicket() { + for(int i = PositionsTotal() - 1; i >= 0; i--) { + const ulong t = PositionGetTicket(i); + if(t == 0 || !PositionSelectByTicket(t)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic) continue; + return t; + } + return 0; +} + +void ResetPositionTrack() { + g_trackTicket = 0; + g_trackEntry = 0.0; + g_trackRiskDist = 0.0; + g_trackIsBuy = false; + g_trackPartialDone = false; +} + +void SyncPositionTrack() { + const ulong t = FindOurPositionTicket(); + if(t == 0) { + ResetPositionTrack(); + return; + } + if(t == g_trackTicket && g_trackRiskDist > 0.0) + return; + + if(!PositionSelectByTicket(t)) + return; + + g_trackTicket = t; + g_trackEntry = PositionGetDouble(POSITION_PRICE_OPEN); + const double sl = PositionGetDouble(POSITION_SL); + g_trackRiskDist = MathAbs(g_trackEntry - sl); + if(g_trackRiskDist < _Point) + g_trackRiskDist = _Point; + g_trackIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY); + g_trackPartialDone = false; +} + +void StartPositionTrack(const ulong ticket, const bool isBuy, + const double entry, const double sl) { + g_trackTicket = ticket; + g_trackEntry = entry; + g_trackRiskDist = MathAbs(entry - sl); + if(g_trackRiskDist < _Point) + g_trackRiskDist = _Point; + g_trackIsBuy = isBuy; + g_trackPartialDone = false; +} + +int CurrentSpreadPoints() { + const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + if(ask <= 0.0 || bid <= 0.0 || _Point <= 0.0) + return 0; + return (int)MathRound((ask - bid) / _Point); +} + +bool IsSpreadAllowed() { + if(!InpUseSpreadFilter) + return true; + return CurrentSpreadPoints() <= InpMaxSpreadPoints; +} + +bool IsSessionAllowed() { + if(!InpUseSessionFilter) + return true; + + MqlDateTime dt; + TimeToStruct(TimeCurrent(), dt); + const int h = dt.hour; + + if(InpSessionStartHour == InpSessionEndHour) + return true; + if(InpSessionStartHour < InpSessionEndHour) + return (h >= InpSessionStartHour && h < InpSessionEndHour); + return (h >= InpSessionStartHour || h < InpSessionEndHour); +} + +void UpdateRiskScale() { + if(!InpUseDdRiskScale) { + g_riskScale = 1.0; + return; + } + + const double eq = AccountInfoDouble(ACCOUNT_EQUITY); + if(g_equityPeak <= 0.0) + g_equityPeak = eq; + if(eq > g_equityPeak) + g_equityPeak = eq; + + g_riskScale = 1.0; + if(g_equityPeak > 0.0) { + const double ddPct = (g_equityPeak - eq) / g_equityPeak * 100.0; + if(ddPct >= InpDdHalveRiskPct) + g_riskScale = 0.5; + } +} + +bool IsAllowedNewEntry(string &why) { + why = ""; + if(!IsSpreadAllowed()) { + why = StringFormat("spread %d > %d pts", CurrentSpreadPoints(), InpMaxSpreadPoints); + return false; + } + if(!IsSessionAllowed()) { + why = StringFormat("ngoài session %02d–%02d server", InpSessionStartHour, InpSessionEndHour); + return false; + } + return true; +} + double VolumeForRisk(const bool isBuy, const double entry, const double sl) { if(MathAbs(entry - sl) < _Point) return 0.0; - const double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0); + const double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) + * (InpRiskPercent / 100.0) + * g_riskScale; double profit = 0.0; if(!OrderCalcProfit(isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, _Symbol, 1.0, entry, sl, profit)) return 0.0; @@ -596,26 +735,111 @@ bool OpenMarketFromReject(const bool isBuy, const datetime t = iTime(_Symbol, _Period, rejectShift); const double px = isBuy ? iHigh(_Symbol, _Period, rejectShift) : iLow(_Symbol, _Period, rejectShift); DrawSetupMarker("SIG", t, px, isBuy ? clrLime : clrOrangeRed, isBuy ? "Entry Buy" : "Entry Sell"); + + const ulong posTicket = FindOurPositionTicket(); + if(posTicket > 0) + StartPositionTrack(posTicket, isBuy, entry, sl); + + return true; +} + +bool TryPartialCloseAndBreakeven(const ulong ticket) { + if(!InpUsePartialAt2R || g_trackPartialDone || g_trackRiskDist <= 0.0) + return false; + if(ticket == 0 || !PositionSelectByTicket(ticket)) + return false; + + const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + const double px = g_trackIsBuy ? bid : ask; + double profitR = 0.0; + + if(g_trackIsBuy) + profitR = (px - g_trackEntry) / g_trackRiskDist; + else + profitR = (g_trackEntry - px) / g_trackRiskDist; + + if(profitR < InpPartialCloseRR - 1e-8) + return false; + + const double vol = PositionGetDouble(POSITION_VOLUME); + const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + const double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + if(vol <= vmin + 1e-12) + return false; + + double closeVol = vol * InpPartialCloseFrac; + if(step > 0.0) + closeVol = MathFloor(closeVol / step) * step; + closeVol = NormalizeLots(closeVol); + + if(closeVol < vmin) { + if(vol <= vmin * 2.0 + 1e-12) + return false; + closeVol = vmin; + } + if(vol - closeVol < vmin - 1e-12) + closeVol = NormalizeLots(vol - vmin); + if(closeVol < vmin - 1e-12) + return false; + + if(!g_trade.PositionClosePartial(ticket, closeVol)) { + PrintFormat("[EPB] Partial close fail ticket=%I64u vol=%.2f ret=%d", + ticket, closeVol, g_trade.ResultRetcode()); + return false; + } + + const double beOff = InpBeOffsetPoints * _Point; + const double tp = PositionGetDouble(POSITION_TP); + double newSl = g_trackIsBuy + ? NormalizePrice(g_trackEntry + beOff) + : NormalizePrice(g_trackEntry - beOff); + + const double minDist = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; + if(g_trackIsBuy) { + if(bid - newSl < MathMax(_Point, minDist)) + newSl = NormalizePrice(bid - MathMax(_Point, minDist)); + } else { + if(newSl - ask < MathMax(_Point, minDist)) + newSl = NormalizePrice(ask + MathMax(_Point, minDist)); + } + + if(!g_trade.PositionModify(ticket, newSl, tp)) { + PrintFormat("[EPB] BE modify fail ticket=%I64u SL=%.*f ret=%d", + ticket, _Digits, newSl, g_trade.ResultRetcode()); + } + + g_trackPartialDone = true; + PrintFormat("[EPB] 2R manage: chốt %.2f lot (%.0f%%) @ %.2fR | SL→BE %.*f", + closeVol, InpPartialCloseFrac * 100.0, profitR, _Digits, newSl); return true; } void ManageOpenPosition() { - if(InpMaxBarsInTrade <= 0 || !HasOurPosition()) + if(!HasOurPosition()) { + ResetPositionTrack(); + return; + } + + SyncPositionTrack(); + const ulong ticket = FindOurPositionTicket(); + if(ticket == 0) return; - for(int i = PositionsTotal() - 1; i >= 0; i--) { - const ulong t = PositionGetTicket(i); - if(t == 0 || !PositionSelectByTicket(t)) continue; - if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; - if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic) continue; + TryPartialCloseAndBreakeven(ticket); - const datetime openT = (datetime)PositionGetInteger(POSITION_TIME); - const int barsIn = iBarShift(_Symbol, _Period, openT, false); - if(barsIn >= 0 && barsIn >= InpMaxBarsInTrade) { - g_trade.PositionClose(t); - Print("[EPB] Đóng lệnh — quá ", InpMaxBarsInTrade, " bar"); - } - break; + if(InpMaxBarsInTrade <= 0) + return; + + if(!PositionSelectByTicket(ticket)) + return; + + const datetime openT = (datetime)PositionGetInteger(POSITION_TIME); + const int barsIn = iBarShift(_Symbol, _Period, openT, false); + if(barsIn >= 0 && barsIn >= InpMaxBarsInTrade) { + g_trade.PositionClose(ticket); + Print("[EPB] Đóng lệnh — quá ", InpMaxBarsInTrade, " bar"); + ResetPositionTrack(); } } @@ -704,6 +928,12 @@ bool TryEntryOnBreakout(const int signalShift, const double atr) { return false; } + string allowWhy = ""; + if(!IsAllowedNewEntry(allowWhy)) { + g_setupDetail = allowWhy; + return false; + } + const int touchShift = FindTouchBarShift(); string why = ""; const bool isBuy = (g_setupDir > 0); @@ -923,7 +1153,10 @@ void UpdateChartComment(const int shift, " shift=", touchShift, "\n", "Trade: ", (InpTradeEnabled ? "ON" : "OFF"), " Pos: ", (HasOurPosition() ? "YES" : "NO"), - " Risk: ", DoubleToString(InpRiskPercent, 1), "% TP=", DoubleToString(InpTpRR, 1), "R\n", + " Risk: ", DoubleToString(InpRiskPercent * g_riskScale, 2), "% (×", DoubleToString(g_riskScale, 1), + ") TP=", DoubleToString(InpTpRR, 1), "R\n", + "Spread: ", CurrentSpreadPoints(), " pts", + InpUsePartialAt2R ? StringFormat(" | 2R: chốt %.0f%%+BE", InpPartialCloseFrac * 100.0) : "", "\n", "Close/EMA/ADX: ", DoubleToString(iClose(_Symbol, _Period, shift), _Digits), " / ", DoubleToString(ema, _Digits), " / ", DoubleToString(adx, 1) @@ -931,8 +1164,6 @@ void UpdateChartComment(const int shift, } void OnNewClosedBar() { - ManageOpenPosition(); - const int shift = 1; double ema = 0.0, adx = 0.0, atr = 0.0; @@ -985,6 +1216,9 @@ int OnInit() { } ResetSetupState("init"); + ResetPositionTrack(); + g_equityPeak = AccountInfoDouble(ACCOUNT_EQUITY); + g_riskScale = 1.0; double probe = 0.0; string err = ""; @@ -1002,12 +1236,14 @@ int OnInit() { } PrintFormat( - "[EPB] Init — trade=%s risk=%.1f%% TP=%.1fR | breakout %d bar, %s High/Low touch", + "[EPB] Init Phase 6 — trade=%s risk=%.1f%%×DD TP=%.1fR | spread<=%d | session %02d-%02dh | 2R: %.0f%%+BE | no daily cap", InpTradeEnabled ? "ON" : "OFF", InpRiskPercent, InpTpRR, - InpMaxBarsWaitBreakout, - InpBreakoutUseTouchHighLow ? "Close>" : "Close>Close" + InpMaxSpreadPoints, + InpSessionStartHour, + InpSessionEndHour, + InpPartialCloseFrac * 100.0 ); return INIT_SUCCEEDED; } @@ -1033,6 +1269,9 @@ void OnTradeTransaction(const MqlTradeTransaction &trans, } void OnTick() { + UpdateRiskScale(); + ManageOpenPosition(); + const datetime barOpen = iTime(_Symbol, _Period, 0); if(barOpen == g_lastBarTime) return; diff --git a/Experts/HyperICT.mq5 b/Experts/HyperICT.mq5 index 9bffbeb..214c4e4 100644 --- a/Experts/HyperICT.mq5 +++ b/Experts/HyperICT.mq5 @@ -17,7 +17,7 @@ //| CHƯA CÓ TRONG DỰ ÁN: đặt lệnh, LTF, session, alert | //+------------------------------------------------------------------+ #property copyright "HyperICT" -#property version "1.11" +#property version "1.13" #property description "HTF structure | Fib=init only | pivot confirm=InpSwingRange" #include diff --git a/Experts/ICT_2026_compile.log b/Experts/ICT_2026_compile.log index e109db4..4296249 100644 Binary files a/Experts/ICT_2026_compile.log and b/Experts/ICT_2026_compile.log differ diff --git a/Experts/RsiMomentumEA.mq5 b/Experts/RsiMomentumEA.mq5 index 3e3ae6b..9fff6c1 100644 --- a/Experts/RsiMomentumEA.mq5 +++ b/Experts/RsiMomentumEA.mq5 @@ -1,16 +1,25 @@ //+------------------------------------------------------------------+ //| RsiMomentumEA.mq5 | //| EA tự động — logic độc lập (không đọc RsiMomentumIndicator). | -//| RSI×WMA45 + 5phase entry | ATR↑ EMA200 phiên | Limit 50% body | +//| RSI×WMA45 + 5phase | ATR↑ ADX trend EMA200 phiên | Limit 50% body | //+------------------------------------------------------------------+ #property copyright "RsiMomentumEA" -#property version "4.27" +#property version "4.31" #include #include #include +#include +#include #include +//--- Cơ chế vào lệnh (switch test — logic Limit giữ nguyên trong TradeExecuteLimitOrder) +enum ENUM_RSI_MOM_ENTRY_MODE +{ + RSI_MOM_ENTRY_LIMIT_BODY50 = 0, // Limit @ 50% thân nến tín hiệu (mặc định) + RSI_MOM_ENTRY_MARKET = 1 // Market ngay khi nến mới sau tín hiệu +}; + //--- Input (khớp Indicators/RsiMomentumIndicator/Lib/Inputs.mqh) input group "Chỉ báo" input int InpRSIPeriod = 14; @@ -19,14 +28,29 @@ input int InpWMA45Period = 45; input int InpEMATrendPeriod = 200; input group "Bộ lọc tín hiệu (RSI + ATR + trend EMA200)" -input bool InpTrendFilterEnabled = false; // BUY: close > EMA200 | SELL: close < EMA200 +input bool InpTrendFilterEnabled = true; // BUY: close > EMA200 | SELL: close < EMA200 input int InpTrendConfirmBars = 1; // BUY: N close > EMA200 | SELL: N close < EMA200 -input bool InpAtrExpFilterEnabled = true; // ATR tăng vs N bar + liên tiếp (lọc sideway) +input bool InpAtrExpFilterEnabled = false; // tắt tạm — test riêng ADX input int InpAtrExpPeriod = 14; input int InpAtrExpCompareBars = 3; // so ATR[shift] vs ATR[shift+N] input double InpAtrExpMinRatio = 1.005; // ≥1.005 = +0.5% (tối ưu 1.003–1.02) input int InpAtrExpRiseBars = 2; // ATR tăng liên tiếp N nến (1 = lỏng hơn) +input group "ADX — trend mạnh (pullback hiệu quả)" +input bool InpAdxFilterEnabled = true; // ADX + hướng +DI/-DI tại nến tín hiệu +input int InpAdxPeriod = 14; +input double InpAdxMinLevel = 22.0; // ADX >= ngưỡng (sideway ~<20, trend 22–35+) +input double InpAdxMaxLevel = 0.0; // 0=tắt; ví dụ 45 tránh trend quá già +input bool InpAdxRequireDiDirection = true; // BUY +DI>-DI | SELL -DI>+DI +input double InpAdxMinDiSpread = 0.0; // |+DI−(-DI)| tối thiểu (5–10 = chặt hơn) +input int InpAdxRiseBars = 0; // ADX tăng vs N nến trước (0=tắt, 1–2 bật) + +input group "2 swing — đáy tăng / đỉnh giảm (thân nến)" +input bool InpSwingStructFilterEnabled = true; +input int InpSwingStructRange = 2; // pivot: N nến mỗi bên mỗi đáy/đỉnh +input int InpSwingStructLookback = 120; // quét tối đa N nến trước tín hiệu +input double InpSwingStructTolPts = 0.0; // cho phép 2 đáy/đỉnh bằng nhau (points) + input group "Lọc RSI quá mua / quá bán (nến tín hiệu)" input bool InpRsiObOsFilterEnabled = true; // BUY khi RSI<70 | SELL khi RSI>30 input double InpRSIOverbought = 70.0; // RSI ≥ ngưỡng → bỏ BUY @@ -47,17 +71,20 @@ input double InpPhaseWmaFlatMaxSlope = 0.55; // P4: |slope| WMA45 gần 0 tạ input bool InpPhaseWmaRelaxPrior = true; // P4: chỉ cần WMA45 từng đi đúng hướng, không cần dốc mạnh input double InpPhaseMaxEma9WmaGap = 16.0; // P5: EMA9–WMA45 tối đa khi cắt (lớn hơn = lỏng) -input group "Entry — Limit 50% thân nến tín hiệu" +input group "Cơ chế vào lệnh (switch test)" +input ENUM_RSI_MOM_ENTRY_MODE InpEntryMode = RSI_MOM_ENTRY_MARKET; + +input group "Entry — Limit 50% thân nến (chỉ InpEntryMode=Limit)" input int InpSignalBarShift = 1; // nến tín hiệu (1 = nến vừa đóng) -input group "Quản lý Limit pending" -input int InpLimitExpireBars = 40; // hủy Limit nếu không khớp sau N nến (trước: 20) +input group "Quản lý Limit pending (chỉ InpEntryMode=Limit)" +input int InpLimitExpireBars = 40; // hủy Limit nếu không khớp sau N nến -input group "Debug — đánh dấu RSI×WMA45 (hợp lệ / skip + lý do)" -input bool InpDebugMarkSignals = true; // mọi cross: OK xanh | SIG vàng | SKIP đỏ + nhãn phase fail -input int InpDebugMarkMaxBars = 400; // chỉ tạo mới trong N nến gần nhất (dấu cũ vẫn giữ) -input bool InpDebugLogExperts = true; // 1 dòng Experts / nến tín hiệu (không lặp mỗi tick) -input bool InpDebugHoverHint = true; // rê chuột lên dấu X: panel góc dưới-trái + tooltip +input group "Debug — đánh dấu RSI×WMA45 (tắt = backtest nhanh)" +input bool InpDebugMarkSignals = false; // mọi cross: OK xanh | SIG vàng | SKIP đỏ +input int InpDebugMarkMaxBars = 400; // chỉ tạo mới trong N nến gần nhất +input bool InpDebugLogExperts = false; // 1 dòng Experts / nến tín hiệu +input bool InpDebugHoverHint = false; // rê chuột lên dấu X input group "Mũi tên giao cắt" input color InpArrowUpColor = clrLime; @@ -66,7 +93,7 @@ input int InpArrowOffsetPts = 30; input int InpArrowSize = 1; input group "Panel trạng thái (góc trên-trái)" -input bool InpShowPanel = true; +input bool InpShowPanel = false; // tắt = tester nhanh hơn input int InpPanelFontSize = 8; input int InpPanelLinePad = 14; // khoảng cách dọc giữa các dòng input int InpPanelLeftMargin = 8; @@ -75,9 +102,9 @@ input color InpPanelColorEMA9 = clrGold; input color InpPanelColorWMA45 = clrDodgerBlue; input group "Cảnh báo / Notification (khi có entry mới)" -input bool InpAlertPush = true; -input bool InpAlertPopup = true; -input bool InpAlertSound = true; +input bool InpAlertPush = false; +input bool InpAlertPopup = false; +input bool InpAlertSound = false; input string InpSoundBuy = "alert.wav"; input string InpSoundSell = "alert2.wav"; input bool InpAlertEmail = false; @@ -87,7 +114,7 @@ input group "Giao dịch tự động" input bool InpTradeEnabled = true; input ulong InpMagic = 202602; input double InpRiskPercent = 1; // % balance mất nếu SL khớp (theo lot tính từ SL) -input double InpRewardRiskRatio = 1.1; // R:R — TP = tỷ lệ × khoảng SL (2.0 = 1:2) +input double InpRewardRiskRatio = 1.05; // R:R — TP = tỷ lệ × khoảng SL (2.0 = 1:2) input int InpSwingMaxBars = 30; // quét swing pivot / fallback min-max input int InpSlippagePoints = 30; input bool InpOnePositionFlat = true; @@ -111,7 +138,7 @@ input bool InpSpreadSkipInTester = true; // Tester: bỏ lọc spread ( input bool InpTesterCalcOnNewBarOnly = true; // Tester: OnCalculate chỉ khi nến mới (tránh chậm dần) input group "Quản lý lệnh mở @ 1R" -input bool InpManageAt1R = true; // @1R: chốt một phần + dời SL về entry +input bool InpManageAt1R = false; // @1R: chốt một phần + dời SL về entry input double InpPartialCloseRatio = 0.5; // tỷ lệ volume chốt khi đạt 1R (0.5 = 50%) input int InpBreakevenOffsetPts = 0; // SL tại entry ± point (0 = đúng entry) @@ -122,11 +149,11 @@ input double InpSlAtrMultiplier = 0.5; // khoảng cách thêm = ATR(shift input bool InpSlAtrAddSpread = true; // cộng thêm buffer spread vào SL input group "Xuất CSV thống kê (FILE_COMMON)" -input bool InpExportTradeJournal = true; // journal từng lệnh + summary theo tháng khi kết thúc test/EA +input bool InpExportTradeJournal = false; // bật lại khi cần phân tích CSV (chậm hơn một chút) input bool InpJournalResetOnInit = true; // Tester: xóa CSV cũ mỗi lần chạy backtest mới input group "Thống kê (góc dưới-trái chart)" -input bool InpShowStats = true; +input bool InpShowStats = true; // tắt = tester nhanh hơn input int InpStatFontSize = 9; input int InpStatLinePad = 26; // khoảng cách dọc giữa các dòng (pixel) input int InpStatBottomMargin = 28; // lề dưới block thống kê @@ -146,13 +173,14 @@ int h_WMA45 = INVALID_HANDLE; int h_EMA200 = INVALID_HANDLE; int h_ATR = INVALID_HANDLE; int h_ATR_Regime = INVALID_HANDLE; +int h_ADX = INVALID_HANDLE; const string OBJ_PREFIX = "RsiMomEA_"; const string DBG_PREFIX = OBJ_PREFIX + "DBG_"; -#define PANEL_LINE_COUNT 22 -#define PANEL_IDX_MARKET 16 // dòng 16+ = RSI / signal (sau block trạng thái) +#define PANEL_LINE_COUNT 25 +#define PANEL_IDX_MARKET 19 // dòng 19+ = RSI / signal (sau block trạng thái) const string PNL_PREFIX = OBJ_PREFIX + "pnl_"; -const string EA_VERSION_STR = "4.27"; +const string EA_VERSION_STR = "4.31"; datetime g_dbgLogBarTime = 0; // chống spam Experts: 1 dòng / (nến, BUY|SELL) int g_dbgLogSide = 0; // 1=BUY, -1=SELL @@ -221,6 +249,11 @@ double NormalizeLots(double v); double VolumeForRiskPercent(const bool isBuy, const double entryRef, const double slPrice); void TradeTryOnBarOpen(const int calcRet); void TradeExecuteOrder(const bool isBuy); +void TradeExecuteLimitOrder(const bool isBuy); +void TradeExecuteMarketOrder(const bool isBuy); +bool EntryModeIsLimit(); +bool EntryModeIsMarket(); +string EntryModeLabel(); bool Signal_BodyMidPrice(const int shift, double &midOut); bool LimitPriceValid(const bool isBuy, const double limitPx); void Pending_ManageExpiry(); @@ -233,6 +266,11 @@ bool AtrExp_GetAt(const int shift, double &atr); bool AtrExp_IsExpandingAt(const int shift); bool AtrExp_AllowsAt(const int shift); bool AtrExp_AllowsNow(); +bool Adx_AllowsBuyAt(const int shift, string &why); +bool Adx_AllowsSellAt(const int shift, string &why); +void Adx_GetAtBar(const int shift, double &adx, double &plusDi, double &minusDi); +bool SwingStruct_AllowsBuyAt(const int shift, string &why, double &bodyOld, double &bodyNew); +bool SwingStruct_AllowsSellAt(const int shift, string &why, double &bodyOld, double &bodyNew); int Env_CurrentSpreadPts(); bool Env_SpreadAllows(); bool Env_AllowsSessionAt(const datetime t, string &why); @@ -291,6 +329,12 @@ bool Handles_CreateAll() Print("[RsiMomEA] Không tạo được handle ATR (regime)"); return false; } + h_ADX = iADX(_Symbol, _Period, MathMax(2, InpAdxPeriod)); + if (h_ADX == INVALID_HANDLE) + { + Print("[RsiMomEA] Không tạo được handle ADX"); + return false; + } return true; } @@ -303,7 +347,8 @@ void Handles_ReleaseAll() if (h_EMA200 != INVALID_HANDLE) IndicatorRelease(h_EMA200); if (h_ATR != INVALID_HANDLE) IndicatorRelease(h_ATR); if (h_ATR_Regime != INVALID_HANDLE) IndicatorRelease(h_ATR_Regime); - h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = h_ATR = h_ATR_Regime = INVALID_HANDLE; + if (h_ADX != INVALID_HANDLE) IndicatorRelease(h_ADX); + h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = h_ATR = h_ATR_Regime = h_ADX = INVALID_HANDLE; } //+------------------------------------------------------------------+ @@ -434,8 +479,12 @@ void Panel_UpdateStatus() MathMax(1, InpSignalBarShift)), Panel_ClrOnOff(InpTradeEnabled)); - Panel_SetLine(ln++, StringFormat("Risk %.2f%% | R:R 1:%.2f | Limit 50%% body Exp %d bar", - InpRiskPercent, InpRewardRiskRatio, InpLimitExpireBars), + Panel_SetLine(ln++, StringFormat("Entry: %s", EntryModeLabel()), + InpEntryMode == RSI_MOM_ENTRY_MARKET ? clrGold : clrSilver); + Panel_SetLine(ln++, StringFormat("Risk %.2f%% | R:R 1:%.2f%s", + InpRiskPercent, InpRewardRiskRatio, + EntryModeIsLimit() + ? StringFormat(" | Limit exp %d bar", InpLimitExpireBars) : ""), clrSilver); Panel_SetLine(ln++, StringFormat("Manage @1R: %s (partial %.0f%%)", @@ -453,8 +502,19 @@ void Panel_UpdateStatus() MathMax(1, InpTrendConfirmBars)), InpPhaseFilterEnabled ? clrWhite : clrDimGray); - Panel_SetLine(ln++, StringFormat("ATR expand: %s | RSI OB/OS: %s (%.0f / %.0f)", + Panel_SetLine(ln++, StringFormat("ATR expand: %s | ADX: %s >=%.0f DI: %s", Panel_FmtOnOff(InpAtrExpFilterEnabled), + Panel_FmtOnOff(InpAdxFilterEnabled), + InpAdxMinLevel, + Panel_FmtOnOff(InpAdxRequireDiDirection)), + clrSilver); + + Panel_SetLine(ln++, StringFormat("Swing2 body: %s range=%d LB=%d", + Panel_FmtOnOff(InpSwingStructFilterEnabled), + InpSwingStructRange, InpSwingStructLookback), + clrSilver); + + Panel_SetLine(ln++, StringFormat("RSI OB/OS: %s (%.0f / %.0f)", Panel_FmtOnOff(InpRsiObOsFilterEnabled), InpRSIOverbought, InpRSIOversold), clrSilver); @@ -570,6 +630,17 @@ void Panel_Update(const double &closeArr[], const double &ema200Arr[], Panel_SetLine(ln++, StringFormat("RSI %6.2f", buf_RSI[1]), clrMediumOrchid); Panel_SetLine(ln++, StringFormat("EMA9 %6.2f", buf_EMA9[1]), InpPanelColorEMA9); Panel_SetLine(ln++, StringFormat("WMA45 %6.2f", buf_WMA45[1]), InpPanelColorWMA45); + if(InpAdxFilterEnabled) + { + double adx = 0.0, pdi = 0.0, mdi = 0.0; + Adx_GetAtBar(1, adx, pdi, mdi); + string adxWhy = ""; + const bool adxOk = panelTrendUp ? Adx_AllowsBuyAt(1, adxWhy) + : (panelTrendDown ? Adx_AllowsSellAt(1, adxWhy) : true); + Panel_SetLine(ln++, StringFormat("ADX %5.1f +DI=%.1f -DI=%.1f %s", + adx, pdi, mdi, adxOk ? "OK" : "FAIL"), + adxOk ? clrSilver : clrOrangeRed); + } Panel_SetLine(ln++, StringFormat("EMA200 close[1] %s %.5f", panelTrendUp ? ">" : (panelTrendDown ? "<" : "~"), ema200Arr[1]), trendClr); @@ -707,6 +778,84 @@ bool AtrExp_AllowsNow() return AtrExp_AllowsAt(1); } +//+------------------------------------------------------------------+ +AdxFilterConfig GetAdxFilterConfig() +{ + AdxFilterConfig c; + c.enabled = InpAdxFilterEnabled; + c.period = MathMax(2, InpAdxPeriod); + c.minLevel = MathMax(0.0, InpAdxMinLevel); + c.maxLevel = MathMax(0.0, InpAdxMaxLevel); + c.requireDiDir = InpAdxRequireDiDirection; + c.minDiSpread = MathMax(0.0, InpAdxMinDiSpread); + c.riseBars = MathMax(0, InpAdxRiseBars); + return c; +} + +void Adx_GetAtBar(const int shift, double &adx, double &plusDi, double &minusDi) +{ + adx = plusDi = minusDi = 0.0; + if(!InpAdxFilterEnabled || h_ADX == INVALID_HANDLE) + return; + AdxFilter_GetAt(h_ADX, shift, adx, plusDi, minusDi); +} + +bool Adx_AllowsBuyAt(const int shift, string &why) +{ + why = ""; + if(!InpAdxFilterEnabled) + return true; + return AdxFilter_PassesBuy(h_ADX, shift, GetAdxFilterConfig(), why); +} + +bool Adx_AllowsSellAt(const int shift, string &why) +{ + why = ""; + if(!InpAdxFilterEnabled) + return true; + return AdxFilter_PassesSell(h_ADX, shift, GetAdxFilterConfig(), why); +} + +SwingStructConfig SwingStruct_BuildConfig() +{ + SwingStructConfig c; + c.enabled = InpSwingStructFilterEnabled; + c.pivotRange = MathMax(1, InpSwingStructRange); + c.lookback = MathMax(20, InpSwingStructLookback); + c.tolPts = MathMax(0.0, InpSwingStructTolPts); + return c; +} + +bool SwingStruct_AllowsBuyAt(const int shift, string &why, double &bodyOld, double &bodyNew) +{ + why = ""; + bodyOld = bodyNew = 0.0; + if(!InpSwingStructFilterEnabled) + return true; + + SwingStructPoint older, newer; + const SwingStructConfig cfg = SwingStruct_BuildConfig(); + const bool ok = SwingStruct_PassesBuyAt(_Symbol, _Period, shift, cfg, why, older, newer); + bodyOld = older.bodyPrice; + bodyNew = newer.bodyPrice; + return ok; +} + +bool SwingStruct_AllowsSellAt(const int shift, string &why, double &bodyOld, double &bodyNew) +{ + why = ""; + bodyOld = bodyNew = 0.0; + if(!InpSwingStructFilterEnabled) + return true; + + SwingStructPoint older, newer; + const SwingStructConfig cfg = SwingStruct_BuildConfig(); + const bool ok = SwingStruct_PassesSellAt(_Symbol, _Period, shift, cfg, why, older, newer); + bodyOld = older.bodyPrice; + bodyNew = newer.bodyPrice; + return ok; +} + //+------------------------------------------------------------------+ //| Phiên London/NY + chặn giao phiên + spread tối đa (trade env) | //+------------------------------------------------------------------+ @@ -919,16 +1068,21 @@ bool Signal_RsiOkSellAt(const int shift) //+------------------------------------------------------------------+ string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool phaseOk, const string phaseFail, - const bool atrExpOk, const bool rsiObOsOk, const bool envOk, const bool valid) + const bool atrExpOk, const bool adxOk, const bool swingOk, + const bool rsiObOsOk, const bool envOk, const bool valid) { if(valid) { string s = InpPhaseFilterEnabled ? "RSI↑WMA45 | 5phase OK | EMA200 UP" : "RSI↑WMA45 EMA9=%.0f", InpAdxMinLevel); + if(InpSwingStructFilterEnabled) + s += " | 2đáy↑"; return s; } @@ -941,6 +1095,10 @@ string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool ph f += "ngược EMA200 "; if(InpAtrExpFilterEnabled && !atrExpOk) f += "ATR co "; + if(InpAdxFilterEnabled && !adxOk) + f += "ADX yếu "; + if(InpSwingStructFilterEnabled && !swingOk) + f += "2 đáy "; if(InpRsiObOsFilterEnabled && !rsiObOsOk) f += "RSI quá mua "; if(!envOk) @@ -951,16 +1109,21 @@ string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool ph } string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool phaseOk, const string phaseFail, - const bool atrExpOk, const bool rsiObOsOk, const bool envOk, const bool valid) + const bool atrExpOk, const bool adxOk, const bool swingOk, + const bool rsiObOsOk, const bool envOk, const bool valid) { if(valid) { string s = InpPhaseFilterEnabled ? "RSI↓WMA45 | 5phase OK | EMA200 DOWN" : "RSI↓WMA45 EMA9>WMA45 | EMA200 DOWN"; - s += StringFormat(" (%d bar) | Limit 50%% body", MathMax(1, InpTrendConfirmBars)); + s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel()); if(InpAtrExpFilterEnabled) s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars); + if(InpAdxFilterEnabled) + s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel); + if(InpSwingStructFilterEnabled) + s += " | 2đỉnh↓"; return s; } @@ -973,6 +1136,10 @@ string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool f += "ngược EMA200 "; if(InpAtrExpFilterEnabled && !atrExpOk) f += "ATR co "; + if(InpAdxFilterEnabled && !adxOk) + f += "ADX yếu "; + if(InpSwingStructFilterEnabled && !swingOk) + f += "2 đỉnh "; if(InpRsiObOsFilterEnabled && !rsiObOsOk) f += "RSI quá bán "; if(!envOk) @@ -1038,12 +1205,27 @@ void Signal_DebugMarkCross(const long ch, const int shift, const datetime barTim if(DebugMarksEffective() && Signal_DebugMarkExists(ch, DBG_PREFIX, barTime, isBuy)) return; + double adxVal = 0.0, plusDi = 0.0, minusDi = 0.0; + string adxWhy = ""; + Adx_GetAtBar(shift, adxVal, plusDi, minusDi); + const bool adxOkBar = isBuy ? Adx_AllowsBuyAt(shift, adxWhy) : Adx_AllowsSellAt(shift, adxWhy); + + string swingWhy = ""; + double swingOld = 0.0, swingNew = 0.0; + const bool swingOkBar = isBuy + ? SwingStruct_AllowsBuyAt(shift, swingWhy, swingOld, swingNew) + : SwingStruct_AllowsSellAt(shift, swingWhy, swingOld, swingNew); + SignalEvalResult ev; if(isBuy) ev = Signal_EvaluateBuyAt(shift, rates_total, trendN, buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr, phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled, - InpAtrExpFilterEnabled, InpRsiObOsFilterEnabled, + InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar, + adxVal, plusDi, minusDi, adxWhy, + InpSwingStructFilterEnabled, swingOkBar, + swingOld, swingNew, swingWhy, + InpRsiObOsFilterEnabled, InpRSIOverbought, InpRSIOversold, InpSessionFilterEnabled, atrExpOkBar, sessionAtBar, sessionFailWhy); @@ -1051,7 +1233,11 @@ void Signal_DebugMarkCross(const long ch, const int shift, const datetime barTim ev = Signal_EvaluateSellAt(shift, rates_total, trendN, buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr, phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled, - InpAtrExpFilterEnabled, InpRsiObOsFilterEnabled, + InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar, + adxVal, plusDi, minusDi, adxWhy, + InpSwingStructFilterEnabled, swingOkBar, + swingOld, swingNew, swingWhy, + InpRsiObOsFilterEnabled, InpRSIOverbought, InpRSIOversold, InpSessionFilterEnabled, atrExpOkBar, sessionAtBar, sessionFailWhy); @@ -1127,6 +1313,13 @@ void SignalScan_Run(const int barsToScan, const int rates_total, const int need, const bool coreBuyOk = InpPhaseFilterEnabled ? phaseBuyOk : ema9BuyOk; const bool coreSellOk = InpPhaseFilterEnabled ? phaseSellOk : ema9SellOk; const bool atrExpOkBar = AtrExp_AllowsAt(i); + string adxWhyB = "", adxWhyS = ""; + const bool adxOkBuy = Adx_AllowsBuyAt(i, adxWhyB); + const bool adxOkSell = Adx_AllowsSellAt(i, adxWhyS); + string swingWhyB = "", swingWhyS = ""; + double swOld = 0.0, swNew = 0.0; + const bool swingOkBuy = SwingStruct_AllowsBuyAt(i, swingWhyB, swOld, swNew); + const bool swingOkSell = SwingStruct_AllowsSellAt(i, swingWhyS, swOld, swNew); const bool rsiOkBuy = Signal_RsiOkBuyAt(i); const bool rsiOkSell = Signal_RsiOkSellAt(i); string envWhy = ""; @@ -1134,7 +1327,8 @@ void SignalScan_Run(const int barsToScan, const int rates_total, const int need, if(crossUpWma45) { - const bool validBuy = coreBuyOk && trendUp && atrExpOkBar && rsiOkBuy && envOkBar; + const bool validBuy = coreBuyOk && trendUp && atrExpOkBar && adxOkBuy + && swingOkBuy && rsiOkBuy && envOkBar; if(dbgMarks && i <= dbgMax) { @@ -1148,7 +1342,8 @@ void SignalScan_Run(const int barsToScan, const int rates_total, const int need, if(validBuy) { const string reason = Signal_ReasonBuy(trendUp, ema9BuyOk, phaseBuyOk, phaseFailBuy, - atrExpOkBar, rsiOkBuy, envOkBar, true); + atrExpOkBar, adxOkBuy, swingOkBuy, + rsiOkBuy, envOkBar, true); const double arrowPrice = lowArr[i] - arrowOffset; const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]); if(ObjectFind(ch, arrowName) < 0) @@ -1168,7 +1363,8 @@ void SignalScan_Run(const int barsToScan, const int rates_total, const int need, if(crossDownWma45) { - const bool validSell = coreSellOk && trendDown && atrExpOkBar && rsiOkSell && envOkBar; + const bool validSell = coreSellOk && trendDown && atrExpOkBar && adxOkSell + && swingOkSell && rsiOkSell && envOkBar; if(dbgMarks && i <= dbgMax) { @@ -1182,7 +1378,8 @@ void SignalScan_Run(const int barsToScan, const int rates_total, const int need, if(validSell) { const string reason = Signal_ReasonSell(trendDown, ema9SellOk, phaseSellOk, phaseFailSell, - atrExpOkBar, rsiOkSell, envOkBar, true); + atrExpOkBar, adxOkSell, swingOkSell, + rsiOkSell, envOkBar, true); const double arrowPrice = highArr[i] + arrowOffset; const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]); if(ObjectFind(ch, arrowName) < 0) @@ -1227,11 +1424,14 @@ void Diagnostics_FirstPass(const int rates_total, const int copyN, const int tre { int crossUp = 0, crossDn = 0, passTrendUp = 0, passTrendDn = 0, passAtrUp = 0, passAtrDn = 0; + int passAdxUp = 0, passAdxDn = 0; + int passSwingUp = 0, passSwingDn = 0; int passEnvUp = 0, passEnvDn = 0; int passEma9Up = 0, passEma9Dn = 0; int passPhaseUp = 0, passPhaseDn = 0; int validBuy = 0, validSell = 0; - const int needSh = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars); + const int needSh = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars) + + (InpAdxFilterEnabled ? MathMax(0, InpAdxRiseBars) : 0); const int phaseLb = InpPhaseFilterEnabled ? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 3 : 0; @@ -1253,6 +1453,12 @@ void Diagnostics_FirstPass(const int rates_total, const int copyN, const int tre passTrendDn++; if(up && AtrExp_AllowsAt(i)) passAtrUp++; if(dn && AtrExp_AllowsAt(i)) passAtrDn++; + string adxW = ""; + if(up && Adx_AllowsBuyAt(i, adxW)) passAdxUp++; + if(dn && Adx_AllowsSellAt(i, adxW)) passAdxDn++; + double swO = 0.0, swN = 0.0; + if(up && SwingStruct_AllowsBuyAt(i, adxW, swO, swN)) passSwingUp++; + if(dn && SwingStruct_AllowsSellAt(i, adxW, swO, swN)) passSwingDn++; if(up && Signal_Ema9CoreBuyOkAt(i)) passEma9Up++; if(dn && Signal_Ema9CoreSellOkAt(i)) passEma9Dn++; string pfB = "", pfS = ""; @@ -1265,16 +1471,20 @@ void Diagnostics_FirstPass(const int rates_total, const int copyN, const int tre if(buf_Signal[i] > 0.5) validBuy++; if(buf_Signal[i] < -0.5) validSell++; } - PrintFormat("[RsiMomEA] last %d bars: cross UP=%d DN=%d | EMA9 UP=%d DN=%d | 5phase UP=%d DN=%d | EMA200 UP=%d DOWN=%d | ATR↑ UP=%d DN=%d | phiên UP=%d DN=%d | signal BUY=%d SELL=%d", + PrintFormat("[RsiMomEA] last %d bars: cross UP=%d DN=%d | EMA9 UP=%d DN=%d | 5phase UP=%d DN=%d | EMA200 UP=%d DOWN=%d | ATR↑ UP=%d DN=%d | ADX UP=%d DN=%d | Swing2 UP=%d DN=%d | phiên UP=%d DN=%d | signal BUY=%d SELL=%d", n, crossUp, crossDn, passEma9Up, passEma9Dn, passPhaseUp, passPhaseDn, - passTrendUp, passTrendDn, passAtrUp, passAtrDn, - passEnvUp, passEnvDn, validBuy, validSell); + passTrendUp, passTrendDn, passAtrUp, passAtrDn, passAdxUp, passAdxDn, + passSwingUp, passSwingDn, passEnvUp, passEnvDn, validBuy, validSell); if(crossUp > 0 && passPhaseUp == 0 && InpPhaseFilterEnabled) Print("[RsiMomEA] Gợi ý: cross UP bị 5phase — xem P1-P5 hoặc hạ InpPhaseMinExpandSpread / InpPhaseMinRsiEma9Cross"); else if(crossUp > 0 && passEma9Up == 0) Print("[RsiMomEA] Gợi ý: cross UP nhưng EMA9>=WMA45 — không đủ điều kiện lõi BUY"); if(InpAtrExpFilterEnabled && crossUp > 0 && passAtrUp == 0) Print("[RsiMomEA] Gợi ý: cross bị chặn ATR — hạ InpAtrExpMinRatio / InpAtrExpRiseBars hoặc tắt InpAtrExpFilterEnabled"); + if(InpAdxFilterEnabled && crossUp > 0 && passAdxUp == 0) + Print("[RsiMomEA] Gợi ý: cross bị chặn ADX — hạ InpAdxMinLevel hoặc tắt InpAdxRequireDiDirection"); + if(InpSwingStructFilterEnabled && crossUp > 0 && passSwingUp == 0) + Print("[RsiMomEA] Gợi ý: cross bị chặn 2 đáy — tăng lookback hoặc tắt InpSwingStructFilterEnabled"); if(InpSessionFilterEnabled && crossUp > 0 && passEnvUp == 0) Print("[RsiMomEA] Gợi ý: cross bị chặn PHIÊN — chỉnh giờ London/NY (server) hoặc tắt InpSessionFilterEnabled"); } @@ -1302,11 +1512,14 @@ void EnsureBuffers(const int rates_total) int RsiMomentum_OnCalculate(const int rates_total, const int prev_calculated) { const int atrNeed = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars) + 5; + const int adxNeed = InpAdxFilterEnabled + ? MathMax(InpAdxPeriod, 5) + MathMax(0, InpAdxRiseBars) + 3 + : 0; const int phaseNeed = InpPhaseFilterEnabled ? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 10 : 0; const int minBars = MathMax(InpWMA45Period + InpRSIPeriod + 5 + phaseNeed, - MathMax(InpAtrExpPeriod, InpSlAtrPeriod) + atrNeed); + MathMax(MathMax(InpAtrExpPeriod, InpSlAtrPeriod) + atrNeed, adxNeed)); if (rates_total < minBars) return 0; EnsureBuffers(rates_total); @@ -1316,19 +1529,23 @@ int RsiMomentum_OnCalculate(const int rates_total, const int prev_calculated) const int wmaBars = BarsCalculated(h_WMA45); const int ema200Bars = BarsCalculated(h_EMA200); const int atrRegBars = BarsCalculated(h_ATR_Regime); - if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0 || atrRegBars <= 0) + const int adxBars = InpAdxFilterEnabled ? BarsCalculated(h_ADX) : 1; + if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0 || atrRegBars <= 0 + || adxBars <= 0) { static datetime lastWarn = 0; if (TimeCurrent() - lastWarn > 30) { - PrintFormat("[RsiMomEA] Source not ready: RSI=%d EMA9=%d WMA45=%d EMA200=%d ATRreg=%d (rates=%d)", - rsiBars, ema9Bars, wmaBars, ema200Bars, atrRegBars, rates_total); + PrintFormat("[RsiMomEA] Source not ready: RSI=%d EMA9=%d WMA45=%d EMA200=%d ATRreg=%d ADX=%d (rates=%d)", + rsiBars, ema9Bars, wmaBars, ema200Bars, atrRegBars, adxBars, rates_total); lastWarn = TimeCurrent(); } return 0; } - const int srcMin = MathMin(MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars), atrRegBars); + int srcMin = MathMin(MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars), atrRegBars); + if(InpAdxFilterEnabled) + srcMin = MathMin(srcMin, adxBars); const int copyN = MathMin(srcMin, rates_total); if (copyN < minBars) return 0; @@ -1405,11 +1622,17 @@ int OnInit() const long chInit = ActChart(); ObjectsDeleteAll(chInit, OBJ_PREFIX + "RSN_"); - Signal_DebugConfigureChart(chInit); + ObjectsDeleteAll(chInit, DBG_PREFIX); + if(DebugMarksEffective()) + Signal_DebugConfigureChart(chInit); - Panel_CreateAll(); - Stats_CreateObjects(); - Stats_UpdateDisplay(); + if(InpShowPanel) + Panel_CreateAll(); + if(InpShowStats) + { + Stats_CreateObjects(); + Stats_UpdateDisplay(); + } const int spr = Env_CurrentSpreadPts(); string envWhy = ""; const bool envNow = Env_AllowsTradeAtBar(MathMax(1, InpSignalBarShift), envWhy); @@ -1417,13 +1640,17 @@ int OnInit() Print("[RsiMomEA] Journal CSV: ", RsiMomJournal_TradesPath(_Symbol, _Period), " | summary: ", RsiMomJournal_SummaryPath(_Symbol, _Period), " (FILE_COMMON)"); - Print("[RsiMomEA] Init OK v4.14 — RSI×WMA45 + ", InpPhaseFilterEnabled ? "5phase" : "core", + Print("[RsiMomEA] Init OK v", EA_VERSION_STR, " — entry=", EntryModeLabel(), + " | RSI×WMA45 + ", InpPhaseFilterEnabled ? "5phase" : "core", " | EMA200=", InpTrendFilterEnabled ? "on" : "OFF", " | session=", InpSessionFilterEnabled ? "on" : "OFF", " | debugMarks=", InpDebugMarkSignals ? "on" : "off", " | ATR+EMA200+phiên | trade=", InpTradeEnabled ? "on" : "off", " risk%=", InpRiskPercent, " ATRexp=", InpAtrExpFilterEnabled ? StringFormat("ratio>=%.2f rise%d cmp%d", InpAtrExpMinRatio, InpAtrExpRiseBars, InpAtrExpCompareBars) : "OFF", + " ADX=", InpAdxFilterEnabled + ? StringFormat(">=%.0f DI=%s rise%d", InpAdxMinLevel, + InpAdxRequireDiDirection ? "on" : "off", InpAdxRiseBars) : "OFF", " session=", InpSessionFilterEnabled ? StringFormat("L%d-%d NY%d-%d avoid%d%s", InpLondonStartHour, InpLondonEndHour, InpNYStartHour, InpNYEndHour, InpSessionAvoidLastMin, @@ -1458,8 +1685,11 @@ void OnDeinit(const int reason) //+------------------------------------------------------------------+ void OnTick() { - Pending_EnvCancelIfBad(); - Pending_ManageExpiry(); + if(EntryModeIsLimit()) + { + Pending_EnvCancelIfBad(); + Pending_ManageExpiry(); + } Position_ManageAt1R(); const datetime t0 = iTime(_Symbol, _Period, 0); @@ -1650,8 +1880,33 @@ bool LimitPriceValid(const bool isBuy, const double limitPx) return (limitPx > tk.bid + md); } +//+------------------------------------------------------------------+ +bool EntryModeIsLimit() +{ + return (InpEntryMode == RSI_MOM_ENTRY_LIMIT_BODY50); +} + +bool EntryModeIsMarket() +{ + return (InpEntryMode == RSI_MOM_ENTRY_MARKET); +} + +string EntryModeLabel() +{ + return EntryModeIsMarket() ? "Market" : "Limit 50% body"; +} + //+------------------------------------------------------------------+ void TradeExecuteOrder(const bool isBuy) +{ + if(EntryModeIsMarket()) + TradeExecuteMarketOrder(isBuy); + else + TradeExecuteLimitOrder(isBuy); +} + +//+------------------------------------------------------------------+ +void TradeExecuteLimitOrder(const bool isBuy) { if (InpOnePositionFlat && HasMyMagicPositionOrPending()) return; @@ -1720,6 +1975,61 @@ void TradeExecuteOrder(const bool isBuy) } } +//+------------------------------------------------------------------+ +void TradeExecuteMarketOrder(const bool isBuy) +{ + if(InpOnePositionFlat && CountMyMagicPositions() > 0) + return; + + string envWhy = ""; + if(!Env_AllowsTradeNow(envWhy)) + { + Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL", ": ", envWhy, + "(spread=", Env_CurrentSpreadPts(), " pts)"); + return; + } + + MqlTick tk; + if(!SymbolInfoTick(_Symbol, tk)) + { + Print("[RsiMomEA] Trade skip: không lấy được tick"); + return; + } + + const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + const double entryPx = NormalizeDouble(isBuy ? tk.ask : tk.bid, dig); + + double sl = 0.0, tp = 0.0; + if(!NearestSwingSlTp(isBuy, entryPx, dig, sl, tp)) + { + Print("[RsiMomEA] Trade skip: SL/TP swing không hợp lệ"); + return; + } + if(!StopsValid(isBuy, entryPx, sl, tp)) + { + Print("[RsiMomEA] Trade skip: STOPS_LEVEL / FREEZE"); + return; + } + + double vol = VolumeForRiskPercent(isBuy, entryPx, sl); + vol = NormalizeLots(vol); + if(vol <= 0.0) + { + Print("[RsiMomEA] Trade skip: volume=0"); + return; + } + + const bool ok = isBuy + ? g_trade.Buy(vol, _Symbol, 0.0, sl, tp, "RsiMom BUY mkt") + : g_trade.Sell(vol, _Symbol, 0.0, sl, tp, "RsiMom SELL mkt"); + if(!ok) + Print("[RsiMomEA] Market fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment()); + else + Print("[RsiMomEA] Market OK #", g_trade.ResultDeal(), " ", isBuy ? "BUY" : "SELL", + " @~", DoubleToString(entryPx, dig), + " vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig)); +} + //+------------------------------------------------------------------+ void TradeTryOnBarOpen(const int calcRet) { @@ -2063,7 +2373,8 @@ void Stats_UpdateDisplay() ObjectSetString(ch, STAT_L1, OBJPROP_TEXT, ""); ObjectSetString(ch, STAT_L2, OBJPROP_TEXT, ""); ObjectSetString(ch, STAT_L3, OBJPROP_TEXT, ""); - ChartRedraw(ch); + if(ChartRedrawEffective()) + ChartRedraw(ch); return; } diff --git a/Experts/RsiMomentumEA_README.md b/Experts/RsiMomentumEA_README.md index eeca64f..7ce1d57 100644 --- a/Experts/RsiMomentumEA_README.md +++ b/Experts/RsiMomentumEA_README.md @@ -94,6 +94,7 @@ Không có cross → không xét tiếp. |-------|------------------|------------------------| | `InpTrendFilterEnabled` | `false` | BUY: `InpTrendConfirmBars` nến liên tiếp có `Close > EMA200`. SELL: `Close < EMA200`. | | `InpAtrExpFilterEnabled` | `true` | ATR(shift) / ATR(shift+cmp) ≥ `InpAtrExpMinRatio` **và** ATR tăng liên tiếp `InpAtrExpRiseBars` nến. | +| `InpAdxFilterEnabled` | `true` | ADX ≥ `InpAdxMinLevel`; BUY +DI>−DI, SELL ngược; tùy chọn ADX tăng / max / spread DI. | | `InpSessionFilterEnabled` | `false` | Thời gian **nến tín hiệu** nằm London hoặc NY; không trong cửa sổ giao phiên; trừ `InpSessionAvoidLastMin` phút cuối phiên. | **Tín hiệu hợp lệ (ghi `buf_Signal`):** @@ -458,4 +459,22 @@ Debug: SKIP `ATR co` hoặc tooltip `ATR:FAIL`. Nếu **0 lệnh** sau bật fil --- -*Tài liệu đồng bộ với mã nguồn v4.25.* +--- + +## 17. Lọc ADX (trend mạnh) + +**Mục tiêu:** Chỉ pullback khi thị trường **có trend** (ADX đủ cao) và **đúng hướng** (+DI/−DI). + +| Input | Mặc định | Ý nghĩa | +|-------|----------|---------| +| `InpAdxMinLevel` | 22 | ADX < 20 thường sideway; 22–35 vùng trend ổn | +| `InpAdxRequireDiDirection` | true | BUY: +DI > −DI; SELL: −DI > +DI | +| `InpAdxMinDiSpread` | 0 | Chênh +DI−(−DI) tối thiểu (thử 5–10) | +| `InpAdxRiseBars` | 0 | ADX tăng vs N nến (1–2 = trend đang mạnh lên) | +| `InpAdxMaxLevel` | 0 | 0=tắt; ví dụ 45 tránh vào khi trend quá già | + +Debug: `ADX:OK(28.5 +DI=32.0 -DI=18.0)` hoặc SKIP `ADX yếu`. + +--- + +*Tài liệu đồng bộ với mã nguồn v4.29.* diff --git a/Include/HyperICT/README.md b/Include/HyperICT/README.md index 31d05e2..b6dd4a5 100644 --- a/Include/HyperICT/README.md +++ b/Include/HyperICT/README.md @@ -112,9 +112,9 @@ Gán pivot: từ L0/H0 gần nhất, lùi từng bước tới pivot cũ **kề* #### Bull CHoCH (phá L0) 1. **P1:** Track `newL0` (low thấp hơn). -2. **P2:** `IsConfirmedSwingLow(newL0)` → vào phase 3. +2. **P2:** `IsConfirmedSwingLow(newL0)` **hoặc** hồi lên đủ `InpSwingRange` (swing high sau đáy) → khóa `newL0`, vào phase 3 (không kéo `newL0` nữa). 3. **P3 case 1:** Phá lên H0 → `newH0` confirm → roll bull (`L1` giữ). -4. **P3 case 2:** Phá xuống `newL0` → `newH0` + `newL02` confirm → roll bear. +4. **P3 case 2:** Body phá xuống `newL0` đã khóa → track `newH0` (confirm) → track `newL02` (confirm tuần tự) → roll bear. Bear Continue / CHoCH: **đối xứng** với bull. diff --git a/Include/HyperICT/Types.mqh b/Include/HyperICT/Types.mqh index c49f38b..140c942 100644 --- a/Include/HyperICT/Types.mqh +++ b/Include/HyperICT/Types.mqh @@ -123,9 +123,11 @@ struct UpdateContext SwingPoint newH0; SwingPoint newL0; SwingPoint newL02; // CHoCH bull case2 → roll thành L0 - bool newH0Locked; // ngừng track high khi swing confirm - bool newLLocked; - bool case1TrackingH0; // CHoCH P3 case1: đang tạo đỉnh mới + bool newH0Locked; + bool newLLocked; // newL0 hoàn thiện (P2) — không kéo newL0 nữa + bool case1TrackingH0; // P3 case1: phá lên H0 + bool case2Active; // P3 case2: phá xuống newL0 đã khóa + bool newH0Case2Locked; // P3 case2: newH0 (hồi) đã confirm void Clear() { event = UPD_NONE; @@ -134,6 +136,8 @@ struct UpdateContext newH0.Clear(); newL0.Clear(); newL02.Clear(); newH0Locked = newLLocked = false; case1TrackingH0 = false; + case2Active = false; + newH0Case2Locked = false; } }; diff --git a/Include/HyperICT/UpdateEngine.mqh b/Include/HyperICT/UpdateEngine.mqh index ea24296..74192b0 100644 --- a/Include/HyperICT/UpdateEngine.mqh +++ b/Include/HyperICT/UpdateEngine.mqh @@ -1,18 +1,5 @@ //+------------------------------------------------------------------+ -//| UpdateEngine.mqh | -//| §1.4 CHoCH & Trend Continue — cập nhật incremental | -//+------------------------------------------------------------------+ -//| ĐÃ GIẢI QUYẾT: | -//| • Chỉ chạy khi nến HTF mới đóng; không quét lại full lookback | -//| • Phá Key LV1 (body) → CHoCH | Phá Key LV2 → Continue | -//| • §1.4a Bull Continue P1: track newH0 (nến xanh), newL0 (đáy) | -//| • §1.4a P2: IsConfirmedSwingHigh(newH0) → roll, khóa newH0 | -//| • §1.4b Bull CHoCH P1–2: track newL0 → swing low confirm → P3 | -//| • §1.4b P3 case1: phá H0 → newH0 confirm → roll (L1 giữ) | -//| • §1.4b P3 case2: phá newL0 → newH0+newL02 confirm → bear roll| -//| • §1.4c/d Bear: đối xứng Continue & CHoCH | -//| • Xác nhận đỉnh/đáy = InpSwingRange (KHÔNG dùng Fib ở đây) | -//| CHƯA TỐI ƯU: gắn OB chính xác cho newL0 mỗi leg; edge case đa tín hiệu | +//| UpdateEngine.mqh — §1.4 CHoCH / Continue | //+------------------------------------------------------------------+ #ifndef HYPERICT_UPDATEENGINE_MQH #define HYPERICT_UPDATEENGINE_MQH @@ -31,9 +18,9 @@ class CUpdateEngine } static void TrackNewHigh(const string sym, const ENUM_TIMEFRAMES tf, - const int shift, SwingPoint &pt, const bool onlyIfLocked) + const int shift, SwingPoint &pt, const bool frozen) { - if(onlyIfLocked) + if(frozen) return; const double h = iHigh(sym, tf, shift); if(!pt.Valid() || h > pt.price) @@ -41,27 +28,112 @@ class CUpdateEngine } static void TrackNewLow(const string sym, const ENUM_TIMEFRAMES tf, - const int shift, SwingPoint &pt, const bool onlyIfLocked) + const int shift, SwingPoint &pt, const bool frozen) { - if(onlyIfLocked) + if(frozen) return; const double l = iLow(sym, tf, shift); if(!pt.Valid() || l < pt.price) CSwingEngine::FillSwingLow(sym, tf, shift, pt); } - static void FinalizePivotHigh(const string sym, const ENUM_TIMEFRAMES tf, - SwingPoint &pt, const int range) + static bool ConfirmedSwingHighAt(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const int range) { - if(pt.Valid()) - CSwingEngine::FillSwingHigh(sym, tf, pt.shift, pt); + SwingPoint pt; + CSwingEngine::FillSwingHigh(sym, tf, shift, pt); + return CSwingEngine::IsConfirmedSwingHigh(sym, tf, pt, range); } - static void FinalizePivotLow(const string sym, const ENUM_TIMEFRAMES tf, - SwingPoint &pt, const int range) + static bool ConfirmedSwingLowAt(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const int range) { - if(pt.Valid()) - CSwingEngine::FillSwingLow(sym, tf, pt.shift, pt); + SwingPoint pt; + CSwingEngine::FillSwingLow(sym, tf, shift, pt); + return CSwingEngine::IsConfirmedSwingLow(sym, tf, pt, range); + } + + // P2 bull CHoCH: đáy confirm (swing low) HOẶC đã hồi lên có swing high sau đáy + static bool TryConfirmBullChochNewL0(const string sym, const ENUM_TIMEFRAMES tf, + const SwingPoint &newL0, const int range) + { + if(!newL0.Valid()) + return false; + + if(CSwingEngine::IsConfirmedSwingLow(sym, tf, newL0, range)) + return true; + + for(int sh = 1; sh < newL0.shift; sh++) + { + if(iLow(sym, tf, sh) <= newL0.price + _Point) + continue; + if(ConfirmedSwingHighAt(sym, tf, sh, range)) + return true; + } + return false; + } + + // Đỉnh hồi cao nhất (đã confirm) sau đáy newL0 — dùng seed newH0 case2 + static bool FindRetraceHighAfterLow(const string sym, const ENUM_TIMEFRAMES tf, + const SwingPoint &low, const int range, + SwingPoint &outHigh) + { + outHigh.Clear(); + if(!low.Valid()) + return false; + + for(int sh = 1; sh < low.shift; sh++) + { + if(iLow(sym, tf, sh) <= low.price + _Point) + continue; + if(!ConfirmedSwingHighAt(sym, tf, sh, range)) + continue; + const double h = iHigh(sym, tf, sh); + if(!outHigh.Valid() || h > outHigh.price) + CSwingEngine::FillSwingHigh(sym, tf, sh, outHigh); + } + return outHigh.Valid(); + } + + static bool FindRetraceLowAfterHigh(const string sym, const ENUM_TIMEFRAMES tf, + const SwingPoint &high, const int range, + SwingPoint &outLow) + { + outLow.Clear(); + if(!high.Valid()) + return false; + + for(int sh = 1; sh < high.shift; sh++) + { + if(iHigh(sym, tf, sh) >= high.price - _Point) + continue; + if(!ConfirmedSwingLowAt(sym, tf, sh, range)) + continue; + const double l = iLow(sym, tf, sh); + if(!outLow.Valid() || l < outLow.price) + CSwingEngine::FillSwingLow(sym, tf, sh, outLow); + } + return outLow.Valid(); + } + + // P2 bear CHoCH: đỉnh confirm hoặc hồi xuống có swing low sau đỉnh + static bool TryConfirmBearChochNewH0(const string sym, const ENUM_TIMEFRAMES tf, + const SwingPoint &newH0, const int range) + { + if(!newH0.Valid()) + return false; + + if(CSwingEngine::IsConfirmedSwingHigh(sym, tf, newH0, range)) + return true; + + for(int sh = 1; sh < newH0.shift; sh++) + { + if(iHigh(sym, tf, sh) >= newH0.price - _Point) + continue; + if(ConfirmedSwingLowAt(sym, tf, sh, range)) + return true; + } + return false; } static void FinishRoll(HtfContext &ctx) @@ -71,6 +143,50 @@ class CUpdateEngine Dbg(StringFormat("Roll xong — bias=%d", ctx.structBias)); } + static void LockBullChochNewL0(HtfContext &ctx) + { + CSwingEngine::FillSwingLow(ctx.symbol, ctx.htf, ctx.update.newL0.shift, ctx.update.newL0); + ctx.update.newLLocked = true; + ctx.update.phase = UPD_PHASE_CHOCH_RESOLVE; + ctx.update.case2Active = false; + ctx.update.newH0Case2Locked = false; + ctx.update.newL02.Clear(); + + SwingPoint retrace; + if(FindRetraceHighAfterLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange, retrace)) + { + ctx.update.newH0 = retrace; + Dbg(StringFormat("Bull CHoCH P2 — seed newH0 @ %.5f (hồi sau đáy)", retrace.price)); + } + else + ctx.update.newH0.Clear(); + + Dbg(StringFormat("Bull CHoCH P2 — newL0 khóa @ %.5f sh=%d", + ctx.update.newL0.price, ctx.update.newL0.shift)); + } + + static void LockBearChochNewH0(HtfContext &ctx) + { + CSwingEngine::FillSwingHigh(ctx.symbol, ctx.htf, ctx.update.newH0.shift, ctx.update.newH0); + ctx.update.newH0Locked = true; + ctx.update.phase = UPD_PHASE_CHOCH_RESOLVE; + ctx.update.case2Active = false; + ctx.update.newH0Case2Locked = false; + ctx.update.newL02.Clear(); + + SwingPoint retrace; + if(FindRetraceLowAfterHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange, retrace)) + { + ctx.update.newL0 = retrace; + Dbg(StringFormat("Bear CHoCH P2 — seed newL0 @ %.5f (hồi sau đỉnh)", retrace.price)); + } + else + ctx.update.newL0.Clear(); + + Dbg(StringFormat("Bear CHoCH P2 — newH0 khóa @ %.5f sh=%d", + ctx.update.newH0.price, ctx.update.newH0.shift)); + } + public: static bool IsNewHtfBar(HtfContext &ctx) { @@ -78,7 +194,6 @@ public: return t0 != 0 && t0 != ctx.lastHtfBar; } - //--- Phát hiện body phá Key LV1/L2 lần đầu → bật BUILD static void DetectBreakEvents(HtfContext &ctx) { if(ctx.update.phase != UPD_PHASE_IDLE) @@ -94,7 +209,10 @@ public: ctx.update.newL0.Clear(); ctx.update.newH0Locked = false; ctx.update.newLLocked = false; - Dbg("Bull CHoCH — body break L0"); + ctx.update.case2Active = false; + ctx.update.newH0Case2Locked = false; + ctx.update.case1TrackingH0 = false; + Dbg("Bull CHoCH P1 — body break L0"); } else if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.keyLv2.price)) { @@ -116,7 +234,10 @@ public: ctx.update.newH0.Clear(); ctx.update.newH0Locked = false; ctx.update.newLLocked = false; - Dbg("Bear CHoCH — body break H0"); + ctx.update.case2Active = false; + ctx.update.newH0Case2Locked = false; + ctx.update.case1TrackingH0 = false; + Dbg("Bear CHoCH P1 — body break H0"); } else if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.keyLv2.price)) { @@ -131,7 +252,6 @@ public: } } - //--- §1.4a Bull Continue — P1 track + P2 swing high confirm → roll static void ProcessBullContinueBuild(HtfContext &ctx, const int sh) { if(CKeyLevels::IsBullCandle(ctx.symbol, ctx.htf, sh)) @@ -145,12 +265,11 @@ public: ctx.update.newH0, InpSwingRange)) return; - FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); - ctx.update.newH0Locked = true; - FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); + CSwingEngine::FillSwingHigh(ctx.symbol, ctx.htf, ctx.update.newH0.shift, ctx.update.newH0); + CSwingEngine::FillSwingLow(ctx.symbol, ctx.htf, ctx.update.newL0.shift, ctx.update.newL0); CSwingEngine::RollBullContinue(ctx.swings, ctx.update.newH0, ctx.update.newL0); FinishRoll(ctx); - Dbg("Bull Continue — swing high confirmed, rolled"); + Dbg("Bull Continue — roll"); } static void ProcessBearContinueBuild(HtfContext &ctx, const int sh) @@ -166,144 +285,206 @@ public: ctx.update.newL0, InpSwingRange)) return; - FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); - ctx.update.newLLocked = true; - FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); + CSwingEngine::FillSwingLow(ctx.symbol, ctx.htf, ctx.update.newL0.shift, ctx.update.newL0); + CSwingEngine::FillSwingHigh(ctx.symbol, ctx.htf, ctx.update.newH0.shift, ctx.update.newH0); CSwingEngine::RollBearContinue(ctx.swings, ctx.update.newH0, ctx.update.newL0); FinishRoll(ctx); - Dbg("Bear Continue — swing low confirmed, rolled"); + Dbg("Bear Continue — roll"); } - //--- §1.4b Bull CHoCH — P1 track newL0, P2 swing low confirm → CHOCH_RESOLVE static void ProcessBullChochBuild(HtfContext &ctx, const int sh) { - TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, ctx.update.newLLocked); + if(!ctx.update.newLLocked) + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + if(ctx.update.newLLocked) return; - if(!CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, - ctx.update.newL0, InpSwingRange)) - return; - - FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); - ctx.update.newLLocked = true; - ctx.update.phase = UPD_PHASE_CHOCH_RESOLVE; - Dbg("Bull CHoCH — newL0 swing confirmed → phase 3"); + if(TryConfirmBullChochNewL0(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange)) + LockBullChochNewL0(ctx); } static void ProcessBearChochBuild(HtfContext &ctx, const int sh) { - TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, ctx.update.newH0Locked); + if(!ctx.update.newH0Locked) + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + if(ctx.update.newH0Locked) return; - if(!CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, - ctx.update.newH0, InpSwingRange)) - return; - - FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); - ctx.update.newH0Locked = true; - ctx.update.phase = UPD_PHASE_CHOCH_RESOLVE; - Dbg("Bear CHoCH — newH0 swing confirmed → phase 3"); + if(TryConfirmBearChochNewH0(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange)) + LockBearChochNewH0(ctx); + } + + // P3 case2: newL0 khóa → hồi (newH0) → phá xuống (newL02) → roll bear + static void ProcessBullChochCase2(HtfContext &ctx, const int sh) + { + if(!ctx.update.newLLocked || !ctx.update.newL0.Valid()) + return; + + if(!CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.update.newL0.price)) + { + if(!ctx.update.case2Active) + return; + } + else if(!ctx.update.case2Active) + { + ctx.update.case2Active = true; + ctx.update.newL02.Clear(); + ctx.update.case1TrackingH0 = false; + if(ctx.update.newH0.Valid() && + CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newH0, InpSwingRange)) + ctx.update.newH0Case2Locked = true; + else + ctx.update.newH0Case2Locked = false; + Dbg("Bull CHoCH P3 case2 — bắt đầu (newL0 đã khóa, phá xuống)"); + } + + if(!ctx.update.case2Active) + return; + + if(!ctx.update.newH0Case2Locked) + { + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + if(CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newH0, InpSwingRange)) + { + CSwingEngine::FillSwingHigh(ctx.symbol, ctx.htf, ctx.update.newH0.shift, ctx.update.newH0); + ctx.update.newH0Case2Locked = true; + ctx.update.newL02.Clear(); + Dbg(StringFormat("Bull CHoCH case2 — newH0 confirm @ %.5f", ctx.update.newH0.price)); + } + return; + } + + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL02, false); + + if(!CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL02, InpSwingRange)) + return; + + CSwingEngine::FillSwingLow(ctx.symbol, ctx.htf, ctx.update.newL02.shift, ctx.update.newL02); + CSwingEngine::RollBullChochCase2(ctx.swings, + ctx.update.newH0, ctx.update.newL0, ctx.update.newL02); + FinishRoll(ctx); + Dbg("Bull CHoCH case2 — roll bear (H0→H1, newH0→H0, newL0→L1, newL02→L0)"); } - //--- §1.4b Bull CHoCH P3 — case1 về bull / case2 confirm bear static void ProcessBullChochResolve(HtfContext &ctx, const int sh) { if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.swings.h0.price)) { + ctx.update.case2Active = false; + ctx.update.newH0Case2Locked = false; + if(!ctx.update.case1TrackingH0) { ctx.update.case1TrackingH0 = true; ctx.update.newH0.Clear(); ctx.update.newH0Locked = false; - Dbg("Bull CHoCH case1 — break H0, track newH0"); + Dbg("Bull CHoCH case1 — phá H0, track newH0"); } - if(CKeyLevels::IsBullCandle(ctx.symbol, ctx.htf, sh)) - TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, ctx.update.newH0Locked); + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, ctx.update.newH0Locked); - if(ctx.update.case1TrackingH0 && !ctx.update.newH0Locked && + if(!ctx.update.newH0Locked && CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange)) { - FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); - ctx.update.newH0Locked = true; + CSwingEngine::FillSwingHigh(ctx.symbol, ctx.htf, ctx.update.newH0.shift, ctx.update.newH0); CSwingEngine::RollBullChochCase1(ctx.swings, ctx.update.newH0, ctx.update.newL0); FinishRoll(ctx); - Dbg("Bull CHoCH case1 — newH0 confirmed, rolled"); + Dbg("Bull CHoCH case1 — roll bull"); } return; } - if(ctx.update.newL0.Valid() && - CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.update.newL0.price)) - { - TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); - TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL02, false); + ProcessBullChochCase2(ctx, sh); + } - const bool hOk = CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, - ctx.update.newH0, InpSwingRange); - const bool lOk = CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, - ctx.update.newL02, InpSwingRange); - if(hOk && lOk) - { - FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); - FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL02, InpSwingRange); - CSwingEngine::RollBullChochCase2(ctx.swings, - ctx.update.newH0, ctx.update.newL0, ctx.update.newL02); - FinishRoll(ctx); - Dbg("Bull CHoCH case2 — bear confirmed, rolled"); - } + static void ProcessBearChochCase2(HtfContext &ctx, const int sh) + { + if(!ctx.update.newH0Locked || !ctx.update.newH0.Valid()) + return; + + if(!CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.update.newH0.price)) + { + if(!ctx.update.case2Active) + return; } + else if(!ctx.update.case2Active) + { + ctx.update.case2Active = true; + ctx.update.newL02.Clear(); + ctx.update.case1TrackingH0 = false; + if(ctx.update.newL0.Valid() && + CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL0, InpSwingRange)) + ctx.update.newH0Case2Locked = true; + else + ctx.update.newH0Case2Locked = false; + Dbg("Bear CHoCH P3 case2 — bắt đầu"); + } + + if(!ctx.update.case2Active) + return; + + if(!ctx.update.newH0Case2Locked) + { + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + if(CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL0, InpSwingRange)) + { + CSwingEngine::FillSwingLow(ctx.symbol, ctx.htf, ctx.update.newL0.shift, ctx.update.newL0); + ctx.update.newH0Case2Locked = true; + ctx.update.newL02.Clear(); + Dbg(StringFormat("Bear CHoCH case2 — newL0 retrace confirm @ %.5f", ctx.update.newL0.price)); + } + return; + } + + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newL02, false); + + if(!CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newL02, InpSwingRange)) + return; + + CSwingEngine::FillSwingHigh(ctx.symbol, ctx.htf, ctx.update.newL02.shift, ctx.update.newL02); + CSwingEngine::RollBearChochCase2(ctx.swings, + ctx.update.newH0, ctx.update.newL0, ctx.update.newL02); + FinishRoll(ctx); + Dbg("Bear CHoCH case2 — roll bull"); } static void ProcessBearChochResolve(HtfContext &ctx, const int sh) { if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.swings.l0.price)) { + ctx.update.case2Active = false; + ctx.update.newH0Case2Locked = false; + if(!ctx.update.case1TrackingH0) { ctx.update.case1TrackingH0 = true; ctx.update.newL0.Clear(); ctx.update.newLLocked = false; - Dbg("Bear CHoCH case1 — break L0, track newL0"); + Dbg("Bear CHoCH case1 — phá L0"); } - if(CKeyLevels::IsBearCandle(ctx.symbol, ctx.htf, sh)) - TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, ctx.update.newLLocked); + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, ctx.update.newLLocked); - if(ctx.update.case1TrackingH0 && !ctx.update.newLLocked && + if(!ctx.update.newLLocked && CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange)) { - FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); - ctx.update.newLLocked = true; + CSwingEngine::FillSwingLow(ctx.symbol, ctx.htf, ctx.update.newL0.shift, ctx.update.newL0); CSwingEngine::RollBearChochCase1(ctx.swings, ctx.update.newH0, ctx.update.newL0); FinishRoll(ctx); - Dbg("Bear CHoCH case1 — newL0 confirmed, rolled"); + Dbg("Bear CHoCH case1 — roll bear"); } return; } - if(ctx.update.newH0.Valid() && - CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.update.newH0.price)) - { - TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); - TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newL02, false); - - const bool lOk = CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, - ctx.update.newL0, InpSwingRange); - const bool hOk = CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, - ctx.update.newL02, InpSwingRange); - if(lOk && hOk) - { - FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); - FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newL02, InpSwingRange); - CSwingEngine::RollBearChochCase2(ctx.swings, - ctx.update.newH0, ctx.update.newL0, ctx.update.newL02); - FinishRoll(ctx); - Dbg("Bear CHoCH case2 — bull confirmed, rolled"); - } - } + ProcessBearChochCase2(ctx, sh); } static void OnHtfBarClose(HtfContext &ctx) diff --git a/Include/RsiMom/SignalDebug.mqh b/Include/RsiMom/SignalDebug.mqh index ee2b405..d8a76a1 100644 --- a/Include/RsiMom/SignalDebug.mqh +++ b/Include/RsiMom/SignalDebug.mqh @@ -61,6 +61,17 @@ SignalEvalResult Signal_EvaluateBuyAt(const int shift, const int rates_total, co const bool phaseFilterEnabled, const bool trendFilterEnabled, const bool atrFilterEnabled, + const bool adxFilterEnabled, + const bool adxOkAtBar, + const double adxVal, + const double plusDi, + const double minusDi, + const string adxFailWhy, + const bool swingFilterEnabled, + const bool swingOkAtBar, + const double swingBodyOld, + const double swingBodyNew, + const string swingFailWhy, const bool rsiObOsFilterEnabled, const double rsiOverbought, const double rsiOversold, @@ -149,6 +160,30 @@ SignalEvalResult Signal_EvaluateBuyAt(const int shift, const int rates_total, co if(atrFilterEnabled && !atrExpAtBar) SignalEval_SetFail(r.failTag, "ATR co"); + if(!adxFilterEnabled) + SignalEval_Append(r.detail, "ADX:OFF"); + else if(adxOkAtBar) + SignalEval_Append(r.detail, StringFormat("ADX:OK(%.1f +DI=%.1f -DI=%.1f)", adxVal, plusDi, minusDi)); + else + { + SignalEval_Append(r.detail, StringLen(adxFailWhy) > 0 + ? StringFormat("ADX:FAIL(%.1f %s)", adxVal, adxFailWhy) + : StringFormat("ADX:FAIL(%.1f)", adxVal)); + SignalEval_SetFail(r.failTag, "ADX yếu"); + } + + if(!swingFilterEnabled) + SignalEval_Append(r.detail, "Swing2:OFF"); + else if(swingOkAtBar) + SignalEval_Append(r.detail, StringFormat("Swing2:OK(đáy %.5f→%.5f)", swingBodyOld, swingBodyNew)); + else + { + SignalEval_Append(r.detail, StringLen(swingFailWhy) > 0 + ? "Swing2:FAIL " + swingFailWhy + : "Swing2:FAIL"); + SignalEval_SetFail(r.failTag, "2 đáy"); + } + bool rsiOk = true; if(!rsiObOsFilterEnabled) SignalEval_Append(r.detail, "RSI OB/OS:OFF"); @@ -178,7 +213,10 @@ SignalEvalResult Signal_EvaluateBuyAt(const int shift, const int rates_total, co } r.signalOk = cross && coreOk && trendUp - && (!atrFilterEnabled || atrExpAtBar) && rsiOk && envBar; + && (!atrFilterEnabled || atrExpAtBar) + && (!adxFilterEnabled || adxOkAtBar) + && (!swingFilterEnabled || swingOkAtBar) + && rsiOk && envBar; r.tradeOk = r.signalOk; if(r.signalOk && r.tradeOk) @@ -200,6 +238,17 @@ SignalEvalResult Signal_EvaluateSellAt(const int shift, const int rates_total, c const bool phaseFilterEnabled, const bool trendFilterEnabled, const bool atrFilterEnabled, + const bool adxFilterEnabled, + const bool adxOkAtBar, + const double adxVal, + const double plusDi, + const double minusDi, + const string adxFailWhy, + const bool swingFilterEnabled, + const bool swingOkAtBar, + const double swingBodyOld, + const double swingBodyNew, + const string swingFailWhy, const bool rsiObOsFilterEnabled, const double rsiOverbought, const double rsiOversold, @@ -287,6 +336,30 @@ SignalEvalResult Signal_EvaluateSellAt(const int shift, const int rates_total, c if(atrFilterEnabled && !atrExpAtBar) SignalEval_SetFail(r.failTag, "ATR co"); + if(!adxFilterEnabled) + SignalEval_Append(r.detail, "ADX:OFF"); + else if(adxOkAtBar) + SignalEval_Append(r.detail, StringFormat("ADX:OK(%.1f +DI=%.1f -DI=%.1f)", adxVal, plusDi, minusDi)); + else + { + SignalEval_Append(r.detail, StringLen(adxFailWhy) > 0 + ? StringFormat("ADX:FAIL(%.1f %s)", adxVal, adxFailWhy) + : StringFormat("ADX:FAIL(%.1f)", adxVal)); + SignalEval_SetFail(r.failTag, "ADX yếu"); + } + + if(!swingFilterEnabled) + SignalEval_Append(r.detail, "Swing2:OFF"); + else if(swingOkAtBar) + SignalEval_Append(r.detail, StringFormat("Swing2:OK(đỉnh %.5f→%.5f)", swingBodyOld, swingBodyNew)); + else + { + SignalEval_Append(r.detail, StringLen(swingFailWhy) > 0 + ? "Swing2:FAIL " + swingFailWhy + : "Swing2:FAIL"); + SignalEval_SetFail(r.failTag, "2 đỉnh"); + } + bool rsiOk = true; if(!rsiObOsFilterEnabled) SignalEval_Append(r.detail, "RSI OB/OS:OFF"); @@ -315,7 +388,11 @@ SignalEvalResult Signal_EvaluateSellAt(const int shift, const int rates_total, c SignalEval_SetFail(r.failTag, "Phiên"); } - r.signalOk = cross && coreOk && trendDown && (!atrFilterEnabled || atrExpAtBar) && rsiOk && envBar; + r.signalOk = cross && coreOk && trendDown + && (!atrFilterEnabled || atrExpAtBar) + && (!adxFilterEnabled || adxOkAtBar) + && (!swingFilterEnabled || swingOkAtBar) + && rsiOk && envBar; r.tradeOk = r.signalOk; if(r.signalOk && r.tradeOk) diff --git a/Profiles/Charts/Default/order.wnd b/Profiles/Charts/Default/order.wnd index c5cfb52..b64f092 100644 Binary files a/Profiles/Charts/Default/order.wnd and b/Profiles/Charts/Default/order.wnd differ diff --git a/experts.dat b/experts.dat index fb89a62..e9abf20 100644 Binary files a/experts.dat and b/experts.dat differ