移动平均彩虹 (Stormer) 策略
该策略绘制十二条不同周期的移动平均线形成“彩虹”。当趋势得到确认且价格触碰其中一条平均线时开仓。
价格创新高、中央均线向上排列并收于所有均线平均值之上时做多;出现相反条件时做空。
止损放在上一次触碰的移动平均线位置,止盈根据入场价与止损价之间的距离乘以系数计算。
细节
- 指标:12 条可配置类型的移动平均线。
- 做多:上升趋势、新高并存在上一次触碰价格。
- 做空:下降趋势、新低并存在上一次触碰价格。
- 退出:止损在触碰的均线,目标 = 入场 ± 距离 * 系数,可选的趋势反转退出。
- 参数:均线类型、周期、目标系数、反转选项。
- 时间框架:任意。
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()