Die Fluktuations-Strategie ist ein StockSharp-Port des MetaTrader Expert Advisors "Fluctuate". Sie reproduziert das gitterartige Verhalten des Originals mithilfe der High-Level-API: Eine Kerzen-Subscription steuert alle Entscheidungen, Markteinstiege werden mit BuyMarket / SellMarket durchgeführt und Wiederherstellungsaufträge werden mit Stop-Orders platziert. Long- und Short-Exposure werden separat verfolgt, um die Hedging-Positionsbuchhaltung aus MetaTrader nachzuahmen, während die tatsächliche StockSharp-Position netto bleibt.
Grundidee
Jedes Mal wenn eine neue Kerze schließt, vergleicht die Strategie die letzten beiden Schlusskurse. Ein höherer Schluss eröffnet einen Marktkauf, ein niedrigerer Schluss eröffnet einen Marktverkauf. Wenn beide Schlusskurse gleich sind, wird der Balken ignoriert.
Jede ausgeführte Position erhält einen festen Stop-Loss und Take-Profit (in Pips ausgedrückt). Die Strategie zeichnet auch den genauen Ausführungspreis und das netto durch den Trade hinzugefügte Volumen auf.
Nach einem Einstieg wird eine entgegengesetzte Stop-Order StepPips weit vom letzten Preis entfernt aktiviert (plus ein kleiner Spread-Puffer). Ihr Volumen leitet sich vom vorherigen Trade und dem LotCoefficient ab, optional unter Verwendung des kumulierten Exposures, wenn MultiplyLotCoefficient = true.
Wenn die Stop-Order ausgelöst wird, storniert sie die vorherige ausstehende Order, aktualisiert die internen Exposure-Statistiken und plant sofort eine neue Wiederherstellungs-Stop-Order in die andere Richtung. Dies reproduziert die Averaging/Martingale-Schleife der MQL-Implementierung.
Der Trailing-Schutz hebt (oder senkt) den Stop, sobald sich der Preis mindestens TrailingStopPips + TrailingStepPips zugunsten der Position bewegt. Dies emuliert den ursprünglichen EA, der einen zusätzlichen Gewinnpuffer benötigte, bevor der Stop angezogen wurde.
Handelsablauf
Signalerkennung. Der Kerzenfeed wird via SubscribeCandles abonniert. Es werden nur abgeschlossene Kerzen verarbeitet. Die Strategie weigert sich außerhalb des Zeitfensters [StartHour, EndHour) zu handeln oder wenn der Equity-Wächter ausgelöst wird.
Anfängliche Positionsgröße. Je nach PositionSizingMode verwendet der erste Trade einer Sequenz entweder ein festes Los (FixedVolume) oder ein risikobasiertes Los (RiskPercent). Im Risikomodus wird das erlaubte Risiko (Prozentsatz des aktuellen Eigenkapitals) durch den monetären Verlust dividiert, der eintreten würde, wenn der Stop-Loss ausgelöst wird. Preisschritt und Schrittpreis werden verwendet, um Pips in Währung umzurechnen.
Exposure-Buchhaltung. Separate Akkumulatoren verfolgen Long- und Short-Volumen, Durchschnittspreis und den seit dem Einstieg erreichten Extrempreis. Dies ermöglicht es der Strategie, beide Seiten intern "offen" zu halten, obwohl StockSharp Netting verwendet.
Wiederherstellungsaufträge. Nach jeder Ausführung berechnet der Algorithmus das Volumen der nächsten Stop-Order:
Wenn MultiplyLotCoefficient = false, entspricht das neue Volumen LastVolume × LotCoefficient.
Wenn true, wird das gesamte absolute Exposure mit LotCoefficient multipliziert.
Das Volumen wird an Börsenbeschränkungen normalisiert (Schritt, Min- und Max-Volumen) und abgelehnt, wenn es MaxTotalVolume überschreiten würde oder die Anzahl aktiver Positionen plus Orders MaxPositions überschreiten würde.
Gewinnziel und Equity-Wächter. Aggregierter unrealisierter PnL wird berechnet, indem Preisdifferenzen mithilfe von PriceStep/StepPrice in Währung übersetzt werden. Wenn er ProfitTarget erreicht, werden alle Positionen geschlossen und ausstehende Orders storniert. Der Handel wird auch ausgesetzt, wenn das Eigenkapital unter MinEquityPercent des Anfangsguthabens fällt.
Trailing-Logik. Für Long-Positionen wird der höchste seit dem Einstieg gesehene Preis aufgezeichnet. Sobald er den Eintrittspreis um TrailingStopPips + TrailingStepPips übersteigt, wird ein Trailing Stop TrailingStopPips hinter dem Hoch gesetzt. Short-Positionen wenden die symmetrische Regel mit dem niedrigsten Preis an. Trailing-Updates überschreiben den festen Stop-Loss.
Risikomanagement-Details
Stop / Take-Profit. Beide sind optional (den Pip-Wert auf null setzen zum Deaktivieren). Sie werden für das aggregierte Long- oder Short-Exposure neu berechnet, wann immer ein neuer Trade Volumen hinzufügt.
Max. Positionen. Zählt die Anzahl offener Seiten (Long + Short) plus die aktive Wiederherstellungs-Stop-Order. Wenn das Limit erreicht ist, verweigert die Strategie die Einreichung neuer Stop-Orders.
Maximales Gesamtvolumen. Begrenzt die Summe des absoluten offenen Volumens und des Volumens der aktiven Wiederherstellungsorder.
CloseAllAtStart. Optionaler Sicherheitsschalter zum Schließen aller Positionen bevor die Strategie mit dem Handel beginnt.
Parameter
Name
Beschreibung
Standard
CandleType
Primärer Zeitrahmen für die Signalerkennung.
1-Minuten-Zeitrahmen
StopLossPips
Distanz zwischen Eintrittspreis und Stop-Loss (Pips). 0 deaktiviert den Stop.
50
TakeProfitPips
Distanz zwischen Eintrittspreis und Take-Profit (Pips). 0 deaktiviert den Take-Profit.
FixedVolume für statische Lots, RiskPercent für prozentuale Eigenkapitalgröße.
FixedVolume
VolumeOrRisk
Feste Losgröße (bei FixedVolume) oder Risikoprozentsatz (bei RiskPercent).
1.0
Implementierungshinweise
Stop-Order-Preise verwenden eine minimale Spread-Annäherung (PriceStep wenn verfügbar), weil MetaTrader verlangte, dass die Order außerhalb des Freeze-Levels liegt. StepPips anpassen, wenn der tatsächliche Spread breiter ist.
Die Strategie storniert alle verbleibenden Wiederherstellungsaufträge, wenn ein neuer Trade ausgeführt wird. Dies entspricht dem ursprünglichen EA, der alle ausstehenden Orders nach einer Ausführung löschte.
Da StockSharp-Portfolios genet sind, wird das Hedging-Exposure intern simuliert. Die tatsächliche Broker-Position wird immer die Nettomenge widerspiegeln.
Risikobasierte Positionsgrößen erfordern gültige PriceStep- und StepPrice-Werte aus der Instrumentenbeschreibung.
Verwendungstipps
Einen geeigneten Kerzentyp wählen, der dem ursprünglichen EA-Testzeitrahmen entspricht (typischerweise M5 oder M15) für beste Wiedergabetreue.
Börsenlimitvolumen doppelt prüfen: wenn das normalisierte Wiederherstellungsvolumen null wird, wird die Strategie aufhören, neue Legs hinzuzufügen.
Wenn PositionSizingMode = RiskPercent, sicherstellen, dass das Portfolio aktuelle Eigenkapitalinformationen enthält; andernfalls fällt die Strategie auf die feste Losgröße zurück.
Mit dem eingebauten StrategyProtection von StockSharp (aktiviert via StartProtection()) kombinieren, um bei Bedarf zusätzliche Schutzmaßnahmen auf Kontoebene hinzuzufügen.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fluctuate strategy using EMA crossover with stop-loss and take-profit.
/// Buys when fast EMA crosses above slow EMA, sells on reverse cross.
/// </summary>
public class FluctuateStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initialize <see cref="FluctuateStrategy"/>.
/// </summary>
public FluctuateStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 50;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 50;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 50;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 50;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// EMA crossover buy signal
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 50;
}
// EMA crossover sell signal
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 50;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fluctuate_strategy(Strategy):
def __init__(self):
super(fluctuate_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 50) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 200) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(fluctuate_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(fluctuate_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 50
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 50
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 50
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 50
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# EMA crossover buy signal
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 50
# EMA crossover sell signal
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 50
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return fluctuate_strategy()