Auf GitHub ansehen

Figurelli Series Strategy

Overview

This strategy converts the MetaTrader5 expert "Exp_FigurelliSeries" to StockSharp. It uses a custom Figurelli Series indicator which measures the difference between the number of moving averages above and below the current price. Trading occurs once per day at a user-defined start time and all positions are closed at a stop time.

Indicator

The Figurelli Series indicator creates a chain of exponential moving averages starting from Start Period and increasing by Step for Total averages. For each bar it counts how many averages are above and below the close price. The indicator value is bids - asks where bids is the count of averages below price and asks is the count of averages above price.

Trading Rules

  • At Start Hour:Start Minute:
    • Buy if the indicator value is positive and there is no long position.
    • Sell if the indicator value is negative and there is no short position.
  • At or after Stop Hour:Stop Minute, any open position is closed.
  • Only finished candles of the selected Candle Type are used.

Parameters

  • StartPeriod – initial moving average period.
  • Step – period increment between averages.
  • Total – number of moving averages.
  • StartHour / StartMinute – time when entries may occur.
  • StopHour / StopMinute – time to exit all positions.
  • CandleType – candle type for calculations.
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>
/// Strategy based on the Figurelli Series indicator.
/// Opens positions at a specified start time when the indicator is positive or negative.
/// Closes all positions at the stop time.
/// </summary>
public class FigurelliSeriesStrategy : Strategy
{
	private readonly StrategyParam<int> _startPeriod;
	private readonly StrategyParam<int> _step;
	private readonly StrategyParam<int> _total;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _stopHour;
	private readonly StrategyParam<int> _stopMinute;
	private readonly StrategyParam<DataType> _candleType;

	private int _lastSign;

	/// <summary>Initial period for moving averages.</summary>
	public int StartPeriod { get => _startPeriod.Value; set => _startPeriod.Value = value; }
	/// <summary>Step between moving average periods.</summary>
	public int Step { get => _step.Value; set => _step.Value = value; }
	/// <summary>Number of moving averages.</summary>
	public int Total { get => _total.Value; set => _total.Value = value; }
	/// <summary>Hour to start trading.</summary>
	public int StartHour { get => _startHour.Value; set => _startHour.Value = value; }
	/// <summary>Minute to start trading.</summary>
	public int StartMinute { get => _startMinute.Value; set => _startMinute.Value = value; }
	/// <summary>Hour to stop trading.</summary>
	public int StopHour { get => _stopHour.Value; set => _stopHour.Value = value; }
	/// <summary>Minute to stop trading.</summary>
	public int StopMinute { get => _stopMinute.Value; set => _stopMinute.Value = value; }
	/// <summary>Candle type to use.</summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>Constructor.</summary>
	public FigurelliSeriesStrategy()
	{
		_startPeriod = Param(nameof(StartPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Start Period", "Initial period for moving averages", "Indicator")
			
			.SetOptimize(6, 18, 6);

		_step = Param(nameof(Step), 2)
			.SetGreaterThanZero()
			.SetDisplay("Step", "Step between moving average periods", "Indicator")
			
			.SetOptimize(6, 12, 2);

		_total = Param(nameof(Total), 6)
			.SetGreaterThanZero()
			.SetDisplay("Total", "Number of moving averages", "Indicator")
			
			.SetOptimize(12, 48, 12);

		_startHour = Param(nameof(StartHour), 8)
			.SetDisplay("Start Hour", "Hour to start trading", "Time")
			
			.SetOptimize(0, 23, 1);

		_startMinute = Param(nameof(StartMinute), 0)
			.SetDisplay("Start Minute", "Minute to start trading", "Time")
			
			.SetOptimize(0, 59, 1);

		_stopHour = Param(nameof(StopHour), 23)
			.SetDisplay("Stop Hour", "Hour to stop trading", "Time")
			
			.SetOptimize(0, 23, 1);

		_stopMinute = Param(nameof(StopMinute), 59)
			.SetDisplay("Stop Minute", "Minute to stop trading", "Time")
			
			.SetOptimize(0, 59, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for calculations", "General");
	}

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

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

		_lastSign = 0;

		var figurelli = new FigurelliSeriesIndicator
		{
			StartPeriod = StartPeriod,
			Step = Step,
			Total = Total
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(figurelli, ProcessCandle)
			.Start();

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_lastSign = 0;
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var sign = Math.Sign(value);
		if (sign == 0 || sign == _lastSign)
			return;

		if (sign > 0 && Position <= 0)
			BuyMarket();
		else if (sign < 0 && Position >= 0)
			SellMarket();

		_lastSign = sign;
	}

	private class FigurelliSeriesIndicator : BaseIndicator
	{
		public int StartPeriod { get; set; }
		public int Step { get; set; }
		public int Total { get; set; }

		private ExponentialMovingAverage[] _averages = [];

		public override void Reset()
		{
			base.Reset();
			foreach (var ma in _averages)
				ma.Reset();
		}

		protected override IIndicatorValue OnProcess(IIndicatorValue input)
		{
			var candle = input.ToCandle();
			var price = candle.ClosePrice;

			if (_averages.Length == 0)
			{
				_averages = new ExponentialMovingAverage[Total];

				for (var i = 0; i < Total; i++)
					_averages[i] = new ExponentialMovingAverage { Length = StartPeriod + (Step * i) };
			}

			var bids = 0;
			var asks = 0;
			var allFormed = true;

			foreach (var ma in _averages)
			{
				var maValue = ma.Process(price, input.Time, input.IsFinal).GetValue<decimal>();

				if (!ma.IsFormed)
				{
					allFormed = false;
					continue;
				}

				if (price > maValue)
					bids++;
				else if (price < maValue)
					asks++;
			}

			if (allFormed)
				IsFormed = true;

			var result = bids - asks;
			return new DecimalIndicatorValue(this, result, input.Time);
		}
	}
}