Kyrie Crossover-Strategie
Diese Strategie handelt Kreuzungen exponentieller gleitender Durchschnitte (EMA). Sie kauft, wenn der kurzfristige EMA den langfristigen EMA von unten nach oben kreuzt, und geht short, wenn der kurzfristige EMA den langfristigen EMA von oben nach unten kreuzt. Ein Stop-Loss wird in einem konfigurierbaren Prozentsatz vom Einstiegspreis gesetzt.
Parameter
- Kerzentyp
- Kurzfristiger EMA-Zeitraum
- Langfristiger EMA-Zeitraum
- Risikoprozentsatz
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>
/// EMA crossover strategy with configurable stop-loss.
/// Buys when the short EMA crosses above the long EMA and sells short on the opposite crossover.
/// </summary>
public class KyrieCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _shortEmaPeriod;
private readonly StrategyParam<int> _longEmaPeriod;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _maxEntries;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private bool _isLong;
private int _entriesExecuted;
private int _barsSinceSignal;
/// <summary>
/// Short EMA period.
/// </summary>
public int ShortEmaPeriod
{
get => _shortEmaPeriod.Value;
set => _shortEmaPeriod.Value = value;
}
/// <summary>
/// Long EMA period.
/// </summary>
public int LongEmaPeriod
{
get => _longEmaPeriod.Value;
set => _longEmaPeriod.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Maximum entries per run.
/// </summary>
public int MaxEntries
{
get => _maxEntries.Value;
set => _maxEntries.Value = value;
}
/// <summary>
/// Minimum bars between orders.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public KyrieCrossoverStrategy()
{
_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 11)
.SetGreaterThanZero()
.SetDisplay("Short EMA Period", "Period of the short EMA", "EMA Settings")
.SetOptimize(5, 20, 1);
_longEmaPeriod = Param(nameof(LongEmaPeriod), 323)
.SetGreaterThanZero()
.SetDisplay("Long EMA Period", "Period of the long EMA", "EMA Settings")
.SetOptimize(100, 500, 10);
_riskPercent = Param(nameof(RiskPercent), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(0.5m, 5.0m, 0.5m);
_maxEntries = Param(nameof(MaxEntries), 45)
.SetGreaterThanZero()
.SetDisplay("Max Entries", "Maximum entries per run", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 240)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Minimum bars between orders", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_isLong = false;
_entriesExecuted = 0;
_barsSinceSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entriesExecuted = 0;
_barsSinceSignal = CooldownBars;
var shortEma = new EMA { Length = ShortEmaPeriod };
var longEma = new EMA { Length = LongEmaPeriod };
var subscription = SubscribeCandles(CandleType);
var prevShort = 0m;
var prevLong = 0m;
var wasShortBelowLong = false;
var initialized = false;
subscription
.Bind(shortEma, longEma, (candle, shortValue, longValue) =>
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceSignal++;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!initialized && shortEma.IsFormed && longEma.IsFormed)
{
prevShort = shortValue;
prevLong = longValue;
wasShortBelowLong = shortValue < longValue;
initialized = true;
return;
}
if (!initialized)
return;
var isShortBelowLong = shortValue < longValue;
if (wasShortBelowLong != isShortBelowLong && _entriesExecuted < MaxEntries && _barsSinceSignal >= CooldownBars)
{
if (!isShortBelowLong && Position <= 0)
{
_entryPrice = candle.ClosePrice;
_isLong = true;
BuyMarket(Volume + Math.Abs(Position));
_entriesExecuted++;
_barsSinceSignal = 0;
}
else if (isShortBelowLong && Position >= 0)
{
_entryPrice = candle.ClosePrice;
_isLong = false;
SellMarket(Volume + Math.Abs(Position));
_entriesExecuted++;
_barsSinceSignal = 0;
}
wasShortBelowLong = isShortBelowLong;
}
if (Position != 0 && _entryPrice != 0)
CheckStopLoss(candle.ClosePrice);
prevShort = shortValue;
prevLong = longValue;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortEma);
DrawIndicator(area, longEma);
DrawOwnTrades(area);
}
}
private void CheckStopLoss(decimal currentPrice)
{
var stopLossThreshold = _riskPercent.Value / 100m;
if (_isLong && Position > 0)
{
var stopPrice = _entryPrice * (1m - stopLossThreshold);
if (currentPrice <= stopPrice)
{
SellMarket(Math.Abs(Position));
_entryPrice = 0m;
_barsSinceSignal = 0;
}
}
else if (!_isLong && Position < 0)
{
var stopPrice = _entryPrice * (1m + stopLossThreshold);
if (currentPrice >= stopPrice)
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0m;
_barsSinceSignal = 0;
}
}
}
}
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 kyrie_crossover_strategy(Strategy):
def __init__(self):
super(kyrie_crossover_strategy, self).__init__()
self._short_ema_period = self.Param("ShortEmaPeriod", 11) \
.SetGreaterThanZero() \
.SetDisplay("Short EMA Period", "Period of the short EMA", "EMA Settings")
self._long_ema_period = self.Param("LongEmaPeriod", 323) \
.SetGreaterThanZero() \
.SetDisplay("Long EMA Period", "Period of the long EMA", "EMA Settings")
self._risk_percent = self.Param("RiskPercent", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Risk Pct", "Stop loss percentage from entry price", "Risk Management")
self._max_entries = self.Param("MaxEntries", 45) \
.SetGreaterThanZero() \
.SetDisplay("Max Entries", "Maximum entries per run", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 240) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Minimum bars between orders", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._entry_price = 0.0
self._is_long = False
self._entries_executed = 0
self._bars_since_signal = 0
self._prev_short = 0.0
self._prev_long = 0.0
self._was_short_below_long = False
self._initialized = False
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(kyrie_crossover_strategy, self).OnReseted()
self._entry_price = 0.0
self._is_long = False
self._entries_executed = 0
self._bars_since_signal = 0
self._prev_short = 0.0
self._prev_long = 0.0
self._was_short_below_long = False
self._initialized = False
def OnStarted2(self, time):
super(kyrie_crossover_strategy, self).OnStarted2(time)
self._entries_executed = 0
self._bars_since_signal = self._cooldown_bars.Value
short_ema = ExponentialMovingAverage()
short_ema.Length = self._short_ema_period.Value
long_ema = ExponentialMovingAverage()
long_ema.Length = self._long_ema_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(short_ema, long_ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ema)
self.DrawIndicator(area, long_ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, short_val, long_val):
if candle.State != CandleStates.Finished:
return
self._bars_since_signal += 1
sv = float(short_val)
lv = float(long_val)
close = float(candle.ClosePrice)
if not self._initialized:
self._prev_short = sv
self._prev_long = lv
self._was_short_below_long = sv < lv
self._initialized = True
return
is_short_below_long = sv < lv
if self._was_short_below_long != is_short_below_long and self._entries_executed < self._max_entries.Value and self._bars_since_signal >= self._cooldown_bars.Value:
if not is_short_below_long and self.Position <= 0:
self._entry_price = close
self._is_long = True
self.BuyMarket()
self._entries_executed += 1
self._bars_since_signal = 0
elif is_short_below_long and self.Position >= 0:
self._entry_price = close
self._is_long = False
self.SellMarket()
self._entries_executed += 1
self._bars_since_signal = 0
self._was_short_below_long = is_short_below_long
if self.Position != 0 and self._entry_price != 0.0:
self._check_stop_loss(close)
self._prev_short = sv
self._prev_long = lv
def _check_stop_loss(self, current_price):
threshold = float(self._risk_percent.Value) / 100.0
if self._is_long and self.Position > 0:
stop_price = self._entry_price * (1.0 - threshold)
if current_price <= stop_price:
self.SellMarket()
self._entry_price = 0.0
self._bars_since_signal = 0
elif not self._is_long and self.Position < 0:
stop_price = self._entry_price * (1.0 + threshold)
if current_price >= stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._bars_since_signal = 0
def CreateClone(self):
return kyrie_crossover_strategy()