GitHub で見る

Tunnel Gen4 Hedged Grid Strategy

This strategy replicates the logic of the MetaTrader "Tunnel gen4" expert advisor using the StockSharp high-level API. It maintains a market neutral hedge by opening an initial buy/sell pair, doubles the position in the direction of the break-out once the price travels a configurable number of pips, and exits the entire basket when the same distance is covered again beyond the second anchor.

Trading Logic

  • Initial hedge: As soon as no exposure exists, the strategy sends simultaneous market buy and sell orders with volume StartVolume. The first fill defines the reference price for all subsequent decisions.
  • Step detection: The configured StepPips is converted to a price offset using the instrument tick size (with automatic adjustments for three- and five-decimal forex quotes). Best bid/ask updates from the Level 1 stream are compared against this offset.
  • Reinforcement order: When the best bid moves up by at least one step from the first fill, a sell order with twice the base volume is sent. When the best ask moves down by at least one step, a buy order of the same size is issued instead. The first fill of this order becomes the second anchor.
  • Cycle termination: After the second anchor is active, any further step-sized move in either direction triggers a full liquidation of all open positions. Once both sides are closed the state resets and a new cycle can start.
  • Volume validation: Strategy start-up checks that both the initial and doubled volumes respect the instrument's minimum, maximum, and increment requirements so that every order sent to the connector is executable.

Entry Conditions

Long reinforcement

  • There is at least one open position from the initial hedge.
  • The second anchor has not been created yet.
  • Current best ask price is less than or equal to first_fill_price - StepPips_in_price.

Short reinforcement

  • There is at least one open position from the initial hedge.
  • The second anchor has not been created yet.
  • Current best bid price is greater than or equal to first_fill_price + StepPips_in_price.

Exit Management

  • Basket closure: Once the second anchor is defined, if the best bid rises above second_anchor + StepOffset or the best ask falls below second_anchor - StepOffset, market orders are submitted to close the cumulative long and short exposure. Closing orders are tracked to ensure the state resets only after all trades are confirmed.
  • State reset: After both sides are closed and no closing orders remain active, the strategy clears internal anchors and waits for a new hedge to be opened.

Data and Indicators

  • Level 1 subscription delivers best bid and best ask prices used for step comparisons.
  • No additional indicators are required; the whole logic works on raw quote updates.
  • Price step conversion mimics the MetaTrader point-to-pip adjustment so forex symbols with three or five decimals behave the same as in the source expert.

Parameters

Parameter Description
StartVolume Volume of the buy and sell orders that form the initial hedge.
StepPips Distance in pips that triggers the reinforcement order and the subsequent basket exit.

Implementation Notes

  • StockSharp maintains a net position per security. The strategy keeps internal exposure counters to emulate the separate long and short tickets used by the MetaTrader expert and issues market orders with the accumulated volumes when closing the basket.
  • Because the logic relies on real-time spreads, provide Level 1 data in both backtests and live trading sessions. Missing bid/ask information disables the trading loop.
  • Ensure the trading account supports simultaneous buy and sell orders for the same instrument, as the algorithm assumes both sides of the hedge can coexist until the exit condition is met.
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;

/// <summary>
/// Tunnel strategy that uses Bollinger Bands to define a price channel.
/// Buys when price crosses above the lower band (reversal from oversold),
/// sells when price crosses below the upper band (reversal from overbought).
/// </summary>
public class TunnelGen4Strategy : Strategy
{
	private readonly StrategyParam<int> _bbLength;
	private readonly StrategyParam<decimal> _bbWidth;
	private readonly StrategyParam<decimal> _stepPips;

	private BollingerBands _bb;

	private decimal _prevClose;
	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _entryPrice;

	/// <summary>
	/// Bollinger Bands period length.
	/// </summary>
	public int BbLength
	{
		get => _bbLength.Value;
		set => _bbLength.Value = value;
	}

	/// <summary>
	/// Bollinger Bands width (standard deviations).
	/// </summary>
	public decimal BbWidth
	{
		get => _bbWidth.Value;
		set => _bbWidth.Value = value;
	}

	/// <summary>
	/// Step distance expressed in pips for profit target.
	/// </summary>
	public decimal StepPips
	{
		get => _stepPips.Value;
		set => _stepPips.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public TunnelGen4Strategy()
	{
		_bbLength = Param(nameof(BbLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Length", "Bollinger Bands period", "Indicator");

		_bbWidth = Param(nameof(BbWidth), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("BB Width", "Bollinger Bands width", "Indicator");

		_stepPips = Param(nameof(StepPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Step (pips)", "Distance between tunnel anchors", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_bb = null;
		_prevClose = 0;
		_prevUpper = 0;
		_prevLower = 0;
		_entryPrice = 0;
	}

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

		_bb = new BollingerBands
		{
			Length = BbLength,
			Width = BbWidth
		};

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.BindEx(_bb, OnProcess);
		subscription.Start();
	}

	private void OnProcess(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)value;
		if (bb.UpBand is not decimal upper ||
			bb.LowBand is not decimal lower)
			return;

		if (!_bb.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevUpper = upper;
			_prevLower = lower;
			return;
		}

		var close = candle.ClosePrice;

		// Buy signal: price crosses above lower band from below
		if (_prevClose < _prevLower && close >= lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
		}
		// Sell signal: price crosses below upper band from above
		else if (_prevClose > _prevUpper && close <= upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
		}

		// Exit on profit target if in position
		if (Position > 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice + StepPips * pipValue;
			if (close >= target)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice - StepPips * pipValue;
			if (close <= target)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		_prevClose = close;
		_prevUpper = upper;
		_prevLower = lower;
	}
}