Diese Strategie rekonstruiert den MetaTrader 5-Expertenberater Futures Portfolio Control Expiration auf der StockSharp High-Level-API. Sie verwaltet ein Drei-Leg-Futures-Portfolio, behält die gewünschte Long/Short-Exposition für jedes Leg und rollt automatisch jeden Kontrakt zum nächsten Verfallstermin, wenn die verbleibende Laufzeit unter einen konfigurierbaren Schwellenwert fällt.
Die Implementierung repliziert den ursprünglichen Workflow:
Den aktuell handelbaren Kontrakt für jede Futures-Familie anhand eines Kurzzeichens identifizieren (z. B. MXI oder BR).
Die Position öffnen oder anpassen, damit das tatsächliche Portfolio-Volumen dem konfigurierten Lot-Wert entspricht (positiv = Long, negativ = Short).
Die Verfallszeit bei jeder abgeschlossenen Kerze einer Heartbeat-Subscription überwachen.
Den ablaufenden Kontrakt schließen, den nächsten Verfall in derselben Familie ermitteln und die Zielexposition auf dem neuen Kontrakt wiederherstellen.
Parameter
Name
Beschreibung
Standard
BoardCode
Exchange-Board, das an Futures-Kennungen angehängt wird (z. B. FORTS). Leer lassen, wenn der Anbieter kein Board-Suffix benötigt.
FORTS
Symbol1, Symbol2, Symbol3
Kurzcodes der drei Futures-Familien. Die Strategie iteriert Futures-Verfall durch Aufbau von Kennungen wie CODE-M.YY.
Anzahl der Stunden vor Kontraktverfall, wann das Rollen beginnen soll.
25
MonitoringCandleType
Kerzentyp, der nur als Heartbeat zur Auslösung von Verfalls-Checks verwendet wird (z. B. Stundenkerzen).
Zeitrahmen 1H
Rollen und Positionsverwaltung
Kontrakt-Entdeckung. Für jedes Leg scannt die Strategie bis zu zwölf aufeinanderfolgende Kalendermonate. Sie versucht mehrere Kennungsformate (CODE-M.YY, CODE-MM.YY, CODEMMYY, CODEMYY) und hängt optional den konfigurierten BoardCode an. Nur Wertpapiere mit einem Verfallsdatum nach der Referenzzeit sind zugelassen.
Heartbeat-Updates. Eine Kerzen-Subscription auf jedem aktiven Kontrakt liefert einen Fertig-Kerzen-Callback, der Verfalls-Timer neu bewertet und die Portfolio-Exposition synchronisiert.
Roll-Logik. Wenn die verbleibende Laufzeit kleiner oder gleich HoursBeforeExpiration ist, schließt die Strategie offene Positionen auf dem aktuellen Kontrakt, lokalisiert den nächsten Future mit einem späteren Verfall, abonniert Heartbeat-Kerzen neu und stellt das Ziel-Lot auf dem neuen Kontrakt wieder her.
Positions-Synchronisation. Nach jedem Heartbeat wird die tatsächliche Position mit dem Ziel-Lot verglichen. Die Strategie erhöht oder verringert die Exposition mit Market-Orders, damit die Live-Position immer dem angeforderten Volumen entspricht (einschließlich null).
Verwendungshinweise
Stellen Sie sicher, dass der SecurityProvider alle Futures-Symbole für die ausgewählten Familien kennt. Konfigurieren Sie BoardCode, wenn Ihre Datenquelle Kennungen wie Si-9.23@FORTS erfordert.
Starten Sie die Strategie mit den gewünschten Portfolio-Parametern. Positionen werden nur eröffnet, wenn die Strategie online ist und der Handel erlaubt ist.
Die Strategie protokolliert jede Zuweisung, Anpassung und jedes Roll-Ereignis. Verwenden Sie diese Meldungen, um die Zuordnung zwischen Kurzzeichen und tatsächlichen Futures zu überprüfen.
Da die Heartbeat-Subscription nur ein Timer ist, können Sie jeden Kerzentyp wählen, der für die gehandelten Instrumente konsistent verfügbar ist.
Implementierungsdetails
High-Level-API-Komponenten (SubscribeCandles, StrategyParam, BuyMarket/SellMarket) halten den Code prägnant und entsprechen den Projektrichtlinien.
Keine benutzerdefinierten Sammlungen historischer Daten werden gespeichert; die Strategie arbeitet nur mit dem letzten Kerzen-Ereignis und dem Positionszustand.
Englische Kommentare im Code beschreiben jeden wichtigen Schritt für eine einfachere Wartung.
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()