Stoch Levels Strategy — это перенос советника MetaTrader 4 Stoch.mq4 в инфраструктуру StockSharp. Исходный скрипт привязан к границам торгового дня: перед началом новой сессии он вычисляет ценовые уровни на основании предыдущей свечи и размещает два отложенных ордера. C# версия полностью повторяет эту идею, используя высокоуровневый API стратегий.
Стратегия расширяет диапазон предыдущей свечи (по умолчанию множитель 1.1) и размещает:
Sell Limit выше цены закрытия на половину расширенного диапазона.
Buy Limit ниже цены закрытия на половину расширенного диапазона.
При исполнении любого из ордеров создаются зеркальные защитные заявки — стоп-лосс и тейк-профит с фиксированным отступом в шагах цены. В начале каждого нового дня все позиции и отложенные заявки закрываются, что соответствует блоку «23:59» из MQL-версии.
Логика работы
Подписка на выбранный тип свечей (по умолчанию дневные) и ожидание их полного закрытия.
При поступлении новой свечи:
Закрыть текущую позицию и отменить все активные защитные/входные ордера.
Рассчитать расширенный диапазон range * RangeMultiplier по предыдущей свече.
Выставить новые Sell Limit и Buy Limit на уровнях Close ± range / 2.
После срабатывания ордера сформировать стоп-лосс и тейк-профит с нужным отступом в шагах цены.
Если один из защитных ордеров сработал, второй отменяется, и стратегия ждёт следующего ежедневного пересчёта.
Параметры
Параметр
Описание
Значение по умолчанию
Комментарий
TakeProfitPoints
Дистанция тейк-профита в шагах цены.
20
Аналог параметра TakeProfit в MQL. Значение 0 отключает тейк-профит.
StopLossPoints
Дистанция стоп-лосса в шагах цены.
40
Аналог StopLoss из MQL. Значение 0 отключает стоп-лосс.
RangeMultiplier
Множитель диапазона предыдущей свечи.
1.1
Совпадает с жёстко заданным коэффициентом из исходника.
OrderVolume
Объём ордера.
1
Соответствует параметру Lots.
CandleType
Тип свечей, определяющий торговой период.
Дневные
Можно изменить для других таймфреймов.
Параметры объявлены через Param(), что обеспечивает оптимизацию и отображение в интерфейсе.
Управление рисками
Для длинных позиций выставляется пара Sell Stop + Sell Limit; для коротких — зеркальные Buy Stop + Buy Limit.
Объём защитных ордеров совпадает с объёмом входа. После исполнения одного защитного ордера второй отменяется.
Каждый новый день стратегия полностью закрывает позицию и снимает отложенные заявки, исключая перенос позиций через ночь.
Особенности переноса
В MQL использовались глобальные переменные для контроля «один раз в день». Теперь это реализовано через поле _lastProcessedDay.
Ночной блок закрытия позиций перенесён в метод ResetOrders(), который отменяет все ордера и отправляет команду ClosePosition() при необходимости.
Параметры StopLoss и TakeProfit реализуются отдельными заявками StockSharp, а не встроенными полями OrderSend.
Параметры TrailingStop, MM, Risk, LotLimit из исходника не имели логики — они сохранены как неиспользуемые.
Рекомендации по использованию
Настройте объём, дистанции защитных ордеров и тип свечей в соответствии с торгуемым инструментом.
Убедитесь, что у бумаги корректно задан PriceStep. Если нет, стратегия будет использовать значение 1 и запишет предупреждение в лог.
Сохраните дневной таймфрейм, чтобы поведение соответствовало оригинальному советнику.
Отслеживайте журнал сообщений: там отражаются ежедневный сброс, выставление ордеров и постановка защитных заявок.
Состав пакета
CS/StochLevelsStrategy.cs — реализация стратегии.
README.md, README_zh.md, README_ru.md — документация на разных языках.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stoch Levels: Previous candle range breakout with EMA filter and ATR stops.
/// </summary>
public class StochLevelsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _entryPrice;
public StochLevelsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (_prevHigh == 0 || atrVal <= 0) { _prevHigh = candle.HighPrice; _prevLow = candle.LowPrice; return; }
if (Position > 0)
{
if (close <= _entryPrice - atrVal * 1.5m || close >= _entryPrice + atrVal * 2.5m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close >= _entryPrice + atrVal * 1.5m || close <= _entryPrice - atrVal * 2.5m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (close > _prevHigh && close > emaVal) { _entryPrice = close; BuyMarket(); }
else if (close < _prevLow && close < emaVal) { _entryPrice = close; SellMarket(); }
}
_prevHigh = candle.HighPrice; _prevLow = candle.LowPrice;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class stoch_levels_strategy(Strategy):
def __init__(self):
super(stoch_levels_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(stoch_levels_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_high == 0 or av <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position > 0:
if close <= self._entry_price - av * 1.5 or close >= self._entry_price + av * 2.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close >= self._entry_price + av * 1.5 or close <= self._entry_price - av * 2.5:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close > self._prev_high and close > ev:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_low and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def OnReseted(self):
super(stoch_levels_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
def CreateClone(self):
return stoch_levels_strategy()