SonnyPanel/SonnyPanelGood/EA/gio41.mq5
sbruyneeel 93a1af8cd5
2026-03-22 22:19:39 +01:00

496 lines
15 KiB
MQL5

//+------------------------------------------------------------------+
//| MA Cross Trend + Single-Direction Recovery EA (MQL5) |
//| Cleaned: no chart objects, restart-safe state reconstruction |
//+------------------------------------------------------------------+
#property strict
#include <Trade\PositionInfo.mqh>
#include <Trade\Trade.mqh>
// ---------------- 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());
}
}
}
//+------------------------------------------------------------------+