Стратегия представляет собой перевод советника SVOS_EURJPY_D1 с MetaTrader 4 на C#. Работа ведётся по дневным свечам EURJPY.
Алгоритм сочетает классификацию рыночного режима, индикаторные фильтры и свечные модели. Индикатор Vertical Horizontal Filter
(VHF) разделяет трендовые и флетовые участки. В тренде используются сигналы гистограммы MACD (OSMA), во флете — осциллятор
Stochastic. Свечные модели (поглощения, «утренняя/вечерняя звезда») применяются для жёсткого закрытия позиции при неблагоприятной
реакции цены.
Логика торговли
Определение режима — значение VHF за предыдущий день сравнивается с параметром VhfThreshold. Превышение порога включает
трендовую логику, иначе используется блок торговли во флете.
Подтверждение тренда — сравниваются EMA с периодами 5 и 20 с медленной EMA (130 периодов, эквивалент полугодовой сглаживающей
средне скользящей оригинального советника). При восходящем тренде объём покупок умножается на RiskBoost, при нисходящем — объём
продаж.
Индикаторные фильтры:
В тренде: длинный вход при положительном и растущем значении OSMA (OSMA[1] > 0 и OSMA[1] > OSMA[2]), короткий — при
отрицательном и убывающем значении.
Во флете: длинный вход при пересечении основной линии Stochastic выше сигнальной, короткий — при обратном пересечении.
Контроль волатильности: стандартное отклонение за прошлый бар должно превышать StdDevMinimum перед открытием новых сделок.
Фильтр по свечам — последняя завершённая свеча не должна быть доджи (DojiDivisor) и должна подтверждать направление
(бычья для лонга, медвежья для шорта). Противоположные поглощения или фигуры утренней/вечерней звезды немедленно закрывают
позицию соответствующего направления.
Ограничение позиций — общее количество открытых ордеров ограничивается MaxTrendOrders в тренде и MaxRangeOrders во флете.
Управление рисками — каждому ордеру назначаются фиксированные стоп-лосс и тейк-профит (StopLossPips, TakeProfitPips).
Трейлинг-стоп (TrailingStopPips) активируется при достижении заданной прибыли и пересчитывается по экстремумам свечи, что
повторяет логику MetaTrader.
Используемые индикаторы
Экспоненциальные средние (5, 20, 130) — подтверждение направления и масштабирование объёма.
Vertical Horizontal Filter — кастомный индикатор, оценивающий отношение направленного движения к сумме приращений закрытия,
позволяющий отделить тренд от боковика.
MACD (OSMA) — разница между линиями MACD и сигнальной формирует трендовые сигналы.
Stochastic Oscillator — линии %K/%D дают точки входа в диапазоне.
Standard Deviation — фильтр минимальной волатильности перед открытием позиций.
Управление позициями
Сделки исполняются через BuyMarket/SellMarket. Каждая запись хранится в списках, что позволяет имитировать индивидуальные
стопы и тейки в неттинговой модели StockSharp.
При достижении стоп-уровня или тейка внутри диапазона свечи соответствующая часть позиции закрывается рыночным ордером.
Трейлинг-стоп следует за максимумом (для лонга) или минимумом (для шорта), сохраняя заданную дистанцию.
Параметры
Параметр
Описание
Значение по умолчанию
LotSize
Базовый объём ордера в лотах.
0.1
RiskBoost
Множитель объёма при подтверждённом тренде.
3
TakeProfitPips
Дистанция тейк-профита в пунктах.
350
StopLossPips
Дистанция стоп-лосса в пунктах.
90
TrailingStopPips
Дистанция трейлинг-стопа в пунктах (всегда активен).
150
StochKPeriod
Период %K осциллятора Stochastic.
8
StochDPeriod
Период %D осциллятора Stochastic.
3
StochSlowing
Сглаживание линии %K.
3
StdDevPeriod
Окно расчёта стандартного отклонения.
20
StdDevMinimum
Минимальное стандартное отклонение для открытия сделок.
0.3
VhfPeriod
Период Vertical Horizontal Filter.
20
VhfThreshold
Порог разделения тренда и флета.
0.4
MaxTrendOrders
Максимум одновременных ордеров в тренде.
4
MaxRangeOrders
Максимум одновременных ордеров во флете.
2
MacdFastLength
Быстрая EMA в MACD.
10
MacdSlowLength
Медленная EMA в MACD.
25
MacdSignalLength
Сигнальная EMA MACD.
5
DojiDivisor
Делитель для определения свечи-доджи.
8.5
CandleType
Таймфрейм свечей (по умолчанию день).
1 день
PipSizeOverride
При необходимости вручную задаёт размер пункта (0 — автоопределение по PriceStep).
0
Особенности реализации
В оригинале использовалась EMA по месячному таймфрейму (6 месяцев). В порте применена EMA(130) по дневным закрытиям — это даёт
сопоставимое сглаживание без отдельной подписки на месячные свечи.
Из-за неттинга в StockSharp стопы, тейки и трейлинг реализованы внутри стратегии: каждая сделка хранится и обслуживается
отдельно, что повторяет механику MetaTrader.
Трейлинг обновляется по максимуму/минимуму свечи, поэтому результаты могут отличаться от тикового трейлинга оригинала при
резких внутридневных разворотах.
Размер пункта рассчитывается по Security.PriceStep. Если у брокера нестандартный шаг, используйте PipSizeOverride.
Рекомендации по запуску
Подключите стратегию к данным EURJPY с дневным таймфреймом либо измените CandleType при использовании другого интервала.
Проверьте корректность определения пункта и при необходимости задайте PipSizeOverride вручную.
Настройте параметры управления капиталом (LotSize, RiskBoost) под требования счёта.
Протестируйте стратегию в StockSharp Designer или Runner перед запуском на реальном счёте.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Regime-switching strategy that uses ADX to detect trend vs range.
/// In trends, follows EMA direction; in ranges, trades mean reversion.
/// </summary>
public class SvosEurJpyD1Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _adxThreshold;
private decimal _entryPrice;
public SvosEurJpyD1Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_adxLength = Param(nameof(AdxLength), 14)
.SetDisplay("ADX Length", "Period for ADX.", "Indicators");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Period for trend EMA.", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "ADX level to distinguish trend from range.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AdxLength
{
get => _adxLength.Value;
set => _adxLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
var atr = new AverageTrueRange { Length = AdxLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal adxValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var isTrending = adxValue > AdxThreshold;
// Position management
if (Position > 0)
{
if (isTrending && close < emaValue)
{
SellMarket(); // Trend reversed
}
else if (!isTrending && close >= emaValue)
{
SellMarket(); // Mean reversion target hit
}
}
else if (Position < 0)
{
if (isTrending && close > emaValue)
{
BuyMarket();
}
else if (!isTrending && close <= emaValue)
{
BuyMarket();
}
}
// Entry
if (Position == 0)
{
if (isTrending)
{
// Trend following
if (close > emaValue)
{
_entryPrice = close;
BuyMarket();
}
else if (close < emaValue)
{
_entryPrice = close;
SellMarket();
}
}
else
{
// Mean reversion
var deviation = Math.Abs(close - emaValue);
if (deviation > 0 && close < emaValue)
{
_entryPrice = close;
BuyMarket();
}
else if (deviation > 0 && close > emaValue)
{
_entryPrice = close;
SellMarket();
}
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
class svos_eur_jpy_d1_strategy(Strategy):
def __init__(self):
super(svos_eur_jpy_d1_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._adx_length = self.Param("AdxLength", 14) \
.SetDisplay("ADX Length", "Period for ADX", "Indicators")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Period for trend EMA", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetDisplay("ADX Threshold", "ADX level to distinguish trend from range", "Indicators")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AdxLength(self):
return self._adx_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AdxThreshold(self):
return self._adx_threshold.Value
def OnStarted2(self, time):
super(svos_eur_jpy_d1_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AdxLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, adx_value, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
adx_val = float(adx_value)
ema_val = float(ema_value)
threshold = float(self.AdxThreshold)
is_trending = adx_val > threshold
# Position management
if self.Position > 0:
if is_trending and close < ema_val:
self.SellMarket()
elif not is_trending and close >= ema_val:
self.SellMarket()
elif self.Position < 0:
if is_trending and close > ema_val:
self.BuyMarket()
elif not is_trending and close <= ema_val:
self.BuyMarket()
# Entry
if self.Position == 0:
if is_trending:
if close > ema_val:
self._entry_price = close
self.BuyMarket()
elif close < ema_val:
self._entry_price = close
self.SellMarket()
else:
deviation = abs(close - ema_val)
if deviation > 0 and close < ema_val:
self._entry_price = close
self.BuyMarket()
elif deviation > 0 and close > ema_val:
self._entry_price = close
self.SellMarket()
def OnReseted(self):
super(svos_eur_jpy_d1_strategy, self).OnReseted()
self._entry_price = 0.0
def CreateClone(self):
return svos_eur_jpy_d1_strategy()