GitHub で見る

Good Gbbi Strategy

This strategy opens a single position at a specific hour of the day based on the difference between historical open prices.

Logic

  • Works on hourly candles by default.
  • At TradeTime hour the strategy compares the open price from T1 bars ago with the open price from T2 bars ago.
  • If the older open is higher than the recent one by DeltaShort points a short position is opened.
  • If the recent open is higher than the older one by DeltaLong points a long position is opened.
  • Only one trade per day is allowed. Trading is enabled again after the hour is greater than TradeTime.
  • Each position is protected by individual take-profit and stop-loss levels and can be forcibly closed after MaxOpenTime hours.

Parameters

Parameter Description
TakeProfitLong Take profit distance in points for long positions.
StopLossLong Stop loss distance in points for long positions.
TakeProfitShort Take profit distance in points for short positions.
StopLossShort Stop loss distance in points for short positions.
TradeTime Hour of day when the entry conditions are checked.
T1 Number of bars back for the first open price.
T2 Number of bars back for the second open price.
DeltaLong Required difference in points to open a long position.
DeltaShort Required difference in points to open a short position.
MaxOpenTime Maximum position holding time in hours, 0 disables the check.
CandleType Candle type to process.

Notes

The original idea comes from the MetaTrader expert advisor GoodG@bi. This port uses StockSharp high-level API and processes only finished candles. Ensure that the security's PriceStep is configured correctly to interpret point values.

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Good Gbbi strategy.
/// Opens a position at specified hour based on historical open price
/// differences.
/// </summary>
public class GoodGbbiStrategy : Strategy {
	private readonly StrategyParam<int> _takeProfitLong;
	private readonly StrategyParam<int> _stopLossLong;
	private readonly StrategyParam<int> _takeProfitShort;
	private readonly StrategyParam<int> _stopLossShort;
	private readonly StrategyParam<int> _t1;
	private readonly StrategyParam<int> _t2;
	private readonly StrategyParam<int> _deltaLong;
	private readonly StrategyParam<int> _deltaShort;
	private readonly StrategyParam<int> _maxOpenTime;
	private readonly StrategyParam<DataType> _candleType;

	private readonly decimal[] _openPrices = new decimal[7];
	private int _candlesCount;
	private DateTimeOffset _entryTime;
	private decimal _entryPrice;

	/// <summary>
	/// Take profit in points for long positions.
	/// </summary>
	public int TakeProfitLong {
	get => _takeProfitLong.Value;
	set => _takeProfitLong.Value = value;
	}

	/// <summary>
	/// Stop loss in points for long positions.
	/// </summary>
	public int StopLossLong {
	get => _stopLossLong.Value;
	set => _stopLossLong.Value = value;
	}

	/// <summary>
	/// Take profit in points for short positions.
	/// </summary>
	public int TakeProfitShort {
	get => _takeProfitShort.Value;
	set => _takeProfitShort.Value = value;
	}

	/// <summary>
	/// Stop loss in points for short positions.
	/// </summary>
	public int StopLossShort {
	get => _stopLossShort.Value;
	set => _stopLossShort.Value = value;
	}

	/// <summary>
	/// Bar offset for first open price.
	/// </summary>
	public int T1 {
	get => _t1.Value;
	set => _t1.Value = value;
	}

	/// <summary>
	/// Bar offset for second open price.
	/// </summary>
	public int T2 {
	get => _t2.Value;
	set => _t2.Value = value;
	}

	/// <summary>
	/// Required open difference for long entries in points.
	/// </summary>
	public int DeltaLong {
	get => _deltaLong.Value;
	set => _deltaLong.Value = value;
	}

	/// <summary>
	/// Required open difference for short entries in points.
	/// </summary>
	public int DeltaShort {
	get => _deltaShort.Value;
	set => _deltaShort.Value = value;
	}

