在 GitHub 上查看

Exp Oracle 策略

该策略是 MetaTrader Exp_Oracle 专家的 C# 移植版。它依赖自定义的 Oracle 指标,该指标将相对强弱指数 (RSI) 与商品通道指数 (CCI) 结合,用于预测未来数根K线的走势。指标输出两条线:

  • Oracle 线:CCI 与 RSI 极值的组合。
  • Signal 线:Oracle 线的平滑移动平均。

策略提供三种信号模式来解释这些线:

  1. Breakdown – 当 Signal 线穿越零轴时开仓。
  2. Twist – 当 Signal 线出现转折点时开仓。
  3. Disposition – 当 Signal 线与 Oracle 线发生交叉时开仓。

参数

  • OraclePeriod – 计算 RSI 与 CCI 的周期。
  • Smooth – 平滑 Signal 线所使用的周期数。
  • Mode – 生成交易信号的算法(BreakdownTwistDisposition)。
  • CandleType – 使用的K线周期。
  • AllowBuy – 是否允许做多。
  • AllowSell – 是否允许做空。
  • Volume – 交易量,继承自 Strategy 基类。

入场与出场规则

Breakdown

  • Signal 线向上穿越零轴时买入。
  • Signal 线向下穿越零轴时卖出。

Twist

  • Signal 线下降后转向上升时买入。
  • Signal 线上升后转向下降时卖出。

Disposition

  • Signal 线向上穿越 Oracle 线时买入。
  • Signal 线向下穿越 Oracle 线时卖出。

出现反向信号时,持仓会被平仓并反向建立新仓。策略使用市价单实现。

指标逻辑

每根K线的计算步骤:

  1. 使用 OraclePeriod 计算 RSI 与 CCI。
  2. 根据最近的 CCI 与 RSI 值构建四个差值。
  3. Oracle 线等于这四个差值中最大值与最小值之和。
  4. Signal 线是 Oracle 线在 Smooth 根K线上的简单移动平均。

该方法通过结合动量 (RSI) 与通道 (CCI) 信息来尝试预测短期价格走势。

说明

  • 策略仅在K线收盘后运行。
  • 未实现保护性止损,请根据需要自行管理风险。
using System;
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>
/// Strategy based on the Oracle indicator combining RSI and CCI.
/// Supports three signal modes: zero line breakdown, direction twist,
/// and crossing between indicator and its signal line.
/// </summary>
public class ExpOracleStrategy : Strategy
{
	/// <summary>
	/// Trading algorithm modes.
	/// </summary>
	public enum AlgorithmModes
	{
		/// <summary>
		/// Signal line crossing zero.
		/// </summary>
		Breakdown,

		/// <summary>
		/// Change of signal line direction.
		/// </summary>
		Twist,

		/// <summary>
		/// Signal line crossing main line.
		/// </summary>
		Disposition
	}

	private readonly StrategyParam<int> _oraclePeriod;
	private readonly StrategyParam<int> _smooth;
	private readonly StrategyParam<AlgorithmModes> _mode;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _allowBuy;
	private readonly StrategyParam<bool> _allowSell;

	private RelativeStrengthIndex _rsi;
	private CommodityChannelIndex _cci;
	private SimpleMovingAverage _sma;

	private readonly decimal[] _rsiBuf = new decimal[4];
	private readonly decimal[] _cciBuf = new decimal[4];

	private decimal _prevSignal;
	private decimal _prevPrevSignal;
	private decimal _prevOracle;
	private int _barsSinceTrade;

	/// <summary>
	/// Oracle calculation period.
	/// </summary>
	public int OraclePeriod
	{
		get => _oraclePeriod.Value;
		set => _oraclePeriod.Value = value;
	}

	/// <summary>
	/// Smoothing length for signal line.
	/// </summary>
	public int Smooth
	{
		get => _smooth.Value;
		set => _smooth.Value = value;
	}

	/// <summary>
	/// Selected trading algorithm.
	/// </summary>
	public AlgorithmModes Mode
	{
		get => _mode.Value;
		set => _mode.Value = value;
	}

	/// <summary>
	/// Minimum number of bars between entries.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Candle type for subscription.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Allow long positions.
	/// </summary>
	public bool AllowBuy
	{
		get => _allowBuy.Value;
		set => _allowBuy.Value = value;
	}

