在 GitHub 上查看

EMA Cross 策略

该策略基于两条指数移动平均线 (EMA) 的交叉。 当快速 EMA 上穿慢速 EMA 时开多;当快速 EMA 下穿慢速 EMA 时开空。 Reverse 参数可交换两条 EMA 的角色,从而反转信号方向。

每笔仓位都设置固定的 Take ProfitStop Loss。 可选的 Trailing Stop 在价格向有利方向移动后跟随价格,锁定盈利。

策略仅处理已完成的K线,并使用高级 API 绑定指标和订阅K线数据。

参数

  • K线类型
  • 快速 EMA 长度
  • 慢速 EMA 长度
  • Take profit
  • Stop loss
  • Trailing stop
  • Reverse
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA crossover strategy with optional reversal and trailing stop.
/// Buys when fast EMA crosses above slow EMA, sells on opposite cross.
/// </summary>
public class EmaCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _shortLength;
	private readonly StrategyParam<int> _longLength;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<bool> _reverse;
	private readonly StrategyParam<DataType> _candleType;
	
	private decimal _entryPrice;
	private decimal _trailPrice;
	private bool _isLong;
	private int _lastDirection;
	
	/// <summary>
	/// Fast EMA length.
	/// </summary>
	public int ShortLength { get => _shortLength.Value; set => _shortLength.Value = value; }
	
	/// <summary>
	/// Slow EMA length.
	/// </summary>
	public int LongLength { get => _longLength.Value; set => _longLength.Value = value; }
	
	/// <summary>
	/// Take profit distance in price units.
	/// </summary>
	public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
	
	/// <summary>
	/// Stop loss distance in price units.
	/// </summary>
	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	
	/// <summary>
	/// Trailing stop distance in price units.
	/// </summary>
	public decimal TrailingStop { get => _trailingStop.Value; set => _trailingStop.Value = value; }
	
	/// <summary>
	/// Reverse cross direction.
	/// </summary>
	public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
	
	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	
	/// <summary>
	/// Constructor.
	/// </summary>
	public EmaCrossStrategy()
	{
		_shortLength = Param(nameof(ShortLength), 9)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA Length", "Period of the fast EMA", "EMA");
		
		_longLength = Param(nameof(LongLength), 45)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA Length", "Period of the slow EMA", "EMA");
		
		_takeProfit = Param(nameof(TakeProfit), 25m)
		.SetDisplay("Take Profit", "Profit target in price units", "Risk");
		
		_stopLoss = Param(nameof(StopLoss), 105m)
		.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
		
		_trailingStop = Param(nameof(TrailingStop), 20m)
		.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk");
		
		_reverse = Param(nameof(Reverse), false)
		.SetDisplay("Reverse", "Swap EMA lines", "General");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_trailPrice = 0m;
		_isLong = false;
		_lastDirection = 0;
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		var fastEma = new EMA
		{
			Length = ShortLength
		};
		
		var slowEma = new EMA
		{
			Length = LongLength
		};
		
		var subscription = SubscribeCandles(CandleType);
		
		subscription
		.Bind(fastEma, slowEma, ProcessCandle)
		.Start();
		
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var cross = Reverse ? GetCross(fast, slow) : GetCross(slow, fast);

		if (Position <= 0 && cross == 1)
		{
			_entryPrice = candle.ClosePrice;
			_trailPrice = 0m;
			_isLong = true;
			BuyMarket();
		}
		else if (Position >= 0 && cross == 2)
		{
			_entryPrice = candle.ClosePrice;
			_trailPrice = 0m;
			_isLong = false;
			SellMarket();
		}
		
		if (Position != 0)
		ManagePosition(candle);
	}
	
	private int GetCross(decimal line1, decimal line2)
	{
		var dir = line1 > line2 ? 1 : 2;
		
		if (_lastDirection == 0)
		{
			_lastDirection = dir;
			return 0;
		}
		
		if (dir != _lastDirection)
		{
			_lastDirection = dir;
			return dir;
		}
		
		return 0;
	}
	
	private void ManagePosition(ICandleMessage candle)
	{
		if (_isLong)
		{
			if (TakeProfit > 0m && candle.ClosePrice >= _entryPrice + TakeProfit)
			{
				SellMarket();
				return;
			}

			if (StopLoss > 0m && candle.ClosePrice <= _entryPrice - StopLoss)
			{
				SellMarket();
				return;
			}

			if (TrailingStop > 0m)
			{
				var newStop = candle.ClosePrice - TrailingStop;
				if (_trailPrice < newStop)
				_trailPrice = newStop;

				if (_trailPrice > 0m && candle.ClosePrice <= _trailPrice)
				SellMarket();
			}
		}
		else
		{
			if (TakeProfit > 0m && candle.ClosePrice <= _entryPrice - TakeProfit)
			{
				BuyMarket();
				return;
			}

			if (StopLoss > 0m && candle.ClosePrice >= _entryPrice + StopLoss)
			{
				BuyMarket();
				return;
			}

			if (TrailingStop > 0m)
			{
				var newStop = candle.ClosePrice + TrailingStop;
				if (_trailPrice == 0m || _trailPrice > newStop)
				_trailPrice = newStop;

				if (candle.ClosePrice >= _trailPrice)
				BuyMarket();
			}
		}
	}
}