Auf GitHub ansehen

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;
			}
		}
	}
}