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;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class moving_average_rainbow_stormer_strategy(Strategy):
def __init__(self):
super(moving_average_rainbow_stormer_strategy, self).__init__()
self._target_factor = self.Param("TargetFactor", 2.0)
self._cooldown_bars = self.Param("CooldownBars", 40) \
.SetGreaterThanZero()
self._min_trend_spread_percent = self.Param("MinTrendSpreadPercent", 0.05) \
.SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(10)))
self._entry_price = 0.0
self._bar_index = 0
self._last_signal_bar = -1000000
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(moving_average_rainbow_stormer_strategy, self).OnReseted()
self._entry_price = 0.0
self._bar_index = 0
self._last_signal_bar = -1000000
def OnStarted2(self, time):
super(moving_average_rainbow_stormer_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._bar_index = 0
self._last_signal_bar = -1000000
self._ma3 = ExponentialMovingAverage()
self._ma3.Length = 3
self._ma8 = ExponentialMovingAverage()
self._ma8.Length = 8
self._ma20 = ExponentialMovingAverage()
self._ma20.Length = 20
self._ma50 = ExponentialMovingAverage()
self._ma50.Length = 50
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ma3, self._ma8, self._ma20, self._ma50, self.OnProcess).Start()
def OnProcess(self, candle, ma3, ma8, ma20, ma50):
if candle.State != CandleStates.Finished:
return
self._bar_index += 1
close = float(candle.ClosePrice)
m3 = float(ma3)
m8 = float(ma8)
m20 = float(ma20)
m50 = float(ma50)
bullish_alignment = m3 > m8 and m8 > m20 and m20 > m50
bearish_alignment = m3 < m8 and m8 < m20 and m20 < m50
trend_spread_percent = abs(m3 - m50) / close * 100.0 if close != 0.0 else 0.0
cd = self._cooldown_bars.Value
can_signal = self._bar_index - self._last_signal_bar >= cd
min_spread = float(self._min_trend_spread_percent.Value)
bullish_signal = bullish_alignment and trend_spread_percent >= min_spread
bearish_signal = bearish_alignment and trend_spread_percent >= min_spread
tf = float(self._target_factor.Value)
if can_signal and bullish_signal and close > m3 and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._last_signal_bar = self._bar_index
elif can_signal and bearish_signal and close < m3 and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._last_signal_bar = self._bar_index
if can_signal and self.Position > 0 and self._entry_price > 0.0:
risk = self._entry_price - m20
if risk > 0.0:
target = self._entry_price + risk * tf
if close >= target or close < m20:
self.SellMarket()
self._last_signal_bar = self._bar_index
elif close < m8:
self.SellMarket()
self._last_signal_bar = self._bar_index
elif can_signal and self.Position < 0 and self._entry_price > 0.0:
risk = m20 - self._entry_price
if risk > 0.0:
target = self._entry_price - risk * tf
if close <= target or close > m20:
self.BuyMarket()
self._last_signal_bar = self._bar_index
elif close > m8:
self.BuyMarket()
self._last_signal_bar = self._bar_index
def CreateClone(self):
return moving_average_rainbow_stormer_strategy()