Hybrid Scalper Strategy
Обзор
Hybrid Scalper Strategy — это алгоритм краткосрочной торговли, конвертированный из MQL4-скрипта hybrid_Scalper.mq4. Он построен на высокоуровневом API StockSharp и рассчитан на работу на таймфрейме 1 минута. Стратегия комбинирует несколько технических индикаторов, чтобы находить быстрые точки входа и избегать периодов слишком низкой или чрезмерной волатильности.
Логика стратегии
- Фильтр тренда – быстрая EMA (21) и медленная EMA (89) определяют направление рынка. Покупки разрешены, если быстрая EMA выше медленной, продажи — при обратном условии.
- Фильтр импульса – стохастический осциллятор (5,3,3) формирует сигналы. Покупка выполняется, когда %K ниже 20 и ниже %D. Продажа выполняется, когда %K выше 80 и остаётся выше %D.
- Подтверждение RSI – индекс относительной силы с периодом 7 подтверждает импульс. Для входа в лонг RSI должен быть ниже 25, для входа в шорт – выше 85.
- Фильтр волатильности – полосы Боллинджера (50, отклонение 4) оценивают ширину рынка. Сделки открываются только при ширине между верхней и нижней полосами от 0.00045 до 0.00262, что исключает как слишком тихие, так и нестабильные периоды.
- Торговые дни – параметры позволяют отдельно включать или отключать торговлю для каждого дня недели (понедельник–пятница).
Параметры
| Имя | Описание |
|---|---|
RsiPeriod |
Период индикатора RSI. |
EmaFastPeriod |
Период быстрой EMA для определения тренда. |
EmaSlowPeriod |
Период медленной EMA для определения тренда. |
BbPeriod |
Период полос Боллинджера. |
BbDeviation |
Множитель отклонения для полос Боллинджера. |
TradeMonday–TradeFriday |
Разрешение торговли в конкретные дни недели. |
CandleType |
Тип/таймфрейм свечей, по умолчанию 1 минута. |
Примечания
- Стратегия использует высокоуровневый API
BindExдля подключения нескольких индикаторов в одной подписке. - В методе
OnStartedвызываетсяStartProtection()для включения встроенной защиты позиций. - Все комментарии в коде написаны на английском языке в соответствии с требованиями репозитория.
Запуск
- Добавьте файл стратегии в проект StockSharp.
- Настройте подключение к рыночным данным и торговому шлюзу.
- Скомпилируйте и запустите стратегию, убедившись, что выбранный инструмент предоставляет свечи с таймфреймом 1 минута.
- При необходимости скорректируйте параметры через интерфейс
StrategyParam.
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()