Auf GitHub ansehen

Ergodic Ticks Volume OSMA Strategy

Overview

This strategy adapts the MQL5 expert "Exp_Ergodic_Ticks_Volume_OSMA" to StockSharp. The original expert uses a custom indicator to evaluate tick volume momentum. In this version the custom indicator is approximated by the MACD histogram.

The strategy looks for consecutive increases or decreases in the histogram:

  • Two rising steps trigger a long entry and close any short position.
  • Two falling steps trigger a short entry and close any long position.

StartProtection() is used to avoid conflicts with existing positions when the strategy starts.

Parameters

  • FastLength – fast EMA period for the MACD. Default: 12.
  • SlowLength – slow EMA period for the MACD. Default: 26.
  • SignalLength – signal EMA period for the MACD. Default: 9.
  • CandleType – timeframe of candles, default is 8 hours.

Trading Logic

  1. Subscribe to candles of the selected CandleType.
  2. Compute the MACD histogram for each finished candle.
  3. If the histogram grows for two consecutive bars, close shorts and buy.
  4. If the histogram falls for two consecutive bars, close longs and sell.
  5. Continue processing for each new candle.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on MACD histogram as an approximation of the Ergodic Ticks Volume OSMA indicator.
/// </summary>
public class ErgodicTicksVolumeOsmaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _signalLength;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevHist;
	private decimal _prevPrevHist;
	private int _candleCount;

	public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
	public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
	public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public ErgodicTicksVolumeOsmaStrategy()
	{
		_fastLength = Param(nameof(FastLength), 12).SetDisplay("Fast EMA", "Fast EMA length", "Indicators");
		_slowLength = Param(nameof(SlowLength), 26).SetDisplay("Slow EMA", "Slow EMA length", "Indicators");
		_signalLength = Param(nameof(SignalLength), 9).SetDisplay("Signal EMA", "Signal EMA length", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame()).SetDisplay("Timeframe", "Timeframe", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevHist = default;
		_prevPrevHist = default;
		_candleCount = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var macd = new MovingAverageConvergenceDivergenceSignal(
			new MovingAverageConvergenceDivergence
			{
				ShortMa = { Length = FastLength },
				LongMa = { Length = SlowLength },
			},
			new ExponentialMovingAverage { Length = SignalLength }
		);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(macd, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (value is not MovingAverageConvergenceDivergenceSignalValue macdTyped)
			return;

		if (macdTyped.Macd is not decimal macdVal || macdTyped.Signal is not decimal signalVal)
			return;

		var hist = macdVal - signalVal;

		_candleCount++;
		if (_candleCount <= 2)
		{
			_prevPrevHist = _prevHist;
			_prevHist = hist;
			return;
		}

		var rising = _prevHist >= _prevPrevHist && hist >= _prevHist;
		var falling = _prevHist <= _prevPrevHist && hist <= _prevHist;

		if (rising && Position <= 0)
		{
			BuyMarket();
		}
		else if (falling && Position >= 0)
		{
			SellMarket();
		}

		_prevPrevHist = _prevHist;
		_prevHist = hist;
	}
}