タイマー戦略
タイマー戦略は、固定時間間隔でブレイクアウトレベルを再計算し、価格がこれらの動的な閾値を超えたときに取引を行います。レベルはAverage True Range(ATR)とオプションの追加pip距離を使用して設定されます。このアプローチはどちらの方向にも短期ブレイクアウトをキャプチャすることを目指します。
WaitSecondsごとに、戦略は以下を設定します:
- 買いレベル:
close + pipDistance + ATR - 売りレベル:
close - pipDistance - ATR
次の完成したローソク足がこれらのレベルのいずれかを超えて終値を迎えると、対応する方向に成行注文が出されます。ポジションは設定可能なストップロス、テイクプロフィット、トレーリングストップで保護されます。
取引時間設定を使用して特定の時間ウィンドウに取引を制限できます。
パラメーター
WaitSeconds– レベル再計算の間隔(秒)。PipDistance– 現在価格からの追加距離(ポイント単位)。AtrPeriod– ATRインジケーターの期間。TakeProfit– テイクプロフィット距離(ポイント単位)。StopLoss– ストップロス距離(ポイント単位)。TrailingStop– トレーリングストップ距離(ポイント単位)。TradeVolume– 注文数量。CandleType– 計算に使用するローソク足のタイプ。UseTradingHours– 時間帯フィルターを有効にする。StartTime– 取引開始時刻。StopTime– 取引終了時刻。
仕組み
- ローソク足の購読とATRの計算。
- 各完成ローソク足において:
- 設定した時間間隔が経過した場合、新しい買い・売りレベルを計算する。
- 取引時間が有効な場合、現在時刻が許可ウィンドウ内にあるか確認する。
- 価格が対応するレベルを超えた場合、買いまたは売りの成行注文を出す。
- ストップロス、テイクプロフィット、トレーリングストップは戦略インフラによって自動的に管理される。
注意事項
- 戦略はロングとショートの両方で取引します。
- あらゆるインスツルメントと時間軸で機能します。
- ATRベースのレベルは市場のボラティリティに適応し、柔軟なブレイクアウト検出を可能にします。
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()