RCI-Strategie
Verwendet den Rank Correlation Index und seinen gleitenden Durchschnitt zum Handeln von Kreuzungen. Eine Long-Position wird eröffnet, wenn der RCI über seinen gleitenden Durchschnitt steigt. Eine Short-Position wird eröffnet, wenn er darunter fällt. Die Handelsrichtung kann auf nur Long oder nur Short beschränkt werden.
Details
- Einstiegskriterien: RCI kreuzt seinen gleitenden Durchschnitt.
- Long/Short: Konfigurierbar (beide, nur Long, nur Short).
- Ausstiegskriterien: Gegenläufige Kreuzung.
- Stops: Nein.
- Standardwerte:
RciLength= 10MaType= SMAMaLength= 14Direction= Long & ShortCandleType= TimeSpan.FromMinutes(1)
- Filter:
- Kategorie: Trend
- Richtung: Konfigurierbar
- Indikatoren: RCI, MA
- Stops: Nein
- Komplexität: Grundlegend
- Zeitrahmen: Intraday
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public enum RciTradeDirection
{
Both,
LongOnly,
ShortOnly,
}
public enum RciMaType
{
Simple,
Exponential,
}
/// <summary>
/// Rank Correlation Index crossover strategy.
/// </summary>
public class RciStrategy : Strategy
{
private readonly StrategyParam<int> _rciLength;
private readonly StrategyParam<RciMaType> _maType;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<RciTradeDirection> _direction;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = [];
private readonly List<decimal> _rciValues = [];
private decimal? _maEma;
private decimal? _previousRci;
private decimal? _previousMa;
public int RciLength { get => _rciLength.Value; set => _rciLength.Value = value; }
public RciMaType MaType { get => _maType.Value; set => _maType.Value = value; }
public int MaLength { get => _maLength.Value; set => _maLength.Value = value; }
public RciTradeDirection Direction { get => _direction.Value; set => _direction.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RciStrategy()
{
_rciLength = Param(nameof(RciLength), 10).SetGreaterThanZero();
_maType = Param(nameof(MaType), RciMaType.Simple);
_maLength = Param(nameof(MaLength), 14).SetGreaterThanZero();
_direction = Param(nameof(Direction), RciTradeDirection.Both);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_rciValues.Clear();
_maEma = null;
_previousRci = null;
_previousMa = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
if (_closes.Count > RciLength)
_closes.RemoveAt(0);
if (_closes.Count < RciLength)
return;
var rci = CalculateRci(_closes);
_rciValues.Add(rci);
if (_rciValues.Count > MaLength)
_rciValues.RemoveAt(0);
decimal ma;
if (MaType == RciMaType.Simple)
{
if (_rciValues.Count < MaLength)
return;
ma = _rciValues.Average();
}
else
{
_maEma = Ema(_maEma, rci, MaLength);
if (_rciValues.Count < MaLength)
return;
ma = _maEma.Value;
}
if (_previousRci is decimal previousRci && _previousMa is decimal previousMa)
{
if (previousRci <= previousMa && rci > ma)
ApplySignal(1);
else if (previousRci >= previousMa && rci < ma)
ApplySignal(-1);
}
_previousRci = rci;
_previousMa = ma;
}
private void ApplySignal(int signal)
{
var allowLong = Direction is RciTradeDirection.Both or RciTradeDirection.LongOnly;
var allowShort = Direction is RciTradeDirection.Both or RciTradeDirection.ShortOnly;
if (signal > 0)
{
if (Position < 0m)
BuyMarket(Math.Abs(Position) + (allowLong ? Volume : 0m));
else if (Position == 0m && allowLong)
BuyMarket();
}
else if (signal < 0)
{
if (Position > 0m)
SellMarket(Math.Abs(Position) + (allowShort ? Volume : 0m));
else if (Position == 0m && allowShort)
SellMarket();
}
}
internal static decimal CalculateRci(IReadOnlyList<decimal> values)
{
var n = values?.Count ?? 0;
if (n < 2)
return 0m;
var priceRanks = new decimal[n];
foreach (var group in values
.Select((value, index) => (value, index))
.OrderBy(x => x.value)
.GroupBy(x => x.value))
{
var ordered = group.ToArray();
var lower = values.Count(v => v < group.Key);
var firstRank = lower + 1m;
var lastRank = lower + ordered.Length;
var averageRank = (firstRank + lastRank) / 2m;
foreach (var item in ordered)
priceRanks[item.index] = averageRank;
}
var sumSquared = 0m;
for (var i = 0; i < n; i++)
{
var timeRank = i + 1m;
var diff = timeRank - priceRanks[i];
sumSquared += diff * diff;
}
return (1m - 6m * sumSquared / (n * (n * n - 1m))) * 100m;
}
private static decimal Ema(decimal? previous, decimal value, int period)
{
if (previous is null)
return value;
var alpha = 2m / (period + 1m);
return previous.Value + alpha * (value - previous.Value);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class rci_strategy(Strategy):
MA_SIMPLE = 0
MA_EXPONENTIAL = 1
DIR_BOTH = 0
DIR_LONG_ONLY = 1
DIR_SHORT_ONLY = 2
def __init__(self):
super(rci_strategy, self).__init__()
self._rci_length = self.Param("RciLength", 10).SetGreaterThanZero()
self._ma_type = self.Param("MaType", self.MA_SIMPLE)
self._ma_length = self.Param("MaLength", 14).SetGreaterThanZero()
self._direction = self.Param("Direction", self.DIR_BOTH)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._closes = []
self._rci_values = []
self._ma_ema = None
self._previous_rci = None
self._previous_ma = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(rci_strategy, self).OnReseted()
self._closes = []
self._rci_values = []
self._ma_ema = None
self._previous_rci = None
self._previous_ma = None
def OnStarted2(self, time):
super(rci_strategy, self).OnStarted2(time)
self.SubscribeCandles(self._candle_type.Value).Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
length = int(self._rci_length.Value)
self._closes.append(float(candle.ClosePrice))
if len(self._closes) > length:
del self._closes[0]
if len(self._closes) < length:
return
rci = self.calculate_rci(self._closes)
ma_length = int(self._ma_length.Value)
self._rci_values.append(rci)
if len(self._rci_values) > ma_length:
del self._rci_values[0]
if int(self._ma_type.Value) == self.MA_SIMPLE:
if len(self._rci_values) < ma_length:
return
ma = sum(self._rci_values) / float(ma_length)
else:
self._ma_ema = self._ema(self._ma_ema, rci, ma_length)
if len(self._rci_values) < ma_length:
return
ma = self._ma_ema
if self._previous_rci is not None and self._previous_ma is not None:
if self._previous_rci <= self._previous_ma and rci > ma:
self._apply_signal(1)
elif self._previous_rci >= self._previous_ma and rci < ma:
self._apply_signal(-1)
self._previous_rci = rci
self._previous_ma = ma
def _apply_signal(self, signal):
direction = int(self._direction.Value)
allow_long = direction in (self.DIR_BOTH, self.DIR_LONG_ONLY)
allow_short = direction in (self.DIR_BOTH, self.DIR_SHORT_ONLY)
if signal > 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position) + (self.Volume if allow_long else 0))
elif self.Position == 0 and allow_long:
self.BuyMarket()
elif signal < 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position) + (self.Volume if allow_short else 0))
elif self.Position == 0 and allow_short:
self.SellMarket()
@staticmethod
def calculate_rci(values):
n = len(values)
if n < 2:
return 0.0
indexed = sorted(enumerate(values), key=lambda item: item[1])
ranks = [0.0] * n
i = 0
while i < n:
j = i + 1
while j < n and indexed[j][1] == indexed[i][1]:
j += 1
average_rank = ((i + 1) + j) / 2.0
for k in range(i, j):
ranks[indexed[k][0]] = average_rank
i = j
sum_squared = 0.0
for i, rank in enumerate(ranks):
diff = (i + 1) - rank
sum_squared += diff * diff
return (1.0 - 6.0 * sum_squared / (n * (n * n - 1.0))) * 100.0
@staticmethod
def _ema(previous, value, period):
if previous is None:
return value
alpha = 2.0 / (period + 1.0)
return previous + alpha * (value - previous)
def CreateClone(self):
return rci_strategy()