Ver no GitHub

Moving Average Rainbow (Stormer) Strategy

This strategy plots a rainbow of twelve moving averages. Trades are opened when the trend is confirmed and price touches one of the averages.

A long position opens when price makes a new high, all middle averages slope upward and the candle closes above the mean of all averages. A short position opens when the opposite conditions occur.

The stop loss is set to the previously touched moving average. The take profit is calculated as a multiple of the distance between entry price and the stop loss.

Details

  • Indicators: 12 moving averages of configurable type.
  • Long: Uptrend, new high and previous touch price.
  • Short: Downtrend, new low and previous touch price.
  • Exits: Stop loss at touched average, target = entry ± distance * factor. Optional turnover exit when the trend shows reversal signals.
  • Parameters: moving average type, lengths, target factor, turnover options.
  • Timeframe: Any.
using System;
using System.Linq;
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>
/// Moving Average Rainbow (Stormer) strategy.
/// Uses multiple EMA rainbow for trend confirmation.
/// Enters long when price is above all MAs (bullish alignment), short when below.
/// </summary>
public class MovingAverageRainbowStormerStrategy : Strategy
{
	private readonly StrategyParam<decimal> _targetFactor;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _minTrendSpreadPercent;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private bool _prevBullish;
	private bool _prevBearish;
	private int _barIndex;
	private int _lastSignalBar = -1000000;

	public decimal TargetFactor { get => _targetFactor.Value; set => _targetFactor.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public decimal MinTrendSpreadPercent { get => _minTrendSpreadPercent.Value; set => _minTrendSpreadPercent.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MovingAverageRainbowStormerStrategy()
	{
		_targetFactor = Param(nameof(TargetFactor), 2m);
		_cooldownBars = Param(nameof(CooldownBars), 40).SetGreaterThanZero();
		_minTrendSpreadPercent = Param(nameof(MinTrendSpreadPercent), 0.05m).SetGreaterThanZero();
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(10).TimeFrame());
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_prevBullish = false;
		_prevBearish = false;
		_barIndex = 0;
		_lastSignalBar = -1000000;

		var ma3 = new ExponentialMovingAverage { Length = 3 };
		var ma8 = new ExponentialMovingAverage { Length = 8 };
		var ma20 = new ExponentialMovingAverage { Length = 20 };
		var ma50 = new ExponentialMovingAverage { Length = 50 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ma3, ma8, ma20, ma50, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ma3, decimal ma8, decimal ma20, decimal ma50)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_barIndex++;

		var close = candle.ClosePrice;

		var bullishAlignment = ma3 > ma8 && ma8 > ma20 && ma20 > ma50;
		var bearishAlignment = ma3 < ma8 && ma8 < ma20 && ma20 < ma50;
		var trendSpreadPercent = close != 0m ? Math.Abs(ma3 - ma50) / close * 100m : 0m;
		var canSignal = _barIndex - _lastSignalBar >= CooldownBars;
		var bullishSignal = bullishAlignment && trendSpreadPercent >= MinTrendSpreadPercent;
		var bearishSignal = bearishAlignment && trendSpreadPercent >= MinTrendSpreadPercent;

		if (canSignal && bullishSignal && close > ma3 && Position <= 0)
		{
			BuyMarket();
			_entryPrice = close;
			_lastSignalBar = _barIndex;
		}
		else if (canSignal && bearishSignal && close < ma3 && Position >= 0)
		{
			SellMarket();
			_entryPrice = close;
			_lastSignalBar = _barIndex;
		}

		if (canSignal && Position > 0 && _entryPrice > 0)
		{
			var risk = _entryPrice - ma20;
			if (risk > 0)
			{
				var target = _entryPrice + risk * TargetFactor;
				if (close >= target || close < ma20)
				{
					SellMarket();
					_lastSignalBar = _barIndex;
				}
			}
			else if (close < ma8)
			{
				SellMarket();
				_lastSignalBar = _barIndex;
			}
		}
		else if (canSignal && Position < 0 && _entryPrice > 0)
		{
			var risk = ma20 - _entryPrice;
			if (risk > 0)
			{
				var target = _entryPrice - risk * TargetFactor;
				if (close <= target || close > ma20)
				{
					BuyMarket();
					_lastSignalBar = _barIndex;
				}
			}
			else if (close > ma8)
			{
				BuyMarket();
				_lastSignalBar = _barIndex;
			}
		}

		_prevBullish = bullishAlignment;
		_prevBearish = bearishAlignment;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0m;
		_prevBullish = false;
		_prevBearish = false;
		_barIndex = 0;
		_lastSignalBar = -1000000;
	}
}