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")
clr.AddReference("StockSharp.BusinessEntities")
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()