Hybrid Scalper Strategy
Overview
The Hybrid Scalper Strategy is a short-term trading algorithm converted from the MQL4 script hybrid_Scalper.mq4. It operates on the StockSharp high-level API and is designed for the 1-minute timeframe. The strategy combines multiple technical indicators to identify fast breakout opportunities while avoiding periods of excessive or insufficient volatility.
Strategy Logic
- Trend Filter – A fast EMA (21) and a slow EMA (89) determine market direction. Long trades are allowed only when the fast EMA is above the slow EMA; short trades require the opposite.
- Momentum Filter – The Stochastic Oscillator (5,3,3) generates entry signals. A buy is triggered when %K is below 20 and below %D. A sell is triggered when %K is above 80 and still above %D.
- RSI Confirmation – The Relative Strength Index with period 7 confirms momentum. Long entries require RSI below 25, while short entries require RSI above 85.
- Volatility Filter – Bollinger Bands (50, deviation 4) measure current market width. The strategy trades only when the difference between the upper and lower bands is between 0.00045 and 0.00262, avoiding both quiet and unstable markets.
- Trading Days – Parameters allow enabling or disabling trading for each weekday individually (Monday–Friday).
Parameters
| Name | Description |
|---|---|
RsiPeriod |
Period of the RSI indicator. |
EmaFastPeriod |
Fast EMA period for trend detection. |
EmaSlowPeriod |
Slow EMA period for trend detection. |
BbPeriod |
Period used in Bollinger Bands. |
BbDeviation |
Deviation multiplier for Bollinger Bands. |
TradeMonday–TradeFriday |
Enable trading on specific weekdays. |
CandleType |
Candle type/timeframe, default is 1-minute candles. |
Notes
- The strategy uses the high-level
BindExAPI to connect multiple indicators in a single subscription. StartProtection()is invoked once on start to activate built-in position protection (no explicit stop-loss or take-profit parameters).- All comments in the code are provided in English in accordance with repository guidelines.
How to Run
- Add the strategy file to a StockSharp project.
- Configure the required market data and execution connectors.
- Compile and launch the strategy; ensure that the selected instrument provides 1-minute candles.
- Adjust parameters through the
StrategyParaminterface as needed.
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hybrid scalping strategy based on Stochastic Oscillator, RSI and Bollinger Bands.
/// </summary>
public class HybridScalperStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaFastPeriod;
private readonly StrategyParam<int> _emaSlowPeriod;
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bbDeviation;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<bool> _tradeMonday;
private readonly StrategyParam<bool> _tradeTuesday;
private readonly StrategyParam<bool> _tradeWednesday;
private readonly StrategyParam<bool> _tradeThursday;
private readonly StrategyParam<bool> _tradeFriday;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevStochK;
private decimal _prevStochD;
private bool _isInitialized;
private int _barsSinceTrade;
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Fast EMA period for trend detection.
/// </summary>
public int EmaFastPeriod
{
get => _emaFastPeriod.Value;
set => _emaFastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period for trend detection.
/// </summary>
public int EmaSlowPeriod
{
get => _emaSlowPeriod.Value;
set => _emaSlowPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BbPeriod
{
get => _bbPeriod.Value;
set => _bbPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands deviation.
/// </summary>
public decimal BbDeviation
{
get => _bbDeviation.Value;
set => _bbDeviation.Value = value;
}
/// <summary>
/// Bars to wait after a completed position.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Allow trading on Monday.
/// </summary>
public bool TradeMonday
{
get => _tradeMonday.Value;
set => _tradeMonday.Value = value;
}
/// <summary>
/// Allow trading on Tuesday.
/// </summary>
public bool TradeTuesday
{
get => _tradeTuesday.Value;
set => _tradeTuesday.Value = value;
}
/// <summary>
/// Allow trading on Wednesday.
/// </summary>
public bool TradeWednesday
{
get => _tradeWednesday.Value;
set => _tradeWednesday.Value = value;
}
/// <summary>
/// Allow trading on Thursday.
/// </summary>
public bool TradeThursday
{
get => _tradeThursday.Value;
set => _tradeThursday.Value = value;
}
/// <summary>
/// Allow trading on Friday.
/// </summary>
public bool TradeFriday
{
get => _tradeFriday.Value;
set => _tradeFriday.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HybridScalperStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 7)
.SetDisplay("RSI Period", "RSI calculation period", "Indicators");
_emaFastPeriod = Param(nameof(EmaFastPeriod), 21)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_emaSlowPeriod = Param(nameof(EmaSlowPeriod), 89)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_bbPeriod = Param(nameof(BbPeriod), 50)
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bbDeviation = Param(nameof(BbDeviation), 4m)
.SetDisplay("BB Deviation", "Bollinger Bands deviation", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 2)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
_tradeMonday = Param(nameof(TradeMonday), true)
.SetDisplay("Trade Monday", "Allow trading on Monday", "Schedule");
_tradeTuesday = Param(nameof(TradeTuesday), true)
.SetDisplay("Trade Tuesday", "Allow trading on Tuesday", "Schedule");
_tradeWednesday = Param(nameof(TradeWednesday), true)
.SetDisplay("Trade Wednesday", "Allow trading on Wednesday", "Schedule");
_tradeThursday = Param(nameof(TradeThursday), true)
.SetDisplay("Trade Thursday", "Allow trading on Thursday", "Schedule");
_tradeFriday = Param(nameof(TradeFriday), true)
.SetDisplay("Trade Friday", "Allow trading on Friday", "Schedule");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle type for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevStochK = 0m;
_prevStochD = 0m;
_isInitialized = false;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var stochastic = new StochasticOscillator();
var emaFast = new ExponentialMovingAverage { Length = EmaFastPeriod };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowPeriod };
var bollinger = new BollingerBands
{
Length = BbPeriod,
Width = BbDeviation,
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, stochastic, emaFast, emaSlow, bollinger, ProcessIndicators)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawIndicator(area, stochastic);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessIndicators(
ICandleMessage candle,
IIndicatorValue rsiValue,
IIndicatorValue stochValue,
IIndicatorValue emaFastValue,
IIndicatorValue emaSlowValue,
IIndicatorValue bollingerValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading() || !IsTradingDay(candle.OpenTime.DayOfWeek))
return;
var rsi = rsiValue.ToDecimal();
var stochastic = (StochasticOscillatorValue)stochValue;
if (stochastic.K is not decimal stochK || stochastic.D is not decimal stochD)
return;
var emaFast = emaFastValue.ToDecimal();
var emaSlow = emaSlowValue.ToDecimal();
var bands = (BollingerBandsValue)bollingerValue;
if (bands.UpBand is not decimal upperBand ||
bands.LowBand is not decimal lowerBand ||
bands.MovingAverage is not decimal middleBand ||
middleBand == 0m)
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
var relativeWidth = (upperBand - lowerBand) / middleBand;
if (!_isInitialized)
{
_prevStochK = stochK;
_prevStochD = stochD;
_isInitialized = true;
return;
}
var longSignal =
_prevStochK <= _prevStochD &&
stochK > stochD &&
stochK < 30m &&
rsi < 40m &&
emaFast > emaSlow &&
relativeWidth is >= 0.005m and <= 0.12m;
var shortSignal =
_prevStochK >= _prevStochD &&
stochK < stochD &&
stochK > 70m &&
rsi > 60m &&
emaFast < emaSlow &&
relativeWidth is >= 0.005m and <= 0.12m;
if (_barsSinceTrade >= CooldownBars)
{
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_prevStochK = stochK;
_prevStochD = stochD;
}
private bool IsTradingDay(DayOfWeek day)
{
return day switch
{
DayOfWeek.Monday => TradeMonday,
DayOfWeek.Tuesday => TradeTuesday,
DayOfWeek.Wednesday => TradeWednesday,
DayOfWeek.Thursday => TradeThursday,
DayOfWeek.Friday => TradeFriday,
_ => false,
};
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, DayOfWeek
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import (
RelativeStrengthIndex, StochasticOscillator,
ExponentialMovingAverage, BollingerBands,
)
from StockSharp.Algo.Strategies import Strategy
class hybrid_scalper_strategy(Strategy):
def __init__(self):
super(hybrid_scalper_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 7) \
.SetDisplay("RSI Period", "RSI calculation period", "Indicators")
self._ema_fast_period = self.Param("EmaFastPeriod", 21) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._ema_slow_period = self.Param("EmaSlowPeriod", 89) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._bb_period = self.Param("BbPeriod", 50) \
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._bb_deviation = self.Param("BbDeviation", 4.0) \
.SetDisplay("BB Deviation", "Bollinger Bands deviation", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 2) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._trade_monday = self.Param("TradeMonday", True) \
.SetDisplay("Trade Monday", "Allow trading on Monday", "Schedule")
self._trade_tuesday = self.Param("TradeTuesday", True) \
.SetDisplay("Trade Tuesday", "Allow trading on Tuesday", "Schedule")
self._trade_wednesday = self.Param("TradeWednesday", True) \
.SetDisplay("Trade Wednesday", "Allow trading on Wednesday", "Schedule")
self._trade_thursday = self.Param("TradeThursday", True) \
.SetDisplay("Trade Thursday", "Allow trading on Thursday", "Schedule")
self._trade_friday = self.Param("TradeFriday", True) \
.SetDisplay("Trade Friday", "Allow trading on Friday", "Schedule")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle type for the strategy", "General")
self._prev_stoch_k = 0.0
self._prev_stoch_d = 0.0
self._is_initialized = False
self._bars_since_trade = 0
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def EmaFastPeriod(self):
return self._ema_fast_period.Value
@EmaFastPeriod.setter
def EmaFastPeriod(self, value):
self._ema_fast_period.Value = value
@property
def EmaSlowPeriod(self):
return self._ema_slow_period.Value
@EmaSlowPeriod.setter
def EmaSlowPeriod(self, value):
self._ema_slow_period.Value = value
@property
def BbPeriod(self):
return self._bb_period.Value
@BbPeriod.setter
def BbPeriod(self, value):
self._bb_period.Value = value
@property
def BbDeviation(self):
return self._bb_deviation.Value
@BbDeviation.setter
def BbDeviation(self, value):
self._bb_deviation.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def TradeMonday(self):
return self._trade_monday.Value
@TradeMonday.setter
def TradeMonday(self, value):
self._trade_monday.Value = value
@property
def TradeTuesday(self):
return self._trade_tuesday.Value
@TradeTuesday.setter
def TradeTuesday(self, value):
self._trade_tuesday.Value = value
@property
def TradeWednesday(self):
return self._trade_wednesday.Value
@TradeWednesday.setter
def TradeWednesday(self, value):
self._trade_wednesday.Value = value
@property
def TradeThursday(self):
return self._trade_thursday.Value
@TradeThursday.setter
def TradeThursday(self, value):
self._trade_thursday.Value = value
@property
def TradeFriday(self):
return self._trade_friday.Value
@TradeFriday.setter
def TradeFriday(self, value):
self._trade_friday.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _is_trading_day(self, day):
if day == DayOfWeek.Monday:
return self.TradeMonday
elif day == DayOfWeek.Tuesday:
return self.TradeTuesday
elif day == DayOfWeek.Wednesday:
return self.TradeWednesday
elif day == DayOfWeek.Thursday:
return self.TradeThursday
elif day == DayOfWeek.Friday:
return self.TradeFriday
return False
def OnStarted2(self, time):
super(hybrid_scalper_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
stochastic = StochasticOscillator()
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self.EmaFastPeriod
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self.EmaSlowPeriod
bollinger = BollingerBands()
bollinger.Length = self.BbPeriod
bollinger.Width = self.BbDeviation
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.BindEx(rsi, stochastic, ema_fast, ema_slow, bollinger, self.ProcessIndicators) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawIndicator(area, stochastic)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def ProcessIndicators(self, candle, rsi_value, stoch_value, ema_fast_value,
ema_slow_value, bollinger_value):
if candle.State != CandleStates.Finished:
return
if not self._is_trading_day(candle.OpenTime.DayOfWeek):
return
rsi = float(rsi_value)
stoch_k_raw = stoch_value.K
stoch_d_raw = stoch_value.D
if stoch_k_raw is None or stoch_d_raw is None:
return
stoch_k = float(stoch_k_raw)
stoch_d = float(stoch_d_raw)
ema_fast = float(ema_fast_value)
ema_slow = float(ema_slow_value)
upper_raw = bollinger_value.UpBand
lower_raw = bollinger_value.LowBand
middle_raw = bollinger_value.MovingAverage
if upper_raw is None or lower_raw is None or middle_raw is None:
return
upper_band = float(upper_raw)
lower_band = float(lower_raw)
middle_band = float(middle_raw)
if middle_band == 0.0:
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
relative_width = (upper_band - lower_band) / middle_band
if not self._is_initialized:
self._prev_stoch_k = stoch_k
self._prev_stoch_d = stoch_d
self._is_initialized = True
return
long_signal = (self._prev_stoch_k <= self._prev_stoch_d
and stoch_k > stoch_d
and stoch_k < 30.0
and rsi < 40.0
and ema_fast > ema_slow
and 0.005 <= relative_width <= 0.12)
short_signal = (self._prev_stoch_k >= self._prev_stoch_d
and stoch_k < stoch_d
and stoch_k > 70.0
and rsi > 60.0
and ema_fast < ema_slow
and 0.005 <= relative_width <= 0.12)
pos = self.Position
if self._bars_since_trade >= self.CooldownBars:
if long_signal and pos <= 0:
self.BuyMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
elif short_signal and pos >= 0:
self.SellMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
self._prev_stoch_k = stoch_k
self._prev_stoch_d = stoch_d
def OnReseted(self):
super(hybrid_scalper_strategy, self).OnReseted()
self._prev_stoch_k = 0.0
self._prev_stoch_d = 0.0
self._is_initialized = False
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return hybrid_scalper_strategy()