Nデイ・ブレイクアウト
NデイのN日高値/安値ブレイクアウト戦略。Nデイ・ブレイクアウトは、指定された期間における新しい高値または安値を探します。モメンタムを予測しながら、価格が直近のN日高値または安値を突破した時にエントリーが発生します。移動平均フィルターとパーセンテージストップがエグジットを管理します。
テスト結果では、年間平均リターンが約43%であることが示されています。株式市場で最も効果を発揮します。
前回の極値のブレイクを待つことで、システムは方向性のある動きの開始を捉えようとします。トレンドフォロー型の移動平均でフィルタリングすることで、保ち合い中に発生するダマシシグナルを回避するのに役立ちます。
詳細
- エントリー条件: MAに基づくシグナル。
- ロング/ショート: 両方の方向。
- エグジット条件: 逆シグナルまたはストップ。
- ストップ: はい。
- デフォルト値:
LookbackPeriod= 20MaPeriod= 20StopLossPercent= 2.0mCandleType= TimeSpan.FromHours(1) 付属の履歴データは1か月分のため、20本のウォームアップ後に残る日足は約10本で、日足では2024年3月のBTCで注文が1件も発生しません。そのため既定値は同じ期間の1時間足を使い、日足は設定で指定できます。
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: MA
- ストップ: はい
- 複雑さ: 基本
- 時間軸: 日足
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// N-day high/low breakout strategy.
/// Enters long when price pierces the high of the previous N candles while closing above the moving average,
/// and short when it pierces their low while closing below it.
/// Exits when the close crosses back through the moving average, on the opposite signal, or at the percent stop.
/// </summary>
public class NdayBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest;
private Lowest _lowest;
private SMA _ma;
// Channel of the candles before the current one; a candle never breaks out of a range it is part of.
private decimal? _channelHigh;
private decimal? _channelLow;
/// <summary>
/// Number of candles that form the high/low range.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Period of the moving average that filters entries and triggers exits.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public NdayBreakoutStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Number of candles that form the high/low range", "Strategy Parameters")
.SetOptimize(10, 30, 5);
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Moving average that filters entries and triggers exits", "Strategy Parameters")
.SetOptimize(10, 30, 5);
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "Strategy Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null;
_lowest = null;
_ma = null;
_channelHigh = null;
_channelLow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = LookbackPeriod };
_lowest = new Lowest { Length = LookbackPeriod };
_ma = new SMA { Length = MaPeriod };
_channelHigh = null;
_channelLow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles; an hourly candle alone would let it act once an hour.
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, _highest);
DrawIndicator(area, _lowest);
DrawIndicator(area, _ma);
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)
{
if (candle.State != CandleStates.Finished)
return;
var maValue = _ma.Process(candle.ClosePrice, candle.OpenTime, true);
// The breakout compares the candle with the range of the candles before it.
var channelHigh = _channelHigh;
var channelLow = _channelLow;
var highValue = _highest.Process(candle.HighPrice, candle.OpenTime, true);
var lowValue = _lowest.Process(candle.LowPrice, candle.OpenTime, true);
if (_highest.IsFormed && _lowest.IsFormed)
{
_channelHigh = highValue.GetValue<decimal>();
_channelLow = lowValue.GetValue<decimal>();
}
if (channelHigh is not decimal high || channelLow is not decimal low || !_ma.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma = maValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (candle.HighPrice > high && close > ma && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
}
else if (candle.LowPrice < low && close < ma && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
}
else if (Position > 0 && close < ma)
{
SellMarket(Position);
}
else if (Position < 0 && close > ma)
{
BuyMarket(-Position);
}
}
}
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 Highest, Lowest, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class nday_breakout_strategy(Strategy):
"""
N-day high/low breakout strategy.
Enters long when price pierces the high of the previous N candles while closing above the moving average,
and short when it pierces their low while closing below it.
Exits when the close crosses back through the moving average, on the opposite signal, or at the percent stop.
"""
def __init__(self):
super(nday_breakout_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Number of candles that form the high/low range", "Strategy Parameters") \
.SetOptimize(10, 30, 5)
self._ma_period = self.Param("MaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("MA Period", "Moving average that filters entries and triggers exits", "Strategy Parameters") \
.SetOptimize(10, 30, 5)
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management") \
.SetOptimize(1.0, 3.0, 0.5)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "Strategy Parameters")
self._highest = None
self._lowest = None
self._ma = None
# Channel of the candles before the current one; a candle never breaks out of a range it is part of.
self._channel_high = None
self._channel_low = None
def OnReseted(self):
super(nday_breakout_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._ma = None
self._channel_high = None
self._channel_low = None
def OnStarted2(self, time):
super(nday_breakout_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self._lookback_period.Value
self._lowest = Lowest()
self._lowest.Length = self._lookback_period.Value
self._ma = SimpleMovingAverage()
self._ma.Length = self._ma_period.Value
self._channel_high = None
self._channel_low = None
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(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; an hourly candle alone would let it act once an hour.
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, self._highest)
self.DrawIndicator(area, self._lowest)
self.DrawIndicator(area, self._ma)
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):
if candle.State != CandleStates.Finished:
return
ma_value = process_value(self._ma, candle.ClosePrice, candle.OpenTime, True)
# The breakout compares the candle with the range of the candles before it.
channel_high = self._channel_high
channel_low = self._channel_low
high_value = process_value(self._highest, candle.HighPrice, candle.OpenTime, True)
low_value = process_value(self._lowest, candle.LowPrice, candle.OpenTime, True)
if self._highest.IsFormed and self._lowest.IsFormed:
self._channel_high = to_decimal(high_value)
self._channel_low = to_decimal(low_value)
if channel_high is None or channel_low is None or not self._ma.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ma = to_decimal(ma_value)
close = candle.ClosePrice
position = self.Position
if candle.HighPrice > channel_high and close > ma and position <= 0:
self.BuyMarket(self.Volume + abs(position))
elif candle.LowPrice < channel_low and close < ma and position >= 0:
self.SellMarket(self.Volume + abs(position))
elif position > 0 and close < ma:
self.SellMarket(position)
elif position < 0 and close > ma:
self.BuyMarket(-position)
def CreateClone(self):
return nday_breakout_strategy()