Estrategia de Ciclo de Tendencia de Momentum Color Schaff
La estrategia utiliza el Color Schaff Momentum Trend Cycle (STC) para detectar reversiones de tendencia cuando el indicador sale de zonas de sobrecompra o sobreventa.
Detalles
- Criterios de entrada:
- Comprar cuando el color STC anterior estaba por encima de la zona superior (>5) y el color actual cae por debajo de 6, cerrando cualquier posición corta.
- Vender cuando el color STC anterior estaba por debajo de la zona inferior (<2) y el color actual sube por encima de 1, cerrando cualquier posición larga.
- Largo/Corto: Ambos.
- Criterios de salida: La señal inversa cierra la posición opuesta.
- Stops: Sin stop loss ni take profit explícito.
- Valores predeterminados:
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()