Открыть на GitHub

Стратегия Vlado Williams %R Threshold

Общее описание

Vlado Williams %R Threshold — перенос советника MetaTrader 4 Vlado_www_forex-instruments_info.mq4 на платформу StockSharp. Алгоритм использует один индикатор Williams %R и меняет направление торговли при пересечении заданного уровня. В любой момент времени стратегия удерживает лишь одну позицию, полностью повторяя последовательность вызовов CheckForSignals → CheckForClose → CheckForOpen из оригинального кода.

Основные особенности

  • Управление позицией основано на сравнении Williams %R с пороговым значением WprLevel (по умолчанию -50).
  • Стратегия не переворачивается мгновенно: сначала закрывает открытую позицию и лишь на следующей свече рассматривает вход в противоположную сторону.
  • Встроенная опция money-management рассчитывает объём по формуле equity × MaximumRiskPercent ÷ 100 ÷ closePrice, что соответствует исходному правилу AccountFreeMargin * MaximumRisk / price.
  • Параметр CandleType задаёт таймфрейм для сигналов и ордеров; стандартное значение — 15-минутные свечи.

Логика торговли

  1. При старте подписываемся на свечи CandleType и вычисляем Williams %R длиной WprLength.
  2. Когда Williams %R становится выше порога WprLevel:
    • Фиксируется бычий режим. Если позиции нет и предыдущая сделка не была длинной, отправляется рыночная заявка на покупку.
    • При наличии короткой позиции она закрывается немедленно; открытие лонга переносится на следующий завершённый бар.
  3. Когда Williams %R опускается ниже WprLevel:
    • Включается медвежий режим. Если позиции нет и предыдущая сделка не была короткой, отправляется рыночная заявка на продажу.
    • При наличии лонга он закрывается мгновенно.
  4. Метод CalculateOrderVolume подбирает размер заявки:
    • При активном флаге UseRiskMoneyManagement используется текущая оценка капитала портфеля. При отсутствии данных или выключенном флаге применяется базовое свойство Volume.
    • Полученный объём нормализуется по VolumeStep инструмента и ограничивается диапазоном MinVolume/MaxVolume, если он указан биржей.

Тем самым достигнута полная совместимость с MT4-версией: выход из позиции всегда происходит раньше, чем попытка переворота.

Особенности переноса

  • Значение MaximumRiskPercent по умолчанию равно 10, что эквивалентно исходному MaximumRisk = 10 в советнике.
  • Параметр shift в исходнике всегда был равен нулю и потому исключён.
  • Цветовые константы (Red, Blue) из вызовов OrderSend в StockSharp не нужны и были удалены.
  • Параметр проскальзывания больше не требуется: рыночные заявки исполняются по текущей лучшей цене.

Параметры

Параметр Тип Значение по умолчанию Описание
CandleType DataType 15-минутные свечи Таймфрейм для расчёта сигналов и размещения заявок.
WprLength int 100 Глубина расчёта индикатора Williams %R.
WprLevel decimal -50 Порог между бычьим и медвежьим режимами.
UseRiskMoneyManagement bool false Включает динамический расчёт объёма.
MaximumRiskPercent decimal 10 Доля капитала, задействуемая в сделке при включённом money-management.

Важно. В стратегии отсутствуют стоп-ордера и тейк-профиты. Для ограничения риска рекомендуется использовать StartProtection() либо внешние защитные механизмы.

Рекомендации по использованию

  1. Назначайте инструмент с корректными параметрами PriceStep, StepPrice, VolumeStep, а также лимитами MinVolume/MaxVolume, чтобы функция расчёта объёма работала без ошибок.
  2. Свойство Volume служит базовым значением на случай отсутствия данных по капиталу или отключённого риск-менеджмента.
  3. Для адаптации к различным рынкам оптимизируйте WprLength и WprLevel. Более крайние уровни (-20 и -80) уменьшают частоту сделок, а значение -50 делает стратегию почти постоянно инвестированной.
  4. Поскольку система является трендовой, в боковике возможны частые ложные сигналы. Добавьте фильтры по волатильности, объёму или старшему таймфрейму при необходимости.

Отличия от версии MT4

  • Реализация основана на высокоуровневых подписках StockSharp; отсутствует ручной перебор ордеров и истории.
  • Расчёт лота использует Portfolio.CurrentValue. Если значение недоступно, стратегия возвращается к фиксированному объёму, что воспроизводит поведение исходного mm = 0.
  • Комментарии и описания параметров переведены на английский язык согласно требованиям репозитория.

