Malr チャネルブレイクアウト戦略
この戦略は、カスタムMALR(Moving Average Linear Regression)チャネルのブレイクアウトを取引します。MALRインジケーターは単純移動平均と線形加重移動平均を組み合わせて中心線を形成します。この線に対する価格の標準偏差が2つの外側バンドを作成します。
上側のブレイクアウトバンドが終値を下抜けすると、上方向のブレイクアウトを示すロングポジションが建てられます。下側のブレイクアウトバンドが終値を上抜けすると、下方向のブレイクアウトを示すショートポジションが建てられます。
パラメーター
MaPeriod– 移動平均と標準偏差の期間。ChannelReversal– 標準偏差で測定した内側MALRチャネルの幅。ChannelBreakout– 外側ブレイクアウトチャネルの追加幅。CandleType– 計算に使用するローソク足の種類。
動作原理
- 終値の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()