Ver no GitHub

4026 – Estratégia de Pivôs

Visão geral

Esta estratégia transporta os MetaTrader 4 arquivos localizados em MQL/8550 (o indicador Pivots e o consultor especialista Pivots_test que o acompanha) para o Strategy API de alto nível de StockSharp. Ele mantém o comportamento original de calcular os níveis diários de pivô mínimo, organizando um par de ordens pendentes opostas no pivô central e gerenciando cada posição resultante com um stop-loss, take-profit e stop móvel fixos.

Cálculo dinâmico

  1. A estratégia assina um período de pivô configurável (PivotCandleType, diariamente por padrão).
  2. Sempre que uma vela desse período termina, ela deriva os níveis clássicos de pivô dos preços OHLC do dia anterior:
    • Pivot = (High + Low + Close) / 3
    • R1 = 2 × Pivot − Low
    • S1 = 2 × Pivot − High
    • R2 = Pivot + (High − Low) e S2 = Pivot − (High − Low)
    • R3 = 2 × Pivot + High − 2 × Low e S3 = 2 × Pivot − (2 × High − Low)
  3. Os níveis ficam ativos no início da próxima sessão. Quando isso acontece a estratégia registra os valores através de AddInfoLog (por exemplo: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …).

Fluxo de trabalho de pedidos pendentes

Uma vez ativos os níveis de pivô, a estratégia garante continuamente a existência de duas ordens pendentes ao preço de pivô:

  • Limite de compra @ Pivot com proteção pós-preenchimento SellStop (stop-loss) em S2 e SellLimit (take-profit) em R2.
  • Sell Stop @ Pivot com proteção pós-preenchimento BuyStop em R2 e BuyLimit em S2.

Todos os pedidos são enviados por meio dos métodos auxiliares de alto nível BuyLimit, SellStop, SellLimit e BuyStop. Se uma ordem for preenchida, o código recalcula o preço médio de entrada para aquela direção, cancela as ordens de proteção existentes e envia um novo par stop/limit que cobre todo o volume aberto (espelhando o comportamento MetaTrader onde cada posição herda a mesma proteção S2/R2). Se o stop de proteção ou o take-profit forem executados, os auxiliares relacionados serão compensados ​​automaticamente.

A estratégia usa uma única posição líquida, portanto, preenchimentos opostos se compensarão (ao contrário da cobertura baseada em tickets de MetaTrader). Este é o único desvio intencional do perito original.

Lógica de parada móvel

  • TrailingStopPoints define a distância em pontos indicadores (multiplicada pelo instrumento PriceStep).
  • Para posições longas, o trailing stop é ativado quando o preço se move mais do que aquela distância acima da entrada média. O protetor SellStop é então movido para mais perto do mercado.
  • Para posições curtas, a lógica de espelho se aplica, diminuindo o BuyStop à medida que o preço se move favoravelmente.
  • As atualizações finais são orientadas pela série intradiária selecionada por meio de CandleType (velas de 15 minutos por padrão).

Parâmetros

Parâmetro Descrição Padrão
OrderVolume Volume de cada ordem pendente (lotes/contratos). 0.1
TrailingStopPoints Distância de parada final em pontos. 0 desativa a lógica final. 30
CandleType Série de velas intradiárias usadas para rastrear e manter a programação da sessão. 15m prazo
PivotCandleType Período usado para calcular os níveis de pivô diários. 1D prazo
LogPivotUpdates Quando true, os níveis pivô são gravados no registro de estratégia sempre que mudam. true

Todos os parâmetros numéricos são expostos por meio de StrategyParam<T> para que possam ser otimizados dentro da infraestrutura StockSharp.

Registro e diagnóstico

  • As atualizações dinâmicas são roteadas por meio de AddInfoLog, que substitui a saída MetaTrader Comment/ObjectSetText.
  • O gerenciamento de ordens de proteção, o tratamento de posições e a lógica de rastreamento dependem exclusivamente dos ajudantes de alto nível de StockSharp; nenhum registro de pedido de baixo nível ou buffers de indicador são usados.

Notas de uso

  1. Anexe a estratégia a um conector que forneça velas diárias e intradiárias para o título escolhido.
  2. Ajuste a etapa do instrumento, se necessário (PriceStep é detectado automaticamente; o substituto é 0.0001).
  3. Opcionalmente, ajuste OrderVolume, TrailingStopPoints ou os tipos de velas para corresponder à configuração original do MT4.

Nenhuma versão do Python é fornecida para esta porta conforme solicitado.

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that calculates classic floor pivot levels from daily candles and trades
/// breakouts around the central pivot. Goes long on close above pivot, short on close below.
/// Uses S2/R2 as stop/target levels.
/// </summary>
public class PivotsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;

	private decimal _pivotLevel;
	private decimal _r1, _r2, _s1, _s2;
	private decimal? _previousClose;
	private decimal? _entryPrice;
	private bool _pivotReady;

	private readonly List<decimal> _dailyHighs = new();
	private readonly List<decimal> _dailyLows = new();
	private readonly List<decimal> _dailyCloses = new();
	private DateTime _currentDay;
	private decimal _dayHigh;
	private decimal _dayLow;
	private decimal _dayClose;

	public PivotsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_pivotLevel = 0m;
		_r1 = _r2 = _s1 = _s2 = 0m;
		_previousClose = null;
		_entryPrice = null;
		_pivotReady = false;
		_dailyHighs.Clear();
		_dailyLows.Clear();
		_dailyCloses.Clear();
		_currentDay = DateTime.MinValue;
		_dayHigh = 0m;
		_dayLow = decimal.MaxValue;
		_dayClose = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var sma = new SimpleMovingAverage { Length = 2 };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(sma, ProcessCandle).Start();
	}

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

		var candleDay = candle.OpenTime.Date;

		// Track daily OHLC
		if (candleDay != _currentDay)
		{
			if (_currentDay != DateTime.MinValue && _dayHigh > 0m)
			{
				// Previous day completed, calculate pivots
				var high = _dayHigh;
				var low = _dayLow;
				var close = _dayClose;

				_pivotLevel = (high + low + close) / 3m;
				_r1 = 2m * _pivotLevel - low;
				_s1 = 2m * _pivotLevel - high;
				_r2 = _pivotLevel + (high - low);
				_s2 = _pivotLevel - (high - low);
				_pivotReady = true;
			}

			_currentDay = candleDay;
			_dayHigh = candle.HighPrice;
			_dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}
		else
		{
			if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
			if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}

		if (!_pivotReady)
		{
			_previousClose = candle.ClosePrice;
			return;
		}

		if (_previousClose is null)
		{
			_previousClose = candle.ClosePrice;
			return;
		}

		// Manage open positions
		if (Position > 0)
		{
			// Exit long at R2 (take profit) or S1 (stop loss)
			if (candle.HighPrice >= _r2 || candle.LowPrice <= _s1)
			{
				SellMarket(Math.Abs(Position));
				_entryPrice = null;
			}
		}
		else if (Position < 0)
		{
			// Exit short at S2 (take profit) or R1 (stop loss)
			if (candle.LowPrice <= _s2 || candle.HighPrice >= _r1)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
			}
		}

		// Entry signals based on pivot cross
		if (Position == 0)
		{
			var crossAbovePivot = _previousClose.Value <= _pivotLevel && candle.ClosePrice > _pivotLevel;
			var crossBelowPivot = _previousClose.Value >= _pivotLevel && candle.ClosePrice < _pivotLevel;

			if (crossAbovePivot)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (crossBelowPivot)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
		}

		_previousClose = candle.ClosePrice;
	}
}