Стратегия SMI Correct
Обзор
Стратегия SMI Correct реализует торговую систему на основе индикатора Stochastic Momentum Index (SMI). Стратегия отслеживает линию SMI и её сигнальную скользящую среднюю. Длинная позиция открывается при пересечении SMI ниже сигнальной линии. Короткая позиция открывается при пересечении SMI выше сигнальной линии.
Параметры
- Candle Type – таймфрейм используемых свечей.
- SMI Length – количество периодов для расчёта SMI.
- Signal Length – период сглаживания сигнальной линии.
Как это работает
- Стратегия подписывается на свечи выбранного таймфрейма.
- Для каждой завершённой свечи обновляются значения стохастика и сигнальной средней.
- При пересечении SMI вниз под сигнальную линию закрывается короткая позиция и открывается длинная.
- При пересечении SMI вверх над сигнальной линией закрывается длинная позиция и открывается короткая.
Пример также отображает свечи и индикаторы на графике.
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()