Exp Oracle Strategy
This strategy is a C# port of the MetaTrader Exp_Oracle expert advisor. It relies on a custom Oracle indicator that blends the Relative Strength Index (RSI) and the Commodity Channel Index (CCI) to forecast market direction several bars ahead. The indicator generates two lines:
- Oracle line – raw combination of CCI and RSI extremes.
- Signal line – smoothed moving average of the Oracle line.
The strategy provides three trading modes to interpret these lines:
- Breakdown – opens positions when the signal line crosses the zero level.
- Twist – reacts to local turning points of the signal line.
- Disposition – trades on crossings between the signal and the Oracle line.
Parameters
OraclePeriod– period for RSI and CCI calculations.Smooth– number of bars used to smooth the signal line.Mode– algorithm used to generate trading signals (Breakdown,Twist, orDisposition).CandleType– timeframe of incoming candles.AllowBuy– enables long entries.AllowSell– enables short entries.Volume– strategy volume inherited from the baseStrategyclass.
Entry and Exit Rules
Breakdown
- Buy when the signal line crosses above zero.
- Sell when the signal line crosses below zero.
Twist
- Buy when the signal line turns upward after a decline.
- Sell when the signal line turns downward after a rise.
Disposition
- Buy when the signal line crosses above the Oracle line.
- Sell when the signal line crosses below the Oracle line.
Existing positions are closed and reversed when an opposite signal appears. The strategy uses market orders for simplicity.
Indicator Logic
For each bar:
- Calculate RSI and CCI with the specified
OraclePeriod. - Build four divergence values using differences between recent CCI and RSI values.
- The Oracle line is the sum of the maximum and minimum divergence.
- The Signal line is the simple moving average of the Oracle line over
Smoothbars.
This approach attempts to predict short-term price movement by combining momentum (RSI) and channel (CCI) information.
Notes
- The strategy operates on completed candles only.
- Protective stops are not implemented; use external risk controls if necessary.
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()