在 GitHub 上查看

Fibonacci 与布林带策略

仅做多的均值回归策略,结合布林带与前一波段低点下方的斐波那契扩展。

默认值:BollingerPeriod = 20、BollingerDeviation = 2、FibLookback = 50、FibonacciLevel = 0.618、CandleType = 1分钟。只使用已完成 K 线。布林带按含信号 K 线的最近20个收盘价和总体标准差计算。波段为之前50根已完成 K 线的最高 High 和最低 Low,不含信号 K 线。

向下扩展为 F = previousLow − FibonacciLevel × (previousHigh − previousLow)。支持大于1的正系数,例如1.618。空仓时严格满足 Close < lowerBand 且 Close > F 才买入。当 Close >= upperBand 或 Close >= previousHigh 时平掉全部多仓。等于 F 不入场。不做空、不设止损单、不使用未来 K 线。

using System;
using System.Collections.Generic;
using System.Linq;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Long-only mean reversion: below the lower Bollinger band while above the
/// preceding swing's Fibonacci price, with exits at the upper band or prior high.
/// </summary>
public class FibonacciBollingerBandsStrategy : Strategy
{
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<int> _fibLookback;
	private readonly StrategyParam<decimal> _fibonacciLevel;
	private readonly StrategyParam<DataType> _candleType;
	private readonly Queue<decimal> _closes = new();
	private readonly Queue<decimal> _previousHighs = new();
	private readonly Queue<decimal> _previousLows = new();
	private Order _pendingOrder;

	public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
	public decimal BollingerDeviation { get => _bollingerDeviation.Value; set => _bollingerDeviation.Value = value; }
	public int FibLookback { get => _fibLookback.Value; set => _fibLookback.Value = value; }
	public decimal FibonacciLevel { get => _fibonacciLevel.Value; set => _fibonacciLevel.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FibonacciBollingerBandsStrategy()
	{
		_bollingerPeriod = Param(nameof(BollingerPeriod), 20).SetGreaterThanZero()
			.SetDisplay("Bollinger Period", "Number of completed closes for band calculation.", "Bollinger");
		_bollingerDeviation = Param(nameof(BollingerDeviation), 2m).SetGreaterThanZero()
			.SetDisplay("Bollinger Deviation", "Number of population standard deviations.", "Bollinger");
		_fibLookback = Param(nameof(FibLookback), 50).SetGreaterThanZero()
			.SetDisplay("Fib Lookback", "Number of previous candles used for the swing range.", "Fibonacci");
		_fibonacciLevel = Param(nameof(FibonacciLevel), 0.618m).SetGreaterThanZero()
			.SetDisplay("Fibonacci Level", "Fraction of preceding swing range projected below its low.", "Fibonacci");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe.", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_closes.Clear();
		_previousHighs.Clear();
		_previousLows.Clear();
		_pendingOrder = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		OrderRegistering += order => _pendingOrder = order;
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

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

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

		_closes.Enqueue(candle.ClosePrice);
		while (_closes.Count > BollingerPeriod) _closes.Dequeue();

		var complete = _closes.Count == BollingerPeriod
			&& _previousHighs.Count == FibLookback && _previousLows.Count == FibLookback;
		var pendingActive = _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
		if (complete && !pendingActive)
		{
			var mean = _closes.Average();
			var variance = _closes.Sum(price => (price - mean) * (price - mean)) / BollingerPeriod;
			var offset = (decimal)Math.Sqrt((double)variance) * BollingerDeviation;
			var upper = mean + offset;
			var lower = mean - offset;
			var swingHigh = _previousHighs.Max();
			var swingLow = _previousLows.Min();
			var fib = swingLow - (swingHigh - swingLow) * FibonacciLevel;

			if (Position > 0m && (candle.ClosePrice >= upper || candle.ClosePrice >= swingHigh))
				SellMarket(Position);
			else if (Position == 0m && candle.ClosePrice < lower && candle.ClosePrice > fib)
				BuyMarket(Volume);
		}

		// The signal candle never contributes to the swing extremes used for itself.
		_previousHighs.Enqueue(candle.HighPrice);
		_previousLows.Enqueue(candle.LowPrice);
		while (_previousHighs.Count > FibLookback) _previousHighs.Dequeue();
		while (_previousLows.Count > FibLookback) _previousLows.Dequeue();
	}
}