Стратегия EMA Cross
Стратегия торгует пересечение двух экспоненциальных скользящих средних (EMA). Лонг открывается, когда быстрая EMA пересекает медленную снизу вверх. Шорт открывается, когда быстрая EMA пересекает медленную сверху вниз. Параметр Reverse меняет роли EMA, инвертируя торговые сигналы.
Каждая позиция защищена уровнями Take Profit и Stop Loss. Опциональный Trailing Stop следует за ценой после движения в прибыльном направлении, фиксируя результат.
Стратегия обрабатывает только завершённые свечи и использует высокоуровневый API для подписки на свечи и индикаторы.
Параметры
- Тип свечей
- Длина быстрой 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()