| +
|
| //| 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.
|
| //+------------------------------------------------------------------+ |