Открыть на GitHub

Стратегия EXP FIBO ZZ

Обзор

EXP FIBO ZZ — порт советника MetaTrader 4 EXP_FIBO_ZZ_V1en на C#. Стратегия отслеживает последний подтверждённый коридор ZigZag, размещает отложенный ордер Buy Stop над максимумом, Sell Stop под минимумом и прикрепляет защитные стоп-лосс и тейк-профит, рассчитанные по коэффициентам Fibonacci. Реализация на StockSharp предоставляет все параметры через StrategyParam, добавляет проверки корректности и сохраняет оригинальные настройки управления капиталом — выбор между балансом и свободными средствами, а также перенос стопа в безубыток.

Торговая логика

  1. Подготовка данных

    • Подписывается на выбранный CandleType (по умолчанию минутные свечи) и подаёт поток цен в индикаторы Highest и Lowest длиной ZigZagDepth.
    • Упрощённый распознаватель ZigZag хранит три последних экстремума. Новый узел формируется, только если:
      • максимум/минимум свечи совпадает с выходом индикатора;
      • прошло не менее ZigZagBackstep баров после предыдущего разворота;
      • отклонение от текущего узла превышает ZigZagDeviationPips (в метатрейдеровских пунктах).
  2. Проверка коридора

    • Как только есть три точки, две старшие задают границы. Торговля разрешена, когда высота коридора лежит между MinCorridorPips и MaxCorridorPips, а последний экстремум находится внутри диапазона с учётом минимального стоп-шагa брокера.
    • Вне торгового окна (StartHour/StartMinuteStopHour/StopMinute) все отложенные ордера снимаются.
  3. Выставление ордеров

    • Уровни Buy Stop и Sell Stop равны границам коридора плюс/минус EntryOffsetPips.
    • Дистанция стоп-лосса: corridor * FiboStopLoss / 100. Тейк-профит: corridor * (FiboTakeProfit / 100 - 1) с отсечением отрицательных значений до нуля.
    • Перед постановкой вычисляется объём. Если RiskPercent > 0, используется выбранный источник капитала (эквити при UseBalanceForRisk = true, либо эквити минус заблокированные средства) умноженный на риск-процент и поделённый на ориентировочную цену. Объём округляется к шагу VolumeStep и ограничивается диапазоном [MinVolume, MaxVolume]. При отсутствии данных применяется FixedVolume.
    • Активные ордера пересоздаются только при изменении цены или объёма, иначе остаются без изменений.
  4. Ведение позиции

    • После открытия позиции противоположный отложенный ордер отменяется, а защитные заявки регистрируются заново:
      • стоп-лосс через SellStop/BuyStop на рассчитанном расстоянии;
      • опциональный тейк-профит через SellLimit/BuyLimit.
    • Модуль безубытка (EnableBreakEven) повторяет функцию MovingInWL: после получения BreakEvenTriggerPips прибыли стоп переносится к цене входа плюс/минус BreakEvenOffsetPips, что фиксирует небольшой доход и исключает повторные переносы.
  5. Поддержание сессии

    • При выходе за рамки торгового окна или при обнулении позиции все отложенные и защитные ордера снимаются. Метод OnStopped также удаляет их при завершении стратегии.

Параметры

