Данная стратегия — перенос на C# эксперта MetaTrader Exp_XWPR_Histogram_Vol. Алгоритм торгует по смене цветов
пользовательского индикатора XWPR Histogram Vol, который умножает Williams %R на объём свечи и сглаживает результат. В версии
для StockSharp сохранены два независимых торговых слота (основной и дополнительный объём) и полностью воспроизведены правила
входа и выхода, основанные на цветах индикатора.
Обработка ведётся только по закрытым свечам. После формирования новой свечи стратегия анализирует цветовую историю на заданное
количество баров назад и реагирует, когда цветовая зона пересекает бычьи или медвежьи пороги индикатора.
Логика индикатора
Значение Williams %R (WprPeriod) сдвигается на +50 и умножается на выбранный тип объёма (VolumeMode).
Сглаживанию подвергаются и полученный произведение, и исходный объём с помощью одинаковых фильтров (SmoothingMethod,
SmoothingLength, SmoothingPhase).
Из сглаженного объёма вычисляются четыре динамических уровня: HighLevel2, HighLevel1, LowLevel1, LowLevel2.
Цвета гистограммы соответствуют следующим зонам:
0 – выше HighLevel2 (сильная бычья зона).
1 – между HighLevel1 и HighLevel2 (умеренно бычья зона).
2 – между LowLevel1 и HighLevel1 (нейтральная зона).
3 – между LowLevel2 и LowLevel1 (умеренно медвежья зона).
4 – ниже LowLevel2 (сильная медвежья зона).
Правила сигналов
Для расчёта берутся два цвета: бар SignalBar + 1 (более старый) и бар SignalBar (более новый).
Открыть основной лонг (PrimaryVolume) — если старый цвет равен 1, а новый переходит в 2, 3 или 4. Одновременно
формируется запрос на закрытие шортов.
Открыть дополнительный лонг (SecondaryVolume) — если старый цвет 0, а новый отличается от 0. Шорты закрываются.
Открыть основной шорт (PrimaryVolume) — если старый цвет 3, а новый поднимается в 0, 1 или 2. Лонги закрываются.
Открыть дополнительный шорт (SecondaryVolume) — если старый цвет 4, а новый становится 0, 1, 2 или 3, также
закрывая лонги.
Закрыть лонги — когда старый цвет 3 или 4.
Закрыть шорты — когда старый цвет 0 или 1.
Для каждого направления поддерживаются две независимые позиции. Сигнал приводит к заявке только тогда, когда соответствующий
слот свободен и разрешён вход (AllowLongEntry, AllowShortEntry).
Управление рисками
StopLossSteps и TakeProfitSteps переводятся в защитные заявки StockSharp через StartProtection и задаются в шагах цены.
DeviationSteps сохранён ради совместимости с оригиналом и не влияет на выставление рыночных ордеров.
Параметры
Имя
Описание
CandleType
Таймфрейм свечей, которые поступают на индикатор.
PrimaryVolume, SecondaryVolume
Объёмы основного и дополнительного слота.
AllowLongEntry, AllowShortEntry
Разрешение на открытие длинных / коротких позиций.
AllowLongExit, AllowShortExit
Разрешение на закрытие длинных / коротких позиций.
StopLossSteps, TakeProfitSteps
Дистанции защитных ордеров в шагах цены (0 отключает).
DeviationSteps
Параметр совместимости, не используемый при заявках.
SignalBar
Сдвиг по закрытым свечам при чтении цвета (0 — последняя закрытая свеча).
WprPeriod
Период расчёта Williams %R.
VolumeMode
Источник объёма: Tick — количество тиков, Real — реальный объём.
HighLevel2, HighLevel1
Множители верхних бычьих уровней.
LowLevel1, LowLevel2
Множители нижних медвежьих уровней.
SmoothingMethod
Тип сглаживания гистограммы и базового объёма.
SmoothingLength
Длина сглаживающих фильтров.
SmoothingPhase
Фаза для сглаживателей на основе Jurik (для остальных методов игнорируется).
Примечания по использованию
Стратегия торгует единственным инструментом из GetWorkingSecurities() и использует рыночные заявки.
Сигналы рассчитываются один раз на закрытой свече, буфер истории предотвращает повторные заявки на том же баре.
Основной и дополнительный слоты работают независимо; чтобы отключить слот, установите его объём в 0 или снимите флаг
Allow*Entry.
В переносе не используются MetaTrader magic number и режимы маржи. Размер позиции определяется параметрами PrimaryVolume
и SecondaryVolume.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy converted from the MetaTrader expert Exp_XWPR_Histogram_Vol.
/// Computes a volume-weighted Williams %R histogram inline and trades on strong colour transitions.
/// </summary>
public class ExpXwprHistogramVolStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<decimal> _highLevel2;
private readonly StrategyParam<decimal> _lowLevel2;
private readonly StrategyParam<int> _signalCooldownBars;
private WilliamsR _wpr;
private SimpleMovingAverage _histSma;
private SimpleMovingAverage _volSma;
private int? _prevColor;
private int _cooldownRemaining;
private DateTimeOffset? _lastEntryTime;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
public decimal HighLevel2 { get => _highLevel2.Value; set => _highLevel2.Value = value; }
public decimal LowLevel2 { get => _lowLevel2.Value; set => _lowLevel2.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public ExpXwprHistogramVolStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_wprPeriod = Param(nameof(WprPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("WPR Period", "Williams %R lookback", "Indicator");
_smoothingLength = Param(nameof(SmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Smoothing", "Smoothing length", "Indicator");
_highLevel2 = Param(nameof(HighLevel2), 17m)
.SetDisplay("High Level 2", "Strong bullish zone", "Indicator");
_lowLevel2 = Param(nameof(LowLevel2), -17m)
.SetDisplay("Low Level 2", "Strong bearish zone", "Indicator");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 48)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait after a new entry", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_wpr = null;
_histSma = null;
_volSma = null;
_prevColor = null;
_cooldownRemaining = 0;
_lastEntryTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevColor = null;
_cooldownRemaining = 0;
_lastEntryTime = null;
_wpr = new WilliamsR { Length = WprPeriod };
_histSma = new SimpleMovingAverage { Length = SmoothingLength };
_volSma = new SimpleMovingAverage { Length = SmoothingLength };
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;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var wprValue = _wpr.Process(candle);
if (!wprValue.IsFormed)
return;
var wpr = wprValue.ToDecimal();
var volume = candle.TotalVolume > 0m ? candle.TotalVolume : 1m;
var histRaw = (wpr + 50m) * volume;
var histSmoothed = _histSma.Process(new DecimalIndicatorValue(_histSma, histRaw, candle.OpenTime) { IsFinal = true });
var volSmoothed = _volSma.Process(new DecimalIndicatorValue(_volSma, volume, candle.OpenTime) { IsFinal = true });
if (!histSmoothed.IsFormed || !volSmoothed.IsFormed)
return;
var baseline = volSmoothed.ToDecimal();
if (baseline == 0m)
return;
var hist = histSmoothed.ToDecimal();
var strongBullLevel = HighLevel2 * baseline;
var strongBearLevel = LowLevel2 * baseline;
var color = hist >= strongBullLevel ? 0 : hist <= strongBearLevel ? 4 : 2;
if (_prevColor == null)
{
_prevColor = color;
return;
}
var previousColor = _prevColor.Value;
_prevColor = color;
if (_cooldownRemaining > 0 || HasRecentEntry(candle))
return;
if (previousColor != 0 && color == 0 && Position <= 0)
{
var volumeToBuy = Volume + Math.Abs(Position);
BuyMarket(volumeToBuy);
_cooldownRemaining = SignalCooldownBars;
_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
}
else if (previousColor != 4 && color == 4 && Position >= 0)
{
var volumeToSell = Volume + Math.Abs(Position);
SellMarket(volumeToSell);
_cooldownRemaining = SignalCooldownBars;
_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
}
}
private bool HasRecentEntry(ICandleMessage candle)
{
if (!_lastEntryTime.HasValue)
return false;
var candleTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
return candleTime.Date == _lastEntryTime.Value.Date;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CandleIndicatorValue, SimpleMovingAverage, WilliamsR
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_xwpr_histogram_vol_strategy(Strategy):
def __init__(self):
super(exp_xwpr_histogram_vol_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._wpr_period = self.Param("WprPeriod", 7) \
.SetDisplay("WPR Period", "Williams %R lookback", "Indicator")
self._smoothing_length = self.Param("SmoothingLength", 5) \
.SetDisplay("Smoothing", "Smoothing length", "Indicator")
self._high_level2 = self.Param("HighLevel2", Decimal(17)) \
.SetDisplay("High Level 2", "Strong bullish zone", "Indicator")
self._low_level2 = self.Param("LowLevel2", Decimal(-17)) \
.SetDisplay("Low Level 2", "Strong bearish zone", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 48) \
.SetDisplay("Signal Cooldown", "Bars to wait after a new entry", "Trading")
self._wpr = None
self._hist_sma = None
self._vol_sma = None
self._prev_color = None
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def WprPeriod(self):
return self._wpr_period.Value
@property
def SmoothingLength(self):
return self._smoothing_length.Value
@property
def HighLevel2(self):
return self._high_level2.Value
@property
def LowLevel2(self):
return self._low_level2.Value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(exp_xwpr_histogram_vol_strategy, self).OnReseted()
self._wpr = None
self._hist_sma = None
self._vol_sma = None
self._prev_color = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(exp_xwpr_histogram_vol_strategy, self).OnStarted2(time)
self._prev_color = None
self._cooldown_remaining = 0
self._wpr = WilliamsR()
self._wpr.Length = self.WprPeriod
self._hist_sma = SimpleMovingAverage()
self._hist_sma.Length = self.SmoothingLength
self._vol_sma = SimpleMovingAverage()
self._vol_sma.Length = self.SmoothingLength
subscription = self.SubscribeCandles(self.CandleType)
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
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
wpr_result = self._wpr.Process(CandleIndicatorValue(self._wpr, candle))
if not wpr_result.IsFormed:
return
wpr = wpr_result.Value
volume = candle.TotalVolume if candle.TotalVolume > Decimal(0) else Decimal(1)
hist_raw = (wpr + Decimal(50)) * volume
hist_smoothed = process_float(self._hist_sma, hist_raw, candle.OpenTime, True)
vol_smoothed = process_float(self._vol_sma, volume, candle.OpenTime, True)
if not hist_smoothed.IsFormed or not vol_smoothed.IsFormed:
return
baseline = vol_smoothed.Value
if baseline == Decimal(0):
return
hist = hist_smoothed.Value
strong_bull_level = self.HighLevel2 * baseline
strong_bear_level = self.LowLevel2 * baseline
if hist >= strong_bull_level:
color = 0
elif hist <= strong_bear_level:
color = 4
else:
color = 2
if self._prev_color is None:
self._prev_color = color
return
previous_color = self._prev_color
self._prev_color = color
if self._cooldown_remaining > 0:
return
if previous_color != 0 and color == 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif previous_color != 4 and color == 4 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
def CreateClone(self):
return exp_xwpr_histogram_vol_strategy()