Add RSI EA
This commit is contained in:
@@ -14,21 +14,30 @@ input int InpEMAFastPeriod = 34;
|
||||
input int InpEMASlowPeriod = 89;
|
||||
|
||||
//--- Step 2: FVG Detection ---
|
||||
input int InpFVGLookbackBars = 100;
|
||||
input int InpFVGMaxAgeBars = 50;
|
||||
input double InpFVGMinBodyPct = 50.0;
|
||||
input double InpFVGMinSizePoints = 0;
|
||||
input double InpFVGTouchedPercent = 33.0;
|
||||
input double InpFVGMinGapVsImpulsePct = 30.0;
|
||||
input int InpFVGLookbackBars = 120;
|
||||
input int InpFVGMaxAgeBars = 36;
|
||||
input double InpFVGMinBodyPct = 55.0;
|
||||
input double InpFVGMinSizePoints = 120;
|
||||
input double InpFVGTouchedPercent = 35.0;
|
||||
input double InpFVGMinGapVsImpulsePct = 35.0;
|
||||
input double InpFVGMaxOuterBarRatio = 2.0;
|
||||
|
||||
//--- Step 3: Trading ---
|
||||
input bool InpTradeEnabled = true;
|
||||
input double InpRiskPercentPerR = 1.0;
|
||||
input double InpRiskPercentPerR = 1;
|
||||
input double InpRRRatio = 2.2;
|
||||
input int InpMaxLimitOrders = 3;
|
||||
input int InpLimitMaxAgeBars = 24;
|
||||
input int InpMaxLimitOrders = 1;
|
||||
input int InpLimitMaxAgeBars = 12;
|
||||
input long InpEAMagic = 123456;
|
||||
input int InpMaxSpreadPoints = 350; // XAUUSD-friendly default
|
||||
input bool InpUseSessionFilter = true;
|
||||
input int InpSessionStartHour = 7; // server time hour [0..23]
|
||||
input int InpSessionEndHour = 23; // server time hour [0..23], supports overnight window
|
||||
input bool InpUseATRFilter = true;
|
||||
input int InpATRPeriod = 14;
|
||||
input double InpMinATRPoints = 1200; // skip low-volatility regime
|
||||
input double InpMaxATRPoints = 7000; // skip extreme-volatility regime
|
||||
input double InpLowTFEntryRangeBufferPoints = 60; // HTF-LTF mapping tolerance
|
||||
|
||||
//--- Step 4: Drawing ---
|
||||
input color InpColorBullFVG = C'30,80,140';
|
||||
|
||||
@@ -228,6 +228,8 @@ void DrawFVGZones()
|
||||
stateStr = " [TOUCHED]";
|
||||
else if(IsZoneMitigated(g_FVGZones[i]))
|
||||
stateStr = " [MITIGATED]";
|
||||
if(g_FVGZones[i].tradeLocked)
|
||||
stateStr += " [LOCKED]";
|
||||
|
||||
color labelColor;
|
||||
if(IsZoneMitigated(g_FVGZones[i]))
|
||||
|
||||
@@ -29,6 +29,9 @@ struct FVGZone
|
||||
double slReferencePrice;
|
||||
datetime createdTime;
|
||||
int ageInBars;
|
||||
bool tradeLocked; // true after one order attempt is placed from this zone
|
||||
ulong linkedOrderTicket;
|
||||
datetime tradeLockedTime;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -302,6 +305,103 @@ bool GetLatestLowTFFVG(string symbol,
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Low-TF FVG nearest and aligned with HTF zone price range |
|
||||
//+------------------------------------------------------------------+
|
||||
bool GetLatestLowTFFVGInRange(string symbol,
|
||||
ENUM_TIMEFRAMES tf,
|
||||
ENUM_FVG_TYPE type,
|
||||
int maxLookbackBars,
|
||||
double rangeLower,
|
||||
double rangeUpper,
|
||||
double rangeBufferPoints,
|
||||
double &outUpper,
|
||||
double &outLower,
|
||||
double &outBarALow,
|
||||
double &outBarAHigh)
|
||||
{
|
||||
int totalBars = Bars(symbol, tf);
|
||||
int maxShift = MathMin(maxLookbackBars, totalBars - 3);
|
||||
if(maxShift < 1)
|
||||
return false;
|
||||
|
||||
double maxOuterRatio = InpFVGMaxOuterBarRatio;
|
||||
double minGapVsBody = InpFVGMinGapVsImpulsePct / 100.0;
|
||||
double bufferPrice = rangeBufferPoints * _Point;
|
||||
double minAllowed = rangeLower - bufferPrice;
|
||||
double maxAllowed = rangeUpper + bufferPrice;
|
||||
|
||||
for(int shift = 1; shift <= maxShift; shift++)
|
||||
{
|
||||
int shiftA = shift + 2;
|
||||
int shiftB = shift + 1;
|
||||
int shiftC = shift;
|
||||
|
||||
double candleA_High = iHigh(symbol, tf, shiftA);
|
||||
double candleA_Low = iLow (symbol, tf, shiftA);
|
||||
double candleB_High = iHigh(symbol, tf, shiftB);
|
||||
double candleB_Low = iLow (symbol, tf, shiftB);
|
||||
double candleB_Open = iOpen (symbol, tf, shiftB);
|
||||
double candleB_Close = iClose(symbol, tf, shiftB);
|
||||
double candleC_High = iHigh(symbol, tf, shiftC);
|
||||
double candleC_Low = iLow (symbol, tf, shiftC);
|
||||
|
||||
double rangeA = candleA_High - candleA_Low;
|
||||
double rangeB = candleB_High - candleB_Low;
|
||||
double rangeC = candleC_High - candleC_Low;
|
||||
double bodyB = MathAbs(candleB_Close - candleB_Open);
|
||||
|
||||
if(rangeB <= 0 || bodyB <= 0)
|
||||
continue;
|
||||
if(rangeA > maxOuterRatio * rangeB || rangeC > maxOuterRatio * rangeB)
|
||||
continue;
|
||||
if(!IsImpulseCandleStrong(symbol, tf, shiftB))
|
||||
continue;
|
||||
|
||||
if(type == FVG_BULLISH)
|
||||
{
|
||||
if(candleA_High >= candleC_Low || candleB_Close <= candleB_Open)
|
||||
continue;
|
||||
|
||||
double gap = candleC_Low - candleA_High;
|
||||
double gapRatio = gap / bodyB;
|
||||
if(gapRatio < minGapVsBody)
|
||||
continue;
|
||||
|
||||
double entry = candleC_Low;
|
||||
if(entry < minAllowed || entry > maxAllowed)
|
||||
continue;
|
||||
|
||||
outUpper = candleC_Low;
|
||||
outLower = candleA_High;
|
||||
outBarALow = candleA_Low;
|
||||
outBarAHigh = candleA_High;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(candleA_Low <= candleC_High || candleB_Close >= candleB_Open)
|
||||
continue;
|
||||
|
||||
double gap = candleA_Low - candleC_High;
|
||||
double gapRatio = gap / bodyB;
|
||||
if(gapRatio < minGapVsBody)
|
||||
continue;
|
||||
|
||||
double entry = candleC_High;
|
||||
if(entry < minAllowed || entry > maxAllowed)
|
||||
continue;
|
||||
|
||||
outUpper = candleA_Low;
|
||||
outLower = candleC_High;
|
||||
outBarALow = candleA_Low;
|
||||
outBarAHigh = candleA_High;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if price has mitigated (filled through) any active FVG |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
+196
-5
@@ -13,6 +13,135 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Internal helpers |
|
||||
//+------------------------------------------------------------------+
|
||||
string FVGTypeToString(ENUM_FVG_TYPE type)
|
||||
{
|
||||
return (type == FVG_BULLISH) ? "BULL" : "BEAR";
|
||||
}
|
||||
|
||||
void LogDecisionTrace(const string stage, const string message)
|
||||
{
|
||||
if(!InpDebugLog)
|
||||
return;
|
||||
PrintFormat("[TRACE][%s] %s", stage, message);
|
||||
}
|
||||
|
||||
bool IsSpreadAcceptable(string symbol)
|
||||
{
|
||||
if(InpMaxSpreadPoints <= 0)
|
||||
return true;
|
||||
|
||||
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
double spreadPoints = -1.0;
|
||||
if(ask > 0.0 && bid > 0.0)
|
||||
spreadPoints = (ask - bid) / _Point;
|
||||
else
|
||||
{
|
||||
// In Strategy Tester, BID/ASK can be unavailable on some bars.
|
||||
// Fallback to broker/tester spread setting (already in points).
|
||||
long spreadInt = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
||||
if(spreadInt > 0)
|
||||
spreadPoints = (double)spreadInt;
|
||||
}
|
||||
|
||||
if(spreadPoints < 0.0)
|
||||
return false;
|
||||
|
||||
return (spreadPoints <= InpMaxSpreadPoints);
|
||||
}
|
||||
|
||||
double GetCurrentSpreadPoints(string symbol)
|
||||
{
|
||||
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
if(ask > 0.0 && bid > 0.0)
|
||||
return (ask - bid) / _Point;
|
||||
|
||||
long spreadInt = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
||||
if(spreadInt > 0)
|
||||
return (double)spreadInt;
|
||||
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
bool IsTradingSessionOpen()
|
||||
{
|
||||
if(!InpUseSessionFilter)
|
||||
return true;
|
||||
|
||||
datetime now = TimeCurrent();
|
||||
if(now == 0)
|
||||
return false;
|
||||
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(now, dt);
|
||||
int hour = dt.hour;
|
||||
|
||||
int startHour = MathMax(0, MathMin(23, InpSessionStartHour));
|
||||
int endHour = MathMax(0, MathMin(23, InpSessionEndHour));
|
||||
|
||||
if(startHour == endHour)
|
||||
return true;
|
||||
if(startHour < endHour)
|
||||
return (hour >= startHour && hour < endHour);
|
||||
return (hour >= startHour || hour < endHour); // overnight session
|
||||
}
|
||||
|
||||
int GetCurrentServerHour()
|
||||
{
|
||||
datetime now = TimeCurrent();
|
||||
if(now == 0)
|
||||
return -1;
|
||||
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(now, dt);
|
||||
return dt.hour;
|
||||
}
|
||||
|
||||
void GetNormalizedSessionHours(int &outStartHour, int &outEndHour)
|
||||
{
|
||||
outStartHour = MathMax(0, MathMin(23, InpSessionStartHour));
|
||||
outEndHour = MathMax(0, MathMin(23, InpSessionEndHour));
|
||||
}
|
||||
|
||||
double GetATRPoints(string symbol, ENUM_TIMEFRAMES tf, int period)
|
||||
{
|
||||
if(period <= 1)
|
||||
return -1.0;
|
||||
|
||||
int handle = iATR(symbol, tf, period);
|
||||
if(handle == INVALID_HANDLE)
|
||||
return -1.0;
|
||||
|
||||
double buffer[1];
|
||||
int copied = CopyBuffer(handle, 0, 1, 1, buffer);
|
||||
IndicatorRelease(handle);
|
||||
if(copied < 1 || buffer[0] <= 0.0)
|
||||
return -1.0;
|
||||
|
||||
return buffer[0] / _Point;
|
||||
}
|
||||
|
||||
bool IsATRRegimeValid(string symbol)
|
||||
{
|
||||
if(!InpUseATRFilter)
|
||||
return true;
|
||||
|
||||
double atrPoints = GetATRPoints(symbol, InpTimeframe, InpATRPeriod);
|
||||
if(atrPoints <= 0.0)
|
||||
return false;
|
||||
if(InpMinATRPoints > 0.0 && atrPoints < InpMinATRPoints)
|
||||
return false;
|
||||
if(InpMaxATRPoints > 0.0 && atrPoints > InpMaxATRPoints)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
double GetCurrentATRPoints(string symbol)
|
||||
{
|
||||
return GetATRPoints(symbol, InpTimeframe, InpATRPeriod);
|
||||
}
|
||||
|
||||
int CountOurPositions()
|
||||
{
|
||||
int count = 0;
|
||||
@@ -382,9 +511,35 @@ void ManageFVGTrades()
|
||||
if(currentLimits >= InpMaxLimitOrders)
|
||||
return;
|
||||
|
||||
string symbol = GetTradeSymbol();
|
||||
if(!IsSpreadAcceptable(symbol))
|
||||
{
|
||||
double spreadPts = GetCurrentSpreadPoints(symbol);
|
||||
LogDecisionTrace("FILTER", StringFormat("Skip entries: spread %.1f > max %d points",
|
||||
spreadPts, InpMaxSpreadPoints));
|
||||
return;
|
||||
}
|
||||
if(!IsTradingSessionOpen())
|
||||
{
|
||||
int startHour, endHour;
|
||||
GetNormalizedSessionHours(startHour, endHour);
|
||||
int currentHour = GetCurrentServerHour();
|
||||
LogDecisionTrace("FILTER", StringFormat("Skip entries: session closed (hour=%d, window=%02d-%02d, useSession=%s)",
|
||||
currentHour, startHour, endHour,
|
||||
InpUseSessionFilter ? "true" : "false"));
|
||||
return;
|
||||
}
|
||||
if(!IsATRRegimeValid(symbol))
|
||||
{
|
||||
double atrPoints = GetCurrentATRPoints(symbol);
|
||||
LogDecisionTrace("FILTER", StringFormat("Skip entries: ATR %.1f outside [%.1f..%.1f] points (period=%d, useATR=%s)",
|
||||
atrPoints, InpMinATRPoints, InpMaxATRPoints,
|
||||
InpATRPeriod, InpUseATRFilter ? "true" : "false"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Chỉ tìm tín hiệu low TF FVG khi high TF FVG đã TOUCHED (giá lấp đủ %), không trigger khi mới chạm cạnh
|
||||
ENUM_TREND_DIRECTION trend = g_CurrentTrend;
|
||||
string symbol = GetTradeSymbol();
|
||||
ENUM_TIMEFRAMES lowTF = GetConfirmationTimeframe(InpTimeframe);
|
||||
const int LOW_TF_FVG_LOOKBACK = 15;
|
||||
|
||||
@@ -392,25 +547,52 @@ void ManageFVGTrades()
|
||||
{
|
||||
FVGZone zone = g_FVGZones[i];
|
||||
if(!IsZoneActive(zone) || IsZoneMitigated(zone))
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d skip: inactive/mitigated", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(zone.tradeLocked)
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d %s skip: locked", i, FVGTypeToString(zone.type)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(zone.type == FVG_BULLISH && trend != TREND_BULLISH)
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d BULL blocked by EMA trend", i));
|
||||
continue;
|
||||
}
|
||||
if(zone.type == FVG_BEARISH && trend != TREND_BEARISH)
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d BEAR blocked by EMA trend", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Điều kiện vào lệnh: FVG high TF phải đã TOUCHED (giá lấp >= InpFVGTouchedPercent), không chỉ chạm cạnh
|
||||
if(!IsZoneTouched(zone))
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d %s not touched", i, FVGTypeToString(zone.type)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Chỉ đặt lệnh khi có low TF để xác nhận (H1->M5, H4->M15, M15->M2)
|
||||
if(lowTF == InpTimeframe)
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d skip: lowTF mapping unavailable", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
double ltfUpper, ltfLower, ltfBarALow, ltfBarAHigh;
|
||||
if(!GetLatestLowTFFVG(symbol, lowTF, zone.type, LOW_TF_FVG_LOOKBACK,
|
||||
ltfUpper, ltfLower, ltfBarALow, ltfBarAHigh))
|
||||
if(!GetLatestLowTFFVGInRange(symbol, lowTF, zone.type, LOW_TF_FVG_LOOKBACK,
|
||||
zone.lowerEdge, zone.upperEdge,
|
||||
InpLowTFEntryRangeBufferPoints,
|
||||
ltfUpper, ltfLower, ltfBarALow, ltfBarAHigh))
|
||||
{
|
||||
LogDecisionTrace("ZONE", StringFormat("#%d %s no LTF FVG aligned to HTF zone [%.5f..%.5f]",
|
||||
i, FVGTypeToString(zone.type), zone.lowerEdge, zone.upperEdge));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Entry theo low TF FVG; SL = bar B của high TF FVG
|
||||
double entryPrice, slPrice;
|
||||
@@ -427,10 +609,19 @@ void ManageFVGTrades()
|
||||
|
||||
if(PlaceLimitFromLowTF(symbol, zone.type, entryPrice, slPrice))
|
||||
{
|
||||
// Mỗi FVG chỉ được dùng để trade 1 lần
|
||||
g_FVGZones[i].status = EXPIRED;
|
||||
// v2 state: lock zone after first successful placement; keep visible for diagnostics.
|
||||
g_FVGZones[i].tradeLocked = true;
|
||||
g_FVGZones[i].tradeLockedTime = TimeCurrent();
|
||||
g_FVGZones[i].linkedOrderTicket = 0;
|
||||
LogDecisionTrace("ORDER", StringFormat("#%d %s placed @%.5f SL=%.5f HTF[%.5f..%.5f] LTF[%.5f..%.5f]",
|
||||
i, FVGTypeToString(zone.type), entryPrice, slPrice,
|
||||
zone.lowerEdge, zone.upperEdge, ltfLower, ltfUpper));
|
||||
currentLimits++;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogDecisionTrace("ORDER", StringFormat("#%d %s failed @%.5f", i, FVGTypeToString(zone.type), entryPrice));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user