N-Kerzen-Sequenz-Strategie
Übersicht
Die N-Kerzen-Sequenz-Strategie repliziert das Verhalten des ursprünglichen MetaTrader-Expertenberaters „N-_Candles_v7" mit der StockSharp High-Level-API. Sie überwacht abgeschlossene Kerzen und sucht nach einer konfigurierbaren Anzahl aufeinanderfolgender bullischer oder bärischer Körper. Wenn eine qualifizierende Serie vorliegt, öffnet die Strategie eine Position in dieselbe Richtung und verwaltet sie mit konfigurierbarem Take-Profit, Stop-Loss, Trailing-Stop, Handelsstunden-Filter und schwebender Gewinnsperre.
Handelslogik
- Bewertet jede abgeschlossene Kerze und klassifiziert sie als bullisch, bärisch oder neutral (Doji). Neutrale Kerzen setzen den Serienzähler zurück und können das „schwarzes Schaf"-Verhalten auslösen.
- Pflegt eine laufende Zählung aufeinanderfolgender Kerzen mit derselben Körperrichtung. Sobald die Zählung den konfigurierten Schwellenwert erreicht, wird die aktuelle Richtung zum aktiven Muster.
- Bei einer aktiven bullischen Serie versucht die Strategie, eine Long-Position zu eröffnen; bei einer aktiven bärischen Serie versucht sie, eine Short-Position zu eröffnen. Es wird immer nur eine Netto-Position gehalten.
- Wenn eine Kerze die einheitliche Richtung bricht ("schwarzes Schaf"), reagiert die Strategie gemäß dem ausgewählten Schließmodus: alles schließen, nur entgegengesetzte Positionen schließen oder nur Positionen schließen, die zur vorherigen Serie ausgerichtet sind.
- Optional schränkt sie Einstiege auf ein durch Start- und Endstunden (inklusiv) definiertes Handelszeitfenster ein.
- Überwacht kontinuierlich die offene Position für Take-Profit, Stop-Loss, Trailing-Stop-Aktualisierungen und den schwebenden Gewinnschwellenwert.
Positions- und Risikomanagement
- Der anfängliche Schutz-Stop und das Ziel werden aus Pip-Distanzen berechnet, die mit dem Preisschritt des Instruments konvertiert wurden. Diese Niveaus werden jedes Mal neu berechnet, wenn eine neue Position eröffnet wird.
- Die Trailing-Stop-Logik ahmt den ursprünglichen Experten nach: Sobald der Preis die Trailing-Distanz plus Schritt zurücklegt, wird der Stop bewegt, um die Trailing-Distanz beizubehalten.
- Ein schwebender Gewinnwächter (
MinProfit) schließt die gesamte Position, sobald der offene Gewinn den konfigurierten Wert übersteigt. - Der Parameter
MaxPositionVolumeverhindert Einstiege, wenn das angeforderte Volumen über dem erlaubten Limit liegt.MaxPositionsfungiert als Schutz gegen Wiedereinstieg, wenn eine Position bereits aktiv ist. - Alle Ausstiege rufen Market-Orders auf, um die Netto-Position zu flatten, da die StockSharp-Strategie in einer Netting-Umgebung arbeitet.
Parameter
| Name | Beschreibung |
|---|---|
ConsecutiveCandles |
Anzahl der Kerzen mit identischer Richtung, die erforderlich sind, um ein Signal auszulösen. |
OrderVolume |
Market-Order-Volumen für Einstiege und Ausstiege. |
TakeProfitPips |
Take-Profit-Distanz in Pips. Auf null setzen zum Deaktivieren. |
StopLossPips |
Stop-Loss-Distanz in Pips. Auf null setzen zum Deaktivieren. |
TrailingStopPips |
Trailing-Stop-Distanz. Auf null setzen zum Deaktivieren des Trailings. |
TrailingStepPips |
Zusätzliche Distanz erforderlich, bevor der Trailing-Stop bewegt wird. |
MaxPositions |
Maximale Anzahl simultaner Einstiege pro Muster (die Strategie hält eine einzelne Netto-Position). |
MaxPositionVolume |
Obergrenze für das erlaubte Netto-Volumen. |
UseTradeHours / StartHour / EndHour |
Aktivieren und konfigurieren des Handelszeitfensters (inklusiv). |
MinProfit |
Schwebender Gewinnschwellenwert, der einen vollständigen Ausstieg auslöst. |
ClosingBehavior |
Definiert, wie auf eine „schwarzes Schaf"-Kerze reagiert wird. |
CandleType |
Die für Berechnungen verwendete Kerzenserie. |
Hinweise und Annahmen
- Die Strategie arbeitet mit Netto-Positionen; mehrere Hedging-Style-Tickets werden nicht erstellt. Dies unterscheidet sich vom ursprünglichen Experten, wo mehrere abgesicherte Positionen gleichzeitig bestehen konnten.
- Der schwebende Gewinn wird approximiert als
(aktueller Preis - Einstiegspreis) * Volumenfür Long-Positionen und umgekehrt für Short-Positionen. - Die Pip-Konvertierung stützt sich auf den
PriceStepdes Instruments. Für Symbole, bei denen der minimale Schritt nicht angegeben ist, wird ein Standard-Pip von 0.0001 angenommen. - Es wird keine Python-Portierung bereitgestellt, wie angefordert.
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()