Estrategia de Volumen por Punto
Esta estrategia calcula el volumen por punto de precio para cada vela. Se abre una operación larga cuando el rango de la vela disminuye pero el volumen aumenta y el filtro RSI (si está activado) confirma la señal. Se abre una operación corta cuando el rango se expande mientras el volumen se contrae.
Parámetros
- RSI Length – período para el cálculo del RSI.
- RSI Above/Below – umbrales para el filtro RSI opcional.
- Use RSI Filter – activar o desactivar el filtrado RSI.
- Candle Type – marco temporal de las velas de entrada.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on volume per price point with RSI filter.
/// Buys when range decreases but volume increases, sells on opposite condition.
/// </summary>
public class VolumePerPointStrategy : Strategy
{
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiHigh;
private readonly StrategyParam<int> _rsiLow;
private readonly StrategyParam<bool> _useRsiFilter;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevRange;
private decimal _prevVolume;
private decimal _prevClose;
private int _cooldownRemaining;
/// <summary>
/// RSI length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Upper RSI threshold.
/// </summary>
public int RsiHigh
{
get => _rsiHigh.Value;
set => _rsiHigh.Value = value;
}
/// <summary>
/// Lower RSI threshold.
/// </summary>
public int RsiLow
{
get => _rsiLow.Value;
set => _rsiLow.Value = value;
}
/// <summary>
/// Use RSI filter.
/// </summary>
public bool UseRsiFilter
{
get => _useRsiFilter.Value;
set => _useRsiFilter.Value = value;
}
/// <summary>
/// Bars to wait between trading actions.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="VolumePerPointStrategy"/>.
/// </summary>
public VolumePerPointStrategy()
{
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "Period for RSI", "Indicators")
.SetOptimize(10, 20, 2);
_rsiHigh = Param(nameof(RsiHigh), 65)
.SetDisplay("RSI Above", "Upper RSI threshold", "Filters");
_rsiLow = Param(nameof(RsiLow), 35)
.SetDisplay("RSI Below", "Lower RSI threshold", "Filters");
_useRsiFilter = Param(nameof(UseRsiFilter), true)
.SetDisplay("Use RSI Filter", "Enable RSI filtering", "Filters");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_prevRange = 0;
_prevVolume = 0;
_prevClose = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (_prevRange == 0)
{
_prevRange = candle.HighPrice - candle.LowPrice;
_prevVolume = candle.TotalVolume;
_prevClose = candle.ClosePrice;
return;
}
var step = Security?.PriceStep ?? 0.0001m;
var range = Math.Max(candle.HighPrice - candle.LowPrice, step);
var previousRange = Math.Max(_prevRange, step);
var volume = candle.TotalVolume;
var volumePerPoint = volume / range;
var previousVolumePerPoint = _prevVolume / previousRange;
var bullishImpulse = candle.ClosePrice > candle.OpenPrice && candle.ClosePrice > _prevClose;
var bearishImpulse = candle.ClosePrice < candle.OpenPrice && candle.ClosePrice < _prevClose;
var buySignal = volumePerPoint >= previousVolumePerPoint * 1.5m && bullishImpulse && (!UseRsiFilter || rsiValue <= RsiLow);
var sellSignal = volumePerPoint >= previousVolumePerPoint * 1.5m && bearishImpulse && (!UseRsiFilter || rsiValue >= RsiHigh);
if (_cooldownRemaining == 0 && buySignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && sellSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
_prevRange = range;
_prevVolume = volume;
_prevClose = candle.ClosePrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Sides
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class volume_per_point_strategy(Strategy):
def __init__(self):
super(volume_per_point_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "Period for RSI", "Indicators")
self._rsi_high = self.Param("RsiHigh", 65) \
.SetDisplay("RSI Above", "Upper RSI threshold", "Filters")
self._rsi_low = self.Param("RsiLow", 35) \
.SetDisplay("RSI Below", "Lower RSI threshold", "Filters")
self._use_rsi_filter = self.Param("UseRsiFilter", True) \
.SetDisplay("Use RSI Filter", "Enable RSI filtering", "Filters")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 12) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._cooldown_remaining = 0
self._prev_range = 0.0
self._prev_volume = 0.0
self._prev_close = 0.0
@property
def rsi_length(self):
return self._rsi_length.Value
@property
def rsi_high(self):
return self._rsi_high.Value
@property
def rsi_low(self):
return self._rsi_low.Value
@property
def use_rsi_filter(self):
return self._use_rsi_filter.Value
@property
def signal_cooldown_bars(self):
return self._signal_cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_per_point_strategy, self).OnReseted()
self._cooldown_remaining = 0
self._prev_range = 0.0
self._prev_volume = 0.0
self._prev_close = 0.0
def OnStarted2(self, time):
super(volume_per_point_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self.on_process).Start()
self.StartProtection(Unit(2, UnitTypes.Percent), Unit(1, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def on_process(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
rsi_value = float(rsi_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_p = float(candle.OpenPrice)
if self._prev_range == 0:
self._prev_range = high - low
self._prev_volume = float(candle.TotalVolume)
self._prev_close = close
return
ps = self.Security.PriceStep if self.Security is not None else None
step = float(ps) if ps is not None else 0.0001
candle_range = max(high - low, step)
previous_range = max(self._prev_range, step)
volume = float(candle.TotalVolume)
volume_per_point = volume / candle_range
previous_volume_per_point = self._prev_volume / previous_range
bullish_impulse = close > open_p and close > self._prev_close
bearish_impulse = close < open_p and close < self._prev_close
buy_signal = volume_per_point >= previous_volume_per_point * 1.5 and bullish_impulse and (not self.use_rsi_filter or rsi_value <= float(self.rsi_low))
sell_signal = volume_per_point >= previous_volume_per_point * 1.5 and bearish_impulse and (not self.use_rsi_filter or rsi_value >= float(self.rsi_high))
if self._cooldown_remaining == 0 and buy_signal and self.Position <= 0:
self.BuyMarket()
self._cooldown_remaining = self.signal_cooldown_bars
elif self._cooldown_remaining == 0 and sell_signal and self.Position >= 0:
self.SellMarket()
self._cooldown_remaining = self.signal_cooldown_bars
self._prev_range = candle_range
self._prev_volume = volume
self._prev_close = close
def CreateClone(self):
return volume_per_point_strategy()