Открыть на GitHub

Стратегия Broadening Top

Описание

Broadening Top Strategy — это трендовая система, основанная на оригинальном советнике MetaTrader «Broadening top». Стратегия стремится поймать пробой после расширяющейся формации. Для фильтрации сигналов используются две линейно взвешенные скользящие средние (LWMA), индикатор Momentum и фильтр MACD.

Логика торговли

  1. Фильтр тренда. Быстрая LWMA должна находиться выше медленной для покупки и ниже — для продажи.
  2. Подтверждение импульса. Индикатор Momentum анализируется по последним трём завершённым свечам. Сделка разрешается только при отклонении хотя бы одного значения от нейтрального уровня 100 не менее чем на заданный порог (отдельно для длинных и коротких сделок).
  3. Подтверждение MACD. Лонг открывается лишь тогда, когда линия MACD выше сигнальной линии, шорт — когда ниже.
  4. Управление позицией. Перед открытием сделки в противоположном направлении стратегия закрывает существующую позицию, чтобы одновременно была активна только одна позиция.

Управление рисками

Защитные ордера настраиваются через StartProtection:

  • Стоп-лосс и тейк-профит задаются в шагах цены (пунктах) и могут быть отключены.
  • Доступен трейлинг-стоп с регулируемым шагом подтягивания.

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Объём ордера в лотах/контрактах. 1
FastMaLength Период быстрой линейно взвешенной средней. 6
SlowMaLength Период медленной линейно взвешенной средней. 85
MomentumPeriod Период индикатора Momentum. 14
MomentumBuyThreshold Минимальное отклонение Momentum от 100 для открытия лонга. 0.3
MomentumSellThreshold Минимальное отклонение Momentum от 100 для открытия шорта. 0.3
MacdFast Быстрый период EMA в MACD. 12
MacdSlow Медленный период EMA в MACD. 26
MacdSignal Период сигнальной EMA в MACD. 9
TakeProfitPips Расстояние до тейк-профита в шагах цены. 50
StopLossPips Расстояние до стоп-лосса в шагах цены. 20
TrailingStopPips Дистанция трейлинг-стопа в шагах цены. 40
TrailingStepPips Дополнительный путь до обновления трейлинг-стопа. 10
CandleType Тип/таймфрейм свечей для расчёта. 15-минутный таймфрейм
EnableLongs Разрешение на лонги. true
EnableShorts Разрешение на шорты. true

Используемые индикаторы

  • LinearWeightedMovingAverage — быстрый и медленный фильтры тренда.
  • Momentum — подтверждает ускорение цены относительно уровня 100.
  • MovingAverageConvergenceDivergenceSignal — проверяет направление по линии MACD и сигнальной линии.

Особенности

  • Порог Momentum проверяется по трём последним завершённым свечам для воссоздания поведения исходного советника.
  • Защитные ордера можно отключить, установив соответствующие параметры равными нулю.
  • Для корректного расчёта размера пункта инструмент должен предоставлять шаг цены и количество знаков после запятой.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class BroadeningTopStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BroadeningTopStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}