Estrategia Volatility Adjusted Moving Average
Esta técnica modifica una banda de media móvil por un múltiplo del ATR. Cuando el precio se mueve más allá de la banda ajustada, indica una tendencia acelerada.
Las pruebas indican un rendimiento anual promedio de aproximadamente 160%. Funciona mejor en el mercado forex.
Las operaciones largas se abren por encima de la banda superior, las cortas por debajo de la banda inferior. Un cruce de vuelta a través de la media móvil base cierra la posición.
Dado que las bandas se expanden con la volatilidad, los stops se adaptan a las condiciones del mercado.
Detalles
- Criterios de entrada: El precio rompe por encima o por debajo de MA ± multiplicador ATR.
- Largo/Corto: Ambas direcciones.
- Criterios de salida: El precio cruza la MA o stop.
- Stops: Sí.
- Valores predeterminados:
MAPeriod= 20ATRPeriod= 14ATRMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Tendencia
- Dirección: Ambos
- Indicadores: MA, ATR
- 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>
/// Vol Adjusted MA strategy.
/// Enters long when price is above MA + k*ATR, short when below MA - k*ATR.
/// Exits when price returns to MA.
/// </summary>
public class VolAdjustedMaStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private int _cooldown;
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// ATR Period.
/// </summary>
public int ATRPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier (k).
/// </summary>
public decimal ATRMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize the VolAdjustedMa strategy.
/// </summary>
public VolAdjustedMaStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_atrPeriod = Param(nameof(ATRPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 28, 7);
_atrMultiplier = Param(nameof(ATRMultiplier), 2.0m)
.SetDisplay("ATR Multiplier", "Multiplier for ATR to adjust MA bands", "Entry")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cooldown = 0;
var ma = new SimpleMovingAverage { Length = MAPeriod };
var atr = new AverageTrueRange { Length = ATRPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var adjustedUpperBand = maValue + ATRMultiplier * atrValue;
var adjustedLowerBand = maValue - ATRMultiplier * atrValue;
if (Position == 0)
{
if (candle.ClosePrice > adjustedUpperBand)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (candle.ClosePrice < adjustedLowerBand)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0 && candle.ClosePrice < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > maValue)
{
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class vol_adjusted_ma_strategy(Strategy):
"""
Vol Adjusted MA strategy.
Enters long when price is above MA + k*ATR, short when below MA - k*ATR.
Exits when price returns to MA.
"""
def __init__(self):
super(vol_adjusted_ma_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
self._atr_period = self.Param("ATRPeriod", 14).SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._atr_multiplier = self.Param("ATRMultiplier", 2.0).SetDisplay("ATR Multiplier", "Multiplier for ATR to adjust MA bands", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vol_adjusted_ma_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(vol_adjusted_ma_strategy, self).OnStarted2(time)
self._cooldown = 0
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, atr_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
return
mv = float(ma_val)
av = float(atr_val)
mult = float(self._atr_multiplier.Value)
close = float(candle.ClosePrice)
cd = self._cooldown_bars.Value
upper_band = mv + mult * av
lower_band = mv - mult * av
if self.Position == 0:
if close > upper_band:
self.BuyMarket()
self._cooldown = cd
elif close < lower_band:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and close < mv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > mv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return vol_adjusted_ma_strategy()