Ver en GitHub

Timer Strategy

The Timer strategy recalculates breakout levels at fixed time intervals and trades when price crosses these dynamic thresholds. The levels are positioned using Average True Range (ATR) and an optional additional pip distance. The approach seeks to capture short-term breakouts in either direction.

Every WaitSeconds, the strategy sets:

  • Buy level at close + pipDistance + ATR.
  • Sell level at close - pipDistance - ATR.

When the next finished candle closes beyond one of these levels, a market order is placed in the corresponding direction. The position is protected by configurable stop-loss, take-profit, and trailing stop distances.

Trading can be limited to a specific time window using the trading hours settings.

Parameters

  • WaitSeconds – seconds between level recalculations.
  • PipDistance – additional distance from the current price, in points.
  • AtrPeriod – ATR indicator period.
  • TakeProfit – take-profit distance in points.
  • StopLoss – stop-loss distance in points.
  • TrailingStop – trailing stop distance in points.
  • TradeVolume – order volume.
  • CandleType – candle type for calculations.
  • UseTradingHours – enable time-of-day filter.
  • StartTime – trading start time.
  • StopTime – trading stop time.

How It Works

  1. Subscribes to candles and calculates ATR.
  2. On each finished candle:
    • If the configured time interval passed, new buy and sell levels are calculated.
    • If trading hours are enabled, checks that current time is within the allowed window.
    • Places buy or sell market order if price crosses the corresponding level.
  3. Stop-loss, take-profit, and trailing stop are managed automatically by the strategy infrastructure.

Notes

  • The strategy trades both long and short.
  • Works on any instrument and timeframe.
  • ATR-based levels adapt to market volatility, allowing flexible breakout detection.
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>
/// Timer strategy using EMA crossover.
/// </summary>
public class TimerStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TimerStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		SubscribeCandles(CandleType)
			.Bind(fast, slow, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal)
	{
		if (candle.State != CandleStates.Finished) return;

		if (!_hasPrev)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fastVal > slowVal;
		var crossDown = _prevFast >= _prevSlow && fastVal < slowVal;

		if (crossUp && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
		else if (crossDown && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}