Risikomanagement ATR Strategie
Überblick
Die Risikomanagement-ATR-Strategie ist eine StockSharp-Umsetzung des MetaTrader 5-Experten Risikomanagement EA basierend auf ATR Volatilität. Das ursprüngliche EA konzentrierte sich auf die automatische Größenbestimmung von Positionen entsprechend dem Kontostand und der aktuellen Marktvolatilität, gemessen durch den Average True Range (ATR). Der StockSharp-Port behält die gleiche Philosophie bei: Er eröffnet nur Long-Positionen, wenn ein einfacher gleitender 10-Perioden-Durchschnitt einen einfachen gleitenden 20-Perioden-Durchschnitt überschreitet, und jede Einstiegsgröße wird so berechnet, dass der potenzielle Verlust am Schutzstopp dem konfigurierten Risikoprozentsatz entspricht.
Die Konvertierung folgt dem übergeordneten StockSharp API. Indikatorberechnungen basieren auf den Komponenten AverageTrueRange und SimpleMovingAverage, die an das Kerzenabonnement angehängt sind, und nicht auf direkten Indikatoraufrufen. Das Handelsmanagement verwendet StockSharp-Hilfsmethoden wieder und bricht den Schutzstopp nach jeder Füllung ab und erstellt ihn neu, sodass die Nettoposition und die Stop-Order immer übereinstimmen.
Handelslogik
- Abonnieren Sie den durch
CandleType definierten Zeitrahmen und warten Sie, bis die Kerzen vollständig geschlossen sind, um vorzeitige Entscheidungen zu vermeiden.
- Geben Sie einen 14-Perioden-ATR und zwei einfache gleitende Durchschnitte (Längen 10 und 20) mit den Abonnementdaten ein.
- Wenn der schnell gleitende Durchschnitt über dem langsam gleitenden Durchschnitt schließt und es keine offene Position gibt, berechnen Sie die Positionsgröße basierend auf dem ausgewählten Risikomodell und erteilen Sie eine Marktkauforder.
- Berechnen Sie nach jeder Füllung die Stop-Loss-Distanz: entweder
ATR * AtrMultiplier oder eine feste Anzahl von Preisschritten, wenn UseAtrStopLoss deaktiviert ist.
- Runden Sie den Stop-Preis auf den nächsten Tick ab und platzieren Sie eine
SellStop-Order mit der aktuellen Positionsgröße. Jeder vorherige Stopp wird gelöscht, bevor der neue registriert wird.
- Wenn die Stop-Order ausgeführt wird und die Position auf Null zurückkehrt, löscht die Strategie ihren internen Zustand und ist bereit für den nächsten Crossover.
Risikomanagement
RiskPercentage bestimmt, wie viel vom Portfoliowert bei einem einzelnen Trade verloren gehen kann. Die Strategie liest Portfolio.CurrentValue (oder BeginValue als Fallback) und multipliziert ihn mit dem Prozentsatz, um das zulässige monetäre Risiko zu erhalten.
- Das zulässige Risiko wird durch die Stop-Loss-Distanz dividiert, um das Handelsvolumen zu erhalten. Bei der Volumenrundung werden der Volumenschritt des Instruments sowie Mindest- und Höchstbeschränkungen berücksichtigt, sodass die generierten Aufträge an der Börse gültig bleiben.
- Wenn
RiskPercentage auf 0 gesetzt ist, greift die Strategie auf die Standardeigenschaft Volume zurück (standardmäßig 1 Lot), während der automatische Schutzstopp beibehalten wird.
Parameter
| Name |
Typ |
Standard |
Beschreibung |
CandleType |
DataType |
Zeitrahmen von 1 Minute |
Von der Strategie verarbeitete primäre Kerzenserie. |
AtrPeriod |
int |
14 |
Anzahl der Kerzen, die zum Glätten des ATR-Indikators verwendet werden. |
AtrMultiplier |
decimal |
2.0 |
Auf den ATR-Wert angewendeter Multiplikator, um die Stop-Loss-Distanz abzuleiten. |
RiskPercentage |
decimal |
1.0 |
Prozentsatz des Portfoliowerts, der bei jedem Trade riskiert wird. Auf Null setzen, um ein festes Volumen zu verwenden. |
UseAtrStopLoss |
bool |
true |
Wenn aktiviert, wird der Stopp bei ATR * AtrMultiplier platziert; andernfalls wird ein fester Abstand verwendet. |
FixedStopLossPoints |
int |
50 |
Anzahl der Preisschritte, die für den Schutzstopp verwendet werden, wenn die ATR-basierte Platzierung deaktiviert wird. |
Unterschiede zum Original EA
- StockSharp arbeitet mit Nettopositionen, daher übermittelt die Konvertierung nur Marktkaufaufträge. Ausstiege erfolgen durch das schützende
SellStop, das das EA-Verhalten reproduziert, nach einem Stopp immer flach zu sein.
- MetaTrader stellt die Konstante
_Point für die Tick-Größe bereit. Der Port fragt Security.PriceStep ab und greift auf eine einzelne Währungseinheit zurück, wenn das Instrument keine Tick-Spezifikation bereitstellt.
- Bei der Positionsgröße werden die Volumenfilter von StockSharp (
VolumeStep, MinVolume, MaxVolume) berücksichtigt, um sicherzustellen, dass das Orderbuch die generierten Ordergrößen akzeptiert.
- Die Indikatorverarbeitung erfolgt ereignisgesteuert über
Subscription.Bind(...) statt synchroner iMA/iATR-Aufrufe.
Anwendungstipps
- Stellen Sie sicher, dass das verbundene Portfolio einen korrekten
CurrentValue meldet; andernfalls sinkt die risikobasierte Positionsgröße auf das Volumen Null zurück.
- Die Eigenschaft
Volume fungiert weiterhin als Sicherheitsnetz. Wenn Sie unabhängig von ATR-Berechnungen eine feste Losgröße wünschen, setzen Sie RiskPercentage auf Null und passen Sie Volume an, bevor Sie mit der Strategie beginnen.
- Hängen Sie die Strategie an ein Diagramm an, um die Kerzen, sowohl gleitende Durchschnitte als auch ausgeführte Trades, zu visualisieren. Es hilft zu bestätigen, dass neue Einträge nur dann erscheinen, wenn der schnelle Durchschnitt über dem langsamen schließt und dass Stopps genau unter der letzten Preisschwankung liegen.
- Erwägen Sie,
AtrMultiplier bei volatileren Instrumenten zu erhöhen, um vorzeitige Stop-Outs zu vermeiden, oder deaktivieren Sie die ATR-basierte Platzierung und stellen Sie eine benutzerdefinierte feste Distanz durch FixedStopLossPoints bereit.
Indikatoren
AverageTrueRange (Länge AtrPeriod).
SimpleMovingAverage (schnelle Länge 10).
SimpleMovingAverage (langsame Länge 20).
namespace StockSharp.Samples.Strategies;
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;
public class RiskManagementAtrStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _riskPercentage;
private readonly StrategyParam<bool> _useAtrStopLoss;
private readonly StrategyParam<int> _fixedStopLossPoints;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private AverageTrueRange _atr;
private SimpleMovingAverage _fastMovingAverage;
private SimpleMovingAverage _slowMovingAverage;
private decimal? _lastAtrValue;
private Order _stopLossOrder;
private decimal _priceStep;
private decimal? _virtualStopPrice;
public RiskManagementAtrStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle type", "Primary timeframe processed by the strategy.", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR period", "Number of candles used to smooth the ATR volatility measure.", "Indicator");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR multiplier", "Distance multiplier applied to the ATR for stop-loss placement.", "Risk");
_riskPercentage = Param(nameof(RiskPercentage), 1m)
.SetNotNegative()
.SetDisplay("Risk %", "Percentage of portfolio value risked on every trade.", "Risk");
_useAtrStopLoss = Param(nameof(UseAtrStopLoss), true)
.SetDisplay("Use ATR stop", "Switch between ATR-based and fixed-distance stop-loss modes.", "Risk");
_fixedStopLossPoints = Param(nameof(FixedStopLossPoints), 50)
.SetGreaterThanZero()
.SetDisplay("Fixed stop (points)", "Stop-loss distance expressed in price steps when ATR mode is disabled.", "Risk");
_fastMaPeriod = Param(nameof(FastMaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast MA period", "Length of the fast moving average used for signals.", "Indicators");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Slow MA period", "Length of the slow moving average used for signals.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
public decimal RiskPercentage
{
get => _riskPercentage.Value;
set => _riskPercentage.Value = value;
}
public bool UseAtrStopLoss
{
get => _useAtrStopLoss.Value;
set => _useAtrStopLoss.Value = value;
}
public int FixedStopLossPoints
{
get => _fixedStopLossPoints.Value;
set => _fixedStopLossPoints.Value = value;
}
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atr = null;
_fastMovingAverage = null;
_slowMovingAverage = null;
_lastAtrValue = null;
_stopLossOrder = null;
_priceStep = 0m;
_virtualStopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = Volume > 0m ? Volume : 1m; // Provide a default lot size when no risk-based sizing is used
_priceStep = Security?.PriceStep ?? 0m;
if (_priceStep <= 0m)
_priceStep = 1m; // Fallback to a single currency unit when the instrument does not expose a price step
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
_fastMovingAverage = new SimpleMovingAverage
{
Length = FastMaPeriod
};
_slowMovingAverage = new SimpleMovingAverage
{
Length = SlowMaPeriod
};
_lastAtrValue = null;
CancelStopLossOrder();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_atr, _fastMovingAverage, _slowMovingAverage, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _atr);
DrawIndicator(area, _fastMovingAverage);
DrawIndicator(area, _slowMovingAverage);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal fastMaValue, decimal slowMaValue)
{
if (candle.State != CandleStates.Finished)
return; // Work exclusively with closed candles to avoid premature entries
_lastAtrValue = atrValue;
// Check virtual stop-loss
if (_virtualStopPrice.HasValue && Position > 0m && candle.LowPrice <= _virtualStopPrice.Value)
{
SellMarket(Math.Abs(Position));
_virtualStopPrice = null;
return;
}
if (Position == 0m)
_virtualStopPrice = null;
if (_atr == null || _fastMovingAverage == null || _slowMovingAverage == null)
return;
if (!_atr.IsFormed || !_fastMovingAverage.IsFormed || !_slowMovingAverage.IsFormed)
return; // Ensure all indicators accumulated enough history
if (fastMaValue <= slowMaValue)
return; // The simple moving average crossover only buys when the fast average is above the slow one
if (Position != 0m)
return; // Mimic the MetaTrader expert: enter only when there is no open position
var volume = CalculateOrderVolume(atrValue);
if (volume <= 0m)
return;
CancelStopLossOrder();
BuyMarket(volume);
}
private decimal CalculateOrderVolume(decimal atrValue)
{
var volume = Volume > 0m ? Volume : 0m;
var stopDistance = CalculateStopDistance(atrValue);
if (stopDistance <= 0m)
return 0m; // Skip trading when the stop distance cannot be computed
var riskPercent = RiskPercentage;
if (riskPercent > 0m)
{
var portfolioValue = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (portfolioValue <= 0m)
return 0m; // Unable to size the trade without a portfolio valuation
var riskAmount = portfolioValue * riskPercent / 100m;
if (riskAmount <= 0m)
return 0m;
volume = riskAmount / stopDistance;
}
volume = RoundVolume(volume);
volume = ClampVolume(volume);
return volume > 0m ? volume : 0m;
}
private decimal CalculateStopDistance(decimal atrValue)
{
if (UseAtrStopLoss)
{
if (atrValue <= 0m)
return 0m;
var distance = atrValue * AtrMultiplier;
return distance > 0m ? distance : 0m;
}
var steps = FixedStopLossPoints;
if (steps <= 0)
return 0m;
return steps * _priceStep;
}
private decimal RoundVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
{
var steps = Math.Floor(volume / step);
if (steps <= 0m)
return step; // Use the minimum tradable lot when the calculated volume is below one step
return steps * step;
}
return Math.Round(volume, 2, MidpointRounding.ToZero);
}
private decimal ClampVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var minVolume = Security?.MinVolume;
if (minVolume != null && minVolume.Value > 0m && volume < minVolume.Value)
volume = minVolume.Value;
var maxVolume = Security?.MaxVolume;
if (maxVolume != null && maxVolume.Value > 0m && volume > maxVolume.Value)
volume = maxVolume.Value;
return volume;
}
private decimal AdjustPrice(decimal price)
{
if (price <= 0m)
return 0m;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return Math.Round(price, 4, MidpointRounding.AwayFromZero);
var steps = Math.Floor(price / step);
if (steps <= 0m)
return step; // Never place protective stops at non-positive prices
return steps * step;
}
private void CancelStopLossOrder()
{
if (_stopLossOrder == null)
return;
if (_stopLossOrder.State == OrderStates.Active)
CancelOrder(_stopLossOrder);
_stopLossOrder = null;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Order.Security != Security)
return;
if (Position <= 0m)
CancelStopLossOrder();
if (trade.Order.Side != Sides.Buy)
return; // The expert only opens long trades; sell trades come from stop-loss execution
var atrValue = _lastAtrValue ?? 0m;
var stopDistance = CalculateStopDistance(atrValue);
if (stopDistance <= 0m)
return;
var stopPrice = trade.Trade.Price - stopDistance;
stopPrice = AdjustPrice(stopPrice);
if (stopPrice <= 0m || stopPrice >= trade.Trade.Price)
return; // Do not place invalid protective stops
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
CancelStopLossOrder();
// Use virtual stop-loss instead of SellStop order
_virtualStopPrice = stopPrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class risk_management_atr_strategy(Strategy):
"""ATR risk management with MA crossover, buy-only strategy with virtual stop-loss."""
def __init__(self):
super(risk_management_atr_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle type", "Primary timeframe processed by the strategy", "General")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR period", "Number of candles used to smooth the ATR volatility measure", "Indicator")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR multiplier", "Distance multiplier applied to the ATR for stop-loss placement", "Risk")
self._use_atr_stop_loss = self.Param("UseAtrStopLoss", True) \
.SetDisplay("Use ATR stop", "Switch between ATR-based and fixed-distance stop-loss modes", "Risk")
self._fixed_stop_loss_points = self.Param("FixedStopLossPoints", 50) \
.SetGreaterThanZero() \
.SetDisplay("Fixed stop (points)", "Stop-loss distance expressed in price steps when ATR mode is disabled", "Risk")
self._fast_ma_period = self.Param("FastMaPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Fast MA period", "Length of the fast moving average used for signals", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Slow MA period", "Length of the slow moving average used for signals", "Indicators")
self._last_atr_value = None
self._price_step = 0.0
self._virtual_stop_price = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
@property
def UseAtrStopLoss(self):
return self._use_atr_stop_loss.Value
@property
def FixedStopLossPoints(self):
return self._fixed_stop_loss_points.Value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
def OnReseted(self):
super(risk_management_atr_strategy, self).OnReseted()
self._last_atr_value = None
self._price_step = 0.0
self._virtual_stop_price = None
def OnStarted2(self, time):
super(risk_management_atr_strategy, self).OnStarted2(time)
self._price_step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps > 0:
self._price_step = ps
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
fast_ma = SimpleMovingAverage()
fast_ma.Length = self.FastMaPeriod
slow_ma = SimpleMovingAverage()
slow_ma.Length = self.SlowMaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, fast_ma, slow_ma, self._process_candle).Start()
def _process_candle(self, candle, atr_value, fast_ma_value, slow_ma_value):
if candle.State != CandleStates.Finished:
return
atr_v = float(atr_value)
fast_v = float(fast_ma_value)
slow_v = float(slow_ma_value)
self._last_atr_value = atr_v
if self._virtual_stop_price is not None and self.Position > 0 and float(candle.LowPrice) <= self._virtual_stop_price:
self.SellMarket(abs(self.Position))
self._virtual_stop_price = None
return
if self.Position == 0:
self._virtual_stop_price = None
if fast_v <= slow_v:
return
if self.Position != 0:
return
self.BuyMarket()
stop_distance = self._calculate_stop_distance(atr_v)
if stop_distance > 0:
stop_price = float(candle.ClosePrice) - stop_distance
if stop_price > 0 and stop_price < float(candle.ClosePrice):
self._virtual_stop_price = stop_price
def _calculate_stop_distance(self, atr_value):
if self.UseAtrStopLoss:
if atr_value <= 0:
return 0.0
distance = atr_value * float(self.AtrMultiplier)
return distance if distance > 0 else 0.0
else:
steps = self.FixedStopLossPoints
if steps <= 0:
return 0.0
return steps * self._price_step
def CreateClone(self):
return risk_management_atr_strategy()