Timer-Strategie
Die Timer-Strategie berechnet Ausbruchsniveaus in festen Zeitintervallen neu und handelt, wenn der Preis diese dynamischen Schwellen kreuzt. Die Niveaus werden anhand des Average True Range (ATR) und einem optionalen zusätzlichen Pip-Abstand positioniert. Der Ansatz zielt darauf ab, kurzfristige Ausbrüche in beide Richtungen zu erfassen.
Alle WaitSeconds setzt die Strategie:
- Kaufniveau bei
close + pipDistance + ATR. - Verkaufsniveau bei
close - pipDistance - ATR.
Wenn die nächste abgeschlossene Kerze jenseits eines dieser Niveaus schließt, wird eine Marktorder in der entsprechenden Richtung platziert. Die Position ist durch konfigurierbaren Stop-Loss, Take-Profit und Trailing Stop geschützt.
Der Handel kann mithilfe der Handelszeiteinstellungen auf ein bestimmtes Zeitfenster begrenzt werden.
Parameter
WaitSeconds– Sekunden zwischen Neuberechnungen der Niveaus.PipDistance– zusätzlicher Abstand vom aktuellen Preis in Punkten.AtrPeriod– ATR-Indikatorperiode.TakeProfit– Take-Profit-Abstand in Punkten.StopLoss– Stop-Loss-Abstand in Punkten.TrailingStop– Trailing-Stop-Abstand in Punkten.TradeVolume– Ordervolumen.CandleType– Kerzentyp für Berechnungen.UseTradingHours– Tageszeit-Filter aktivieren.StartTime– Handelsstartzeit.StopTime– Handelsendzeit.
Funktionsweise
- Anmeldung auf Kerzen und ATR-Berechnung.
- Bei jeder abgeschlossenen Kerze:
- Wenn das konfigurierte Zeitintervall abgelaufen ist, werden neue Kauf- und Verkaufsniveaus berechnet.
- Wenn Handelszeiten aktiviert sind, wird geprüft, ob die aktuelle Zeit im erlaubten Fenster liegt.
- Kauf- oder Verkaufs-Marktorder wird platziert, wenn der Preis das entsprechende Niveau kreuzt.
- Stop-Loss, Take-Profit und Trailing Stop werden automatisch von der Strategie-Infrastruktur verwaltet.
Hinweise
- Die Strategie handelt sowohl Long als auch Short.
- Funktioniert mit jedem Instrument und Zeitrahmen.
- ATR-basierte Niveaus passen sich der Marktvolatilität an und ermöglichen eine flexible Ausbruchserkennung.
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 ATR breakout strategy.
/// </summary>
public class TimerStrategy : Strategy
{
private readonly StrategyParam<int> _waitSeconds;
private readonly StrategyParam<decimal> _pipDistance;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _useTradingHours;
private readonly StrategyParam<TimeSpan> _startTime;
private readonly StrategyParam<TimeSpan> _stopTime;
private readonly List<Order> _pendingOrders = [];
private DateTimeOffset? _lastLevelTime;
private decimal? _buyLevel;
private decimal? _sellLevel;
private decimal? _trailExtreme;
public int WaitSeconds { get => _waitSeconds.Value; set => _waitSeconds.Value = value; }
public decimal PipDistance { get => _pipDistance.Value; set => _pipDistance.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TrailingStop { get => _trailingStop.Value; set => _trailingStop.Value = value; }
public decimal TradeVolume { get => _tradeVolume.Value; set => _tradeVolume.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public bool UseTradingHours { get => _useTradingHours.Value; set => _useTradingHours.Value = value; }
public TimeSpan StartTime { get => _startTime.Value; set => _startTime.Value = value; }
public TimeSpan StopTime { get => _stopTime.Value; set => _stopTime.Value = value; }
public TimerStrategy()
{
_waitSeconds = Param(nameof(WaitSeconds), 60).SetGreaterThanZero();
_pipDistance = Param(nameof(PipDistance), 10m).SetNotNegative();
_atrPeriod = Param(nameof(AtrPeriod), 14).SetGreaterThanZero();
_takeProfit = Param(nameof(TakeProfit), 100m).SetNotNegative();
_stopLoss = Param(nameof(StopLoss), 50m).SetNotNegative();
_trailingStop = Param(nameof(TrailingStop), 0m).SetNotNegative();
_tradeVolume = Param(nameof(TradeVolume), 1m).SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame());
_useTradingHours = Param(nameof(UseTradingHours), false);
_startTime = Param(nameof(StartTime), TimeSpan.Zero);
_stopTime = Param(nameof(StopTime), new TimeSpan(23, 59, 59));
OrderRegistering += _pendingOrders.Add;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_lastLevelTime = null;
_buyLevel = null;
_sellLevel = null;
_trailExtreme = null;
_pendingOrders.Clear();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var point = Security?.PriceStep ?? 1m;
if (point <= 0m)
point = 1m;
var take = TakeProfit > 0m ? new Unit(TakeProfit * point, UnitTypes.Absolute) : null;
var stop = StopLoss > 0m ? new Unit(StopLoss * point, UnitTypes.Absolute) : null;
if (take is not null || stop is not null)
StartProtection(take, stop, useMarketOrders: true);
var atr = new AverageTrueRange { Length = AtrPeriod };
SubscribeCandles(CandleType)
.Bind(atr, (candle, atrValue) =>
{
if (candle.State != CandleStates.Finished || !atr.IsFormed || atrValue <= 0m)
return;
ProcessCandle(candle, atrValue);
})
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
var now = candle.CloseTime;
var point = Security?.PriceStep ?? 1m;
if (point <= 0m)
point = 1m;
var position = ApplyTrailingStop(candle, GetExpectedPosition(), point);
if (IsTradingTime(now.TimeOfDay))
{
if (_buyLevel is decimal buy && candle.ClosePrice >= buy && position <= 0m)
{
BuyMarket(TradeVolume + Math.Abs(position));
_trailExtreme = candle.ClosePrice;
}
else if (_sellLevel is decimal sell && candle.ClosePrice <= sell && position >= 0m)
{
SellMarket(TradeVolume + Math.Abs(position));
_trailExtreme = candle.ClosePrice;
}
}
if (_lastLevelTime is null || now - _lastLevelTime.Value >= TimeSpan.FromSeconds(WaitSeconds))
{
(_buyLevel, _sellLevel) = CalculateLevels(candle.ClosePrice, PipDistance, point, atr);
_lastLevelTime = now;
}
}
/// <summary>
/// Position once the orders still in flight are filled. Protection registers its exit before the
/// candle handler runs, so the handler must not close or size against the position that exit closes.
/// </summary>
private decimal GetExpectedPosition()
{
_pendingOrders.RemoveAll(order => order.State is OrderStates.Done or OrderStates.Failed);
var position = Position;
foreach (var order in _pendingOrders)
position += order.Side == Sides.Buy ? order.Balance : -order.Balance;
return position;
}
private decimal ApplyTrailingStop(ICandleMessage candle, decimal position, decimal point)
{
if (position == 0m)
{
_trailExtreme = null;
return position;
}
if (TrailingStop <= 0m || _trailExtreme is not decimal extreme)
return position;
var trail = TrailingStop * point;
// Earlier candles set the extreme: within this candle the order of high and low is unknown.
if (position > 0m)
{
if (candle.LowPrice <= extreme - trail)
{
SellMarket(position);
_trailExtreme = null;
return 0m;
}
_trailExtreme = Math.Max(extreme, candle.HighPrice);
}
else
{
if (candle.HighPrice >= extreme + trail)
{
BuyMarket(-position);
_trailExtreme = null;
return 0m;
}
_trailExtreme = Math.Min(extreme, candle.LowPrice);
}
return position;
}
private bool IsTradingTime(TimeSpan time)
{
if (!UseTradingHours)
return true;
return StartTime <= StopTime
? time >= StartTime && time <= StopTime
: time >= StartTime || time <= StopTime;
}
internal static (decimal buy, decimal sell) CalculateLevels(
decimal close,
decimal pipDistancePoints,
decimal priceStep,
decimal atr)
{
var distance = pipDistancePoints * priceStep + atr;
return (close + distance, close - distance);
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Sides
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class timer_strategy(Strategy):
def __init__(self):
super(timer_strategy, self).__init__()
self._wait_seconds = self.Param("WaitSeconds", 60).SetGreaterThanZero()
self._pip_distance = self.Param("PipDistance", 10.0).SetNotNegative()
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero()
self._take_profit = self.Param("TakeProfit", 100.0).SetNotNegative()
self._stop_loss = self.Param("StopLoss", 50.0).SetNotNegative()
self._trailing_stop = self.Param("TrailingStop", 0.0).SetNotNegative()
self._trade_volume = self.Param("TradeVolume", 1.0).SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._use_trading_hours = self.Param("UseTradingHours", False)
self._start_time = self.Param("StartTime", TimeSpan.Zero)
self._stop_time = self.Param("StopTime", TimeSpan(23, 59, 59))
self._pending_orders = []
self._last_level_time = None
self._buy_level = None
self._sell_level = None
self._trail_extreme = None
self.OrderRegistering += self._track_pending_order
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(timer_strategy, self).OnReseted()
self._last_level_time = None
self._buy_level = None
self._sell_level = None
self._trail_extreme = None
self._pending_orders = []
def OnStarted2(self, time):
super(timer_strategy, self).OnStarted2(time)
point = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if point <= 0:
point = 1.0
tp = float(self._take_profit.Value)
sl = float(self._stop_loss.Value)
take = Unit(tp * point, UnitTypes.Absolute) if tp > 0 else None
stop = Unit(sl * point, UnitTypes.Absolute) if sl > 0 else None
if take is not None or stop is not None:
self.StartProtection(take, stop, useMarketOrders=True)
atr = AverageTrueRange()
atr.Length = int(self._atr_period.Value)
def on_candle(candle, atr_value):
if candle.State != CandleStates.Finished or not atr.IsFormed or float(atr_value) <= 0:
return
self._process_candle(candle, float(atr_value))
self.SubscribeCandles(self._candle_type.Value).Bind(atr, on_candle).Start()
def _process_candle(self, candle, atr):
now = candle.CloseTime
step = self.Security.PriceStep if self.Security is not None else None
point = step if step is not None and step > 0 else Decimal.One
position = self._apply_trailing_stop(candle, self._expected_position(), point)
if self._is_trading_time(now.TimeOfDay):
close = float(candle.ClosePrice)
volume = float(self._trade_volume.Value) + abs(float(position))
if self._buy_level is not None and close >= self._buy_level and position <= 0:
self.BuyMarket(volume)
self._trail_extreme = candle.ClosePrice
elif self._sell_level is not None and close <= self._sell_level and position >= 0:
self.SellMarket(volume)
self._trail_extreme = candle.ClosePrice
wait = int(self._wait_seconds.Value)
if self._last_level_time is None or (now - self._last_level_time).TotalSeconds >= wait:
self._buy_level, self._sell_level = self.calculate_levels(
float(candle.ClosePrice), float(self._pip_distance.Value), float(point), atr)
self._last_level_time = now
def _track_pending_order(self, order):
self._pending_orders.append(order)
def _expected_position(self):
# Protection registers its exit before the candle handler runs, so the handler must not
# close or size against the position that exit closes.
self._pending_orders = [o for o in self._pending_orders if o.State not in (OrderStates.Done, OrderStates.Failed)]
position = self.Position
for order in self._pending_orders:
if order.Side == Sides.Buy:
position += order.Balance
else:
position -= order.Balance
return position
def _apply_trailing_stop(self, candle, position, point):
if position == 0:
self._trail_extreme = None
return position
trailing_stop = float(self._trailing_stop.Value)
if trailing_stop <= 0 or self._trail_extreme is None:
return position
trail = Decimal(trailing_stop) * point
extreme = self._trail_extreme
# Earlier candles set the extreme: within this candle the order of high and low is unknown.
if position > 0:
if candle.LowPrice <= extreme - trail:
self.SellMarket(position)
self._trail_extreme = None
return Decimal.Zero
self._trail_extreme = Math.Max(extreme, candle.HighPrice)
else:
if candle.HighPrice >= extreme + trail:
self.BuyMarket(Math.Abs(position))
self._trail_extreme = None
return Decimal.Zero
self._trail_extreme = Math.Min(extreme, candle.LowPrice)
return position
def _is_trading_time(self, value):
if not bool(self._use_trading_hours.Value):
return True
start = self._start_time.Value
stop = self._stop_time.Value
return start <= value <= stop if start <= stop else value >= start or value <= stop
@staticmethod
def calculate_levels(close, pip_distance_points, price_step, atr):
distance = pip_distance_points * price_step + atr
return close + distance, close - distance
def CreateClone(self):
return timer_strategy()