Auf GitHub ansehen

Fibonacci- und Bollinger-Bands-Strategie

Long-Mean-Reversion mit Bollinger-Bändern und Fibonacci-Erweiterung unterhalb der vorherigen Handelsspanne.

Standardwerte: BollingerPeriod = 20, BollingerDeviation = 2, FibLookback = 50, FibonacciLevel = 0.618, CandleType = 1 Minute. Nur abgeschlossene Kerzen. Bollinger verwendet die letzten 20 Schlusskurse einschließlich Signalkerze und die Populationsstandardabweichung. Die Spanne stammt aus höchstem High und niedrigstem Low der vorherigen 50 abgeschlossenen Kerzen, ohne Signalkerze.

Erweiterung nach unten: F = previousLow − FibonacciLevel × (previousHigh − previousLow). Positive Koeffizienten über 1 wie 1.618 sind zulässig. Ohne Position strikt bei Close < lowerBand und Close > F kaufen. Long vollständig bei Close >= upperBand oder Close >= previousHigh schließen. Gleichheit mit F erlaubt keinen Einstieg. Keine Shorts, Stop-Aufträge oder zukünftigen Kerzen.

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();
	}
}