Cho With Flat Strategy
This strategy trades based on the crossover of the Chaikin Oscillator and its moving average. A Bollinger Bands filter is used to avoid trading during flat markets.
Parameters
- Candle Type – timeframe of input candles.
- Fast Period – fast period of the Chaikin Oscillator.
- Slow Period – slow period of the Chaikin Oscillator.
- MA Period – period of the moving average applied to the oscillator.
- MA Type – moving average type for the signal line.
- Bollinger Period – period of the Bollinger Bands.
- Std Deviation – standard deviation for the Bollinger Bands.
- Flat Threshold – minimal band width (in points) to consider market active.
Trading Logic
- Calculate Chaikin Oscillator and its moving average.
- Build Bollinger Bands on price for flat market detection.
- Skip trades if the Bollinger band width is below
Flat Threshold. - Buy when the oscillator crosses below its signal line.
- Sell when the oscillator crosses above its signal line.
The position direction always follows the latest crossover while the flat filter prevents trading in sideways market conditions.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Chaikin-style oscillator strategy with flat filter.
/// Uses difference between fast and slow EMA as oscillator, EMA as signal line,
/// and Bollinger Bands to detect flat market.
/// </summary>
public class ChoWithFlatStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _stdDeviation;
private readonly StrategyParam<decimal> _flatThreshold;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private ExponentialMovingAverage _signalEma;
private decimal _prevOsc;
private decimal _prevSignal;
private bool _isInitialized;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
public decimal StdDeviation { get => _stdDeviation.Value; set => _stdDeviation.Value = value; }
public decimal FlatThreshold { get => _flatThreshold.Value; set => _flatThreshold.Value = value; }
public ChoWithFlatStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
_fastPeriod = Param(nameof(FastPeriod), 3)
.SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 10)
.SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_signalPeriod = Param(nameof(SignalPeriod), 9)
.SetDisplay("Signal Period", "Signal line EMA period", "Indicator");
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Flat Filter");
_stdDeviation = Param(nameof(StdDeviation), 2.0m)
.SetDisplay("Std Deviation", "Deviation for Bollinger Bands", "Flat Filter");
_flatThreshold = Param(nameof(FlatThreshold), 0.005m)
.SetDisplay("Flat Threshold", "Minimum band width ratio to detect trending", "Flat Filter");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = default;
_slowEma = default;
_signalEma = default;
_prevOsc = 0;
_prevSignal = 0;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new ExponentialMovingAverage { Length = FastPeriod };
_slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
_signalEma = new ExponentialMovingAverage { Length = SignalPeriod };
Indicators.Add(_fastEma);
Indicators.Add(_slowEma);
Indicators.Add(_signalEma);
var bollinger = new BollingerBands { Length = BollingerPeriod, Width = StdDeviation };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
var bb = (BollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upperBand ||
bb.LowBand is not decimal lowerBand ||
bb.MovingAverage is not decimal middleBand)
return;
var fastResult = _fastEma.Process(candle.ClosePrice, candle.OpenTime, true);
var slowResult = _slowEma.Process(candle.ClosePrice, candle.OpenTime, true);
if (!fastResult.IsFormed || !slowResult.IsFormed)
return;
var oscValue = fastResult.ToDecimal() - slowResult.ToDecimal();
var sigResult = _signalEma.Process(oscValue, candle.OpenTime, true);
if (!sigResult.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var signalValue = sigResult.ToDecimal();
if (!_isInitialized)
{
_prevOsc = oscValue;
_prevSignal = signalValue;
_isInitialized = true;
return;
}
var bandWidth = upperBand - lowerBand;
if (middleBand != 0 && (bandWidth / middleBand) < FlatThreshold)
{
_prevOsc = oscValue;
_prevSignal = signalValue;
return;
}
var wasAbove = _prevOsc > _prevSignal;
var isAbove = oscValue > signalValue;
if (!wasAbove && isAbove)
{
if (Position <= 0)
BuyMarket();
}
else if (wasAbove && !isAbove)
{
if (Position >= 0)
SellMarket();
}
_prevOsc = oscValue;
_prevSignal = signalValue;
}
}
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 ExponentialMovingAverage, BollingerBands
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class cho_with_flat_strategy(Strategy):
def __init__(self):
super(cho_with_flat_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._fast_period = self.Param("FastPeriod", 3) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 10) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._signal_period = self.Param("SignalPeriod", 9) \
.SetDisplay("Signal Period", "Signal line EMA period", "Indicator")
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Flat Filter")
self._std_deviation = self.Param("StdDeviation", 2.0) \
.SetDisplay("Std Deviation", "Deviation for Bollinger Bands", "Flat Filter")
self._flat_threshold = self.Param("FlatThreshold", 0.005) \
.SetDisplay("Flat Threshold", "Minimum band width ratio to detect trending", "Flat Filter")
self._fast_ema = None
self._slow_ema = None
self._signal_ema = None
self._prev_osc = 0.0
self._prev_signal = 0.0
self._is_initialized = False
@property
def candle_type(self):
return self._candle_type.Value
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def signal_period(self):
return self._signal_period.Value
@property
def bollinger_period(self):
return self._bollinger_period.Value
@property
def std_deviation(self):
return self._std_deviation.Value
@property
def flat_threshold(self):
return self._flat_threshold.Value
def OnReseted(self):
super(cho_with_flat_strategy, self).OnReseted()
self._fast_ema = None
self._slow_ema = None
self._signal_ema = None
self._prev_osc = 0.0
self._prev_signal = 0.0
self._is_initialized = False
def OnStarted2(self, time):
super(cho_with_flat_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.fast_period
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.slow_period
self._signal_ema = ExponentialMovingAverage()
self._signal_ema.Length = self.signal_period
self.Indicators.Add(self._fast_ema)
self.Indicators.Add(self._slow_ema)
self.Indicators.Add(self._signal_ema)
bollinger = BollingerBands()
bollinger.Length = self.bollinger_period
bollinger.Width = self.std_deviation
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
upper_band = float(bb_value.UpBand)
lower_band = float(bb_value.LowBand)
middle_band = float(bb_value.MovingAverage)
fast_result = process_float(self._fast_ema, candle.ClosePrice, candle.OpenTime, True)
slow_result = process_float(self._slow_ema, candle.ClosePrice, candle.OpenTime, True)
if not fast_result.IsFormed or not slow_result.IsFormed:
return
osc_value = float(fast_result) - float(slow_result)
sig_result = process_float(self._signal_ema, osc_value, candle.OpenTime, True)
if not sig_result.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
signal_value = float(sig_result)
if not self._is_initialized:
self._prev_osc = osc_value
self._prev_signal = signal_value
self._is_initialized = True
return
band_width = upper_band - lower_band
if middle_band != 0 and (band_width / middle_band) < float(self.flat_threshold):
self._prev_osc = osc_value
self._prev_signal = signal_value
return
was_above = self._prev_osc > self._prev_signal
is_above = osc_value > signal_value
if not was_above and is_above:
if self.Position <= 0:
self.BuyMarket()
elif was_above and not is_above:
if self.Position >= 0:
self.SellMarket()
self._prev_osc = osc_value
self._prev_signal = signal_value
def CreateClone(self):
return cho_with_flat_strategy()