GitHub で見る

OzFx Simple Strategy

Overview

  • 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

  1. 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.
  2. Subscribe to the stochastic oscillator (%K length = StochasticLength, smoothing 3/3) and read the main line on candle close.
  3. 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).
  4. Short setup mirrors the rules in the opposite direction.
  5. 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.
  6. 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;
	}
}