Estrategia MA Volume
MA Volume combina un filtro de tendencia de media móvil con aumentos de volumen para sincronizar las entradas. El volumen creciente junto con el precio por encima de la media señala una acumulación fuerte; el volumen decreciente por debajo de la media indica distribución.
Las pruebas indican un rendimiento anual promedio de aproximadamente 136%. Funciona mejor en el mercado de acciones.
La estrategia opera en la dirección de la media móvil cuando el volumen se expande, saliendo una vez que el volumen se agota o la media se revierte.
Un stop porcentual protege contra cambios repentinos en la tendencia.
Detalles
- Criterios de entrada: señal de indicador
- Largo/Corto: Ambos
- Criterios de salida: stop-loss o señal opuesta
- Stops: Sí, basado en porcentaje
- Valores predeterminados:
CandleType= 15 minuteStopLossPercent= 2MaPeriod= 20VolumePeriod= 20VolumeThreshold= 1.2 El volumen se expande cuando el volumen de una vela supera VolumeThreshold veces la media de las VolumePeriod velas anteriores. Sin posición, una vela con expansión que cierra por encima de una SMA ascendente abre un largo y una que cierra por debajo de una SMA descendente abre un corto. La posición se cierra cuando el volumen cae por debajo de su media o la SMA gira en su contra.
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Ambos
- Indicadores: Moving Average, Volume
- 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 System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MA Volume strategy.
/// Volume expands when a candle's volume exceeds VolumeThreshold times the average of the previous VolumePeriod candles.
/// While flat, an expanding candle that closes above a rising MaPeriod SMA goes long and one that closes below a falling SMA goes short.
/// The position closes once volume dries up below its average or the SMA turns against it, and a percent stop limits the loss.
/// </summary>
public class MaVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<decimal> _volumeThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
private decimal? _prevMa;
/// <summary>
/// Period of the trend SMA.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// How many times the average volume an expanding candle must exceed.
/// </summary>
public decimal VolumeThreshold
{
get => _volumeThreshold.Value;
set => _volumeThreshold.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaVolumeStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period of the trend SMA", "Indicators");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Indicators");
_volumeThreshold = Param(nameof(VolumeThreshold), 1.2m)
.SetGreaterThanZero()
.SetDisplay("Volume Threshold", "How many times the average volume an expanding candle must exceed", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_volumes.Clear();
_prevMa = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
_prevMa = null;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
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();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Expansion is measured against the candles before this one.
var average = _volumes.Count == VolumePeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumePeriod)
_volumes.RemoveAt(0);
if (!smaValue.IsFormed)
return;
var ma = smaValue.GetValue<decimal>();
var prevMa = _prevMa;
_prevMa = ma;
if (average is not decimal avgVolume || prevMa is not decimal lastMa)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
if (Position > 0)
{
if (volume < avgVolume || ma < lastMa)
SellMarket(Position);
}
else if (Position < 0)
{
if (volume < avgVolume || ma > lastMa)
BuyMarket(-Position);
}
else if (volume > avgVolume * VolumeThreshold)
{
if (close > ma && ma > lastMa)
BuyMarket(Volume);
else if (close < ma && ma < lastMa)
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
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_volume_strategy(Strategy):
"""
MA Volume strategy.
Volume expands when a candle's volume exceeds VolumeThreshold times the average of the previous VolumePeriod candles.
While flat, an expanding candle that closes above a rising MaPeriod SMA goes long and one that closes below a falling SMA goes short.
The position closes once volume dries up below its average or the SMA turns against it, and a percent stop limits the loss.
"""
def __init__(self):
super(ma_volume_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period of the trend SMA", "Indicators")
self._volume_period = self.Param("VolumePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Indicators")
self._volume_threshold = self.Param("VolumeThreshold", 1.2).SetGreaterThanZero().SetDisplay("Volume Threshold", "How many times the average volume an expanding candle must exceed", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).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._volumes = []
self._prev_ma = None
def OnReseted(self):
super(ma_volume_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ma_volume_strategy, self).OnStarted2(time)
self._reset_state()
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
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()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
# Expansion is measured against the candles before this one.
period = self._volume_period.Value
average = None
if len(self._volumes) == period:
total = Decimal(0)
for volume in self._volumes:
total += volume
average = total / Decimal(period)
self._volumes.append(candle.TotalVolume)
if len(self._volumes) > period:
self._volumes.pop(0)
if not sma_value.IsFormed:
return
ma = sma_value.GetValue[Decimal](None)
prev_ma = self._prev_ma
self._prev_ma = ma
if average is None or prev_ma is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
volume = candle.TotalVolume
if self.Position > 0:
if volume < average or ma < prev_ma:
self.SellMarket(self.Position)
elif self.Position < 0:
if volume < average or ma > prev_ma:
self.BuyMarket(-self.Position)
elif volume > average * Decimal(self._volume_threshold.Value):
if close > ma and ma > prev_ma:
self.BuyMarket(self.Volume)
elif close < ma and ma < prev_ma:
self.SellMarket(self.Volume)
def CreateClone(self):
return ma_volume_strategy()