Стратегия Meeting Lines Stochastic переносит механику MetaTrader-советника Expert_AML_Stoch в инфраструктуру StockSharp. Торговые решения принимаются на основе свечных моделей «бычьи/медвежьи встречные линии», подтверждённых показаниями сигнальной линии %D стохастического осциллятора. Использование высокоуровневого API StockSharp обеспечивает чистую архитектуру, гибкие параметры и готовность к оптимизации в Designer.
Логика торговли
Фильтр свечных моделей
На каждой закрывшейся свече анализируются две предыдущие свечи.
Для бычьей конфигурации требуется длинная чёрная свеча, за которой следует длинная белая свеча; их цены закрытия должны отличаться менее чем на 10% от среднего тела.
Для медвежьей конфигурации условия зеркальны: сначала длинная белая свеча, затем длинная чёрная с близкими ценами закрытия.
Средний размер тела вычисляется простым скользящим средним по модулю тела, что эквивалентно функции AvgBody из исходного MQL.
Подтверждение стохастиком
Учитывается только сигнальная линия %D.
Для открытия длинной позиции %D предыдущей свечи должен находиться ниже порогового значения (по умолчанию 30).
Для открытия короткой позиции %D должен превышать верхний порог (по умолчанию 70).
Правила выхода
Короткие позиции закрываются при пробое %D вверх через нижний (20) или верхний (80) уровень.
Длинные позиции закрываются при пробое %D вниз через те же уровни.
При появлении противоположного сигнала стратегия отправляет рыночную заявку объёмом, достаточным для переворота позиции.
Обработка объёма
Если свойство Volume задано положительным числом, используется оно; иначе применяется объём 1, что соответствует фиксированному лоту оригинального эксперта.
Параметры
Параметр
Назначение
Значение по умолчанию
Примечание
CandleType
Тип свечей для анализа
15-минутные свечи
Можно использовать любой DataType.
StochasticLength
База расчёта %K
3
Аналог %K period в MetaTrader.
StochasticSmoothing
Сглаживание %K (slowing)
25
Контролирует сглаживание входной серии.
StochasticSignal
Период сигнальной линии %D
36
Аналог %D period.
BodyAveragePeriod
Длина окна для среднего тела свечи
3
Отсекает малые свечи.
LongEntryLevel
Максимальное значение %D для входа в лонг
30
Порог перепроданности.
ShortEntryLevel
Минимальное значение %D для входа в шорт
70
Порог перекупленности.
ExitLowerLevel
Нижняя граница для выхода
20
Используется для фиксации прибыли в обе стороны.
ExitUpperLevel
Верхняя граница для выхода
80
Используется совместно с нижней границей.
Все параметры реализованы через StrategyParam<T>, что упрощает оптимизацию и интеграцию с интерфейсами StockSharp.
Сигналы
Открытие лонга: обнаружена бычья встречная линия и %D предыдущей свечи ниже LongEntryLevel. При наличии короткой позиции выполняется переворот.
Открытие шорта: обнаружена медвежья встречная линия и %D выше ShortEntryLevel. При наличии лонга выполняется переворот.
Закрытие лонга: %D пересекает вниз ExitUpperLevel или ExitLowerLevel.
Закрытие шорта: %D пересекает вверх ExitLowerLevel или ExitUpperLevel.
Особенности реализации
Подписка на свечи выполняется через SubscribeCandles, а значения стохастика поступают в обработчик посредством BindEx, поэтому дополнительных коллекций индикаторов не требуется.
Среднее тело вычисляется индикатором SimpleMovingAverage, который получает абсолютный размер тела через DecimalIndicatorValue — полностью соответствует MQL-алгоритму.
Код оформлен с соблюдением требований AGENTS.md: английские комментарии и табуляция.
При наличии окна графика автоматически отображаются свечи и стохастик, что облегчает визуальный контроль.
Рекомендации по применению
Оптимизация: тестируйте параметры на истории и используйте walk-forward для контроля устойчивости.
Риск-менеджмент: при необходимости подключите StartProtection или внешние модули управления капиталом для ограничения убытков.
Качество данных: формация чувствительна к ценам открытия/закрытия, поэтому желательно исключить периоды низкой ликвидности и сильных гэпов.
Гибкость таймфреймов: параметр CandleType позволяет адаптировать стратегию под внутридневные или позиционные подходы.
Стратегия подходит тем, кто доверяет свечным разворотным моделям, но предпочитает подтверждать их объективными индикаторными фильтрами.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Meeting Lines + Stochastic strategy.
/// Buys on bullish meeting lines with low stochastic, sells on bearish meeting lines with high stochastic.
/// </summary>
public class MeetingLinesStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _stochLow;
private readonly StrategyParam<decimal> _stochHigh;
private ICandleMessage _prevCandle;
private ICandleMessage _prevPrevCandle;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public decimal StochLow { get => _stochLow.Value; set => _stochLow.Value = value; }
public decimal StochHigh { get => _stochHigh.Value; set => _stochHigh.Value = value; }
public MeetingLinesStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Stochastic K period", "Indicators");
_stochLow = Param(nameof(StochLow), 30m)
.SetDisplay("Stoch Low", "Stochastic oversold level", "Signals");
_stochHigh = Param(nameof(StochHigh), 70m)
.SetDisplay("Stoch High", "Stochastic overbought level", "Signals");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCandle = null;
_prevPrevCandle = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCandle = null;
_prevPrevCandle = null;
var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(stoch, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished) return;
var stochTyped = stochValue as StochasticOscillatorValue;
if (stochTyped?.K is not decimal kValue) { UpdateState(candle); return; }
if (_prevCandle != null && _prevPrevCandle != null)
{
var avgBody = (Math.Abs(_prevCandle.ClosePrice - _prevCandle.OpenPrice) +
Math.Abs(_prevPrevCandle.ClosePrice - _prevPrevCandle.OpenPrice)) / 2m;
if (avgBody > 0)
{
// Bullish meeting lines: prev bearish, current bullish, closes near
var prevBearish = _prevCandle.OpenPrice > _prevCandle.ClosePrice &&
(_prevCandle.OpenPrice - _prevCandle.ClosePrice) > avgBody * 0.5m;
var currBullish = candle.ClosePrice > candle.OpenPrice &&
(candle.ClosePrice - candle.OpenPrice) > avgBody * 0.5m;
var closesNear = Math.Abs(candle.ClosePrice - _prevCandle.ClosePrice) < avgBody * 0.3m;
if (prevBearish && currBullish && closesNear && kValue < StochLow && Position <= 0)
BuyMarket();
// Bearish meeting lines: prev bullish, current bearish, closes near
var prevBullish = _prevCandle.ClosePrice > _prevCandle.OpenPrice &&
(_prevCandle.ClosePrice - _prevCandle.OpenPrice) > avgBody * 0.5m;
var currBearish = candle.OpenPrice > candle.ClosePrice &&
(candle.OpenPrice - candle.ClosePrice) > avgBody * 0.5m;
var closesNear2 = Math.Abs(candle.ClosePrice - _prevCandle.ClosePrice) < avgBody * 0.3m;
if (prevBullish && currBearish && closesNear2 && kValue > StochHigh && Position >= 0)
SellMarket();
}
}
UpdateState(candle);
}
private void UpdateState(ICandleMessage candle)
{
_prevPrevCandle = _prevCandle;
_prevCandle = candle;
}
}
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.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class meeting_lines_stochastic_strategy(Strategy):
def __init__(self):
super(meeting_lines_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._stoch_period = self.Param("StochPeriod", 14)
self._stoch_low = self.Param("StochLow", 30.0)
self._stoch_high = self.Param("StochHigh", 70.0)
self._prev_candle = None
self._prev_prev_candle = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_period.Value = value
@property
def StochLow(self):
return self._stoch_low.Value
@StochLow.setter
def StochLow(self, value):
self._stoch_low.Value = value
@property
def StochHigh(self):
return self._stoch_high.Value
@StochHigh.setter
def StochHigh(self, value):
self._stoch_high.Value = value
def OnReseted(self):
super(meeting_lines_stochastic_strategy, self).OnReseted()
self._prev_candle = None
self._prev_prev_candle = None
def OnStarted2(self, time):
super(meeting_lines_stochastic_strategy, self).OnStarted2(time)
self._prev_candle = None
self._prev_prev_candle = None
stoch = StochasticOscillator()
stoch.K.Length = self.StochPeriod
stoch.D.Length = 3
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stoch, self._process_candle).Start()
def _process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
k_val = stoch_value.K
if k_val is None:
self._update_state(candle)
return
k_value = float(k_val)
if self._prev_candle is not None and self._prev_prev_candle is not None:
avg_body = (abs(float(self._prev_candle.ClosePrice) - float(self._prev_candle.OpenPrice))
+ abs(float(self._prev_prev_candle.ClosePrice) - float(self._prev_prev_candle.OpenPrice))) / 2.0
if avg_body > 0:
# Bullish meeting lines
prev_bearish = (float(self._prev_candle.OpenPrice) > float(self._prev_candle.ClosePrice)
and (float(self._prev_candle.OpenPrice) - float(self._prev_candle.ClosePrice)) > avg_body * 0.5)
curr_bullish = (float(candle.ClosePrice) > float(candle.OpenPrice)
and (float(candle.ClosePrice) - float(candle.OpenPrice)) > avg_body * 0.5)
closes_near = abs(float(candle.ClosePrice) - float(self._prev_candle.ClosePrice)) < avg_body * 0.3
if prev_bearish and curr_bullish and closes_near and k_value < self.StochLow and self.Position <= 0:
self.BuyMarket()
# Bearish meeting lines
prev_bullish = (float(self._prev_candle.ClosePrice) > float(self._prev_candle.OpenPrice)
and (float(self._prev_candle.ClosePrice) - float(self._prev_candle.OpenPrice)) > avg_body * 0.5)
curr_bearish = (float(candle.OpenPrice) > float(candle.ClosePrice)
and (float(candle.OpenPrice) - float(candle.ClosePrice)) > avg_body * 0.5)
closes_near2 = abs(float(candle.ClosePrice) - float(self._prev_candle.ClosePrice)) < avg_body * 0.3
if prev_bullish and curr_bearish and closes_near2 and k_value > self.StochHigh and self.Position >= 0:
self.SellMarket()
self._update_state(candle)
def _update_state(self, candle):
self._prev_prev_candle = self._prev_candle
self._prev_candle = candle
def CreateClone(self):
return meeting_lines_stochastic_strategy()