Knux Multi-Indicator Strategy
This strategy blends trend strength and momentum oscillators to trade breakouts. It waits for a bullish or bearish crossover of two moving averages while the Average Directional Index (ADX) signals a strong trend. The Relative Vigor Index (RVI), Commodity Channel Index (CCI) and Williams %R act as filters to ensure momentum confirms the move and that the market is not overextended.
The system can open both long and short positions. It holds the position until an opposite signal appears and does not use a dedicated stop-loss. All parameters such as indicator periods and thresholds are configurable.
Details
- Entry Criteria:
- Long: Fast SMA crosses above slow SMA,
ADX > AdxLevel, RVI rising, CCI < -CciLevel, and WPR <= -100 + WprBuyRange.
- Short: Fast SMA crosses below slow SMA,
ADX > AdxLevel, RVI falling, CCI > CciLevel, and WPR >= -WprSellRange.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal (crossover in the other direction).
- Stops: No explicit stop-loss.
- Default Values:
FastMaLength = 5
SlowMaLength = 20
AdxPeriod = 14
AdxLevel = 15
RviPeriod = 20
CciPeriod = 40
CciLevel = 150
WprPeriod = 60
WprBuyRange = 15
WprSellRange = 15
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Multiple
- Stops: None
- Complexity: Medium
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Knux multi-indicator strategy.
/// Combines CCI, Williams %R and a moving average crossover for entry signals.
/// </summary>
public class KnuxStrategy : Strategy
{
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _slowMa;
private CommodityChannelIndex _cci;
private WilliamsR _wpr;
private decimal _prevFast;
private decimal _prevSlow;
private bool _isInitialized;
public int FastMaLength { get => _fastMaLength.Value; set => _fastMaLength.Value = value; }
public int SlowMaLength { get => _slowMaLength.Value; set => _slowMaLength.Value = value; }
public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public KnuxStrategy()
{
_fastMaLength = Param(nameof(FastMaLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Period of fast moving average", "General");
_slowMaLength = Param(nameof(SlowMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Slow MA Length", "Period of slow moving average", "General");
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI calculation period", "General");
_wprPeriod = Param(nameof(WprPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("WPR Period", "Williams %R period", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_prevFast = _prevSlow = 0m;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastMa = new SimpleMovingAverage { Length = FastMaLength };
_slowMa = new SimpleMovingAverage { Length = SlowMaLength };
_cci = new CommodityChannelIndex { Length = CciPeriod };
_wpr = new WilliamsR { Length = WprPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, _slowMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast)
{
if (candle.State != CandleStates.Finished)
return;
var slowResult = _slowMa.Process(candle.ClosePrice, candle.OpenTime, true);
var cciResult = _cci.Process(candle);
var wprResult = _wpr.Process(candle);
if (!slowResult.IsFormed)
{
_prevFast = fast;
return;
}
var slow = slowResult.ToDecimal();
if (!_isInitialized)
{
_prevFast = fast;
_prevSlow = slow;
_isInitialized = true;
return;
}
var crossUp = _prevFast <= _prevSlow && fast > slow;
var crossDown = _prevFast >= _prevSlow && fast < slow;
if (cciResult.IsFormed && wprResult.IsFormed)
{
if (crossUp && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (crossDown && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
else
{
// Fallback without CCI/WPR
if (crossUp && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (crossDown && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
_prevFast = fast;
_prevSlow = slow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, CommodityChannelIndex, WilliamsR, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class knux_strategy(Strategy):
"""
Knux multi-indicator: SMA crossover with CCI and Williams %R confirmation.
"""
def __init__(self):
super(knux_strategy, self).__init__()
self._fast_ma_length = self.Param("FastMaLength", 5) \
.SetDisplay("Fast MA Length", "Period of fast MA", "General")
self._slow_ma_length = self.Param("SlowMaLength", 20) \
.SetDisplay("Slow MA Length", "Period of slow MA", "General")
self._cci_period = self.Param("CciPeriod", 20) \
.SetDisplay("CCI Period", "CCI calculation period", "General")
self._wpr_period = self.Param("WprPeriod", 14) \
.SetDisplay("WPR Period", "Williams %R period", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._slow_ma = None
self._cci = None
self._wpr = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(knux_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
def OnStarted2(self, time):
super(knux_strategy, self).OnStarted2(time)
fast_ma = SimpleMovingAverage()
fast_ma.Length = self._fast_ma_length.Value
self._slow_ma = SimpleMovingAverage()
self._slow_ma.Length = self._slow_ma_length.Value
self._cci = CommodityChannelIndex()
self._cci.Length = self._cci_period.Value
self._wpr = WilliamsR()
self._wpr.Length = self._wpr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, self._slow_ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow_result = process_float(self._slow_ma, candle.ClosePrice, candle.OpenTime, True)
cci_inp = CandleIndicatorValue(self._cci, candle)
self._cci.Process(cci_inp)
wpr_inp = CandleIndicatorValue(self._wpr, candle)
self._wpr.Process(wpr_inp)
if not slow_result.IsFormed:
self._prev_fast = fast
return
slow = float(slow_result)
if not self._is_initialized:
self._prev_fast = fast
self._prev_slow = slow
self._is_initialized = True
return
cross_up = self._prev_fast <= self._prev_slow and fast > slow
cross_down = self._prev_fast >= self._prev_slow and fast < slow
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return knux_strategy()