	/// <summary>
	/// Allow short positions.
	/// </summary>
	public bool AllowSell
	{
		get => _allowSell.Value;
		set => _allowSell.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="ExpOracleStrategy"/>.
	/// </summary>
	public ExpOracleStrategy()
	{
		_oraclePeriod = Param(nameof(OraclePeriod), 55)
			.SetGreaterThanZero()
			.SetDisplay("Oracle Period", "Oracle period", "Parameters");

		_smooth = Param(nameof(Smooth), 8)
			.SetGreaterThanZero()
			.SetDisplay("Smooth", "Smoothing length", "Parameters");

		_mode = Param(nameof(Mode), AlgorithmModes.Breakdown)
			.SetDisplay("Mode", "Signal algorithm", "Parameters");

		_cooldownBars = Param(nameof(CooldownBars), 4)
			.SetGreaterThanZero()
			.SetDisplay("Cooldown Bars", "Minimum number of bars between entries", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "Parameters");

		_allowBuy = Param(nameof(AllowBuy), true)
			.SetDisplay("Allow Buy", "Enable long entries", "Parameters");

		_allowSell = Param(nameof(AllowSell), true)
			.SetDisplay("Allow Sell", "Enable short entries", "Parameters");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevSignal = 0;
		_prevPrevSignal = 0;
		_prevOracle = 0;
		_barsSinceTrade = CooldownBars;
		Array.Clear(_rsiBuf, 0, _rsiBuf.Length);
		Array.Clear(_cciBuf, 0, _cciBuf.Length);
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_rsi = new RelativeStrengthIndex { Length = OraclePeriod };
		_cci = new CommodityChannelIndex { Length = OraclePeriod };
		_sma = new SimpleMovingAverage { Length = Smooth };

		StartProtection(null, null);

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// RSI uses decimal input, CCI uses candle input
		var rsiResult = _rsi.Process(candle.ClosePrice, candle.OpenTime, true);
		var cciResult = _cci.Process(candle);

		if (!rsiResult.IsFormed || !cciResult.IsFormed)
			return;

		var rsiVal = rsiResult.ToDecimal();
		var cciVal = cciResult.ToDecimal();

		// shift buffers
		_rsiBuf[3] = _rsiBuf[2];
		_rsiBuf[2] = _rsiBuf[1];
		_rsiBuf[1] = _rsiBuf[0];
		_rsiBuf[0] = rsiVal;

		_cciBuf[3] = _cciBuf[2];
		_cciBuf[2] = _cciBuf[1];
		_cciBuf[1] = _cciBuf[0];
		_cciBuf[0] = cciVal;

		// compute Oracle value
		var div0 = _cciBuf[0] - _rsiBuf[0];
		var dDiv = div0;
		var div1 = _cciBuf[1] - _rsiBuf[1] - dDiv;
		dDiv += div1;
		var div2 = _cciBuf[2] - _rsiBuf[2] - dDiv;
		dDiv += div2;
		var div3 = _cciBuf[3] - _rsiBuf[3] - dDiv;

		var max = Math.Max(Math.Max(div0, div1), Math.Max(div2, div3));
		var min = Math.Min(Math.Min(div0, div1), Math.Min(div2, div3));
		var oracle = max + min;

		// smooth to get signal
		var signalResult = _sma.Process(oracle, candle.OpenTime, true);
		if (!signalResult.IsFormed)
			return;

		var signal = signalResult.ToDecimal();
		_barsSinceTrade++;

		switch (Mode)
		{
			case AlgorithmModes.Breakdown:
				if (AllowBuy && _barsSinceTrade >= CooldownBars && _prevSignal <= 0m && signal > 0m && Position <= 0)
				{
					if (Position < 0)
						BuyMarket();

					BuyMarket();
					_barsSinceTrade = 0;
				}
				else if (AllowSell && _barsSinceTrade >= CooldownBars && _prevSignal >= 0m && signal < 0m && Position >= 0)
				{
					if (Position > 0)
						SellMarket();

					SellMarket();
					_barsSinceTrade = 0;
				}
				break;

			case AlgorithmModes.Twist:
				if (AllowBuy && _barsSinceTrade >= CooldownBars && _prevPrevSignal > _prevSignal && signal >= _prevSignal && Position <= 0)
				{
					if (Position < 0)
						BuyMarket();

					BuyMarket();
					_barsSinceTrade = 0;
				}
				else if (AllowSell && _barsSinceTrade >= CooldownBars && _prevPrevSignal < _prevSignal && signal <= _prevSignal && Position >= 0)
				{
					if (Position > 0)
						SellMarket();

					SellMarket();
					_barsSinceTrade = 0;
				}
				break;

			case AlgorithmModes.Disposition:
				if (AllowBuy && _barsSinceTrade >= CooldownBars && _prevSignal < _prevOracle && signal >= oracle && Position <= 0)
				{
					if (Position < 0)
						BuyMarket();

					BuyMarket();
					_barsSinceTrade = 0;
				}
				else if (AllowSell && _barsSinceTrade >= CooldownBars && _prevSignal > _prevOracle && signal <= oracle && Position >= 0)
				{
					if (Position > 0)
						SellMarket();

					SellMarket();
					_barsSinceTrade = 0;
				}
				break;
		}

		_prevPrevSignal = _prevSignal;
		_prevSignal = signal;
		_prevOracle = oracle;
	}
}