Stochastic-Scalper-XAUUSD-MQL5/StochasticScalper.mq5

286 lines
No EOL
11 KiB
MQL5

//+------------------------------------------------------------------+
//| StochasticScalper.mq5 |
//| Copyright 2024, VerttaxxFX Organization |
//| https://verttaxfx.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, VerttaxxFX"
#property link "https://verttaxfx.com"
#property version "1.01" // RECTIFIED: Version updated to reflect changes.
// RECTIFIED: #property strict is obsolete in MQL5 and has been removed.
//+------------------------------------------------------------------+
//| Input parameters for the Kanban system |
//+------------------------------------------------------------------+
input ulong Inp_MagicNumber = 202409; // Unique Magic Number for this EA
input int Inp_MaxOpenTickets = 1; // Max open trades (The Kanban limit)
input double Inp_RiskPerTrade = 1.0; // Risk % per trade
input bool Inp_UseEquityForRisk = false; // Use Equity? (False uses Balance)
//+------------------------------------------------------------------+
//| Input parameters for the Stochastic strategy |
//+------------------------------------------------------------------+
input int Inp_KPeriod = 14; // Stochastic K Period
input int Inp_DPeriod = 3; // Stochastic D Period
input int Inp_Slowing = 3; // Stochastic Slowing
input int Inp_OverboughtLevel = 80; // Overbought Level
input int Inp_OversoldLevel = 20; // Oversold Level
//+------------------------------------------------------------------+
//| Input parameters for trade management |
//+------------------------------------------------------------------+
input int Inp_StopLoss = 50; // Stop Loss (in pips) // RECTIFIED: Comment changed to 'pips' for clarity.
input int Inp_TakeProfit = 75; // Take Profit (in pips) // RECTIFIED: Comment changed to 'pips' for clarity.
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
// RECTIFIED: The indicator handle is now a global variable.
// This is created once in OnInit() for efficiency, instead of on every tick.
int stoch_handle = INVALID_HANDLE;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// RECTIFIED: Create the indicator handle once when the EA starts.
// This is vastly more efficient than creating it on every tick.
stoch_handle = iStochastic(_Symbol, _Period, Inp_KPeriod, Inp_DPeriod, Inp_Slowing, MODE_SMA, STO_LOWHIGH);
if(stoch_handle == INVALID_HANDLE)
{
Print("Failed to create Stochastic indicator handle. Error: ", GetLastError());
return(INIT_FAILED);
}
// RECTIFIED: The old 'MyPoint' calculation was removed. MQL5's predefined
// '_Point' variable and '_Digits' handle this automatically and more reliably.
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// RECTIFIED: Release the indicator handle when the EA is removed or the chart is closed.
// This frees up terminal resources and is good practice.
if(stoch_handle != INVALID_HANDLE)
{
IndicatorRelease(stoch_handle);
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check Kanban first! If limit is reached, do nothing.
if(!CheckKanban())
return;
// 2. Get Stochastic values
double stoch_main0, stoch_signal0;
if(!GetStochastic(0, stoch_main0, stoch_signal0)) return; // Current bar
double stoch_main1, stoch_signal1;
if(!GetStochastic(1, stoch_main1, stoch_signal1)) return; // Previous bar
// 3. Check for BUY signal: Cross above oversold level
if(stoch_main1 < Inp_OversoldLevel && stoch_main0 > Inp_OversoldLevel && stoch_main0 > stoch_signal0)
{
if(OpenOrder(ORDER_TYPE_BUY))
Print("Buy order opened successfully.");
}
// 4. Check for SELL signal: Cross below overbought level
if(stoch_main1 > Inp_OverboughtLevel && stoch_main0 < Inp_OverboughtLevel && stoch_main0 < stoch_signal0)
{
if(OpenOrder(ORDER_TYPE_SELL))
Print("Sell order opened successfully.");
}
}
//+------------------------------------------------------------------+
//| Kanban Function - Checks max open tickets |
//+------------------------------------------------------------------+
bool CheckKanban()
{
int current_tickets = 0;
// Iterate through all open positions
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
// RECTIFIED: You must select a position by its index before you can get its properties.
// The original code was missing this crucial step.
if(PositionSelect(i))
{
if(PositionGetInteger(POSITION_MAGIC) == Inp_MagicNumber &&
PositionGetString(POSITION_SYMBOL) == _Symbol)
{
current_tickets++;
}
}
}
// Also check pending orders
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
// RECTIFIED: OrderGetTicket(i) correctly gets the ticket number for the order at the specified index.
ulong ord_ticket = OrderGetTicket(i);
// RECTIFIED: The original functions OrderGetInteger(ORDER_MAGIC) were incorrect.
// You must provide the ticket number to the function to specify which order you are querying.
if(OrderGetInteger(ord_ticket, ORDER_MAGIC) == Inp_MagicNumber &&
OrderGetString(ord_ticket, ORDER_SYMBOL) == _Symbol)
{
current_tickets++;
}
}
// The Kanban rule: If current tickets >= max allowed, return false.
if(current_tickets >= Inp_MaxOpenTickets)
{
Comment("Kanban Limit Reached. Current Trades: ", current_tickets);
return false;
}
else
{
Comment("Kanban Status: OK. Current Trades: ", current_tickets);
return true;
}
}
//+------------------------------------------------------------------+
//| Function to get Stochastic values |
//+------------------------------------------------------------------+
// RECTIFIED: Function now returns a boolean to indicate success or failure.
bool GetStochastic(int shift, double &main, double &signal)
{
// RECTIFIED: Check if the global handle is valid before using it.
if(stoch_handle == INVALID_HANDLE) return false;
double k[1];
double d[1];
// RECTIFIED: ArraySetAsSeries is not needed when copying only one element.
// RECTIFIED: Use the globally created handle. Do not create or release it here.
if(CopyBuffer(stoch_handle, 0, shift, 1, k) < 1 || CopyBuffer(stoch_handle, 1, shift, 1, d) < 1)
{
Print("Failed to copy stochastic buffers. Error ", GetLastError());
return false;
}
main = k[0];
signal = d[0];
return true;
}
//+------------------------------------------------------------------+
//| Function to calculate lot size based on risk |
//+------------------------------------------------------------------+
double CalculateLotSize(double risk_percent, bool use_equity)
{
double account_balance;
if(use_equity) account_balance = AccountInfoDouble(ACCOUNT_EQUITY);
else account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double risk_amount = account_balance * (risk_percent / 100.0);
// RECTIFIED: This logic is more robust for calculating the price distance of the stop loss
// based on the input pips. It correctly handles both 3/5 and 2/4 digit brokers.
double point_value = _Point * (_Digits == 3 || _Digits == 5 ? 10 : 1);
double stop_loss_in_price = Inp_StopLoss * point_value;
if(stop_loss_in_price == 0) return 0.01;
// RECTIFIED: Using SymbolInfoDouble directly for clarity and correctness.
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tick_size == 0 || tick_value == 0) return 0.01;
double loss_per_lot = (stop_loss_in_price / tick_size) * tick_value;
if(loss_per_lot == 0) return 0.01;
double lot_size = risk_amount / loss_per_lot;
// Normalize and validate lot size
double vol_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lot_size = MathRound(lot_size / vol_step) * vol_step;
double min_vol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_vol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(lot_size < min_vol) lot_size = min_vol;
if(lot_size > max_vol) lot_size = max_vol;
return lot_size;
}
//+------------------------------------------------------------------+
//| Function to open a new order |
//+------------------------------------------------------------------+
bool OpenOrder(ENUM_ORDER_TYPE order_type)
{
double bid_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl_price = 0, tp_price = 0;
double open_price = 0;
// RECTIFIED: More robust calculation for SL/TP in price units.
double stop_loss_distance = Inp_StopLoss * _Point * (_Digits == 3 || _Digits == 5 ? 10 : 1);
double take_profit_distance = Inp_TakeProfit * _Point * (_Digits == 3 || _Digits == 5 ? 10 : 1);
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
// Calculate lot size
double lot_size = CalculateLotSize(Inp_RiskPerTrade, Inp_UseEquityForRisk);
if(lot_size <= 0) return false;
// Set order type and price
if(order_type == ORDER_TYPE_BUY)
{
open_price = ask_price;
sl_price = open_price - stop_loss_distance;
tp_price = open_price + take_profit_distance;
}
else if(order_type == ORDER_TYPE_SELL)
{
open_price = bid_price;
sl_price = open_price + stop_loss_distance;
tp_price = open_price - take_profit_distance;
}
// Fill in the trade request structure
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = lot_size;
request.type = order_type;
request.price = NormalizeDouble(open_price, _Digits);
request.sl = NormalizeDouble(sl_price, _Digits);
request.tp = NormalizeDouble(tp_price, _Digits);
request.magic = Inp_MagicNumber;
request.comment = "Stochastic Scalper EA";
request.deviation = 10;
// Send the order
bool success = OrderSend(request, result);
if(!success)
{
Print("OrderSend failed. Error code: ", result.retcode, " - ", result.comment);
return false;
}
return true;
}
// RECTIFIED: The custom Point() function was redundant as MQL5 has a built-in _Point variable.
// It has been removed.
//+------------------------------------------------------------------+