Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo. Co-authored-by: Cursor <cursoragent@cursor.com>
332 lines
9.2 KiB
Plaintext
332 lines
9.2 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| RSIScalpingSuper.mq5 |
|
|
//| Multi-symbol RSI Scalping portfolio (H1, MT5-optimized params) |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Frontline"
|
|
#property version "1.00"
|
|
#property description "RSI Scalping Super EA — EURUSD GBPUSD USDJPY AUDUSD USDCHF USDCAD NZDUSD EURJPY XAUUSD"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include "MagicNumberHelpers.mqh"
|
|
#include "RSIScalpingSuperParams.mqh"
|
|
|
|
input group "=== Portfolio ==="
|
|
input double LotMultiplier = 1.0;
|
|
input bool ScaleLotsToDeposit = true;
|
|
input double ReferenceDeposit = 10000.0;
|
|
input int Slippage = 3;
|
|
input int MaxOpenPositions = 9;
|
|
|
|
input group "=== Slot toggles ==="
|
|
input bool Enable_EURUSD = true;
|
|
input bool Enable_GBPUSD = true;
|
|
input bool Enable_USDJPY = true;
|
|
input bool Enable_AUDUSD = true;
|
|
input bool Enable_USDCHF = true;
|
|
input bool Enable_USDCAD = true;
|
|
input bool Enable_NZDUSD = true;
|
|
input bool Enable_EURJPY = true;
|
|
input bool Enable_XAUUSD = true;
|
|
|
|
#define RS_TF PERIOD_H1
|
|
|
|
struct RSSymCtx
|
|
{
|
|
string name;
|
|
RSSlotParams p;
|
|
int magic;
|
|
bool enabled;
|
|
int rsiHandle;
|
|
datetime lastBar;
|
|
bool posOpen;
|
|
ulong posTicket;
|
|
ENUM_POSITION_TYPE posType;
|
|
bool rsiAgainst;
|
|
int barsAgainst;
|
|
};
|
|
|
|
CTrade g_trade;
|
|
RSSymCtx g_ctx[RS_SUPER_SLOT_COUNT];
|
|
int g_count = 0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
bool SlotEnabled(const int idx)
|
|
{
|
|
switch(idx)
|
|
{
|
|
case 0: return Enable_EURUSD;
|
|
case 1: return Enable_GBPUSD;
|
|
case 2: return Enable_USDJPY;
|
|
case 3: return Enable_AUDUSD;
|
|
case 4: return Enable_USDCHF;
|
|
case 5: return Enable_USDCAD;
|
|
case 6: return Enable_NZDUSD;
|
|
case 7: return Enable_EURJPY;
|
|
case 8: return Enable_XAUUSD;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
double CalcLot(const string sym, const double baseLot)
|
|
{
|
|
double lot = baseLot * LotMultiplier;
|
|
if(ScaleLotsToDeposit && ReferenceDeposit > 0)
|
|
{
|
|
double bal = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
lot *= bal / ReferenceDeposit;
|
|
}
|
|
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
|
double minL = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
|
double maxL = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
|
if(step > 0)
|
|
lot = MathFloor(lot / step) * step;
|
|
if(lot < minL) lot = minL;
|
|
if(lot > maxL) lot = maxL;
|
|
return lot;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
int CountOurPositions()
|
|
{
|
|
int n = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(PositionGetTicket(i) == 0) continue;
|
|
ulong mg = (ulong)PositionGetInteger(POSITION_MAGIC);
|
|
if(mg >= (ulong)RS_SUPER_MAGIC_BASE && mg < (ulong)(RS_SUPER_MAGIC_BASE + RS_SUPER_SLOT_COUNT + 1))
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
bool UpdateRsi(RSSymCtx &c, double &cur, double &prev, double &two)
|
|
{
|
|
double buf[];
|
|
ArraySetAsSeries(buf, true);
|
|
if(CopyBuffer(c.rsiHandle, 0, 0, 3, buf) < 3)
|
|
return false;
|
|
cur = buf[0];
|
|
prev = buf[1];
|
|
two = buf[2];
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void SyncPosition(RSSymCtx &c)
|
|
{
|
|
if(!PositionExistsByMagic(c.name, c.magic))
|
|
{
|
|
c.posOpen = false;
|
|
c.posTicket = 0;
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
return;
|
|
}
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong t = PositionGetTicket(i);
|
|
if(t == 0) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != c.name) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != c.magic) continue;
|
|
c.posTicket = t;
|
|
c.posOpen = true;
|
|
c.posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void CloseSlot(RSSymCtx &c)
|
|
{
|
|
g_trade.SetExpertMagicNumber(c.magic);
|
|
ClosePositionByMagic(g_trade, c.name, c.magic);
|
|
c.posOpen = false;
|
|
c.posTicket = 0;
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OpenBuy(RSSymCtx &c)
|
|
{
|
|
if(CountOurPositions() >= MaxOpenPositions) return;
|
|
if(PositionExistsByMagic(c.name, c.magic)) return;
|
|
g_trade.SetExpertMagicNumber(c.magic);
|
|
double ask = SymbolInfoDouble(c.name, SYMBOL_ASK);
|
|
double lot = CalcLot(c.name, c.p.lotSize);
|
|
if(g_trade.Buy(lot, c.name, ask, 0, 0, "RS Super Buy"))
|
|
{
|
|
ulong t = g_trade.ResultOrder();
|
|
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, c.name, c.magic))
|
|
{
|
|
c.posTicket = t;
|
|
c.posOpen = true;
|
|
c.posType = POSITION_TYPE_BUY;
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OpenSell(RSSymCtx &c)
|
|
{
|
|
if(CountOurPositions() >= MaxOpenPositions) return;
|
|
if(PositionExistsByMagic(c.name, c.magic)) return;
|
|
g_trade.SetExpertMagicNumber(c.magic);
|
|
double bid = SymbolInfoDouble(c.name, SYMBOL_BID);
|
|
double lot = CalcLot(c.name, c.p.lotSize);
|
|
if(g_trade.Sell(lot, c.name, bid, 0, 0, "RS Super Sell"))
|
|
{
|
|
ulong t = g_trade.ResultOrder();
|
|
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, c.name, c.magic))
|
|
{
|
|
c.posTicket = t;
|
|
c.posOpen = true;
|
|
c.posType = POSITION_TYPE_SELL;
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void ManageExit(RSSymCtx &c, const double cur)
|
|
{
|
|
if(!c.posOpen)
|
|
SyncPosition(c);
|
|
if(!c.posOpen) return;
|
|
|
|
if(!PositionSelectByTicketSymbolAndMagic(c.posTicket, c.name, c.magic))
|
|
{
|
|
c.posOpen = false;
|
|
c.posTicket = 0;
|
|
return;
|
|
}
|
|
|
|
if(c.posType == POSITION_TYPE_BUY)
|
|
{
|
|
if(cur < c.p.rsiOversold)
|
|
{
|
|
if(!c.rsiAgainst) { c.rsiAgainst = true; c.barsAgainst = 1; }
|
|
else c.barsAgainst++;
|
|
if(c.barsAgainst >= c.p.barsToWait) { CloseSlot(c); return; }
|
|
}
|
|
else
|
|
{
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
if(cur >= c.p.rsiTargetBuy) CloseSlot(c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(cur > c.p.rsiOverbought)
|
|
{
|
|
if(!c.rsiAgainst) { c.rsiAgainst = true; c.barsAgainst = 1; }
|
|
else c.barsAgainst++;
|
|
if(c.barsAgainst >= c.p.barsToWait) { CloseSlot(c); return; }
|
|
}
|
|
else
|
|
{
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
if(cur <= c.p.rsiTargetSell) CloseSlot(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void CheckEntry(RSSymCtx &c, const double prev, const double two)
|
|
{
|
|
if(c.posOpen || PositionExistsByMagic(c.name, c.magic)) return;
|
|
if(two <= c.p.rsiOversold && prev > c.p.rsiOversold)
|
|
OpenBuy(c);
|
|
if(two >= c.p.rsiOverbought && prev < c.p.rsiOverbought)
|
|
OpenSell(c);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void ProcessSlot(RSSymCtx &c)
|
|
{
|
|
if(!c.enabled) return;
|
|
if(!SymbolSelect(c.name, true)) return;
|
|
if(Bars(c.name, RS_TF) < c.p.rsiPeriod + 2) return;
|
|
|
|
datetime bt = iTime(c.name, RS_TF, 0);
|
|
if(bt <= 0 || bt == c.lastBar) return;
|
|
c.lastBar = bt;
|
|
|
|
double cur, prev, two;
|
|
if(!UpdateRsi(c, cur, prev, two)) return;
|
|
|
|
ManageExit(c, cur);
|
|
if(!c.posOpen)
|
|
CheckEntry(c, prev, two);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
g_trade.SetDeviationInPoints(Slippage);
|
|
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
g_count = 0;
|
|
|
|
for(int i = 0; i < RS_SUPER_SLOT_COUNT; i++)
|
|
{
|
|
const RSSlotConfig cfg = RS_SUPER_SLOTS[i];
|
|
RSSymCtx c;
|
|
c.name = cfg.symbol;
|
|
c.p = cfg.p;
|
|
c.magic = cfg.magic;
|
|
c.enabled = cfg.enabled && SlotEnabled(i);
|
|
c.rsiHandle = INVALID_HANDLE;
|
|
c.lastBar = 0;
|
|
c.posOpen = false;
|
|
c.posTicket = 0;
|
|
c.rsiAgainst = false;
|
|
c.barsAgainst = 0;
|
|
|
|
if(c.enabled)
|
|
{
|
|
SymbolSelect(c.name, true);
|
|
c.rsiHandle = iRSI(c.name, RS_TF, c.p.rsiPeriod, PRICE_CLOSE);
|
|
if(c.rsiHandle == INVALID_HANDLE)
|
|
{
|
|
Print("Failed RSI handle for ", c.name);
|
|
c.enabled = false;
|
|
}
|
|
SyncPosition(c);
|
|
}
|
|
g_ctx[g_count] = c;
|
|
g_count++;
|
|
}
|
|
|
|
Print("RSIScalpingSuper initialized slots=", g_count);
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
for(int i = 0; i < g_count; i++)
|
|
if(g_ctx[i].rsiHandle != INVALID_HANDLE)
|
|
IndicatorRelease(g_ctx[i].rsiHandle);
|
|
Comment("");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
string status = "RSIScalpingSuper\n";
|
|
for(int i = 0; i < g_count; i++)
|
|
{
|
|
ProcessSlot(g_ctx[i]);
|
|
if(g_ctx[i].enabled)
|
|
status += StringFormat("%s %s | ", g_ctx[i].name, g_ctx[i].posOpen ? "IN" : "--");
|
|
}
|
|
Comment(status);
|
|
}
|