Стратегия представляет собой перенос на StockSharp высокоуровневого MQL-панеля «MultiTrader» (код 24786). Исходный эксперт был интерактивной доской, показывающей относительную силу восьми основных валют, подававшей сигналы при экстремальных значениях и предлагавшей подходящие валютные пары для торговли. В реализации на StockSharp эти расчёты автоматизированы, а при желании можно сразу открывать сделки по связке «самая сильная против самой слабой».
Для каждого инструмента вычисляется доля текущей цены закрытия в диапазоне свечи. Затем стратегия усредняет соответствующие кроссы и получает силу AUD, CAD, CHF, EUR, GBP, JPY, NZD и USD. Когда одна валюта поднимается выше заданного порога покупки, а другая падает ниже порога продажи, формируется рекомендация по соответствующей паре. Если эта пара присутствует в настроенной вселенной, можно автоматически отправить рыночный ордер.
Модель силы валют
Формула для процента по инструменту:
percent = 100 * (Close - Low) / (High - Low)
Каждая валюта складывается из семи кроссов по той же схеме, что и в MQL. Если валюта стоит в котируемой части пары, применяется инверсия 100 - percent:
Стратегия использует только завершённые свечи. После получения новой закрытой свечи пересчитываются проценты и силы валют.
Торговля и оповещения
Как только доступны данные по всем восьми валютам, в журнале появляется снимок (от самой сильной к самой слабой).
Если сильнейшая валюта ≥ BuyLevel, а слабейшая ≤ SellLevel, строится рекомендация.
В первую очередь ищется прямая пара «сильная валюта в базе, слабая в котировке». При её отсутствии рассматривается обратный порядок и, в крайнем случае, пары с участием USD.
Результат записывается в лог. Если EnableAutoTrading = true и OrderVolume > 0, стратегия отправит рыночный ордер. При наличии встречной позиции объём увеличивается для закрытия старого и открытия нового направления.
Последний предложенный инструмент и направление запоминаются, чтобы избежать повторяющихся сигналов, пока рынок не выйдет из зоны порогов.
Параметры
Имя
Описание
Значение по умолчанию
Universe
Набор инструментов Security, участвующих в расчётах (рекомендуется 28 основных кроссов).
Обязательно
CandleType
Тип свечей для анализа (день, неделя, месяц и т. п.).
Дневные свечи
BuyLevel
Порог, выше которого валюта считается перекупленной.
90
SellLevel
Порог, ниже которого валюта считается перепроданной.
10
EnableAutoTrading
Включить автоматическое исполнение сделок.
false
OrderVolume
Объём рыночных заявок при автоторговле.
1
SymbolPrefix
Приставка к тикеру, требуемая брокером/биржей (например, m.).
""
SymbolSuffix
Суффикс тикера (например, .FX).
""
Настройка
Вселенной инструментов. Добавьте в Universe 28 основных форекс-пар. Коды должны совпадать с стандартными обозначениями (EURUSD, GBPJPY и т. д.). При наличии префиксов/суффиксов заполните соответствующие параметры.
Выбор таймфрейма. Укажите CandleType. Дневные, недельные и месячные свечи соответствуют режимам исходного панели.
Настройка порогов. Измените BuyLevel и SellLevel, чтобы определить требуемую степень перекупленности/перепроданности.
Автоторговля (опционально). Установите EnableAutoTrading = true и задайте OrderVolume. При необходимости только аналитики оставьте флаг выключенным.
Особенности переноса
Графический интерфейс MQL-версии не переносился — все результаты выдаются через журнал стратегии.
Push/Email/Popup-оповещения заменены на записи LogInfo.
Автоматические расчёты стоп-лоссов и тейк-профитов не реализованы. Рекомендуется использовать защиту StartProtection или отдельные риск-модули.
DES-шифрование и вспомогательные функции лицензирования удалены.
Рекомендации по использованию
Запускайте стратегию в связке с коннектором, который предоставляет исторические и текущие свечи по всем выбранным инструментам.
Можно дополнительно вывести график рекомендованной пары для визуального контроля.
Для управления риском воспользуйтесь встроенной защитой StartProtection или отдельными стратегиями.
Тестирование
Убедитесь, что поставщик данных передаёт завершённые свечи выбранного таймфрейма — незакрытые бары игнорируются.
Отсутствие хотя бы одной пары во вселенной делает расчёт соответствующей валюты невозможным, следовательно сигнал не будет создан.
В бэктестах держите состав Universe постоянным на всём горизонте, чтобы не возникало разрывов в силах валют.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class MultiTraderStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public MultiTraderStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class multi_trader_strategy(Strategy):
def __init__(self):
super(multi_trader_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(multi_trader_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(multi_trader_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return multi_trader_strategy()