Стратегия Currency Strength v1.1 воспроизводит логику одноимённого советника MetaTrader. Она оценивает относительную силу восьми основных валют (USD, EUR, JPY, CAD, AUD, NZD, GBP, CHF), используя процентное изменение 26 популярных валютных пар на дневных свечах. Когда расхождение сил двух валют превышает заданный порог, стратегия открывает позицию по соответствующей паре в сторону более сильной валюты.
Таймфрейм: дневные свечи (D1). Обрабатываются только полностью сформированные свечи.
Необходимые данные: цены открытия, максимума, минимума и закрытия каждой свечи.
Расчёт силы валют
Для каждой пары вычисляется дневное изменение в процентах:
(change) = (Close − Open) / Open × 100
Далее показатели объединяются в индексы силы валют по формулам оригинального советника:
Сила EUR = среднее по EURJPY, EURCAD, EURGBP, EURCHF, EURAUD, EURUSD, EURNZD
Сила USD = среднее по USDJPY, USDCAD, –AUDUSD, USDCHF, –GBPUSD, –EURUSD, –NZDUSD
Сила JPY = отрицательное среднее по USDJPY, EURJPY, AUDJPY, CHFJPY, GBPJPY, CADJPY, NZDJPY
Сила CAD = среднее по CADCHF, CADJPY, –GBPCAD, –AUDCAD, –EURCAD, –USDCAD
Сила AUD = среднее по AUDUSD, AUDNZD, AUDCAD, AUDCHF, AUDJPY, –EURAUD, –GBPAUD
Сила NZD = среднее по NZDUSD, NZDJPY, –EURNZD, –AUDNZD, –GBPNZD
Сила GBP = среднее по GBPUSD, –EURGBP, GBPCHF, GBPAUD, GBPCAD, GBPJPY, GBPNZD
Сила CHF = среднее по CHFJPY, –USDCHF, –EURCHF, –AUDCHF, –GBPCHF, –CADCHF
Каждый индекс использует то же количество компонентов, что и в исходном алгоритме, сохраняя исходные веса.
Логика торговли
После получения новых завершённых дневных свечей по всем 26 парам пересчитываются силы валют.
Для каждой пары сравниваются силы базовой и котируемой валют. Если модуль разницы превышает DifferenceThreshold, формируется сигнал.
Направление сделки определяется более сильной валютой:
базовая валюта сильнее → покупка пары;
котируемая валюта сильнее → продажа пары.
Сделка разрешена только в том случае, если дневная свеча пары подтверждает направление (закрытие выше открытия для покупок, ниже — для продаж), как и в оригинальном советнике.
Стратегия учитывает чистую позицию. При появлении противоположного сигнала существующая позиция закрывается и сразу переворачивается рыночной заявкой.
При включённом TradeOncePerDay каждая пара может открыть не более одной длинной и одной короткой позиции за торговый день.
Управление рисками и выходы
Флаг UseSlTp активирует дневные проверки стоп-лосса и тейк-профита. Расстояния задаются в пунктах (StopLossPips, TakeProfitPips).
Защитная логика анализирует максимум и минимум свежей дневной свечи. Если уровень достигнут, позиция закрывается рыночным ордером при следующей обработке.
Если стопы/тейки отключены, позиции удерживаются до появления противоположного сигнала или ручного закрытия, что соответствует поведению исходного советника.
Параметры стратегии
Параметр
Описание
CandleType
Таймфрейм свечей (по умолчанию — дневной).
DifferenceThreshold
Минимальный разрыв сил валют (в процентных пунктах) для открытия сделки.
TradeOncePerDay
Ограничивает число входов в день по каждому направлению.
UseSlTp
Включает стоп-лосс и тейк-профит, рассчитываемые по дневным свечам.
TakeProfitPips
Размер тейк-профита в пунктах.
StopLossPips
Размер стоп-лосса в пунктах.
Параметры пар
26 параметров Security, которые необходимо задать перед запуском.
Volume
Размер позиции (свойство базового класса, по умолчанию 0.01 лота).
Особенности реализации
Для каждой пары создаётся отдельная подписка на свечи через высокоуровневый метод SubscribeCandles.
В расчётах участвуют только свечи с состоянием Finished, что соответствует требованиям StockSharp.
Сигналы генерируются только после получения данных за одинаковую дату по всем парам, обеспечивая синхронность корзины валют.
Служебные словари хранят дату последней сделки по каждому направлению и параметры входа для контроля стопов/тейков.
Рекомендации по использованию
Перед запуском стратегии задайте все 26 инструментов, иначе будет выброшено исключение.
Убедитесь, что поставщик данных передаёт дневные свечи по каждому инструменту, чтобы расчёт сил оставался синхронным.
Подбирайте DifferenceThreshold под желаемую активность: меньший порог даёт больше сделок и разворотов.
Настраивайте параметры стопов в соответствии с точностью котировок вашего брокера; по умолчанию предполагается наличие дробных пунктов.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades based on percentage change momentum of candles.
/// Simplified from the original multi-pair currency strength approach to single-security.
/// </summary>
public class CurrencyStrengthV11Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _differenceThreshold;
private decimal? _prevChange;
private decimal? _prevMomentum;
private decimal _entryPrice;
private DateTimeOffset? _lastTradeTime;
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum percentage change to trigger trades.
/// </summary>
public decimal DifferenceThreshold
{
get => _differenceThreshold.Value;
set => _differenceThreshold.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public CurrencyStrengthV11Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strength calculation", "General");
_differenceThreshold = Param(nameof(DifferenceThreshold), 0.2m)
.SetDisplay("Threshold", "Minimum percentage change to trigger trade", "Parameters")
.SetOptimize(0.05m, 1m, 0.05m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevChange = null;
_prevMomentum = null;
_entryPrice = 0m;
_lastTradeTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var change = candle.OpenPrice != 0m
? (candle.ClosePrice - candle.OpenPrice) / candle.OpenPrice * 100m
: 0m;
if (_prevChange == null)
{
_prevChange = change;
return;
}
var momentum = change - _prevChange.Value;
var cooldownPassed = _lastTradeTime is null || candle.CloseTime - _lastTradeTime >= TimeSpan.FromHours(24);
var longSignal = _prevMomentum is decimal prevMomentum && prevMomentum <= DifferenceThreshold && momentum > DifferenceThreshold;
var shortSignal = _prevMomentum is decimal prevMomentum2 && prevMomentum2 >= -DifferenceThreshold && momentum < -DifferenceThreshold;
if (cooldownPassed && longSignal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
else if (cooldownPassed && shortSignal && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
_prevChange = change;
_prevMomentum = momentum;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class currency_strength_v11_strategy(Strategy):
def __init__(self):
super(currency_strength_v11_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._difference_threshold = self.Param("DifferenceThreshold", 0.2)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DifferenceThreshold(self):
return self._difference_threshold.Value
@DifferenceThreshold.setter
def DifferenceThreshold(self, value):
self._difference_threshold.Value = value
def OnStarted2(self, time):
super(currency_strength_v11_strategy, self).OnStarted2(time)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
change = (close - open_price) / open_price * 100.0 if open_price != 0.0 else 0.0
if self._prev_change is None:
self._prev_change = change
return
momentum = change - self._prev_change
threshold = float(self.DifferenceThreshold)
long_signal = (self._prev_momentum is not None and
self._prev_momentum <= threshold and
momentum > threshold)
short_signal = (self._prev_momentum is not None and
self._prev_momentum >= -threshold and
momentum < -threshold)
cooldown_passed = (self._last_trade_time is None or
candle.CloseTime - self._last_trade_time >= TimeSpan.FromHours(24))
if cooldown_passed and long_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.BuyMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
elif cooldown_passed and short_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.SellMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
self._prev_change = change
self._prev_momentum = momentum
def OnReseted(self):
super(currency_strength_v11_strategy, self).OnReseted()
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
def CreateClone(self):
return currency_strength_v11_strategy()