	/// <summary>
	/// Maximum position lifetime in hours.
	/// </summary>
	public int MaxOpenTime {
	get => _maxOpenTime.Value;
	set => _maxOpenTime.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="GoodGbbiStrategy"/>.
	/// </summary>
	public GoodGbbiStrategy() {
	_takeProfitLong =
		Param(nameof(TakeProfitLong), 39)
		.SetGreaterThanZero()
		.SetDisplay("Take Profit Long",
				"Profit target for long positions in points",
				"Risk Management");

	_stopLossLong =
		Param(nameof(StopLossLong), 147)
		.SetGreaterThanZero()
		.SetDisplay("Stop Loss Long",
				"Stop loss for long positions in points",
				"Risk Management");

	_takeProfitShort =
		Param(nameof(TakeProfitShort), 15)
		.SetGreaterThanZero()
		.SetDisplay("Take Profit Short",
				"Profit target for short positions in points",
				"Risk Management");

	_stopLossShort =
		Param(nameof(StopLossShort), 6000)
		.SetGreaterThanZero()
		.SetDisplay("Stop Loss Short",
				"Stop loss for short positions in points",
				"Risk Management");

	_t1 = Param(nameof(T1), 6)
		  .SetGreaterThanZero()
		  .SetDisplay("T1", "First open price offset", "Logic");

	_t2 = Param(nameof(T2), 2)
		  .SetGreaterThanZero()
		  .SetDisplay("T2", "Second open price offset", "Logic");

	_deltaLong =
		Param(nameof(DeltaLong), 6)
		.SetGreaterThanZero()
		.SetDisplay("Delta Long",
				"Open difference for long entries in points",
				"Logic");

	_deltaShort =
		Param(nameof(DeltaShort), 21)
		.SetGreaterThanZero()
		.SetDisplay("Delta Short",
				"Open difference for short entries in points",
				"Logic");

	_maxOpenTime =
		Param(nameof(MaxOpenTime), 504)
		.SetDisplay("Max Open Time",
				"Maximum holding time in hours (0 - unlimited)",
				"Risk Management");

	_candleType =
		Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted() {
	base.OnReseted();
	Array.Clear(_openPrices, 0, _openPrices.Length);
	_candlesCount = 0;
	_entryPrice = 0m;
	_entryTime = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time) {
	var subscription = SubscribeCandles(CandleType);
	subscription.Bind(ProcessCandle).Start();

	base.OnStarted2(time);
	}

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

	// store open price in circular buffer
	_openPrices[_candlesCount % _openPrices.Length] = candle.OpenPrice;
	_candlesCount++;

	var step = Security.PriceStep ?? 1m;

	// manage existing position and protection
	if (Position > 0) {
		var tp = _entryPrice + TakeProfitLong * step;
		var sl = _entryPrice - StopLossLong * step;
		var expired =
		MaxOpenTime > 0 &&
		(candle.OpenTime - _entryTime).TotalHours >= MaxOpenTime;
		if (candle.ClosePrice >= tp || candle.ClosePrice <= sl || expired)
		SellMarket();
		return;
	} else if (Position < 0) {
		var tp = _entryPrice - TakeProfitShort * step;
		var sl = _entryPrice + StopLossShort * step;
		var expired =
		MaxOpenTime > 0 &&
		(candle.OpenTime - _entryTime).TotalHours >= MaxOpenTime;
		if (candle.ClosePrice <= tp || candle.ClosePrice >= sl || expired)
		BuyMarket();
		return;
	}

	// ensure enough history is collected
	if (_candlesCount <= Math.Max(T1, T2))
		return;

	var openT1 = _openPrices[(_candlesCount - 1 - T1 + _openPrices.Length) %
				 _openPrices.Length];
	var openT2 = _openPrices[(_candlesCount - 1 - T2 + _openPrices.Length) %
				 _openPrices.Length];

	if (openT1 - openT2 > DeltaShort * step && Position >= 0) {
		SellMarket();
		_entryPrice = candle.ClosePrice;
		_entryTime = candle.OpenTime;
	} else if (openT2 - openT1 > DeltaLong * step && Position <= 0) {
		BuyMarket();
		_entryPrice = candle.ClosePrice;
		_entryTime = candle.OpenTime;
	}
	}
}