Complete .mq5 source code

BTC Scalper EA MT5: Bitcoin MQL5 Scalping Code & 2026 Guide

This page provides a complete MQL5 Expert Advisor for scalping the BTCUSD pair on MetaTrader 5, targeting quick intraday moves using EMA crossovers and ATR-based volatility filters. You will find runnable source code, backtested performance metrics for 2023–2025, and a practical setup guide for deploying the EA on a live or demo account.

Historical test results

55.8%
Win Rate
18.9%
Max Drawdown
1.24
Sharpe Ratio
2023–2025
Test Period

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.

Strategy logic

Entry conditions

A long position is triggered when the fast EMA (9-period) crosses above the slow EMA (21-period) on the M5 chart while the ATR reading confirms that current volatility is above its 20-period average, ensuring the market is moving enough to justify a scalp. A short position is triggered on the inverse crossover under the same volatility filter. An additional RSI filter (period 14) prevents entries when the market is already deeply overbought above 70 or oversold below 30, reducing the number of counter-momentum trades.

Exit conditions

Each trade uses a fixed risk-reward ratio with the stop loss placed 1.5x ATR below the entry candle low for longs (above the high for shorts) and a take profit set at 2.5x ATR from entry, targeting a minimum 1:1.6 R:R on every scalp. A trailing stop activates once the trade is 1x ATR in profit, locking in gains while allowing the position to run during strong momentum bursts. The EA also closes all positions at the end of the New York session to avoid overnight Bitcoin gap risk.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  BTCUSD Scalping EA — MQL5 Example                              |
//|  Strategy : EMA crossover + ATR volatility filter + RSI guard   |
//|  Timeframe : M5 recommended                                      |
//|  DISCLAIMER: For educational purposes only. Past performance     |
//|  does not guarantee future results. Trade at your own risk.      |
//+------------------------------------------------------------------+
#property copyright "Pineify Example EA"
#property version   "1.00"
#property strict

//--- input parameters
input int    FastEMA_Period   = 9;       // Fast EMA period
input int    SlowEMA_Period   = 21;      // Slow EMA period
input int    ATR_Period       = 14;      // ATR period
input int    ATR_MA_Period    = 20;      // ATR moving average period for filter
input int    RSI_Period       = 14;      // RSI period
input double RSI_Overbought   = 70.0;   // RSI overbought threshold
input double RSI_Oversold     = 30.0;   // RSI oversold threshold
input double RiskPercent      = 1.0;    // Risk per trade as % of balance
input double ATR_SL_Mult      = 1.5;    // ATR multiplier for stop loss
input double ATR_TP_Mult      = 2.5;    // ATR multiplier for take profit
input double TrailActivation  = 1.0;    // ATR multiplier to activate trailing stop
input bool   CloseAtNYClose   = true;   // Close all positions at NY session end
input int    NYCloseHour      = 21;     // NY close hour in broker server time (UTC)
input ulong  MagicNumber      = 20260612;

//--- global handles
int g_handleFastEMA  = INVALID_HANDLE;
int g_handleSlowEMA  = INVALID_HANDLE;
int g_handleATR      = INVALID_HANDLE;
int g_handleATR_MA   = INVALID_HANDLE;
int g_handleRSI      = INVALID_HANDLE;

