在 GitHub 上查看

BBStrategy 策略

概览

BBStrategy 是从 MetaTrader 专家顾问 "BBStrategy" 移植而来的布林带突破系统。策略同时追踪两组布林带,周期相同但偏差倍数不同。当价格突破外侧布林带时,算法先进入等待状态,只有在价格回到内侧布林带并确认趋势延续时才真正入场,以避免在极端延伸的价位盲目追单。

核心逻辑

  1. 订阅蜡烛数据并计算两组布林带:
    • 外侧布林带 使用可配置的偏差倍数(默认 3.0)。
    • 内侧布林带 使用较小的偏差倍数(默认 2.0)。
  2. 当收盘价位于外侧布林带之外时记录信号:
    • 收在上轨之上触发做多等待。
    • 收在下轨之下触发做空等待。
  3. 只有当下一根完成的蜡烛在对应方向上重新收回到内侧布林带时才执行交易。在等待期间策略保持对应方向的待机状态。
  4. 条件满足且没有持仓或活动订单时,提交单笔市价单;如有反向仓位,则通过增加市价单数量先行平仓。
  5. 可选的止损与止盈距离以点数输入,并通过内置保护模块自动转换为绝对价格。

参数

名称 说明
Order Volume 每笔交易的下单数量。
Bollinger Period 两组布林带的计算周期。
Inner Deviation 内侧布林带的偏差倍数。
Outer Deviation 外侧布林带的偏差倍数。
Stop-Loss Points 止损点数(0 表示禁用)。
Take-Profit Points 止盈点数(0 表示禁用)。
Candle Type 用于计算的蜡烛类型/周期。

说明

  • 策略一次只持有一个方向的仓位,并在有活动订单时忽略新的信号。
  • 点数会根据交易品种的最小报价步长转换为实际价格距离。
  • 图表上会绘制蜡烛、两组布林带以及策略成交,方便分析与调试。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bollinger Bands breakout strategy.
/// When price breaks above outer band, waits for re-entry into inner band then goes long.
/// When price breaks below outer band, waits for re-entry into inner band then goes short.
/// </summary>
public class BBStrategy : Strategy
{
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _innerDeviation;
	private readonly StrategyParam<decimal> _outerDeviation;
	private readonly StrategyParam<DataType> _candleType;

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal InnerDeviation
	{
		get => _innerDeviation.Value;
		set => _innerDeviation.Value = value;
	}

	public decimal OuterDeviation
	{
		get => _outerDeviation.Value;
		set => _outerDeviation.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public BBStrategy()
	{
		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");

		_innerDeviation = Param(nameof(InnerDeviation), 2m)
			.SetDisplay("Inner Dev", "Inner band deviations", "Indicators");

		_outerDeviation = Param(nameof(OuterDeviation), 3m)
			.SetDisplay("Outer Dev", "Outer band deviations", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle series", "General");
	}

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

		var innerBand = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = InnerDeviation
		};

		var outerBand = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = OuterDeviation
		};

		var waitDirection = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(innerBand, outerBand, (candle, innerVal, outerVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!innerBand.IsFormed || !outerBand.IsFormed)
					return;

				if (innerVal.IsEmpty || outerVal.IsEmpty)
					return;

				var innerBb = innerVal as IBollingerBandsValue;
				var outerBb = outerVal as IBollingerBandsValue;

				if (innerBb == null || outerBb == null)
					return;

				var innerUpper = innerBb.UpBand ?? 0;
				var innerLower = innerBb.LowBand ?? 0;
				var outerUpper = outerBb.UpBand ?? 0;
				var outerLower = outerBb.LowBand ?? 0;

				if (innerUpper == 0 || innerLower == 0 || outerUpper == 0 || outerLower == 0)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var price = candle.ClosePrice;
				var signal = 0;

				// Detect outer band breakout
				if (price > outerUpper)
					waitDirection = 1;
				else if (price < outerLower)
					waitDirection = -1;

				// Check re-entry into inner band
				if (waitDirection > 0 && price < innerUpper && price > innerLower)
				{
					signal = 1;
					waitDirection = 0;
				}
				else if (waitDirection < 0 && price > innerLower && price < innerUpper)
				{
					signal = -1;
					waitDirection = 0;
				}

				if (signal == 1 && Position <= 0)
					BuyMarket();
				else if (signal == -1 && Position >= 0)
					SellMarket();
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, innerBand);
			DrawIndicator(area, outerBand);
			DrawOwnTrades(area);
		}
	}
}