在 GitHub 上查看

布林带会话反转策略

该策略是 MetaTrader 专家顾问 BollingerBandsEA (ver. 3.0) 的 C# 版本。它在交易时段内捕捉价格突破布林带后的均值回归机会。

交易逻辑

  1. 订阅主图的日内 K 线(默认 15 分钟)以及用于趋势过滤的日线数据。
  2. 在日内数据上计算布林带(周期 20,倍数 2.0),并在日线上计算 100 周期简单移动平均线。
  3. 实时记录当日和前一日的最高价/最低价,并保存上一根柱子的布林带数值。
  4. 仅在交易时段窗口内开仓:从开盘后的 SessionStartOffsetMinutes 分钟开始,到收盘前 SessionEndOffsetMinutes 分钟结束。
  5. 当日收益(含浮动盈亏)转为正值后停止交易,模拟 EA 的日盈利保护逻辑。
  6. 做空条件:上一根 K 线收阴、收盘价在上轨之上、当前收盘价仍在上轨之上、带宽足够大、价格位于日线均线下方,并且价格突破当日或上一日高点。
  7. 做多条件:上一根 K 线收阳、收盘价在下轨之下、当前收盘价仍在下轨之下、带宽足够大、价格位于日线均线上方,并且价格跌破当日或上一日低点。
  8. 仓位大小可采用固定手数,或根据止损距离(点数)计算的风险百分比手数。
  9. 平仓逻辑包含:止损、止盈、可选的中轨反向离场、追踪止损、保本调整,以及在亏损持续超过设定时间后强制离场。

参数说明

参数 含义
CandleType 用于计算的日内 K 线类型。
BollingerLength 布林带移动平均线周期。
BollingerWidth 布林带宽度倍数。
DailyMaLength 日线趋势过滤 SMA 的周期。
StopLossPoints 止损距离(点)。
UseRiskVolume 是否启用风险百分比手数。
RiskPercent 单笔交易承担的账户风险百分比。
FixedVolume 未启用风险手数时使用的固定手数。
SessionStartOffsetMinutes 开盘后允许开仓的延迟分钟数。
SessionEndOffsetMinutes 收盘前禁止开仓的提前分钟数。
CloseOnMiddleBand 价格触及中轨时是否平仓。
EnableTrailing 是否启用追踪止损。
TrailingFactor 激活追踪止损所需的盈利倍数。
EnableBreakEven 是否启用保本止损。
BreakEvenFactor 将止损移动到入场价所需的盈利倍数。
CloseLosingAfterMinutes 亏损持仓持续多少分钟后强制离场。

注意事项

  • 策略在每根 K 线结束时通过最高价/最低价模拟止损与止盈。如需交易所端保护单,可自行扩展相关逻辑。
  • 风险手数依赖于 Security.StepSecurity.StepPrice。当市场资料缺失时会退回到固定手数。
  • 日内收益限制基于策略 PnL,确保投资组合的盈亏计算单位与风险参数一致。
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>
/// Trades reversals from Bollinger Bands.
/// Buys when price closes below lower band and sells when price closes above upper band.
/// </summary>
public class BollingerBandsSessionReversalStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bollingerLength;
	private readonly StrategyParam<decimal> _bollingerWidth;

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

	/// <summary>
	/// Length of the Bollinger Bands moving average.
	/// </summary>
	public int BollingerLength
	{
		get => _bollingerLength.Value;
		set => _bollingerLength.Value = value;
	}

	/// <summary>
	/// Width multiplier for Bollinger Bands.
	/// </summary>
	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public BollingerBandsSessionReversalStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series", "General");

		_bollingerLength = Param(nameof(BollingerLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Length", "MA period for Bollinger Bands", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Width", "Band width multiplier", "Indicators");
	}

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

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

		var bollinger = new BollingerBands
		{
			Length = BollingerLength,
			Width = BollingerWidth
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bollinger, ProcessCandle)
			.Start();

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

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

		if (bbValue is not IBollingerBandsValue bb)
			return;

		var middle = bb.MovingAverage ?? 0m;
		var upper = bb.UpBand ?? 0m;
		var lower = bb.LowBand ?? 0m;

		if (middle == 0m || upper == 0m || lower == 0m)
			return;

		var price = candle.ClosePrice;

		// Reversal: buy when price falls below lower band
		if (price < lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Reversal: sell when price rises above upper band
		else if (price > upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit at middle band
		else if (Position > 0 && price >= middle)
		{
			SellMarket();
		}
		else if (Position < 0 && price <= middle)
		{
			BuyMarket();
		}
	}
}