Стратегия Exp Oracle
Стратегия является портом советника Exp_Oracle на C#. В основе лежит пользовательский индикатор Oracle, объединяющий индикаторы RSI и CCI для прогнозирования движения цены на несколько баров вперёд. Индикатор строит две линии:
- Oracle – комбинация экстремумов CCI и RSI.
- Signal – сглаженное скользящее среднее линии Oracle.
Для интерпретации линий предусмотрено три режима работы:
- Breakdown – вход при пересечении нулевой линии сигналом.
- Twist – реагирует на развороты направления сигналной линии.
- Disposition – торгует на пересечениях сигналом линии Oracle.
Параметры
OraclePeriod– период расчёта RSI и CCI.Smooth– длина сглаживания сигналной линии.Mode– используемый алгоритм (Breakdown,TwistилиDisposition).CandleType– используемый таймфрейм свечей.AllowBuy– разрешение на открытие длинных позиций.AllowSell– разрешение на открытие коротких позиций.Volume– объём сделки, унаследованный от базового классаStrategy.
Правила входа и выхода
Breakdown
- Покупка при пересечении сигналом уровня 0 вверх.
- Продажа при пересечении уровня 0 вниз.
Twist
- Покупка при развороте сигналной линии вверх после снижения.
- Продажа при развороте вниз после роста.
Disposition
- Покупка при пересечении сигналом линии Oracle снизу вверх.
- Продажа при пересечении сверху вниз.
При появлении противоположного сигнала текущая позиция закрывается и открывается новая в обратном направлении. Используются рыночные заявки.
Логика индикатора
Для каждой свечи выполняются шаги:
- Вычисляются RSI и CCI с периодом
OraclePeriod. - По последним значениям CCI и RSI рассчитываются четыре разности.
- Линия Oracle равна сумме максимальной и минимальной разностей.
- Линия Signal — простое скользящее среднее линии Oracle за
Smoothсвечей.
Подход сочетает импульс (RSI) и канал (CCI), что позволяет оценивать краткосрочное направление рынка.
Примечания
- Стратегия работает только на закрытых свечах.
- Защитные стопы не реализованы; при необходимости используйте внешние средства управления рисками.
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()