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>
494 lines
16 KiB
Plaintext
494 lines
16 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| RSIReversalAsianStrategy.mqh |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| RSI Reversal Asian Strategy Data Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct RSIReversalAsianData {
|
|
string symbol;
|
|
bool isInitialized;
|
|
int rsiHandle;
|
|
CTrade trade;
|
|
bool isPositionOpen;
|
|
double positionOpenPrice;
|
|
datetime positionOpenTime;
|
|
ENUM_POSITION_TYPE lastPositionType;
|
|
bool sessionCloseAttempted;
|
|
|
|
// RSI crossover variables
|
|
double rsiCurrent;
|
|
double rsiPrevious;
|
|
double rsiPrevious2;
|
|
bool rsiCrossedOverbought;
|
|
bool rsiCrossedOversold;
|
|
bool rsiCrossedExitLevel;
|
|
|
|
// Strategy parameters
|
|
int RSIPeriod;
|
|
double OverboughtLevel;
|
|
double OversoldLevel;
|
|
int TakeProfitPips;
|
|
int StopLossPips;
|
|
double MaxLotSize;
|
|
int MaxSpread;
|
|
int MaxDuration;
|
|
bool UseStopLoss;
|
|
bool UseTakeProfit;
|
|
bool UseRSIExit;
|
|
double RSIExitLevel;
|
|
bool CloseOutsideSession;
|
|
ENUM_TIMEFRAMES TimeFrame;
|
|
int MagicNumber;
|
|
int Slippage;
|
|
double point;
|
|
bool closeUnprofitableOnNewSignal;
|
|
};
|
|
|
|
// Session times (UTC)
|
|
const int AsianSessionStart = 0; // 00:00 UTC
|
|
const int AsianSessionEnd = 8; // 08:00 UTC
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if current time is in Asian session |
|
|
//+------------------------------------------------------------------+
|
|
bool IsAsianSession()
|
|
{
|
|
datetime currentTime = TimeCurrent();
|
|
MqlDateTime timeStruct;
|
|
TimeToStruct(currentTime, timeStruct);
|
|
|
|
return (timeStruct.hour >= AsianSessionStart && timeStruct.hour < AsianSessionEnd);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if trading is allowed for symbol |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTradingAllowed(RSIReversalAsianData& data)
|
|
{
|
|
// Do not require SYMBOL_TRADE_MODE_FULL: many symbols allow one side only (long/short).
|
|
const long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE);
|
|
if(tradeMode == SYMBOL_TRADE_MODE_DISABLED || tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY)
|
|
return false;
|
|
|
|
if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check RSI crossover conditions |
|
|
//+------------------------------------------------------------------+
|
|
void CheckRSICrossover(RSIReversalAsianData& data)
|
|
{
|
|
// Reset crossover flags
|
|
data.rsiCrossedOverbought = false;
|
|
data.rsiCrossedOversold = false;
|
|
data.rsiCrossedExitLevel = false;
|
|
|
|
// Check for overbought crossover (RSI crosses above overbought level)
|
|
if(data.rsiPrevious < data.OverboughtLevel && data.rsiCurrent >= data.OverboughtLevel)
|
|
{
|
|
data.rsiCrossedOverbought = true;
|
|
}
|
|
|
|
// Check for oversold crossover (RSI crosses below oversold level)
|
|
if(data.rsiPrevious > data.OversoldLevel && data.rsiCurrent <= data.OversoldLevel)
|
|
{
|
|
data.rsiCrossedOversold = true;
|
|
}
|
|
|
|
// Check for exit level crossover
|
|
if(data.rsiPrevious < data.RSIExitLevel && data.rsiCurrent >= data.RSIExitLevel)
|
|
{
|
|
data.rsiCrossedExitLevel = true;
|
|
}
|
|
else if(data.rsiPrevious > data.RSIExitLevel && data.rsiCurrent <= data.RSIExitLevel)
|
|
{
|
|
data.rsiCrossedExitLevel = true;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close all trades for the symbol |
|
|
//+------------------------------------------------------------------+
|
|
bool CloseAllTrades(RSIReversalAsianData& data, string reason = "")
|
|
{
|
|
bool allClosed = true;
|
|
int totalPositions = PositionsTotal();
|
|
|
|
if(totalPositions == 0)
|
|
return true;
|
|
|
|
for(int i = totalPositions - 1; i >= 0; i--)
|
|
{
|
|
if(PositionGetSymbol(i) == data.symbol)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket > 0 && PositionSelectByTicket(ticket))
|
|
{
|
|
if(PositionGetInteger(POSITION_MAGIC) == (ulong)data.MagicNumber)
|
|
{
|
|
// Try to close position with retry logic
|
|
int retryCount = 0;
|
|
bool positionClosed = false;
|
|
|
|
while(retryCount < 3 && !positionClosed)
|
|
{
|
|
if(data.trade.PositionClose(ticket))
|
|
{
|
|
data.isPositionOpen = false;
|
|
positionClosed = true;
|
|
}
|
|
else
|
|
{
|
|
int error = GetLastError();
|
|
|
|
// If error is 4756 (Trade disabled), wait longer before retry
|
|
if(error == 4756)
|
|
{
|
|
Sleep(5000); // Wait 5 seconds before retry
|
|
retryCount++;
|
|
}
|
|
else
|
|
{
|
|
// For other errors, break the loop
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!positionClosed)
|
|
{
|
|
allClosed = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return allClosed;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize RSI Reversal Asian Strategy |
|
|
//+------------------------------------------------------------------+
|
|
bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol,
|
|
int RSIPeriod, double OverboughtLevel, double OversoldLevel,
|
|
int TakeProfitPips, int StopLossPips, double MaxLotSize,
|
|
int MaxSpread, int MaxDuration, bool UseStopLoss,
|
|
bool UseTakeProfit, bool UseRSIExit, double RSIExitLevel,
|
|
bool CloseOutsideSession, ENUM_TIMEFRAMES TimeFrame,
|
|
int MagicNumber, int Slippage)
|
|
{
|
|
data.symbol = symbol;
|
|
data.isInitialized = false;
|
|
|
|
// Check if symbol exists
|
|
if(!SymbolSelect(symbol, true))
|
|
{
|
|
Print("RSIReversalAsian: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
|
return false;
|
|
}
|
|
|
|
// Wait a bit for symbol to be ready
|
|
Sleep(100);
|
|
|
|
// Get symbol point
|
|
data.point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
// Store parameters
|
|
data.RSIPeriod = RSIPeriod;
|
|
data.OverboughtLevel = OverboughtLevel;
|
|
data.OversoldLevel = OversoldLevel;
|
|
data.TakeProfitPips = TakeProfitPips;
|
|
data.StopLossPips = StopLossPips;
|
|
data.MaxLotSize = MaxLotSize;
|
|
data.MaxSpread = MaxSpread;
|
|
data.MaxDuration = MaxDuration;
|
|
data.UseStopLoss = UseStopLoss;
|
|
data.UseTakeProfit = UseTakeProfit;
|
|
data.UseRSIExit = UseRSIExit;
|
|
data.RSIExitLevel = RSIExitLevel;
|
|
data.CloseOutsideSession = CloseOutsideSession;
|
|
data.TimeFrame = TimeFrame;
|
|
data.MagicNumber = MagicNumber;
|
|
data.Slippage = Slippage;
|
|
|
|
// Initialize RSI indicator with retry logic (for insufficient history in backtesting)
|
|
data.rsiHandle = INVALID_HANDLE;
|
|
int retryCount = 0;
|
|
int maxRetries = 5;
|
|
|
|
while(retryCount < maxRetries && data.rsiHandle == INVALID_HANDLE)
|
|
{
|
|
data.rsiHandle = iRSI(symbol, TimeFrame, RSIPeriod, PRICE_CLOSE);
|
|
|
|
if(data.rsiHandle == INVALID_HANDLE)
|
|
{
|
|
int error = GetLastError();
|
|
|
|
// Error 4805 = insufficient history - wait longer and retry
|
|
if(error == 4805 && retryCount < maxRetries - 1)
|
|
{
|
|
Sleep(1000); // Wait 1 second for history to load
|
|
retryCount++;
|
|
continue;
|
|
}
|
|
|
|
Print("RSIReversalAsian: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", error == 4805 ? "Insufficient history data" : "Unknown", ")");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(data.rsiHandle == INVALID_HANDLE)
|
|
{
|
|
Print("RSIReversalAsian: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries");
|
|
return false;
|
|
}
|
|
|
|
// Wait a bit for the indicator to be ready
|
|
Sleep(100);
|
|
|
|
// Initialize RSI values with retry logic
|
|
double rsi[];
|
|
ArraySetAsSeries(rsi, true);
|
|
|
|
retryCount = 0;
|
|
bool rsiInitialized = false;
|
|
|
|
while(retryCount < 10 && !rsiInitialized)
|
|
{
|
|
int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi);
|
|
if(copied >= 3)
|
|
{
|
|
data.rsiCurrent = rsi[0];
|
|
data.rsiPrevious = rsi[1];
|
|
data.rsiPrevious2 = rsi[2];
|
|
rsiInitialized = true;
|
|
}
|
|
else
|
|
{
|
|
retryCount++;
|
|
Sleep(100);
|
|
}
|
|
}
|
|
|
|
if(!rsiInitialized)
|
|
{
|
|
// Don't fail initialization, just set default values
|
|
data.rsiCurrent = 50.0;
|
|
data.rsiPrevious = 50.0;
|
|
data.rsiPrevious2 = 50.0;
|
|
}
|
|
|
|
// Set trade parameters
|
|
data.trade.SetExpertMagicNumber(MagicNumber);
|
|
data.trade.SetDeviationInPoints(Slippage);
|
|
data.trade.SetTypeFillingBySymbol(symbol);
|
|
|
|
// Initialize state
|
|
data.isPositionOpen = false;
|
|
data.positionOpenPrice = 0;
|
|
data.positionOpenTime = 0;
|
|
data.lastPositionType = POSITION_TYPE_BUY;
|
|
data.sessionCloseAttempted = false;
|
|
data.rsiCrossedOverbought = false;
|
|
data.rsiCrossedOversold = false;
|
|
data.rsiCrossedExitLevel = false;
|
|
|
|
data.isInitialized = true;
|
|
|
|
Print("RSIReversalAsian: Successfully initialized for symbol '", symbol, "'");
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Deinitialize RSI Reversal Asian Strategy |
|
|
//+------------------------------------------------------------------+
|
|
void DeinitRSIReversalAsian(RSIReversalAsianData& data)
|
|
{
|
|
if(data.rsiHandle != INVALID_HANDLE)
|
|
IndicatorRelease(data.rsiHandle);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Process RSI Reversal Asian Strategy |
|
|
//+------------------------------------------------------------------+
|
|
void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize)
|
|
{
|
|
if(!data.isInitialized)
|
|
return;
|
|
|
|
// Check if trading is allowed
|
|
if(!IsTradingAllowed(data))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if we're in Asian session
|
|
if(!IsAsianSession())
|
|
{
|
|
// Close all positions if outside Asian session and CloseOutsideSession is true
|
|
if(data.CloseOutsideSession && !data.sessionCloseAttempted)
|
|
{
|
|
CloseAllTrades(data, "Outside Asian session");
|
|
data.sessionCloseAttempted = true;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Reset the session close attempt flag when we enter Asian session
|
|
data.sessionCloseAttempted = false;
|
|
}
|
|
|
|
// Get current spread
|
|
double spread = SymbolInfoDouble(data.symbol, SYMBOL_ASK) - SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
|
int spreadInPips = (int)(spread / data.point);
|
|
|
|
// Check if spread is too high
|
|
if(spreadInPips > data.MaxSpread)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get RSI values from bar data
|
|
double rsi[];
|
|
ArraySetAsSeries(rsi, true);
|
|
|
|
int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi);
|
|
if(copied < 3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update RSI values
|
|
data.rsiPrevious2 = data.rsiPrevious;
|
|
data.rsiPrevious = data.rsiCurrent;
|
|
data.rsiCurrent = rsi[0];
|
|
|
|
// Validate RSI values
|
|
if(data.rsiCurrent == 0 || data.rsiPrevious == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for RSI crossovers
|
|
CheckRSICrossover(data);
|
|
|
|
// Get current prices
|
|
double currentBid = SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
|
double currentAsk = SymbolInfoDouble(data.symbol, SYMBOL_ASK);
|
|
|
|
// Check for open position
|
|
bool hasOpenPosition = PositionExistsByMagic(data.symbol, (ulong)data.MagicNumber);
|
|
|
|
if(hasOpenPosition)
|
|
{
|
|
// Get position details
|
|
ulong ticket = GetPositionTicketByMagic(data.symbol, (ulong)data.MagicNumber);
|
|
if(ticket > 0 && PositionSelectByTicket(ticket))
|
|
{
|
|
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
|
|
|
// Check for RSI exit if enabled
|
|
if(data.UseRSIExit && data.rsiCrossedExitLevel)
|
|
{
|
|
bool shouldExit = false;
|
|
|
|
// For long positions, exit when RSI crosses above exit level
|
|
if(posType == POSITION_TYPE_BUY && data.rsiCurrent >= data.RSIExitLevel && data.rsiPrevious < data.RSIExitLevel)
|
|
{
|
|
shouldExit = true;
|
|
}
|
|
// For short positions, exit when RSI crosses below exit level
|
|
else if(posType == POSITION_TYPE_SELL && data.rsiCurrent <= data.RSIExitLevel && data.rsiPrevious > data.RSIExitLevel)
|
|
{
|
|
shouldExit = true;
|
|
}
|
|
|
|
if(shouldExit)
|
|
{
|
|
CloseAllTrades(data, "RSI Exit Crossover");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for timeout
|
|
if(TimeCurrent() - openTime > data.MaxDuration * 3600)
|
|
{
|
|
CloseAllTrades(data, "Timeout");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no position is open (or replacement enabled), look for entry signals
|
|
if(!hasOpenPosition || data.closeUnprofitableOnNewSignal)
|
|
{
|
|
// Place buy order if RSI crosses below oversold level (oversold crossover)
|
|
if(data.rsiCrossedOversold)
|
|
{
|
|
if(!United_PrepareEntrySlot(data.trade, data.symbol, (ulong)data.MagicNumber, data.closeUnprofitableOnNewSignal))
|
|
return;
|
|
double sl = data.UseStopLoss ? currentBid - data.StopLossPips * data.point : 0;
|
|
double tp = data.UseTakeProfit ? currentBid + data.TakeProfitPips * data.point : 0;
|
|
|
|
if(data.UseStopLoss && sl >= currentBid)
|
|
return;
|
|
if(data.UseTakeProfit && tp <= currentBid)
|
|
return;
|
|
|
|
data.trade.SetDeviationInPoints(data.Slippage);
|
|
data.trade.SetTypeFillingBySymbol(data.symbol);
|
|
data.trade.SetExpertMagicNumber(data.MagicNumber);
|
|
|
|
double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize;
|
|
const double vol = United_NormalizeVolume(data.symbol, tradeLotSize);
|
|
if(vol <= 0.0)
|
|
return;
|
|
|
|
if(data.trade.Buy(vol, data.symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy"))
|
|
{
|
|
data.isPositionOpen = true;
|
|
data.positionOpenPrice = currentAsk;
|
|
data.positionOpenTime = TimeCurrent();
|
|
data.lastPositionType = POSITION_TYPE_BUY;
|
|
}
|
|
}
|
|
// Place sell order if RSI crosses above overbought level (overbought crossover)
|
|
else if(data.rsiCrossedOverbought)
|
|
{
|
|
if(!United_PrepareEntrySlot(data.trade, data.symbol, (ulong)data.MagicNumber, data.closeUnprofitableOnNewSignal))
|
|
return;
|
|
double sl = data.UseStopLoss ? currentAsk + data.StopLossPips * data.point : 0;
|
|
double tp = data.UseTakeProfit ? currentAsk - data.TakeProfitPips * data.point : 0;
|
|
|
|
if(data.UseStopLoss && sl <= currentAsk)
|
|
return;
|
|
if(data.UseTakeProfit && tp >= currentAsk)
|
|
return;
|
|
|
|
data.trade.SetDeviationInPoints(data.Slippage);
|
|
data.trade.SetTypeFillingBySymbol(data.symbol);
|
|
data.trade.SetExpertMagicNumber(data.MagicNumber);
|
|
|
|
double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize;
|
|
const double vol = United_NormalizeVolume(data.symbol, tradeLotSize);
|
|
if(vol <= 0.0)
|
|
return;
|
|
|
|
if(data.trade.Sell(vol, data.symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell"))
|
|
{
|
|
data.isPositionOpen = true;
|
|
data.positionOpenPrice = currentBid;
|
|
data.positionOpenTime = TimeCurrent();
|
|
data.lastPositionType = POSITION_TYPE_SELL;
|
|
}
|
|
}
|
|
}
|
|
}
|