在 GitHub 上查看

Hybrid Scalper Strategy

概览

Hybrid Scalper Strategy 是从 MQL4 脚本 hybrid_Scalper.mq4 转换而来的短线交易算法。它基于 StockSharp 的高级 API 并默认运行在 1 分钟周期。该策略结合多种技术指标,用于捕捉快速突破机会,同时避开过低或过高的波动时期。

策略逻辑

  1. 趋势过滤 – 通过快 EMA(21) 和慢 EMA(89) 判断市场方向。只有当快 EMA 位于慢 EMA 之上时才允许做多;做空则需要快 EMA 低于慢 EMA。
  2. 动量过滤 – 使用随机指标 (5,3,3) 生成入场信号。当 %K 低于 20 且低于 %D 时触发买入;当 %K 高于 80 并高于 %D 时触发卖出。
  3. RSI 确认 – 周期为 7 的 RSI 用于确认动量。做多需要 RSI 低于 25;做空需要 RSI 高于 85。
  4. 波动率过滤 – 采用布林带 (50, 偏差 4) 测量市场宽度。只有当上下轨之间的距离介于 0.00045 与 0.00262 之间时才允许交易,从而避免过于平静或极端波动的阶段。
  5. 交易日控制 – 可分别设置周一至周五是否允许交易。

参数

名称 描述
RsiPeriod RSI 指标周期。
EmaFastPeriod 用于判定趋势的快 EMA 周期。
EmaSlowPeriod 用于判定趋势的慢 EMA 周期。
BbPeriod 布林带周期。
BbDeviation 布林带偏差倍数。
TradeMondayTradeFriday 是否在对应的工作日启用交易。
CandleType 蜡烛类型/时间框架,默认 1 分钟。

说明

  • 策略通过 BindEx 高级接口一次性绑定多个指标。
  • OnStarted 方法中调用 StartProtection() 以启用内置的仓位保护功能。
  • 代码中的所有注释均使用英文,符合仓库要求。

运行步骤

  1. 将策略文件添加到 StockSharp 项目中。
  2. 配置市场数据和交易连接。
  3. 编译并启动策略,确保所选证券提供 1 分钟的蜡烛数据。
  4. 可通过 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,
		};
	}
}