Ver no GitHub

Estratégia de Controle de Portfólio de Futuros com Expiração

Visão geral

Esta estratégia reconstrói o assessor especialista do MetaTrader 5 Futures Portfolio Control Expiration sobre a API de alto nível do StockSharp. Mantém um portfólio de futuros de três pernas, preserva a exposição comprada/vendida desejada para cada perna e rola automaticamente cada contrato para o próximo vencimento quando a vida útil restante cai abaixo de um limite configurável.

A implementação replica o fluxo de trabalho original:

  1. Identificar o contrato atualmente negociável para cada família de futuros com base em um código curto (por exemplo MXI ou BR).
  2. Abrir ou ajustar a posição para que o volume real do portfólio corresponda ao valor de lote configurado (positivo = comprado, negativo = vendido).
  3. Monitorar o tempo de vencimento em cada vela finalizada de uma assinatura de heartbeat.
  4. Fechar o contrato que expira, descobrir o próximo vencimento na mesma família e recriar a exposição alvo no novo contrato.

Parâmetros

Nome Descrição Padrão
BoardCode Quadro de bolsa anexado aos identificadores de futuros (por exemplo FORTS). Deixe vazio se o provedor não exigir um sufixo de quadro. FORTS
Symbol1, Symbol2, Symbol3 Códigos curtos das três famílias de futuros. A estratégia itera os vencimentos de futuros construindo identificadores como CODE-M.YY. MXI, BR, SBRF
Lot1, Lot2, Lot3 Tamanho de posição alvo por perna. Valores positivos criam exposição comprada, valores negativos criam exposição vendida. -4, -1, 5
HoursBeforeExpiration Número de horas antes do vencimento do contrato quando o roll deve começar. 25
MonitoringCandleType Tipo de vela usado apenas como heartbeat para acionar verificações de vencimento (por exemplo velas horárias). Período 1H

Gestão de roll e posição

  • Descoberta de contratos. Para cada perna a estratégia escaneia até doze meses consecutivos do calendário. Tenta múltiplos formatos de identificador (CODE-M.YY, CODE-MM.YY, CODEMMYY, CODEMYY) e opcionalmente anexa o BoardCode configurado. Apenas instrumentos com data de vencimento posterior ao tempo de referência são elegíveis.
  • Atualizações de heartbeat. Uma assinatura de velas em cada contrato ativo fornece um callback de vela finalizada que reavalia os temporizadores de vencimento e sincroniza a exposição do portfólio.
  • Lógica de roll. Quando a vida útil restante é menor ou igual a HoursBeforeExpiration, a estratégia fecha qualquer posição aberta no contrato atual, localiza o próximo futuro com vencimento posterior, re-assina as velas de heartbeat e restaura o lote alvo no novo contrato.
  • Sincronização de posição. Após cada heartbeat, a posição real é comparada ao lote alvo. A estratégia aumenta ou diminui a exposição com ordens a mercado para que a posição ao vivo sempre corresponda ao volume solicitado (incluindo zero).

Notas de uso

  1. Certifique-se de que o SecurityProvider conheça todos os símbolos de futuros para as famílias selecionadas. Configure BoardCode se sua fonte de dados exigir identificadores como Si-9.23@FORTS.
  2. Inicie a estratégia com os parâmetros de portfólio desejados. As posições são abertas apenas quando a estratégia está online e o trading é permitido.
  3. A estratégia registra cada atribuição, ajuste e evento de roll. Use essas mensagens para verificar o mapeamento entre códigos curtos e futuros reais.
  4. Como a assinatura de heartbeat é apenas um temporizador, você pode escolher qualquer tipo de vela que esteja disponível de forma consistente para os instrumentos negociados.

Detalhes de implementação

  • Os componentes da API de alto nível (SubscribeCandles, StrategyParam, BuyMarket/SellMarket) mantêm o código conciso e aderem às diretrizes do projeto.
  • Nenhuma coleção personalizada de dados históricos é armazenada; a estratégia trabalha apenas com o último evento de vela e o estado da posição.
  • Comentários em inglês dentro do código descrevem cada etapa importante para facilitar a manutenção.
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>
/// Monitors position and rebalances to maintain a target exposure.
/// Simplified from the multi-leg futures portfolio controller to single security.
/// </summary>
public class FuturesPortfolioControlExpirationStrategy : Strategy
{
	private readonly StrategyParam<int> _targetPosition;
	private readonly StrategyParam<int> _rebalancePeriod;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private int _barCount;

	/// <summary>
	/// Target position size. Positive for long, negative for short.
	/// </summary>
	public int TargetPosition
	{
		get => _targetPosition.Value;
		set => _targetPosition.Value = value;
	}

	/// <summary>
	/// Number of bars between rebalance checks.
	/// </summary>
	public int RebalancePeriod
	{
		get => _rebalancePeriod.Value;
		set => _rebalancePeriod.Value = value;
	}

	/// <summary>
	/// Candle type used as heartbeat for monitoring.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public FuturesPortfolioControlExpirationStrategy()
	{
		_targetPosition = Param(nameof(TargetPosition), 1)
			.SetDisplay("Target Position", "Desired position size (positive=long, negative=short)", "Portfolio");

		_rebalancePeriod = Param(nameof(RebalancePeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Rebalance Period", "Number of bars between rebalance checks", "General");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sma = null;
		_barCount = 0;
	}

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

		_sma = new SimpleMovingAverage { Length = 20 };

		SubscribeCandles(CandleType)
			.Bind(_sma, ProcessCandle)
			.Start();
	}

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

		if (!IsFormed)
			return;

		_barCount++;

		var price = candle.ClosePrice;
		var target = (decimal)TargetPosition;

		// Rebalance: ensure position matches target
		if (_barCount % RebalancePeriod == 0)
		{
			var current = Position;
			var diff = target - current;

			if (diff > 0)
				BuyMarket(Math.Abs(diff));
			else if (diff < 0)
				SellMarket(Math.Abs(diff));
		}

		// Trend reversal exit and re-entry
		if (Position > 0 && price < smaValue)
		{
			SellMarket(Math.Abs(Position));
		}
		else if (Position < 0 && price > smaValue)
		{
			BuyMarket(Math.Abs(Position));
		}
		else if (Position == 0)
		{
			if (target > 0 && price > smaValue)
				BuyMarket(Math.Abs(target));
			else if (target < 0 && price < smaValue)
				SellMarket(Math.Abs(target));
			else if (target > 0)
				BuyMarket(Math.Abs(target));
			else if (target < 0)
				SellMarket(Math.Abs(target));
		}
	}
}