Estrategia Malr de Ruptura de Canal
Esta estrategia opera rupturas de un canal MALR (Moving Average Linear Regression) personalizado. El indicador MALR combina una media móvil simple y una media móvil lineal ponderada para formar una línea central. La desviación estándar del precio relativa a esta línea crea dos bandas exteriores.
Se abre una posición larga cuando la banda superior de ruptura cruza por debajo del precio de cierre, indicando una ruptura al alza. Se abre una posición corta cuando la banda inferior de ruptura cruza por encima del precio de cierre, señalando una ruptura a la baja.
Parámetros
MaPeriod– período para las medias móviles y la desviación estándar.ChannelReversal– anchura del canal MALR interior medida en desviaciones estándar.ChannelBreakout– anchura adicional para el canal exterior de ruptura.CandleType– tipo de velas utilizado para los cálculos.
Cómo funciona
- Calcular SMA y LWMA del precio de cierre.
- Computar la línea MALR
FF = 3 * LWMA - 2 * SMA. - Medir la desviación estándar de
close - FFsobre el mismo período. - Derivar bandas de ruptura:
FF ± StdDev * (ChannelReversal + ChannelBreakout). - Entrar largo cuando la banda superior cruza de arriba a abajo el cierre.
- Entrar corto cuando la banda inferior cruza de abajo a arriba el cierre.
La estrategia siempre cierra la posición opuesta antes de abrir una nueva.
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>
/// MALR channel breakout strategy.
/// Enters long when price breaks above the upper MALR band and short when breaking below the lower band.
/// </summary>
public class MalrChannelBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _channelReversal;
private readonly StrategyParam<decimal> _channelBreakout;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _sma;
private WeightedMovingAverage _lwma;
private StandardDeviation _stdDev;
private decimal? _prevUpper;
private decimal? _prevLower;
private decimal? _prevClose;
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public decimal ChannelReversal { get => _channelReversal.Value; set => _channelReversal.Value = value; }
public decimal ChannelBreakout { get => _channelBreakout.Value; set => _channelBreakout.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MalrChannelBreakoutStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("MA", "Moving average period", "General")
.SetOptimize(50, 200, 10);
_channelReversal = Param(nameof(ChannelReversal), 1.1m)
.SetGreaterThanZero()
.SetDisplay("Reversal", "Channel reversal width", "General")
.SetOptimize(0.5m, 2m, 0.1m);
_channelBreakout = Param(nameof(ChannelBreakout), 1.1m)
.SetGreaterThanZero()
.SetDisplay("Breakout", "Channel breakout width", "General")
.SetOptimize(0.5m, 2m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle", "Candle type", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = default;
_lwma = default;
_stdDev = default;
_prevUpper = null;
_prevLower = null;
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new SimpleMovingAverage { Length = MaPeriod };
_lwma = new WeightedMovingAverage { Length = MaPeriod };
_stdDev = new StandardDeviation { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
DrawIndicator(area, _lwma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var smaResult = _sma.Process(candle.ClosePrice, candle.OpenTime, true);
var lwmaResult = _lwma.Process(candle.ClosePrice, candle.OpenTime, true);
if (!smaResult.IsFormed || !lwmaResult.IsFormed)
{
_prevClose = candle.ClosePrice;
return;
}
var smaVal = smaResult.ToDecimal();
var lwmaVal = lwmaResult.ToDecimal();
var ff = 3m * lwmaVal - 2m * smaVal;
var deviation = candle.ClosePrice - ff;
var stdResult = _stdDev.Process(deviation, candle.OpenTime, true);
if (!stdResult.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevUpper = ff;
_prevLower = ff;
return;
}
var std = stdResult.ToDecimal();
var upper = ff + std * (ChannelReversal + ChannelBreakout);
var lower = ff - std * (ChannelReversal + ChannelBreakout);
if (_prevUpper.HasValue && _prevLower.HasValue && _prevClose.HasValue)
{
// Price breaks above upper channel
if (_prevClose.Value <= _prevUpper.Value && candle.ClosePrice > upper && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Price breaks below lower channel
else if (_prevClose.Value >= _prevLower.Value && candle.ClosePrice < lower && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
_prevUpper = upper;
_prevLower = lower;
_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
from StockSharp.Messages import DataType, CandleStates
from System import Decimal
from StockSharp.Algo.Indicators import SimpleMovingAverage, WeightedMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class malr_channel_breakout_strategy(Strategy):
def __init__(self):
super(malr_channel_breakout_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 120) \
.SetGreaterThanZero() \
.SetDisplay("MA", "Moving average period", "General") \
.SetOptimize(50, 200, 10)
self._channel_reversal = self.Param("ChannelReversal", 1.1) \
.SetGreaterThanZero() \
.SetDisplay("Reversal", "Channel reversal width", "General") \
.SetOptimize(0.5, 2.0, 0.1)
self._channel_breakout = self.Param("ChannelBreakout", 1.1) \
.SetGreaterThanZero() \
.SetDisplay("Breakout", "Channel breakout width", "General") \
.SetOptimize(0.5, 2.0, 0.1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle", "Candle type", "General")
self._sma = None
self._lwma = None
self._std_dev = None
self._prev_upper = None
self._prev_lower = None
self._prev_close = None
@property
def ma_period(self):
return self._ma_period.Value
@property
def channel_reversal(self):
return self._channel_reversal.Value
@property
def channel_breakout(self):
return self._channel_breakout.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(malr_channel_breakout_strategy, self).OnReseted()
self._prev_upper = None
self._prev_lower = None
self._prev_close = None
def OnStarted2(self, time):
super(malr_channel_breakout_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self.ma_period
self._lwma = WeightedMovingAverage()
self._lwma.Length = self.ma_period
self._std_dev = StandardDeviation()
self._std_dev.Length = self.ma_period
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._sma)
self.DrawIndicator(area, self._lwma)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sma_result = process_float(self._sma, candle.ClosePrice, candle.OpenTime, True)
lwma_result = process_float(self._lwma, candle.ClosePrice, candle.OpenTime, True)
if not sma_result.IsFormed or not lwma_result.IsFormed:
self._prev_close = close
return
sma_val = float(sma_result)
lwma_val = float(lwma_result)
ff = 3.0 * lwma_val - 2.0 * sma_val
deviation = close - ff
std_result = process_float(self._std_dev, Decimal(deviation), candle.OpenTime, True)
if not std_result.IsFormed:
self._prev_close = close
self._prev_upper = ff
self._prev_lower = ff
return
std = float(std_result)
cr = float(self.channel_reversal)
cb = float(self.channel_breakout)
upper = ff + std * (cr + cb)
lower = ff - std * (cr + cb)
if self._prev_upper is not None and self._prev_lower is not None and self._prev_close is not None:
if self._prev_close <= self._prev_upper and close > upper and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close >= self._prev_lower and close < lower and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_upper = upper
self._prev_lower = lower
self._prev_close = close
def CreateClone(self):
return malr_channel_breakout_strategy()