Стратегия Williams Fractal Trailing Stops
Стратегия строит длинные и короткие трейлинг‑стопы по фракталам Вильямса и переворачивает позицию при пробое активного стоп‑уровня ценой.
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>
/// Strategy using Williams fractals as trailing stops.
/// Position flips when price breaks the trailing stop level.
/// </summary>
public class WilliamsFractalTrailingStopsStrategy : Strategy
{
private readonly StrategyParam<decimal> _bufferPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
private decimal? _longStop;
private decimal? _shortStop;
/// <summary>
/// Buffer percentage added to fractal price.
/// </summary>
public decimal BufferPercent
{
get => _bufferPercent.Value;
set => _bufferPercent.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="WilliamsFractalTrailingStopsStrategy"/>.
/// </summary>
public WilliamsFractalTrailingStopsStrategy()
{
_bufferPercent = Param(nameof(BufferPercent), 0m)
.SetDisplay("Stop Buffer %", "Percent buffer added to fractal price", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_longStop = null;
_shortStop = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Enqueue(candle.HighPrice);
_lows.Enqueue(candle.LowPrice);
if (_highs.Count > 5)
_highs.Dequeue();
if (_lows.Count > 5)
_lows.Dequeue();
if (_highs.Count == 5 && _lows.Count == 5)
{
var hs = _highs.ToArray();
var ls = _lows.ToArray();
if (hs[2] > hs[0] && hs[2] > hs[1] && hs[2] > hs[3] && hs[2] > hs[4])
{
var price = hs[2] * (1m + BufferPercent / 100m);
_shortStop = _shortStop is decimal s ? Math.Min(s, price) : price;
}
if (ls[2] < ls[0] && ls[2] < ls[1] && ls[2] < ls[3] && ls[2] < ls[4])
{
var price = ls[2] * (1m - BufferPercent / 100m);
_longStop = _longStop is decimal l ? Math.Max(l, price) : price;
}
}
if (_shortStop is decimal shortStop && candle.ClosePrice > shortStop && Position <= 0)
{
BuyMarket();
}
else if (_longStop is decimal longStop && candle.ClosePrice < longStop && Position >= 0)
{
SellMarket();
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class williams_fractal_trailing_stops_strategy(Strategy):
def __init__(self):
super(williams_fractal_trailing_stops_strategy, self).__init__()
self._buffer_percent = self.Param("BufferPercent", 0.0) \
.SetDisplay("Stop Buffer %", "Percent buffer added to fractal price", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._highs = []
self._lows = []
self._long_stop = None
self._short_stop = None
@property
def buffer_percent(self):
return self._buffer_percent.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(williams_fractal_trailing_stops_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._long_stop = None
self._short_stop = None
def OnStarted2(self, time):
super(williams_fractal_trailing_stops_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._long_stop = None
self._short_stop = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
if len(self._highs) > 5:
self._highs.pop(0)
if len(self._lows) > 5:
self._lows.pop(0)
if len(self._highs) == 5 and len(self._lows) == 5:
hs = self._highs
ls = self._lows
if hs[2] > hs[0] and hs[2] > hs[1] and hs[2] > hs[3] and hs[2] > hs[4]:
price = hs[2] * (1.0 + self.buffer_percent / 100.0)
if self._short_stop is not None:
self._short_stop = min(self._short_stop, price)
else:
self._short_stop = price
if ls[2] < ls[0] and ls[2] < ls[1] and ls[2] < ls[3] and ls[2] < ls[4]:
price = ls[2] * (1.0 - self.buffer_percent / 100.0)
if self._long_stop is not None:
self._long_stop = max(self._long_stop, price)
else:
self._long_stop = price
close = float(candle.ClosePrice)
if self._short_stop is not None and close > self._short_stop and self.Position <= 0:
self.BuyMarket()
elif self._long_stop is not None and close < self._long_stop and self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return williams_fractal_trailing_stops_strategy()