Estrategia CHO With Flat
Esta estrategia opera basándose en el cruce del Chaikin Oscillator y su media móvil. Se utiliza un filtro de Bandas de Bollinger para evitar operar durante mercados planos.
Parámetros
- Candle Type – marco temporal de las velas de entrada.
- Fast Period – período rápido del Chaikin Oscillator.
- Slow Period – período lento del Chaikin Oscillator.
- MA Period – período de la media móvil aplicada al oscilador.
- MA Type – tipo de media móvil para la línea de señal.
- Bollinger Period – período de las Bandas de Bollinger.
- Std Deviation – desviación estándar para las Bandas de Bollinger.
- Flat Threshold – ancho mínimo de banda (en puntos) para considerar el mercado activo.
Lógica de trading
- Calcular el Chaikin Oscillator y su media móvil.
- Construir Bandas de Bollinger sobre el precio para detectar mercado plano.
- Omitir operaciones si el ancho de la banda de Bollinger está por debajo de
Flat Threshold. - Comprar cuando el oscilador cruza por debajo de su línea de señal.
- Vender cuando el oscilador cruza por encima de su línea de señal.
La dirección de la posición siempre sigue el último cruce mientras el filtro plano evita operar en condiciones de mercado lateral.
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()