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
TradeTimehour the strategy compares the open price fromT1bars ago with the open price fromT2bars ago. - If the older open is higher than the recent one by
DeltaShortpoints a short position is opened. - If the recent open is higher than the older one by
DeltaLongpoints 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
MaxOpenTimehours.
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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class good_gbbi_strategy(Strategy):
"""
Good Gbbi strategy.
Opens positions based on historical open price differences
using circular buffer comparison. Manages SL/TP and time-based exit.
"""
def __init__(self):
super(good_gbbi_strategy, self).__init__()
self._take_profit_long = self.Param("TakeProfitLong", 39) \
.SetDisplay("Take Profit Long", "Profit target for longs in points", "Risk Management")
self._stop_loss_long = self.Param("StopLossLong", 147) \
.SetDisplay("Stop Loss Long", "Stop loss for longs in points", "Risk Management")
self._take_profit_short = self.Param("TakeProfitShort", 15) \
.SetDisplay("Take Profit Short", "Profit target for shorts in points", "Risk Management")
self._stop_loss_short = self.Param("StopLossShort", 6000) \
.SetDisplay("Stop Loss Short", "Stop loss for shorts in points", "Risk Management")
self._t1 = self.Param("T1", 6) \
.SetDisplay("T1", "First open price offset", "Logic")
self._t2 = self.Param("T2", 2) \
.SetDisplay("T2", "Second open price offset", "Logic")
self._delta_long = self.Param("DeltaLong", 6) \
.SetDisplay("Delta Long", "Open difference for long entries in points", "Logic")
self._delta_short = self.Param("DeltaShort", 21) \
.SetDisplay("Delta Short", "Open difference for short entries in points", "Logic")
self._max_open_time = self.Param("MaxOpenTime", 504) \
.SetDisplay("Max Open Time", "Maximum holding time in hours (0 = unlimited)", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._open_prices = [0.0] * 7
self._candles_count = 0
self._entry_price = 0.0
self._entry_time = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(good_gbbi_strategy, self).OnReseted()
self._open_prices = [0.0] * 7
self._candles_count = 0
self._entry_price = 0.0
self._entry_time = None
def OnStarted2(self, time):
super(good_gbbi_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
buf_len = len(self._open_prices)
self._open_prices[self._candles_count % buf_len] = float(candle.OpenPrice)
self._candles_count += 1
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
close = float(candle.ClosePrice)
if self.Position > 0:
tp = self._entry_price + self._take_profit_long.Value * step
sl = self._entry_price - self._stop_loss_long.Value * step
expired = False
if self._max_open_time.Value > 0 and self._entry_time is not None:
elapsed = (candle.OpenTime - self._entry_time).TotalHours
expired = elapsed >= self._max_open_time.Value
if close >= tp or close <= sl or expired:
self.SellMarket()
return
elif self.Position < 0:
tp = self._entry_price - self._take_profit_short.Value * step
sl = self._entry_price + self._stop_loss_short.Value * step
expired = False
if self._max_open_time.Value > 0 and self._entry_time is not None:
elapsed = (candle.OpenTime - self._entry_time).TotalHours
expired = elapsed >= self._max_open_time.Value
if close <= tp or close >= sl or expired:
self.BuyMarket()
return
t1 = self._t1.Value
t2 = self._t2.Value
if self._candles_count <= max(t1, t2):
return
idx_t1 = (self._candles_count - 1 - t1 + buf_len) % buf_len
idx_t2 = (self._candles_count - 1 - t2 + buf_len) % buf_len
open_t1 = self._open_prices[idx_t1]
open_t2 = self._open_prices[idx_t2]
if open_t1 - open_t2 > self._delta_short.Value * step and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._entry_time = candle.OpenTime
elif open_t2 - open_t1 > self._delta_long.Value * step and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._entry_time = candle.OpenTime
def CreateClone(self):
return good_gbbi_strategy()