GitHub で見る

MA Oscillator Histogram Strategy

Overview

This strategy is a translation of the MQL5 expert Exp_MAOscillatorHist.mq5. It uses the difference between a fast and a slow Simple Moving Average (SMA) to form an oscillator. Trading signals are generated when the oscillator forms local minima or maxima, which are interpreted as potential trend reversals.

Trading Logic

  1. Two SMAs are calculated on the selected candle timeframe:
    • Fast SMA with a shorter period.
    • Slow SMA with a longer period.
  2. The oscillator value is the fast SMA minus the slow SMA.
  3. The strategy tracks the last three oscillator values. A local minimum occurs when the older value is higher than the previous one and the previous value is lower than the current one. A local maximum is the opposite.
  4. When a local minimum is detected:
    • Close short positions (if allowed).
    • Open a new long position (if allowed).
  5. When a local maximum is detected:
    • Close long positions (if allowed).
    • Open a new short position (if allowed).

Parameters

Parameter Description
Fast Period Period of the fast SMA.
Slow Period Period of the slow SMA.
Enable Buy Open If true, long positions can be opened.
Enable Sell Open If true, short positions can be opened.
Enable Buy Close If true, long positions can be closed on opposite signals.
Enable Sell Close If true, short positions can be closed on opposite signals.
Candle Type Timeframe of candles used for calculations.

Notes

  • The strategy uses high-level StockSharp API with SubscribeCandles and indicator binding.
  • StartProtection is enabled with market orders for safer execution.
  • No Python version is provided.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Moving Average Oscillator Histogram strategy.
/// Generates signals when the oscillator forms local minima or maxima.
/// </summary>
public class MaOscillatorHistogramStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<bool> _enableBuyOpen;
	private readonly StrategyParam<bool> _enableSellOpen;
	private readonly StrategyParam<bool> _enableBuyClose;
	private readonly StrategyParam<bool> _enableSellClose;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevOsc1;
	private decimal _prevOsc2;
	private bool _isWarmup;

	/// <summary>
	/// Fast MA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow MA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Enable opening long positions.
	/// </summary>
	public bool EnableBuyOpen
	{
		get => _enableBuyOpen.Value;
		set => _enableBuyOpen.Value = value;
	}

	/// <summary>
	/// Enable opening short positions.
	/// </summary>
	public bool EnableSellOpen
	{
		get => _enableSellOpen.Value;
		set => _enableSellOpen.Value = value;
	}

	/// <summary>
	/// Enable closing long positions.
	/// </summary>
	public bool EnableBuyClose
	{
		get => _enableBuyClose.Value;
		set => _enableBuyClose.Value = value;
	}

	/// <summary>
	/// Enable closing short positions.
	/// </summary>
	public bool EnableSellClose
	{
		get => _enableSellClose.Value;
		set => _enableSellClose.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="MaOscillatorHistogramStrategy"/>.
	/// </summary>
	public MaOscillatorHistogramStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 13)
			.SetDisplay("Fast Period", "Period of fast moving average", "Indicators")
			;

		_slowPeriod = Param(nameof(SlowPeriod), 24)
			.SetDisplay("Slow Period", "Period of slow moving average", "Indicators")
			;

		_enableBuyOpen = Param(nameof(EnableBuyOpen), true)
			.SetDisplay("Enable Buy Open", "Allow opening long positions", "Signals");

		_enableSellOpen = Param(nameof(EnableSellOpen), true)
			.SetDisplay("Enable Sell Open", "Allow opening short positions", "Signals");

		_enableBuyClose = Param(nameof(EnableBuyClose), true)
			.SetDisplay("Enable Buy Close", "Allow closing long positions", "Signals");

		_enableSellClose = Param(nameof(EnableSellClose), true)
			.SetDisplay("Enable Sell Close", "Allow closing short positions", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevOsc1 = default;
		_prevOsc2 = default;
		_isWarmup = true;
	}

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

		// Create moving averages
		var fastMa = new ExponentialMovingAverage { Length = FastPeriod };
		var slowMa = new ExponentialMovingAverage { Length = SlowPeriod };

		// Subscribe to candles and bind indicators
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastMa, slowMa, ProcessCandle)
			.Start();

		// Chart visualization
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastMa);
			DrawIndicator(area, slowMa);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		// Process only finished candles
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure indicators are formed and trading is allowed
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var osc = fastValue - slowValue;

		if (_isWarmup)
		{
			_prevOsc1 = osc;
			_prevOsc2 = osc;
			_isWarmup = false;
			return;
		}

		var buySignal = _prevOsc2 > _prevOsc1 && _prevOsc1 < osc;
		var sellSignal = _prevOsc2 < _prevOsc1 && _prevOsc1 > osc;

		if (buySignal)
		{
			if (EnableSellClose && Position < 0)
				BuyMarket();

			if (EnableBuyOpen && Position <= 0)
				BuyMarket();
		}
		else if (sellSignal)
		{
			if (EnableBuyClose && Position > 0)
				SellMarket();

			if (EnableSellOpen && Position >= 0)
				SellMarket();
		}

		_prevOsc2 = _prevOsc1;
		_prevOsc1 = osc;
	}
}