Открыть на GitHub

NTK_07 — сеточная стратегия

Общие сведения

NTK_07 — симметричная сетка из отложенных ордеров, первоначально реализованная под MetaTrader 4. Стратегия размещает buy stop и sell stop вокруг текущей цены, наращивает позицию по шагам с множителем Multiplier и управляет ею с помощью настраиваемых стоп-лоссов, тейк-профитов и трейлинг-стопов. Порт на StockSharp повторяет оригинальный алгоритм и предоставляет все параметры через механизм StrategyParam.

Основные особенности:

  • При отсутствии ордеров выставляется пара симметричных stop-заявок.
  • После срабатывания одной из заявок противоположная немедленно отменяется, чтобы избежать хеджирования.
  • В направлении открытой позиции добавляются новые уровни сетки, если расчетный объем не превышает LotLimit.
  • Когда добавить следующий уровень нельзя, включается трейлинг-стоп (и при необходимости динамический тейк-профит), защищающий текущую позицию.
  • Любые изменения позиции приводят к пересозданию защитных ордеров, поэтому все заявки всегда используют единые уровни выхода.

Алгоритм работы

  1. Торговая сессия. В субботу и воскресенье, а также вне диапазона часов [StartHour, EndHour] новые заявки не выставляются. Значение EndHour = 24 разрешает торговать в течение всего дня.
  2. Проверка капитала. Если стратегия привязана к портфелю, то его текущая стоимость должна быть не меньше MinCapital.
  3. Канальный фильтр (по желанию). При ChannelPeriod > 0 отслеживаются максимум и минимум последних завершенных свечей. Поведение задается параметром UseChannelCenter:
    • false — ордера выставляются только при пробое диапазона.
    • true — ордера выставляются, когда цена возвращается к середине диапазона.
  4. Первичные ордера. При отсутствии активных заявок buy stop ставится на NetStepPips выше лучшей цены ask, а sell stop — на NetStepPips ниже лучшей цены bid. Базовый объем рассчитывается модулем управления капиталом.
  5. Пирамидинг. После исполнения ордера противоположная заявка отменяется. Если позиция остается открытой, следующий уровень сетки ставится через NetStepPips с объемом RoundVolume(previousVolume × Multiplier). Добавление прекращается, если объем превысит лимит.
  6. Стоп-лосс и тейк-профит. При каждом изменении объема позиция защищается новыми stop- и limit-заявками с дистанциями StopLossPips и TakeProfitPips.
  7. Выход в безубыток. Если UseBreakEven = true и цена прошла BreakEvenOffsetPips в прибыльную сторону, стоп переводится к средневзвешенной цене входа, округленной через PriceRoundingFactor.
  8. Трейлинг. Когда дальнейший пирамидинг недоступен, стоп подтягивается на основе экстремума последней свечи с шагом TrailingStopPips. При TrailProfit = true тейк-профит также переносится, сохраняя исходную дистанцию. Включение UseMovingAverageFilter уменьшает шаг трейлинга вдвое, если цена движется против выбранной скользящей средней.

Управление капиталом

Режим Описание
Fixed Всегда использовать InitialLot и ограничивать объем значением LotLimit.
BalanceBased Пересчитывать стартовый объем по формуле ceil(balance / 1000 × PercentRisk / 100), затем делить его на Multiplier, пока не будет достигнут минимальный уровень сетки. Округление выполняется через LotRoundingFactor, а исходное LotLimit трактуется как максимальный объем.
Progressive Сохранять InitialLot, но оценивать максимально возможный объем, умножая его на Multiplier для каждого уровня сетки.

Объемы округляются делителем LotRoundingFactor (по умолчанию 10 ⇒ шаг 0.1), а цены — PriceRoundingFactor (по умолчанию 10000 ⇒ шаг 0.0001).

Параметры