Контрольный список

  • ✅ Код следует внутреннему стандарту: file-scoped namespace, табуляция, inheritdoc в переопределённых методах.
  • ✅ Параметры созданы через Param() и отмечены SetCanOptimize(true) там, где уместно.
  • ✅ Значение Williams %R берётся из Bind, без использования запрещённых GetValue()/GetCurrentValue().
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>
/// Port of the "Vlado" MetaTrader expert advisor that trades Williams %R level breakouts.
/// </summary>
public class VladoWilliamsPercentRangeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wprLength;
	private readonly StrategyParam<decimal> _wprLevel;
	private readonly StrategyParam<bool> _useRiskMoneyManagement;
	private readonly StrategyParam<decimal> _maximumRiskPercent;

	private bool _buySignal;
	private bool _sellSignal;
	private int _lastSignal;

	private WilliamsR _williamsR;

	/// <summary>
	/// Initializes a new instance of the <see cref="VladoWilliamsPercentRangeStrategy"/> class.
	/// </summary>
	public VladoWilliamsPercentRangeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General");

		_wprLength = Param(nameof(WprLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("Williams %R Period", "Lookback period for Williams %R", "Indicators")
			;

		_wprLevel = Param(nameof(WprLevel), -50m)
			.SetDisplay("Williams %R Level", "Threshold that flips the bias", "Signals")
			;

		_useRiskMoneyManagement = Param(nameof(UseRiskMoneyManagement), false)
			.SetDisplay("Risk Money Management", "Recalculate volume from equity before entries", "Risk")
			;

		_maximumRiskPercent = Param(nameof(MaximumRiskPercent), 10m)
			.SetDisplay("Maximum Risk Percent", "Equity percentage used when sizing orders", "Risk")
			;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Williams %R lookback length.
	/// </summary>
	public int WprLength
	{
		get => _wprLength.Value;
		set => _wprLength.Value = value;
	}

	/// <summary>
	/// Threshold that toggles bullish or bearish bias.
	/// </summary>
	public decimal WprLevel
	{
		get => _wprLevel.Value;
		set => _wprLevel.Value = value;
	}

	/// <summary>
	/// Enables risk based volume sizing similar to the MetaTrader version.
	/// </summary>
	public bool UseRiskMoneyManagement
	{
		get => _useRiskMoneyManagement.Value;
		set => _useRiskMoneyManagement.Value = value;
	}

	/// <summary>
	/// Fraction of the current equity used to size entries when risk management is enabled.
	/// </summary>
	public decimal MaximumRiskPercent
	{
		get => _maximumRiskPercent.Value;
		set => _maximumRiskPercent.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_williamsR = null;
		_buySignal = false;
		_sellSignal = false;
		_lastSignal = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_williamsR = new WilliamsR
		{
			Length = WprLength
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_williamsR, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _williamsR);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal wprValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		UpdateSignals(wprValue);

		if (Position != 0)
		{
			if (Position > 0 && _sellSignal)
			{
				// Exit long positions when the bearish Williams %R regime appears.
				if (Position > 0) SellMarket(); else BuyMarket();
				return;
			}

			if (Position < 0 && _buySignal)
			{
				// Exit short positions when the bullish Williams %R regime appears.
				if (Position > 0) SellMarket(); else BuyMarket();
				return;
			}

			return;
		}

		// No open position - evaluate fresh entries.
		var volume = CalculateOrderVolume(candle.ClosePrice);
		if (volume <= 0m)
			return;

		if (_sellSignal && _lastSignal != -1)
		{
			// Enter short once Williams %R falls below the chosen level.
			SellMarket();
			_lastSignal = -1;
			return;
		}

		if (_buySignal && _lastSignal != 1)
		{
			// Enter long once Williams %R rises above the chosen level.
			BuyMarket();
			_lastSignal = 1;
		}
	}

	private void UpdateSignals(decimal wprValue)
	{
		// Williams %R values are negative: less negative indicates bullish momentum.
		if (wprValue > WprLevel)
		{
			_buySignal = true;
			_sellSignal = false;
		}
		else if (wprValue < WprLevel)
		{
			_sellSignal = true;
			_buySignal = false;
		}
	}

	private decimal CalculateOrderVolume(decimal referencePrice)
	{
		var volume = Volume;

		if (UseRiskMoneyManagement && MaximumRiskPercent > 0m && referencePrice > 0m)
		{
			var equity = Portfolio?.CurrentValue ?? 0m;
			if (equity > 0m)
			{
				// Convert risk capital to volume using the latest close price as approximation.
				volume = equity * (MaximumRiskPercent / 100m) / referencePrice;
			}
		}

		return NormalizeVolume(volume);
	}

	private decimal NormalizeVolume(decimal volume)
	{
		var normalized = volume;

		if (Security?.VolumeStep is decimal step && step > 0m)
		{
			var steps = decimal.Floor(normalized / step);
			normalized = steps * step;

			if (normalized <= 0m)
				normalized = step;
		}

		if (Security?.MinVolume is decimal minVolume && minVolume > 0m && normalized < minVolume)
			normalized = minVolume;

		if (Security?.MaxVolume is decimal maxVolume && maxVolume > 0m && normalized > maxVolume)
			normalized = maxVolume;

		if (normalized <= 0m && volume > 0m)
			normalized = volume;

		return normalized;
	}
}