在 GitHub 上查看

RAVIiAO 策略(StockSharp)

概览

RAVIiAO 策略 在 StockSharp 高级 API 中复刻 MetaTrader 4 专家顾问 "RAVIiAO"。策略等待每根 K 线收盘,同时评估 RAVI 振荡指标的斜率以及比尔·威廉姆斯的加速/减速(AC)振荡指标。当两个指标一致指向同一趋势方向时,立即以市价建仓。 移植版保留了原有的全部参数——均线周期、阈值、止损/止盈距离以及下单手数——方便交易者无缝复现旧版逻辑。

工作流程

  1. 订阅蜡烛线——策略订阅可配置的时间框架(默认 30 分钟)。
  2. 指标更新——每当蜡烛线收盘时,更新两条简单移动平均线以构建 RAVI,并将蜡烛送入 Awesome Oscillator 与 5 周期简单均线的组合中,得到 AC 数值。
  3. 信号缓存——最新收盘的蜡烛被保存为“第 1 根柱子”,前一数值成为“第 2 根柱子”,对应 MT4 中 iCustom(...,1)iCustom(...,2) 的调用结果。
  4. 入场决策——当 AC 与 RAVI 同时向上并满足 AC[1] > AC[2] > 0RAVI[1] > RAVI[2] > Threshold 时做多; 做空条件为镜像逻辑。
  5. 风控管理——下单后立即记录以点数表示的固定止损与止盈(即 StopLossPoints * PriceStep),并通过蜡烛的 最高价/最低价监控是否被触发。
  6. 状态复位——当保护位被触发时,通过市价单平仓,并清空内部缓存,等待下一次机会。

交易规则

  • 做多条件
    • 前一根 AC 值高于更早的 AC 值,且二者都大于 0。
    • 前一根 RAVI 值高于阈值且高于更早的 RAVI 值。
    • 信号触发时必须没有持仓。
  • 做空条件
    • 前一根 AC 值低于更早的 AC 值,且二者都小于 0。
    • 前一根 RAVI 值低于负阈值且低于更早的 RAVI 值。
    • 触发信号时不得有持仓。
  • 离场逻辑
    • 止损与止盈以点数表示,并通过 PriceStep 转换为价格偏移量。
    • 使用蜡烛的极值来检测突破(多头看低点,空头看高点等),并立即以市价平仓,从而模拟 MT4 的保护单执行。

参数

名称 说明
CandleType 订阅的蜡烛时间框架(默认 30 分钟)。
FastLength 构建 RAVI 时使用的快速均线周期。
SlowLength 构建 RAVI 时使用的慢速均线周期。
Threshold 验证趋势所需的 RAVI 百分比阈值。
StopLossPoints 以点数表示的止损距离(与 PriceStep 相乘)。
TakeProfitPoints 以点数表示的止盈距离。
TradeVolume 每次入场的下单手数。

移植说明

  • 策略缓存最近两根柱子的指标数值,使得第 n 根蜡烛的决策继续使用 MT4 中的 AC[1]RAVI[1],保持 “新柱子开盘立即决策” 的执行节奏。
  • AC 通过 Awesome Oscillator 与其 5 周期简单移动平均线的差值得到,与 MT4 的计算链完全一致。
  • 止损与止盈采用蜡烛极值检测,而不是注册挂单,更符合 StockSharp 的实现习惯,同时与 MT4 的结果保持一致。

使用建议

  • 确保标的品种的 PriceStep 设置正确,否则保护距离会与 MT4 版本不一致。
  • 在不同波动水平的市场上使用时,可优化 ThresholdFastLengthSlowLength 参数。
  • 建议结合 StockSharp 的组合风险控制或连接器保护功能,以提升实盘交易的安全性。
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>
/// Port of the MetaTrader 4 expert advisor "RAVIiAO" that combines the RAVI oscillator and the Acceleration/Deceleration oscillator.
/// </summary>
public class RaviIaoStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<decimal> _threshold;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private SMA _aoAverage;

	private decimal? _prevRavi;
	private decimal? _prevPrevRavi;
	private decimal? _prevAc;
	private decimal? _prevPrevAc;

	/// <summary>
	/// Type of candles used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Fast moving average length for the RAVI oscillator.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow moving average length for the RAVI oscillator.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// Threshold for bullish or bearish confirmation of the RAVI oscillator (percentage value).
	/// </summary>
	public decimal Threshold
	{
		get => _threshold.Value;
		set => _threshold.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in absolute price units.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="RaviIaoStrategy"/>.
	/// </summary>
	public RaviIaoStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(10).TimeFrame())
			.SetDisplay("Candle Type", "Time-frame for analysis", "General");

		_fastLength = Param(nameof(FastLength), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Length", "Fast SMA period inside RAVI", "RAVI");

		_slowLength = Param(nameof(SlowLength), 72)
			.SetGreaterThanZero()
			.SetDisplay("Slow Length", "Slow SMA period inside RAVI", "RAVI");

		_threshold = Param(nameof(Threshold), 0.3m)
			.SetDisplay("RAVI Threshold", "Minimum absolute RAVI value to confirm the trend", "Signals");

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price units", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price units", "Risk");
	}

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

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

		_prevRavi = null;
		_prevPrevRavi = null;
		_prevAc = null;
		_prevPrevAc = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var fastMa = new SMA { Length = FastLength };
		var slowMa = new SMA { Length = SlowLength };
		var ao = new AwesomeOscillator();
		_aoAverage = new SMA { Length = 5 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(new IIndicator[] { fastMa, slowMa, ao }, ProcessCandle)
			.Start();

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

		// Use StartProtection for SL/TP
		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var fastVal = values[0];
		var slowVal = values[1];
		var aoVal = values[2];

		if (fastVal.IsEmpty || slowVal.IsEmpty || aoVal.IsEmpty)
			return;

		var fastValue = fastVal.ToDecimal();
		var slowValue = slowVal.ToDecimal();
		var aoValue = aoVal.ToDecimal();

		// Compute AC = AO - SMA(AO)
		var aoAvgResult = _aoAverage.Process(aoVal);
		if (aoAvgResult.IsEmpty)
			return;

		var aoAvgValue = aoAvgResult.ToDecimal();
		var ac = aoValue - aoAvgValue;

		if (slowValue == 0m)
		{
			UpdateHistory(null, ac);
			return;
		}

		var ravi = 100m * (fastValue - slowValue) / slowValue;

		if (_prevRavi is decimal prevRavi &&
			_prevPrevRavi is decimal prevPrevRavi &&
			_prevAc is decimal prevAc &&
			_prevPrevAc is decimal prevPrevAc &&
			Position == 0 &&
			IsFormedAndOnlineAndAllowTrading())
		{
			var bullish = prevAc > prevPrevAc && prevPrevAc > 0m && prevRavi > prevPrevRavi && prevRavi > Threshold;
			var bearish = prevAc < prevPrevAc && prevPrevAc < 0m && prevRavi < prevPrevRavi && prevRavi < -Threshold;

			if (bullish)
			{
				BuyMarket(Volume);
			}
			else if (bearish)
			{
				SellMarket(Volume);
			}
		}

		UpdateHistory(ravi, ac);
	}

	private void UpdateHistory(decimal? ravi, decimal ac)
	{
		_prevPrevRavi = _prevRavi;
		_prevRavi = ravi;
		_prevPrevAc = _prevAc;
		_prevAc = ac;
	}
}