Стратегия переносит мультивалютного советника "RingSystemEA" с MetaTrader 4 на высокоуровневый API StockSharp. Она формирует треугольные кольца из заданного списка валют (каждая тройка валют образует три коррелированных инструмента) и управляет двумя хеджированными корзинами для каждого кольца: плюсовой (покупка/продажа/покупка) и минусовой (продажа/покупка/продажа). Алгоритм постоянно отслеживает плавающий PnL по каждому кольцу, добавляет дополнительные уровни ордеров при достижении заданных убытков и синхронно закрывает корзины при достижении целевой прибыли или допустимого убытка.
Логика торговли
Формируются все уникальные комбинации из трёх валют из строки CurrenciesTrade (например, EUR/GBP/AUD превращается в пары EURGBP, EURAUD и GBPAUD).
Для каждой тройки поддерживаются две синхронные корзины:
Плюсовая открывает BUY по первой паре, SELL по второй и BUY по третьей.
Минусовая зеркально открывает SELL/BUY/SELL.
Корзины запускаются автоматически, когда получены цены по всем инструментам и сессия допускает торговлю. Набор сторон определяется параметром SideOpenOrders.
Если плавающий результат активной корзины ухудшается ниже порога StepOpenNextOrders (порог может увеличиваться по геометрической или экспоненциальной схеме), запускается дополнительный уровень сделок с объёмом, рассчитанным через LotOrdersProgress.
Закрытие корзин происходит согласно выбранному режиму TypeCloseInProfit: по отдельной стороне, всей корзиной или по каждой паре отдельно.
Защитные сценарии (TypeCloseInLoss) повторяют логику MT4: можно закрывать корзину полностью, частично сокращать объём или пропускать принудительное закрытие.
При необходимости включается фильтр торговой недели: ожидание после открытия рынка в понедельник и остановка перед закрытием в пятницу.
Денежный менеджмент соответствует оригиналу: автообъём рассчитывается от текущей стоимости портфеля и множителя риска, опция "fair lot" компенсирует различия тиковой стоимости между парами.
Основные параметры
Параметр
Назначение
CurrenciesTrade
Порядок валют, по которому строятся кольца.
NoOfGroupToSkip
Номера групп, которые следует исключить из торговли.
Логика корзин и управления PnL идентична оригинальному советнику, но состояние хранится в объектных структурах StockSharp, а не в статических массивах.
Индикаторы не используются — решения принимаются только на основании цен свечей и текущего PnL портфеля.
Все ордера помечаются строкой StringOrdersEA для упрощения внешнего учёта.
Визуализация и файловые отчёты MT4 не перенесены. При включённой опции SaveInformations информация пишется в лог стратегий.
Режим Open_With_Auto_Step использует значение ручного шага, потому что специфические расчёты маржи MT4 недоступны в среде StockSharp.
Рекомендации по использованию
Подключите и сопоставьте все валютные пары, которые формируются из CurrenciesTrade, с учётом префиксов и суффиксов брокера.
Настройте SideOpenOrders, чтобы определить, будет ли стратегия вести обе корзины или только одну сторону.
Аккуратно подберите StepOpenNextOrders, StepOrdersProgress и LotOrdersProgress: именно они задают темп наращивания объёмов и уровень риска.
Включите SaveInformations, чтобы наблюдать в логах процесс добавления уровней и закрытия корзин.
Порт сохраняет ключевую идею «кольцевого» хеджированного грид-алгоритма, адаптируя её к событийной модели StockSharp и расширенной системе параметров.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Ring System EA strategy: ROC momentum with SMA filter.
/// Buys when ROC is positive and price above SMA, sells when ROC is negative and below SMA.
/// </summary>
public class RingSystemEaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _rocPeriod;
private bool _wasBullish;
private bool _hasPrevSignal;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
public int RocPeriod { get => _rocPeriod.Value; set => _rocPeriod.Value = value; }
public RingSystemEaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_smaPeriod = Param(nameof(SmaPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "SMA trend filter period", "Indicators");
_rocPeriod = Param(nameof(RocPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ROC Period", "Rate of Change period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_wasBullish = false;
_hasPrevSignal = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrevSignal = false;
var sma = new SimpleMovingAverage { Length = SmaPeriod };
var roc = new RateOfChange { Length = RocPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, roc, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal sma, decimal roc)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var isBullish = roc > 0 && close > sma;
if (_hasPrevSignal && isBullish != _wasBullish)
{
if (isBullish && Position <= 0)
BuyMarket();
else if (!isBullish && roc < 0 && close < sma && Position >= 0)
SellMarket();
}
_wasBullish = isBullish;
_hasPrevSignal = true;
}
}
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 SimpleMovingAverage, RateOfChange
from StockSharp.Algo.Strategies import Strategy
class ring_system_ea_strategy(Strategy):
def __init__(self):
super(ring_system_ea_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 30) \
.SetDisplay("SMA Period", "SMA trend filter period", "Indicators")
self._roc_period = self.Param("RocPeriod", 10) \
.SetDisplay("ROC Period", "Rate of Change period", "Indicators")
self._sma = None
self._roc = None
self._was_bullish = False
self._has_prev_signal = False
@property
def sma_period(self):
return self._sma_period.Value
@property
def roc_period(self):
return self._roc_period.Value
def OnReseted(self):
super(ring_system_ea_strategy, self).OnReseted()
self._sma = None
self._roc = None
self._was_bullish = False
self._has_prev_signal = False
def OnStarted2(self, time):
super(ring_system_ea_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self.sma_period
self._roc = RateOfChange()
self._roc.Length = self.roc_period
self._has_prev_signal = False
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15)))
subscription.Bind(self._sma, self._roc, self._process_candle)
subscription.Start()
def _process_candle(self, candle, sma_value, roc_value):
if candle.State != CandleStates.Finished:
return
if not self._sma.IsFormed or not self._roc.IsFormed:
return
close = float(candle.ClosePrice)
sma_val = float(sma_value)
roc_val = float(roc_value)
is_bullish = roc_val > 0.0 and close > sma_val
if self._has_prev_signal and is_bullish != self._was_bullish:
if is_bullish and self.Position <= 0:
self.BuyMarket()
elif not is_bullish and roc_val < 0.0 and close < sma_val and self.Position >= 0:
self.SellMarket()
self._was_bullish = is_bullish
self._has_prev_signal = True
def CreateClone(self):
return ring_system_ea_strategy()