N-Candles Sequence Strategy
Overview
The N-Candles Sequence strategy replicates the behaviour of the original MetaTrader expert "N-_Candles_v7" using the StockSharp high-level API. It monitors finished candles and looks for a configurable number of consecutive bullish or bearish bodies. When a qualifying streak is present, the strategy opens a position in the same direction and manages it with configurable take profit, stop loss, trailing stop, trading hours filter, and floating profit lock.
Trading Logic
- Evaluate each finished candle and classify it as bullish, bearish, or neutral (doji). Neutral candles reset the streak counter and can trigger the "black sheep" behaviour.
- Maintain a running count of consecutive candles with the same body direction. Once the count reaches the configured threshold, the current direction becomes the active pattern.
- When a bullish streak is active the strategy attempts to open a long position; when a bearish streak is active it attempts to open a short position. Only one net position is held at a time.
- If a candle breaks the uniform direction ("black sheep"), the strategy reacts according to the selected closing mode: close everything, close only opposite positions, or close only positions aligned with the previous streak.
- Optionally restrict entries to a trading window defined by start and end hours (inclusive).
- Continuously monitor the open position for take profit, stop loss, trailing stop updates, and the floating profit threshold.
Position and Risk Management
- The initial protective stop and target are calculated from pip distances converted with the instrument price step. These levels are recalculated each time a new position is opened.
- Trailing stop logic mimics the original expert: once price travels by the trailing distance plus step, the stop is moved to maintain the trailing distance.
- A floating profit guard (
MinProfit) closes the entire position once the open profit exceeds the configured value. - The
MaxPositionVolumeparameter prevents entries if the requested volume is above the allowed limit.MaxPositionsworks as a guard against re-entry when a position is already active. - All exits call market orders to flatten the net position because the StockSharp strategy operates in a netting environment.
Parameters
| Name | Description |
|---|---|
ConsecutiveCandles |
Number of candles with identical direction required to trigger a signal. |
OrderVolume |
Market order volume used for entries and exits. |
TakeProfitPips |
Take profit distance expressed in pips. Set to zero to disable. |
StopLossPips |
Stop loss distance expressed in pips. Set to zero to disable. |
TrailingStopPips |
Trailing stop distance. Set to zero to disable trailing. |
TrailingStepPips |
Additional distance required before the trailing stop is moved. |
MaxPositions |
Maximum number of simultaneous entries per pattern (the strategy keeps a single net position). |
MaxPositionVolume |
Upper bound for the allowed net volume. |
UseTradeHours / StartHour / EndHour |
Enable and configure the trading time window (inclusive). |
MinProfit |
Floating profit threshold that triggers a full exit. |
ClosingBehavior |
Defines how to react when a "black sheep" candle appears. |
CandleType |
The candle series used for calculations. |
Notes and Assumptions
- The strategy operates with net positions; hedging-style multiple tickets are not created. This differs from the original expert where several hedged positions could coexist.
- Floating profit is approximated as
(current price - entry price) * volumefor long positions and the inverse for short positions. - The pip conversion relies on the instrument
PriceStep. For symbols where the minimal step is not provided, a default 0.0001 pip is assumed. - No Python port is provided, as requested.
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()