Открыть на GitHub

Стратегия HTH Trader Hedge

Описание

Стратегия представляет собой перенос эксперта MetaTrader «HTH Trader». Она торгует корзину из четырёх валютных пар, пытаясь использовать дневное расхождение между EURUSD и зеркальной комбинацией USDCHF, GBPUSD и AUDUSD. Реализация для StockSharp сохраняет первоначальные правила по времени, управлению риском и экстренному усреднению.

Основные особенности:

  • Один раз в сутки, в промежутке 00:05–00:12 по серверному времени, формируется хедж из четырёх позиций.
  • Направление корзины определяется знаком изменения закрытия EURUSD за предыдущие два торговых дня.
  • Одновременно управляет четырьмя инструментами: EURUSD (основной), USDCHF, GBPUSD и AUDUSD.
  • Подсчитывает общий нереализованный результат в пунктах и поддерживает дневные цели по прибыли и убытку.
  • Содержит аварийную функцию удвоения прибыльных ног при достижении заданной просадки.
  • Закрывает все позиции в 23:00 или при срабатывании целевых значений.

Требования к данным

  • Внутридневные свечи: все четыре инструмента должны предоставлять свечи выбранного таймфрейма (IntradayCandleType, по умолчанию 5 минут) для контроля времени и текущей цены.
  • Дневные свечи: для каждого инструмента необходимы дневные свечи, чтобы отслеживать два последних закрытия.

Логика работы

  1. После формирования каждой внутридневной свечи стратегия вычисляет суммарную прибыль в пунктах:
    • При включённом AllowEmergencyTrading и просадке ≤ -EmergencyLossPips происходит удвоение всех прибыльных ног, после чего аварийная функция отключается до следующего дня.
    • При активном UseProfitTarget и прибыли ≥ ProfitTargetPips корзина закрывается.
    • При активном UseLossLimit и убытке ≤ -LossLimitPips корзина закрывается.
  2. В 23:00 все позиции закрываются независимо от результата.
  3. Когда позиций нет и текущее время попадает в окно 00:05–00:12, анализируются два последних дневных закрытия EURUSD:
    • Положительное изменение запускает покупки EURUSD, USDCHF и AUDUSD и продажу GBPUSD.
    • Отрицательное изменение приводит к продажам EURUSD, USDCHF и AUDUSD и покупке GBPUSD.
    • При нулевом изменении или отсутствии данных торговля пропускается.
  4. Закрытие осуществляется рыночными заявками через ClosePosition.

Параметры

Имя Назначение Значение по умолчанию
TradeEnabled Разрешение на выставление сделок. true
ShowProfitInfo Логирование текущей прибыли корзины в пунктах. true
UseProfitTarget Автоматическое закрытие по цели прибыли. false
UseLossLimit Автоматическое закрытие по пределу убытка. false
AllowEmergencyTrading Разрешение аварийного удвоения. true
EmergencyLossPips Просадка в пунктах для запуска удвоения. 60
ProfitTargetPips Цель по прибыли (пункты). 80
LossLimitPips Допустимый убыток (пункты). 40
TradingVolume Объём каждой ноги. 0.01
Symbol2 Второй инструмент (USDCHF по умолчанию). null
Symbol3 Третий инструмент (GBPUSD по умолчанию). null
Symbol4 Четвёртый инструмент (AUDUSD по умолчанию). null
IntradayCandleType Таймфрейм внутренних свечей. Свечи 5 минут

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

  • Назначьте Strategy.Security на EURUSD (или другой ведущий актив) и заполните параметры Symbol2, Symbol3, Symbol4 соответствующими бумагами до запуска.
  • Убедитесь, что для каждого инструмента задан корректный PriceStep; без него расчёт пунктов невозможен и аварийная логика не сработает.
  • Аварийное удвоение применяется только к прибыльным ногам, чтобы не увеличивать текущий убыток.
  • Предполагается исполнение рыночных заявок около цены закрытия последней свечи; для точного повторения необходимо обеспечить стабильный поток внутридневных данных.
  • Поскольку стратегия реагирует на закрытие свечей, возможны небольшие отличия по времени входа относительно тиковой версии в MetaTrader, однако последовательность решений полностью соответствует оригиналу.
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>
/// Hedge strategy simplified from HTH Trader. Trades based on daily close deviation.
/// </summary>
public class HthTraderStrategy : Strategy
{
	private readonly StrategyParam<bool> _tradeEnabled;
	private readonly StrategyParam<bool> _useProfitTarget;
	private readonly StrategyParam<bool> _useLossLimit;
	private readonly StrategyParam<int> _profitTargetPips;
	private readonly StrategyParam<int> _lossLimitPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose1;
	private decimal _prevClose2;
	private decimal _entryPrice;
	private decimal _priceStep;