Параметр Значение по умолчанию Описание
NetStepPips 23 Расстояние между уровнями сетки.
StopLossPips 115 Дистанция защитного стоп-лосса (0 — отключить).
TakeProfitPips 300 Дистанция тейк-профита (0 — отключить).
TrailingStopPips 75 Шаг трейлинг-стопа, который используется после завершения пирамидинга.
Multiplier 1.7 Множитель объема для следующего уровня.
TrailProfit true Смещать тейк-профит вместе с трейлинг-стопом.
ManagementMode Progressive Выбранный режим управления капиталом.
InitialLot 1 Базовый объем первой заявки.
LotLimit 7 Максимальный объем одной заявки.
MaxTrades 4 Максимальное число уровней сетки.
PercentRisk 10 Доля капитала, используемая в режиме BalanceBased.
MinCapital 5000 Минимальный капитал портфеля для запуска стратегии.
UseBreakEven false Переводить ли стоп в безубыток.
BreakEvenOffsetPips 5 Размер прибыли (в пунктах), необходимый для перевода стопа.
UseMovingAverageFilter false Включение фильтра по скользящей средней для трейлинг-стопа.
MovingAverageLength 100 Период скользящей средней.
MovingAverageShift 0 Смещение, задающее, сколько завершенных свечей использовать при оценке MA.
StartHour 0 Начальный час торговли (0–23).
EndHour 24 Конечный час торговли (включительно).
ChannelPeriod 0 Количество свечей для канального фильтра (0 — выключить фильтр).
UseChannelCenter false Режим работы фильтра: пробой (false) или возврат к центру (true).
LotRoundingFactor 10 Делитель для округления объемов.
PriceRoundingFactor 10000 Делитель для округления цены безубыточности.
CandleType 15 минут Тип свечей, используемых для расчетов.

Особенности реализации

  • Используется подписка на стакан, чтобы получать лучшие bid/ask; при отсутствии данных используется цена закрытия свечи.
  • Защитные заявки создаются заново вместо модификации существующих — так надежнее работать через high-level API StockSharp.
  • При недостатке истории для MovingAverageShift используется последнее доступное значение MA, что приближает поведение к исходной реализации.
  • Цены нормализуются через Security.ShrinkPrice, поэтому уровни всегда совпадают с минимальным шагом инструмента.

Практические советы

  1. Настройте Strategy.Volume, если требуется масштабировать торговлю относительно портфеля.
  2. Для инструментов с нестандартным шагом цены скорректируйте LotRoundingFactor и PriceRoundingFactor.
  3. Базовые параметры подобраны под тесты EURUSD H1 за период 01.01.2008 – 01.11.2008; для других рынков рекомендуется переоптимизация.
  4. Сетка может накапливать крупную позицию, поэтому контролируйте значения LotLimit и MaxTrades, чтобы управлять рисками.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Grid strategy that enters on ATR-based breakouts and scales positions
/// using a multiplier on subsequent breakouts in the same direction.
/// </summary>
public class Ntk07Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _gridMultiplier;

	private decimal _referencePrice;
	private decimal _entryPrice;
	private bool _initialized;

	public Ntk07Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis.", "General");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "Period for ATR.", "Indicators");

		_gridMultiplier = Param(nameof(GridMultiplier), 1.5m)
			.SetDisplay("Grid Multiplier", "ATR multiplier for grid step.", "Grid");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal GridMultiplier
	{
		get => _gridMultiplier.Value;
		set => _gridMultiplier.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_referencePrice = 0;
		_entryPrice = 0;
		_initialized = false;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_referencePrice = 0;
		_entryPrice = 0;
		_initialized = false;

		var atr = new AverageTrueRange { Length = AtrLength };
		var ema = new ExponentialMovingAverage { Length = 20 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ema, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrValue <= 0)
			return;

		var close = candle.ClosePrice;
		var gridStep = atrValue * GridMultiplier;

		if (!_initialized)
		{
			_referencePrice = close;
			_initialized = true;
			return;
		}

		// Position management
		if (Position > 0)
		{
			// Take profit at 2x grid step from entry
			if (_entryPrice > 0 && close >= _entryPrice + gridStep * 2)
			{
				SellMarket();
				_referencePrice = close;
			}
			// Stop-loss at 1.5x grid step from entry
			else if (_entryPrice > 0 && close <= _entryPrice - gridStep * 1.5m)
			{
				SellMarket();
				_referencePrice = close;
			}
		}
		else if (Position < 0)
		{
			if (_entryPrice > 0 && close <= _entryPrice - gridStep * 2)
			{
				BuyMarket();
				_referencePrice = close;
			}
			else if (_entryPrice > 0 && close >= _entryPrice + gridStep * 1.5m)
			{
				BuyMarket();
				_referencePrice = close;
			}
		}

		// Entry: price moves a full grid step from reference
		if (Position == 0)
		{
			if (close > _referencePrice + gridStep && close > emaValue)
			{
				_entryPrice = close;
				_referencePrice = close;
				BuyMarket();
			}
			else if (close < _referencePrice - gridStep && close < emaValue)
			{
				_entryPrice = close;
				_referencePrice = close;
				SellMarket();
			}
		}
	}
}