Estratégia de Canal de Bandas de Regressão Polinomial
Esta estratégia ajusta uma linha de regressão polinomial aos preços recentes e constrói bandas superior e inferior a partir do desvio padrão dos resíduos. Posições compradas são abertas quando o preço cai abaixo da banda inferior e posições vendidas quando o preço sobe acima da banda superior.
Detalhes
Critérios de entrada:
Comprado: Close < LowerBand.
Vendido: Close > UpperBand.
Comprado/Vendido: Ambos.
Critérios de saída:
Sinal oposto.
Stops: Não.
Valores padrão:
Length = 100.
Degree = 2.
StdDevMultiplier = 2.
CandleType = TimeSpan.FromMinutes(5).TimeFrame().
Filtros:
Categoria: Reversão à média
Direção: Ambos
Indicadores: Regressão polinomial
Stops: Não
Complexidade: Moderado
Período: Médio prazo
Sazonalidade: Não
Redes neurais: Não
Divergência: Não
Nível de risco: Médio
Cálculo e execução
Cada vela concluída ajusta por mínimos quadrados um polinômio de grau Degree aos últimos Length fechamentos, incluindo o atual, em uma grade uniforme de -1 a 1. O centro do canal é o valor ajustado da observação mais recente. Largura: StdDevMultiplier * sqrt(sum((close[i] - fitted[i])^2) / Length), desvio populacional dos resíduos sem correção por graus de liberdade.
Fechamento estritamente abaixo da banda inferior abre compra; acima da superior abre venda a descoberto. Entrada oposta inverte toda a posição com uma ordem a mercado de Volume + abs(Position); sem posição usa Volume. Dentro das bandas mantém a posição. Só negocia com janela completa; sem espera ou stops protetores.
Configuração válida: 0 <= Degree <= 6, Length >= Degree + 2, StdDevMultiplier positivo. Grau zero é uma constante. A grade normalizada fixa usa uma base polinomial ortonormal pré-calculada com duas passagens de ortogonalização; cada janela projeta deslocamentos de preço nela com precisão double. C# e Python calculam igualmente; quantidades de ordens permanecem 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()