RAVIiAO 策略(StockSharp)
概览
RAVIiAO 策略 在 StockSharp 高级 API 中复刻 MetaTrader 4 专家顾问 "RAVIiAO"。策略等待每根 K 线收盘,同时评估 RAVI 振荡指标的斜率以及比尔·威廉姆斯的加速/减速(AC)振荡指标。当两个指标一致指向同一趋势方向时,立即以市价建仓。 移植版保留了原有的全部参数——均线周期、阈值、止损/止盈距离以及下单手数——方便交易者无缝复现旧版逻辑。
工作流程
- 订阅蜡烛线——策略订阅可配置的时间框架(默认 30 分钟)。
- 指标更新——每当蜡烛线收盘时,更新两条简单移动平均线以构建 RAVI,并将蜡烛送入 Awesome Oscillator 与 5 周期简单均线的组合中,得到 AC 数值。
- 信号缓存——最新收盘的蜡烛被保存为“第 1 根柱子”,前一数值成为“第 2 根柱子”,对应 MT4 中
iCustom(...,1)与iCustom(...,2)的调用结果。 - 入场决策——当 AC 与 RAVI 同时向上并满足
AC[1] > AC[2] > 0、RAVI[1] > RAVI[2] > Threshold时做多; 做空条件为镜像逻辑。 - 风控管理——下单后立即记录以点数表示的固定止损与止盈(即
StopLossPoints * PriceStep),并通过蜡烛的 最高价/最低价监控是否被触发。 - 状态复位——当保护位被触发时,通过市价单平仓,并清空内部缓存,等待下一次机会。
交易规则
- 做多条件
- 前一根 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 版本不一致。 - 在不同波动水平的市场上使用时,可优化
Threshold、FastLength与SlowLength参数。 - 建议结合 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage, AwesomeOscillator
from indicator_extensions import *
class ravi_iao_strategy(Strategy):
def __init__(self):
super(ravi_iao_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(10))) \
.SetDisplay("Candle Type", "Time-frame for analysis", "General")
self._fast_length = self.Param("FastLength", 12) \
.SetDisplay("Fast Length", "Fast SMA period inside RAVI", "RAVI")
self._slow_length = self.Param("SlowLength", 72) \
.SetDisplay("Slow Length", "Slow SMA period inside RAVI", "RAVI")
self._threshold = self.Param("Threshold", 0.3) \
.SetDisplay("RAVI Threshold", "Minimum absolute RAVI value to confirm the trend", "Signals")
self._stop_loss_points = self.Param("StopLossPoints", 500.0) \
.SetDisplay("Stop Loss", "Stop-loss distance in price units", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0) \
.SetDisplay("Take Profit", "Take-profit distance in price units", "Risk")
self._prev_ravi = None
self._prev_prev_ravi = None
self._prev_ac = None
self._prev_prev_ac = None
self._ao_average = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
@property
def Threshold(self):
return self._threshold.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
def OnStarted2(self, time):
super(ravi_iao_strategy, self).OnStarted2(time)
fast_ma = SimpleMovingAverage()
fast_ma.Length = self.FastLength
slow_ma = SimpleMovingAverage()
slow_ma.Length = self.SlowLength
ao = AwesomeOscillator()
self._ao_average = SimpleMovingAverage()
self._ao_average.Length = 5
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(fast_ma, slow_ma, ao, self.ProcessCandle).Start()
tp = Unit(float(self.TakeProfitPoints), UnitTypes.Absolute) if float(self.TakeProfitPoints) > 0 else None
sl = Unit(float(self.StopLossPoints), UnitTypes.Absolute) if float(self.StopLossPoints) > 0 else None
self.StartProtection(tp, sl)
def ProcessCandle(self, candle, fast_val, slow_val, ao_val):
if candle.State != CandleStates.Finished:
return
if fast_val.IsEmpty or slow_val.IsEmpty or ao_val.IsEmpty:
return
fast_value = float(fast_val)
slow_value = float(slow_val)
ao_value = float(ao_val)
ao_avg_result = process_float(self._ao_average, ao_value, candle.OpenTime, True)
if ao_avg_result.IsEmpty:
return
ao_avg_value = float(ao_avg_result)
ac = ao_value - ao_avg_value
if slow_value == 0:
self._update_history(None, ac)
return
ravi = 100.0 * (fast_value - slow_value) / slow_value
if (self._prev_ravi is not None and
self._prev_prev_ravi is not None and
self._prev_ac is not None and
self._prev_prev_ac is not None and
self.Position == 0 and
self.IsFormedAndOnlineAndAllowTrading()):
threshold = float(self.Threshold)
bullish = (self._prev_ac > self._prev_prev_ac and self._prev_prev_ac > 0
and self._prev_ravi > self._prev_prev_ravi and self._prev_ravi > threshold)
bearish = (self._prev_ac < self._prev_prev_ac and self._prev_prev_ac < 0
and self._prev_ravi < self._prev_prev_ravi and self._prev_ravi < -threshold)
if bullish:
self.BuyMarket(self.Volume)
elif bearish:
self.SellMarket(self.Volume)
self._update_history(ravi, ac)
def _update_history(self, ravi, ac):
self._prev_prev_ravi = self._prev_ravi
self._prev_ravi = ravi
self._prev_prev_ac = self._prev_ac
self._prev_ac = ac
def OnReseted(self):
super(ravi_iao_strategy, self).OnReseted()
self._prev_ravi = None
self._prev_prev_ravi = None
self._prev_ac = None
self._prev_prev_ac = None
def CreateClone(self):
return ravi_iao_strategy()