EMA Cross Strategy
This strategy trades the crossover of two exponential moving averages (EMAs). A long position is opened when the fast EMA crosses above the slow EMA, while a short position is opened when the fast EMA crosses below the slow EMA. The Reverse parameter swaps the EMA roles, effectively inverting the entry signals.
Every position is protected by fixed Take Profit and Stop Loss levels. An optional Trailing Stop follows the price once it moves in the favorable direction, locking in profits.
The strategy processes only finished candles and uses high-level API binding for indicators and candle subscriptions.
Parameters
- Candle type
- Fast EMA length
- Slow EMA length
- 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()