Открыть на GitHub

Стратегия Currency Strength v1.1

Обзор

Стратегия Currency Strength v1.1 воспроизводит логику одноимённого советника MetaTrader. Она оценивает относительную силу восьми основных валют (USD, EUR, JPY, CAD, AUD, NZD, GBP, CHF), используя процентное изменение 26 популярных валютных пар на дневных свечах. Когда расхождение сил двух валют превышает заданный порог, стратегия открывает позицию по соответствующей паре в сторону более сильной валюты.

Рынок и данные

  • Торгуемые инструменты: 26 основных и кросс-курсов (USDJPY, USDCAD, AUDUSD, USDCHF, GBPUSD, EURUSD, NZDUSD, EURJPY, EURCAD, EURGBP, EURCHF, EURAUD, EURNZD, AUDNZD, AUDCAD, AUDCHF, AUDJPY, CHFJPY, GBPCHF, GBPAUD, GBPCAD, GBPJPY, CADJPY, NZDJPY, GBPNZD, CADCHF).
  • Таймфрейм: дневные свечи (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

Каждый индекс использует то же количество компонентов, что и в исходном алгоритме, сохраняя исходные веса.

Логика торговли

  1. После получения новых завершённых дневных свечей по всем 26 парам пересчитываются силы валют.
  2. Для каждой пары сравниваются силы базовой и котируемой валют. Если модуль разницы превышает DifferenceThreshold, формируется сигнал.
  3. Направление сделки определяется более сильной валютой:
    • базовая валюта сильнее → покупка пары;
    • котируемая валюта сильнее → продажа пары.
  4. Сделка разрешена только в том случае, если дневная свеча пары подтверждает направление (закрытие выше открытия для покупок, ниже — для продаж), как и в оригинальном советнике.
  5. Стратегия учитывает чистую позицию. При появлении противоположного сигнала существующая позиция закрывается и сразу переворачивается рыночной заявкой.
  6. При включённом TradeOncePerDay каждая пара может открыть не более одной длинной и одной короткой позиции за торговый день.

Управление рисками и выходы

  • Флаг UseSlTp активирует дневные проверки стоп-лосса и тейк-профита. Расстояния задаются в пунктах (StopLossPips, TakeProfitPips).
  • Защитная логика анализирует максимум и минимум свежей дневной свечи. Если уровень достигнут, позиция закрывается рыночным ордером при следующей обработке.
  • Если стопы/тейки отключены, позиции удерживаются до появления противоположного сигнала или ручного закрытия, что соответствует поведению исходного советника.

Параметры стратегии

Параметр Описание
CandleType Таймфрейм свечей (по умолчанию — дневной).
DifferenceThreshold Минимальный разрыв сил валют (в процентных пунктах) для открытия сделки.
TradeOncePerDay Ограничивает число входов в день по каждому направлению.
UseSlTp Включает стоп-лосс и тейк-профит, рассчитываемые по дневным свечам.
TakeProfitPips Размер тейк-профита в пунктах.
StopLossPips Размер стоп-лосса в пунктах.
Параметры пар 26 параметров Security, которые необходимо задать перед запуском.
Volume Размер позиции (свойство базового класса, по умолчанию 0.01 лота).

Особенности реализации

  • Для каждой пары создаётся отдельная подписка на свечи через высокоуровневый метод SubscribeCandles.
  • В расчётах участвуют только свечи с состоянием Finished, что соответствует требованиям StockSharp.
  • Сигналы генерируются только после получения данных за одинаковую дату по всем парам, обеспечивая синхронность корзины валют.
  • Служебные словари хранят дату последней сделки по каждому направлению и параметры входа для контроля стопов/тейков.

Рекомендации по использованию

  1. Перед запуском стратегии задайте все 26 инструментов, иначе будет выброшено исключение.
  2. Убедитесь, что поставщик данных передаёт дневные свечи по каждому инструменту, чтобы расчёт сил оставался синхронным.
  3. Подбирайте DifferenceThreshold под желаемую активность: меньший порог даёт больше сделок и разворотов.
  4. Настраивайте параметры стопов в соответствии с точностью котировок вашего брокера; по умолчанию предполагается наличие дробных пунктов.
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;
	}
}