	/// <summary>
	/// Enable automated trading.
	/// </summary>
	public bool TradeEnabled
	{
		get => _tradeEnabled.Value;
		set => _tradeEnabled.Value = value;
	}

	/// <summary>
	/// Enable closing by reaching the profit target.
	/// </summary>
	public bool UseProfitTarget
	{
		get => _useProfitTarget.Value;
		set => _useProfitTarget.Value = value;
	}

	/// <summary>
	/// Enable closing by reaching the loss limit.
	/// </summary>
	public bool UseLossLimit
	{
		get => _useLossLimit.Value;
		set => _useLossLimit.Value = value;
	}

	/// <summary>
	/// Profit target in pips.
	/// </summary>
	public int ProfitTargetPips
	{
		get => _profitTargetPips.Value;
		set => _profitTargetPips.Value = value;
	}

	/// <summary>
	/// Loss limit in pips.
	/// </summary>
	public int LossLimitPips
	{
		get => _lossLimitPips.Value;
		set => _lossLimitPips.Value = value;
	}

	/// <summary>
	/// Candle type for monitoring.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes parameters.
	/// </summary>
	public HthTraderStrategy()
	{
		_tradeEnabled = Param(nameof(TradeEnabled), true)
			.SetDisplay("Trade Enabled", "Allow the strategy to submit orders", "General");

		_useProfitTarget = Param(nameof(UseProfitTarget), true)
			.SetDisplay("Use Profit Target", "Close when profit target is reached", "Risk");

		_useLossLimit = Param(nameof(UseLossLimit), true)
			.SetDisplay("Use Loss Limit", "Close when loss limit is reached", "Risk");

		_profitTargetPips = Param(nameof(ProfitTargetPips), 80)
			.SetDisplay("Profit Target (pips)", "Profit target in pips", "Risk");

		_lossLimitPips = Param(nameof(LossLimitPips), 40)
			.SetDisplay("Loss Limit (pips)", "Loss limit in pips", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for monitoring", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose1 = 0m;
		_prevClose2 = 0m;
		_entryPrice = 0m;
		_priceStep = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_priceStep = Security?.PriceStep ?? 0.0001m;
		if (_priceStep <= 0m)
			_priceStep = 0.0001m;

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!TradeEnabled)
			return;

		// Check exit conditions
		if (Position != 0 && _entryPrice > 0m)
		{
			var priceDiff = Position > 0
				? candle.ClosePrice - _entryPrice
				: _entryPrice - candle.ClosePrice;

			var pipsDiff = priceDiff / _priceStep;

			if (UseProfitTarget && pipsDiff >= ProfitTargetPips)
			{
				if (Position > 0) SellMarket();
				else BuyMarket();
				_entryPrice = 0m;
				return;
			}

			if (UseLossLimit && pipsDiff <= -LossLimitPips)
			{
				if (Position > 0) SellMarket();
				else BuyMarket();
				_entryPrice = 0m;
				return;
			}
		}

		// Entry logic based on daily close deviation
		if (Position == 0 && _prevClose1 > 0m && _prevClose2 > 0m)
		{
			var deviation = (100m * _prevClose1 / _prevClose2) - 100m;

			if (deviation > 0.1m)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (deviation < -0.1m)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
		}

		_prevClose2 = _prevClose1;
		_prevClose1 = candle.ClosePrice;
	}
}