Color Schaff RSI Trend Cycle Strategy
Trend-following system based on the Color Schaff RSI Trend Cycle (STC) oscillator. The strategy reacts to color transitions of the STC indicator to enter and exit both long and short positions.
Details
- Entry Criteria:
- Long: Indicator color two bars ago > 5 and last bar < 6.
- Short: Indicator color two bars ago < 2 and last bar > 1.
- Long/Short: Both directions.
- Exit Criteria:
- Long positions close when indicator color two bars ago < 2.
- Short positions close when indicator color two bars ago > 5.
- Indicators: Color Schaff RSI Trend Cycle.
- Default Values:
Fast RSI= 23Slow RSI= 50Cycle= 10High Level= 60Low Level= -60
- Timeframe: 4-hour candles by default.
- Stops: None.
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Single
- Stops: No
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy based on a Schaff-style cycle built from fast and slow RSI values.
/// </summary>
public class ColorSchaffRsiTrendCycleStrategy : Strategy
{
private readonly StrategyParam<int> _fastRsi;
private readonly StrategyParam<int> _slowRsi;
private readonly StrategyParam<int> _cycle;
private readonly StrategyParam<int> _highLevel;
private readonly StrategyParam<int> _lowLevel;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly List<decimal> _macdHistory = [];
private readonly List<decimal> _stHistory = [];
private RelativeStrengthIndex _fastIndicator = null!;
private RelativeStrengthIndex _slowIndicator = null!;
private decimal _prevStc;
private int? _prevColor;
private int _cooldownRemaining;
/// <summary>
/// Fast RSI period.
/// </summary>
public int FastRsi
{
get => _fastRsi.Value;
set => _fastRsi.Value = value;
}
/// <summary>
/// Slow RSI period.
/// </summary>
public int SlowRsi
{
get => _slowRsi.Value;
set => _slowRsi.Value = value;
}
/// <summary>
/// Cycle length used in the Schaff calculation.
/// </summary>
public int Cycle
{
get => _cycle.Value;
set => _cycle.Value = value;
}
/// <summary>
/// Upper level for color classification.
/// </summary>
public int HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Lower level for color classification.
/// </summary>
public int LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Candle type for subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bars to wait after each trading action.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ColorSchaffRsiTrendCycleStrategy"/>.
/// </summary>
public ColorSchaffRsiTrendCycleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculations", "General");
_fastRsi = Param(nameof(FastRsi), 23)
.SetGreaterThanZero()
.SetDisplay("Fast RSI", "Fast RSI period", "Parameters")
.SetOptimize(10, 30, 5);
_slowRsi = Param(nameof(SlowRsi), 50)
.SetGreaterThanZero()
.SetDisplay("Slow RSI", "Slow RSI period", "Parameters")
.SetOptimize(30, 70, 5);
_cycle = Param(nameof(Cycle), 10)
.SetGreaterThanZero()
.SetDisplay("Cycle", "Cycle length", "Parameters")
.SetOptimize(5, 20, 1);
_highLevel = Param(nameof(HighLevel), 60)
.SetDisplay("High Level", "Upper level for the cycle", "Parameters")
.SetOptimize(40, 80, 5);
_lowLevel = Param(nameof(LowLevel), -60)
.SetDisplay("Low Level", "Lower level for the cycle", "Parameters")
.SetOptimize(-80, -40, 5);
_signalCooldownBars = Param(nameof(SignalCooldownBars), 8)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trading actions", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastIndicator = null!;
_slowIndicator = null!;
_macdHistory.Clear();
_stHistory.Clear();
_prevStc = 0m;
_prevColor = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastIndicator = new RelativeStrengthIndex { Length = FastRsi };
_slowIndicator = new RelativeStrengthIndex { Length = SlowRsi };
_macdHistory.Clear();
_stHistory.Clear();
_prevStc = 0m;
_prevColor = null;
_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--;
var fastValue = _fastIndicator.Process(candle.ClosePrice, candle.OpenTime, true);
var slowValue = _slowIndicator.Process(candle.ClosePrice, candle.OpenTime, true);
if (!fastValue.IsFormed || !slowValue.IsFormed)
return;
var diff = fastValue.ToDecimal() - slowValue.ToDecimal();
AddValue(_macdHistory, diff, Cycle);
if (_macdHistory.Count < Cycle)
return;
GetMinMax(_macdHistory, out var macdMin, out var macdMax);
var previousSt = _stHistory.Count > 0 ? _stHistory[^1] : 0m;
var st = macdMax == macdMin ? previousSt : (diff - macdMin) / (macdMax - macdMin) * 100m;
AddValue(_stHistory, st, Cycle);
GetMinMax(_stHistory, out var stMin, out var stMax);
var stc = stMax == stMin ? _prevStc : (st - stMin) / (stMax - stMin) * 200m - 100m;
var delta = stc - _prevStc;
var color = GetColor(stc, delta);
if (_prevColor.HasValue && _cooldownRemaining == 0)
{
if (_prevColor.Value == 6 && color == 7 && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldownRemaining = SignalCooldownBars;
}
else if (_prevColor.Value == 1 && color == 0 && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldownRemaining = SignalCooldownBars;
}
else if (Position > 0 && color <= 1)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (Position < 0 && color >= 6)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
}
_prevColor = color;
_prevStc = stc;
}
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;
}
}
private int GetColor(decimal stc, decimal delta)
{
if (stc > 0m)
{
if (stc > HighLevel)
return delta >= 0m ? 7 : 6;
return delta >= 0m ? 5 : 4;
}
if (stc < LowLevel)
return delta < 0m ? 0 : 1;
return delta < 0m ? 2 : 3;
}
}
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.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class color_schaff_rsi_trend_cycle_strategy(Strategy):
def __init__(self):
super(color_schaff_rsi_trend_cycle_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles for calculations", "General")
self._fast_rsi = self.Param("FastRsi", 23) \
.SetDisplay("Fast RSI", "Fast RSI period", "Parameters")
self._slow_rsi = self.Param("SlowRsi", 50) \
.SetDisplay("Slow RSI", "Slow RSI period", "Parameters")
self._cycle = self.Param("Cycle", 10) \
.SetDisplay("Cycle", "Cycle length", "Parameters")
self._high_level = self.Param("HighLevel", 60) \
.SetDisplay("High Level", "Upper level for the cycle", "Parameters")
self._low_level = self.Param("LowLevel", -60) \
.SetDisplay("Low Level", "Lower level for the cycle", "Parameters")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 8) \
.SetDisplay("Signal Cooldown", "Bars to wait between trading actions", "Trading")
self._macd_history = []
self._st_history = []
self._fast_indicator = None
self._slow_indicator = None
self._prev_stc = 0.0
self._prev_color = None
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastRsi(self):
return self._fast_rsi.Value
@FastRsi.setter
def FastRsi(self, value):
self._fast_rsi.Value = value
@property
def SlowRsi(self):
return self._slow_rsi.Value
@SlowRsi.setter
def SlowRsi(self, value):
self._slow_rsi.Value = value
@property
def Cycle(self):
return self._cycle.Value
@Cycle.setter
def Cycle(self, value):
self._cycle.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
def OnStarted2(self, time):
super(color_schaff_rsi_trend_cycle_strategy, self).OnStarted2(time)
self._fast_indicator = RelativeStrengthIndex()
self._fast_indicator.Length = self.FastRsi
self._slow_indicator = RelativeStrengthIndex()
self._slow_indicator.Length = self.SlowRsi
self._macd_history = []
self._st_history = []
self._prev_stc = 0.0
self._prev_color = None
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
close = float(candle.ClosePrice)
fast_result = process_float(self._fast_indicator, candle.ClosePrice, candle.OpenTime, True)
slow_result = process_float(self._slow_indicator, candle.ClosePrice, candle.OpenTime, True)
if not fast_result.IsFormed or not slow_result.IsFormed:
return
diff = float(fast_result) - float(slow_result)
cycle = self.Cycle
self._add_value(self._macd_history, diff, cycle)
if len(self._macd_history) < cycle:
return
macd_min, macd_max = self._get_min_max(self._macd_history)
previous_st = self._st_history[-1] if len(self._st_history) > 0 else 0.0
if macd_max == macd_min:
st = previous_st
else:
st = (diff - macd_min) / (macd_max - macd_min) * 100.0
self._add_value(self._st_history, st, cycle)
st_min, st_max = self._get_min_max(self._st_history)
if st_max == st_min:
stc = self._prev_stc
else:
stc = (st - st_min) / (st_max - st_min) * 200.0 - 100.0
delta = stc - self._prev_stc
color = self._get_color(stc, delta)
if self._prev_color is not None and self._cooldown_remaining == 0:
if self._prev_color == 6 and color == 7 and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown_remaining = self.SignalCooldownBars
elif self._prev_color == 1 and color == 0 and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown_remaining = self.SignalCooldownBars
elif self.Position > 0 and color <= 1:
self.SellMarket(self.Position)
self._cooldown_remaining = self.SignalCooldownBars
elif self.Position < 0 and color >= 6:
self.BuyMarket(abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
self._prev_color = color
self._prev_stc = stc
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 _get_color(self, stc, delta):
high = float(self.HighLevel)
low = float(self.LowLevel)
if stc > 0:
if stc > high:
return 7 if delta >= 0 else 6
return 5 if delta >= 0 else 4
if stc < low:
return 0 if delta < 0 else 1
return 2 if delta < 0 else 3
def OnReseted(self):
super(color_schaff_rsi_trend_cycle_strategy, self).OnReseted()
self._fast_indicator = None
self._slow_indicator = None
self._macd_history = []
self._st_history = []
self._prev_stc = 0.0
self._prev_color = None
self._cooldown_remaining = 0
def CreateClone(self):
return color_schaff_rsi_trend_cycle_strategy()