Nadaraya-Watson エンベロープ戦略
対数スケールでNadaraya-Watsonカーネル回帰エンベロープを構築します。価格が下部エンベロープを上抜けたときにロングを取り、オプションで上部エンベロープを下抜けたときにショートを取ります。
詳細
- エントリー条件:
- 終値が下部エンベロープを上抜けたときにロング。
- 終値が上部エンベロープを下抜けたときにショート(ロング/ショートモード時)。
- ロング/ショート: 設定可能。
- エグジット条件: 逆方向のエンベロープクロス。
- ストップ: なし。
- デフォルト値:
LookbackWindow= 8RelativeWeighting= 8StartRegressionBar= 25StrategyType= Long OnlyCandleType= TimeSpan.FromMinutes(1)
- フィルター:
- カテゴリ: Envelope
- 方向: 設定可能
- インジケーター: Nadaraya-Watson
- ストップ: なし
- 複雑さ: 上級
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Nadaraya-Watson envelope strategy.
/// The upper and lower envelopes are rational quadratic kernel regressions of log highs and log lows over the last
/// StartRegressionBar candles, with LookbackWindow as bandwidth and RelativeWeighting as the relative weight of time frames.
/// A close crossing above the lower envelope goes long, a close crossing below the upper envelope exits the long and,
/// in LongShort mode, goes short. A short exits when the close crosses above the lower envelope.
/// </summary>
public class NadarayaWatsonEnvelopeStrategy : Strategy
{
/// <summary>
/// Allowed trade directions.
/// </summary>
public enum StrategyTypes
{
/// <summary>
/// Only long trades.
/// </summary>
LongOnly,
/// <summary>
/// Long and short trades.
/// </summary>
LongShort,
}
private readonly StrategyParam<int> _lookbackWindow;
private readonly StrategyParam<decimal> _relativeWeighting;
private readonly StrategyParam<int> _startRegressionBar;
private readonly StrategyParam<StrategyTypes> _strategyType;
private readonly StrategyParam<DataType> _candleType;
private readonly List<double> _logHighs = [];
private readonly List<double> _logLows = [];
private decimal? _prevClose;
private decimal? _prevUpper;
private decimal? _prevLower;
/// <summary>
/// Kernel bandwidth in bars.
/// </summary>
public int LookbackWindow
{
get => _lookbackWindow.Value;
set => _lookbackWindow.Value = value;
}
/// <summary>
/// Relative weighting of time frames of the rational quadratic kernel.
/// </summary>
public decimal RelativeWeighting
{
get => _relativeWeighting.Value;
set => _relativeWeighting.Value = value;
}
/// <summary>
/// Number of bars used in the regression.
/// </summary>
public int StartRegressionBar
{
get => _startRegressionBar.Value;
set => _startRegressionBar.Value = value;
}
/// <summary>
/// Allowed trade directions.
/// </summary>
public StrategyTypes StrategyType
{
get => _strategyType.Value;
set => _strategyType.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public NadarayaWatsonEnvelopeStrategy()
{
_lookbackWindow = Param(nameof(LookbackWindow), 8)
.SetGreaterThanZero()
.SetDisplay("Lookback Window", "Kernel bandwidth in bars", "Kernel");
_relativeWeighting = Param(nameof(RelativeWeighting), 8m)
.SetGreaterThanZero()
.SetDisplay("Relative Weighting", "Relative weighting of time frames", "Kernel");
_startRegressionBar = Param(nameof(StartRegressionBar), 25)
.SetGreaterThanZero()
.SetDisplay("Start Regression Bar", "Number of bars used in the regression", "Kernel");
_strategyType = Param(nameof(StrategyType), StrategyTypes.LongOnly)
.SetDisplay("Strategy Type", "Long only or long and short", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_logHighs.Clear();
_logLows.Clear();
_prevClose = null;
_prevUpper = null;
_prevLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var h = (double)LookbackWindow;
var r = (double)RelativeWeighting;
var weights = new double[StartRegressionBar];
for (var i = 0; i < weights.Length; i++)
weights[i] = Math.Pow(1 + i * i / (h * h * 2 * r), -r);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(candle => ProcessCandle(candle, weights))
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
// Weighted average where index 0 is the latest bar.
private static decimal Estimate(List<double> values, double[] weights)
{
double sum = 0, weightSum = 0;
var last = values.Count - 1;
for (var i = 0; i < weights.Length; i++)
{
sum += values[last - i] * weights[i];
weightSum += weights[i];
}
return (decimal)Math.Exp(sum / weightSum);
}
private void ProcessCandle(ICandleMessage candle, double[] weights)
{
if (candle.State != CandleStates.Finished)
return;
if (candle.HighPrice <= 0 || candle.LowPrice <= 0)
return;
_logHighs.Add(Math.Log((double)candle.HighPrice));
_logLows.Add(Math.Log((double)candle.LowPrice));
if (_logHighs.Count > StartRegressionBar)
{
_logHighs.RemoveAt(0);
_logLows.RemoveAt(0);
}
if (_logHighs.Count < StartRegressionBar)
return;
var upper = Estimate(_logHighs, weights);
var lower = Estimate(_logLows, weights);
var close = candle.ClosePrice;
var prevClose = _prevClose;
var prevUpper = _prevUpper;
var prevLower = _prevLower;
_prevClose = close;
_prevUpper = upper;
_prevLower = lower;
if (prevClose is not decimal pc || prevUpper is not decimal pu || prevLower is not decimal pl)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var crossAboveLower = pc <= pl && close > lower;
var crossBelowUpper = pc >= pu && close < upper;
if (crossAboveLower && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
}
else if (crossBelowUpper && Position >= 0)
{
if (StrategyType == StrategyTypes.LongShort)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0)
SellMarket(Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class nadaraya_watson_envelope_strategy(Strategy):
"""
Nadaraya-Watson envelope strategy.
The upper and lower envelopes are rational quadratic kernel regressions of log highs and log lows over the last
StartRegressionBar candles, with LookbackWindow as bandwidth and RelativeWeighting as the relative weight of time frames.
A close crossing above the lower envelope goes long, a close crossing below the upper envelope exits the long and,
in LongShort mode, goes short. A short exits when the close crosses above the lower envelope.
StrategyType: LongOnly or LongShort.
"""
def __init__(self):
super(nadaraya_watson_envelope_strategy, self).__init__()
self._lookback_window = self.Param("LookbackWindow", 8).SetGreaterThanZero().SetDisplay("Lookback Window", "Kernel bandwidth in bars", "Kernel")
self._relative_weighting = self.Param("RelativeWeighting", 8.0).SetGreaterThanZero().SetDisplay("Relative Weighting", "Relative weighting of time frames", "Kernel")
self._start_regression_bar = self.Param("StartRegressionBar", 25).SetGreaterThanZero().SetDisplay("Start Regression Bar", "Number of bars used in the regression", "Kernel")
self._strategy_type = self.Param("StrategyType", "LongOnly").SetDisplay("Strategy Type", "Long only or long and short", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._weights = []
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _reset_state(self):
self._log_highs = []
self._log_lows = []
self._prev_close = None
self._prev_upper = None
self._prev_lower = None
def OnReseted(self):
super(nadaraya_watson_envelope_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(nadaraya_watson_envelope_strategy, self).OnStarted2(time)
self._reset_state()
h = float(self._lookback_window.Value)
r = float(self._relative_weighting.Value)
self._weights = [math.pow(1.0 + i * i / (h * h * 2.0 * r), -r) for i in range(self._start_regression_bar.Value)]
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.DrawOwnTrades(area)
# Weighted average where index 0 is the latest bar.
def _estimate(self, values):
total = 0.0
weight_sum = 0.0
last = len(values) - 1
for i, w in enumerate(self._weights):
total += values[last - i] * w
weight_sum += w
return math.exp(total / weight_sum)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if high <= 0 or low <= 0:
return
length = self._start_regression_bar.Value
self._log_highs.append(math.log(high))
self._log_lows.append(math.log(low))
if len(self._log_highs) > length:
self._log_highs.pop(0)
self._log_lows.pop(0)
if len(self._log_highs) < length:
return
upper = self._estimate(self._log_highs)
lower = self._estimate(self._log_lows)
close = float(candle.ClosePrice)
prev_close = self._prev_close
prev_upper = self._prev_upper
prev_lower = self._prev_lower
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
if prev_close is None or prev_upper is None or prev_lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
cross_above_lower = prev_close <= prev_lower and close > lower
cross_below_upper = prev_close >= prev_upper and close < upper
if cross_above_lower and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_below_upper and self.Position >= 0:
if str(self._strategy_type.Value) == "LongShort":
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0:
self.SellMarket(self.Position)
def CreateClone(self):
return nadaraya_watson_envelope_strategy()