Conversion of the MetaTrader 4 expert advisor OzFx (folder MQL/7994) to the StockSharp high-level API.
Uses the Accelerator/Decelerator oscillator (AC) together with the %K line of the stochastic oscillator to detect momentum reversals around the zero line.
Replicates the expert's behaviour of stacking five market orders with staggered take-profits and breakeven protection after the first target is hit.
Trading Logic
Build the Awesome Oscillator (5/34) and subtract its 5-period SMA to obtain the Accelerator Oscillator value of the previous and current completed candle.
Subscribe to the stochastic oscillator (%K length = StochasticLength, smoothing 3/3) and read the main line on candle close.
Long setup requires:
%K above the configured mid-level (default 50).
Current AC value positive and higher than the previous one.
Previous AC value still below zero (momentum crosses the baseline).
Short setup mirrors the rules in the opposite direction.
When a signal appears on a new bar the strategy opens five equal market orders:
Layers 1-4 receive take-profits spaced by TakeProfitPips multiples.
Layer 5 has no profit target and remains to trail the move.
If the opposite setup appears while a stack is open the remaining orders are closed at market, keeping the strategy flat before new entries.
Position Management
Every layer shares the same stop-loss distance defined by StopLossPips.
After the first take-profit executes, the remaining orders tighten their stops to the breakeven (entry) price, matching the original "modok" logic.
Protective exits are executed when candle extremes pierce the stored stop or target levels; broker-side pending orders are not used.
The strategy allows only one direction at a time and waits for all orders to close before resetting the entry block flags.
Parameters
Name
Description
Default
OrderVolume
Lot size for each of the five market orders.
0.1
StopLossPips
Distance between entry and stop loss, expressed in pips.
100
TakeProfitPips
Increment between consecutive take-profit levels (layers 1-4).
50
StochasticLevel
Threshold applied to the stochastic %K value.
50
StochasticLength
Lookback period of the stochastic %K calculation.
5
CandleType
Source candle series used by the strategy (defaults to 4-hour candles).
4h time-frame
Implementation Notes
Signals are evaluated only on finished candles to stay consistent with the MT4 expert that works on new bars.
Pip conversion adapts automatically to 3/5-digit forex symbols by multiplying the minimal price step by 10 when needed.
Staggered entries and exits are handled in-memory via layered objects so that the strategy can properly close portions of the position.
All comments inside the C# code are written in English, as required by the repository guidelines.
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()