Update all
This commit is contained in:
+263
-24
@@ -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;
|
||||
|
||||
@@ -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
@@ -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.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<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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.*
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
Binary file not shown.
Reference in New Issue
Block a user