Vlt Trader
Стратегия ищет периоды низкой волатильности и выставляет отложенные ордера на пробой. Когда диапазон текущей свечи становится минимальным за заданный период, стратегия размещает стоп-ордера на покупку и продажу около предыдущей свечи.
Параметры
- Period – период для вычисления минимального диапазона.
- Pending level – расстояние в тиках от максимума/минимума предыдущей свечи до стоп-ордеров.
- Stop loss – размер стоп-лосса в тиках.
- Take profit – размер тейк-профита в тиках.
- Candle type – используемый таймфрейм.
Логика
- Для каждой завершённой свечи вычисляется диапазон (High - Low).
- Отслеживается минимальный диапазон за последние Period свечей.
- При появлении нового минимума отменяются все активные ордера и выставляются стопы выше и ниже предыдущей свечи с учётом смещения.
StartProtectionавтоматически управляет стоп-лоссом и тейк-профитом после открытия позиции.
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>
/// Volatility breakout strategy. Enters on a breakout of a low-volatility bar.
/// Uses market orders when price exceeds the high/low of the signal bar.
/// </summary>
public class VltTraderStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalLifeBars;
private Lowest _lowest = null!;
private decimal _prevRange;
private decimal _prevMinRange;
private decimal _signalHigh;
private decimal _signalLow;
private bool _pendingBreakout;
private int _remainingSignalBars;
/// <summary>
/// Indicator period for lowest range.
/// </summary>
public int Period { get => _period.Value; set => _period.Value = value; }
/// <summary>
/// Stop loss in price steps.
/// </summary>
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
/// <summary>
/// Take profit in price steps.
/// </summary>
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Number of bars to keep a pending breakout signal alive.
/// </summary>
public int SignalLifeBars { get => _signalLifeBars.Value; set => _signalLifeBars.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="VltTraderStrategy"/> class.
/// </summary>
public VltTraderStrategy()
{
_period = Param(nameof(Period), 6)
.SetDisplay("Period", "Indicator period", "General")
.SetOptimize(5, 30, 5);
_stopLoss = Param(nameof(StopLoss), 550m)
.SetDisplay("Stop loss", "Stop loss in price steps", "Risk")
.SetOptimize(100m, 2000m, 100m);
_takeProfit = Param(nameof(TakeProfit), 550m)
.SetDisplay("Take profit", "Take profit in price steps", "Risk")
.SetOptimize(100m, 2000m, 100m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candles for calculation", "General");
_signalLifeBars = Param(nameof(SignalLifeBars), 3)
.SetDisplay("Signal Life Bars", "Number of bars to keep pending breakout signal", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRange = 0m;
_prevMinRange = decimal.MaxValue;
_signalHigh = 0m;
_signalLow = 0m;
_pendingBreakout = false;
_remainingSignalBars = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lowest = new Lowest { Length = Period };
_prevRange = 0m;
_prevMinRange = decimal.MaxValue;
_signalHigh = 0m;
_signalLow = 0m;
_pendingBreakout = false;
_remainingSignalBars = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var step = Security?.PriceStep ?? 1m;
StartProtection(
takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var range = candle.HighPrice - candle.LowPrice;
var lowestResult = _lowest.Process(range, candle.OpenTime, true);
if (!_lowest.IsFormed)
return;
var minRange = lowestResult.ToDecimal();
// Check for pending breakout entry
if (_pendingBreakout && Position == 0)
{
if (candle.HighPrice >= _signalHigh && candle.ClosePrice > _signalHigh)
{
BuyMarket();
_pendingBreakout = false;
_remainingSignalBars = 0;
}
else if (candle.LowPrice <= _signalLow && candle.ClosePrice < _signalLow)
{
SellMarket();
_pendingBreakout = false;
_remainingSignalBars = 0;
}
else if (--_remainingSignalBars <= 0)
_pendingBreakout = false;
}
// Detect low-volatility signal: range drops below minimum
var hasPreviousRange = _prevMinRange != decimal.MaxValue;
var isSignal = hasPreviousRange &&
range <= minRange * 1.08m &&
_prevRange > _prevMinRange * 1.05m;
_prevRange = range;
_prevMinRange = minRange;
if (isSignal && Position == 0)
{
_signalHigh = candle.HighPrice;
_signalLow = candle.LowPrice;
_pendingBreakout = true;
_remainingSignalBars = SignalLifeBars;
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import Lowest
from StockSharp.Algo.Strategies import Strategy
class vlt_trader_strategy(Strategy):
def __init__(self):
super(vlt_trader_strategy, self).__init__()
self._period = self.Param("Period", 6) \
.SetDisplay("Period", "Indicator period", "General")
self._stop_loss = self.Param("StopLoss", 550.0) \
.SetDisplay("Stop loss", "Stop loss in price steps", "Risk")
self._take_profit_param = self.Param("TakeProfit", 550.0) \
.SetDisplay("Take profit", "Take profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candles for calculation", "General")
self._signal_life_bars = self.Param("SignalLifeBars", 3) \
.SetDisplay("Signal Life Bars", "Number of bars to keep pending breakout signal", "General")
self._prev_range = 0.0
self._prev_min_range = None
self._signal_high = 0.0
self._signal_low = 0.0
self._pending_breakout = False
self._remaining_signal_bars = 0
self._ranges = []
@property
def period(self):
return self._period.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def take_profit(self):
return self._take_profit_param.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def signal_life_bars(self):
return self._signal_life_bars.Value
def OnReseted(self):
super(vlt_trader_strategy, self).OnReseted()
self._prev_range = 0.0
self._prev_min_range = None
self._signal_high = 0.0
self._signal_low = 0.0
self._pending_breakout = False
self._remaining_signal_bars = 0
self._ranges = []
def OnStarted2(self, time):
super(vlt_trader_strategy, self).OnStarted2(time)
self._prev_range = 0.0
self._prev_min_range = None
self._signal_high = 0.0
self._signal_low = 0.0
self._pending_breakout = False
self._remaining_signal_bars = 0
self._ranges = []
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
self.StartProtection(
Unit(float(self.take_profit) * step, UnitTypes.Absolute),
Unit(float(self.stop_loss) * step, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
range_val = float(candle.HighPrice) - float(candle.LowPrice)
self._ranges.append(range_val)
period = self.period
if len(self._ranges) > period:
self._ranges.pop(0)
if len(self._ranges) < period:
return
min_range = min(self._ranges)
# Check for pending breakout entry
if self._pending_breakout and self.Position == 0:
if float(candle.HighPrice) >= self._signal_high and float(candle.ClosePrice) > self._signal_high:
self.BuyMarket()
self._pending_breakout = False
self._remaining_signal_bars = 0
elif float(candle.LowPrice) <= self._signal_low and float(candle.ClosePrice) < self._signal_low:
self.SellMarket()
self._pending_breakout = False
self._remaining_signal_bars = 0
else:
self._remaining_signal_bars -= 1
if self._remaining_signal_bars <= 0:
self._pending_breakout = False
# Detect low-volatility signal
has_previous_range = self._prev_min_range is not None
is_signal = (has_previous_range and
range_val <= min_range * 1.08 and
self._prev_range > self._prev_min_range * 1.05)
self._prev_range = range_val
self._prev_min_range = min_range
if is_signal and self.Position == 0:
self._signal_high = float(candle.HighPrice)
self._signal_low = float(candle.LowPrice)
self._pending_breakout = True
self._remaining_signal_bars = self.signal_life_bars
def CreateClone(self):
return vlt_trader_strategy()