Exp Oracle 戦略
この戦略はMetaTraderエキスパートアドバイザー Exp_Oracle のC#移植版です。相対力指数(RSI)と商品チャネル指数(CCI)を組み合わせて数バー先の市場の方向を予測するカスタム Oracle インジケーターに依存しています。インジケーターは2本のラインを生成します。
- Oracleライン – CCIとRSI極値の生の組み合わせ。
- シグナルライン – Oracleラインの平滑化移動平均。
戦略はこれらのラインを解釈するための3つのトレードモードを提供します。
- Breakdown – シグナルラインがゼロレベルをクロスしたときにポジションをオープンします。
- Twist – シグナルラインの局所的な転換点に反応します。
- Disposition – シグナルラインとOracleラインのクロスで取引します。
パラメーター
OraclePeriod– RSIとCCI計算の期間。Smooth– シグナルラインを平滑化するために使用するバーの数。Mode– トレードシグナルの生成に使用するアルゴリズム(Breakdown、Twist、またはDisposition)。CandleType– 受信ローソク足の時間軸。AllowBuy– ロングエントリーを有効にします。AllowSell– ショートエントリーを有効にします。Volume– ベースクラスStrategyから継承された戦略ボリューム。
エントリーとエグジットのルール
Breakdown
- シグナルラインがゼロを上抜けしたときに買い。
- シグナルラインがゼロを下抜けしたときに売り。
Twist
- シグナルラインが下落後に上向きに転換したときに買い。
- シグナルラインが上昇後に下向きに転換したときに売り。
Disposition
- シグナルラインがOracleラインを上抜けしたときに買い。
- シグナルラインがOracleラインを下抜けしたときに売り。
反対のシグナルが現れると既存のポジションは決済され反転されます。戦略はシンプルさのために成行注文を使用します。
インジケーターのロジック
各バーについて:
- 指定された
OraclePeriodでRSIとCCIを計算します。 - 最近のCCIとRSI値の差を使用して4つのダイバージェンス値を構築します。
- OracleラインはダイバージェンスのMaxとMinの合計です。
- シグナルラインは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")
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()