SMI Correct Strategy
Overview
SMI Correct Strategy implements a trading system based on the Stochastic Momentum Index (SMI) indicator. The strategy watches the SMI line and its moving average signal line. A long position is opened when the SMI crosses below the signal line. A short position is opened when the SMI crosses above the signal line.
Parameters
- Candle Type – time frame of candles used for calculations.
- SMI Length – number of periods for the Stochastic calculation.
- Signal Length – smoothing period for the signal line.
How it works
- The strategy subscribes to candles of the specified type.
- For each finished candle, it updates the Stochastic oscillator and the signal moving average.
- When the SMI crosses below the signal line, any short position is closed and a long position is opened.
- When the SMI crosses above the signal line, any long position is closed and a short position is opened.
The sample also draws candles and indicator lines on a chart for visualization.
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>
/// Strategy based on Stochastic Momentum Index crossings.
/// Uses Stochastic %K with a signal line (SMA of %K).
/// Buys when K crosses above signal, sells when K crosses below.
/// </summary>
public class SmiCorrectStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smiLength;
private readonly StrategyParam<int> _signalLength;
private StochasticOscillator _stochastic;
private SimpleMovingAverage _signal;
private decimal? _prevSmi;
private decimal? _prevSignal;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmiLength
{
get => _smiLength.Value;
set => _smiLength.Value = value;
}
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
public SmiCorrectStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_smiLength = Param(nameof(SmiLength), 13)
.SetGreaterThanZero()
.SetDisplay("SMI Length", "Period for SMI calculation", "SMI");
_signalLength = Param(nameof(SignalLength), 5)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Smoothing period", "SMI");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stochastic = null;
_signal = null;
_prevSmi = null;
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSmi = null;
_prevSignal = null;
_stochastic = new StochasticOscillator
{
K = { Length = SmiLength },
D = { Length = 1 }
};
_signal = new SimpleMovingAverage { Length = SignalLength };
Indicators.Add(_stochastic);
Indicators.Add(_signal);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandleNew)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandleNew(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var stochResult = _stochastic.Process(candle);
if (!_stochastic.IsFormed)
return;
var stochTyped = (StochasticOscillatorValue)stochResult;
if (stochTyped.K is not decimal k)
return;
var signalResult = _signal.Process(new DecimalIndicatorValue(_signal, k, candle.OpenTime) { IsFinal = true });
if (!_signal.IsFormed)
{
_prevSmi = k;
return;
}
var signal = signalResult.ToDecimal();
if (_prevSmi is null || _prevSignal is null)
{
_prevSmi = k;
_prevSignal = signal;
return;
}
var crossUp = _prevSmi < _prevSignal && k >= signal;
var crossDown = _prevSmi > _prevSignal && k <= signal;
if (crossUp && Position == 0)
BuyMarket();
else if (crossDown && Position == 0)
SellMarket();
_prevSmi = k;
_prevSignal = signal;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import StochasticOscillator, SimpleMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class smi_correct_strategy(Strategy):
def __init__(self):
super(smi_correct_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._smi_length = self.Param("SmiLength", 13) \
.SetDisplay("SMI Length", "Period for SMI calculation", "SMI")
self._signal_length = self.Param("SignalLength", 5) \
.SetDisplay("Signal Length", "Smoothing period", "SMI")
self._stochastic = None
self._signal = None
self._prev_smi = None
self._prev_signal = None
@property
def candle_type(self):
return self._candle_type.Value
@property
def smi_length(self):
return self._smi_length.Value
@property
def signal_length(self):
return self._signal_length.Value
def OnReseted(self):
super(smi_correct_strategy, self).OnReseted()
self._stochastic = None
self._signal = None
self._prev_smi = None
self._prev_signal = None
def OnStarted2(self, time):
super(smi_correct_strategy, self).OnStarted2(time)
self._prev_smi = None
self._prev_signal = None
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.smi_length
self._stochastic.D.Length = 1
self._signal = SimpleMovingAverage()
self._signal.Length = self.signal_length
self.Indicators.Add(self._stochastic)
self.Indicators.Add(self._signal)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._stochastic)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
stoch_input = CandleIndicatorValue(self._stochastic, candle)
stoch_input.IsFinal = True
stoch_result = self._stochastic.Process(stoch_input)
if not self._stochastic.IsFormed:
return
k = stoch_result.K
if k is None:
return
k = float(k)
signal_result = process_float(self._signal, k, candle.OpenTime, True)
if not self._signal.IsFormed:
self._prev_smi = k
return
signal = float(signal_result)
if self._prev_smi is None or self._prev_signal is None:
self._prev_smi = k
self._prev_signal = signal
return
cross_up = self._prev_smi < self._prev_signal and k >= signal
cross_down = self._prev_smi > self._prev_signal and k <= signal
if cross_up and self.Position == 0:
self.BuyMarket()
elif cross_down and self.Position == 0:
self.SellMarket()
self._prev_smi = k
self._prev_signal = signal
def CreateClone(self):
return smi_correct_strategy()