diff --git a/Experts/SimpleFVG/Config.mqh b/Experts/SimpleFVG/Config.mqh index 0284fea..70ba0d7 100644 --- a/Experts/SimpleFVG/Config.mqh +++ b/Experts/SimpleFVG/Config.mqh @@ -102,4 +102,16 @@ bool IsNewBar() return true; } +//+------------------------------------------------------------------+ +//| Low TF dùng để xác nhận khi giá chạm FVG: H1->M5, H4->M15, M15->M2 | +//| Trả về low TF hoặc chính highTF nếu không có mapping (không dùng confirm) | +//+------------------------------------------------------------------+ +ENUM_TIMEFRAMES GetConfirmationTimeframe(ENUM_TIMEFRAMES highTF) +{ + if(highTF == PERIOD_H1) return PERIOD_M5; + if(highTF == PERIOD_H4) return PERIOD_M15; + if(highTF == PERIOD_M15) return PERIOD_M2; + return highTF; +} + #endif diff --git a/Experts/SimpleFVG/Drawing.mqh b/Experts/SimpleFVG/Drawing.mqh index a139cd9..0b22491 100644 --- a/Experts/SimpleFVG/Drawing.mqh +++ b/Experts/SimpleFVG/Drawing.mqh @@ -80,6 +80,7 @@ void DrawTradeStatsPanel() { int totalTrades, tpTrades, slTrades; GetTradeStats(totalTrades, tpTrades, slTrades); + double balance = AccountInfoDouble(ACCOUNT_BALANCE); string name = EA_PREFIX + "PNL_STATS"; if(ObjectFind(0, name) < 0) @@ -92,8 +93,8 @@ void DrawTradeStatsPanel() ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite); ObjectSetString (0, name, OBJPROP_FONT, "Consolas"); ObjectSetString (0, name, OBJPROP_TEXT, - StringFormat("Trades: %d | TP: %d | SL: %d", - totalTrades, tpTrades, slTrades)); + StringFormat("Balance: %.2f | Trades: %d | TP: %d | SL: %d", + balance, totalTrades, tpTrades, slTrades)); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); } diff --git a/Experts/SimpleFVG/FVG.mqh b/Experts/SimpleFVG/FVG.mqh index 4142090..13e31f3 100644 --- a/Experts/SimpleFVG/FVG.mqh +++ b/Experts/SimpleFVG/FVG.mqh @@ -222,6 +222,86 @@ void ScanForNewFVGs() } } +//+------------------------------------------------------------------+ +//| Quét TF bất kỳ, tìm FVG cùng hướng gần nhất (shift 1..maxLookback) | +//| Trả về cạnh trên/dưới vùng và bar A low/high (để đặt SL). | +//+------------------------------------------------------------------+ +bool GetLatestLowTFFVG(string symbol, + ENUM_TIMEFRAMES tf, + ENUM_FVG_TYPE type, + int maxLookbackBars, + 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; + + 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; + outUpper = candleC_Low; + outLower = candleA_High; + outBarALow = candleA_Low; + outBarAHigh = candleA_High; + return true; + } + else // FVG_BEARISH + { + if(candleA_Low <= candleC_High || candleB_Close >= candleB_Open) + continue; + double gap = candleA_Low - candleC_High; + double gapRatio = gap / bodyB; + if(gapRatio < minGapVsBody) + 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 | //+------------------------------------------------------------------+ diff --git a/Experts/SimpleFVG/Trade.mqh b/Experts/SimpleFVG/Trade.mqh index 502f590..ed05cf9 100644 --- a/Experts/SimpleFVG/Trade.mqh +++ b/Experts/SimpleFVG/Trade.mqh @@ -241,6 +241,55 @@ double CalculateRiskLotSize(double entryPrice, double slPrice) return rounded2; } +// Đặt lệnh limit theo entry/SL từ low TF FVG (sau khi có tín hiệu xác nhận) +bool PlaceLimitFromLowTF(string symbol, ENUM_FVG_TYPE type, double entryPrice, double slPrice) +{ + double tpPrice; + ENUM_ORDER_TYPE orderType; + + if(type == FVG_BULLISH) + { + double risk = entryPrice - slPrice; + if(risk <= 0.0) return false; + tpPrice = entryPrice + risk * InpRRRatio; + orderType = ORDER_TYPE_BUY_LIMIT; + } + else + { + double risk = slPrice - entryPrice; + if(risk <= 0.0) return false; + tpPrice = entryPrice - risk * InpRRRatio; + orderType = ORDER_TYPE_SELL_LIMIT; + } + + if(HasLimitOrderAtPrice(orderType, entryPrice)) + return false; + + double volume = CalculateRiskLotSize(entryPrice, slPrice); + if(volume <= 0.0) + return false; + + MqlTradeRequest req; + MqlTradeResult res; + ZeroMemory(req); + ZeroMemory(res); + req.action = TRADE_ACTION_PENDING; + req.symbol = symbol; + req.magic = InpEAMagic; + req.type = orderType; + req.volume = volume; + req.price = entryPrice; + req.sl = slPrice; + req.tp = tpPrice; + req.type_filling = ORDER_FILLING_RETURN; + req.deviation = 10; + req.comment = "SimpleFVG_LTF"; + + if(!OrderSend(req, res)) + return false; + return (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED); +} + bool PlaceLimitForZone(const FVGZone &zone) { string symbol = GetTradeSymbol(); @@ -333,12 +382,15 @@ void ManageFVGTrades() if(currentLimits >= InpMaxLimitOrders) return; - // Đặt lệnh limit khi giá vừa chạm cạnh ngoài FVG (trước khi fill đủ 35%) + // Khi giá chạm cạnh FVG (high TF) -> tìm tín hiệu xác nhận trên low TF (FVG cùng hướng) ENUM_TREND_DIRECTION trend = g_CurrentTrend; string symbol = GetTradeSymbol(); double lastHigh = iHigh(symbol, InpTimeframe, 1); double lastLow = iLow (symbol, InpTimeframe, 1); + ENUM_TIMEFRAMES lowTF = GetConfirmationTimeframe(InpTimeframe); + const int LOW_TF_FVG_LOOKBACK = 15; + for(int i = g_FVGCount - 1; i >= 0 && currentLimits < InpMaxLimitOrders; i--) { FVGZone zone = g_FVGZones[i]; @@ -351,24 +403,42 @@ void ManageFVGTrades() continue; bool touchedEdge = false; - if(zone.type == FVG_BULLISH) { - // giá chạm cạnh trên (upperEdge) của bullish FVG if(lastLow <= zone.upperEdge && lastHigh >= zone.upperEdge) touchedEdge = true; } - else // FVG_BEARISH + else { - // giá chạm cạnh dưới (lowerEdge) của bearish FVG if(lastHigh >= zone.lowerEdge && lastLow <= zone.lowerEdge) touchedEdge = true; } - if(!touchedEdge) continue; - if(PlaceLimitForZone(zone)) + // Chỉ đặt lệnh khi có low TF để xác nhận (H1->M5, H4->M15, M15->M2) + if(lowTF == InpTimeframe) + continue; + + double ltfUpper, ltfLower, ltfBarALow, ltfBarAHigh; + if(!GetLatestLowTFFVG(symbol, lowTF, zone.type, LOW_TF_FVG_LOOKBACK, + ltfUpper, ltfLower, ltfBarALow, ltfBarAHigh)) + continue; + + // Entry theo low TF FVG; SL vẫn dùng bar A của high TF (đủ xa) + double entryPrice, slPrice; + if(zone.type == FVG_BULLISH) + { + entryPrice = ltfUpper; // Buy limit tại cạnh trên low TF FVG + slPrice = zone.slReferencePrice; // SL dưới bar A của high TF FVG + } + else + { + entryPrice = ltfLower; // Sell limit tại cạnh dưới low TF FVG + slPrice = zone.slReferencePrice; // SL trên bar A của high TF FVG + } + + if(PlaceLimitFromLowTF(symbol, zone.type, entryPrice, slPrice)) currentLimits++; } }