Estrategia de Ciclo de Tendencia Color Schaff MFI
Esta estrategia es una traducción del experto MQL5 Exp_ColorSchaffMFITrendCycle.
Emplea el indicador Color Schaff MFI Trend Cycle, que combina
valores del Índice de Flujo de Dinero con un cálculo de doble estocástico. El indicador
produce ocho estados de color que representan zonas de impulso y sobrecompra/sobreventa.
Lógica de trading:
- Cuando el color anterior del indicador es verde (índices 6-7) y el color actual cae por debajo de la zona de fuerte tendencia alcista, la estrategia cierra posiciones cortas y abre una nueva posición larga.
- Cuando el color anterior del indicador es naranja (índices 0-1) y el color actual sube por encima de la zona de fuerte tendencia bajista, la estrategia cierra posiciones largas y abre una nueva posición corta.
Parámetros:
FastMfiPeriod– período del MFI rápido.SlowMfiPeriod– período del MFI lento.CycleLength– longitud del buffer cíclico usado en el indicador.HighLevel/LowLevel– umbrales de sobrecompra y sobreventa para el valor STC.CandleType– marco temporal de las velas de entrada (predeterminado 1 hora).
La estrategia utiliza la API de alto nivel de StockSharp y procesa únicamente velas terminadas.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on the Color Schaff MoneyFlowIndex Trend Cycle indicator.
/// </summary>
public class ColorSchaffMfiTrendCycleStrategy : Strategy
{
private readonly StrategyParam<int> _fastMfiPeriod;
private readonly StrategyParam<int> _slowMfiPeriod;
private readonly StrategyParam<int> _cycleLength;
private readonly StrategyParam<int> _highLevel;
private readonly StrategyParam<int> _lowLevel;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private MoneyFlowIndex _fastMfi;
private MoneyFlowIndex _slowMfi;
private decimal[] _macd;
private decimal[] _st;
private int _index;
private int _valuesCount;
private bool _st1;
private bool _st2;
private decimal _prevStc;
private int _prevColor;
private int _cooldownRemaining;
public int FastMfiPeriod { get => _fastMfiPeriod.Value; set => _fastMfiPeriod.Value = value; }
public int SlowMfiPeriod { get => _slowMfiPeriod.Value; set => _slowMfiPeriod.Value = value; }
public int CycleLength { get => _cycleLength.Value; set => _cycleLength.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 DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorSchaffMfiTrendCycleStrategy()
{
_fastMfiPeriod = Param(nameof(FastMfiPeriod), 23)
.SetDisplay("Fast MoneyFlowIndex", "Fast MoneyFlowIndex period", "Indicator");
_slowMfiPeriod = Param(nameof(SlowMfiPeriod), 50)
.SetDisplay("Slow MoneyFlowIndex", "Slow MoneyFlowIndex period", "Indicator");
_cycleLength = Param(nameof(CycleLength), 10)
.SetDisplay("Cycle Length", "Cycle length for STC", "Indicator");
_highLevel = Param(nameof(HighLevel), 60)
.SetDisplay("High Level", "Overbought threshold", "Indicator");
_lowLevel = Param(nameof(LowLevel), -60)
.SetDisplay("Low Level", "Oversold threshold", "Indicator");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_fastMfi = null;
_slowMfi = null;
_macd = null;
_st = null;
_index = 0;
_valuesCount = 0;
_st1 = false;
_st2 = false;
_prevStc = 0m;
_prevColor = 0;
_cooldownRemaining = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMfi = new MoneyFlowIndex { Length = FastMfiPeriod };
_slowMfi = new MoneyFlowIndex { Length = SlowMfiPeriod };
_macd = new decimal[CycleLength];
_st = new decimal[CycleLength];
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fastMfi, _slowMfi, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastMfi, decimal slowMfi)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var color = CalculateColor(fastMfi, slowMfi);
if (_cooldownRemaining == 0 && _prevColor == 6 && color == 7 && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && _prevColor == 1 && color == 0 && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
_prevColor = color;
}
private int CalculateColor(decimal fastMfi, decimal slowMfi)
{
var diff = fastMfi - slowMfi;
_macd[_index] = diff;
var count = _valuesCount < CycleLength ? _valuesCount + 1 : CycleLength;
GetMinMax(_macd, count, out var llv, out var hhv);
var prevIndex = (_index - 1 + CycleLength) % CycleLength;
var stPrev = _st[prevIndex];
var st = hhv != llv ? (diff - llv) / (hhv - llv) * 100m : stPrev;
if (_st1 && _valuesCount > 0)
st = 0.5m * (st - stPrev) + stPrev;
_st1 = true;
_st[_index] = st;
GetMinMax(_st, count, out llv, out hhv);
var stcPrev = _prevStc;
var stc = hhv != llv ? (st - llv) / (hhv - llv) * 200m - 100m : stcPrev;
if (_st2 && _valuesCount > 0)
stc = 0.5m * (stc - stcPrev) + stcPrev;
_st2 = true;
var dStc = stc - stcPrev;
_prevStc = stc;
_index = (_index + 1) % CycleLength;
if (_valuesCount < CycleLength)
_valuesCount++;
int color;
if (stc > 0)
{
if (stc > HighLevel)
color = dStc >= 0 ? 7 : 6;
else
color = dStc >= 0 ? 5 : 4;
}
else
{
if (stc < LowLevel)
color = dStc < 0 ? 0 : 1;
else
color = dStc < 0 ? 2 : 3;
}
return color;
}
private static void GetMinMax(decimal[] buffer, int count, out decimal min, out decimal max)
{
min = buffer[0];
max = buffer[0];
for (var i = 1; i < count; i++)
{
var val = buffer[i];
if (val < min)
min = val;
if (val > max)
max = val;
}
}
}
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 MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class color_schaff_mfi_trend_cycle_strategy(Strategy):
def __init__(self):
super(color_schaff_mfi_trend_cycle_strategy, self).__init__()
self._fast_mfi_period = self.Param("FastMfiPeriod", 23) \
.SetDisplay("Fast MoneyFlowIndex", "Fast MoneyFlowIndex period", "Indicator")
self._slow_mfi_period = self.Param("SlowMfiPeriod", 50) \
.SetDisplay("Slow MoneyFlowIndex", "Slow MoneyFlowIndex period", "Indicator")
self._cycle_length = self.Param("CycleLength", 10) \
.SetDisplay("Cycle Length", "Cycle length for STC", "Indicator")
self._high_level = self.Param("HighLevel", 60) \
.SetDisplay("High Level", "Overbought threshold", "Indicator")
self._low_level = self.Param("LowLevel", -60) \
.SetDisplay("Low Level", "Oversold threshold", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 12) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles timeframe", "General")
self._macd = None
self._st = None
self._index = 0
self._values_count = 0
self._st1 = False
self._st2 = False
self._prev_stc = 0.0
self._prev_color = 0
self._cooldown_remaining = 0
@property
def FastMfiPeriod(self):
return self._fast_mfi_period.Value
@FastMfiPeriod.setter
def FastMfiPeriod(self, value):
self._fast_mfi_period.Value = value
@property
def SlowMfiPeriod(self):
return self._slow_mfi_period.Value
@SlowMfiPeriod.setter
def SlowMfiPeriod(self, value):
self._slow_mfi_period.Value = value
@property
def CycleLength(self):
return self._cycle_length.Value
@CycleLength.setter
def CycleLength(self, value):
self._cycle_length.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 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_mfi_trend_cycle_strategy, self).OnStarted2(time)
fast_mfi = MoneyFlowIndex()
fast_mfi.Length = self.FastMfiPeriod
slow_mfi = MoneyFlowIndex()
slow_mfi.Length = self.SlowMfiPeriod
cycle = self.CycleLength
self._macd = [0.0] * cycle
self._st = [0.0] * cycle
self._index = 0
self._values_count = 0
self._cooldown_remaining = 0
self.SubscribeCandles(self.CandleType) \
.Bind(fast_mfi, slow_mfi, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, fast_mfi_val, slow_mfi_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
color = self._calculate_color(float(fast_mfi_val), float(slow_mfi_val))
if self._cooldown_remaining == 0 and self._prev_color == 6 and color == 7 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and self._prev_color == 1 and color == 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
self._prev_color = color
def _calculate_color(self, fast_mfi, slow_mfi):
cycle = self.CycleLength
diff = fast_mfi - slow_mfi
self._macd[self._index] = diff
count = self._values_count + 1 if self._values_count < cycle else cycle
llv, hhv = self._get_min_max(self._macd, count)
prev_index = (self._index - 1 + cycle) % cycle
st_prev = self._st[prev_index]
if hhv != llv:
st = (diff - llv) / (hhv - llv) * 100.0
else:
st = st_prev
if self._st1 and self._values_count > 0:
st = 0.5 * (st - st_prev) + st_prev
self._st1 = True
self._st[self._index] = st
llv2, hhv2 = self._get_min_max(self._st, count)
stc_prev = self._prev_stc
if hhv2 != llv2:
stc = (st - llv2) / (hhv2 - llv2) * 200.0 - 100.0
else:
stc = stc_prev
if self._st2 and self._values_count > 0:
stc = 0.5 * (stc - stc_prev) + stc_prev
self._st2 = True
d_stc = stc - stc_prev
self._prev_stc = stc
self._index = (self._index + 1) % cycle
if self._values_count < cycle:
self._values_count += 1
high = float(self.HighLevel)
low = float(self.LowLevel)
if stc > 0:
if stc > high:
color = 7 if d_stc >= 0 else 6
else:
color = 5 if d_stc >= 0 else 4
else:
if stc < low:
color = 0 if d_stc < 0 else 1
else:
color = 2 if d_stc < 0 else 3
return color
def _get_min_max(self, buffer, count):
min_val = buffer[0]
max_val = buffer[0]
for i in range(1, count):
val = buffer[i]
if val < min_val:
min_val = val
if val > max_val:
max_val = val
return min_val, max_val
def OnReseted(self):
super(color_schaff_mfi_trend_cycle_strategy, self).OnReseted()
self._macd = None
self._st = None
self._index = 0
self._values_count = 0
self._st1 = False
self._st2 = False
self._prev_stc = 0.0
self._prev_color = 0
self._cooldown_remaining = 0
def CreateClone(self):
return color_schaff_mfi_trend_cycle_strategy()