EMA WMA Crossover Strategy
Strategy based on crossover between exponential moving average (EMA) and weighted moving average (WMA) calculated on candle open prices. Enters long when EMA crosses below WMA and short when EMA crosses above WMA. Position size is determined by risk percent of account equity. The strategy uses fixed take profit and stop loss distances defined in ticks.
Details
- Entry Criteria:
- Long:
EMA crosses below WMA - Short:
EMA crosses above WMA
- Long:
- Long/Short: Both
- Exit Criteria: Stop loss or take profit
- Stops: Yes
- Default Values:
EmaPeriod= 28WmaPeriod= 8StopLossTicks= 50TakeProfitTicks= 50RiskPercent= 10CandleType= TimeSpan.FromMinutes(1).TimeFrame()
- Filters:
- Category: Moving average crossover
- Direction: Both
- Indicators: EMA, WMA
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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 and WMA crossover strategy with fixed risk management.
/// Goes long when EMA crosses below WMA and short when EMA crosses above WMA.
/// Position size is calculated from a percentage of equity.
/// </summary>
public class EmaWmaCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _wmaPeriod;
private readonly StrategyParam<int> _stopLossTicks;
private readonly StrategyParam<int> _takeProfitTicks;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopLossDistance;
private decimal _takeProfitDistance;
private decimal _prevEma;
private decimal _prevWma;
private bool _hasPrev;
/// <summary>
/// EMA period length.
/// </summary>
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
/// <summary>
/// WMA period length.
/// </summary>
public int WmaPeriod { get => _wmaPeriod.Value; set => _wmaPeriod.Value = value; }
/// <summary>
/// Stop loss distance in ticks.
/// </summary>
public int StopLossTicks { get => _stopLossTicks.Value; set => _stopLossTicks.Value = value; }
/// <summary>
/// Take profit distance in ticks.
/// </summary>
public int TakeProfitTicks { get => _takeProfitTicks.Value; set => _takeProfitTicks.Value = value; }
/// <summary>
/// Percent of equity risked per trade.
/// </summary>
public decimal RiskPercent { get => _riskPercent.Value; set => _riskPercent.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initialize <see cref="EmaWmaCrossoverStrategy"/>.
/// </summary>
public EmaWmaCrossoverStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period length", "Indicators")
;
_wmaPeriod = Param(nameof(WmaPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("WMA Period", "WMA period length", "Indicators")
;
_stopLossTicks = Param(nameof(StopLossTicks), 50)
.SetNotNegative()
.SetDisplay("Stop Loss Ticks", "Stop loss distance in ticks", "Risk");
_takeProfitTicks = Param(nameof(TakeProfitTicks), 50)
.SetNotNegative()
.SetDisplay("Take Profit Ticks", "Take profit distance in ticks", "Risk");
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Risk Percent", "Percent of equity risked per trade", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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();
_stopLossDistance = 0m;
_takeProfitDistance = 0m;
_hasPrev = false;
_prevEma = 0m;
_prevWma = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var tick = Security?.PriceStep ?? 1m;
_stopLossDistance = StopLossTicks * tick;
_takeProfitDistance = TakeProfitTicks * tick;
StartProtection(
takeProfit: new Unit(_takeProfitDistance, UnitTypes.Absolute),
stopLoss: new Unit(_stopLossDistance, UnitTypes.Absolute));
var ema = new EMA { Length = EmaPeriod };
var wma = new WeightedMovingAverage { Length = WmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, wma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawIndicator(area, wma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal wma)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_hasPrev)
{
_prevEma = ema;
_prevWma = wma;
_hasPrev = true;
return;
}
var crossDown = ema < wma && _prevEma > _prevWma;
var crossUp = ema > wma && _prevEma < _prevWma;
var equity = Portfolio?.CurrentValue ?? 0m;
var riskAmount = equity * RiskPercent / 100m;
var volume = _stopLossDistance > 0m ? riskAmount / _stopLossDistance : 0m;
var step = Security?.VolumeStep ?? 1m;
if (step > 0m)
volume = Math.Floor(volume / step) * step;
if (crossDown && Position <= 0)
{
var qty = volume > 0m ? volume + Math.Abs(Position) : Volume + Math.Abs(Position);
BuyMarket(qty);
}
else if (crossUp && Position >= 0)
{
var qty = volume > 0m ? volume + Math.Abs(Position) : Volume + Math.Abs(Position);
SellMarket(qty);
}
_prevEma = ema;
_prevWma = wma;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_wma_crossover_strategy(Strategy):
"""
EMA and WMA crossover strategy with fixed risk management.
Goes long when EMA crosses below WMA and short when EMA crosses above WMA.
Uses StartProtection for TP/SL.
"""
def __init__(self):
super(ema_wma_crossover_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 34) \
.SetDisplay("EMA Period", "EMA period length", "Indicators")
self._wma_period = self.Param("WmaPeriod", 13) \
.SetDisplay("WMA Period", "WMA period length", "Indicators")
self._stop_loss_ticks = self.Param("StopLossTicks", 50) \
.SetDisplay("Stop Loss Ticks", "Stop loss distance in ticks", "Risk")
self._take_profit_ticks = self.Param("TakeProfitTicks", 50) \
.SetDisplay("Take Profit Ticks", "Take profit distance in ticks", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_ema = 0.0
self._prev_wma = 0.0
self._has_prev = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_wma_crossover_strategy, self).OnReseted()
self._has_prev = False
self._prev_ema = 0.0
self._prev_wma = 0.0
def OnStarted2(self, time):
super(ema_wma_crossover_strategy, self).OnStarted2(time)
tick = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
tick = float(self.Security.PriceStep)
if tick <= 0:
tick = 1.0
sl_dist = self._stop_loss_ticks.Value * tick
tp_dist = self._take_profit_ticks.Value * tick
self.StartProtection(
Unit(float(tp_dist), UnitTypes.Absolute),
Unit(float(sl_dist), UnitTypes.Absolute)
)
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
wma = WeightedMovingAverage()
wma.Length = self._wma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, wma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawIndicator(area, wma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_val, wma_val):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_val)
wma_val = float(wma_val)
if not self._has_prev:
self._prev_ema = ema_val
self._prev_wma = wma_val
self._has_prev = True
return
cross_down = ema_val < wma_val and self._prev_ema > self._prev_wma
cross_up = ema_val > wma_val and self._prev_ema < self._prev_wma
if cross_down and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif cross_up and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_ema = ema_val
self._prev_wma = wma_val
def CreateClone(self):
return ema_wma_crossover_strategy()