MALR 渠道突破策略
该策略交易自定义 MALR(移动平均线线性回归)渠道的突破。MALR 指标结合简单移动平均线和线性加权移动平均线形成中心线,价格相对于该线的标准差形成外部通道。
当上轨从上方跌破收盘价时,认为向上突破并开多仓;当下轨从下方突破收盘价时,认为向下突破并开空仓。
参数
MaPeriod– 移动平均和标准差的周期。ChannelReversal– 内部 MALR 通道的宽度(标准差倍数)。ChannelBreakout– 外部突破通道的附加宽度。CandleType– 计算所使用的 K 线类型。
工作原理
- 计算收盘价的 SMA 和 LWMA。
- 计算 MALR 线
FF = 3 * LWMA - 2 * SMA。 - 在相同周期内测量
close - FF的标准差。 - 得到突破边界:
FF ± StdDev * (ChannelReversal + ChannelBreakout)。 - 当上轨从上向下穿过收盘价时开多仓。
- 当下轨从下向上穿过收盘价时开空仓。
在开新仓前策略会平掉相反方向的仓位。
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()