多項式回帰バンドチャネル戦略
この戦略は直近の価格に多項式回帰線を当てはめ、残差の標準偏差から上下バンドを構築します。価格が下バンドを下回るとロングポジションを開き、価格が上バンドを上回るとショートポジションを開きます。
詳細
- エントリー条件:
- ロング:
Close < LowerBand. - ショート:
Close > UpperBand.
- ロング:
- ロング/ショート: 両方。
- エグジット条件:
- 逆のシグナル。
- ストップ: なし。
- デフォルト値:
Length= 100.Degree= 2.StdDevMultiplier= 2.CandleType= TimeSpan.FromMinutes(5).TimeFrame().
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: 多項式回帰
- ストップ: いいえ
- 複雑さ: 中程度
- 時間軸: 中期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
計算と執行
確定足ごとに、現在の終値を含む直近 Length 個の終値に対し、-1から1までの等間隔格子上で次数 Degree の多項式を最小二乗法で当てはめる。チャネル中心は最新点の当てはめ値。幅は StdDevMultiplier * sqrt(sum((close[i] - fitted[i])^2) / Length)、自由度補正なしの残差の母標準偏差となる。
終値が下限を厳密に下回るとロング、上限を厳密に上回るとショート。反対エントリーは数量 Volume + abs(Position) の1件の成行注文で全反転し、保有なしの新規注文は Volume。バンド内では保有を維持する。全ウィンドウが揃うまで取引せず、待機時間や保護ストップはない。
有効設定:0 <= Degree <= 6、Length >= Degree + 2、正の StdDevMultiplier。次数0は定数。固定の正規化格子で2回の直交化を行った正規直交多項式基底を事前計算し、各ウィンドウの価格差分をdouble精度で射影する。C#とPythonで計算は共通、注文数量はdecimalを維持する。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Mean reversion outside a polynomial regression endpoint and its residual deviation bands.
/// </summary>
public class PolynomialRegressionBandsChannelStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _degree;
private readonly StrategyParam<decimal> _stdDevMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _prices = [];
private double[][] _basis;
/// <summary>
/// Initializes the published regression length, degree and band multiplier.
/// </summary>
public PolynomialRegressionBandsChannelStrategy()
{
_length = Param(nameof(Length), 100).SetGreaterThanZero()
.SetDisplay("Length", "Rolling regression window, including the current close", "Indicators");
_degree = Param(nameof(Degree), 2).SetRange(0, 6)
.SetDisplay("Degree", "Polynomial degree from zero through six", "Indicators");
_stdDevMultiplier = Param(nameof(StdDevMultiplier), 2m).SetGreaterThanZero()
.SetDisplay("Std Dev Multiplier", "Population deviation multiplier of fitted residuals", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of finished candles", "General");
}
/// <summary>
/// Number of finished closes in the regression window.
/// </summary>
public int Length { get => _length.Value; set => _length.Value = value; }
/// <summary>
/// Polynomial degree from zero through six; the window needs at least two more observations.
/// </summary>
public int Degree { get => _degree.Value; set => _degree.Value = value; }
/// <summary>
/// Positive multiplier of the population residual deviation.
/// </summary>
public decimal StdDevMultiplier { get => _stdDevMultiplier.Value; set => _stdDevMultiplier.Value = value; }
/// <summary>
/// Candle type; five minutes by default.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_basis = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Degree < 0 || Degree > 6 || Length < Degree + 2)
throw new ArgumentException("Degree must be between zero and six and Length at least Degree + 2.");
_prices.Clear();
_basis = BuildBasis(Length, Degree);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private static double[][] BuildBasis(int length, int degree)
{
// The fixed grid permits one QR basis for every rolling window.
var basis = new double[degree + 1][];
for (var column = 0; column <= degree; column++)
{
var values = new double[length];
for (var row = 0; row < length; row++)
values[row] = Math.Pow(2.0 * row / (length - 1) - 1.0, column);
for (var pass = 0; pass < 2; pass++)
{
for (var previous = 0; previous < column; previous++)
{
var projection = 0.0;
for (var row = 0; row < length; row++)
projection += values[row] * basis[previous][row];
for (var row = 0; row < length; row++)
values[row] -= projection * basis[previous][row];
}
}
var squaredNorm = 0.0;
foreach (var value in values)
squaredNorm += value * value;
var norm = Math.Sqrt(squaredNorm);
for (var row = 0; row < length; row++)
values[row] /= norm;
basis[column] = values;
}
return basis;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_prices.Add(candle.ClosePrice);
if (_prices.Count > Length)
_prices.RemoveAt(0);
if (_prices.Count < Length || !IsFormedAndOnlineAndAllowTrading())
return;
var values = new double[Length];
for (var row = 0; row < Length; row++)
values[row] = (double)(_prices[row] - _prices[0]);
var coefficients = new double[_basis.Length];
for (var column = 0; column < _basis.Length; column++)
{
for (var row = 0; row < Length; row++)
coefficients[column] += values[row] * _basis[column][row];
}
var squaredError = 0.0;
var center = 0.0;
for (var row = 0; row < Length; row++)
{
var fitted = 0.0;
for (var column = 0; column < _basis.Length; column++)
fitted += coefficients[column] * _basis[column][row];
var residual = values[row] - fitted;
squaredError += residual * residual;
if (row == Length - 1)
center = fitted;
}
var width = (double)StdDevMultiplier * Math.Sqrt(squaredError / Length);
if (values[^1] < center - width && Position <= 0m)
BuyMarket(Volume + Math.Abs(Position));
else if (values[^1] > center + width && Position >= 0m)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import ArgumentException, Decimal, Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class polynomial_regression_bands_channel_strategy(Strategy):
def __init__(self):
super(polynomial_regression_bands_channel_strategy, self).__init__()
self._length = self.Param("Length", 100).SetGreaterThanZero() \
.SetDisplay("Length", "Rolling regression window, including the current close", "Indicators")
self._degree = self.Param("Degree", 2).SetRange(0, 6) \
.SetDisplay("Degree", "Polynomial degree from zero through six", "Indicators")
self._std_dev_multiplier = self.Param("StdDevMultiplier", Decimal.Parse("2")).SetGreaterThanZero() \
.SetDisplay("Std Dev Multiplier", "Population deviation multiplier of fitted residuals", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of finished candles", "General")
self._prices = []
self._basis = None
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(polynomial_regression_bands_channel_strategy, self).OnReseted()
self._prices = []
self._basis = None
def OnStarted2(self, time):
super(polynomial_regression_bands_channel_strategy, self).OnStarted2(time)
length = self._length.Value
degree = self._degree.Value
if degree < 0 or degree > 6 or length < degree + 2:
raise ArgumentException("Degree must be between zero and six and Length at least Degree + 2.")
self._prices = []
self._basis = self._build_basis(length, degree)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
@staticmethod
def _build_basis(length, degree):
basis = []
for column in range(degree + 1):
values = [Math.Pow(2.0 * row / (length - 1) - 1.0, column) for row in range(length)]
for unused in range(2):
for previous in basis:
projection = sum(value * previous[row] for row, value in enumerate(values))
values = [value - projection * previous[row] for row, value in enumerate(values)]
norm = Math.Sqrt(sum(value * value for value in values))
basis.append([value / norm for value in values])
return basis
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._prices.append(candle.ClosePrice)
length = self._length.Value
if len(self._prices) > length:
self._prices.pop(0)
if len(self._prices) < length or not self.IsFormedAndOnlineAndAllowTrading():
return
origin = self._prices[0]
values = [float(price - origin) for price in self._prices]
coefficients = [sum(value * column[row] for row, value in enumerate(values)) for column in self._basis]
squared_error = 0.0
center = 0.0
for row, value in enumerate(values):
fitted = sum(coefficients[column] * vector[row] for column, vector in enumerate(self._basis))
residual = value - fitted
squared_error += residual * residual
if row == length - 1:
center = fitted
width = float(self._std_dev_multiplier.Value) * Math.Sqrt(squared_error / length)
if values[-1] < center - width and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif values[-1] > center + width and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return polynomial_regression_bands_channel_strategy()