Exp Oracle 策略
该策略是 MetaTrader Exp_Oracle 专家的 C# 移植版。它依赖自定义的 Oracle 指标,该指标将相对强弱指数 (RSI) 与商品通道指数 (CCI) 结合,用于预测未来数根K线的走势。指标输出两条线:
- Oracle 线:CCI 与 RSI 极值的组合。
- Signal 线:Oracle 线的平滑移动平均。
策略提供三种信号模式来解释这些线:
- Breakdown – 当 Signal 线穿越零轴时开仓。
- Twist – 当 Signal 线出现转折点时开仓。
- Disposition – 当 Signal 线与 Oracle 线发生交叉时开仓。
参数
OraclePeriod– 计算 RSI 与 CCI 的周期。Smooth– 平滑 Signal 线所使用的周期数。Mode– 生成交易信号的算法(Breakdown、Twist或Disposition)。CandleType– 使用的K线周期。AllowBuy– 是否允许做多。AllowSell– 是否允许做空。Volume– 交易量,继承自Strategy基类。
入场与出场规则
Breakdown
- Signal 线向上穿越零轴时买入。
- Signal 线向下穿越零轴时卖出。
Twist
- Signal 线下降后转向上升时买入。
- Signal 线上升后转向下降时卖出。
Disposition
- Signal 线向上穿越 Oracle 线时买入。
- Signal 线向下穿越 Oracle 线时卖出。
出现反向信号时,持仓会被平仓并反向建立新仓。策略使用市价单实现。
指标逻辑
每根K线的计算步骤:
- 使用
OraclePeriod计算 RSI 与 CCI。 - 根据最近的 CCI 与 RSI 值构建四个差值。
- Oracle 线等于这四个差值中最大值与最小值之和。
- 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;
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, CommodityChannelIndex, SimpleMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_oracle_strategy(Strategy):
# Algorithm modes
BREAKDOWN = 0
TWIST = 1
DISPOSITION = 2
def __init__(self):
super(exp_oracle_strategy, self).__init__()
self._oracle_period = self.Param("OraclePeriod", 55) \
.SetGreaterThanZero() \
.SetDisplay("Oracle Period", "Oracle period", "Parameters")
self._smooth = self.Param("Smooth", 8) \
.SetGreaterThanZero() \
.SetDisplay("Smooth", "Smoothing length", "Parameters")
self._mode = self.Param("Mode", 0) \
.SetDisplay("Mode", "Signal algorithm (0=Breakdown, 1=Twist, 2=Disposition)", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Minimum number of bars between entries", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle type", "Parameters")
self._allow_buy = self.Param("AllowBuy", True) \
.SetDisplay("Allow Buy", "Enable long entries", "Parameters")
self._allow_sell = self.Param("AllowSell", True) \
.SetDisplay("Allow Sell", "Enable short entries", "Parameters")
self._rsi = None
self._cci = None
self._sma = None
self._rsi_buf = [0.0] * 4
self._cci_buf = [0.0] * 4
self._prev_signal = 0.0
self._prev_prev_signal = 0.0
self._prev_oracle = 0.0
self._bars_since_trade = 0
@property
def oracle_period(self):
return self._oracle_period.Value
@property
def smooth(self):
return self._smooth.Value
@property
def mode(self):
return self._mode.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def allow_buy(self):
return self._allow_buy.Value
@property
def allow_sell(self):
return self._allow_sell.Value
def OnReseted(self):
super(exp_oracle_strategy, self).OnReseted()
self._prev_signal = 0.0
self._prev_prev_signal = 0.0
self._prev_oracle = 0.0
self._bars_since_trade = self.cooldown_bars
self._rsi_buf = [0.0] * 4
self._cci_buf = [0.0] * 4
def OnStarted2(self, time):
super(exp_oracle_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.oracle_period
self._cci = CommodityChannelIndex()
self._cci.Length = self.oracle_period
self._sma = SimpleMovingAverage()
self._sma.Length = self.smooth
self.StartProtection(None, None)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
rsi_result = process_float(self._rsi, candle.ClosePrice, candle.OpenTime, True)
cci_inp = CandleIndicatorValue(self._cci, candle)
cci_result = self._cci.Process(cci_inp)
if not rsi_result.IsFormed or not cci_result.IsFormed:
return
rsi_val = float(rsi_result)
cci_val = float(cci_result)
# shift buffers
self._rsi_buf[3] = self._rsi_buf[2]
self._rsi_buf[2] = self._rsi_buf[1]
self._rsi_buf[1] = self._rsi_buf[0]
self._rsi_buf[0] = rsi_val
self._cci_buf[3] = self._cci_buf[2]
self._cci_buf[2] = self._cci_buf[1]
self._cci_buf[1] = self._cci_buf[0]
self._cci_buf[0] = cci_val
# compute Oracle value
div0 = self._cci_buf[0] - self._rsi_buf[0]
d_div = div0
div1 = self._cci_buf[1] - self._rsi_buf[1] - d_div
d_div += div1
div2 = self._cci_buf[2] - self._rsi_buf[2] - d_div
d_div += div2
div3 = self._cci_buf[3] - self._rsi_buf[3] - d_div
max_val = max(div0, div1, div2, div3)
min_val = min(div0, div1, div2, div3)
oracle = max_val + min_val
# smooth to get signal
signal_result = process_float(self._sma, Decimal(oracle), candle.OpenTime, True)
if not signal_result.IsFormed:
return
signal = float(signal_result)
self._bars_since_trade += 1
m = self.mode
if m == self.BREAKDOWN:
if self.allow_buy and self._bars_since_trade >= self.cooldown_bars and self._prev_signal <= 0.0 and signal > 0.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._bars_since_trade = 0
elif self.allow_sell and self._bars_since_trade >= self.cooldown_bars and self._prev_signal >= 0.0 and signal < 0.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._bars_since_trade = 0
elif m == self.TWIST:
if self.allow_buy and self._bars_since_trade >= self.cooldown_bars and self._prev_prev_signal > self._prev_signal and signal >= self._prev_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._bars_since_trade = 0
elif self.allow_sell and self._bars_since_trade >= self.cooldown_bars and self._prev_prev_signal < self._prev_signal and signal <= self._prev_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._bars_since_trade = 0
elif m == self.DISPOSITION:
if self.allow_buy and self._bars_since_trade >= self.cooldown_bars and self._prev_signal < self._prev_oracle and signal >= oracle and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._bars_since_trade = 0
elif self.allow_sell and self._bars_since_trade >= self.cooldown_bars and self._prev_signal > self._prev_oracle and signal <= oracle and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._bars_since_trade = 0
self._prev_prev_signal = self._prev_signal
self._prev_signal = signal
self._prev_oracle = oracle
def CreateClone(self):
return exp_oracle_strategy()