Портирование советника MetaTrader 4 OzFx (каталог MQL/7994) на высокоуровневый API StockSharp.
Использует осциллятор Уильямса Accelerator (разность Awesome Oscillator 5/34 и его 5-периодной SMA) совместно с линией %K стохастика для фиксации разворотов импульса около нулевой отметки.
Полностью повторяет поведение оригинала: открывает пять рыночных ордеров одинакового объёма, размещает каскадные тейк-профиты и переводит остаток позиции в безубыток после первого тейка.
Торговая логика
Рассчитывается Awesome Oscillator (5/34) и его 5-периодная SMA, разница которых даёт значение Accelerator Oscillator для предыдущей и текущей завершённой свечи.
Подписка на стохастик с длиной %K = StochasticLength и сглаживаниями 3/3, считывание основной линии по закрытию свечи.
Условия для покупки:
%K выше заданного порога (по умолчанию 50).
Текущее значение AC положительное и выше предыдущего.
Предыдущее значение AC отрицательное (импульс переходит через ноль снизу вверх).
Условия для продажи симметричны.
При появлении сигнала на новой свече стратегия открывает пять одинаковых рыночных ордеров:
Слои 1–4 получают тейк-профиты с шагом TakeProfitPips.
Пятый слой остаётся без тейка и сопровождает тренд.
Если во время удержания позиции появляется противоположный сигнал, остаток ордеров закрывается по рынку, и стратегия переходит в ожидание следующего входа.
Управление позицией
Все слои используют общий стоп-лосс, вычисленный из параметра 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;
}
}
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
from StockSharp.Algo.Indicators import WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class oz_fx_simple_strategy(Strategy):
def __init__(self):
super(oz_fx_simple_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 80).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 100).SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(oz_fx_simple_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(oz_fx_simple_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._has_prev = False
self._cooldown_remaining = 0
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
def OnProcess(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
if not self._has_prev:
self._prev_fast = fast
self._prev_slow = slow
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_fast = fast
self._prev_slow = slow
return
if self._prev_fast <= self._prev_slow and fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self._cooldown_candles.Value
elif self._prev_fast >= self._prev_slow and fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self._cooldown_candles.Value
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return oz_fx_simple_strategy()