Стратегия анализирует последние закрытые свечи и ищет последовательность, в которой N баров подряд имеют одинаковое направление (все бычьи или все медвежьи). При появлении такой серии открывается позиция в сторону движения с учётом ограничения на максимальное количество одновременных входов. Данный код переносит оригинального советника MetaTrader 5 на высокоуровневый API StockSharp.
Логика торговли
Подписка оформляется на выбранный тип свечей, обрабатываются только полностью сформированные бары.
Для каждой свечи определяется направление тела: закрытие выше открытия — бычья свеча, ниже — медвежья, равенство трактуется как доджи.
Доджи сбрасывает счётчик. В противном случае при совпадении направления счётчик увеличивается. Как только его значение достигает параметра Identical Candles, генерируется новый торговый сигнал.
Для длинных сигналов сначала закрываются текущие короткие позиции, после чего добавляется лот до тех пор, пока суммарный объём не превысит Max Positions * Volume.
Короткие сигналы работают зеркально для серий из медвежьих свечей.
Управление рисками
После каждого исполнения заявок стратегия пересоздаёт защитные ордера стоп-лосс и тейк-профит, основываясь на средней цене текущей позиции.
Расстояния измеряются в шагах цены инструмента: параметры Take Profit Points и Stop Loss Points умножаются на PriceStep для расчёта уровней защиты.
Следящий стоп активируется, когда цена проходит в прибыльную сторону расстояние Trailing Stop Points. Перенос стопа выполняется только при дополнительном движении минимум на Trailing Step Points сверх предыдущего уровня.
Параметры
Candle Type – тип и таймфрейм свечей для анализа.
Identical Candles – количество однонаправленных свечей, необходимое для открытия позиции.
Volume – объём одной заявки в единицах актива.
Max Positions – максимальное число входов в одном направлении одновременно.
Take Profit Points – расстояние до тейк-профита в шагах цены.
Stop Loss Points – расстояние до стоп-лосса в шагах цены.
Trailing Stop Points – базовое расстояние для следящего стопа; ноль отключает функцию.
Trailing Step Points – дополнительное движение в шагах цены, необходимое перед переносом стопа.
Дополнительные замечания
Стратегия работает в неттинговом режиме: при появлении сигнала противоположного направления существующие позиции закрываются, затем открывается новая сделка.
Объёмы защитных ордеров синхронизируются с фактической позицией после каждого исполнения.
Если инструмент не предоставляет ненулевого значения PriceStep, используется значение 1 по умолчанию.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that opens positions after detecting a sequence of candles with the same direction.
/// Uses StartProtection for take profit and stop loss management.
/// </summary>
public class NCandlesV3Strategy : Strategy
{
private readonly StrategyParam<int> _identicalCandles;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<DataType> _candleType;
private int _sequenceDirection;
private int _sequenceCount;
/// <summary>
/// Candle type used for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of consecutive candles required before entering a trade.
/// </summary>
public int IdenticalCandles
{
get => _identicalCandles.Value;
set => _identicalCandles.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public NCandlesV3Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles analysed by the strategy", "General");
_identicalCandles = Param(nameof(IdenticalCandles), 3)
.SetRange(1, 10)
.SetDisplay("Identical Candles", "Required number of equal candles", "Pattern");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
.SetRange(0m, 500m)
.SetDisplay("Take Profit Points", "Take profit distance in price steps", "Risk Management");
_stopLossPoints = Param(nameof(StopLossPoints), 50m)
.SetRange(0m, 500m)
.SetDisplay("Stop Loss Points", "Stop loss distance in price steps", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sequenceDirection = 0;
_sequenceCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sequenceDirection = 0;
_sequenceCount = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
var takeProfit = TakeProfitPoints > 0 ? TakeProfitPoints * step : (decimal?)null;
var stopLoss = StopLossPoints > 0 ? StopLossPoints * step : (decimal?)null;
if (takeProfit != null || stopLoss != null)
StartProtection(takeProfit ?? 0, stopLoss ?? 0);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var direction = GetDirection(candle);
UpdateSequence(direction);
if (_sequenceCount < IdenticalCandles)
return;
if (_sequenceDirection > 0 && Position <= 0)
{
BuyMarket();
}
else if (_sequenceDirection < 0 && Position >= 0)
{
SellMarket();
}
}
private static int GetDirection(ICandleMessage candle)
{
if (candle.ClosePrice > candle.OpenPrice)
return 1;
if (candle.ClosePrice < candle.OpenPrice)
return -1;
return 0;
}
private void UpdateSequence(int direction)
{
if (direction == 0)
{
_sequenceDirection = 0;
_sequenceCount = 0;
return;
}
if (_sequenceDirection == direction)
{
_sequenceCount++;
}
else
{
_sequenceDirection = direction;
_sequenceCount = 1;
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class n_candles_v3_strategy(Strategy):
"""
N Candles v3: consecutive same-direction candles entry with StartProtection SL/TP.
"""
def __init__(self):
super(n_candles_v3_strategy, self).__init__()
self._identical = self.Param("IdenticalCandles", 3).SetDisplay("Identical", "Required consecutive candles", "Pattern")
self._tp_points = self.Param("TakeProfitPoints", 50.0).SetDisplay("TP Points", "Take profit steps", "Risk")
self._sl_points = self.Param("StopLossPoints", 50.0).SetDisplay("SL Points", "Stop loss steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Candles", "General")
self._seq_dir = 0
self._seq_count = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(n_candles_v3_strategy, self).OnReseted()
self._seq_dir = 0
self._seq_count = 0
def OnStarted2(self, time):
super(n_candles_v3_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
tp = float(self._tp_points.Value)
sl = float(self._sl_points.Value)
if tp > 0 or sl > 0:
self.StartProtection(
Unit(tp, UnitTypes.Absolute) if tp > 0 else Unit(0, UnitTypes.Absolute),
Unit(sl, UnitTypes.Absolute) if sl > 0 else Unit(0, UnitTypes.Absolute)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
direction = 1 if close > open_p else (-1 if close < open_p else 0)
if direction == 0:
self._seq_dir = 0
self._seq_count = 0
return
if self._seq_dir == direction:
self._seq_count += 1
else:
self._seq_dir = direction
self._seq_count = 1
if self._seq_count < self._identical.Value:
return
if self._seq_dir > 0 and self.Position <= 0:
self.BuyMarket()
elif self._seq_dir < 0 and self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return n_candles_v3_strategy()