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:
Identificar o contrato atualmente negociável para cada família de futuros com base em um código curto (por exemplo MXI ou BR).
Abrir ou ajustar a posição para que o volume real do portfólio corresponda ao valor de lote configurado (positivo = comprado, negativo = vendido).
Monitorar o tempo de vencimento em cada vela finalizada de uma assinatura de heartbeat.
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
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.
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.
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.
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));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class futures_portfolio_control_expiration_strategy(Strategy):
"""
Futures Portfolio Control Expiration: rebalances to target position
at regular intervals with SMA trend reversal exits.
"""
def __init__(self):
super(futures_portfolio_control_expiration_strategy, self).__init__()
self._target_position = self.Param("TargetPosition", 1) \
.SetDisplay("Target Position", "Desired position size (positive=long, negative=short)", "Portfolio")
self._rebalance_period = self.Param("RebalancePeriod", 10) \
.SetDisplay("Rebalance Period", "Number of bars between rebalance checks", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle series for monitoring", "General")
self._bar_count = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(futures_portfolio_control_expiration_strategy, self).OnReseted()
self._bar_count = 0
def OnStarted2(self, time):
super(futures_portfolio_control_expiration_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = 20
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
self._bar_count += 1
price = float(candle.ClosePrice)
sma = float(sma_value)
target = self._target_position.Value
# Rebalance at intervals
if self._bar_count % self._rebalance_period.Value == 0:
current = self.Position
diff = target - current
if diff > 0:
self.BuyMarket()
elif diff < 0:
self.SellMarket()
# Trend reversal exit
if self.Position > 0 and price < sma:
self.SellMarket()
elif self.Position < 0 and price > sma:
self.BuyMarket()
elif self.Position == 0:
if target > 0 and price > sma:
self.BuyMarket()
elif target < 0 and price < sma:
self.SellMarket()
elif target > 0:
self.BuyMarket()
elif target < 0:
self.SellMarket()
def CreateClone(self):
return futures_portfolio_control_expiration_strategy()