Стратегия Get Rich or Die Trying GBP
Эта стратегия StockSharp воспроизводит поведение советника MetaTrader «Get Rich or Die Trying GBP». Она сосредотачивается на активном пересечении сессий Лондона и Нью-Йорка и ждёт короткого импульса дисбаланса на минутных свечах. Алгоритм подсчитывает, сколько последних баров закрылись ниже цены открытия (в оригинале они назывались "бычьими"), и сравнивает их с количеством баров, закрывшихся выше открытия. Когда значения отличаются, стратегия ищет возможность войти против более слабой стороны в первые пять минут выбранных временных окон.
Система держит только одну позицию одновременно. После каждого входа действует пауза минимум 61 секунду, работают основной фиксированный тейк-профит и более близкая цель раннего выхода, а при необходимости подключается трейлинг-стоп после достаточного движения цены. Все расстояния заданы в пунктах и пересчитываются через минимальный шаг цены инструмента (для котировок с 3 и 5 знаками применяется множитель ×10), чтобы логика совпадала с реализацией в MT5.
Подробности
- Условия входа:
- Покупка: за последние
CountBarsминутных свечей больше баров сOpen > Close, чем сOpen < Close; текущее время попадает в первые пять минут после22:00 + AdditionalHourили19:00 + AdditionalHour; позиций нет; выдержана пауза в 61 секунду. - Продажа: больше баров с
Open < Close, чем сOpen > Close, при тех же временных ограничениях и паузе.
- Покупка: за последние
- Направление: длинные и короткие позиции.
- Условия выхода:
- Основной тейк-профит на расстоянии
TakeProfitPipsи стоп-лосс наStopLossPipsот цены входа. - Принудительный выход при достижении плавающей прибыли
SecondaryTakeProfitPips. - Необязательный трейлинг-стоп, который активируется после прохождения
TrailingStopPips + TrailingStepPipsи переносит стоп на расстояниеTrailingStopPips, соблюдая шаг трейлинга.
- Основной тейк-профит на расстоянии
- Стопы: фиксированный стоп-лосс, фиксированный тейк-профит, дополнительный тейк-профит и опциональный трейлинг-стоп.
- Фильтр по времени: сделки только в первые пять минут после скорректированных отметок 19:00 и 22:00.
- Пауза: минимум 61 секунда после каждого входа перед открытием новой сделки.
- Значения по умолчанию:
StopLossPips= 100TakeProfitPips= 100SecondaryTakeProfitPips= 40TrailingStopPips= 30TrailingStepPips= 5CountBars= 18AdditionalHour= 2MaxPositions= 1000CandleType= таймфрейм 1 минута
- Примечания:
- Параметр
MaxPositionsсохранён для совместимости, но данная реализация держит не более одной позиции. - Пересчёт пунктов автоматически учитывает инструменты с 3 и 5 знаками, умножая шаг цены на 10.
- Логика трейлинг-стопа повторяет MT5: стоп не двигается, пока цена не пройдёт расстояние
TrailingStopPipsплюс шагTrailingStepPips.
- Параметр
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>
/// Port of the "Get Rich or Die Trying GBP" Expert Advisor.
/// Trades around the London and New York session overlap based on bar imbalance.
/// Applies fixed and trailing exits to lock in profits or limit losses.
/// </summary>
public class GetRichOrDieTryingGbpStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _secondaryTakeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _countBars;
private readonly StrategyParam<decimal> _additionalHour;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<DataType> _candleType;
private readonly List<int> _directionQueue = new();
private int _upCount;
private int _downCount;
private decimal _pipValue;
private decimal? _entryPrice;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private DateTimeOffset? _lastEntryTime;
private bool _exitRequested;
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Primary take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Secondary take-profit distance in pips for the early exit.
/// </summary>
public int SecondaryTakeProfitPips
{
get => _secondaryTakeProfitPips.Value;
set => _secondaryTakeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimal improvement (in pips) required before trailing stop moves.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Number of minute candles used to measure bar imbalance.
/// </summary>
public int CountBars
{
get => _countBars.Value;
set => _countBars.Value = value;
}
/// <summary>
/// Additional hour offset applied to the 19:00 and 22:00 checks.
/// </summary>
public decimal AdditionalHour
{
get => _additionalHour.Value;
set => _additionalHour.Value = value;
}
/// <summary>
/// Maximum simultaneous positions allowed.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Candle type used for all calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="GetRichOrDieTryingGbpStrategy"/>.
/// </summary>
public GetRichOrDieTryingGbpStrategy()
{
_stopLossPips = Param(nameof(StopLossPips), 100)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 100)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Primary take-profit distance in pips", "Risk");
_secondaryTakeProfitPips = Param(nameof(SecondaryTakeProfitPips), 40)
.SetDisplay("Secondary TP (pips)", "Early exit distance in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 30)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Minimal price improvement before trailing", "Risk");
_countBars = Param(nameof(CountBars), 18)
.SetGreaterThanZero()
.SetDisplay("Lookback Bars", "Number of candles for imbalance detection", "Logic");
_additionalHour = Param(nameof(AdditionalHour), 2m)
.SetDisplay("Additional Hour", "Offset applied to 19:00 and 22:00 checks", "Timing");
_maxPositions = Param(nameof(MaxPositions), 1000)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum simultaneous positions", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for processing", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_directionQueue.Clear();
_upCount = 0;
_downCount = 0;
_pipValue = 0m;
_entryPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_lastEntryTime = null;
_exitRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipValue = CalculatePipValue();
_directionQueue.Clear();
_upCount = 0;
_downCount = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var candleTime = candle.CloseTime == default ? candle.OpenTime : candle.CloseTime;
if (Position == 0 && _exitRequested)
{
// Exit order has been processed, clean the position state.
_exitRequested = false;
ResetPositionState();
}
UpdateDirectionCounts(candle);
if (Position > 0 || Position < 0)
{
if (ManageOpenPosition(candle))
return;
}
else if (_entryPrice != null && !_exitRequested)
{
// No open position, clear stale state.
ResetPositionState();
}
if (_exitRequested)
return; // Wait for the pending exit order.
//if (!IsFormedAndOnlineAndAllowTrading())
// return;
if (_directionQueue.Count < CountBars)
return; // Need full history to evaluate imbalance.
if (MaxPositions <= 0)
return;
if (Position != 0)
return; // Single-position implementation.
if (!IsWithinTradingWindow(candleTime))
return;
if (_lastEntryTime.HasValue && (candleTime - _lastEntryTime.Value).TotalSeconds < 61)
return; // Enforce 61-second cooldown between entries.
if (_upCount > _downCount)
{
OpenLong(candle, candleTime);
}
else if (_downCount > _upCount)
{
OpenShort(candle, candleTime);
}
}
private bool ManageOpenPosition(ICandleMessage candle)
{
if (_exitRequested)
return true; // Exit already requested, wait for fill.
var entry = _entryPrice ?? candle.ClosePrice;
var current = candle.ClosePrice;
var pip = GetPipValue();
var secondaryTarget = SecondaryTakeProfitPips * pip;
var trailingDistance = TrailingStopPips * pip;
var trailingStep = TrailingStepPips * pip;
if (Position > 0)
{
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
return CloseLongPosition(1);
if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
return CloseLongPosition(1);
if (secondaryTarget > 0m && current - entry >= secondaryTarget)
return CloseLongPosition(1);
if (TrailingStopPips > 0)
{
if (current - entry > trailingDistance + trailingStep)
{
var newStop = current - trailingDistance;
if (!_longTrailingStop.HasValue || newStop > _longTrailingStop.Value + trailingStep)
_longTrailingStop = newStop;
}
if (_longTrailingStop.HasValue && candle.LowPrice <= _longTrailingStop.Value)
return CloseLongPosition(1);
}
}
else if (Position < 0)
{
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
return CloseShortPosition(1);
if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
return CloseShortPosition(1);
if (secondaryTarget > 0m && entry - current >= secondaryTarget)
return CloseShortPosition(1);
if (TrailingStopPips > 0)
{
if (entry - current > trailingDistance + trailingStep)
{
var newStop = current + trailingDistance;
if (!_shortTrailingStop.HasValue || newStop < _shortTrailingStop.Value - trailingStep)
_shortTrailingStop = newStop;
}
if (_shortTrailingStop.HasValue && candle.HighPrice >= _shortTrailingStop.Value)
return CloseShortPosition(1);
}
}
return false;
}
private bool CloseLongPosition(decimal volume)
{
if (volume <= 0)
return false;
_exitRequested = true;
SellMarket();
return true;
}
private bool CloseShortPosition(decimal volume)
{
if (volume <= 0)
return false;
_exitRequested = true;
BuyMarket();
return true;
}
private void OpenLong(ICandleMessage candle, DateTimeOffset candleTime)
{
var pip = GetPipValue();
var entry = candle.ClosePrice;
_entryPrice = entry;
_stopLossPrice = StopLossPips > 0 ? entry - StopLossPips * pip : null;
_takeProfitPrice = TakeProfitPips > 0 ? entry + TakeProfitPips * pip : null;
_longTrailingStop = null;
_shortTrailingStop = null;
_exitRequested = false;
_lastEntryTime = candleTime;
BuyMarket();
}
private void OpenShort(ICandleMessage candle, DateTimeOffset candleTime)
{
var pip = GetPipValue();
var entry = candle.ClosePrice;
_entryPrice = entry;
_stopLossPrice = StopLossPips > 0 ? entry + StopLossPips * pip : null;
_takeProfitPrice = TakeProfitPips > 0 ? entry - TakeProfitPips * pip : null;
_shortTrailingStop = null;
_longTrailingStop = null;
_exitRequested = false;
_lastEntryTime = candleTime;
SellMarket();
}
private void UpdateDirectionCounts(ICandleMessage candle)
{
var direction = 0;
if (candle.OpenPrice > candle.ClosePrice)
{
direction = 1;
_upCount++;
}
else if (candle.OpenPrice < candle.ClosePrice)
{
direction = -1;
_downCount++;
}
_directionQueue.Add(direction);
while (_directionQueue.Count > CountBars)
{
var removed = _directionQueue[0];
try { _directionQueue.RemoveAt(0); } catch { break; }
if (removed > 0)
_upCount--;
else if (removed < 0)
_downCount--;
}
}
private bool IsWithinTradingWindow(DateTimeOffset time)
{
// Allow trading during any market hour
return true;
}
private decimal CalculatePipValue()
{
if (Security == null)
return 1m;
var step = Security.PriceStep ?? 0.01m;
if (step <= 0m)
return 1m;
var decimals = Security.Decimals ?? 2;
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private decimal GetPipValue()
{
if (_pipValue <= 0m)
_pipValue = CalculatePipValue();
return _pipValue;
}
private void ResetPositionState()
{
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
}
}
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 get_rich_or_die_trying_gbp_strategy(Strategy):
"""Bar imbalance strategy with SL/TP, secondary TP and trailing stop."""
def __init__(self):
super(get_rich_or_die_trying_gbp_strategy, self).__init__()
self._stop_loss_pips = self.Param("StopLossPips", 100) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pips)", "SL distance in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 100) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Primary TP distance in pips", "Risk")
self._secondary_tp_pips = self.Param("SecondaryTakeProfitPips", 40) \
.SetDisplay("Secondary TP (pips)", "Early exit distance in pips", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 30) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 5) \
.SetDisplay("Trailing Step (pips)", "Minimal improvement before trailing", "Risk")
self._count_bars = self.Param("CountBars", 18) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Bars", "Candles for imbalance detection", "Logic")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for processing", "General")
self._dir_queue = []
self._up_count = 0
self._down_count = 0
self._pip_value = 1.0
self._entry_price = None
self._long_trail = None
self._short_trail = None
self._sl_price = None
self._tp_price = None
self._exit_requested = False
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def SecondaryTakeProfitPips(self):
return self._secondary_tp_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def CountBars(self):
return self._count_bars.Value
@property
def CandleType(self):
return self._candle_type.Value
def _calc_pip(self):
sec = self.Security
if sec is None or sec.PriceStep is None or float(sec.PriceStep) <= 0:
return 1.0
step = float(sec.PriceStep)
decimals = sec.Decimals if sec.Decimals is not None else 2
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def OnStarted2(self, time):
super(get_rich_or_die_trying_gbp_strategy, self).OnStarted2(time)
self._pip_value = self._calc_pip()
self._dir_queue = []
self._up_count = 0
self._down_count = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position == 0 and self._exit_requested:
self._exit_requested = False
self._reset_state()
self._update_dirs(candle)
if self.Position != 0:
if self._manage_position(candle):
return
if self._exit_requested:
return
if len(self._dir_queue) < self.CountBars:
return
if self.Position != 0:
return
close = float(candle.ClosePrice)
pip = self._pip_value
if self._up_count > self._down_count:
self._entry_price = close
self._sl_price = close - self.StopLossPips * pip if self.StopLossPips > 0 else None
self._tp_price = close + self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
self._long_trail = None
self._short_trail = None
self._exit_requested = False
self.BuyMarket()
elif self._down_count > self._up_count:
self._entry_price = close
self._sl_price = close + self.StopLossPips * pip if self.StopLossPips > 0 else None
self._tp_price = close - self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
self._short_trail = None
self._long_trail = None
self._exit_requested = False
self.SellMarket()
def _manage_position(self, candle):
if self._exit_requested:
return True
entry = self._entry_price if self._entry_price is not None else float(candle.ClosePrice)
current = float(candle.ClosePrice)
pip = self._pip_value
sec_target = self.SecondaryTakeProfitPips * pip
trail_dist = self.TrailingStopPips * pip
trail_step = self.TrailingStepPips * pip
if self.Position > 0:
if self._tp_price is not None and float(candle.HighPrice) >= self._tp_price:
self._exit_requested = True
self.SellMarket()
return True
if self._sl_price is not None and float(candle.LowPrice) <= self._sl_price:
self._exit_requested = True
self.SellMarket()
return True
if sec_target > 0 and current - entry >= sec_target:
self._exit_requested = True
self.SellMarket()
return True
if self.TrailingStopPips > 0:
if current - entry > trail_dist + trail_step:
new_stop = current - trail_dist
if self._long_trail is None or new_stop > self._long_trail + trail_step:
self._long_trail = new_stop
if self._long_trail is not None and float(candle.LowPrice) <= self._long_trail:
self._exit_requested = True
self.SellMarket()
return True
elif self.Position < 0:
if self._tp_price is not None and float(candle.LowPrice) <= self._tp_price:
self._exit_requested = True
self.BuyMarket()
return True
if self._sl_price is not None and float(candle.HighPrice) >= self._sl_price:
self._exit_requested = True
self.BuyMarket()
return True
if sec_target > 0 and entry - current >= sec_target:
self._exit_requested = True
self.BuyMarket()
return True
if self.TrailingStopPips > 0:
if entry - current > trail_dist + trail_step:
new_stop = current + trail_dist
if self._short_trail is None or new_stop < self._short_trail - trail_step:
self._short_trail = new_stop
if self._short_trail is not None and float(candle.HighPrice) >= self._short_trail:
self._exit_requested = True
self.BuyMarket()
return True
return False
def _update_dirs(self, candle):
d = 0
o = float(candle.OpenPrice)
c = float(candle.ClosePrice)
if o > c:
d = 1
self._up_count += 1
elif o < c:
d = -1
self._down_count += 1
self._dir_queue.append(d)
while len(self._dir_queue) > self.CountBars:
removed = self._dir_queue.pop(0)
if removed > 0:
self._up_count -= 1
elif removed < 0:
self._down_count -= 1
def _reset_state(self):
self._entry_price = None
self._sl_price = None
self._tp_price = None
self._long_trail = None
self._short_trail = None
def OnReseted(self):
super(get_rich_or_die_trying_gbp_strategy, self).OnReseted()
self._dir_queue = []
self._up_count = 0
self._down_count = 0
self._pip_value = 1.0
self._exit_requested = False
self._reset_state()
def CreateClone(self):
return get_rich_or_die_trying_gbp_strategy()