Стратегия Good Gbbi
Стратегия открывает одну позицию в заданный час суток, опираясь на разницу между историческими ценами открытия.
Логика
- По умолчанию используется часовый таймфрейм.
- В момент
TradeTimeсравниваются цены открытияT1иT2баров назад. - Если более старая цена выше недавней на
DeltaShortпунктов — открывается короткая позиция. - Если недавняя цена выше старой на
DeltaLongпунктов — открывается длинная позиция. - В день допускается только одна сделка. Торговля снова разрешается после того, как час станет больше
TradeTime. - Каждая позиция имеет собственные уровни тейк-профита и стоп-лосса и может быть закрыта принудительно по истечении
MaxOpenTimeчасов.
Параметры
| Параметр | Описание |
|---|---|
TakeProfitLong |
Тейк-профит в пунктах для длинных позиций. |
StopLossLong |
Стоп-лосс в пунктах для длинных позиций. |
TakeProfitShort |
Тейк-профит в пунктах для коротких позиций. |
StopLossShort |
Стоп-лосс в пунктах для коротких позиций. |
TradeTime |
Час суток, когда проверяются условия входа. |
T1 |
Количество баров назад для первой цены открытия. |
T2 |
Количество баров назад для второй цены открытия. |
DeltaLong |
Необходимая разница в пунктах для открытия длинной позиции. |
DeltaShort |
Необходимая разница в пунктах для открытия короткой позиции. |
MaxOpenTime |
Максимальное время удержания позиции в часах, 0 отключает проверку. |
CandleType |
Тип обрабатываемых свечей. |
Примечания
Идея взята из советника MetaTrader GoodG@bi. Эта реализация использует высокоуровневый API StockSharp и обрабатывает только завершённые свечи. Убедитесь, что у инструмента корректно задан шаг цены (PriceStep).
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()