Color Schaff RVIトレンドサイクル戦略
この戦略はStockSharpのハイレベルAPIを使用してColor Schaff RVI Trend Cycleを実装します。インジケーターは高速・低速のRelative Vigor Index値の差にダブルストキャスティクスプロセスを適用し、結果を平滑化します。
パラメーター
FastRviLength– 高速RVI計算の期間(デフォルト23)。SlowRviLength– 低速RVI計算の期間(デフォルト50)。CycleLength– ストキャスティクスサイクルの長さ(デフォルト10)。HighLevel– 強気条件の検出に使用する上限しきい値(デフォルト60)。LowLevel– 弱気条件の検出に使用する下限しきい値(デフォルト-60)。CandleType– 戦略が処理するローソク足タイプ(デフォルト4時間足)。
取引ロジック
- 高速・低速のRVI値を計算する。
- RVI差からSchaff Trend Cycleを構築する。
- STC値が上限レベルを上回り上昇しているときに買い。
- STC値が下限レベルを下回り下降しているときに売り。
注意事項
- 戦略は完成したローソク足のみを処理します。
- 開始時にポジション保護が有効になります。
- このサンプルは教育目的のみで提供されており、投資アドバイスではありません。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Trading strategy based on a Schaff-style cycle built from fast and slow RVI averages.
/// </summary>
public class ColorSchaffRviTrendCycleStrategy : Strategy
{
private readonly StrategyParam<int> _fastRviLength;
private readonly StrategyParam<int> _slowRviLength;
private readonly StrategyParam<int> _cycleLength;
private readonly StrategyParam<int> _highLevel;
private readonly StrategyParam<int> _lowLevel;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<ICandleMessage> _recentCandles = [];
private readonly Queue<decimal> _fastWindow = [];
private readonly Queue<decimal> _slowWindow = [];
private readonly List<decimal> _macd = [];
private readonly List<decimal> _st = [];
private decimal _fastSum;
private decimal _slowSum;
private bool _stReady;
private bool _stcReady;
private decimal _prevSt;
private decimal _prevStc;
private int _cooldownRemaining;
/// <summary>
/// Fast RVI smoothing length.
/// </summary>
public int FastRviLength
{
get => _fastRviLength.Value;
set => _fastRviLength.Value = value;
}
/// <summary>
/// Slow RVI smoothing length.
/// </summary>
public int SlowRviLength
{
get => _slowRviLength.Value;
set => _slowRviLength.Value = value;
}
/// <summary>
/// Cycle length for stochastic calculations.
/// </summary>
public int CycleLength
{
get => _cycleLength.Value;
set => _cycleLength.Value = value;
}
/// <summary>
/// Upper threshold for the cycle.
/// </summary>
public int HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Lower threshold for the cycle.
/// </summary>
public int LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Bars to wait between reversals.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Candle type used for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ColorSchaffRviTrendCycleStrategy"/>.
/// </summary>
public ColorSchaffRviTrendCycleStrategy()
{
_fastRviLength = Param(nameof(FastRviLength), 23)
.SetGreaterThanZero()
.SetDisplay("Fast RVI Length", "Smoothing length for fast RVI", "General");
_slowRviLength = Param(nameof(SlowRviLength), 50)
.SetGreaterThanZero()
.SetDisplay("Slow RVI Length", "Smoothing length for slow RVI", "General");
_cycleLength = Param(nameof(CycleLength), 10)
.SetGreaterThanZero()
.SetDisplay("Cycle", "Length of the stochastic cycle", "General");
_highLevel = Param(nameof(HighLevel), 60)
.SetDisplay("High Level", "Upper threshold", "General");
_lowLevel = Param(nameof(LowLevel), -60)
.SetDisplay("Low Level", "Lower threshold", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_recentCandles.Clear();
_fastWindow.Clear();
_slowWindow.Clear();
_macd.Clear();
_st.Clear();
_fastSum = 0m;
_slowSum = 0m;
_stReady = false;
_stcReady = false;
_prevSt = 0m;
_prevStc = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_recentCandles.Clear();
_fastWindow.Clear();
_slowWindow.Clear();
_macd.Clear();
_st.Clear();
_fastSum = 0m;
_slowSum = 0m;
_stReady = false;
_stcReady = false;
_prevSt = 0m;
_prevStc = 0m;
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
_recentCandles.Add(candle);
if (_recentCandles.Count > 4)
_recentCandles.RemoveAt(0);
if (_recentCandles.Count < 4)
return;
var rawRvi = CalculateRawRvi();
UpdateWindow(_fastWindow, ref _fastSum, rawRvi, FastRviLength);
UpdateWindow(_slowWindow, ref _slowSum, rawRvi, SlowRviLength);
if (_fastWindow.Count < FastRviLength || _slowWindow.Count < SlowRviLength)
return;
var fast = _fastSum / _fastWindow.Count;
var slow = _slowSum / _slowWindow.Count;
var macd = fast - slow;
AddValue(_macd, macd, CycleLength);
if (_macd.Count < CycleLength)
return;
GetMinMax(_macd, out var minMacd, out var maxMacd);
var st = maxMacd == minMacd ? _prevSt : (macd - minMacd) / (maxMacd - minMacd) * 100m;
if (_stReady)
st = 0.5m * (st - _prevSt) + _prevSt;
else
_stReady = true;
_prevSt = st;
AddValue(_st, st, CycleLength);
GetMinMax(_st, out var minSt, out var maxSt);
var previousStc = _prevStc;
var stc = maxSt == minSt ? previousStc : (st - minSt) / (maxSt - minSt) * 200m - 100m;
if (_stcReady)
stc = 0.5m * (stc - previousStc) + previousStc;
else
_stcReady = true;
_prevStc = stc;
var delta = stc - previousStc;
var longEntry = previousStc <= HighLevel && stc > HighLevel && delta > 0m;
var shortEntry = previousStc >= LowLevel && stc < LowLevel && delta < 0m;
var longExit = Position > 0 && stc < 0m;
var shortExit = Position < 0 && stc > 0m;
if (longExit)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (shortExit)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && longEntry && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && shortEntry && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
}
private decimal CalculateRawRvi()
{
var c0 = _recentCandles[0];
var c1 = _recentCandles[1];
var c2 = _recentCandles[2];
var c3 = _recentCandles[3];
var valueUp = ((c0.ClosePrice - c0.OpenPrice) +
2m * (c1.ClosePrice - c1.OpenPrice) +
2m * (c2.ClosePrice - c2.OpenPrice) +
(c3.ClosePrice - c3.OpenPrice)) / 6m;
var valueDn = ((c0.HighPrice - c0.LowPrice) +
2m * (c1.HighPrice - c1.LowPrice) +
2m * (c2.HighPrice - c2.LowPrice) +
(c3.HighPrice - c3.LowPrice)) / 6m;
return valueDn == 0m ? valueUp : valueUp / valueDn;
}
private static void UpdateWindow(Queue<decimal> window, ref decimal sum, decimal value, int length)
{
window.Enqueue(value);
sum += value;
while (window.Count > length)
sum -= window.Dequeue();
}
private static void AddValue(List<decimal> values, decimal value, int limit)
{
values.Add(value);
if (values.Count > limit)
values.RemoveAt(0);
}
private static void GetMinMax(List<decimal> values, out decimal min, out decimal max)
{
min = values[0];
max = values[0];
for (var i = 1; i < values.Count; i++)
{
var value = values[i];
if (value < min)
min = value;
if (value > max)
max = value;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from collections import deque
class color_schaff_rvi_trend_cycle_strategy(Strategy):
def __init__(self):
super(color_schaff_rvi_trend_cycle_strategy, self).__init__()
self._fast_rvi_length = self.Param("FastRviLength", 23) \
.SetDisplay("Fast RVI Length", "Smoothing length for fast RVI", "General")
self._slow_rvi_length = self.Param("SlowRviLength", 50) \
.SetDisplay("Slow RVI Length", "Smoothing length for slow RVI", "General")
self._cycle_length = self.Param("CycleLength", 10) \
.SetDisplay("Cycle", "Length of the stochastic cycle", "General")
self._high_level = self.Param("HighLevel", 60) \
.SetDisplay("High Level", "Upper threshold", "General")
self._low_level = self.Param("LowLevel", -60) \
.SetDisplay("Low Level", "Lower threshold", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 6) \
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._recent_candles = []
self._fast_window = deque()
self._slow_window = deque()
self._macd_history = []
self._st_history = []
self._fast_sum = 0.0
self._slow_sum = 0.0
self._st_ready = False
self._stc_ready = False
self._prev_st = 0.0
self._prev_stc = 0.0
self._cooldown_remaining = 0
@property
def FastRviLength(self):
return self._fast_rvi_length.Value
@FastRviLength.setter
def FastRviLength(self, value):
self._fast_rvi_length.Value = value
@property
def SlowRviLength(self):
return self._slow_rvi_length.Value
@SlowRviLength.setter
def SlowRviLength(self, value):
self._slow_rvi_length.Value = value
@property
def CycleLength(self):
return self._cycle_length.Value
@CycleLength.setter
def CycleLength(self, value):
self._cycle_length.Value = value
@property
def HighLevel(self):
return self._high_level.Value
@HighLevel.setter
def HighLevel(self, value):
self._high_level.Value = value
@property
def LowLevel(self):
return self._low_level.Value
@LowLevel.setter
def LowLevel(self, value):
self._low_level.Value = value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
@SignalCooldownBars.setter
def SignalCooldownBars(self, value):
self._signal_cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(color_schaff_rvi_trend_cycle_strategy, self).OnStarted2(time)
self._recent_candles = []
self._fast_window = deque()
self._slow_window = deque()
self._macd_history = []
self._st_history = []
self._fast_sum = 0.0
self._slow_sum = 0.0
self._st_ready = False
self._stc_ready = False
self._prev_st = 0.0
self._prev_stc = 0.0
self._cooldown_remaining = 0
self.SubscribeCandles(self.CandleType) \
.Bind(self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._recent_candles.append(candle)
if len(self._recent_candles) > 4:
self._recent_candles.pop(0)
if len(self._recent_candles) < 4:
return
raw_rvi = self._calculate_raw_rvi()
fast_len = self.FastRviLength
slow_len = self.SlowRviLength
cycle = self.CycleLength
self._fast_window.append(raw_rvi)
self._fast_sum += raw_rvi
while len(self._fast_window) > fast_len:
self._fast_sum -= self._fast_window.popleft()
self._slow_window.append(raw_rvi)
self._slow_sum += raw_rvi
while len(self._slow_window) > slow_len:
self._slow_sum -= self._slow_window.popleft()
if len(self._fast_window) < fast_len or len(self._slow_window) < slow_len:
return
fast = self._fast_sum / len(self._fast_window)
slow = self._slow_sum / len(self._slow_window)
macd = fast - slow
self._add_value(self._macd_history, macd, cycle)
if len(self._macd_history) < cycle:
return
min_macd, max_macd = self._get_min_max(self._macd_history)
if max_macd == min_macd:
st = self._prev_st
else:
st = (macd - min_macd) / (max_macd - min_macd) * 100.0
if self._st_ready:
st = 0.5 * (st - self._prev_st) + self._prev_st
else:
self._st_ready = True
self._prev_st = st
self._add_value(self._st_history, st, cycle)
min_st, max_st = self._get_min_max(self._st_history)
previous_stc = self._prev_stc
if max_st == min_st:
stc = previous_stc
else:
stc = (st - min_st) / (max_st - min_st) * 200.0 - 100.0
if self._stc_ready:
stc = 0.5 * (stc - previous_stc) + previous_stc
else:
self._stc_ready = True
self._prev_stc = stc
delta = stc - previous_stc
high = float(self.HighLevel)
low = float(self.LowLevel)
long_entry = previous_stc <= high and stc > high and delta > 0
short_entry = previous_stc >= low and stc < low and delta < 0
long_exit = self.Position > 0 and stc < 0
short_exit = self.Position < 0 and stc > 0
if long_exit:
self.SellMarket(self.Position)
self._cooldown_remaining = self.SignalCooldownBars
elif short_exit:
self.BuyMarket(abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and long_entry and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and short_entry and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
def _calculate_raw_rvi(self):
c0 = self._recent_candles[0]
c1 = self._recent_candles[1]
c2 = self._recent_candles[2]
c3 = self._recent_candles[3]
value_up = ((float(c0.ClosePrice) - float(c0.OpenPrice)) +
2.0 * (float(c1.ClosePrice) - float(c1.OpenPrice)) +
2.0 * (float(c2.ClosePrice) - float(c2.OpenPrice)) +
(float(c3.ClosePrice) - float(c3.OpenPrice))) / 6.0
value_dn = ((float(c0.HighPrice) - float(c0.LowPrice)) +
2.0 * (float(c1.HighPrice) - float(c1.LowPrice)) +
2.0 * (float(c2.HighPrice) - float(c2.LowPrice)) +
(float(c3.HighPrice) - float(c3.LowPrice))) / 6.0
if value_dn == 0:
return value_up
return value_up / value_dn
def _add_value(self, values, value, limit):
values.append(value)
if len(values) > limit:
values.pop(0)
def _get_min_max(self, values):
min_val = values[0]
max_val = values[0]
for i in range(1, len(values)):
val = values[i]
if val < min_val:
min_val = val
if val > max_val:
max_val = val
return min_val, max_val
def OnReseted(self):
super(color_schaff_rvi_trend_cycle_strategy, self).OnReseted()
self._recent_candles = []
self._fast_window = deque()
self._slow_window = deque()
self._macd_history = []
self._st_history = []
self._fast_sum = 0.0
self._slow_sum = 0.0
self._st_ready = False
self._stc_ready = False
self._prev_st = 0.0
self._prev_stc = 0.0
self._cooldown_remaining = 0
def CreateClone(self):
return color_schaff_rvi_trend_cycle_strategy()