//+------------------------------------------------------------------+ //| Trade.mqh – Step 3: Trade management for FVG strategy | //+------------------------------------------------------------------+ #ifndef __SIMPLE_FVG_TRADE_MQH__ #define __SIMPLE_FVG_TRADE_MQH__ #include "Config.mqh" #include "Trend.mqh" #include "FVG.mqh" //--- Trading config nằm trong Config.mqh (Step 3) //+------------------------------------------------------------------+ //| Internal helpers | //+------------------------------------------------------------------+ int CountOurPositions() { int count = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if((long)PositionGetInteger(POSITION_MAGIC) == InpEAMagic && (string)PositionGetString(POSITION_SYMBOL) == GetTradeSymbol()) { count++; } } return count; } int CountOurLimitOrders() { int count = 0; for(int i = 0; i < OrdersTotal(); i++) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); long magic = (long)OrderGetInteger(ORDER_MAGIC); string symbol = (string)OrderGetString(ORDER_SYMBOL); if(magic == InpEAMagic && symbol == GetTradeSymbol() && (type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT)) { count++; } } return count; } void CancelAllOurLimitOrders() { for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); long magic = (long)OrderGetInteger(ORDER_MAGIC); string symbol = (string)OrderGetString(ORDER_SYMBOL); if(magic == InpEAMagic && symbol == GetTradeSymbol() && (type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT)) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_REMOVE; req.order = ticket; if(!OrderSend(req, res) && InpDebugLog) PrintFormat("[Trade] Failed to cancel order #%I64u, retcode=%d", ticket, res.retcode); } } } bool HasLimitOrderAtPrice(ENUM_ORDER_TYPE orderType, double entryPrice) { double eps = 2 * _Point; for(int i = 0; i < OrdersTotal(); i++) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; if((long)OrderGetInteger(ORDER_MAGIC) != InpEAMagic) continue; if((string)OrderGetString(ORDER_SYMBOL) != GetTradeSymbol()) continue; ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(type != orderType) continue; double price = OrderGetDouble(ORDER_PRICE_OPEN); if(MathAbs(price - entryPrice) <= eps) return true; } return false; } // Hủy các limit order quá "già" (tính theo số bar trên InpTimeframe) void CancelStaleLimitOrders() { if(InpLimitMaxAgeBars <= 0) return; datetime now = TimeCurrent(); if(now == 0) return; int tfSeconds = PeriodSeconds(InpTimeframe); if(tfSeconds <= 0) return; int maxAgeSeconds = InpLimitMaxAgeBars * tfSeconds; for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); long magic = (long)OrderGetInteger(ORDER_MAGIC); string symbol = (string)OrderGetString(ORDER_SYMBOL); if(magic != InpEAMagic) continue; if(symbol != GetTradeSymbol()) continue; if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT) continue; datetime setupTime = (datetime)OrderGetInteger(ORDER_TIME_SETUP); int ageSeconds = int(now - setupTime); if(ageSeconds < maxAgeSeconds) continue; MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_REMOVE; req.order = ticket; if(!OrderSend(req, res) && InpDebugLog) PrintFormat("[Trade] Failed to cancel stale order #%I64u, ageBars=%d, retcode=%d", ticket, ageSeconds / tfSeconds, res.retcode); } } void GetTradeStats(int &totalTrades, int &tpTrades, int &slTrades) { totalTrades = 0; tpTrades = 0; slTrades = 0; datetime now = TimeCurrent(); if(now == 0) return; if(!HistorySelect(0, now)) return; int deals = HistoryDealsTotal(); string symbol = GetTradeSymbol(); for(int i = 0; i < deals; i++) { ulong dealTicket = HistoryDealGetTicket(i); if(dealTicket == 0) continue; string dealSymbol = (string)HistoryDealGetString(dealTicket, DEAL_SYMBOL); long dealMagic = (long)HistoryDealGetInteger(dealTicket, DEAL_MAGIC); if(dealSymbol != symbol || dealMagic != InpEAMagic) continue; ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY); if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT) continue; totalTrades++; ENUM_DEAL_REASON reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON); if(reason == DEAL_REASON_TP) tpTrades++; else if(reason == DEAL_REASON_SL) slTrades++; } } // Tính khối lượng lot sao cho 1R = InpRiskPercentPerR % balance double CalculateRiskLotSize(double entryPrice, double slPrice) { string symbol = GetTradeSymbol(); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double volMin = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double volMax = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double volStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double priceDiff = MathAbs(entryPrice - slPrice); if(balance <= 0 || tickSize <= 0 || tickValue <= 0 || priceDiff <= 0) return 0.0; double riskMoney = balance * InpRiskPercentPerR / 100.0; if(riskMoney <= 0.0) return 0.0; double ticks = priceDiff / tickSize; double costPerLot = ticks * tickValue; // tiền lỗ nếu 1 lot hit SL if(costPerLot <= 0.0) return 0.0; double rawVolume = riskMoney / costPerLot; // Làm tròn đến 2 chữ số thập phân double rounded2 = MathFloor(rawVolume * 100.0 + 0.5) / 100.0; // Canh theo step volume if(volStep > 0.0) rounded2 = MathFloor(rounded2 / volStep) * volStep; // Giới hạn theo min/max if(rounded2 < volMin) rounded2 = volMin; if(rounded2 > volMax) rounded2 = volMax; return rounded2; } bool PlaceLimitForZone(const FVGZone &zone) { string symbol = GetTradeSymbol(); double entryPrice; double slPrice; double tpPrice; ENUM_ORDER_TYPE orderType; double zoneHeight = zone.upperEdge - zone.lowerEdge; if(zoneHeight <= 0.0) return false; double touchRatio = InpFVGTouchedPercent / 100.0; if(zone.type == FVG_BULLISH) { // Entry: 40% từ đỉnh vùng xuống entryPrice = zone.upperEdge - zoneHeight * touchRatio; slPrice = zone.slReferencePrice; double risk = entryPrice - slPrice; if(risk <= 0) return false; tpPrice = entryPrice + risk * InpRRRatio; orderType = ORDER_TYPE_BUY_LIMIT; } else // FVG_BEARISH { // Entry: 40% từ đáy vùng lên entryPrice = zone.lowerEdge + zoneHeight * touchRatio; slPrice = zone.slReferencePrice; double risk = slPrice - entryPrice; if(risk <= 0) return false; tpPrice = entryPrice - risk * InpRRRatio; orderType = ORDER_TYPE_SELL_LIMIT; } // Không tạo lệnh nếu đã có limit trùng giá/type 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"; if(!OrderSend(req, res)) return false; return (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED); } //+------------------------------------------------------------------+ //| Public: main trade manager | //+------------------------------------------------------------------+ void ManageFVGTrades() { if(!InpTradeEnabled) return; // Hủy các lệnh limit đã quá số bar cho phép CancelStaleLimitOrders(); // Nếu đã có vị thế, hủy toàn bộ limit còn lại if(CountOurPositions() > 0) { CancelAllOurLimitOrders(); return; } int currentLimits = CountOurLimitOrders(); if(currentLimits >= InpMaxLimitOrders) return; // Đặt lệnh limit khi giá vừa chạm cạnh ngoài FVG (trước khi fill đủ 40%) ENUM_TREND_DIRECTION trend = g_CurrentTrend; string symbol = GetTradeSymbol(); double lastHigh = iHigh(symbol, InpTimeframe, 1); double lastLow = iLow (symbol, InpTimeframe, 1); for(int i = g_FVGCount - 1; i >= 0 && currentLimits < InpMaxLimitOrders; i--) { FVGZone zone = g_FVGZones[i]; if(!IsZoneActive(zone) || IsZoneMitigated(zone)) continue; if(zone.type == FVG_BULLISH && trend != TREND_BULLISH) continue; if(zone.type == FVG_BEARISH && trend != TREND_BEARISH) 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 { // 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)) currentLimits++; } } #endif