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