Открыть на GitHub

Стратегия Figurelli Series

Обзор

Эта стратегия является конверсией советника MetaTrader5 "Exp_FigurelliSeries" в StockSharp. Используется индикатор Figurelli Series, измеряющий разницу между количеством скользящих средних выше и ниже текущей цены. Сделки совершаются один раз в день в заданное время начала, а все позиции закрываются во время остановки.

Индикатор

Индикатор строит цепочку экспоненциальных скользящих средних, начиная со значения Start Period и увеличивая период на Step для каждой из Total средних. На каждом баре считается сколько средних выше цены закрытия и сколько ниже. Значение индикатора равно bids - asks, где bids — количество средних ниже цены, а asks — количество средних выше цены.

Правила торговли

  • В момент Start Hour:Start Minute:
    • Покупка, если значение индикатора положительное и нет длинной позиции.
    • Продажа, если значение индикатора отрицательное и нет короткой позиции.
  • В момент Stop Hour:Stop Minute и позже все открытые позиции закрываются.
  • Используются только завершенные свечи выбранного типа CandleType.

Параметры

  • StartPeriod – начальный период скользящих средних.
  • Step – приращение периода между средними.
  • Total – количество скользящих средних.
  • StartHour / StartMinute – время возможного входа.
  • StopHour / StopMinute – время выхода из всех позиций.
  • CandleType – тип свечей для расчета.
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);
		}
	}
}