Day-Trading-Strategie mit Risikomanagement
Diese Strategie geht long, wenn die schnelle EMA die langsame EMA von unten kreuzt, während der RSI unterhalb des Überkauft-Niveaus liegt. Sie geht short, wenn die schnelle EMA die langsame EMA von oben kreuzt, während der RSI oberhalb des Überverkauft-Niveaus liegt. Das Risikomanagement verwendet feste Take-Profit- und Stop-Loss-Prozentwerte.
Exakte Einstieg- und Schutzregeln
Standard: CandleType=1 Minute, FastEmaPeriod=20, SlowEmaPeriod=50, RsiLength=14, RsiOverbought=70, RsiOversold=30, TakeProfitPercent=2, StopLossPercent=1. Auf abgeschlossenen Kerzen Long, falls vorher EMA20 <= EMA50, jetzt EMA20 > EMA50 und RSI14 < 70; Short, falls vorher EMA20 >= EMA50, jetzt EMA20 < EMA50 und RSI14 > 30. Aus Flat oder zur Umkehr einer bestehenden Gegenposition wird Volume + abs(Position) gehandelt. Ohne RSI-Bestätigung kein Einstieg. Ausgeführte Positionen haben nativen festen 2%-Take-Profit und 1%-Stop-Loss, ohne Trailing, zwischen Kerzenabschlüssen aktiv. Zuvor verwendete die Umsetzung nur EMA120/450 ohne RSI oder Schutz. Die Zahlenwerte waren im ursprünglichen README nicht angegeben und sind hier explizit festgelegt.
Parameter
- Kerzentyp
- Schnelle EMA-Länge
- Langsame EMA-Länge
- RSI-Länge
- RSI Überkauft
- RSI Überverkauft
- Take-Profit-Prozent
- Stop-Loss-Prozent
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>
/// Intraday EMA cross with an RSI confirmation and native percent take-profit
/// and stop-loss protection on actual executions.
/// </summary>
public class DayTradingStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private RelativeStrengthIndex _rsi;
private decimal? _previousFast;
private decimal? _previousSlow;
public int FastEmaPeriod { get => _fastEmaPeriod.Value; set => _fastEmaPeriod.Value = value; }
public int SlowEmaPeriod { get => _slowEmaPeriod.Value; set => _slowEmaPeriod.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public decimal RsiOverbought { get => _rsiOverbought.Value; set => _rsiOverbought.Value = value; }
public decimal RsiOversold { get => _rsiOversold.Value; set => _rsiOversold.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DayTradingStrategy()
{
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Length", "Fast EMA signal length", "Indicators");
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Length", "Slow EMA signal length", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI confirmation length", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetRange(0m, 100m)
.SetDisplay("RSI Overbought", "Strict upper RSI bound for long entries", "Signals");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetRange(0m, 100m)
.SetDisplay("RSI Oversold", "Strict lower RSI bound for short entries", "Signals");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Native take-profit percent on filled positions", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Native stop-loss percent on filled positions", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Finished candles for EMA and RSI", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_rsi = null;
_previousFast = null;
_previousSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (FastEmaPeriod >= SlowEmaPeriod)
throw new ArgumentException("FastEmaPeriod must be less than SlowEmaPeriod.");
if (RsiOversold >= RsiOverbought)
throw new ArgumentException("RsiOversold must be less than RsiOverbought.");
_fast = new ExponentialMovingAverage { Length = FastEmaPeriod };
_slow = new ExponentialMovingAverage { Length = SlowEmaPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_previousFast = null;
_previousSlow = null;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fast, _slow, _rsi, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fast);
DrawIndicator(area, _slow);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
StartProtection(
new Unit(TakeProfitPercent, UnitTypes.Percent),
new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Start();
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
var crossUp = _previousFast is decimal prevFast &&
_previousSlow is decimal prevSlow &&
prevFast <= prevSlow && fast > slow;
var crossDown = _previousFast is decimal prevFastDown &&
_previousSlow is decimal prevSlowDown &&
prevFastDown >= prevSlowDown && fast < slow;
// Always record finished-bar averages, including warmup and periods
// when signals are not permitted, so the next cross is genuine.
_previousFast = fast;
_previousSlow = slow;
if (!_fast.IsFormed || !_slow.IsFormed || !_rsi.IsFormed ||
!IsFormedAndOnlineAndAllowTrading())
return;
if (crossUp && rsi < RsiOverbought && Position <= 0m)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDown && rsi > RsiOversold && Position >= 0m)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.BusinessEntities import Subscription
from StockSharp.Messages import Level1Fields
from StockSharp.Algo.Strategies import Strategy
class day_trading_strategy(Strategy):
"""EMA cross with RSI confirmation and native TP/SL percent protection."""
def __init__(self):
super(day_trading_strategy, self).__init__()
self._fast_ema_period = self.Param("FastEmaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA Length", "Fast EMA signal length", "Indicators")
self._slow_ema_period = self.Param("SlowEmaPeriod", 50) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA Length", "Slow EMA signal length", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI confirmation length", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 70.0) \
.SetRange(0.0, 100.0) \
.SetDisplay("RSI Overbought", "Strict long-side RSI upper bound", "Signals")
self._rsi_oversold = self.Param("RsiOversold", 30.0) \
.SetRange(0.0, 100.0) \
.SetDisplay("RSI Oversold", "Strict short-side RSI lower bound", "Signals")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit %", "Native percentage take-profit", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Native percentage stop-loss", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Finished signal candles", "General")
self._fast = None
self._slow = None
self._rsi = None
self._previous_fast = None
self._previous_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(day_trading_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._rsi = None
self._previous_fast = None
self._previous_slow = None
def OnStarted2(self, time):
super(day_trading_strategy, self).OnStarted2(time)
if int(self._fast_ema_period.Value) >= int(self._slow_ema_period.Value):
raise ValueError("FastEmaPeriod must be less than SlowEmaPeriod")
if float(self._rsi_oversold.Value) >= float(self._rsi_overbought.Value):
raise ValueError("RsiOversold must be less than RsiOverbought")
self._fast = ExponentialMovingAverage()
self._fast.Length = int(self._fast_ema_period.Value)
self._slow = ExponentialMovingAverage()
self._slow.Length = int(self._slow_ema_period.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._previous_fast = None
self._previous_slow = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast, self._slow, self._rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast)
self.DrawIndicator(area, self._slow)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(float(self._take_profit_percent.Value), UnitTypes.Percent),
Unit(float(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Start()
def _process_candle(self, candle, fast_val, slow_val, rsi_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
rsi = float(rsi_val)
cross_up = (self._previous_fast is not None and
self._previous_fast <= self._previous_slow and fast > slow)
cross_down = (self._previous_fast is not None and
self._previous_fast >= self._previous_slow and fast < slow)
self._previous_fast = fast
self._previous_slow = slow
if not self._fast.IsFormed or not self._slow.IsFormed or not self._rsi.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if cross_up and rsi < float(self._rsi_overbought.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif cross_down and rsi > float(self._rsi_oversold.Value) and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def CreateClone(self):
return day_trading_strategy()