Стратегия Color Schaff Momentum Trend Cycle
Стратегия использует индикатор Color Schaff Momentum Trend Cycle (STC) для определения разворотов тренда, когда индикатор выходит из зон перекупленности или перепроданности.
Подробности
- Условия входа:
- Покупка, когда цвет STC на предыдущем баре был выше верхней зоны (>5), а на текущем баре опускается ниже 6; при этом закрываются короткие позиции.
- Продажа, когда цвет STC на предыдущем баре был ниже нижней зоны (<2), а на текущем баре поднимается выше 1; при этом закрываются длинные позиции.
- Направление: Лонг и шорт.
- Условия выхода: Обратный сигнал закрывает противоположную позицию.
- Стопы: Нет явного стоп-лосса или тейк-профита.
- Параметры по умолчанию:
FastMomentum= 23SlowMomentum= 50Cycle= 10HighLevel= 60LowLevel= -60BuyPosOpen= trueSellPosOpen= trueBuyPosClose= trueSellPosClose= true
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Color Schaff momentum trend cycle strategy based on fast and slow momentum.
/// </summary>
public class ColorSchaffMomentumTrendCycleStrategy : Strategy
{
private readonly StrategyParam<int> _fastMomentumLength;
private readonly StrategyParam<int> _slowMomentumLength;
private readonly StrategyParam<int> _cycle;
private readonly StrategyParam<int> _highLevel;
private readonly StrategyParam<int> _lowLevel;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _macdHistory = new();
private readonly List<decimal> _stHistory = new();
private Momentum _fastMomentum;
private Momentum _slowMomentum;
private decimal _prevStc;
private int? _prevColor;
private int _cooldownRemaining;
public int FastMomentum { get => _fastMomentumLength.Value; set => _fastMomentumLength.Value = value; }
public int SlowMomentum { get => _slowMomentumLength.Value; set => _slowMomentumLength.Value = value; }
public int Cycle { get => _cycle.Value; set => _cycle.Value = value; }
public int HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
public int LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public bool BuyPosOpen { get => _buyPosOpen.Value; set => _buyPosOpen.Value = value; }
public bool SellPosOpen { get => _sellPosOpen.Value; set => _sellPosOpen.Value = value; }
public bool BuyPosClose { get => _buyPosClose.Value; set => _buyPosClose.Value = value; }
public bool SellPosClose { get => _sellPosClose.Value; set => _sellPosClose.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorSchaffMomentumTrendCycleStrategy()
{
_fastMomentumLength = Param(nameof(FastMomentum), 23)
.SetDisplay("Fast Momentum", "Fast momentum length", "Indicator");
_slowMomentumLength = Param(nameof(SlowMomentum), 50)
.SetDisplay("Slow Momentum", "Slow momentum length", "Indicator");
_cycle = Param(nameof(Cycle), 10)
.SetDisplay(nameof(Cycle), "Cycle length", "Indicator");
_highLevel = Param(nameof(HighLevel), 60)
.SetDisplay("High Level", "Upper threshold", "Indicator");
_lowLevel = Param(nameof(LowLevel), -60)
.SetDisplay("Low Level", "Lower threshold", "Indicator");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
_buyPosOpen = Param(nameof(BuyPosOpen), true).SetDisplay("Enable Long", "Allow long entries", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true).SetDisplay("Enable Short", "Allow short entries", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true).SetDisplay("Close Long", "Allow closing long positions", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true).SetDisplay("Close Short", "Allow closing short positions", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Candles timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMomentum = null;
_slowMomentum = null;
_macdHistory.Clear();
_stHistory.Clear();
_prevStc = 0m;
_prevColor = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMomentum = new Momentum { Length = FastMomentum };
_slowMomentum = new Momentum { Length = SlowMomentum };
_macdHistory.Clear();
_stHistory.Clear();
_prevStc = 0m;
_prevColor = null;
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var fastValue = _fastMomentum.Process(candle.ClosePrice, candle.OpenTime, true);
var slowValue = _slowMomentum.Process(candle.ClosePrice, candle.OpenTime, true);
if (!fastValue.IsFormed || !slowValue.IsFormed)
return;
var diff = fastValue.ToDecimal() - slowValue.ToDecimal();
AddValue(_macdHistory, diff, Cycle);
if (_macdHistory.Count < Cycle)
return;
GetMinMax(_macdHistory, out var macdMin, out var macdMax);
var previousSt = _stHistory.Count > 0 ? _stHistory[^1] : 0m;
var st = macdMax == macdMin ? previousSt : (diff - macdMin) / (macdMax - macdMin) * 100m;
AddValue(_stHistory, st, Cycle);
GetMinMax(_stHistory, out var stMin, out var stMax);
var stc = stMax == stMin ? _prevStc : (st - stMin) / (stMax - stMin) * 200m - 100m;
var delta = stc - _prevStc;
var color = GetColor(stc, delta);
if (_prevColor.HasValue && _cooldownRemaining == 0)
{
if (_prevColor.Value == 6 && color == 7 && BuyPosOpen && Position <= 0)
{
if (Position < 0 && SellPosClose)
BuyMarket(Math.Abs(Position));
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (_prevColor.Value == 1 && color == 0 && SellPosOpen && Position >= 0)
{
if (Position > 0 && BuyPosClose)
SellMarket(Position);
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (Position > 0 && BuyPosClose && color <= 1)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (Position < 0 && SellPosClose && color >= 6)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
}
_prevColor = color;
_prevStc = stc;
}
private static void AddValue(List<decimal> values, decimal value, int limit)
{
values.Add(value);
if (values.Count > limit)
values.RemoveAt(0);
}
private static void GetMinMax(List<decimal> values, out decimal min, out decimal max)
{
min = values[0];
max = values[0];
for (var i = 1; i < values.Count; i++)
{
var value = values[i];
if (value < min)
min = value;
if (value > max)
max = value;
}
}
private int GetColor(decimal stc, decimal delta)
{
if (stc > 0m)
{
if (stc > HighLevel)
return delta >= 0m ? 7 : 6;
return delta >= 0m ? 5 : 4;
}
if (stc < LowLevel)
return delta < 0m ? 0 : 1;
return delta < 0m ? 2 : 3;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Momentum
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class color_schaff_momentum_trend_cycle_strategy(Strategy):
def __init__(self):
super(color_schaff_momentum_trend_cycle_strategy, self).__init__()
self._fast_momentum_length = self.Param("FastMomentum", 23) \
.SetDisplay("Fast Momentum", "Fast momentum length", "Indicator")
self._slow_momentum_length = self.Param("SlowMomentum", 50) \
.SetDisplay("Slow Momentum", "Slow momentum length", "Indicator")
self._cycle = self.Param("Cycle", 10) \
.SetDisplay("Cycle", "Cycle length", "Indicator")
self._high_level = self.Param("HighLevel", 60) \
.SetDisplay("High Level", "Upper threshold", "Indicator")
self._low_level = self.Param("LowLevel", -60) \
.SetDisplay("Low Level", "Lower threshold", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 12) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Enable Long", "Allow long entries", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Enable Short", "Allow short entries", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Close Long", "Allow closing long positions", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Close Short", "Allow closing short positions", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles timeframe", "General")
self._macd_history = []
self._st_history = []
self._fast_momentum = None
self._slow_momentum = None
self._prev_stc = 0.0
self._prev_color = None
self._cooldown_remaining = 0
@property
def FastMomentum(self):
return self._fast_momentum_length.Value
@FastMomentum.setter
def FastMomentum(self, value):
self._fast_momentum_length.Value = value
@property
def SlowMomentum(self):
return self._slow_momentum_length.Value
@SlowMomentum.setter
def SlowMomentum(self, value):
self._slow_momentum_length.Value = value
@property
def Cycle(self):
return self._cycle.Value
@Cycle.setter
def Cycle(self, value):
self._cycle.Value = value
@property
def HighLevel(self):
return self._high_level.Value
@HighLevel.setter
def HighLevel(self, value):
self._high_level.Value = value
@property
def LowLevel(self):
return self._low_level.Value
@LowLevel.setter
def LowLevel(self, value):
self._low_level.Value = value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
@SignalCooldownBars.setter
def SignalCooldownBars(self, value):
self._signal_cooldown_bars.Value = value
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@BuyPosOpen.setter
def BuyPosOpen(self, value):
self._buy_pos_open.Value = value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@SellPosOpen.setter
def SellPosOpen(self, value):
self._sell_pos_open.Value = value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@BuyPosClose.setter
def BuyPosClose(self, value):
self._buy_pos_close.Value = value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
@SellPosClose.setter
def SellPosClose(self, value):
self._sell_pos_close.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(color_schaff_momentum_trend_cycle_strategy, self).OnStarted2(time)
self._fast_momentum = Momentum()
self._fast_momentum.Length = self.FastMomentum
self._slow_momentum = Momentum()
self._slow_momentum.Length = self.SlowMomentum
self._macd_history = []
self._st_history = []
self._prev_stc = 0.0
self._prev_color = None
self._cooldown_remaining = 0
self.SubscribeCandles(self.CandleType) \
.Bind(self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
close = float(candle.ClosePrice)
fast_result = process_float(self._fast_momentum, candle.ClosePrice, candle.OpenTime, True)
slow_result = process_float(self._slow_momentum, candle.ClosePrice, candle.OpenTime, True)
if not fast_result.IsFormed or not slow_result.IsFormed:
return
diff = float(fast_result) - float(slow_result)
cycle = self.Cycle
self._add_value(self._macd_history, diff, cycle)
if len(self._macd_history) < cycle:
return
macd_min, macd_max = self._get_min_max(self._macd_history)
previous_st = self._st_history[-1] if len(self._st_history) > 0 else 0.0
if macd_max == macd_min:
st = previous_st
else:
st = (diff - macd_min) / (macd_max - macd_min) * 100.0
self._add_value(self._st_history, st, cycle)
st_min, st_max = self._get_min_max(self._st_history)
if st_max == st_min:
stc = self._prev_stc
else:
stc = (st - st_min) / (st_max - st_min) * 200.0 - 100.0
delta = stc - self._prev_stc
color = self._get_color(stc, delta)
if self._prev_color is not None and self._cooldown_remaining == 0:
if self._prev_color == 6 and color == 7 and self.BuyPosOpen and self.Position <= 0:
if self.Position < 0 and self.SellPosClose:
self.BuyMarket(abs(self.Position))
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif self._prev_color == 1 and color == 0 and self.SellPosOpen and self.Position >= 0:
if self.Position > 0 and self.BuyPosClose:
self.SellMarket(self.Position)
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif self.Position > 0 and self.BuyPosClose and color <= 1:
self.SellMarket(self.Position)
self._cooldown_remaining = self.SignalCooldownBars
elif self.Position < 0 and self.SellPosClose and color >= 6:
self.BuyMarket(abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
self._prev_color = color
self._prev_stc = stc
def _add_value(self, values, value, limit):
values.append(value)
if len(values) > limit:
values.pop(0)
def _get_min_max(self, values):
min_val = values[0]
max_val = values[0]
for i in range(1, len(values)):
val = values[i]
if val < min_val:
min_val = val
if val > max_val:
max_val = val
return min_val, max_val
def _get_color(self, stc, delta):
high = float(self.HighLevel)
low = float(self.LowLevel)
if stc > 0:
if stc > high:
return 7 if delta >= 0 else 6
return 5 if delta >= 0 else 4
if stc < low:
return 0 if delta < 0 else 1
return 2 if delta < 0 else 3
def OnReseted(self):
super(color_schaff_momentum_trend_cycle_strategy, self).OnReseted()
self._fast_momentum = None
self._slow_momentum = None
self._macd_history = []
self._st_history = []
self._prev_stc = 0.0
self._prev_color = None
self._cooldown_remaining = 0
def CreateClone(self):
return color_schaff_momentum_trend_cycle_strategy()