Update all

This commit is contained in:
Bell
2026-05-25 02:01:26 +07:00
parent b9c03196f6
commit f966c5ba2c
11 changed files with 1028 additions and 197 deletions
+263 -24
View File
@@ -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 <Trade\Trade.mqh>
@@ -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;
+1 -1
View File
@@ -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 <HyperICT/Config.mqh>
Binary file not shown.
+365 -54
View File
@@ -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 <Trade/Trade.mqh>
#include <RsiMom/TradeJournal.mqh>
#include <RsiMom/PhaseEntry.mqh>
#include <RsiMom/AdxFilter.mqh>
#include <RsiMom/SwingStructure.mqh>
#include <RsiMom/SignalDebug.mqh>
//--- 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.0031.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 2235+)
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 (510 = chặt hơn)
input int InpAdxRiseBars = 0; // ADX tăng vs N nến trước (0=tắt, 12 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: EMA9WMA45 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 tc test/EA
input bool InpExportTradeJournal = false; // bật lại khi cần phân tích CSV (chậm hơn một ct)
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<WMA45 | EMA200 UP";
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đá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;
}
+20 -1
View File
@@ -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 &lt; 20 thường sideway; 2235 vùng trend ổn |
| `InpAdxRequireDiDirection` | true | BUY: +DI &gt; DI; SELL: DI &gt; +DI |
| `InpAdxMinDiSpread` | 0 | Chênh +DI(DI) tối thiểu (thử 510) |
| `InpAdxRiseBars` | 0 | ADX tăng vs N nến (12 = 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.*
+2 -2
View File
@@ -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.
+7 -3
View File
@@ -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;
}
};
+291 -110
View File
@@ -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 P12: 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)
+79 -2
View File
@@ -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)
Binary file not shown.
BIN
View File
Binary file not shown.