Стратегия RCI
Стратегия использует индекс ранговой корреляции (RCI) и его скользящую среднюю для торговли на пересечениях. Длинная позиция открывается, когда RCI пересекает свою среднюю снизу вверх. Короткая позиция открывается при обратном пересечении. Направление торговли можно ограничить только длинными или только короткими сделками.
Детали
- Критерии входа: пересечение RCI и его средней.
- Длинные/короткие: настраиваемо (оба, только лонг, только шорт).
- Критерии выхода: противоположное пересечение.
- Стопы: нет.
- Значения по умолчанию:
RciLength= 10MaType= SMAMaLength= 14Direction= Long & ShortCandleType= TimeSpan.FromMinutes(1)
- Фильтры:
- Категория: Тренд
- Направление: Настраиваемое
- Индикаторы: RCI, MA
- Стопы: Нет
- Сложность: Базовая
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()