Cruce de MA de Volumen (Volume MA Cross)
Esta estrategia procesa el volumen a través de medias móviles rápida y lenta. Cuando la MA de volumen rápida cruza por encima de la MA de volumen lenta, indica mayor participación y genera una entrada en largo. Un cruce por debajo señala debilidad e inicia una posición corta.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 46%. Funciona mejor en el mercado de acciones.
Las posiciones se cierran cuando se produce el cruce inverso. El precio se monitorea con su propia media móvil para ayudar a filtrar las operaciones.
Las señales basadas en volumen a menudo preceden al movimiento del precio, permitiendo entradas tempranas.
Detalles
- Criterios de entrada: La MA de volumen rápida cruza la MA de volumen lenta.
- Largo/Corto: Ambos.
- Criterios de salida: Cruce inverso o stop.
- Stops: Sí.
- Valores predeterminados:
FastVolumeMALength= 10SlowVolumeMALength= 50CandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Momentum
- Dirección: Ambos
- Indicadores: Volume MA
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía
- 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>
/// Trades actual fast/slow native TotalVolume SMA crosses with Close/SMA entry confirmation.
/// Fully exits on the opposite volume cross or actual-fill percent protection.
/// </summary>
public class VolumeMAXrossStrategy : Strategy
{
private readonly StrategyParam<int> _priceMaPeriod;
private readonly StrategyParam<int> _fastVolumeMaLength;
private readonly StrategyParam<int> _slowVolumeMaLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private SimpleMovingAverage _priceMa;
private SimpleMovingAverage _fastVolumeMa;
private SimpleMovingAverage _slowVolumeMa;
private decimal? _previousFast;
private decimal? _previousSlow;
private Order _pendingOrder;
public int PriceMaPeriod { get => _priceMaPeriod.Value; set => _priceMaPeriod.Value = value; }
public int FastVolumeMALength { get => _fastVolumeMaLength.Value; set => _fastVolumeMaLength.Value = value; }
public int SlowVolumeMALength { get => _slowVolumeMaLength.Value; set => _slowVolumeMaLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public VolumeMAXrossStrategy()
{
_priceMaPeriod = Param(nameof(PriceMaPeriod), 20).SetGreaterThanZero()
.SetDisplay("Price MA Period", "Current-inclusive Close SMA entry filter", "Indicators");
_fastVolumeMaLength = Param(nameof(FastVolumeMALength), 10).SetGreaterThanZero()
.SetDisplay("Fast Volume MA Length", "Current-inclusive fast TotalVolume SMA length", "Indicators");
_slowVolumeMaLength = Param(nameof(SlowVolumeMALength), 50).SetGreaterThanZero()
.SetDisplay("Slow Volume MA Length", "Current-inclusive slow TotalVolume SMA length", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearSignalState();
}
private void ClearSignalState()
{
_priceMa = null;
_fastVolumeMa = null;
_slowVolumeMa = null;
_previousFast = null;
_previousSlow = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearSignalState();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
_priceMa = new SimpleMovingAverage { Length = PriceMaPeriod, Name = "Price SMA" };
_fastVolumeMa = new SimpleMovingAverage { Length = FastVolumeMALength, Name = "Fast volume SMA" };
_slowVolumeMa = new SimpleMovingAverage { Length = SlowVolumeMALength, Name = "Slow volume SMA" };
Indicators.Add(_priceMa);
Indicators.Add(_fastVolumeMa);
Indicators.Add(_slowVolumeMa);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _priceMa);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var priceValue = _priceMa.Process(candle);
// Process actual candle TotalVolume, not a price or previous-volume proxy.
var fastValue = _fastVolumeMa.Process(new DecimalIndicatorValue(_fastVolumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
var slowValue = _slowVolumeMa.Process(new DecimalIndicatorValue(_slowVolumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
decimal? priceMa = !priceValue.IsEmpty && _priceMa.IsFormed ? priceValue.GetValue<decimal>() : null;
decimal? fast = !fastValue.IsEmpty && _fastVolumeMa.IsFormed ? fastValue.GetValue<decimal>() : null;
decimal? slow = !slowValue.IsEmpty && _slowVolumeMa.IsFormed ? slowValue.GetValue<decimal>() : null;
var up = _previousFast is decimal oldFastUp && _previousSlow is decimal oldSlowUp &&
fast is decimal currentFastUp && slow is decimal currentSlowUp &&
oldFastUp <= oldSlowUp && currentFastUp > currentSlowUp;
var down = _previousFast is decimal oldFastDown && _previousSlow is decimal oldSlowDown &&
fast is decimal currentFastDown && slow is decimal currentSlowDown &&
oldFastDown >= oldSlowDown && currentFastDown < currentSlowDown;
// Independently formed values seed the next cross even while trading is unavailable.
_previousFast = fast;
_previousSlow = slow;
if (!IsFormedAndOnlineAndAllowTrading() ||
_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (Position > 0m && down) SellMarket(Position);
else if (Position < 0m && up) BuyMarket(Math.Abs(Position));
else if (Position == 0m && priceMa is decimal mean)
{
if (up && candle.ClosePrice > mean) BuyMarket(Volume);
else if (down && candle.ClosePrice < mean) SellMarket(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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage, DecimalIndicatorValue, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class volume_ma_cross_strategy(Strategy):
"""
Trades actual fast/slow native TotalVolume SMA crosses with Close/SMA entry confirmation.
Fully exits on the opposite volume cross or actual-fill percent protection.
"""
def __init__(self):
super(volume_ma_cross_strategy, self).__init__()
self._price_ma_period = self.Param("PriceMaPeriod", 20).SetGreaterThanZero().SetDisplay("Price MA Period", "Current-inclusive Close SMA entry filter", "Indicators")
self._fast_volume_ma_length = self.Param("FastVolumeMALength", 10).SetGreaterThanZero().SetDisplay("Fast Volume MA Length", "Current-inclusive fast TotalVolume SMA length", "Indicators")
self._slow_volume_ma_length = self.Param("SlowVolumeMALength", 50).SetGreaterThanZero().SetDisplay("Slow Volume MA Length", "Current-inclusive slow TotalVolume SMA length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._price_ma = None
self._fast_volume_ma = None
self._slow_volume_ma = None
self._previous_fast = None
self._previous_slow = None
self._pending_order = None
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def _clear_signal_state(self):
self._price_ma = None
self._fast_volume_ma = None
self._slow_volume_ma = None
self._previous_fast = None
self._previous_slow = None
self._pending_order = None
def OnReseted(self):
super(volume_ma_cross_strategy, self).OnReseted()
self._clear_signal_state()
def OnStarted2(self, time):
super(volume_ma_cross_strategy, self).OnStarted2(time)
self._clear_signal_state()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
self._price_ma = SimpleMovingAverage()
self._price_ma.Length = self._price_ma_period.Value
self._price_ma.Name = "Price SMA"
self._fast_volume_ma = SimpleMovingAverage()
self._fast_volume_ma.Length = self._fast_volume_ma_length.Value
self._fast_volume_ma.Name = "Fast volume SMA"
self._slow_volume_ma = SimpleMovingAverage()
self._slow_volume_ma.Length = self._slow_volume_ma_length.Value
self._slow_volume_ma.Name = "Slow volume SMA"
self.Indicators.Add(self._price_ma)
self.Indicators.Add(self._fast_volume_ma)
self.Indicators.Add(self._slow_volume_ma)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._price_ma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between finished candles.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price_value = self._price_ma.Process(CandleIndicatorValue(self._price_ma, candle))
# Use actual current TotalVolume for both native SMAs, including while unavailable.
fast_input = DecimalIndicatorValue(self._fast_volume_ma, candle.TotalVolume, candle.OpenTime)
fast_input.IsFinal = True
fast_value = self._fast_volume_ma.Process(fast_input)
slow_input = DecimalIndicatorValue(self._slow_volume_ma, candle.TotalVolume, candle.OpenTime)
slow_input.IsFinal = True
slow_value = self._slow_volume_ma.Process(slow_input)
price_ma = price_value.GetValue[Decimal](None) if not price_value.IsEmpty and self._price_ma.IsFormed else None
fast = fast_value.GetValue[Decimal](None) if not fast_value.IsEmpty and self._fast_volume_ma.IsFormed else None
slow = slow_value.GetValue[Decimal](None) if not slow_value.IsEmpty and self._slow_volume_ma.IsFormed else None
up = self._previous_fast is not None and self._previous_slow is not None and fast is not None and slow is not None and self._previous_fast <= self._previous_slow and fast > slow
down = self._previous_fast is not None and self._previous_slow is not None and fast is not None and slow is not None and self._previous_fast >= self._previous_slow and fast < slow
# Formed values seed the next cross even while trading is disabled or pending.
self._previous_fast = fast
self._previous_slow = slow
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
if self.Position > 0 and down:
self.SellMarket(self.Position)
elif self.Position < 0 and up:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and price_ma is not None:
if up and candle.ClosePrice > price_ma:
self.BuyMarket(self.Volume)
elif down and candle.ClosePrice < price_ma:
self.SellMarket(self.Volume)
def CreateClone(self):
return volume_ma_cross_strategy()