Color Schaff MFI Trend Cycle Strategy
This strategy is a translation of the MQL5 expert Exp_ColorSchaffMFITrendCycle.
It employs the Color Schaff MFI Trend Cycle indicator, which combines
Money Flow Index values with a double stochastic calculation. The indicator
produces eight color states representing momentum and overbought/oversold zones.
Trading logic:
- When the previous indicator color is green (indexes 6-7) and the current color drops below the strong uptrend zone, the strategy closes short positions and opens a new long position.
- When the previous indicator color is orange (indexes 0-1) and the current color rises above the strong downtrend zone, the strategy closes long positions and opens a new short position.
Parameters:
FastMfiPeriod– period of the fast MFI.SlowMfiPeriod– period of the slow MFI.CycleLength– length of the cyclical buffer used in the indicator.HighLevel/LowLevel– overbought and oversold thresholds for the STC value.CandleType– timeframe of the input candles (default 1 hour).
The strategy uses StockSharp high level API and processes only finished candles.
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()