Estrategia de Secuencia de N Velas
Descripción general
La estrategia de Secuencia de N Velas replica el comportamiento del experto MetaTrader original "N-_Candles_v7" usando la API de alto nivel de StockSharp. Monitorea velas finalizadas y busca un número configurable de cuerpos consecutivos alcistas o bajistas. Cuando hay una racha calificada presente, la estrategia abre una posición en la misma dirección y la gestiona con take profit, stop loss, trailing stop, filtro de horas de trading y bloqueo de beneficio flotante configurables.
Lógica de trading
- Evalúa cada vela finalizada y la clasifica como alcista, bajista o neutral (doji). Las velas neutras reinician el contador de racha y pueden activar el comportamiento de "oveja negra".
- Mantiene un recuento continuo de velas consecutivas con la misma dirección de cuerpo. Una vez que el recuento alcanza el umbral configurado, la dirección actual se convierte en el patrón activo.
- Cuando hay una racha alcista activa, la estrategia intenta abrir una posición larga; cuando hay una racha bajista activa, intenta abrir una posición corta. Solo se mantiene una posición neta a la vez.
- Si una vela rompe la dirección uniforme ("oveja negra"), la estrategia reacciona según el modo de cierre seleccionado: cerrar todo, cerrar solo posiciones opuestas, o cerrar solo posiciones alineadas con la racha anterior.
- Opcionalmente, restringe las entradas a una ventana de trading definida por horas de inicio y fin (inclusivas).
- Monitorea continuamente la posición abierta para take profit, stop loss, actualizaciones de trailing stop y el umbral de beneficio flotante.
Gestión de posición y riesgo
- El stop inicial de protección y el objetivo se calculan a partir de distancias en pips convertidas con el paso de precio del instrumento. Estos niveles se recalculan cada vez que se abre una nueva posición.
- La lógica del trailing stop imita el experto original: una vez que el precio recorre la distancia de trailing más el paso, el stop se mueve para mantener la distancia de trailing.
- Un guardián de beneficio flotante (
MinProfit) cierra toda la posición una vez que el beneficio abierto supera el valor configurado. - El parámetro
MaxPositionVolumeevita entradas si el volumen solicitado supera el límite permitido.MaxPositionsfunciona como protección contra la re-entrada cuando ya hay una posición activa. - Todas las salidas llaman a órdenes de mercado para aplanar la posición neta porque la estrategia de StockSharp opera en un entorno de compensación.
Parámetros
| Nombre | Descripción |
|---|---|
ConsecutiveCandles |
Número de velas con dirección idéntica necesarias para activar una señal. |
OrderVolume |
Volumen de orden de mercado usado para entradas y salidas. |
TakeProfitPips |
Distancia de take profit expresada en pips. Poner en cero para deshabilitar. |
StopLossPips |
Distancia de stop loss expresada en pips. Poner en cero para deshabilitar. |
TrailingStopPips |
Distancia del trailing stop. Poner en cero para deshabilitar el trailing. |
TrailingStepPips |
Distancia adicional requerida antes de que se mueva el trailing stop. |
MaxPositions |
Número máximo de entradas simultáneas por patrón (la estrategia mantiene una sola posición neta). |
MaxPositionVolume |
Límite superior para el volumen neto permitido. |
UseTradeHours / StartHour / EndHour |
Habilitar y configurar la ventana de tiempo de trading (inclusiva). |
MinProfit |
Umbral de beneficio flotante que activa una salida completa. |
ClosingBehavior |
Define cómo reaccionar cuando aparece una vela de "oveja negra". |
CandleType |
La serie de velas usada para los cálculos. |
Notas y supuestos
- La estrategia opera con posiciones netas; no se crean múltiples tickets de estilo cobertura. Esto difiere del experto original donde varias posiciones cubiertas podían coexistir.
- El beneficio flotante se aproxima como
(precio actual - precio de entrada) * volumenpara posiciones largas y lo inverso para posiciones cortas. - La conversión de pips depende del
PriceStepdel instrumento. Para símbolos donde no se proporciona el paso mínimo, se asume un pip predeterminado de 0.0001. - No se proporciona portación a Python, según lo solicitado.
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>
/// Strategy that searches for N identical candles in a row and trades in the direction of the streak.
/// Implements optional take profit, stop loss, trailing stop, trading hours filter and profit lock.
/// </summary>
public class NCandlesSequenceStreakStrategy : Strategy
{
private readonly StrategyParam<int> _consecutiveCandles;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<decimal> _maxPositionVolume;
private readonly StrategyParam<bool> _useTradeHours;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<decimal> _minProfit;
private readonly StrategyParam<ClosingModes> _closingBehavior;
private readonly StrategyParam<DataType> _candleType;
private int _streakCount;
private int _lastDirection;
private int _patternDirection;
private int _entriesInDirection;
private bool _blackSheepTriggered;
private bool _hasPosition;
private decimal _entryPrice;
private decimal _pipSize;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
/// <summary>
/// Defines how positions are closed when a "black sheep" candle appears.
/// </summary>
public enum ClosingModes
{
/// <summary>Close every open position.</summary>
All,
/// <summary>Close positions opposite to the previously detected streak.</summary>
Opposite,
/// <summary>Close positions that follow the previously detected streak.</summary>
SameDirection
}
/// <summary>
/// Initializes a new instance of the <see cref="NCandlesSequenceStreakStrategy"/> class.
/// </summary>
public NCandlesSequenceStreakStrategy()
{
_consecutiveCandles = Param(nameof(ConsecutiveCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Consecutive Candles", "Number of candles with identical direction", "Pattern")
.SetOptimize(2, 10, 1);
_orderVolume = Param(nameof(OrderVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume used for market orders", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 50)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Distance for the take profit target", "Risk")
.SetOptimize(10, 200, 10);
_stopLossPips = Param(nameof(StopLossPips), 50)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Distance for the protective stop", "Risk")
.SetOptimize(10, 200, 10);
_trailingStopPips = Param(nameof(TrailingStopPips), 10)
.SetDisplay("Trailing Stop (pips)", "Distance used when trailing is active", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 4)
.SetDisplay("Trailing Step (pips)", "Additional distance before moving the trailing stop", "Risk");
_maxPositions = Param(nameof(MaxPositions), 2)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum number of sequential entries in the same direction", "Risk");
_maxPositionVolume = Param(nameof(MaxPositionVolume), 2m)
.SetGreaterThanZero()
.SetDisplay("Max Position Volume", "Maximum volume allowed for an open position", "Risk");
_useTradeHours = Param(nameof(UseTradeHours), false)
.SetDisplay("Use Trade Hours", "Enable intraday trading window", "Timing");
_startHour = Param(nameof(StartHour), 11)
.SetDisplay("Start Hour", "First trading hour (0-23)", "Timing");
_endHour = Param(nameof(EndHour), 18)
.SetDisplay("End Hour", "Last trading hour (0-23)", "Timing");
_minProfit = Param(nameof(MinProfit), 3m)
.SetDisplay("Min Profit", "Close positions when floating profit exceeds this value", "Risk")
.SetOptimize(1m, 20m, 1m);
_closingBehavior = Param(nameof(ClosingBehavior), ClosingModes.All)
.SetDisplay("Black Sheep Closing", "Reaction when the streak is broken", "Pattern");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to analyze", "General");
}
/// <summary>
/// Required number of candles with the same direction.
/// </summary>
public int ConsecutiveCandles
{
get => _consecutiveCandles.Value;
set => _consecutiveCandles.Value = value;
}
/// <summary>
/// Volume for market orders.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Additional step before the trailing stop is moved.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Maximum number of consecutive entries in the same direction.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Maximum allowed volume for an open position.
/// </summary>
public decimal MaxPositionVolume
{
get => _maxPositionVolume.Value;
set => _maxPositionVolume.Value = value;
}
/// <summary>
/// Enables the trading hours filter.
/// </summary>
public bool UseTradeHours
{
get => _useTradeHours.Value;
set => _useTradeHours.Value = value;
}
/// <summary>
/// First trading hour (inclusive).
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Last trading hour (inclusive).
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Minimum floating profit that forces the strategy to close all positions.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// How to handle positions when the streak is broken.
/// </summary>
public ClosingModes ClosingBehavior
{
get => _closingBehavior.Value;
set => _closingBehavior.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_streakCount = 0;
_lastDirection = 0;
_patternDirection = 0;
_entriesInDirection = 0;
_blackSheepTriggered = false;
_hasPosition = false;
_entryPrice = 0m;
_pipSize = 0m;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (UseTradeHours && StartHour >= EndHour)
throw new InvalidOperationException("Start hour must be less than end hour when the trading window is enabled.");
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// no indicators to check formation
UpdateTrailingStops(candle);
ManageFloatingProfit(candle);
var direction = GetCandleDirection(candle);
if (direction == 0)
{
HandlePatternBreak();
_lastDirection = 0;
_streakCount = 0;
return;
}
if (_lastDirection == direction)
{
_streakCount++;
}
else
{
if (_lastDirection != 0)
HandlePatternBreak();
_lastDirection = direction;
_streakCount = 1;
}
if (_streakCount >= ConsecutiveCandles)
{
if (_patternDirection != direction)
{
_patternDirection = direction;
_entriesInDirection = 0;
_blackSheepTriggered = false;
}
if (direction > 0)
TryEnterLong(candle);
else
TryEnterShort(candle);
}
ManageExits(candle);
}
private void ManageFloatingProfit(ICandleMessage candle)
{
if (MinProfit <= 0m || Position == 0m || !_hasPosition)
return;
var floating = CalculateOpenProfit(candle.ClosePrice);
if (floating >= MinProfit)
ClosePosition();
}
private void TryEnterLong(ICandleMessage candle)
{
if (OrderVolume <= 0m || OrderVolume > MaxPositionVolume)
return;
if (_entriesInDirection >= MaxPositions)
return;
if (UseTradeHours && !IsWithinTradeHours(candle.CloseTime))
return;
if (Position < 0m)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (Position != 0m)
return;
BuyMarket(OrderVolume);
InitializeLongState(candle.ClosePrice);
}
private void TryEnterShort(ICandleMessage candle)
{
if (OrderVolume <= 0m || OrderVolume > MaxPositionVolume)
return;
if (_entriesInDirection >= MaxPositions)
return;
if (UseTradeHours && !IsWithinTradeHours(candle.CloseTime))
return;
if (Position > 0m)
{
SellMarket(Position);
ResetPositionState();
return;
}
if (Position != 0m)
return;
SellMarket(OrderVolume);
InitializeShortState(candle.ClosePrice);
}
private void InitializeLongState(decimal entryPrice)
{
_hasPosition = true;
_entryPrice = entryPrice;
_entriesInDirection = 1;
_blackSheepTriggered = false;
var stopDistance = StopLossPips > 0 ? ToPrice(StopLossPips) : (decimal?)null;
var takeDistance = TakeProfitPips > 0 ? ToPrice(TakeProfitPips) : (decimal?)null;
_longStop = stopDistance.HasValue ? entryPrice - stopDistance : null;
_longTake = takeDistance.HasValue ? entryPrice + takeDistance : null;
_shortStop = null;
_shortTake = null;
}
private void InitializeShortState(decimal entryPrice)
{
_hasPosition = true;
_entryPrice = entryPrice;
_entriesInDirection = 1;
_blackSheepTriggered = false;
var stopDistance = StopLossPips > 0 ? ToPrice(StopLossPips) : (decimal?)null;
var takeDistance = TakeProfitPips > 0 ? ToPrice(TakeProfitPips) : (decimal?)null;
_shortStop = stopDistance.HasValue ? entryPrice + stopDistance : null;
_shortTake = takeDistance.HasValue ? entryPrice - takeDistance : null;
_longStop = null;
_longTake = null;
}
private void ManageExits(ICandleMessage candle)
{
if (!_hasPosition)
return;
if (Position > 0m)
{
if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
SellMarket(Position);
ResetPositionState();
return;
}
if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
{
SellMarket(Position);
ResetPositionState();
return;
}
}
else if (Position < 0m)
{
if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
}
}
private void UpdateTrailingStops(ICandleMessage candle)
{
if (!_hasPosition || TrailingStopPips <= 0 || _pipSize <= 0m)
return;
var distance = ToPrice(TrailingStopPips);
var step = TrailingStepPips > 0 ? ToPrice(TrailingStepPips) : 0m;
if (Position > 0m)
{
var threshold = candle.ClosePrice - (distance + step);
if (candle.ClosePrice - _entryPrice > distance + step && (!_longStop.HasValue || _longStop.Value < threshold))
_longStop = candle.ClosePrice - distance;
}
else if (Position < 0m)
{
var threshold = candle.ClosePrice + (distance + step);
if (_entryPrice - candle.ClosePrice > distance + step && (!_shortStop.HasValue || _shortStop.Value > threshold))
_shortStop = candle.ClosePrice + distance;
}
}
private void HandlePatternBreak()
{
if (_patternDirection == 0 || _blackSheepTriggered)
return;
switch (ClosingBehavior)
{
case ClosingModes.All:
ClosePosition();
break;
case ClosingModes.Opposite:
if (_patternDirection > 0 && Position < 0m)
ClosePosition();
else if (_patternDirection < 0 && Position > 0m)
ClosePosition();
break;
case ClosingModes.SameDirection:
if (_patternDirection > 0 && Position > 0m)
ClosePosition();
else if (_patternDirection < 0 && Position < 0m)
ClosePosition();
break;
}
_blackSheepTriggered = true;
_entriesInDirection = 0;
_patternDirection = 0;
}
private void ClosePosition()
{
if (Position > 0m)
SellMarket(Position);
else if (Position < 0m)
BuyMarket(Math.Abs(Position));
ResetPositionState();
}
private void ResetPositionState()
{
_hasPosition = Position != 0m;
_entriesInDirection = _hasPosition ? 1 : 0;
if (!_hasPosition)
{
_entryPrice = 0m;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
}
private decimal CalculateOpenProfit(decimal currentPrice)
{
if (!_hasPosition || Position == 0m)
return 0m;
var volume = Math.Abs(Position);
return Position > 0m ? (currentPrice - _entryPrice) * volume : (_entryPrice - currentPrice) * volume;
}
private static int GetCandleDirection(ICandleMessage candle)
{
if (candle.OpenPrice < candle.ClosePrice)
return 1;
if (candle.OpenPrice > candle.ClosePrice)
return -1;
return 0;
}
private bool IsWithinTradeHours(DateTimeOffset time)
{
var hour = time.Hour;
return hour >= StartHour && hour <= EndHour;
}
private decimal ToPrice(int pips)
{
return pips * _pipSize;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 0.0001m;
var decimals = CountDecimals(step);
return decimals == 3 || decimals == 5 ? step * 10m : step;
}
private static int CountDecimals(decimal value)
{
value = Math.Abs(value);
var decimals = 0;
while (value != Math.Truncate(value) && decimals < 10)
{
value *= 10m;
decimals++;
}
return decimals;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class n_candles_sequence_streak_strategy(Strategy):
def __init__(self):
super(n_candles_sequence_streak_strategy, self).__init__()
self._consecutive_candles = self.Param("ConsecutiveCandles", 4)
self._take_profit_pips = self.Param("TakeProfitPips", 50)
self._stop_loss_pips = self.Param("StopLossPips", 50)
self._trailing_stop_pips = self.Param("TrailingStopPips", 10)
self._trailing_step_pips = self.Param("TrailingStepPips", 4)
self._max_positions = self.Param("MaxPositions", 2)
self._use_trade_hours = self.Param("UseTradeHours", False)
self._start_hour = self.Param("StartHour", 11)
self._end_hour = self.Param("EndHour", 18)
self._min_profit = self.Param("MinProfit", 3.0)
self._closing_behavior = self.Param("ClosingBehavior", 0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._streak_count = 0
self._last_direction = 0
self._pattern_direction = 0
self._entries_in_direction = 0
self._black_sheep_triggered = False
self._has_position = False
self._entry_price = 0.0
self._pip_size = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(n_candles_sequence_streak_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._update_trailing_stops(candle)
self._manage_floating_profit(candle)
direction = self._get_candle_direction(candle)
if direction == 0:
self._handle_pattern_break()
self._last_direction = 0
self._streak_count = 0
return
if self._last_direction == direction:
self._streak_count += 1
else:
if self._last_direction != 0:
self._handle_pattern_break()
self._last_direction = direction
self._streak_count = 1
if self._streak_count >= self._consecutive_candles.Value:
if self._pattern_direction != direction:
self._pattern_direction = direction
self._entries_in_direction = 0
self._black_sheep_triggered = False
if direction > 0:
self._try_enter_long(candle)
else:
self._try_enter_short(candle)
self._manage_exits(candle)
def _manage_floating_profit(self, candle):
if self._min_profit.Value <= 0 or self.Position == 0 or not self._has_position:
return
floating = self._calculate_open_profit(float(candle.ClosePrice))
if floating >= self._min_profit.Value:
self._close_position()
def _try_enter_long(self, candle):
if self._entries_in_direction >= self._max_positions.Value:
return
if self._use_trade_hours.Value and not self._is_within_trade_hours(candle.CloseTime):
return
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
return
if self.Position != 0:
return
self.BuyMarket()
self._initialize_long_state(float(candle.ClosePrice))
def _try_enter_short(self, candle):
if self._entries_in_direction >= self._max_positions.Value:
return
if self._use_trade_hours.Value and not self._is_within_trade_hours(candle.CloseTime):
return
if self.Position > 0:
self.SellMarket(self.Position)
self._reset_position_state()
return
if self.Position != 0:
return
self.SellMarket()
self._initialize_short_state(float(candle.ClosePrice))
def _initialize_long_state(self, entry_price):
self._has_position = True
self._entry_price = entry_price
self._entries_in_direction = 1
self._black_sheep_triggered = False
stop_distance = self._to_price(self._stop_loss_pips.Value) if self._stop_loss_pips.Value > 0 else None
take_distance = self._to_price(self._take_profit_pips.Value) if self._take_profit_pips.Value > 0 else None
self._long_stop = entry_price - stop_distance if stop_distance is not None else None
self._long_take = entry_price + take_distance if take_distance is not None else None
self._short_stop = None
self._short_take = None
def _initialize_short_state(self, entry_price):
self._has_position = True
self._entry_price = entry_price
self._entries_in_direction = 1
self._black_sheep_triggered = False
stop_distance = self._to_price(self._stop_loss_pips.Value) if self._stop_loss_pips.Value > 0 else None
take_distance = self._to_price(self._take_profit_pips.Value) if self._take_profit_pips.Value > 0 else None
self._short_stop = entry_price + stop_distance if stop_distance is not None else None
self._short_take = entry_price - take_distance if take_distance is not None else None
self._long_stop = None
self._long_take = None
def _manage_exits(self, candle):
if not self._has_position:
return
if self.Position > 0:
if self._long_stop is not None and float(candle.LowPrice) <= self._long_stop:
self.SellMarket(self.Position)
self._reset_position_state()
return
if self._long_take is not None and float(candle.HighPrice) >= self._long_take:
self.SellMarket(self.Position)
self._reset_position_state()
return
elif self.Position < 0:
if self._short_stop is not None and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
return
if self._short_take is not None and float(candle.LowPrice) <= self._short_take:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
return
def _update_trailing_stops(self, candle):
if not self._has_position or self._trailing_stop_pips.Value <= 0 or self._pip_size <= 0:
return
distance = self._to_price(self._trailing_stop_pips.Value)
step = self._to_price(self._trailing_step_pips.Value) if self._trailing_step_pips.Value > 0 else 0.0
if self.Position > 0:
threshold = float(candle.ClosePrice) - (distance + step)
if float(candle.ClosePrice) - self._entry_price > distance + step:
if self._long_stop is None or self._long_stop < threshold:
self._long_stop = float(candle.ClosePrice) - distance
elif self.Position < 0:
threshold = float(candle.ClosePrice) + (distance + step)
if self._entry_price - float(candle.ClosePrice) > distance + step:
if self._short_stop is None or self._short_stop > threshold:
self._short_stop = float(candle.ClosePrice) + distance
def _handle_pattern_break(self):
if self._pattern_direction == 0 or self._black_sheep_triggered:
return
closing = self._closing_behavior.Value
if closing == 0:
self._close_position()
elif closing == 1:
if self._pattern_direction > 0 and self.Position < 0:
self._close_position()
elif self._pattern_direction < 0 and self.Position > 0:
self._close_position()
elif closing == 2:
if self._pattern_direction > 0 and self.Position > 0:
self._close_position()
elif self._pattern_direction < 0 and self.Position < 0:
self._close_position()
self._black_sheep_triggered = True
self._entries_in_direction = 0
self._pattern_direction = 0
def _close_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
def _reset_position_state(self):
self._has_position = self.Position != 0
self._entries_in_direction = 1 if self._has_position else 0
if not self._has_position:
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def _calculate_open_profit(self, current_price):
if not self._has_position or self.Position == 0:
return 0.0
volume = abs(self.Position)
if self.Position > 0:
return (current_price - self._entry_price) * volume
else:
return (self._entry_price - current_price) * volume
def _get_candle_direction(self, candle):
if float(candle.OpenPrice) < float(candle.ClosePrice):
return 1
if float(candle.OpenPrice) > float(candle.ClosePrice):
return -1
return 0
def _is_within_trade_hours(self, time):
hour = time.Hour
return hour >= self._start_hour.Value and hour <= self._end_hour.Value
def _to_price(self, pips):
return pips * self._pip_size
def _calculate_pip_size(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
if step <= 0:
return 0.0001
decimals = self._count_decimals(step)
return step * 10.0 if decimals == 3 or decimals == 5 else step
def _count_decimals(self, value):
value = abs(value)
decimals = 0
while value != int(value) and decimals < 10:
value *= 10.0
decimals += 1
return decimals
def OnReseted(self):
super(n_candles_sequence_streak_strategy, self).OnReseted()
self._streak_count = 0
self._last_direction = 0
self._pattern_direction = 0
self._entries_in_direction = 0
self._black_sheep_triggered = False
self._has_position = False
self._entry_price = 0.0
self._pip_size = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def CreateClone(self):
return n_candles_sequence_streak_strategy()