Molly ETF EMA Crossover Strategy
The strategy enters a long position when the fast EMA crosses above the slow EMA and exits when the fast EMA crosses below. It includes optional parameters to restrict trading to a specific date range.
Details
- Entry Criteria:
- Long: Fast EMA crosses above slow EMA within the date range.
- Long/Short: Long only.
- Exit Criteria:
- Fast EMA crosses below slow EMA or the date range ends.
- Stops: None.
- Default Values:
Fast EMA= 10Slow EMA= 21Start Date= 2018-01-01End Date= 2023-09-07
- Filters:
- Category: Trend following
- Direction: Long
- Indicators: EMA
- Stops: No
- Complexity: Basic
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Low
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>
/// Molly ETF EMA crossover strategy.
/// Goes long when fast EMA crosses above slow EMA and exits on opposite cross.
/// Supports optional date range filter.
/// </summary>
public class MollyEtfEmaCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<bool> _useDateFilter;
private readonly StrategyParam<DateTime> _startDate;
private readonly StrategyParam<DateTime> _endDate;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Fast EMA period length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA period length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Enable trading only within specified date range.
/// </summary>
public bool UseDateFilter
{
get => _useDateFilter.Value;
set => _useDateFilter.Value = value;
}
/// <summary>
/// Start date of allowed trading period.
/// </summary>
public DateTime StartDate
{
get => _startDate.Value;
set => _startDate.Value = value;
}
/// <summary>
/// End date of allowed trading period.
/// </summary>
public DateTime EndDate
{
get => _endDate.Value;
set => _endDate.Value = value;
}
/// <summary>
/// Candle type to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MollyEtfEmaCrossoverStrategy"/> class.
/// </summary>
public MollyEtfEmaCrossoverStrategy()
{
_fastLength = Param(nameof(FastLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Length of the fast EMA", "Parameters");
_slowLength = Param(nameof(SlowLength), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Length of the slow EMA", "Parameters");
_useDateFilter = Param(nameof(UseDateFilter), false)
.SetDisplay("Use Date Filter", "Enable date range filtering", "Date Range");
_startDate = Param(nameof(StartDate), new DateTime(2018, 1, 1))
.SetDisplay("Start Date", "Beginning of trading period", "Date Range");
_endDate = Param(nameof(EndDate), new DateTime(2030, 1, 1))
.SetDisplay("End Date", "End of trading period", "Date Range");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new EMA { Length = FastLength };
var slowEma = new EMA { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
var wasFastAboveSlow = false;
var initialized = false;
var wasInTradeWindow = false;
subscription
.Bind(fastEma, slowEma, (candle, fastValue, slowValue) =>
{
if (candle.State != CandleStates.Finished)
return;
var candleTime = candle.OpenTime;
var inTradeWindow = !UseDateFilter || (candleTime >= StartDate && candleTime < EndDate);
if (!inTradeWindow && wasInTradeWindow)
{
CancelActiveOrders();
ClosePosition();
}
wasInTradeWindow = inTradeWindow;
if (!inTradeWindow)
return;
if (!initialized)
{
if (fastEma.IsFormed && slowEma.IsFormed)
{
wasFastAboveSlow = fastValue > slowValue;
initialized = true;
}
return;
}
var isFastAboveSlow = fastValue > slowValue;
var crossOver = !wasFastAboveSlow && isFastAboveSlow;
var crossUnder = wasFastAboveSlow && !isFastAboveSlow;
if (crossOver && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
if (crossUnder && Position > 0)
SellMarket(Math.Abs(Position));
wasFastAboveSlow = isFastAboveSlow;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class molly_etf_ema_crossover_strategy(Strategy):
"""
Molly ETF EMA crossover: long when fast EMA crosses above slow, exit on opposite cross.
"""
def __init__(self):
super(molly_etf_ema_crossover_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 10).SetDisplay("Fast EMA", "Fast EMA length", "Parameters")
self._slow_length = self.Param("SlowLength", 21).SetDisplay("Slow EMA", "Slow EMA length", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Candles", "General")
self._was_fast_above = False
self._initialized = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(molly_etf_ema_crossover_strategy, self).OnReseted()
self._was_fast_above = False
self._initialized = False
def OnStarted2(self, time):
super(molly_etf_ema_crossover_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
if not self._initialized:
self._was_fast_above = fast > slow
self._initialized = True
return
is_fast_above = fast > slow
cross_over = not self._was_fast_above and is_fast_above
cross_under = self._was_fast_above and not is_fast_above
if cross_over and self.Position <= 0:
self.BuyMarket()
if cross_under and self.Position > 0:
self.SellMarket()
self._was_fast_above = is_fast_above
def CreateClone(self):
return molly_etf_ema_crossover_strategy()