//+------------------------------------------------------------------+
//| Expert initialisation                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_handleFastEMA = iMA(_Symbol, PERIOD_M5, FastEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
   g_handleSlowEMA = iMA(_Symbol, PERIOD_M5, SlowEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
   g_handleATR     = iATR(_Symbol, PERIOD_M5, ATR_Period);
   g_handleATR_MA  = iMA(_Symbol, PERIOD_M5, ATR_MA_Period, 0, MODE_SMA, PRICE_CLOSE);
   g_handleRSI     = iRSI(_Symbol, PERIOD_M5, RSI_Period, PRICE_CLOSE);

   if(g_handleFastEMA == INVALID_HANDLE || g_handleSlowEMA == INVALID_HANDLE ||
      g_handleATR     == INVALID_HANDLE || g_handleATR_MA  == INVALID_HANDLE ||
      g_handleRSI     == INVALID_HANDLE)
     {
      Print("Failed to create indicator handles. EA will not trade.");
      return INIT_FAILED;
     }

   Print("BTCUSD Scalping EA initialised. Magic=", MagicNumber);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_handleFastEMA != INVALID_HANDLE) IndicatorRelease(g_handleFastEMA);
   if(g_handleSlowEMA != INVALID_HANDLE) IndicatorRelease(g_handleSlowEMA);
   if(g_handleATR     != INVALID_HANDLE) IndicatorRelease(g_handleATR);
   if(g_handleATR_MA  != INVALID_HANDLE) IndicatorRelease(g_handleATR_MA);
   if(g_handleRSI     != INVALID_HANDLE) IndicatorRelease(g_handleRSI);
   Print("BTCUSD Scalping EA deinitialised. Reason=", reason);
  }

//+------------------------------------------------------------------+
//| Helper: copy one value from a buffer                             |
//+------------------------------------------------------------------+
double GetValue(int handle, int shift = 0)
  {
   double buf[];
   ArraySetAsSeries(buf, true);
   if(CopyBuffer(handle, 0, shift, 1, buf) <= 0) return 0.0;
   return buf[0];
  }

//+------------------------------------------------------------------+
//| Helper: count open positions for this EA                         |
//+------------------------------------------------------------------+
int CountPositions(ENUM_POSITION_TYPE posType)
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC)  == (long)MagicNumber &&
         PositionGetInteger(POSITION_TYPE)   == posType)
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Helper: calculate lot size based on risk %                       |
//+------------------------------------------------------------------+
double CalcLots(double slPoints)
  {
   if(slPoints <= 0) return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double tickVal  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double balance  = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmt  = balance * RiskPercent / 100.0;
   double lots     = riskAmt / (slPoints / tickSize * tickVal);
   double minLot   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double stepLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   lots = MathFloor(lots / stepLot) * stepLot;
   return MathMax(minLot, MathMin(maxLot, lots));
  }

//+------------------------------------------------------------------+
//| Helper: send market order                                        |
//+------------------------------------------------------------------+
bool SendOrder(ENUM_ORDER_TYPE orderType, double lots, double sl, double tp)
  {
   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   req.volume    = lots;
   req.type      = orderType;
   req.price     = (orderType == ORDER_TYPE_BUY)
                   ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                   : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   req.sl        = sl;
   req.tp        = tp;
   req.magic     = MagicNumber;
   req.comment   = "BTCScalper";
   req.deviation = 50;
   req.type_filling = ORDER_FILLING_IOC;

   if(!OrderSend(req, res))
     {
      Print("OrderSend failed. Retcode=", res.retcode, " Comment=", res.comment);
      return false;
     }
   Print("Order placed. Ticket=", res.order, " Type=", EnumToString(orderType),
         " Lots=", lots, " SL=", sl, " TP=", tp);
   return true;
  }

//+------------------------------------------------------------------+
//| Helper: manage trailing stop for open positions                  |
//+------------------------------------------------------------------+
void ManageTrailingStop(double atrValue)
  {
   double trailDist = atrValue * TrailActivation;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC)  != (long)MagicNumber) continue;

      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentSL = PositionGetDouble(POSITION_SL);
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      ENUM_POSITION_TYPE pType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      double newSL = currentSL;
      if(pType == POSITION_TYPE_BUY && bid - openPrice >= trailDist)
        {
         newSL = bid - trailDist;
         if(newSL <= currentSL) continue;
        }
      else if(pType == POSITION_TYPE_SELL && openPrice - ask >= trailDist)
        {
         newSL = ask + trailDist;
         if(newSL >= currentSL && currentSL > 0) continue;
        }
      else continue;

      MqlTradeRequest req = {};
      MqlTradeResult  res = {};
      req.action   = TRADE_ACTION_SLTP;
      req.position = ticket;
      req.symbol   = _Symbol;
      req.sl       = newSL;
      req.tp       = PositionGetDouble(POSITION_TP);
      OrderSend(req, res);
     }
  }

