Estrategia Keltner Volume
Implementación de la estrategia Keltner Channels + Volume. Comprar cuando el precio rompe por encima del canal Keltner superior con volumen superior al promedio. Vender cuando el precio rompe por debajo del canal Keltner inferior con volumen superior al promedio.
Las pruebas indican un retorno anual promedio de aproximadamente el 58%. Funciona mejor en el mercado de acciones.
Los límites del canal Keltner definen posibles reversiones, y el aumento del volumen señala convicción. El sistema opera cuando el precio toca una banda con volumen en expansión.
Los traders que buscan confirmación de volumen alrededor de bandas de volatilidad pueden preferir esta configuración. Los stops se calculan a partir del ATR.
Detalles
- Criterios de entrada:
- Largo:
Close < LowerBand && Volume > AvgVolume - Corto:
Close > UpperBand && Volume > AvgVolume
- Largo:
- Largo/Corto: Ambos
- Criterios de salida:
- El precio cruza la EMA
- Stops: Basados en ATR usando
StopLoss - Valores predeterminados:
EmaPeriod= 20AtrPeriod= 14Multiplier= 2.0mVolumeAvgPeriod= 20StopLoss= new Unit(2, UnitTypes.Absolute)CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoría: Reversión a la media
- Dirección: Ambos
- Indicadores: Keltner Channel, Volume
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Medio plazo
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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 combining Keltner Channels with volume confirmation.
/// Buys on upper channel breakout with above-average volume,
/// sells on lower channel breakdown with above-average volume.
/// </summary>
public class KeltnerVolumeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<int> _cooldownBars;
private decimal _averageVolume;
private int _volumeCounter;
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// EMA period for center line.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// ATR period for channel width.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for channel width.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Volume average period.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public KeltnerVolumeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetRange(10, 40)
.SetDisplay("EMA Period", "EMA period for center line", "Keltner");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetRange(7, 21)
.SetDisplay("ATR Period", "ATR period for channel width", "Keltner");
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetDisplay("ATR Multiplier", "Multiplier for ATR", "Keltner");
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 20)
.SetRange(10, 50)
.SetDisplay("Volume Avg Period", "Period for volume average", "Volume");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_averageVolume = 0;
_volumeCounter = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
if (_volumeCounter < VolumeAvgPeriod)
{
_volumeCounter++;
_averageVolume = ((_averageVolume * (_volumeCounter - 1)) + volume) / _volumeCounter;
}
else
{
_averageVolume = (_averageVolume * (VolumeAvgPeriod - 1) + volume) / VolumeAvgPeriod;
}
if (_volumeCounter < VolumeAvgPeriod)
{
if (_cooldown > 0)
_cooldown--;
return;
}
var upperBand = emaValue + Multiplier * atrValue;
var lowerBand = emaValue - Multiplier * atrValue;
var highVolume = volume > _averageVolume;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Buy: price above upper band + high volume
if (close > upperBand && highVolume && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Sell: price below lower band + high volume
else if (close < lowerBand && highVolume && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price below EMA
if (Position > 0 && close < emaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price above EMA
else if (Position < 0 && close > emaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class keltner_volume_strategy(Strategy):
"""
Strategy combining Keltner Channels with volume confirmation.
"""
def __init__(self):
super(keltner_volume_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ema_period = self.Param("EmaPeriod", 20) \
.SetRange(10, 40) \
.SetDisplay("EMA Period", "EMA period for center line", "Keltner")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetRange(7, 21) \
.SetDisplay("ATR Period", "ATR period for channel width", "Keltner")
self._multiplier = self.Param("Multiplier", 2.0) \
.SetDisplay("ATR Multiplier", "Multiplier for ATR", "Keltner")
self._volume_avg_period = self.Param("VolumeAvgPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("Volume Avg Period", "Period for volume average", "Volume")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._average_volume = 0.0
self._volume_counter = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(keltner_volume_strategy, self).OnStarted2(time)
self._average_volume = 0.0
self._volume_counter = 0
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
volume = float(candle.TotalVolume)
ev = float(ema_value)
av = float(atr_value)
vol_avg_prd = self._volume_avg_period.Value
if self._volume_counter < vol_avg_prd:
self._volume_counter += 1
self._average_volume = ((self._average_volume * (self._volume_counter - 1)) + volume) / self._volume_counter
else:
self._average_volume = (self._average_volume * (vol_avg_prd - 1) + volume) / vol_avg_prd
if self._volume_counter < vol_avg_prd:
if self._cooldown > 0:
self._cooldown -= 1
return
mult = float(self._multiplier.Value)
upper_band = ev + mult * av
lower_band = ev - mult * av
high_volume = volume > self._average_volume
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
# Buy: price above upper band + high volume
if close > upper_band and high_volume and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif close < lower_band and high_volume and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit long: price below EMA
if self.Position > 0 and close < ev:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > ev:
self.BuyMarket()
self._cooldown = cd
def OnReseted(self):
super(keltner_volume_strategy, self).OnReseted()
self._average_volume = 0.0
self._volume_counter = 0
self._cooldown = 0
def CreateClone(self):
return keltner_volume_strategy()