在 GitHub 上查看

移动平均彩虹 (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;
	}
}