Auf GitHub ansehen

HTH Trader Hedge Strategy

Overview

This strategy is a direct conversion of the MetaTrader "HTH Trader" expert advisor. It trades a four-leg forex basket and attempts to capture daily mean-reversion between EURUSD and a mirrored basket of USDCHF, GBPUSD, and AUDUSD. The StockSharp port keeps the original risk controls and timing rules while using the high-level API for multi-security trading.

Key characteristics:

  • Opens a hedged basket once per day between 00:05 and 00:12 terminal time.
  • Uses the previous two daily closes of EURUSD to decide the basket direction.
  • Manages four instruments simultaneously: EURUSD (primary security), USDCHF, GBPUSD, and AUDUSD.
  • Tracks open profit in pips and supports basket-wide profit and loss targets.
  • Includes an emergency doubling feature that adds to profitable legs when the basket drawdown breaches a threshold.
  • Closes all trades at 23:00 terminal time or when the basket hits configured profit/loss limits.

Data requirements

  • Intraday candles: All four symbols must deliver intraday candles for the timeframe configured in IntradayCandleType (default 5 minutes). These candles provide the latest price and the session clock.
  • Daily candles: Each symbol must provide daily candles so the strategy can monitor the latest two completed daily closes.

Trading logic

  1. At the end of each finished intraday candle the strategy checks current open profit:
    • If AllowEmergencyTrading is enabled and total open profit ≤ -EmergencyLossPips, the strategy doubles every leg that is currently in profit and disables further emergency trades for that day.
    • If UseProfitTarget is enabled and total open profit ≥ ProfitTargetPips, the basket is closed immediately.
    • If UseLossLimit is enabled and total open profit ≤ -LossLimitPips, the basket is closed immediately.
  2. Once the clock reaches 23:00 the basket is closed regardless of profit.
  3. When no positions are open and the clock is inside the 00:05–00:12 window, the strategy checks the latest two completed daily closes of the primary symbol (EURUSD by default):
    • If the day-over-day percentage change is positive, the strategy opens: long EURUSD, long USDCHF, short GBPUSD, long AUDUSD.
    • If the change is negative, it opens: short EURUSD, short USDCHF, long GBPUSD, short AUDUSD.
    • If the change is zero or any daily close is missing, the strategy skips trading for that day.
  4. All positions are closed using market orders via ClosePosition.

Parameters

Name Description Default
TradeEnabled Enables or disables order placement. true
ShowProfitInfo Logs the basket profit in pips on every update while positions are open. true
UseProfitTarget Enables auto-closing when ProfitTargetPips is reached. false
UseLossLimit Enables auto-closing when LossLimitPips is reached. false
AllowEmergencyTrading Allows the emergency doubling feature. true
EmergencyLossPips Basket drawdown (in pips) that triggers emergency doubling. 60
ProfitTargetPips Basket profit (in pips) that triggers closing when UseProfitTarget is enabled. 80
LossLimitPips Basket loss (in pips) that triggers closing when UseLossLimit is enabled. 40
TradingVolume Order volume for each leg. 0.01
Symbol2 Second security (USDCHF by default). null
Symbol3 Third security (GBPUSD by default). null
Symbol4 Fourth security (AUDUSD by default). null
IntradayCandleType Intraday timeframe used for scheduling and price updates. 5 minute candles

Usage notes

  • Assign the primary security (Strategy.Security) to EURUSD (or the desired leading pair) and map Symbol2, Symbol3, Symbol4 to the correlated instruments before starting.
  • Make sure each security has a valid PriceStep, otherwise profit calculations in pips cannot be performed and emergency logic will remain idle.
  • The emergency doubling feature only adds to legs that are currently profitable; losing legs are left untouched to avoid amplifying drawdown.
  • The implementation assumes market orders fill close to the latest candle close. For precise accounting, connect the strategy to a data feed that delivers timely intraday candles.
  • Because the logic is driven by a single bar per minute (or chosen timeframe), the original tick-by-tick MQL behaviour may differ slightly in execution timing, but trade sequencing and conditions match the reference expert advisor.
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;
	}
}