EMA Cross 策略
该策略基于两条指数移动平均线 (EMA) 的交叉。 当快速 EMA 上穿慢速 EMA 时开多;当快速 EMA 下穿慢速 EMA 时开空。 Reverse 参数可交换两条 EMA 的角色,从而反转信号方向。
每笔仓位都设置固定的 Take Profit 和 Stop 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();
}
}
}
}
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 as EMA
from StockSharp.Algo.Strategies import Strategy
class ema_cross_strategy(Strategy):
def __init__(self):
super(ema_cross_strategy, self).__init__()
self._short_length = self.Param("ShortLength", 9)
self._long_length = self.Param("LongLength", 45)
self._take_profit = self.Param("TakeProfit", 25.0)
self._stop_loss = self.Param("StopLoss", 105.0)
self._trailing_stop = self.Param("TrailingStop", 20.0)
self._reverse = self.Param("Reverse", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._entry_price = 0.0
self._trail_price = 0.0
self._is_long = False
self._last_direction = 0
@property
def ShortLength(self): return self._short_length.Value
@ShortLength.setter
def ShortLength(self, v): self._short_length.Value = v
@property
def LongLength(self): return self._long_length.Value
@LongLength.setter
def LongLength(self, v): self._long_length.Value = v
@property
def TakeProfit(self): return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, v): self._take_profit.Value = v
@property
def StopLoss(self): return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, v): self._stop_loss.Value = v
@property
def TrailingStop(self): return self._trailing_stop.Value
@TrailingStop.setter
def TrailingStop(self, v): self._trailing_stop.Value = v
@property
def Reverse(self): return self._reverse.Value
@Reverse.setter
def Reverse(self, v): self._reverse.Value = v
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, v): self._candle_type.Value = v
def OnStarted2(self, time):
super(ema_cross_strategy, self).OnStarted2(time)
fast_ema = EMA()
fast_ema.Length = self.ShortLength
slow_ema = EMA()
slow_ema.Length = self.LongLength
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast_ema, slow_ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
f = float(fast)
s = float(slow)
cross = self._get_cross(f, s) if self.Reverse else self._get_cross(s, f)
if self.Position <= 0 and cross == 1:
self._entry_price = float(candle.ClosePrice)
self._trail_price = 0.0
self._is_long = True
self.BuyMarket()
elif self.Position >= 0 and cross == 2:
self._entry_price = float(candle.ClosePrice)
self._trail_price = 0.0
self._is_long = False
self.SellMarket()
if self.Position != 0:
self._manage_position(candle)
def _get_cross(self, line1, line2):
d = 1 if line1 > line2 else 2
if self._last_direction == 0:
self._last_direction = d
return 0
if d != self._last_direction:
self._last_direction = d
return d
return 0
def _manage_position(self, candle):
close = float(candle.ClosePrice)
tp = float(self.TakeProfit)
sl = float(self.StopLoss)
ts = float(self.TrailingStop)
if self._is_long:
if tp > 0 and close >= self._entry_price + tp:
self.SellMarket(); return
if sl > 0 and close <= self._entry_price - sl:
self.SellMarket(); return
if ts > 0:
ns = close - ts
if self._trail_price < ns:
self._trail_price = ns
if self._trail_price > 0 and close <= self._trail_price:
self.SellMarket()
else:
if tp > 0 and close <= self._entry_price - tp:
self.BuyMarket(); return
if sl > 0 and close >= self._entry_price + sl:
self.BuyMarket(); return
if ts > 0:
ns = close + ts
if self._trail_price == 0 or self._trail_price > ns:
self._trail_price = ns
if close >= self._trail_price:
self.BuyMarket()
def OnReseted(self):
super(ema_cross_strategy, self).OnReseted()
self._entry_price = 0.0
self._trail_price = 0.0
self._is_long = False
self._last_direction = 0
def CreateClone(self):
return ema_cross_strategy()