Diese Strategie repliziert den klassischen "Return Strategy"-Expertenberater. Sie bereitet ein Gitter aus gepaarten Buy-Limit- und Sell-Limit-Orders zu Beginn eines konfigurierten Handelsfensters vor. Das Gitter ist symmetrisch um den Marktpreis, verwendet feste Abstände in Pips und kann entweder nach einem festen Volumen oder einem prozentualen Risikomodell dimensioniert werden. Sobald Orders ausgeführt werden, überwacht die Strategie die Position mit statischer und Trailing-Stop-Loss-Logik, überwacht den kumulativen offenen Gewinn und erzwingt ein vollständiges Glattstellen zur täglichen Cut-off-Zeit oder jeden Freitag.
Das ursprüngliche System wurde für Netting-Konten entwickelt und konzentrierte sich auf das Erfassen von Mean-Reversion-Bewegungen nach geplanten Zeiten. Die Konvertierung behält diese Struktur bei und adaptiert Orderverwaltung, Trailing und Kapitalkontrolle an die StockSharp-High-Level-API.
Trading-Regeln
Tägliche Vorbereitung – Zur StartHour prüft die Strategie, dass keine Gitterorders aktiv sind, und platziert PendingOrderCount Buy-Limits unterhalb und Sell-Limits oberhalb des aktuellen Preises. Das erste Level ist um DistancePips versetzt und jedes weitere Level fügt StepPips Abstand hinzu.
Risikosteuerung – Jede ausstehende Order kann entweder ein festes OrderVolume oder eine risikobasierte Größe aus RiskPercent verwenden. Wenn Risikosizing verwendet wird, bestimmen das verfügbare Kapital und der Stop-Loss-Abstand das Volumen pro Order, sodass das Gesamtgitterrisiko dem konfigurierten Prozentsatz entspricht.
Stop-Verwaltung – Jede ausgeführte Position erhält einen initialen Stop-Loss basierend auf StopLossPips. Wenn TrailingStopPips größer als null ist, wird der Stop, sobald der Preis den Trailing-Schwellenwert überschreitet, in Schritten von TrailingStepPips nachgezogen.
Gewinnziel und Sitzungsausstieg – Der netto offene Gewinn wird in Pips verfolgt. Wenn er TotalProfitPips erreicht, markiert die Strategie alle Positionen und Orders zum Schließen. Dasselbe Leeren erfolgt auch zur konfigurierten EndHour und jeden Freitag unabhängig vom Gewinn.
Order-Ablauf – Ausstehende Orders können nach ExpirationHours automatisch ablaufen. Abgelaufene oder manuell stornierte Orders werden aus der Verfolgungsliste entfernt, um am nächsten Tag ein neues Gitter platzieren zu können.
Parameter
Parameter
Beschreibung
StopLossPips
Initialer Stop-Abstand für jede ausgeführte Position (in angepassten Pips).
StartHour
Stunde (0–23), wann das ausstehende Order-Gitter erstellt wird.
EndHour
Stunde (0–23), die einen vollständigen Ausstieg aus Positionen und Orders auslöst.
TotalProfitPips
Netto offenes Gewinnziel (in Pips), das alle Trades zum Schließen zwingt.
TrailingStopPips
Abstand des Trailing-Stops vom Preis nach Aktivierung. Auf null setzen, um Trailing zu deaktivieren.
TrailingStepPips
Zusätzlicher Vorschub erforderlich, bevor der Trailing-Stop bewegt wird. Muss positiv sein, wenn Trailing aktiviert ist.
DistancePips
Initialer Versatz für die erste ausstehende Order auf jeder Marktseite.
StepPips
Inkrementeller Abstand zwischen aufeinanderfolgenden ausstehenden Orders.
PendingOrderCount
Anzahl der Buy-Limits und Sell-Limits, die zur StartHour registriert werden.
ExpirationHours
Lebensdauer ausstehender Orders in Stunden. Null deaktiviert den Ablauf.
OrderVolume
Festes Volumen pro ausstehender Order. Auf null lassen, um risikobasiertes Sizing zu aktivieren.
RiskPercent
Portfolio-Prozentsatz, der dem gesamten Gitter zugeordnet wird. Das Volumen pro Order wird aus diesem Wert abgeleitet, wenn OrderVolume null ist.
CandleType
Kerzenserie zur Steuerung der Timing- und Stop-Verwaltungslogik.
Zusätzliche Hinweise
Die Pip-Konvertierung spiegelt die ursprüngliche MetaTrader-Logik wider, indem die Schrittgröße für Instrumente mit drei und fünf Dezimalstellen angepasst wird.
Wenn RiskPercent verwendet wird, gilt der Prozentsatz für das kombinierte Gitter und wird gleichmäßig auf alle ausstehenden Orders aufgeteilt.
Die Strategie erzwingt Validierungsregeln identisch zum Quell-EA: Stunden müssen innerhalb des täglichen Bereichs liegen, Trailing erfordert einen Nicht-Null-Schritt, und nur eines von OrderVolume/RiskPercent darf gleichzeitig aktiv sein.
Alle öffentlichen Kommentare im Code werden aus Konsistenzgründen mit den Repository-Richtlinien auf Englisch bereitgestellt.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mean reversion strategy using Bollinger Bands.
/// Buys when price drops below lower band and sells when price rises above upper band.
/// </summary>
public class ReturnStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _width;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
public decimal Width
{
get => _width.Value;
set => _width.Value = value;
}
public ReturnStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_period = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Bollinger Bands period", "Indicators");
_width = Param(nameof(Width), 2m)
.SetGreaterThanZero()
.SetDisplay("Width", "Bollinger Bands width", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ma = new SimpleMovingAverage { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal middle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bandWidth = Width / 100m;
var upper = middle * (1m + bandWidth);
var lower = middle * (1m - bandWidth);
var close = candle.ClosePrice;
// Buy when price drops below lower band (mean reversion)
if (close < lower && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell when price rises above upper band
else if (close > upper && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit long at middle band
else if (Position > 0 && close >= middle)
{
SellMarket();
}
// Exit short at middle band
else if (Position < 0 && close <= middle)
{
BuyMarket();
}
}
}
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 return_strategy(Strategy):
def __init__(self):
super(return_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 20) \
.SetDisplay("Period", "Bollinger Bands period", "Indicators")
self._width = self.Param("Width", 2.0) \
.SetDisplay("Width", "Bollinger Bands width", "Indicators")
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
@property
def Width(self):
return self._width.Value
def OnStarted2(self, time):
super(return_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ma, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _on_process(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
middle = float(ma_value)
band_width = float(self.Width) / 100.0
upper = middle * (1.0 + band_width)
lower = middle * (1.0 - band_width)
close = float(candle.ClosePrice)
# Buy when price drops below lower band (mean reversion)
if close < lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell when price rises above upper band
elif close > upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
# Exit long at middle band
elif self.Position > 0 and close >= middle:
self.SellMarket()
# Exit short at middle band
elif self.Position < 0 and close <= middle:
self.BuyMarket()
def CreateClone(self):
return return_strategy()