Color Schaff DeMarker Trend Cycle Strategy
The Color Schaff DeMarker Trend Cycle Strategy uses a custom oscillator derived from fast and slow DeMarker values. The indicator applies two stochastic steps to create a cycle value that oscillates between -100 and +100. Colors are assigned based on the level and slope of the oscillator, which are then used to generate trading signals.
The strategy enters long positions when the oscillator exits the upper zone and leaves short positions. It opens short positions when the oscillator leaves the lower zone and exits long positions. The idea is to react to momentum changes at extreme levels.
Details
- Entry Criteria:
- Long: Previous color > 5 and current color < 6.
- Short: Previous color < 2 and current color > 1.
- Long/Short: Both.
- Exit Criteria:
- Long: Color < 2 when a long position is open.
- Short: Color > 5 when a short position is open.
- Stops: No explicit stop-loss or take-profit.
- Default Values:
FastDeMarker= 23SlowDeMarker= 50Cycle= 10HighLevel= 60LowLevel= -60
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: DeMarker, Highest, Lowest
- Stops: No
- Complexity: Medium
- Timeframe: 4H
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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 the Color Schaff DeMarker Trend Cycle oscillator.
/// Uses fast/slow DeMarker difference with double stochastic smoothing.
/// </summary>
public class ColorSchaffDeMarkerTrendCycleStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _cycle;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevSt;
private decimal _prevStc;
private bool _st1Pass;
private bool _st2Pass;
private int _prevColor;
private readonly List<decimal> _macdBuf = new();
private readonly List<decimal> _stBuf = new();
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int Cycle { get => _cycle.Value; set => _cycle.Value = value; }
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
public decimal Factor { get => _factor.Value; set => _factor.Value = value; }
public decimal StopLossPct { get => _stopLossPct.Value; set => _stopLossPct.Value = value; }
public decimal TakeProfitPct { get => _takeProfitPct.Value; set => _takeProfitPct.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorSchaffDeMarkerTrendCycleStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 23)
.SetGreaterThanZero()
.SetDisplay("Fast DeMarker", "Fast DeMarker period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow DeMarker", "Slow DeMarker period", "Indicator");
_cycle = Param(nameof(Cycle), 10)
.SetGreaterThanZero()
.SetDisplay("Cycle", "Cycle length", "Indicator");
_highLevel = Param(nameof(HighLevel), 60m)
.SetDisplay("High Level", "Upper threshold", "Levels");
_lowLevel = Param(nameof(LowLevel), -60m)
.SetDisplay("Low Level", "Lower threshold", "Levels");
_factor = Param(nameof(Factor), 0.5m)
.SetDisplay("Factor", "Smoothing factor", "Indicator");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSt = 0; _prevStc = 0;
_st1Pass = false; _st2Pass = false;
_prevColor = 0;
_macdBuf.Clear();
_stBuf.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new DeMarker { Length = FastPeriod };
var slow = new DeMarker { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
var macd = fastVal - slowVal;
// Track high/low of macd manually
_macdBuf.Add(macd);
if (_macdBuf.Count > Cycle) _macdBuf.RemoveAt(0);
if (_macdBuf.Count < Cycle) return;
var macdHigh = decimal.MinValue;
var macdLow = decimal.MaxValue;
foreach (var v in _macdBuf)
{
if (v > macdHigh) macdHigh = v;
if (v < macdLow) macdLow = v;
}
decimal st;
if (macdHigh == macdLow) st = _prevSt;
else st = (macd - macdLow) / (macdHigh - macdLow) * 100m;
if (_st1Pass) st = Factor * (st - _prevSt) + _prevSt;
_prevSt = st;
_st1Pass = true;
_stBuf.Add(st);
if (_stBuf.Count > Cycle) _stBuf.RemoveAt(0);
if (_stBuf.Count < Cycle) return;
var stHigh = decimal.MinValue;
var stLow = decimal.MaxValue;
foreach (var v in _stBuf)
{
if (v > stHigh) stHigh = v;
if (v < stLow) stLow = v;
}
decimal stc;
if (stHigh == stLow) stc = _prevStc;
else stc = (st - stLow) / (stHigh - stLow) * 200m - 100m;
if (_st2Pass) stc = Factor * (stc - _prevStc) + _prevStc;
var dStc = stc - _prevStc;
_prevStc = stc;
_st2Pass = true;
int color;
if (stc > 0) color = stc > HighLevel ? (dStc >= 0 ? 7 : 6) : (dStc >= 0 ? 5 : 4);
else color = stc < LowLevel ? (dStc < 0 ? 0 : 1) : (dStc < 0 ? 2 : 3);
if (_prevColor > 5 && color < 6 && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (_prevColor < 2 && color > 1 && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_prevColor = color;
})
.Start();
StartProtection(
takeProfit: new Unit(TakeProfitPct, UnitTypes.Percent),
stopLoss: new Unit(StopLossPct, UnitTypes.Percent),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
}
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 DeMarker
from StockSharp.Algo.Strategies import Strategy
class color_schaff_de_marker_trend_cycle_strategy(Strategy):
def __init__(self):
super(color_schaff_de_marker_trend_cycle_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 23) \
.SetDisplay("Fast DeMarker", "Fast DeMarker period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow DeMarker", "Slow DeMarker period", "Indicator")
self._cycle = self.Param("Cycle", 10) \
.SetDisplay("Cycle", "Cycle length", "Indicator")
self._high_level = self.Param("HighLevel", 60.0) \
.SetDisplay("High Level", "Upper threshold", "Levels")
self._low_level = self.Param("LowLevel", -60.0) \
.SetDisplay("Low Level", "Lower threshold", "Levels")
self._factor = self.Param("Factor", 0.5) \
.SetDisplay("Factor", "Smoothing factor", "Indicator")
self._stop_loss_pct = self.Param("StopLossPct", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_pct = self.Param("TakeProfitPct", 3.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_st = 0.0
self._prev_stc = 0.0
self._st1_pass = False
self._st2_pass = False
self._prev_color = 0
self._macd_buf = []
self._st_buf = []
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def cycle(self):
return self._cycle.Value
@property
def high_level(self):
return self._high_level.Value
@property
def low_level(self):
return self._low_level.Value
@property
def factor(self):
return self._factor.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def take_profit_pct(self):
return self._take_profit_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(color_schaff_de_marker_trend_cycle_strategy, self).OnReseted()
self._prev_st = 0.0
self._prev_stc = 0.0
self._st1_pass = False
self._st2_pass = False
self._prev_color = 0
self._macd_buf = []
self._st_buf = []
def OnStarted2(self, time):
super(color_schaff_de_marker_trend_cycle_strategy, self).OnStarted2(time)
fast = DeMarker()
fast.Length = self.fast_period
slow = DeMarker()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.on_candle).Start()
self.StartProtection(
takeProfit=Unit(self.take_profit_pct, UnitTypes.Percent),
stopLoss=Unit(self.stop_loss_pct, UnitTypes.Percent),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
macd = fast_val - slow_val
cycle = int(self.cycle)
fct = float(self.factor)
self._macd_buf.append(macd)
if len(self._macd_buf) > cycle:
self._macd_buf.pop(0)
if len(self._macd_buf) < cycle:
return
macd_high = max(self._macd_buf)
macd_low = min(self._macd_buf)
if macd_high == macd_low:
st = self._prev_st
else:
st = (macd - macd_low) / (macd_high - macd_low) * 100.0
if self._st1_pass:
st = fct * (st - self._prev_st) + self._prev_st
self._prev_st = st
self._st1_pass = True
self._st_buf.append(st)
if len(self._st_buf) > cycle:
self._st_buf.pop(0)
if len(self._st_buf) < cycle:
return
st_high = max(self._st_buf)
st_low = min(self._st_buf)
if st_high == st_low:
stc = self._prev_stc
else:
stc = (st - st_low) / (st_high - st_low) * 200.0 - 100.0
if self._st2_pass:
stc = fct * (stc - self._prev_stc) + self._prev_stc
d_stc = stc - self._prev_stc
self._prev_stc = stc
self._st2_pass = True
hl = float(self.high_level)
ll = float(self.low_level)
if stc > 0:
if stc > hl:
color = 7 if d_stc >= 0 else 6
else:
color = 5 if d_stc >= 0 else 4
else:
if stc < ll:
color = 0 if d_stc < 0 else 1
else:
color = 2 if d_stc < 0 else 3
if self._prev_color > 5 and color < 6 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_color < 2 and color > 1 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_color = color
def CreateClone(self):
return color_schaff_de_marker_trend_cycle_strategy()