Estratégia de Canais com NVI
Esta estratégia combina Bandas de Bollinger ou Canais de Keltner com o Índice de Volume Negativo (NVI). Uma posição comprada é aberta quando o preço fecha abaixo da banda inferior enquanto o NVI está acima de sua EMA. A posição é fechada quando o NVI cai abaixo de sua EMA. Percentuais opcionais de stop-loss e take-profit estão disponíveis.
Detalhes
- Critérios de entrada:
- Comprado: Fechamento < banda inferior e NVI > EMA do NVI.
- Comprado/Vendido: Somente comprado.
- Critérios de saída:
- Comprado: NVI < EMA do NVI.
- Stops: Opcional, percentual do preço de entrada.
- Valores padrão:
ChannelType= "BB"ChannelLength= 20ChannelMultiplier= 2NviEmaLength= 200EnableStopLoss= falseStopLossPercent= 0EnableTakeProfit= falseTakeProfitPercent= 0
- Filtros:
- Categoria: Canal
- Direção: Somente comprado
- Indicadores: Bollinger Bands ou Keltner Channels, EMA, NVI
- Stops: Opcional
- Complexidade: Baixo
- Período: Qualquer
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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>
/// Channels with NVI strategy.
/// The channel is a Bollinger band (ChannelType "BB") or a Keltner channel (ChannelType "KC": EMA plus and minus ChannelMultiplier
/// ATRs) of ChannelLength. The Negative Volume Index starts at 1000 and moves with the close only on candles whose volume is below the
/// previous one. Long only: buys when the close is below the lower channel line and NVI is above EMA(NviEmaLength) of NVI, and closes the
/// long when NVI falls below that EMA. Optional percent stop loss and take profit protect the position.
/// </summary>
public class ChannelsWithNviStrategy : Strategy
{
private readonly StrategyParam<string> _channelType;
private readonly StrategyParam<int> _channelLength;
private readonly StrategyParam<decimal> _channelMultiplier;
private readonly StrategyParam<int> _nviEmaLength;
private readonly StrategyParam<bool> _enableStopLoss;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _enableTakeProfit;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _nviEma;
private decimal _nvi;
private decimal? _prevClose;
private decimal? _prevVolume;
/// <summary>
/// Channel kind: "BB" for Bollinger Bands, "KC" for Keltner Channels.
/// </summary>
public string ChannelType
{
get => _channelType.Value;
set => _channelType.Value = value;
}
/// <summary>
/// Channel period.
/// </summary>
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}
/// <summary>
/// Channel width multiplier.
/// </summary>
public decimal ChannelMultiplier
{
get => _channelMultiplier.Value;
set => _channelMultiplier.Value = value;
}
/// <summary>
/// EMA period of NVI.
/// </summary>
public int NviEmaLength
{
get => _nviEmaLength.Value;
set => _nviEmaLength.Value = value;
}
/// <summary>
/// Enable the stop loss.
/// </summary>
public bool EnableStopLoss
{
get => _enableStopLoss.Value;
set => _enableStopLoss.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Enable the take profit.
/// </summary>
public bool EnableTakeProfit
{
get => _enableTakeProfit.Value;
set => _enableTakeProfit.Value = value;
}
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ChannelsWithNviStrategy()
{
_channelType = Param(nameof(ChannelType), "BB")
.SetDisplay("Channel Type", "Channel kind: BB for Bollinger Bands, KC for Keltner Channels", "Channel");
_channelLength = Param(nameof(ChannelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Length", "Channel period", "Channel");
_channelMultiplier = Param(nameof(ChannelMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Channel Multiplier", "Channel width multiplier", "Channel");
_nviEmaLength = Param(nameof(NviEmaLength), 200)
.SetGreaterThanZero()
.SetDisplay("NVI EMA Length", "EMA period of NVI", "NVI");
_enableStopLoss = Param(nameof(EnableStopLoss), false)
.SetDisplay("Enable Stop Loss", "Enable the stop loss", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_enableTakeProfit = Param(nameof(EnableTakeProfit), false)
.SetDisplay("Enable Take Profit", "Enable the take profit", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 0m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_nviEma = null;
_nvi = 1000m;
_prevClose = null;
_prevVolume = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_nvi = 1000m;
_prevClose = null;
_prevVolume = null;
_nviEma = new ExponentialMovingAverage { Length = NviEmaLength };
var bollinger = new BollingerBands { Length = ChannelLength, Width = ChannelMultiplier };
var ema = new ExponentialMovingAverage { Length = ChannelLength };
var atr = new AverageTrueRange { Length = ChannelLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, ema, atr, ProcessCandle)
.Start();
var takeProfit = EnableTakeProfit && TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit();
var stopLoss = EnableStopLoss && StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit();
StartProtection(takeProfit, stopLoss, useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue emaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
if (_prevClose is decimal pc && _prevVolume is decimal pv && pc != 0 && volume < pv)
_nvi += _nvi * (close - pc) / pc;
_prevClose = close;
_prevVolume = volume;
var nviEmaValue = _nviEma.Process(new DecimalIndicatorValue(_nviEma, _nvi, candle.OpenTime) { IsFinal = true });
if (!nviEmaValue.IsFormed)
return;
decimal lower;
if (ChannelType.EqualsIgnoreCase("KC"))
{
if (!emaValue.IsFormed || !atrValue.IsFormed)
return;
lower = emaValue.GetValue<decimal>() - ChannelMultiplier * atrValue.GetValue<decimal>();
}
else
{
if (!bollingerValue.IsFormed || ((BollingerBandsValue)bollingerValue).LowBand is not decimal low)
return;
lower = low;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var nviEma = nviEmaValue.GetValue<decimal>();
if (Position > 0)
{
if (_nvi < nviEma)
SellMarket(Position);
}
else if (Position == 0 && close < lower && _nvi > nviEma)
{
BuyMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, ExponentialMovingAverage, AverageTrueRange, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class channels_with_nvi_strategy(Strategy):
"""
Channels with NVI strategy.
The channel is a Bollinger band (ChannelType "BB") or a Keltner channel (ChannelType "KC": EMA plus and minus ChannelMultiplier
ATRs) of ChannelLength. The Negative Volume Index starts at 1000 and moves with the close only on candles whose volume is below the
previous one. Long only: buys when the close is below the lower channel line and NVI is above EMA(NviEmaLength) of NVI, and closes the
long when NVI falls below that EMA. Optional percent stop loss and take profit protect the position.
"""
def __init__(self):
super(channels_with_nvi_strategy, self).__init__()
self._channel_type = self.Param("ChannelType", "BB").SetDisplay("Channel Type", "Channel kind: BB for Bollinger Bands, KC for Keltner Channels", "Channel")
self._channel_length = self.Param("ChannelLength", 20).SetGreaterThanZero().SetDisplay("Channel Length", "Channel period", "Channel")
self._channel_multiplier = self.Param("ChannelMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Channel Multiplier", "Channel width multiplier", "Channel")
self._nvi_ema_length = self.Param("NviEmaLength", 200).SetGreaterThanZero().SetDisplay("NVI EMA Length", "EMA period of NVI", "NVI")
self._enable_stop_loss = self.Param("EnableStopLoss", False).SetDisplay("Enable Stop Loss", "Enable the stop loss", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 0.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._enable_take_profit = self.Param("EnableTakeProfit", False).SetDisplay("Enable Take Profit", "Enable the take profit", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 0.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._nvi_ema = None
self._nvi = Decimal(1000)
self._prev_close = None
self._prev_volume = None
def OnReseted(self):
super(channels_with_nvi_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(channels_with_nvi_strategy, self).OnStarted2(time)
self._reset_state()
self._nvi_ema = ExponentialMovingAverage()
self._nvi_ema.Length = self._nvi_ema_length.Value
length = self._channel_length.Value
bollinger = BollingerBands()
bollinger.Length = length
bollinger.Width = Decimal(self._channel_multiplier.Value)
ema = ExponentialMovingAverage()
ema.Length = length
atr = AverageTrueRange()
atr.Length = length
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, ema, atr, self._process_candle).Start()
take_percent = Decimal(self._take_profit_percent.Value)
stop_percent = Decimal(self._stop_loss_percent.Value)
take_profit = Unit(take_percent, UnitTypes.Percent) if self._enable_take_profit.Value and take_percent > 0 else Unit()
stop_loss = Unit(stop_percent, UnitTypes.Percent) if self._enable_stop_loss.Value and stop_percent > 0 else Unit()
self.StartProtection(take_profit, stop_loss, useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def _process_candle(self, candle, bollinger_value, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
volume = candle.TotalVolume
pc = self._prev_close
pv = self._prev_volume
if pc is not None and pv is not None and pc != 0 and volume < pv:
self._nvi += self._nvi * (close - pc) / pc
self._prev_close = close
self._prev_volume = volume
nvi_input = DecimalIndicatorValue(self._nvi_ema, self._nvi, candle.OpenTime)
nvi_input.IsFinal = True
nvi_ema_value = self._nvi_ema.Process(nvi_input)
if not nvi_ema_value.IsFormed:
return
if str(self._channel_type.Value).upper() == "KC":
if not ema_value.IsFormed or not atr_value.IsFormed:
return
lower = ema_value.GetValue[Decimal](None) - Decimal(self._channel_multiplier.Value) * atr_value.GetValue[Decimal](None)
else:
if not bollinger_value.IsFormed or bollinger_value.LowBand is None:
return
lower = bollinger_value.LowBand
if not self.IsFormedAndOnlineAndAllowTrading():
return
nvi_ema = nvi_ema_value.GetValue[Decimal](None)
if self.Position > 0:
if self._nvi < nvi_ema:
self.SellMarket(self.Position)
elif self.Position == 0 and close < lower and self._nvi > nvi_ema:
self.BuyMarket(self.Volume)
def CreateClone(self):
return channels_with_nvi_strategy()