452 lines
14 KiB
Plaintext
452 lines
14 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| RSIScalpingStrategy.mqh |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| RSI Scalping Strategy Data Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct RSIScalpingData {
|
|
string symbol;
|
|
bool isInitialized;
|
|
CTrade trade;
|
|
int rsi_handle;
|
|
double rsi_buffer[];
|
|
double rsi_prev;
|
|
double rsi_current;
|
|
double rsi_two_bars_ago;
|
|
bool position_open;
|
|
ulong position_ticket;
|
|
ENUM_POSITION_TYPE current_position_type;
|
|
datetime last_bar_time;
|
|
bool rsi_against_position;
|
|
int bars_against_count;
|
|
};
|
|
|
|
string ErrorDescription(int errorCode)
|
|
{
|
|
switch(errorCode)
|
|
{
|
|
case 4801: return "Symbol not found";
|
|
case 4802: return "Symbol not selected";
|
|
case 4803: return "Symbol not visible";
|
|
case 4804: return "Symbol not available";
|
|
case 4805: return "Cannot load indicator - insufficient history data";
|
|
default: return "Unknown error " + IntegerToString(errorCode);
|
|
}
|
|
}
|
|
|
|
bool InitRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period,
|
|
ENUM_APPLIED_PRICE RSI_Applied_Price, int MagicNumber, int Slippage)
|
|
{
|
|
data.symbol = symbol;
|
|
data.isInitialized = false;
|
|
|
|
// Check if symbol exists
|
|
if(!SymbolSelect(symbol, true))
|
|
{
|
|
Print("RSIScalping: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
|
return false; // Return false but don't fail entire EA
|
|
}
|
|
|
|
// Wait a bit for symbol to be ready
|
|
Sleep(100);
|
|
|
|
// Try to create RSI indicator with retry logic (for insufficient history in backtesting)
|
|
data.rsi_handle = INVALID_HANDLE;
|
|
int retryCount = 0;
|
|
int maxRetries = 5;
|
|
|
|
while(retryCount < maxRetries && data.rsi_handle == INVALID_HANDLE)
|
|
{
|
|
data.rsi_handle = iRSI(symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
|
|
|
|
if(data.rsi_handle == 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("RSIScalping: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", ErrorDescription(error), ")");
|
|
return false; // Return false but don't fail entire EA
|
|
}
|
|
}
|
|
|
|
if(data.rsi_handle == INVALID_HANDLE)
|
|
{
|
|
Print("RSIScalping: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries");
|
|
return false;
|
|
}
|
|
|
|
data.trade.SetExpertMagicNumber(MagicNumber);
|
|
data.trade.SetDeviationInPoints(Slippage);
|
|
data.trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
|
|
ArraySetAsSeries(data.rsi_buffer, true);
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
data.isInitialized = true;
|
|
|
|
Print("RSIScalping: Successfully initialized for symbol '", symbol, "'");
|
|
return true;
|
|
}
|
|
|
|
void DeinitRSIScalping(RSIScalpingData& data)
|
|
{
|
|
if(data.rsi_handle != INVALID_HANDLE)
|
|
IndicatorRelease(data.rsi_handle);
|
|
}
|
|
|
|
bool UpdateRSI(RSIScalpingData& data)
|
|
{
|
|
if(CopyBuffer(data.rsi_handle, 0, 0, 3, data.rsi_buffer) < 3)
|
|
return false;
|
|
|
|
data.rsi_current = data.rsi_buffer[0];
|
|
data.rsi_prev = data.rsi_buffer[1];
|
|
data.rsi_two_bars_ago = data.rsi_buffer[2];
|
|
|
|
return true;
|
|
}
|
|
|
|
void CheckExistingPosition(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber,
|
|
double RSI_Oversold, double RSI_Overbought, double RSI_Target_Buy,
|
|
double RSI_Target_Sell, int BarsToWait)
|
|
{
|
|
// Always check if position exists, even if tracking says it doesn't
|
|
bool positionExists = PositionExistsByMagic(data.symbol, MagicNumber);
|
|
|
|
if(!positionExists && data.position_open)
|
|
{
|
|
// Position was closed externally, reset tracking
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
return;
|
|
}
|
|
|
|
if(!positionExists)
|
|
return;
|
|
|
|
// Update tracking if we have a position but tracking was lost
|
|
if(!data.position_open && positionExists)
|
|
{
|
|
ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber);
|
|
if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber))
|
|
{
|
|
data.position_ticket = ticket;
|
|
data.position_open = true;
|
|
data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
}
|
|
}
|
|
|
|
// Verify our tracked position still exists
|
|
if(data.position_open && data.position_ticket > 0)
|
|
{
|
|
if(!PositionSelectByTicketSymbolAndMagic(data.position_ticket, data.symbol, MagicNumber))
|
|
{
|
|
// Try to find the position again
|
|
ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber);
|
|
if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber))
|
|
{
|
|
data.position_ticket = ticket;
|
|
data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
}
|
|
else
|
|
{
|
|
// Position doesn't exist, reset tracking
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Update position type in case it changed (shouldn't happen, but be safe)
|
|
data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
}
|
|
}
|
|
|
|
if(data.current_position_type == POSITION_TYPE_BUY)
|
|
{
|
|
if(data.rsi_current < RSI_Oversold)
|
|
{
|
|
if(!data.rsi_against_position)
|
|
{
|
|
data.rsi_against_position = true;
|
|
data.bars_against_count = 1;
|
|
}
|
|
else
|
|
{
|
|
data.bars_against_count++;
|
|
}
|
|
|
|
if(data.bars_against_count >= BarsToWait)
|
|
{
|
|
ClosePosition(data, MagicNumber);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(data.rsi_against_position)
|
|
{
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
}
|
|
|
|
if(data.rsi_current >= RSI_Target_Buy)
|
|
{
|
|
ClosePosition(data, MagicNumber);
|
|
}
|
|
}
|
|
}
|
|
else if(data.current_position_type == POSITION_TYPE_SELL)
|
|
{
|
|
if(data.rsi_current > RSI_Overbought)
|
|
{
|
|
if(!data.rsi_against_position)
|
|
{
|
|
data.rsi_against_position = true;
|
|
data.bars_against_count = 1;
|
|
}
|
|
else
|
|
{
|
|
data.bars_against_count++;
|
|
}
|
|
|
|
if(data.bars_against_count >= BarsToWait)
|
|
{
|
|
ClosePosition(data, MagicNumber);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(data.rsi_against_position)
|
|
{
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
}
|
|
|
|
if(data.rsi_current <= RSI_Target_Sell)
|
|
{
|
|
ClosePosition(data, MagicNumber);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckEntrySignals(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber,
|
|
double RSI_Oversold, double RSI_Overbought, double LotSize)
|
|
{
|
|
if(data.rsi_two_bars_ago <= RSI_Oversold && data.rsi_prev > RSI_Oversold)
|
|
{
|
|
OpenBuyPosition(data, MagicNumber, LotSize);
|
|
}
|
|
|
|
if(data.rsi_two_bars_ago >= RSI_Overbought && data.rsi_prev < RSI_Overbought)
|
|
{
|
|
OpenSellPosition(data, MagicNumber, LotSize);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize Lot Size According to Symbol Properties |
|
|
//+------------------------------------------------------------------+
|
|
double NormalizeLotSize(string symbol, double lotSize)
|
|
{
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
// Round to lot step
|
|
if(lotStep > 0)
|
|
lotSize = MathFloor(lotSize / lotStep) * lotStep;
|
|
|
|
// Apply min/max constraints
|
|
if(lotSize < minLot)
|
|
lotSize = minLot;
|
|
if(lotSize > maxLot)
|
|
lotSize = maxLot;
|
|
|
|
return lotSize;
|
|
}
|
|
|
|
void OpenBuyPosition(RSIScalpingData& data, int MagicNumber, double LotSize)
|
|
{
|
|
if(PositionExistsByMagic(data.symbol, MagicNumber))
|
|
return;
|
|
|
|
// Normalize lot size according to symbol properties
|
|
double normalizedLot = NormalizeLotSize(data.symbol, LotSize);
|
|
|
|
double ask = SymbolInfoDouble(data.symbol, SYMBOL_ASK);
|
|
|
|
if(data.trade.Buy(normalizedLot, data.symbol, ask, 0, 0, "RSI Scalping Buy"))
|
|
{
|
|
ulong new_ticket = data.trade.ResultOrder();
|
|
if(new_ticket > 0)
|
|
{
|
|
if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber))
|
|
{
|
|
data.position_ticket = new_ticket;
|
|
data.position_open = true;
|
|
data.current_position_type = POSITION_TYPE_BUY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpenSellPosition(RSIScalpingData& data, int MagicNumber, double LotSize)
|
|
{
|
|
if(PositionExistsByMagic(data.symbol, MagicNumber))
|
|
return;
|
|
|
|
// Normalize lot size according to symbol properties
|
|
double normalizedLot = NormalizeLotSize(data.symbol, LotSize);
|
|
|
|
double bid = SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
|
|
|
if(data.trade.Sell(normalizedLot, data.symbol, bid, 0, 0, "RSI Scalping Sell"))
|
|
{
|
|
ulong new_ticket = data.trade.ResultOrder();
|
|
if(new_ticket > 0)
|
|
{
|
|
if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber))
|
|
{
|
|
data.position_ticket = new_ticket;
|
|
data.position_open = true;
|
|
data.current_position_type = POSITION_TYPE_SELL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClosePosition(RSIScalpingData& data, int MagicNumber)
|
|
{
|
|
// First verify position still exists
|
|
if(!PositionExistsByMagic(data.symbol, MagicNumber))
|
|
{
|
|
// Position doesn't exist, reset tracking
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
return;
|
|
}
|
|
|
|
// Try to close by ticket first (more reliable)
|
|
bool closed = false;
|
|
if(data.position_ticket > 0)
|
|
{
|
|
if(PositionSelectByTicket(data.position_ticket))
|
|
{
|
|
// Verify it's our position
|
|
if(PositionGetString(POSITION_SYMBOL) == data.symbol &&
|
|
PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
|
{
|
|
closed = data.trade.PositionClose(data.position_ticket);
|
|
if(!closed)
|
|
{
|
|
Print("RSIScalping: Failed to close position by ticket ", data.position_ticket,
|
|
" - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If ticket method failed, try magic number method
|
|
if(!closed)
|
|
{
|
|
closed = ClosePositionByMagic(data.trade, data.symbol, MagicNumber);
|
|
if(!closed)
|
|
{
|
|
Print("RSIScalping: Failed to close position by magic number for '", data.symbol,
|
|
"' - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")");
|
|
}
|
|
}
|
|
|
|
// Verify position is actually closed
|
|
if(closed)
|
|
{
|
|
// Wait a moment and verify
|
|
Sleep(50);
|
|
if(!PositionExistsByMagic(data.symbol, MagicNumber))
|
|
{
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
Print("RSIScalping: Position successfully closed for '", data.symbol, "'");
|
|
}
|
|
else
|
|
{
|
|
Print("RSIScalping: Warning - Close returned success but position still exists for '", data.symbol, "'");
|
|
// Try one more time
|
|
Sleep(100);
|
|
if(PositionExistsByMagic(data.symbol, MagicNumber))
|
|
{
|
|
ClosePositionByMagic(data.trade, data.symbol, MagicNumber);
|
|
}
|
|
// Reset tracking anyway to prevent getting stuck
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Close failed, but reset tracking to prevent getting stuck
|
|
// The position might have been closed externally
|
|
data.position_open = false;
|
|
data.position_ticket = 0;
|
|
data.rsi_against_position = false;
|
|
data.bars_against_count = 0;
|
|
}
|
|
}
|
|
|
|
void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period,
|
|
ENUM_APPLIED_PRICE RSI_Applied_Price, double RSI_Overbought,
|
|
double RSI_Oversold, double RSI_Target_Buy, double RSI_Target_Sell,
|
|
int BarsToWait, double LotSize, int MagicNumber)
|
|
{
|
|
// Skip if not initialized (symbol not available)
|
|
if(!data.isInitialized)
|
|
return;
|
|
|
|
data.symbol = symbol; // Update symbol in case it changed
|
|
if(Bars(data.symbol, TimeFrame) < RSI_Period + 2)
|
|
return;
|
|
|
|
datetime current_bar_time = iTime(data.symbol, TimeFrame, 0);
|
|
if(current_bar_time == data.last_bar_time)
|
|
return;
|
|
|
|
data.last_bar_time = current_bar_time;
|
|
|
|
if(!UpdateRSI(data))
|
|
return;
|
|
|
|
CheckExistingPosition(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought,
|
|
RSI_Target_Buy, RSI_Target_Sell, BarsToWait);
|
|
|
|
if(!data.position_open && !PositionExistsByMagic(data.symbol, MagicNumber))
|
|
{
|
|
CheckEntrySignals(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought, LotSize);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|