Стратегия IBS RSI CCI v4 X2
Описание
IBS RSI CCI v4 X2 — портированная на StockSharp версия популярной MQ5 стратегии, построенной на сочетании индикаторов Internal Bar Strength (IBS), Relative Strength Index (RSI) и Commodity Channel Index (CCI). Стратегия работает в двух таймфреймах:
- медленный (по умолчанию 8 часов) задаёт направление торговли;
- быстрый (по умолчанию 1 час) формирует конкретные сигналы входа и выхода.
Для каждого таймфрейма рассчитывается композитный осциллятор. Значение осциллятора — это среднее арифметическое взвешенных IBS, RSI и CCI. Резкие скачки значения ограничиваются параметром-порогом, после чего данные проходят через диапазонную «обёртку» (максимум и минимум за окно с дополнительным сглаживанием). Сравнение текущего значения с усреднённой обёрткой и даёт сигналы пересечения.
Логика торговли
- Определение тренда. Если на медленном таймфрейме значение композитного индикатора выше сглаженной средней, стратегия считает, что на рынке восходящая тенденция, иначе — нисходящая.
- Получение сигнала. На быстром таймфрейме анализируются две последних завершённых свечи. Только если предыдущая свеча поддерживает направление пересечения, сигнал признаётся валидным.
- Открытие позиций.
- Длинная позиция открывается, когда разрешены покупки, тренд бычий, а осциллятор подтверждает разворот вверх (композит опускается ниже обёртки, как в оригинальном индикаторе).
- Короткая позиция открывается, когда разрешены продажи, тренд медвежий, а композит поднимается выше обёртки.
- Закрытие позиций.
- Флаги
_CloseLongOnSignalCrossи_CloseShortOnSignalCrossпозволяют закрывать позиции при обратном пересечении на быстром таймфрейме. _CloseLongOnTrendFlipи_CloseShortOnTrendFlipзакрывают позиции при смене тренда на медленном таймфрейме.- Защита позиции настраивается через
StartProtection— значения стоп-лосса и тейк-профита берутся в пунктах и автоматически переводятся в абсолютное смещение цены.
- Флаги
Параметры
| Параметр | Назначение |
|---|---|
OrderVolume |
Базовый объём сделки. При развороте позиция закрывается и открывается в обратном направлении одним ордером. |
TrendCandleType / SignalCandleType |
Тип свечей для трендового и сигнального таймфреймов. |
TrendIbsPeriod, SignalIbsPeriod |
Период сглаживания IBS. |
TrendIbsMaType, SignalIbsMaType |
Тип скользящей средней для сглаживания IBS (простая, экспоненциальная, сглаженная, взвешенная). |
TrendRsiPeriod, SignalRsiPeriod |
Период RSI. |
TrendRsiPrice, SignalRsiPrice |
Тип цены для расчёта RSI. |
TrendCciPeriod, SignalCciPeriod |
Период CCI. |
TrendCciPrice, SignalCciPrice |
Тип цены для расчёта CCI. |
TrendThreshold, SignalThreshold |
Порог ограничения резких изменений композитного индикатора. |
TrendRangePeriod, TrendSmoothPeriod |
Параметры диапазонной обёртки на медленном таймфрейме. |
SignalRangePeriod, SignalSmoothPeriod |
Параметры диапазонной обёртки на быстром таймфрейме. |
TrendSignalBar, SignalSignalBar |
Смещение по количеству завершённых свечей при чтении значений. |
AllowLongEntries, AllowShortEntries |
Разрешение на открытие длинных/коротких позиций. |
CloseLongOnTrendFlip, CloseShortOnTrendFlip |
Обязательное закрытие позиций при смене тренда. |
CloseLongOnSignalCross, CloseShortOnSignalCross |
Закрытие позиций при обратном пересечении на быстром таймфрейме. |
StopLossPoints, TakeProfitPoints |
Размер стоп-лосса и тейк-профита в пунктах (PriceStep). |
Практические рекомендации
- Перед запуском укажите инструмент и таймфреймы. Метод
GetWorkingSecuritiesавтоматически создаёт обе подписки. - Стартовые значения параметров полностью повторяют оригинальный MQ5 советник, что упрощает сравнение результатов.
- При повышенной волатильности уменьшайте
Thresholdи периоды обёртки для ускорения реакции, и наоборот. - Встроенный расчёт CCI требует непрерывного потока свечей. При пропуске данных проверьте корректность входного ряда.
- При наличии доступного окна графика стратегия отображает свечи сигнального таймфрейма и сделки, что помогает в отладке.
Ограничения и риски
- Стратегия не содержит ограничений по времени торговли, фильтров по объёму и т. п. При необходимости добавьте их вручную.
- Если у инструмента неизвестный шаг цены, стоп-лосс и тейк-профит могут работать некорректно; в коде предусмотрено минимальное значение по умолчанию.
- Переворот позиции выполняется одним рыночным ордером. Убедитесь, что торговая площадка допускает такое поведение.
- В математике композитного индикатора используются отрицательные коэффициенты, как и в оригинале. Поэтому на графике пересечения могут казаться инвертированными — это ожидаемое поведение.
Дальнейшее развитие
- Подберите разные комбинации таймфреймов и параметров, чтобы адаптировать стратегию под выбранный инструмент.
- Добавьте вывод значений композитного индикатора на график для визуального контроля.
- Расширьте блок управления рисками (дневные лимиты, трейлинг, частичное закрытие и т. д.).
- Используйте модуль оптимизации StockSharp для перебора параметров и сравнения результатов тестов.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
public class IbsRsiCciV4X2Strategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _trendCandleType;
private readonly StrategyParam<int> _trendIbsPeriod;
private readonly StrategyParam<IbsMovingAverageTypes> _trendIbsMaType;
private readonly StrategyParam<int> _trendRsiPeriod;
private readonly StrategyParam<AppliedPriceTypes> _trendRsiPrice;
private readonly StrategyParam<int> _trendCciPeriod;
private readonly StrategyParam<AppliedPriceTypes> _trendCciPrice;
private readonly StrategyParam<decimal> _trendThreshold;
private readonly StrategyParam<int> _trendRangePeriod;
private readonly StrategyParam<int> _trendSmoothPeriod;
private readonly StrategyParam<int> _trendSignalBar;
private readonly StrategyParam<bool> _allowLongEntries;
private readonly StrategyParam<bool> _allowShortEntries;
private readonly StrategyParam<bool> _closeLongOnTrendFlip;
private readonly StrategyParam<bool> _closeShortOnTrendFlip;
private readonly StrategyParam<decimal> _koefIbs;
private readonly StrategyParam<decimal> _koefRsi;
private readonly StrategyParam<decimal> _koefCci;
private readonly StrategyParam<decimal> _kibs;
private readonly StrategyParam<decimal> _kcci;
private readonly StrategyParam<decimal> _krsi;
private readonly StrategyParam<decimal> _posit;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<int> _signalIbsPeriod;
private readonly StrategyParam<IbsMovingAverageTypes> _signalIbsMaType;
private readonly StrategyParam<int> _signalRsiPeriod;
private readonly StrategyParam<AppliedPriceTypes> _signalRsiPrice;
private readonly StrategyParam<int> _signalCciPeriod;
private readonly StrategyParam<AppliedPriceTypes> _signalCciPrice;
private readonly StrategyParam<decimal> _signalThreshold;
private readonly StrategyParam<int> _signalRangePeriod;
private readonly StrategyParam<int> _signalSmoothPeriod;
private readonly StrategyParam<int> _signalSignalBar;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<bool> _closeLongOnSignalCross;
private readonly StrategyParam<bool> _closeShortOnSignalCross;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly List<IbsRsiCciValue> _trendValues = new();
private readonly List<IbsRsiCciValue> _signalValues = new();
private IbsRsiCciCalculator _trendCalculator;
private IbsRsiCciCalculator _signalCalculator;
private int _trendDirection;
private int _cooldownRemaining;
public IbsRsiCciV4X2Strategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume", "Trading");
_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Trend TF", "Trend timeframe", "Trend");
_trendIbsPeriod = Param(nameof(TrendIbsPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Trend IBS", "IBS smoothing period", "Trend");
_trendIbsMaType = Param(nameof(TrendIbsMaType), IbsMovingAverageTypes.Simple)
.SetDisplay("Trend IBS MA", "IBS smoothing type", "Trend");
_trendRsiPeriod = Param(nameof(TrendRsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Trend RSI", "RSI period", "Trend");
_trendRsiPrice = Param(nameof(TrendRsiPrice), AppliedPriceTypes.Close)
.SetDisplay("Trend RSI Price", "RSI price type", "Trend");
_trendCciPeriod = Param(nameof(TrendCciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Trend CCI", "CCI period", "Trend");
_trendCciPrice = Param(nameof(TrendCciPrice), AppliedPriceTypes.Median)
.SetDisplay("Trend CCI Price", "CCI price type", "Trend");
_trendThreshold = Param(nameof(TrendThreshold), 50m)
.SetGreaterThanZero()
.SetDisplay("Trend Threshold", "Momentum clamp threshold", "Trend");
_trendRangePeriod = Param(nameof(TrendRangePeriod), 25)
.SetGreaterThanZero()
.SetDisplay("Trend Range", "Range period", "Trend");
_trendSmoothPeriod = Param(nameof(TrendSmoothPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Trend Smooth", "Range smoothing period", "Trend");
_trendSignalBar = Param(nameof(TrendSignalBar), 1)
.SetNotNegative()
.SetDisplay("Trend Shift", "Shift used to read indicator", "Trend");
_allowLongEntries = Param(nameof(AllowLongEntries), true)
.SetDisplay("Allow Long", "Enable long entries", "Trading");
_allowShortEntries = Param(nameof(AllowShortEntries), true)
.SetDisplay("Allow Short", "Enable short entries", "Trading");
_closeLongOnTrendFlip = Param(nameof(CloseLongOnTrendFlip), true)
.SetDisplay("Close Long Trend", "Close longs on bearish trend", "Trading");
_closeShortOnTrendFlip = Param(nameof(CloseShortOnTrendFlip), true)
.SetDisplay("Close Short Trend", "Close shorts on bullish trend", "Trading");
_koefIbs = Param(nameof(KoefIbs), 7m)
.SetDisplay("IBS Weight", "Weight applied to the IBS component", "Weights")
;
_koefRsi = Param(nameof(KoefRsi), 9m)
.SetDisplay("RSI Weight", "Weight applied to the RSI component", "Weights")
;
_koefCci = Param(nameof(KoefCci), 1m)
.SetDisplay("CCI Weight", "Weight applied to the CCI component", "Weights")
;
_kibs = Param(nameof(Kibs), -1m)
.SetDisplay("IBS Direction", "Directional multiplier for the IBS input", "Weights")
;
_kcci = Param(nameof(Kcci), -1m)
.SetDisplay("CCI Direction", "Directional multiplier for the CCI input", "Weights")
;
_krsi = Param(nameof(Krsi), -1m)
.SetDisplay("RSI Direction", "Directional multiplier for the RSI input", "Weights")
;
_posit = Param(nameof(Posit), -1m)
.SetDisplay("Output Direction", "Directional multiplier for the composite output", "Weights")
;
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Signal TF", "Signal timeframe", "Signal");
_signalIbsPeriod = Param(nameof(SignalIbsPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Signal IBS", "IBS smoothing period", "Signal");
_signalIbsMaType = Param(nameof(SignalIbsMaType), IbsMovingAverageTypes.Simple)
.SetDisplay("Signal IBS MA", "IBS smoothing type", "Signal");
_signalRsiPeriod = Param(nameof(SignalRsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Signal RSI", "RSI period", "Signal");
_signalRsiPrice = Param(nameof(SignalRsiPrice), AppliedPriceTypes.Close)
.SetDisplay("Signal RSI Price", "RSI price type", "Signal");
_signalCciPeriod = Param(nameof(SignalCciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Signal CCI", "CCI period", "Signal");
_signalCciPrice = Param(nameof(SignalCciPrice), AppliedPriceTypes.Median)
.SetDisplay("Signal CCI Price", "CCI price type", "Signal");
_signalThreshold = Param(nameof(SignalThreshold), 50m)
.SetGreaterThanZero()
.SetDisplay("Signal Threshold", "Momentum clamp threshold", "Signal");
_signalRangePeriod = Param(nameof(SignalRangePeriod), 25)
.SetGreaterThanZero()
.SetDisplay("Signal Range", "Range period", "Signal");
_signalSmoothPeriod = Param(nameof(SignalSmoothPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Signal Smooth", "Range smoothing period", "Signal");
_signalSignalBar = Param(nameof(SignalSignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Shift", "Shift used to read indicator", "Signal");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 10)
.SetNotNegative()
.SetDisplay("Signal Cooldown", "Closed signal candles to wait before the next entry", "Signal");
_closeLongOnSignalCross = Param(nameof(CloseLongOnSignalCross), false)
.SetDisplay("Close Long Signal", "Close longs on bearish cross", "Signal");
_closeShortOnSignalCross = Param(nameof(CloseShortOnSignalCross), false)
.SetDisplay("Close Short Signal", "Close shorts on bullish cross", "Signal");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss in points", "Protection");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit in points", "Protection");
}
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
public DataType TrendCandleType
{
get => _trendCandleType.Value;
set => _trendCandleType.Value = value;
}
public int TrendIbsPeriod
{
get => _trendIbsPeriod.Value;
set => _trendIbsPeriod.Value = value;
}
public IbsMovingAverageTypes TrendIbsMaType
{
get => _trendIbsMaType.Value;
set => _trendIbsMaType.Value = value;
}
public int TrendRsiPeriod
{
get => _trendRsiPeriod.Value;
set => _trendRsiPeriod.Value = value;
}
public AppliedPriceTypes TrendRsiPrice
{
get => _trendRsiPrice.Value;
set => _trendRsiPrice.Value = value;
}
public int TrendCciPeriod
{
get => _trendCciPeriod.Value;
set => _trendCciPeriod.Value = value;
}
public AppliedPriceTypes TrendCciPrice
{
get => _trendCciPrice.Value;
set => _trendCciPrice.Value = value;
}
public decimal TrendThreshold
{
get => _trendThreshold.Value;
set => _trendThreshold.Value = value;
}
public int TrendRangePeriod
{
get => _trendRangePeriod.Value;
set => _trendRangePeriod.Value = value;
}
public int TrendSmoothPeriod
{
get => _trendSmoothPeriod.Value;
set => _trendSmoothPeriod.Value = value;
}
public int TrendSignalBar
{
get => _trendSignalBar.Value;
set => _trendSignalBar.Value = value;
}
public bool AllowLongEntries
{
get => _allowLongEntries.Value;
set => _allowLongEntries.Value = value;
}
public bool AllowShortEntries
{
get => _allowShortEntries.Value;
set => _allowShortEntries.Value = value;
}
public bool CloseLongOnTrendFlip
{
get => _closeLongOnTrendFlip.Value;
set => _closeLongOnTrendFlip.Value = value;
}
public bool CloseShortOnTrendFlip
{
get => _closeShortOnTrendFlip.Value;
set => _closeShortOnTrendFlip.Value = value;
}
public DataType SignalCandleType
{
get => _signalCandleType.Value;
set => _signalCandleType.Value = value;
}
public int SignalIbsPeriod
{
get => _signalIbsPeriod.Value;
set => _signalIbsPeriod.Value = value;
}
public IbsMovingAverageTypes SignalIbsMaType
{
get => _signalIbsMaType.Value;
set => _signalIbsMaType.Value = value;
}
public int SignalRsiPeriod
{
get => _signalRsiPeriod.Value;
set => _signalRsiPeriod.Value = value;
}
public AppliedPriceTypes SignalRsiPrice
{
get => _signalRsiPrice.Value;
set => _signalRsiPrice.Value = value;
}
public int SignalCciPeriod
{
get => _signalCciPeriod.Value;
set => _signalCciPeriod.Value = value;
}
public AppliedPriceTypes SignalCciPrice
{
get => _signalCciPrice.Value;
set => _signalCciPrice.Value = value;
}
public decimal SignalThreshold
{
get => _signalThreshold.Value;
set => _signalThreshold.Value = value;
}
public int SignalRangePeriod
{
get => _signalRangePeriod.Value;
set => _signalRangePeriod.Value = value;
}
public int SignalSmoothPeriod
{
get => _signalSmoothPeriod.Value;
set => _signalSmoothPeriod.Value = value;
}
public int SignalSignalBar
{
get => _signalSignalBar.Value;
set => _signalSignalBar.Value = value;
}
public bool CloseLongOnSignalCross
{
get => _closeLongOnSignalCross.Value;
set => _closeLongOnSignalCross.Value = value;
}
public bool CloseShortOnSignalCross
{
get => _closeShortOnSignalCross.Value;
set => _closeShortOnSignalCross.Value = value;
}
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Weight applied to the IBS component of the composite oscillator.
/// </summary>
public decimal KoefIbs
{
get => _koefIbs.Value;
set => _koefIbs.Value = value;
}
/// <summary>
/// Weight applied to the RSI component of the composite oscillator.
/// </summary>
public decimal KoefRsi
{
get => _koefRsi.Value;
set => _koefRsi.Value = value;
}
/// <summary>
/// Weight applied to the CCI component of the composite oscillator.
/// </summary>
public decimal KoefCci
{
get => _koefCci.Value;
set => _koefCci.Value = value;
}
/// <summary>
/// Directional multiplier applied to the IBS contribution.
/// </summary>
public decimal Kibs
{
get => _kibs.Value;
set => _kibs.Value = value;
}
/// <summary>
/// Directional multiplier applied to the CCI contribution.
/// </summary>
public decimal Kcci
{
get => _kcci.Value;
set => _kcci.Value = value;
}
/// <summary>
/// Directional multiplier applied to the RSI contribution.
/// </summary>
public decimal Krsi
{
get => _krsi.Value;
set => _krsi.Value = value;
}
/// <summary>
/// Directional multiplier applied to the final composite value.
/// </summary>
public decimal Posit
{
get => _posit.Value;
set => _posit.Value = value;
}
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> new[]
{
(Security, TrendCandleType),
(Security, SignalCandleType)
};
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trendValues.Clear();
_signalValues.Clear();
_trendDirection = 0;
_cooldownRemaining = 0;
_trendCalculator?.Reset();
_signalCalculator?.Reset();
_trendCalculator = null;
_signalCalculator = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var priceStep = Security?.PriceStep ?? 0.0001m;
_trendCalculator = new IbsRsiCciCalculator(
TrendIbsPeriod,
TrendIbsMaType,
TrendRsiPeriod,
TrendRsiPrice,
TrendCciPeriod,
TrendCciPrice,
TrendThreshold,
TrendRangePeriod,
TrendSmoothPeriod,
priceStep, KoefIbs, KoefRsi, KoefCci, Kibs, Kcci, Krsi, Posit);
_signalCalculator = new IbsRsiCciCalculator(
SignalIbsPeriod,
SignalIbsMaType,
SignalRsiPeriod,
SignalRsiPrice,
SignalCciPeriod,
SignalCciPrice,
SignalThreshold,
SignalRangePeriod,
SignalSmoothPeriod,
priceStep, KoefIbs, KoefRsi, KoefCci, Kibs, Kcci, Krsi, Posit);
var trendSubscription = SubscribeCandles(TrendCandleType);
trendSubscription.Bind(ProcessTrend).Start();
var signalSubscription = SubscribeCandles(SignalCandleType);
signalSubscription.Bind(ProcessSignal).Start();
if (TakeProfitPoints > 0 || StopLossPoints > 0)
{
var takeProfit = new Unit(TakeProfitPoints * priceStep, UnitTypes.Absolute);
var stopLoss = new Unit(StopLossPoints * priceStep, UnitTypes.Absolute);
StartProtection(stopLoss, takeProfit);
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, signalSubscription);
DrawOwnTrades(area);
}
}
private void ProcessTrend(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || _trendCalculator == null)
return;
var value = _trendCalculator.Process(candle);
if (value == null)
return;
_trendValues.Add(value.Value);
var maxCount = Math.Max(TrendSignalBar + 5, 32);
if (_trendValues.Count > maxCount)
_trendValues.RemoveAt(0);
if (_trendValues.Count <= TrendSignalBar)
return;
var index = _trendValues.Count - (TrendSignalBar + 1);
if (index < 0)
return;
var selected = _trendValues[index];
if (selected.Up > selected.Down)
_trendDirection = 1;
else if (selected.Up < selected.Down)
_trendDirection = -1;
else
_trendDirection = 0;
}
private void ProcessSignal(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || _signalCalculator == null)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var value = _signalCalculator.Process(candle);
if (value == null)
return;
_signalValues.Add(value.Value);
var maxCount = Math.Max(SignalSignalBar + 10, 48);
if (_signalValues.Count > maxCount)
_signalValues.RemoveAt(0);
if (_signalValues.Count <= SignalSignalBar + 1)
return;
var currentIndex = _signalValues.Count - (SignalSignalBar + 1);
var previousIndex = currentIndex - 1;
if (currentIndex < 0 || previousIndex < 0)
return;
var current = _signalValues[currentIndex];
var previous = _signalValues[previousIndex];
var closeLong = CloseLongOnSignalCross && previous.Up < previous.Down;
var closeShort = CloseShortOnSignalCross && previous.Up > previous.Down;
var openLong = false;
var openShort = false;
if (_trendDirection < 0)
{
if (CloseLongOnTrendFlip)
closeLong = true;
if (_cooldownRemaining == 0 && AllowShortEntries && current.Up >= current.Down && previous.Up < previous.Down)
openShort = true;
}
else if (_trendDirection > 0)
{
if (CloseShortOnTrendFlip)
closeShort = true;
if (_cooldownRemaining == 0 && AllowLongEntries && current.Up <= current.Down && previous.Up > previous.Down)
openLong = true;
}
var submitted = false;
if (closeLong && Position > 0)
{
CloseLong();
submitted = true;
}
if (closeShort && Position < 0)
{
CloseShort();
submitted = true;
}
if (openLong && Position <= 0 && AllowLongEntries)
{
EnterLong();
submitted = true;
}
else if (openShort && Position >= 0 && AllowShortEntries)
{
EnterShort();
submitted = true;
}
if (submitted)
_cooldownRemaining = SignalCooldownBars;
}
private void CloseLong()
{
if (Position <= 0)
return;
SellMarket();
}
private void CloseShort()
{
if (Position >= 0)
return;
BuyMarket();
}
private void EnterLong()
{
BuyMarket();
}
private void EnterShort()
{
SellMarket();
}
private readonly record struct IbsRsiCciValue(decimal Up, decimal Down);
private sealed class IbsRsiCciCalculator
{
private readonly decimal _koefIbs;
private readonly decimal _koefRsi;
private readonly decimal _koefCci;
private readonly decimal _kibs;
private readonly decimal _kcci;
private readonly decimal _krsi;
private readonly decimal _posit;
private readonly int _ibsPeriod;
private readonly AppliedPriceTypes _rsiPrice;
private readonly AppliedPriceTypes _cciPrice;
private readonly decimal _threshold;
private readonly decimal _priceStep;
private readonly DecimalLengthIndicator _ibsMa;
private readonly RelativeStrengthIndex _rsi;
private readonly CommodityChannelIndexCalculator _cci;
private readonly Highest _highest;
private readonly Lowest _lowest;
private readonly DecimalLengthIndicator _rangeHighMa;
private readonly DecimalLengthIndicator _rangeLowMa;
private decimal? _previousUp;
public IbsRsiCciCalculator(
int ibsPeriod,
IbsMovingAverageTypes ibsType,
int rsiPeriod,
AppliedPriceTypes rsiPrice,
int cciPeriod,
AppliedPriceTypes cciPrice,
decimal threshold,
int rangePeriod,
int smoothPeriod,
decimal priceStep,
decimal koefIbs,
decimal koefRsi,
decimal koefCci,
decimal kibs,
decimal kcci,
decimal krsi,
decimal posit)
{
_ibsPeriod = ibsPeriod;
_rsiPrice = rsiPrice;
_cciPrice = cciPrice;
_threshold = threshold;
_priceStep = priceStep;
_koefIbs = koefIbs;
_koefRsi = koefRsi;
_koefCci = koefCci;
_kibs = kibs;
_kcci = kcci;
_krsi = krsi;
_posit = posit;
_ibsMa = CreateMovingAverage(ibsType, ibsPeriod);
_rsi = new RelativeStrengthIndex { Length = rsiPeriod };
_cci = new CommodityChannelIndexCalculator(cciPeriod);
_highest = new Highest { Length = rangePeriod };
_lowest = new Lowest { Length = rangePeriod };
_rangeHighMa = CreateMovingAverage(IbsMovingAverageTypes.Smoothed, smoothPeriod);
_rangeLowMa = CreateMovingAverage(IbsMovingAverageTypes.Smoothed, smoothPeriod);
}
public IbsRsiCciValue? Process(ICandleMessage candle)
{
var range = Math.Abs(candle.HighPrice - candle.LowPrice);
if (range == 0m)
range = _priceStep;
if (range == 0m)
return null;
var ibsRaw = (candle.ClosePrice - candle.LowPrice) / range;
var ibsValue = _ibsMa.Process(new DecimalIndicatorValue(_ibsMa, ibsRaw, candle.OpenTime) { IsFinal = true });
if (!ibsValue.IsFinal)
return null;
var rsiInput = GetPrice(candle, _rsiPrice);
var rsiValue = _rsi.Process(new DecimalIndicatorValue(_rsi, rsiInput, candle.OpenTime) { IsFinal = true });
if (!rsiValue.IsFinal)
return null;
var cciInput = GetPrice(candle, _cciPrice);
var cciValue = _cci.Process(cciInput, candle.OpenTime, true);
if (cciValue == null)
return null;
var ibs = ibsValue.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var cci = cciValue.Value;
var sum = 0m;
sum += _kibs * (ibs - 0.5m) * 100m * _koefIbs;
sum += _kcci * cci * _koefCci;
sum += _krsi * (rsi - 50m) * _koefRsi;
sum /= 3m;
var target = _posit * sum;
var up = _previousUp ?? target;
var diff = target - up;
if (Math.Abs(diff) > _threshold)
{
if (diff > 0m)
up = target - _threshold;
else
up = target + _threshold;
}
else
{
up = target;
}
_previousUp = up;
var highestValue = _highest.Process(new DecimalIndicatorValue(_highest, up, candle.OpenTime) { IsFinal = true });
var lowestValue = _lowest.Process(new DecimalIndicatorValue(_lowest, up, candle.OpenTime) { IsFinal = true });
if (!highestValue.IsFinal || !lowestValue.IsFinal)
return null;
var highest = highestValue.GetValue<decimal>();
var lowest = lowestValue.GetValue<decimal>();
var highSmooth = _rangeHighMa.Process(new DecimalIndicatorValue(_rangeHighMa, highest, candle.OpenTime) { IsFinal = true });
var lowSmooth = _rangeLowMa.Process(new DecimalIndicatorValue(_rangeLowMa, lowest, candle.OpenTime) { IsFinal = true });
if (!highSmooth.IsFinal || !lowSmooth.IsFinal)
return null;
var upBand = highSmooth.GetValue<decimal>();
var lowBand = lowSmooth.GetValue<decimal>();
var signal = (upBand + lowBand) / 2m;
return new IbsRsiCciValue(up, signal);
}
public void Reset()
{
_previousUp = null;
_ibsMa.Reset();
_rsi.Reset();
_cci.Reset();
_highest.Reset();
_lowest.Reset();
_rangeHighMa.Reset();
_rangeLowMa.Reset();
}
private static DecimalLengthIndicator CreateMovingAverage(IbsMovingAverageTypes type, int length)
{
return type switch
{
IbsMovingAverageTypes.Simple => new SMA { Length = length },
IbsMovingAverageTypes.Exponential => new EMA { Length = length },
IbsMovingAverageTypes.Weighted => new WeightedMovingAverage { Length = length },
IbsMovingAverageTypes.Smoothed => new SmoothedMovingAverage { Length = length },
_ => new SMA { Length = length }
};
}
private static decimal GetPrice(ICandleMessage candle, AppliedPriceTypes type)
{
return type switch
{
AppliedPriceTypes.Close => candle.ClosePrice,
AppliedPriceTypes.Open => candle.OpenPrice,
AppliedPriceTypes.High => candle.HighPrice,
AppliedPriceTypes.Low => candle.LowPrice,
AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + candle.ClosePrice + candle.ClosePrice) / 4m,
_ => candle.ClosePrice
};
}
}
public enum IbsMovingAverageTypes
{
Simple,
Exponential,
Smoothed,
Weighted
}
public enum AppliedPriceTypes
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted
}
private sealed class CommodityChannelIndexCalculator
{
private readonly int _period;
private readonly SimpleMovingAverage _sma;
private readonly Queue<decimal> _buffer = new();
private readonly object _sync = new();
public CommodityChannelIndexCalculator(int period)
{
_period = period;
_sma = new SMA { Length = period };
}
public decimal? Process(decimal price, DateTimeOffset time, bool isFinal)
{
lock (_sync)
{
var maValue = _sma.Process(new DecimalIndicatorValue(_sma, price, time.UtcDateTime) { IsFinal = true });
_buffer.Enqueue(price);
if (_buffer.Count > _period)
_buffer.Dequeue();
if (!maValue.IsFinal || _buffer.Count < _period)
return null;
var ma = maValue.GetValue<decimal>();
var snapshot = _buffer.ToArray();
decimal sum = 0m;
foreach (var value in snapshot)
sum += Math.Abs(value - ma);
if (sum == 0m)
return 0m;
var meanDeviation = sum / _period;
if (meanDeviation == 0m)
return 0m;
var cci = (price - ma) / (0.015m * meanDeviation);
return cci;
}
}
public void Reset()
{
lock (_sync)
{
_buffer.Clear();
_sma.Reset();
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import (
RelativeStrengthIndex, SimpleMovingAverage,
ExponentialMovingAverage, SmoothedMovingAverage, WeightedMovingAverage,
Highest, Lowest)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
IBS_MA_SIMPLE = 0
IBS_MA_EXPONENTIAL = 1
IBS_MA_SMOOTHED = 2
IBS_MA_WEIGHTED = 3
APPLIED_CLOSE = 0
APPLIED_OPEN = 1
APPLIED_HIGH = 2
APPLIED_LOW = 3
APPLIED_MEDIAN = 4
APPLIED_TYPICAL = 5
APPLIED_WEIGHTED = 6
class IbsRsiCciCalculator(object):
def __init__(self, ibs_period, ibs_type, rsi_period, rsi_price, cci_period, cci_price,
threshold, range_period, smooth_period, price_step,
koef_ibs, koef_rsi, koef_cci, kibs, kcci, krsi, posit):
self._rsi_price = rsi_price
self._cci_price = cci_price
self._threshold = Decimal(float(threshold))
self._price_step = Decimal(float(price_step))
self._koef_ibs = Decimal(float(koef_ibs))
self._koef_rsi = Decimal(float(koef_rsi))
self._koef_cci = Decimal(float(koef_cci))
self._kibs = Decimal(float(kibs))
self._kcci = Decimal(float(kcci))
self._krsi = Decimal(float(krsi))
self._posit = Decimal(float(posit))
self._previous_up = None
self._ibs_ma = self._create_ma(ibs_type, ibs_period)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = rsi_period
self._cci_sma = SimpleMovingAverage()
self._cci_sma.Length = cci_period
self._cci_period = cci_period
self._cci_buffer = []
self._highest = Highest()
self._highest.Length = range_period
self._lowest = Lowest()
self._lowest.Length = range_period
self._range_high_ma = self._create_ma(IBS_MA_SMOOTHED, smooth_period)
self._range_low_ma = self._create_ma(IBS_MA_SMOOTHED, smooth_period)
def _create_ma(self, ma_type, length):
t = int(ma_type)
if t == IBS_MA_EXPONENTIAL:
ind = ExponentialMovingAverage()
elif t == IBS_MA_SMOOTHED:
ind = SmoothedMovingAverage()
elif t == IBS_MA_WEIGHTED:
ind = WeightedMovingAverage()
else:
ind = SimpleMovingAverage()
ind.Length = length
return ind
def _get_price(self, candle, price_type):
h = candle.HighPrice
l = candle.LowPrice
c = candle.ClosePrice
o = candle.OpenPrice
t = int(price_type)
if t == APPLIED_OPEN:
return o
elif t == APPLIED_HIGH:
return h
elif t == APPLIED_LOW:
return l
elif t == APPLIED_MEDIAN:
return Decimal.Divide(Decimal.Add(h, l), Decimal(2))
elif t == APPLIED_TYPICAL:
return Decimal.Divide(Decimal.Add(Decimal.Add(h, l), c), Decimal(3))
elif t == APPLIED_WEIGHTED:
return Decimal.Divide(Decimal.Add(Decimal.Add(Decimal.Add(h, l), c), c), Decimal(4))
else:
return c
def process(self, candle):
h = candle.HighPrice
l = candle.LowPrice
c = candle.ClosePrice
open_time = candle.OpenTime
bar_range = Math.Abs(Decimal.Subtract(h, l))
if bar_range == Decimal(0):
bar_range = self._price_step
if bar_range == Decimal(0):
return None
ibs_raw = Decimal.Divide(Decimal.Subtract(c, l), bar_range)
ibs_result = process_float(self._ibs_ma, ibs_raw, open_time, True)
if not ibs_result.IsFinal:
return None
rsi_input = self._get_price(candle, self._rsi_price)
rsi_result = process_float(self._rsi, rsi_input, open_time, True)
if not rsi_result.IsFinal:
return None
cci_input = self._get_price(candle, self._cci_price)
cci_value = self._process_cci(cci_input, open_time)
if cci_value is None:
return None
ibs = Decimal(float(ibs_result))
rsi = Decimal(float(rsi_result))
cci = cci_value
total = Decimal(0)
# sum += _kibs * (ibs - 0.5) * 100 * _koefIbs
total = Decimal.Add(total, Decimal.Multiply(Decimal.Multiply(Decimal.Multiply(self._kibs, Decimal.Subtract(ibs, Decimal(0.5))), Decimal(100)), self._koef_ibs))
# sum += _kcci * cci * _koefCci
total = Decimal.Add(total, Decimal.Multiply(Decimal.Multiply(self._kcci, cci), self._koef_cci))
# sum += _krsi * (rsi - 50) * _koefRsi
total = Decimal.Add(total, Decimal.Multiply(Decimal.Multiply(self._krsi, Decimal.Subtract(rsi, Decimal(50))), self._koef_rsi))
# sum /= 3
total = Decimal.Divide(total, Decimal(3))
target = Decimal.Multiply(self._posit, total)
up = self._previous_up if self._previous_up is not None else target
diff = Decimal.Subtract(target, up)
if Math.Abs(diff) > self._threshold:
if diff > Decimal(0):
up = Decimal.Subtract(target, self._threshold)
else:
up = Decimal.Add(target, self._threshold)
else:
up = target
self._previous_up = up
highest_result = process_float(self._highest, up, open_time, True)
lowest_result = process_float(self._lowest, up, open_time, True)
if not highest_result.IsFinal or not lowest_result.IsFinal:
return None
highest_val = Decimal(float(highest_result))
lowest_val = Decimal(float(lowest_result))
high_smooth = process_float(self._range_high_ma, highest_val, open_time, True)
low_smooth = process_float(self._range_low_ma, lowest_val, open_time, True)
if not high_smooth.IsFinal or not low_smooth.IsFinal:
return None
up_band = Decimal(float(high_smooth))
low_band = Decimal(float(low_smooth))
signal = Decimal.Divide(Decimal.Add(up_band, low_band), Decimal(2))
return (up, signal)
def _process_cci(self, price, open_time):
ma_result = process_float(self._cci_sma, price, open_time, True)
self._cci_buffer.append(price)
if len(self._cci_buffer) > self._cci_period:
self._cci_buffer.pop(0)
if not ma_result.IsFinal or len(self._cci_buffer) < self._cci_period:
return None
ma = Decimal(float(ma_result))
total = Decimal(0)
for v in self._cci_buffer:
total = Decimal.Add(total, Math.Abs(Decimal.Subtract(v, ma)))
if total == Decimal(0):
return Decimal(0)
mean_deviation = Decimal.Divide(total, Decimal(self._cci_period))
if mean_deviation == Decimal(0):
return Decimal(0)
return Decimal.Divide(Decimal.Subtract(price, ma), Decimal.Multiply(Decimal(0.015), mean_deviation))
def reset(self):
self._previous_up = None
self._ibs_ma.Reset()
self._rsi.Reset()
self._cci_sma.Reset()
self._cci_buffer = []
self._highest.Reset()
self._lowest.Reset()
self._range_high_ma.Reset()
self._range_low_ma.Reset()
class ibs_rsi_cci_v4_x2_strategy(Strategy):
def __init__(self):
super(ibs_rsi_cci_v4_x2_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", Decimal(1))
self._trend_candle_type = self.Param("TrendCandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._trend_ibs_period = self.Param("TrendIbsPeriod", 5)
self._trend_ibs_ma_type = self.Param("TrendIbsMaType", IBS_MA_SIMPLE)
self._trend_rsi_period = self.Param("TrendRsiPeriod", 14)
self._trend_rsi_price = self.Param("TrendRsiPrice", APPLIED_CLOSE)
self._trend_cci_period = self.Param("TrendCciPeriod", 14)
self._trend_cci_price = self.Param("TrendCciPrice", APPLIED_MEDIAN)
self._trend_threshold = self.Param("TrendThreshold", Decimal(50))
self._trend_range_period = self.Param("TrendRangePeriod", 25)
self._trend_smooth_period = self.Param("TrendSmoothPeriod", 3)
self._trend_signal_bar = self.Param("TrendSignalBar", 1)
self._allow_long_entries = self.Param("AllowLongEntries", True)
self._allow_short_entries = self.Param("AllowShortEntries", True)
self._close_long_on_trend_flip = self.Param("CloseLongOnTrendFlip", True)
self._close_short_on_trend_flip = self.Param("CloseShortOnTrendFlip", True)
self._koef_ibs = self.Param("KoefIbs", Decimal(7))
self._koef_rsi = self.Param("KoefRsi", Decimal(9))
self._koef_cci = self.Param("KoefCci", Decimal(1))
self._kibs = self.Param("Kibs", Decimal(-1))
self._kcci = self.Param("Kcci", Decimal(-1))
self._krsi = self.Param("Krsi", Decimal(-1))
self._posit = self.Param("Posit", Decimal(-1))
self._signal_candle_type = self.Param("SignalCandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._signal_ibs_period = self.Param("SignalIbsPeriod", 5)
self._signal_ibs_ma_type = self.Param("SignalIbsMaType", IBS_MA_SIMPLE)
self._signal_rsi_period = self.Param("SignalRsiPeriod", 14)
self._signal_rsi_price = self.Param("SignalRsiPrice", APPLIED_CLOSE)
self._signal_cci_period = self.Param("SignalCciPeriod", 14)
self._signal_cci_price = self.Param("SignalCciPrice", APPLIED_MEDIAN)
self._signal_threshold = self.Param("SignalThreshold", Decimal(50))
self._signal_range_period = self.Param("SignalRangePeriod", 25)
self._signal_smooth_period = self.Param("SignalSmoothPeriod", 3)
self._signal_signal_bar = self.Param("SignalSignalBar", 1)
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 10)
self._close_long_on_signal_cross = self.Param("CloseLongOnSignalCross", False)
self._close_short_on_signal_cross = self.Param("CloseShortOnSignalCross", False)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._trend_values = []
self._signal_values = []
self._trend_calculator = None
self._signal_calculator = None
self._trend_direction = 0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ibs_rsi_cci_v4_x2_strategy, self).OnStarted2(time)
price_step = self.Security.PriceStep if self.Security is not None and self.Security.PriceStep is not None else Decimal(0.0001)
self._trend_calculator = IbsRsiCciCalculator(
int(self._trend_ibs_period.Value), int(self._trend_ibs_ma_type.Value),
int(self._trend_rsi_period.Value), int(self._trend_rsi_price.Value),
int(self._trend_cci_period.Value), int(self._trend_cci_price.Value),
self._trend_threshold.Value, int(self._trend_range_period.Value), int(self._trend_smooth_period.Value),
price_step,
self._koef_ibs.Value, self._koef_rsi.Value, self._koef_cci.Value,
self._kibs.Value, self._kcci.Value, self._krsi.Value, self._posit.Value)
self._signal_calculator = IbsRsiCciCalculator(
int(self._signal_ibs_period.Value), int(self._signal_ibs_ma_type.Value),
int(self._signal_rsi_period.Value), int(self._signal_rsi_price.Value),
int(self._signal_cci_period.Value), int(self._signal_cci_price.Value),
self._signal_threshold.Value, int(self._signal_range_period.Value), int(self._signal_smooth_period.Value),
price_step,
self._koef_ibs.Value, self._koef_rsi.Value, self._koef_cci.Value,
self._kibs.Value, self._kcci.Value, self._krsi.Value, self._posit.Value)
self._trend_values = []
self._signal_values = []
self._trend_direction = 0
self._cooldown_remaining = 0
trend_sub = self.SubscribeCandles(self._trend_candle_type.Value)
trend_sub.Bind(self._process_trend).Start()
signal_sub = self.SubscribeCandles(self._signal_candle_type.Value)
signal_sub.Bind(self._process_signal).Start()
tp = int(self._take_profit_points.Value)
sl = int(self._stop_loss_points.Value)
if tp > 0 or sl > 0:
take_unit = Unit(Decimal.Multiply(Decimal(tp), price_step), UnitTypes.Absolute)
stop_unit = Unit(Decimal.Multiply(Decimal(sl), price_step), UnitTypes.Absolute)
self.StartProtection(stop_unit, take_unit)
def _process_trend(self, candle):
if candle.State != CandleStates.Finished:
return
if self._trend_calculator is None:
return
value = self._trend_calculator.process(candle)
if value is None:
return
self._trend_values.append(value)
tsb = int(self._trend_signal_bar.Value)
max_count = max(tsb + 5, 32)
if len(self._trend_values) > max_count:
self._trend_values.pop(0)
if len(self._trend_values) <= tsb:
return
index = len(self._trend_values) - (tsb + 1)
if index < 0:
return
selected = self._trend_values[index]
up_val = selected[0]
down_val = selected[1]
if up_val > down_val:
self._trend_direction = 1
elif up_val < down_val:
self._trend_direction = -1
else:
self._trend_direction = 0
def _process_signal(self, candle):
if candle.State != CandleStates.Finished:
return
if self._signal_calculator is None:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
value = self._signal_calculator.process(candle)
if value is None:
return
self._signal_values.append(value)
ssb = int(self._signal_signal_bar.Value)
max_count = max(ssb + 10, 48)
if len(self._signal_values) > max_count:
self._signal_values.pop(0)
if len(self._signal_values) <= ssb + 1:
return
current_index = len(self._signal_values) - (ssb + 1)
previous_index = current_index - 1
if current_index < 0 or previous_index < 0:
return
current = self._signal_values[current_index]
previous = self._signal_values[previous_index]
close_long = bool(self._close_long_on_signal_cross.Value) and previous[0] < previous[1]
close_short = bool(self._close_short_on_signal_cross.Value) and previous[0] > previous[1]
open_long = False
open_short = False
if self._trend_direction < 0:
if bool(self._close_long_on_trend_flip.Value):
close_long = True
if self._cooldown_remaining == 0 and bool(self._allow_short_entries.Value) and current[0] >= current[1] and previous[0] < previous[1]:
open_short = True
elif self._trend_direction > 0:
if bool(self._close_short_on_trend_flip.Value):
close_short = True
if self._cooldown_remaining == 0 and bool(self._allow_long_entries.Value) and current[0] <= current[1] and previous[0] > previous[1]:
open_long = True
submitted = False
if close_long and self.Position > 0:
self.SellMarket()
submitted = True
if close_short and self.Position < 0:
self.BuyMarket()
submitted = True
if open_long and self.Position <= 0 and bool(self._allow_long_entries.Value):
self.BuyMarket()
submitted = True
elif open_short and self.Position >= 0 and bool(self._allow_short_entries.Value):
self.SellMarket()
submitted = True
if submitted:
self._cooldown_remaining = int(self._signal_cooldown_bars.Value)
def OnReseted(self):
super(ibs_rsi_cci_v4_x2_strategy, self).OnReseted()
self._trend_values = []
self._signal_values = []
self._trend_direction = 0
self._cooldown_remaining = 0
if self._trend_calculator is not None:
self._trend_calculator.reset()
if self._signal_calculator is not None:
self._signal_calculator.reset()
self._trend_calculator = None
self._signal_calculator = None
def CreateClone(self):
return ibs_rsi_cci_v4_x2_strategy()