在 GitHub 上查看

Figurelli Series 策略

概述

该策略将 MetaTrader5 的 "Exp_FigurelliSeries" 专家顾问转换为 StockSharp。它使用 Figurelli Series 指标,该指标统计当前收盘价上方和下方的移动平均线数量之差。策略在用户设定的开始时间触发一次交易,并在停止时间关闭所有持仓。

指标

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