MA L World Strategy
Weighted moving average crossover strategy with trailing stop based on EMA.
Opens long when the fast WMA crosses above the slow WMA. Opens short when the fast WMA crosses below the slow WMA. Uses a 92-period EMA as a trailing exit and fixed stop-loss and take-profit levels.
Details
- Entry Criteria:
- Long:
Fast WMAcrosses aboveSlow WMA - Short:
Fast WMAcrosses belowSlow WMA
- Long:
- Long/Short: Both
- Exit Criteria: Opposite crossover or price crossing trailing EMA
- Stops: Stop loss and take profit via
StartProtection - Default Values:
FastMaLength= 12SlowMaLength= 25TrailingMaPeriod= 92StopLoss= 95mTakeProfit= 670mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: WMA, EMA
- Stops: Stop loss, take profit, trailing EMA
- Complexity: Basic
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving average crossover strategy with trailing EMA and fixed stop levels.
/// </summary>
public class MaLWorldStrategy : Strategy
{
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _trailingMaPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minSpreadPercent;
private readonly StrategyParam<int> _cooldownBars;
private WeightedMovingAverage _fastMa = null!;
private WeightedMovingAverage _slowMa = null!;
private ExponentialMovingAverage _trailingMa = null!;
private bool _initialized;
private decimal _prevFast;
private decimal _prevSlow;
private int _cooldownRemaining;
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
public int TrailingMaPeriod
{
get => _trailingMaPeriod.Value;
set => _trailingMaPeriod.Value = value;
}
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal MinSpreadPercent
{
get => _minSpreadPercent.Value;
set => _minSpreadPercent.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public MaLWorldStrategy()
{
_fastMaLength = Param(nameof(FastMaLength), 12)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Period of the fast weighted MA", "Parameters");
_slowMaLength = Param(nameof(SlowMaLength), 25)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Period of the slow weighted MA", "Parameters");
_trailingMaPeriod = Param(nameof(TrailingMaPeriod), 92)
.SetGreaterThanZero()
.SetDisplay("Trailing EMA", "Period of trailing EMA", "Risk");
_stopLoss = Param(nameof(StopLoss), 95m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Fixed stop loss distance", "Risk");
_takeProfit = Param(nameof(TakeProfit), 670m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Fixed take profit distance", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_minSpreadPercent = Param(nameof(MinSpreadPercent), 0.0008m)
.SetDisplay("Minimum Spread %", "Minimum normalized spread between fast and slow MA", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 3)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = null!;
_slowMa = null!;
_trailingMa = null!;
_initialized = false;
_prevFast = 0m;
_prevSlow = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = new WeightedMovingAverage { Length = FastMaLength };
_slowMa = new WeightedMovingAverage { Length = SlowMaLength };
_trailingMa = new ExponentialMovingAverage { Length = TrailingMaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fastMa, _slowMa, _trailingMa, ProcessCandle).Start();
StartProtection(
stopLoss: new Unit(StopLoss, UnitTypes.Absolute),
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawIndicator(area, _trailingMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal trail)
{
if (candle.State != CandleStates.Finished || !_fastMa.IsFormed || !_slowMa.IsFormed || !_trailingMa.IsFormed)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (!_initialized)
{
_prevFast = fast;
_prevSlow = slow;
_initialized = true;
return;
}
var spreadPercent = candle.ClosePrice != 0m ? Math.Abs(fast - slow) / candle.ClosePrice : 0m;
var crossUp = _prevFast <= _prevSlow && fast > slow && spreadPercent >= MinSpreadPercent;
var crossDown = _prevFast >= _prevSlow && fast < slow && spreadPercent >= MinSpreadPercent;
if (_cooldownRemaining == 0)
{
if (crossUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (crossDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevFast = fast;
_prevSlow = slow;
if (Position > 0 && candle.LowPrice <= trail)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && candle.HighPrice >= trail)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
}
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 WeightedMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_l_world_strategy(Strategy):
def __init__(self):
super(ma_l_world_strategy, self).__init__()
self._fast_ma_length = self.Param("FastMaLength", 12) \
.SetDisplay("Fast MA", "Period of the fast weighted MA", "Parameters")
self._slow_ma_length = self.Param("SlowMaLength", 25) \
.SetDisplay("Slow MA", "Period of the slow weighted MA", "Parameters")
self._trailing_ma_period = self.Param("TrailingMaPeriod", 92) \
.SetDisplay("Trailing EMA", "Period of trailing EMA", "Risk")
self._stop_loss = self.Param("StopLoss", 95.0) \
.SetDisplay("Stop Loss", "Fixed stop loss distance", "Risk")
self._take_profit = self.Param("TakeProfit", 670.0) \
.SetDisplay("Take Profit", "Fixed take profit distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._min_spread_percent = self.Param("MinSpreadPercent", 0.0008) \
.SetDisplay("Minimum Spread %", "Minimum normalized spread between fast and slow MA", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 3) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._initialized = False
self._prev_fast = 0.0
self._prev_slow = 0.0
self._cooldown_remaining = 0
@property
def fast_ma_length(self):
return self._fast_ma_length.Value
@property
def slow_ma_length(self):
return self._slow_ma_length.Value
@property
def trailing_ma_period(self):
return self._trailing_ma_period.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def min_spread_percent(self):
return self._min_spread_percent.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(ma_l_world_strategy, self).OnReseted()
self._initialized = False
self._prev_fast = 0.0
self._prev_slow = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ma_l_world_strategy, self).OnStarted2(time)
fast_ma = WeightedMovingAverage()
fast_ma.Length = self.fast_ma_length
slow_ma = WeightedMovingAverage()
slow_ma.Length = self.slow_ma_length
trailing_ma = ExponentialMovingAverage()
trailing_ma.Length = self.trailing_ma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ma, slow_ma, trailing_ma, self.process_candle).Start()
self.StartProtection(
Unit(float(self.take_profit), UnitTypes.Absolute),
Unit(float(self.stop_loss), UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawIndicator(area, trailing_ma)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast, slow, trail):
if candle.State != CandleStates.Finished:
return
fast = float(fast)
slow = float(slow)
trail = float(trail)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if not self._initialized:
self._prev_fast = fast
self._prev_slow = slow
self._initialized = True
return
close = float(candle.ClosePrice)
spread_percent = abs(fast - slow) / close if close != 0 else 0.0
min_sp = float(self.min_spread_percent)
cross_up = self._prev_fast <= self._prev_slow and fast > slow and spread_percent >= min_sp
cross_down = self._prev_fast >= self._prev_slow and fast < slow and spread_percent >= min_sp
if self._cooldown_remaining == 0:
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_fast = fast
self._prev_slow = slow
if self.Position > 0 and float(candle.LowPrice) <= trail:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
elif self.Position < 0 and float(candle.HighPrice) >= trail:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
def CreateClone(self):
return ma_l_world_strategy()