How To Set Backtest Time Ranges
This strategy demonstrates restricting trading to specific date and intraday time windows. It enters long when a fast SMA crosses above a slow SMA and exits on the opposite crossover.
Details
- Data: Price candles.
- Entry Criteria:
- Long: Fast SMA crosses above slow SMA within the selected date and entry time ranges.
- Exit Criteria: Fast SMA crosses below slow SMA within the selected date and exit time ranges.
- Stops: None.
- Default Values:
FastLength= 14SlowLength= 28FromDate= 2021-01-01ThruDate= 2112-01-01EntryStart= 00:00EntryEnd= 00:00ExitStart= 00:00ExitEnd= 00:00
- Filters:
- Category: Trend following
- Direction: Long
- Indicators: SMA
- Complexity: Low
- 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>
/// How To Set Backtest Time Ranges strategy.
/// Goes long when the fast SMA crosses above the slow SMA and closes the long when it crosses below. Signals are taken only
/// between FromDate and ThruDate; entries additionally require the candle time of day to be inside EntryStart..EntryEnd and
/// exits inside ExitStart..ExitEnd. Equal window bounds mean the whole day, and a window may wrap past midnight.
/// </summary>
public class HowToSetBacktestTimeRangesStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DateTimeOffset> _fromDate;
private readonly StrategyParam<DateTimeOffset> _thruDate;
private readonly StrategyParam<TimeSpan> _entryStart;
private readonly StrategyParam<TimeSpan> _entryEnd;
private readonly StrategyParam<TimeSpan> _exitStart;
private readonly StrategyParam<TimeSpan> _exitEnd;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevFast;
private decimal? _prevSlow;
/// <summary>
/// Fast SMA length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow SMA length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// First date of the trading range.
/// </summary>
public DateTimeOffset FromDate
{
get => _fromDate.Value;
set => _fromDate.Value = value;
}
/// <summary>
/// Last date of the trading range.
/// </summary>
public DateTimeOffset ThruDate
{
get => _thruDate.Value;
set => _thruDate.Value = value;
}
/// <summary>
/// Start of the entry time window.
/// </summary>
public TimeSpan EntryStart
{
get => _entryStart.Value;
set => _entryStart.Value = value;
}
/// <summary>
/// End of the entry time window.
/// </summary>
public TimeSpan EntryEnd
{
get => _entryEnd.Value;
set => _entryEnd.Value = value;
}
/// <summary>
/// Start of the exit time window.
/// </summary>
public TimeSpan ExitStart
{
get => _exitStart.Value;
set => _exitStart.Value = value;
}
/// <summary>
/// End of the exit time window.
/// </summary>
public TimeSpan ExitEnd
{
get => _exitEnd.Value;
set => _exitEnd.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HowToSetBacktestTimeRangesStrategy()
{
_fastLength = Param(nameof(FastLength), 14)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Fast SMA length", "Indicators");
_slowLength = Param(nameof(SlowLength), 28)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Slow SMA length", "Indicators");
_fromDate = Param(nameof(FromDate), new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("From Date", "First date of the trading range", "Time");
_thruDate = Param(nameof(ThruDate), new DateTimeOffset(2112, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("Thru Date", "Last date of the trading range", "Time");
_entryStart = Param(nameof(EntryStart), TimeSpan.Zero)
.SetDisplay("Entry Start", "Start of the entry time window", "Time");
_entryEnd = Param(nameof(EntryEnd), TimeSpan.Zero)
.SetDisplay("Entry End", "End of the entry time window", "Time");
_exitStart = Param(nameof(ExitStart), TimeSpan.Zero)
.SetDisplay("Exit Start", "Start of the exit time window", "Time");
_exitEnd = Param(nameof(ExitEnd), TimeSpan.Zero)
.SetDisplay("Exit End", "End of the exit time window", "Time");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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 OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevSlow = null;
var fastSma = new SimpleMovingAverage { Length = FastLength };
var slowSma = new SimpleMovingAverage { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastSma, slowSma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastSma);
DrawIndicator(area, slowSma);
DrawOwnTrades(area);
}
}
private static bool InWindow(TimeSpan time, TimeSpan start, TimeSpan end)
{
if (start == end)
return true;
return start < end
? time >= start && time < end
: time >= start || time < end;
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
var prevFast = _prevFast;
var prevSlow = _prevSlow;
_prevFast = fast;
_prevSlow = slow;
if (prevFast is not decimal pf || prevSlow is not decimal ps)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var openTime = candle.OpenTime;
if (openTime < FromDate.UtcDateTime || openTime > ThruDate.UtcDateTime)
return;
var timeOfDay = openTime.TimeOfDay;
if (pf <= ps && fast > slow && Position <= 0 && InWindow(timeOfDay, EntryStart, EntryEnd))
BuyMarket(Volume + Math.Abs(Position));
else if (pf >= ps && fast < slow && Position > 0 && InWindow(timeOfDay, ExitStart, ExitEnd))
SellMarket(Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DateTimeOffset
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class how_to_set_backtest_time_ranges_strategy(Strategy):
"""
How To Set Backtest Time Ranges strategy.
Goes long when the fast SMA crosses above the slow SMA and closes the long when it crosses below. Signals are taken only
between FromDate and ThruDate; entries additionally require the candle time of day to be inside EntryStart..EntryEnd and
exits inside ExitStart..ExitEnd. Equal window bounds mean the whole day, and a window may wrap past midnight.
"""
def __init__(self):
super(how_to_set_backtest_time_ranges_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 14).SetGreaterThanZero().SetDisplay("Fast Length", "Fast SMA length", "Indicators")
self._slow_length = self.Param("SlowLength", 28).SetGreaterThanZero().SetDisplay("Slow Length", "Slow SMA length", "Indicators")
self._from_date = self.Param("FromDate", DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero)).SetDisplay("From Date", "First date of the trading range", "Time")
self._thru_date = self.Param("ThruDate", DateTimeOffset(2112, 1, 1, 0, 0, 0, TimeSpan.Zero)).SetDisplay("Thru Date", "Last date of the trading range", "Time")
self._entry_start = self.Param("EntryStart", TimeSpan.Zero).SetDisplay("Entry Start", "Start of the entry time window", "Time")
self._entry_end = self.Param("EntryEnd", TimeSpan.Zero).SetDisplay("Entry End", "End of the entry time window", "Time")
self._exit_start = self.Param("ExitStart", TimeSpan.Zero).SetDisplay("Exit Start", "Start of the exit time window", "Time")
self._exit_end = self.Param("ExitEnd", TimeSpan.Zero).SetDisplay("Exit End", "End of the exit time window", "Time")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_fast = None
self._prev_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(how_to_set_backtest_time_ranges_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(how_to_set_backtest_time_ranges_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_sma = SimpleMovingAverage()
fast_sma.Length = self._fast_length.Value
slow_sma = SimpleMovingAverage()
slow_sma.Length = self._slow_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_sma, slow_sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_sma)
self.DrawIndicator(area, slow_sma)
self.DrawOwnTrades(area)
@staticmethod
def _in_window(time, start, end):
if start == end:
return True
if start < end:
return time >= start and time < end
return time >= start or time < end
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast = float(fast_value)
slow = float(slow_value)
prev_fast = self._prev_fast
prev_slow = self._prev_slow
self._prev_fast = fast
self._prev_slow = slow
if prev_fast is None or prev_slow is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
open_time = candle.OpenTime
if open_time < self._from_date.Value.UtcDateTime or open_time > self._thru_date.Value.UtcDateTime:
return
time_of_day = open_time.TimeOfDay
if prev_fast <= prev_slow and fast > slow and self.Position <= 0 and self._in_window(time_of_day, self._entry_start.Value, self._entry_end.Value):
self.BuyMarket(self.Volume + abs(self.Position))
elif prev_fast >= prev_slow and fast < slow and self.Position > 0 and self._in_window(time_of_day, self._exit_start.Value, self._exit_end.Value):
self.SellMarket(self.Position)
def CreateClone(self):
return how_to_set_backtest_time_ranges_strategy()