Открыть на GitHub

Стратегия OzFx Simple

Обзор

  • Портирование советника MetaTrader 4 OzFx (каталог MQL/7994) на высокоуровневый API StockSharp.
  • Использует осциллятор Уильямса Accelerator (разность Awesome Oscillator 5/34 и его 5-периодной SMA) совместно с линией %K стохастика для фиксации разворотов импульса около нулевой отметки.
  • Полностью повторяет поведение оригинала: открывает пять рыночных ордеров одинакового объёма, размещает каскадные тейк-профиты и переводит остаток позиции в безубыток после первого тейка.

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

  1. Рассчитывается Awesome Oscillator (5/34) и его 5-периодная SMA, разница которых даёт значение Accelerator Oscillator для предыдущей и текущей завершённой свечи.
  2. Подписка на стохастик с длиной %K = StochasticLength и сглаживаниями 3/3, считывание основной линии по закрытию свечи.
  3. Условия для покупки:
    • %K выше заданного порога (по умолчанию 50).
    • Текущее значение AC положительное и выше предыдущего.
    • Предыдущее значение AC отрицательное (импульс переходит через ноль снизу вверх).
  4. Условия для продажи симметричны.
  5. При появлении сигнала на новой свече стратегия открывает пять одинаковых рыночных ордеров:
    • Слои 1–4 получают тейк-профиты с шагом TakeProfitPips.
    • Пятый слой остаётся без тейка и сопровождает тренд.
  6. Если во время удержания позиции появляется противоположный сигнал, остаток ордеров закрывается по рынку, и стратегия переходит в ожидание следующего входа.

Управление позицией

  • Все слои используют общий стоп-лосс, вычисленный из параметра StopLossPips.
  • После исполнения первого тейк-профита оставшиеся ордера переносят стоп в точку безубытка, повторяя механику флага «modok» из MT4.
  • Защитные выходы исполняются по факту пробоя ценой свечи сохранённых уровней стопа или тейка; отложенные заявки у брокера не выставляются.
  • В работе допускается только одно направленное плечо одновременно, новые входы разблокируются после полного закрытия предыдущего каскада.

Параметры

Имя Описание Значение по умолчанию
OrderVolume Объём каждой из пяти рыночных сделок. 0.1
StopLossPips Расстояние от входа до стоп-лосса в пунктах. 100
TakeProfitPips Шаг между последовательными тейк-профитами (слои 1–4). 50
StochasticLevel Порог для линии %K стохастика. 50
StochasticLength Длина расчёта линии %K. 5
CandleType Тип свечей, используемых стратегией (по умолчанию 4-часовые). 4 часа

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

  • Сигналы оцениваются только на закрытых свечах, чтобы совпадать с логикой MT4-советника, работающего по новым барам.
  • Пересчёт пункта автоматически учитывает инструменты с 3 или 5 десятичными знаками, умножая минимальный шаг цены на 10.
  • Данные о слоях хранятся в памяти для корректной обработки частичных тейк-профитов и защитных выходов.
  • Все комментарии в C# коде написаны на английском языке в соответствии с требованиями репозитория.
using System;

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

namespace StockSharp.Samples.Strategies;

public class OzFxSimpleStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public OzFxSimpleStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 100).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = default;
		_prevSlow = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var fast = new WeightedMovingAverage { Length = FastPeriod };
		var slow = new WeightedMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevFast = fast;
		_prevSlow = slow;
	}
}