在 GitHub 上查看

Breakthrough BB 策略

概述

Breakthrough BB 策略将 MetaTrader 专家顾问 Breakthrough_BB 迁移到 StockSharp 高级 API。系统结合布林带与简单移动平均线(SMA),用于捕捉价格在通道边缘压缩后的突破行情。所有信号均基于已完成的 K 线生成,从而忠实再现原始 MQL5 逻辑。

交易逻辑

  • 趋势过滤器: 使用可配置周期的 SMA 来判断趋势方向。策略比较当前 SMA 数值与四根 K 线前的 SMA 数值。数值上升时允许做多,下降时允许做空。
  • 布林带突破: 策略关注四根 K 线前的收盘价与当前布林带上下轨的关系。如果价格从轨道内部移动到轨道外部,则视为有效突破。
  • 单一持仓模式: 系统任意时刻仅持有一笔仓位。若已有持仓,会先按照离场规则平仓后再评估新的进场机会。

入场条件

做多

  1. 四根 K 线前的收盘价低于当时的布林带上轨。
  2. 最新收盘价高于当前布林带上轨。
  3. 最新 SMA 数值大于四根 K 线前的 SMA 数值(趋势向上)。
  4. 当前无持仓。

做空

  1. 四根 K 线前的收盘价高于当时的布林带下轨。
  2. 最新收盘价低于当前布林带下轨。
  3. 最新 SMA 数值小于四根 K 线前的 SMA 数值(趋势向下)。
  4. 当前无持仓。

当满足相应条件时,策略会按照参数 Volume 定义的数量发送市价单。

离场规则

  • 多头离场: 若持有多单且最新收盘价跌破布林带中轨,则立即以市价卖出平仓。
  • 空头离场: 若持有空单且最新收盘价突破布林带中轨,则立即以市价买入回补。

上述规则与原始 MQL5 程序保持一致,确保价格重新回到中轨附近时及时退出。

指标

  • 简单移动平均线 (SMA): 提供趋势判断,并用于比较四根 K 线之间的斜率变化。
  • 布林带: 提供上轨、中轨、下轨,既用于识别突破,也用于确定出场信号。

参数

名称 说明 默认值 可优化
MaPeriod 用于趋势过滤的 SMA 周期。 9
BandsPeriod 布林带的计算周期。 28
Deviation 布林带的标准差倍数。 1.6
Volume 每次下单的数量(按合约或手数)。 1
CandleType 策略使用的 K 线类型。 1 小时时间框架

所有参数均通过 StrategyParam 暴露,便于在界面中调整或在优化器中批量搜索。

数据要求

  • 适用于任何能够提供所选 CandleType K 线数据的市场品种。
  • 仅在 K 线收盘后评估信号,忽略未完成的 K 线以保证确定性。
  • 默认使用 1 小时 K 线,可根据数据源改为其他周期。

其他说明

  • 策略未直接访问指标历史,而是维护了一个四根 K 线的滚动缓冲区来保存收盘价和 SMA,用于满足项目关于高阶 API 的要求。
  • 原版程序未设置止损或止盈,因此这里也未启用。如需风险控制,可进一步调用 StartProtection
  • 策略使用市价单执行,建议选择流动性充足的品种以降低滑点风险。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that trades Bollinger Bands breakouts with a moving average trend filter.
/// </summary>
public class BreakthroughBbStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private BollingerBands _bollingerBands;

	private decimal? _closeLag0;
	private decimal? _closeLag1;
	private decimal? _closeLag2;
	private decimal? _closeLag3;

	private decimal? _maLag0;
	private decimal? _maLag1;
	private decimal? _maLag2;
	private decimal? _maLag3;

	/// <summary>
	/// Moving average period that defines the long term trend.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands lookback period.
	/// </summary>
	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands width measured in standard deviations.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}


	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="BreakthroughBbStrategy"/>.
	/// </summary>
	public BreakthroughBbStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Simple moving average length", "Parameters")
			;

		_bandsPeriod = Param(nameof(BandsPeriod), 28)
			.SetGreaterThanZero()
			.SetDisplay("Bands Period", "Bollinger Bands lookback", "Parameters")
			;

		_deviation = Param(nameof(Deviation), 1.6m)
			.SetGreaterThanZero()
			.SetDisplay("Deviation", "Bollinger Bands width in deviations", "Parameters")
			;


		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series processed by the strategy", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_sma = default;
		_bollingerBands = default;

		_closeLag0 = null;
		_closeLag1 = null;
		_closeLag2 = null;
		_closeLag3 = null;

		_maLag0 = null;
		_maLag1 = null;
		_maLag2 = null;
		_maLag3 = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_sma = new SimpleMovingAverage { Length = MaPeriod };
		_bollingerBands = new BollingerBands
		{
			Length = BandsPeriod,
			Width = Deviation
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_sma, _bollingerBands, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaIndValue, IIndicatorValue bbValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var smaValue = smaIndValue.IsFormed ? smaIndValue.ToDecimal() : 0m;
		var bb = bbValue as IBollingerBandsValue;
		var middleBand = bb?.MovingAverage ?? 0m;
		var upperBand = bb?.UpBand ?? 0m;
		var lowerBand = bb?.LowBand ?? 0m;

		var close = candle.ClosePrice;
		var maPrev4 = _maLag2;
		var closePrev4 = _closeLag2;

		if (_sma is null || _bollingerBands is null)
		{
			UpdateHistory(close, smaValue);
			return;
		}

		if (!_sma.IsFormed || !_bollingerBands.IsFormed)
		{
			UpdateHistory(close, smaValue);
			return;
		}

		if (Position > 0 && close < middleBand)
		{
			SellMarket();
			UpdateHistory(close, smaValue);
			return;
		}

		if (Position < 0 && close > middleBand)
		{
			BuyMarket();
			UpdateHistory(close, smaValue);
			return;
		}

		if (maPrev4 is null || closePrev4 is null)
		{
			UpdateHistory(close, smaValue);
			return;
		}

		if (Position == 0)
		{
			if (closePrev4.Value < upperBand && close > upperBand && smaValue > maPrev4.Value)
			{
				BuyMarket();
				UpdateHistory(close, smaValue);
				return;
			}

			if (closePrev4.Value > lowerBand && close < lowerBand && smaValue < maPrev4.Value)
			{
				SellMarket();
				UpdateHistory(close, smaValue);
				return;
			}
		}

		UpdateHistory(close, smaValue);
	}

	private void UpdateHistory(decimal close, decimal maValue)
	{
		_maLag3 = _maLag2;
		_maLag2 = _maLag1;
		_maLag1 = _maLag0;
		_maLag0 = maValue;

		_closeLag3 = _closeLag2;
		_closeLag2 = _closeLag1;
		_closeLag1 = _closeLag0;
		_closeLag0 = close;
	}
}