Название Описание Значение по умолчанию Примечания
CandleType Тип свечей для расчёта ZigZag. 1m TimeFrame() Поддерживаются любые свечные типы StockSharp.
ZigZagDepth Минимальное количество баров между разворотами. 12 Аналог ExtDepth.
ZigZagDeviationPips Минимальное отклонение (в пунктах MT4) для нового экстремума. 5 Аналог ExtDeviation.
ZigZagBackstep Минимум баров перед сменой направления. 3 Аналог ExtBackstep.
EntryOffsetPips Смещение от границ коридора для стоп-ордеров. 5 Аналог n_pips.
MinCorridorPips Нижняя граница высоты коридора. 20 Аналог Min_Corridor.
MaxCorridorPips Верхняя граница высоты коридора. 100 Аналог Max_Corridor.
FiboStopLoss Процент Fibonacci для стоп-лосса. 61.8 Аналог Fibo_StopLoss.
FiboTakeProfit Процент Fibonacci для тейк-профита. 161.8 Аналог Fibo_TakeProfit.
StartHour / StartMinute Начало торгового окна. 00:01 Вне окна ордера снимаются.
StopHour / StopMinute Конец торгового окна. 23:59 Поддерживается переход через полночь.
UseBalanceForRisk Источник капитала: эквити (true) или свободные средства (false). true Аналог Choice_method.
RiskPercent Доля капитала на сделку. 1 Значение 0 отключает риск-калькулятор.
FixedVolume Фиксированный объём при отсутствии данных. 0.1 Аналог Lots.
EnableBreakEven Включить перенос стопа в безубыток. true Аналог MovingInWL.
BreakEvenTriggerPips Прибыль в пунктах для переноса стопа. 13 Аналог LevelProfit.
BreakEvenOffsetPips Смещение стопа относительно цены входа. 2 Аналог LevelWLoss.
DrawCorridorLevels Рисовать коридор на графике. false Аналог флага линий в MT4.

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

  • Подсчёт пунктов соответствует MetaTrader: для инструментов с 3/5 знаками PriceStep умножается на 10.
  • Цены и объёмы заявок округляются по биржевым атрибутам (PriceStep, VolumeStep, MinVolume, MaxVolume).
  • При отсутствии информации по портфелю или цене стратегия безопасно возвращается к фиксированному объёму, не останавливая работу.
  • Блок безубытка отменяет и переоформляет стоп только один раз, не вынося его дальше цены входа.
  • При активном DrawCorridorLevels на графике отображается текущий ZigZag-коридор, что упрощает визуальный контроль.

Отличия от версии MetaTrader

  • Исключены графические объекты, звуковые сигналы и комментарии — их заменяют средства визуализации StockSharp.
  • Расчёт объёма использует данные портфеля и последние цены вместо MarketInfo, поскольку требования к марже зависят от брокера.
  • Управление ордерами выполнено через высокоуровневые методы StockSharp (BuyStop, SellStop, SellLimit, BuyLimit), что избавляет от ручной работы с тикетами при сохранении логики.
  • Распознаватель ZigZag реализован через встроенные индикаторы с параметрами глубины, отклонения и обратного шага, чтобы соответствовать потоковой модели свечей StockSharp.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Exp Fibo ZZ: Fibonacci-style breakout using high/low channel with ATR stops.
/// </summary>
public class ExpFiboZzStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _emaLength;

	private decimal _entryPrice;
	private decimal _prevHigh;
	private decimal _prevLow;
	private readonly decimal[] _highs = new decimal[15];
	private readonly decimal[] _lows = new decimal[15];
	private int _barCount;

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

		_channelLength = Param(nameof(ChannelLength), 15)
			.SetDisplay("Channel", "N-bar channel lookback.", "Indicators");

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

		_emaLength = Param(nameof(EmaLength), 50)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");
	}

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

	public int ChannelLength
	{
		get => _channelLength.Value;
		set => _channelLength.Value = value;
	}

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

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

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh = 0;
		_prevLow = 0;
		Array.Clear(_highs, 0, _highs.Length);
		Array.Clear(_lows, 0, _lows.Length);
	}

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

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh = 0;
		_prevLow = 0;

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

		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 atrVal, decimal emaVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var len = Math.Min(ChannelLength, _highs.Length);
		var idx = _barCount % len;
		_highs[idx] = candle.HighPrice;
		_lows[idx] = candle.LowPrice;
		_barCount++;

		if (_barCount < len || atrVal <= 0)
			return;

		var high = decimal.MinValue;
		var low = decimal.MaxValue;
		for (var i = 0; i < len; i++)
		{
			if (_highs[i] > high) high = _highs[i];
			if (_lows[i] < low) low = _lows[i];
		}

		var close = candle.ClosePrice;

		if (_prevHigh == 0 || _prevLow == 0)
		{
			_prevHigh = high;
			_prevLow = low;
			return;
		}

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 1.5m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 1.5m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (close > _prevHigh && close > emaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < _prevLow && close < emaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevHigh = high;
		_prevLow = low;
	}
}