在 GitHub 上查看

Bollinger Band Two MA ZigZag 策略

该混合趋势策略结合了布林带反转、两个高周期均线以及 ZigZag 枢轴点。每次出现信号时会开启两笔仓位:第一笔按照计算出的止盈目标离场,第二笔作为“趋势持仓”依靠保本与移动止损进行管理。

细节

  • 入场条件
    • 做多:上一根 K 线在此前两根跌破下轨后重新收在前一根下轨之上,当前收盘价也高于该下轨,同时价格位于两个高周期均线上方。
    • 做空:上一根 K 线在此前两根突破上轨后重新收在前一根上轨之下,当前收盘价也低于该上轨,同时价格位于两个高周期均线下方。
  • 仓位管理
    • 每次信号都会以 First Volume(带止盈)和 Second Volume(趋势持仓)两个独立手数开仓。
    • 止损基于最近一次 ZigZag 枢轴点并加减 Pivot Offset (pts)
    • 启用保本功能时,当浮盈达到 Break-even Threshold (pts) + Break-even Offset (pts) 时,止损移动到入场价加上 Break-even Offset (pts)
    • 当价格相对当前止损继续运行 Trailing Step (pts) 时,移动止损保持与价格的距离为 Trailing Stop (pts)
  • 止盈处理
    • 第一笔仓位的止盈目标按入场与止损距离的百分比 (Take Profit %) 计算。
    • 趋势持仓没有固定止盈,只通过止损、移动止损或反向信号退出。
  • 额外逻辑
    • 反向信号会在建新仓前立即平掉反向持仓。
    • 仅使用收盘完成的 K 线数据,未完成的蜡烛不会触发信号。
  • 默认参数
    • First Volume = 0.1
    • Second Volume = 0.1
    • Take Profit % = 50
    • Pivot Offset (pts) = 10
    • Use Break-even Move = true
    • Break-even Offset (pts) = 80
    • Break-even Threshold (pts) = 10
    • Trailing Stop (pts) = 80
    • Trailing Step (pts) = 120
    • Bollinger Period = 20
    • Bollinger Width = 2
    • Base Candle = 1 小时
    • MA1 Candle = 日线
    • MA2 Candle = 4 小时
    • MA1 Period = 20
    • MA2 Period = 20
    • ZigZag Depth = 12
    • ZigZag Deviation (pts) = 5
    • ZigZag Backstep = 3
  • 过滤条件
    • 分类:趋势跟随
    • 方向:双向
    • 指标:Bollinger Bands、移动平均、ZigZag
    • 止损:有(枢轴止损、保本、移动止损)
    • 复杂度:高级
    • 周期:多周期(基础 1 小时,过滤日线与 4 小时)
    • 季节性:无
    • 神经网络:无
    • 背离:无
    • 风险等级:中等

说明

  • 策略需要同时订阅三个不同周期的蜡烛数据,用于信号过滤与仓位管理。
    • ZigZag 枢轴检测通过限制深度、偏差与最小间隔来模拟 MetaTrader 的实现。
  • 两笔仓位的手数可以单独调整,用于平衡固定止盈腿与趋势持仓腿的规模。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bollinger Band Two MA ZigZag strategy (simplified). Uses EMA crossover
/// with Highest/Lowest channel for swing-based entries.
/// </summary>
public class BollingerBandTwoMaZigZagStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaFastLength;
	private readonly StrategyParam<int> _emaSlowLength;
	private readonly StrategyParam<int> _channelLength;

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

	public int EmaFastLength
	{
		get => _emaFastLength.Value;
		set => _emaFastLength.Value = value;
	}

	public int EmaSlowLength
	{
		get => _emaSlowLength.Value;
		set => _emaSlowLength.Value = value;
	}

	public int ChannelLength
	{
		get => _channelLength.Value;
		set => _channelLength.Value = value;
	}

	public BollingerBandTwoMaZigZagStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");

		_emaFastLength = Param(nameof(EmaFastLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("EMA Fast", "Fast EMA period", "Indicators");

		_emaSlowLength = Param(nameof(EmaSlowLength), 30)
			.SetGreaterThanZero()
			.SetDisplay("EMA Slow", "Slow EMA period", "Indicators");

		_channelLength = Param(nameof(ChannelLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("Channel Length", "Highest/Lowest lookback", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
		var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };

		decimal prevFast = 0, prevSlow = 0;
		var hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(emaFast, emaSlow, (ICandleMessage candle, decimal fastVal, decimal slowVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevFast = fastVal;
					prevSlow = slowVal;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevFast = fastVal;
					prevSlow = slowVal;
					return;
				}

				var close = candle.ClosePrice;

				// EMA crossover combined with price confirmation
				var bullishCross = prevFast <= prevSlow && fastVal > slowVal;
				var bearishCross = prevFast >= prevSlow && fastVal < slowVal;

				if (bullishCross && Position <= 0 && close > fastVal)
					BuyMarket();
				else if (bearishCross && Position >= 0 && close < fastVal)
					SellMarket();

				prevFast = fastVal;
				prevSlow = slowVal;
			})
			.Start();

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