//+------------------------------------------------------------------+ //| MA Cross Trend + Single-Direction Recovery EA (MQL5) | //| Cleaned: no chart objects, restart-safe state reconstruction | //+------------------------------------------------------------------+ #property strict #include #include // ---------------- INPUTS ---------------- input int FastMAPeriod = 20; // Fast MA input int SlowMAPeriod = 50; // Slow MA input int MAMethod = MODE_SMA; input int MAPrice = PRICE_CLOSE; input double BaseOpenPct = 0.85; // Trigger loss threshold (%) input double BaseClosePct = 0.70; // Target loss threshold (%) input double StepIncrease = 5.0; // Increase thresholds per recovery (%) input int Magic = 1; input double MinLot = 0.1; input double MaxLot = 10.0; input double LotStep = 0.1; input int Slippage = 30; input double TargetNotionalPct = 0.3; // 0.3% of notional input double TrailPercent = 30.0; // trail 30% off max profit // 0 = RETURN, 1 = FOK, 2 = IOC, 3 = AUTO input int FillingType = 1; CPositionInfo m_position; CTrade m_trade; // ---------------- INTERNAL ---------------- int RecoveryCountBuy = 0; int RecoveryCountSell = 0; int fastMAHandle = INVALID_HANDLE; int slowMAHandle = INVALID_HANDLE; bool InitialOrdersPlaced = false; double BasketMaxProfit = 0.0; //+------------------------------------------------------------------+ // Convert FillingType input into actual filling mode //+------------------------------------------------------------------+ ENUM_ORDER_TYPE_FILLING GetFillingMode(string sym) { switch(FillingType) { case 0: return ORDER_FILLING_RETURN; case 1: return ORDER_FILLING_FOK; case 2: return ORDER_FILLING_IOC; case 3: { long filling = 0; if(!SymbolInfoInteger(sym, SYMBOL_FILLING_MODE, filling)) return ORDER_FILLING_RETURN; if(filling & SYMBOL_FILLING_FOK) return ORDER_FILLING_FOK; if(filling & SYMBOL_FILLING_IOC) return ORDER_FILLING_IOC; return ORDER_FILLING_RETURN; } } return ORDER_FILLING_RETURN; } //+------------------------------------------------------------------+ // Check if market is open for this symbol //+------------------------------------------------------------------+ bool IsMarketOpen(string sym) { long tradeMode = 0; if(!SymbolInfoInteger(sym, SYMBOL_TRADE_MODE, tradeMode)) return false; if(tradeMode == SYMBOL_TRADE_MODE_DISABLED || tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY) return false; long sessionDeals = 0; if(!SymbolInfoInteger(sym, SYMBOL_SESSION_DEALS, sessionDeals)) return false; return (true); } //+------------------------------------------------------------------+ // MA CROSS TREND DETECTION //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTrendDirection() { double fastBuf[1]; double slowBuf[1]; if (CopyBuffer(fastMAHandle, 0, 0, 1, fastBuf) <= 0) return ORDER_TYPE_BUY; if (CopyBuffer(slowMAHandle, 0, 0, 1, slowBuf) <= 0) return ORDER_TYPE_BUY; double fastMA = fastBuf[0]; double slowMA = slowBuf[0]; if (fastMA > slowMA) return ORDER_TYPE_BUY; else return ORDER_TYPE_SELL; } //+------------------------------------------------------------------+ // Rebuild internal state from currently open trades (restart-safe) //+------------------------------------------------------------------+ void RebuildState(string sym) { InitialOrdersPlaced = false; RecoveryCountBuy = 0; RecoveryCountSell = 0; int total = CountPositions(sym); if (total >= 3) InitialOrdersPlaced = true; double buyVol, buyAvg, sellVol, sellAvg; GetSideStats(sym, POSITION_TYPE_BUY, buyVol, buyAvg); GetSideStats(sym, POSITION_TYPE_SELL, sellVol, sellAvg); // Count BUY recovery trades if (buyVol > 0.0 && sellVol == 0.0) { int buyCount = 0; for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong t = PositionGetTicket(i); if (!PositionSelectByTicket(t)) continue; if (PositionGetString(POSITION_SYMBOL) != sym) continue; if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) buyCount++; } RecoveryCountBuy = MathMax(0, buyCount - 3); } // Count SELL recovery trades if (sellVol > 0.0 && buyVol == 0.0) { int sellCount = 0; for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong t = PositionGetTicket(i); if (!PositionSelectByTicket(t)) continue; if (PositionGetString(POSITION_SYMBOL) != sym) continue; if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) sellCount++; } RecoveryCountSell = MathMax(0, sellCount - 3); } } //+------------------------------------------------------------------+ int OnInit() { fastMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, (ENUM_MA_METHOD)MAMethod, (ENUM_APPLIED_PRICE)MAPrice); slowMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, (ENUM_MA_METHOD)MAMethod, (ENUM_APPLIED_PRICE)MAPrice); if (fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE) { Print("Failed to create MA handles"); return(INIT_FAILED); } // Rebuild state from open trades (for restart / VPS crash) RebuildState(_Symbol); BasketMaxProfit = 0.0; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ void OnTick() { string sym = _Symbol; double bid = SymbolInfoDouble(sym, SYMBOL_BID); double ask = SymbolInfoDouble(sym, SYMBOL_ASK); if (bid <= 0 || ask <= 0) return; // --------------------------------------------------------------- // INITIAL 3 ORDERS BASED ON MA CROSS TREND (only once) // --------------------------------------------------------------- if (!InitialOrdersPlaced || CountPositions(_Symbol)<3) { if (!IsMarketOpen(sym)) { Print("Market closed, waiting to place initial orders..."); return; } ENUM_ORDER_TYPE trendDir = GetTrendDirection(); double price = (trendDir == ORDER_TYPE_BUY ? ask : bid); double lot = NormalizeLot(MinLot); Print("Attempting initial 3 orders in MA CROSS trend direction: ", (trendDir == ORDER_TYPE_BUY ? "BUY" : "SELL")); int successCount = CountPositions(_Symbol); for (int i = 0; i < 3; i++) { if (SendOrder(sym, trendDir, lot, price)) { successCount++; } else { Print("Initial order ", i+1, " failed."); } Sleep(200); } if (successCount > 0) { Print("Initial orders placed successfully: ", successCount); InitialOrdersPlaced = true; } else { Print("All initial orders failed. Will retry on next tick."); } return; } // --------------------------------------------------------------- // BASKET PROFIT + NOTIONAL-BASED TARGET + TRAILING // --------------------------------------------------------------- double totalProfit = 0.0; double totalNotional = 0.0; for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (!PositionSelectByTicket(ticket)) continue; if (PositionGetString(POSITION_SYMBOL) != sym) continue; double posProfit = PositionGetDouble(POSITION_PROFIT); double posVolume = PositionGetDouble(POSITION_VOLUME); double posPrice = PositionGetDouble(POSITION_PRICE_OPEN); double contractSize = SymbolInfoDouble(sym, SYMBOL_TRADE_CONTRACT_SIZE); if (contractSize <= 0.0) contractSize = 1.0; double notional = posVolume * contractSize * posPrice; totalProfit += posProfit; totalNotional += notional; } if (totalNotional <= 0.0) { BasketMaxProfit = 0.0; } else { if (totalProfit > BasketMaxProfit) BasketMaxProfit = totalProfit; double targetProfit = totalNotional * (TargetNotionalPct / 100.0); if (totalProfit >= targetProfit) { double trailDistance = BasketMaxProfit * (TrailPercent / 100.0); double trailLevel = BasketMaxProfit - trailDistance; if (totalProfit <= trailLevel) { Print("Trailing TP hit. Profit=", totalProfit, " Max=", BasketMaxProfit, " TrailLevel=", trailLevel, " Notional=", totalNotional, " Target=", targetProfit); CloseAllSymbol(sym); InitialOrdersPlaced = false; BasketMaxProfit = 0.0; return; } } } // --------------------------------------------------------------- // Determine direction from existing positions // --------------------------------------------------------------- double buyVol, buyAvg, sellVol, sellAvg; GetSideStats(sym, POSITION_TYPE_BUY, buyVol, buyAvg); GetSideStats(sym, POSITION_TYPE_SELL, sellVol, sellAvg); int posCount = CountPositions(sym); if (posCount < 3) return; if (buyVol > 0.0 && sellVol > 0.0) { Print("Both BUY and SELL positions detected. EA paused."); return; } if (buyVol > 0.0 && sellVol == 0.0) { HandleBuySide(sym, bid, ask, buyVol, buyAvg); } if (sellVol > 0.0 && buyVol == 0.0) { HandleSellSide(sym, bid, ask, sellVol, sellAvg); } } //+------------------------------------------------------------------+ // BUY SIDE LOGIC (no chart objects) //+------------------------------------------------------------------+ void HandleBuySide(string sym, double bid, double ask, double vol, double avg) { double openThr = (BaseOpenPct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountBuy); double closeThr = (BaseClosePct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountBuy); double lossPct = (avg - bid) / bid; if (lossPct >= openThr) { double lot = CalcRecoveryLotBuy(vol, avg, bid, closeThr); lot = NormalizeLot(lot); if (lot >= MinLot) { if (SendOrder(sym, ORDER_TYPE_BUY, lot, ask)) { RecoveryCountBuy++; } } } } //+------------------------------------------------------------------+ // SELL SIDE LOGIC (no chart objects) //+------------------------------------------------------------------+ void HandleSellSide(string sym, double bid, double ask, double vol, double avg) { double openThr = (BaseOpenPct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountSell); double closeThr = (BaseClosePct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountSell); double lossPct = (ask - avg) / ask; if (lossPct >= openThr) { double lot = CalcRecoveryLotSell(vol, avg, ask, closeThr); lot = NormalizeLot(lot); if (lot >= MinLot) { if (SendOrder(sym, ORDER_TYPE_SELL, lot, bid)) { RecoveryCountSell++; } } } } //+------------------------------------------------------------------+ // COUNT POSITIONS //+------------------------------------------------------------------+ int CountPositions(string sym) { int c = 0; for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong t = PositionGetTicket(i); if (!PositionSelectByTicket(t)) continue; if (PositionGetString(POSITION_SYMBOL) == sym) c++; } return c; } //+------------------------------------------------------------------+ // GET SIDE STATS //+------------------------------------------------------------------+ void GetSideStats(string sym, int type, double &vol, double &avg) { vol = 0.0; avg = 0.0; double sum = 0.0; for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong t = PositionGetTicket(i); if (!PositionSelectByTicket(t)) continue; if (PositionGetString(POSITION_SYMBOL) != sym) continue; if ((int)PositionGetInteger(POSITION_TYPE) != type) continue; double v = PositionGetDouble(POSITION_VOLUME); double p = PositionGetDouble(POSITION_PRICE_OPEN); vol += v; sum += v * p; } if (vol > 0.0) avg = sum / vol; } //+------------------------------------------------------------------+ // RECOVERY LOT CALCULATIONS //+------------------------------------------------------------------+ double CalcRecoveryLotBuy(double V_old, double P_avg, double current, double thr) { double target = current * (1.0 + thr); double num = V_old * (P_avg - target); double den = current * thr; if (den <= 0.0) return 0.0; return MathMax(0.0, num / den); } double CalcRecoveryLotSell(double V_old, double P_avg, double current, double thr) { double target = current * (1.0 - thr); double num = V_old * (target - P_avg); double den = current * thr; if (den <= 0.0) return 0.0; return MathMax(0.0, num / den); } //+------------------------------------------------------------------+ // LOT NORMALIZATION //+------------------------------------------------------------------+ double NormalizeLot(double lot) { if (lot <= 0.0) return 0.0; lot = MathMin(lot, MaxLot); lot = MathMax(lot, MinLot); lot = MathFloor(lot / LotStep) * LotStep; return NormalizeDouble(lot, 2); } //+------------------------------------------------------------------+ // SEND ORDER (uses FillingType parameter via GetFillingMode) //+------------------------------------------------------------------+ bool SendOrder(string sym, ENUM_ORDER_TYPE type, double lot, double price) { if (lot <= 0.0) return false; MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_DEAL; req.symbol = sym; req.magic = Magic; req.volume = lot; req.type = type; req.price = price; req.deviation = Slippage; req.type_time = ORDER_TIME_GTC; req.type_filling = 1; if (!OrderSend(req, res)) { Print("OrderSend failed: retcode=", res.retcode); return false; } return (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_DONE_PARTIAL); } //+------------------------------------------------------------------+ // CLOSE ALL POSITIONS (uses CTrade) //+------------------------------------------------------------------+ void CloseAllSymbol(string sym) { for(int i=PositionsTotal()-1; i>=0; i--) { if(m_position.SelectByIndex(i)) { if (m_position.Symbol()==sym) m_trade.PositionClose(m_position.Ticket()); } } } //+------------------------------------------------------------------+