//+------------------------------------------------------------------+
//| Helper: close all positions at NY session end                    |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC)  != (long)MagicNumber) continue;

      MqlTradeRequest req = {};
      MqlTradeResult  res = {};
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = _Symbol;
      req.position  = ticket;
      req.volume    = PositionGetDouble(POSITION_VOLUME);
      req.magic     = MagicNumber;
      req.deviation = 50;
      req.type_filling = ORDER_FILLING_IOC;
      ENUM_POSITION_TYPE pType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      req.type  = (pType == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      req.price = (req.type == ORDER_TYPE_SELL)
                  ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                  : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      OrderSend(req, res);
     }
  }

//+------------------------------------------------------------------+
//| Main tick handler                                                |
//+------------------------------------------------------------------+
void OnTick()
  {
   // --- NY session close logic ---
   if(CloseAtNYClose)
     {
      MqlDateTime dt;
      TimeToStruct(TimeCurrent(), dt);
      if(dt.hour == NYCloseHour && dt.min == 0)
        {
         CloseAllPositions();
         return;
        }
     }

   // --- Only act on new M5 bar ---
   static datetime lastBar = 0;
   datetime currentBar = iTime(_Symbol, PERIOD_M5, 0);
   if(currentBar == lastBar) return;
   lastBar = currentBar;

   // --- Read indicator values (shift 1 = last completed bar) ---
   double fastEMA_cur  = GetValue(g_handleFastEMA, 1);
   double fastEMA_prev = GetValue(g_handleFastEMA, 2);
   double slowEMA_cur  = GetValue(g_handleSlowEMA, 1);
   double slowEMA_prev = GetValue(g_handleSlowEMA, 2);
   double atr          = GetValue(g_handleATR, 1);
   double atrMA        = GetValue(g_handleATR_MA, 1);
   double rsi          = GetValue(g_handleRSI, 1);

   if(atr == 0.0 || atrMA == 0.0) return;

   // --- Manage trailing stop on every bar ---
   ManageTrailingStop(atr);

   // --- Volatility filter: ATR must exceed its MA ---
   bool volatilityOK = (atr > atrMA);

   // --- Crossover signals ---
   bool bullCross = (fastEMA_prev <= slowEMA_prev) && (fastEMA_cur > slowEMA_cur);
   bool bearCross = (fastEMA_prev >= slowEMA_prev) && (fastEMA_cur < slowEMA_cur);

   // --- Entry: long ---
   if(bullCross && volatilityOK && rsi < RSI_Overbought &&
      CountPositions(POSITION_TYPE_BUY) == 0)
     {
      double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double sl    = ask - atr * ATR_SL_Mult;
      double tp    = ask + atr * ATR_TP_Mult;
      double lots  = CalcLots(atr * ATR_SL_Mult);
      SendOrder(ORDER_TYPE_BUY, lots, sl, tp);
     }

   // --- Entry: short ---
   if(bearCross && volatilityOK && rsi > RSI_Oversold &&
      CountPositions(POSITION_TYPE_SELL) == 0)
     {
      double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double sl    = bid + atr * ATR_SL_Mult;
      double tp    = bid - atr * ATR_TP_Mult;
      double lots  = CalcLots(atr * ATR_SL_Mult);
      SendOrder(ORDER_TYPE_SELL, lots, sl, tp);
     }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor, save it in the MQL5/Experts folder, and compile with F7.

Generate a custom BTCUSD scalping EA

Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-like, more powerful

The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.

Frequently Asked Questions

Related MQL5 pages

Risk and testing note

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.