Ver no GitHub

Estratégia Force DiverSign

A estratégia Force DiverSign opera com base em sinais de divergência entre dois indicadores Force Index calculados com períodos de suavização diferentes. Ela procura um padrão de reversão de três candles juntamente com oscilações opostas nos valores rápido e lento do Force. Quando aparece uma divergência de alta, a estratégia compra; quando aparece uma divergência de baixa, vende.

Parâmetros

  • Period1 – período para o Force Index rápido.
  • Period2 – período para o Force Index lento.
  • MaType1 – tipo de média móvel usada para suavizar o Force Index rápido.
  • MaType2 – tipo de média móvel usada para suavizar o Force Index lento.
  • CandleType – período dos candles para os cálculos.

Lógica de operação

  1. Calcular o Force Index bruto como o volume multiplicado pela variação do preço de fechamento.
  2. Suavizar o valor bruto com duas médias móveis para obter as séries Force rápida e lenta.
  3. Armazenar os últimos cinco valores de Force e os últimos quatro candles.
  4. Comprar quando:
    • Os três candles anteriores formam um padrão baixo–alto–baixo.
    • Ambas as séries Force formam um mínimo local e depois sobem.
    • O Force rápido e o lento se movem em direções opostas entre o primeiro e o terceiro candle.
  5. Vender quando:
    • Os três candles anteriores formam um padrão alto–baixo–alto.
    • Ambas as séries Force formam um máximo local e depois caem.
    • O Force rápido e o lento se movem em direções opostas entre o primeiro e o terceiro candle.

As posições são revertidas em cada sinal: uma compra fecha um vendido existente e uma venda fecha um comprado.

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>
/// Force DiverSign strategy.
/// Detects divergence between fast and slow Force Index values
/// combined with a specific candle pattern.
/// </summary>
public class ForceDiverSignStrategy : Strategy
{
	private readonly StrategyParam<int> _period1;
	private readonly StrategyParam<int> _period2;
	private readonly StrategyParam<DataType> _candleType;

	private readonly decimal[] _opens = new decimal[5];
	private readonly decimal[] _closes = new decimal[5];
	private readonly decimal[] _f1 = new decimal[5];
	private readonly decimal[] _f2 = new decimal[5];
	private decimal _prevClose;
	private int _count;

	public int Period1 { get => _period1.Value; set => _period1.Value = value; }
	public int Period2 { get => _period2.Value; set => _period2.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public ForceDiverSignStrategy()
	{
		_period1 = Param(nameof(Period1), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Period for fast Force index", "Indicators");

		_period2 = Param(nameof(Period2), 7)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Period for slow Force index", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_count = default;
		Array.Clear(_opens);
		Array.Clear(_closes);
		Array.Clear(_f1);
		Array.Clear(_f2);
	}

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

		var ma1 = new ExponentialMovingAverage { Length = Period1 };
		var ma2 = new ExponentialMovingAverage { Length = Period2 };
		Indicators.Add(ma1);
		Indicators.Add(ma2);

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(candle =>
		{
			if (candle.State != CandleStates.Finished)
				return;

			if (_count == 0)
			{
				_prevClose = candle.ClosePrice;
				Shift(_opens, candle.OpenPrice);
				Shift(_closes, candle.ClosePrice);
				_count++;
				return;
			}

			var force = (candle.ClosePrice - _prevClose) * candle.TotalVolume;
			_prevClose = candle.ClosePrice;

			var f1v = ma1.Process(force, candle.OpenTime, true);
			var f2v = ma2.Process(force, candle.OpenTime, true);

			Shift(_opens, candle.OpenPrice);
			Shift(_closes, candle.ClosePrice);

			if (f1v.IsEmpty || f2v.IsEmpty)
			{
				_count++;
				return;
			}

			var f1 = f1v.ToDecimal();
			var f2 = f2v.ToDecimal();
			Shift(_f1, f1);
			Shift(_f2, f2);

			if (_count < 5)
			{
				_count++;
				return;
			}

			if (!IsFormedAndOnlineAndAllowTrading())
				return;

			var sellSignal = _opens[3] < _closes[3] && _opens[2] > _closes[2] && _opens[1] < _closes[1]
				&& _f1[4] < _f1[3] && _f1[3] > _f1[2] && _f1[2] < _f1[1]
				&& _f2[4] < _f2[3] && _f2[3] > _f2[2] && _f2[2] < _f2[1]
				&& ((_f1[3] > _f1[1] && _f2[3] < _f2[1]) || (_f1[3] < _f1[1] && _f2[3] > _f2[1]));

			var buySignal = _opens[3] > _closes[3] && _opens[2] < _closes[2] && _opens[1] > _closes[1]
				&& _f1[4] > _f1[3] && _f1[3] < _f1[2] && _f1[2] > _f1[1]
				&& _f2[4] > _f2[3] && _f2[3] < _f2[2] && _f2[2] > _f2[1]
				&& ((_f1[3] > _f1[1] && _f2[3] < _f2[1]) || (_f1[3] < _f1[1] && _f2[3] > _f2[1]));

			if (buySignal && Position <= 0)
			{
				if (Position < 0)
					BuyMarket();
				BuyMarket();
			}
			else if (sellSignal && Position >= 0)
			{
				if (Position > 0)
					SellMarket();
				SellMarket();
			}
		}).Start();
	}

	private static void Shift(decimal[] array, decimal value)
	{
		for (var i = array.Length - 1; i > 0; i--)
			array[i] = array[i - 1];
		array[0] = value;
	}
}