286 lines
No EOL
11 KiB
MQL5
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.
|
|
//+------------------------------------------------------------------+ |