WPR Custom Cloud Simple — это порт эксперта MetaTrader WPR Custom Cloud Simple.mq5 на платформу StockSharp. Робот отслеживает осциллятор Larry Williams %R и открывает сделки, когда индикатор выходит из зон перепроданности или перекупленности. В версии на C# сохранены все ключевые особенности оригинала: анализ только на открытии новой свечи, разворот позиции при обратном сигнале и полный отказ от стоп-лоссов, тейк-профитов и трейлинг-стопов.
Логика торговли
Подписаться на выбранный таймфрейм (CandleType) и передавать свечи в индикатор WilliamsR.
Ждать закрытия свечи — стратегия не реагирует на незавершённые бары.
Хранить два последних завершённых значения %R, аналогичные обращениям wpr[1] и wpr[2] в MetaTrader.
Формировать сигналы при пересечениях порогов:
Покупка: предыдущая свеча закрылась выше OversoldLevel, а свеча до неё находилась ниже уровня — полный аналог условия wpr[2] < уровень и wpr[1] > уровень в исходном коде.
Продажа: предыдущая свеча закрылась ниже OverboughtLevel, а ещё одна свеча назад была выше порога — соответствует проверке wpr[2] > уровень и wpr[1] < уровень.
При покупке стратегия сначала закрывает все короткие позиции и покупает базовый объём. При продаже — закрывает длинную позицию и продаёт такой же объём. Благодаря неттинговой модели StockSharp достаточно отправить BuyMarket/SellMarket с объёмом Volume + |Position|, чтобы повторить метатрейдеровский сценарий «закрыть и развернуться».
Никаких дополнительных выходов не используется: только новый противоположный сигнал завершает открытую позицию, что полностью соответствует оригинальному эксперту.
Параметры
Имя
Тип
Значение по умолчанию
Аналог в MetaTrader
Описание
WprPeriod
int
14
Inp_WPR_Period
Длина расчёта индикатора Williams %R.
OverboughtLevel
decimal
-20
Inp_WPR_Level1
Граница перекупленности; пробой вниз инициирует продажи.
OversoldLevel
decimal
-80
Inp_WPR_Level2
Граница перепроданности; пробой вверх инициирует покупки.
CandleType
DataType
Таймфрейм 1 час
InpWorkingPeriod
Тип свечей, используемый для расчёта и сигналов.
Volume
decimal
Базовый объём стратегии
InpLots
Лот для рыночных ордеров; перед открытием позиции стратегия автоматически сводит нетто-позицию к нулю.
Отличия от оригинального эксперта
В StockSharp используется неттинговый учёт позиций. Закрытие встречного плеча реализовано увеличением объёма рыночного ордера, поэтому необходимость в структурах STRUCT_POSITION отпала.
Все вспомогательные классы MetaTrader (CTrade, CPositionInfo, проверки маржи и т. п.) заменены встроенными механизмами StockSharp. Управление объёмом полностью базируется на Strategy.Volume и параметрах инструмента.
Логирование упрощено: высокоуровневый API и так сообщает о статусах заявок, поэтому дополнительные Print не требуются.
Отсутствуют защитные заявки, что подчёркивает идею «закрываемся только по обратному сигналу» из оригинала.
Рекомендации по использованию
Подберите CandleType в соответствии с таймфреймом в MetaTrader, чтобы получить сопоставимую частоту сигналов.
Пороговые значения %R отрицательны. Чем ближе OverboughtLevel к нулю, тем реже появляются продажи; чем ближе OversoldLevel к -100, тем реже возникают покупки.
Убедитесь, что Volume соответствует минимальному шагу и лотности инструмента. Перед запуском на реальном счёте скорректируйте базовый объём через интерфейс или код.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Williams %R cloud breakout strategy converted from the MetaTrader expert advisor.
/// Looks for %R crossings above -80 to go long and below -20 to go short.
/// Positions are reversed when an opposite signal appears.
/// </summary>
public class WprCustomCloudSimpleStrategy : Strategy
{
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<DataType> _candleType;
private WilliamsR _williamsR;
private decimal? _previousWpr;
private decimal? _olderWpr;
public int WprPeriod
{
get => _wprPeriod.Value;
set => _wprPeriod.Value = value;
}
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public WprCustomCloudSimpleStrategy()
{
_wprPeriod = Param(nameof(WprPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("WPR Period", "Williams %R lookback length", "Williams %R");
_overboughtLevel = Param(nameof(OverboughtLevel), -10m)
.SetDisplay("Overbought Level", "%R level that marks overbought conditions", "Williams %R");
_oversoldLevel = Param(nameof(OversoldLevel), -90m)
.SetDisplay("Oversold Level", "%R level that marks oversold conditions", "Williams %R");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for Williams %R", "Data");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_williamsR = new WilliamsR { Length = WprPeriod };
_previousWpr = null;
_olderWpr = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_williamsR, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williamsR);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal wprValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_williamsR.IsFormed)
{
_olderWpr = _previousWpr;
_previousWpr = wprValue;
return;
}
if (_previousWpr is not null && _olderWpr is not null)
{
var prev = _previousWpr.Value;
var prevPrev = _olderWpr.Value;
var crossedAboveOversold = prevPrev < OversoldLevel && prev > OversoldLevel;
var crossedBelowOverbought = prevPrev > OverboughtLevel && prev < OverboughtLevel;
var volume = Volume;
if (volume <= 0)
volume = 1;
if (crossedAboveOversold)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossedBelowOverbought)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
}
_olderWpr = _previousWpr;
_previousWpr = wprValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_williamsR = null;
_previousWpr = null;
_olderWpr = null;
base.OnReseted();
}
}