Ver no GitHub

Estratégia do Canal de Regressão E

Visão geral

A E Regression Channel Strategy reproduz o consultor especialista MetaTrader "e-Regr" usando a estratégia de alto nível de StockSharp API. Ajusta-se a uma curva de regressão polinomial aos preços de fecho recentes, constrói bandas equidistantes a partir do desvio padrão residual e reage quando o preço ultrapassa esses envelopes. A estratégia foi projetada para negociação de reversão à média com paradas de proteção opcionais, um filtro diário de volatilidade e uma janela de negociação intradiária.

Lógica de negociação

  1. Assine o período principal especificado por Candle Type e calcule um canal de regressão polinomial nos últimos fechamentos de Regression Length.
  2. A faixa intermediária é o ajuste de regressão; as bandas superior e inferior são deslocadas por Std Dev Multiplier multiplicado pelo desvio padrão residual.
  3. Feche qualquer posição comprada existente quando o fechamento da vela cruzar acima da faixa intermediária; fechar posições curtas quando o fechamento cair abaixo dele.
  4. Abra uma posição longa (após fechar qualquer exposição curta existente) quando o mínimo da vela atual tocar ou romper a banda inferior.
  5. Abra uma posição curta (após nivelar a exposição longa) quando a alta da vela atual tocar ou romper a faixa superior.
  6. Opcionalmente, acompanhe as posições abertas usando Trailing Activation e Trailing Distance assim que o preço se mover o suficiente a favor da negociação.
  7. Ignore novas entradas sempre que o intervalo da vela diária anterior exceder o limite Daily Range Filter ou o horário atual estiver fora da janela [Trade Start, Trade End).

Parâmetros

  • Volume – tamanho da ordem usado para cada entrada no mercado (as posições líquidas são achatadas antes da reversão).
  • Trade Start / Trade End – janela de negociação diária, suporta intervalos noturnos (por exemplo, 21h00–02h00).
  • Regression Length – número de velas utilizadas para o ajuste da regressão polinomial.
  • Degree – grau polinomial (1–6) aplicado ao modelo de regressão.
  • Std Dev Multiplier – multiplicador aplicado ao desvio padrão residual da regressão para formar as bandas.
  • Enable Trailing – alterna o gerenciamento do trailing stop.
  • Trailing Activation – número de pontos de movimento favorável necessários antes do início do trailing.
  • Trailing Distance – buffer de rastreamento mantido quando o rastreamento está ativo (em pontos).
  • Stop Loss – distância de parada protetora em pontos (0 desativa a parada automática).
  • Take Profit – distância da meta de lucro protetora em pontos (0 desativa a meta automática).
  • Daily Range Filter – intervalo máximo permitido da vela diária anterior, expresso em pontos.
  • Candle Type – período para a série de preços primária (período padrão de 30 minutos).

Configurações padrão

  • Volume = 0,1
  • Trade Start = 03:00
  • Trade End = 21:20
  • Regression Length = 250 barras
  • Degree = 3
  • Std Dev Multiplier = 1,0
  • Enable Trailing = falso
  • Trailing Activation = 30 pontos
  • Trailing Distance = 30 pontos
  • Stop Loss = 0 pontos (desativado)
  • Take Profit = 0 pontos (desativado)
  • Daily Range Filter = 150 pontos
  • Candle Type = velas de 30 minutos

Notas adicionais

  • A estratégia usa a última vela finalizada para todas as decisões e nunca negocia várias vezes na mesma barra.
  • O trailing interrompe o fechamento de posições por mercado quando o preço atinge o nível de trailing calculado internamente.
  • Se o dia anterior for muito volátil (faixa acima do filtro configurado), as posições existentes serão fechadas e novas entradas serão suspensas pelo restante da barra.
  • O canal de regressão é redesenhado no gráfico a cada atualização para ajudar a visualizar as bandas média, superior e inferior.
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>
/// Polynomial regression channel strategy. Calculates a regression midline with
/// standard deviation bands and trades mean reversion between the bands and midline.
/// </summary>
public class ERegressionChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _regressionLength;
	private readonly StrategyParam<int> _degree;
	private readonly StrategyParam<decimal> _stdMultiplier;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _closes = new();
	private ExponentialMovingAverage _ema;

	private decimal? _previousMid;

	/// <summary>
	/// Initializes a new instance of the <see cref="ERegressionChannelStrategy"/> class.
	/// </summary>
	public ERegressionChannelStrategy()
	{
		_regressionLength = Param(nameof(RegressionLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("Regression Length", "Number of bars used for regression", "Regression");

		_degree = Param(nameof(Degree), 3)
			.SetGreaterThanZero()
			.SetDisplay("Degree", "Polynomial degree for the regression", "Regression");

		_stdMultiplier = Param(nameof(StdDevMultiplier), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Std Dev Multiplier", "Width multiplier for the regression bands", "Regression");

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Protective stop in absolute points (0 disables)", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Target in absolute points (0 disables)", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle type used for trading", "General");

		Volume = 1;
	}

	/// <summary>
	/// Number of bars used for regression.
	/// </summary>
	public int RegressionLength
	{
		get => _regressionLength.Value;
		set => _regressionLength.Value = value;
	}

	/// <summary>
	/// Polynomial degree for the regression.
	/// </summary>
	public int Degree
	{
		get => _degree.Value;
		set => _degree.Value = value;
	}

	/// <summary>
	/// Width multiplier for the regression bands.
	/// </summary>
	public decimal StdDevMultiplier
	{
		get => _stdMultiplier.Value;
		set => _stdMultiplier.Value = value;
	}

	/// <summary>
	/// Protective stop in absolute points (0 disables).
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Target in absolute points (0 disables).
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Primary candle type used for trading.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_closes.Clear();
		_previousMid = null;
		_ema = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		_ema = new ExponentialMovingAverage { Length = Math.Max(2, RegressionLength) };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_ema, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _ema);
			DrawOwnTrades(area);
		}

		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		if (tp != null || sl != null)
			StartProtection(tp, sl);

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_closes.Enqueue(candle.ClosePrice);
		if (_closes.Count > RegressionLength)
			_closes.Dequeue();

		if (_closes.Count < RegressionLength)
			return;

		var prices = new List<decimal>(_closes);
		var coeffs = PolyFit(prices, Degree);
		var currentIndex = prices.Count - 1;
		var mid = PolyEval(coeffs, currentIndex);
		var std = CalcStd(prices, coeffs) * StdDevMultiplier;
		var upper = mid + std;
		var lower = mid - std;

		// Exit at midline
		if (Position > 0 && candle.ClosePrice >= mid)
		{
			SellMarket(Position);
			_previousMid = mid;
			return;
		}
		if (Position < 0 && candle.ClosePrice <= mid)
		{
			BuyMarket(Math.Abs(Position));
			_previousMid = mid;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousMid = mid;
			return;
		}

		// Entry signals: mean reversion from bands
		if (candle.LowPrice <= lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
		}
		else if (candle.HighPrice >= upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(Volume);
		}

		_previousMid = mid;
	}

	private static decimal[] PolyFit(IReadOnlyList<decimal> y, int degree)
	{
		var n = y.Count;
		var actualDegree = Math.Min(degree, Math.Max(1, n - 1));
		var size = actualDegree + 1;
		var matrix = new decimal[size, size + 1];

		for (var row = 0; row < size; row++)
		{
			for (var col = 0; col < size; col++)
			{
				decimal sum = 0m;
				for (var i = 0; i < n; i++)
					sum += (decimal)Math.Pow(i, row + col);

				matrix[row, col] = sum;
			}

			decimal sumY = 0m;
			for (var i = 0; i < n; i++)
				sumY += y[i] * (decimal)Math.Pow(i, row);

			matrix[row, size] = sumY;
		}

		for (var i = 0; i < size; i++)
		{
			var pivot = matrix[i, i];
			if (pivot == 0m)
			{
				for (var k = i + 1; k < size; k++)
				{
					if (matrix[k, i] == 0m)
						continue;

					for (var j = i; j < size + 1; j++)
						(matrix[i, j], matrix[k, j]) = (matrix[k, j], matrix[i, j]);

					pivot = matrix[i, i];
					break;
				}
			}

			if (pivot == 0m)
				continue;

			for (var j = i; j < size + 1; j++)
				matrix[i, j] /= pivot;

			for (var row = 0; row < size; row++)
			{
				if (row == i)
					continue;

				var factor = matrix[row, i];
				if (factor == 0m)
					continue;

				for (var col = i; col < size + 1; col++)
					matrix[row, col] -= factor * matrix[i, col];
			}
		}

		var coeffs = new decimal[size];
		for (var i = 0; i < size; i++)
			coeffs[i] = matrix[i, size];

		return coeffs;
	}

	private static decimal PolyEval(IReadOnlyList<decimal> coeffs, decimal x)
	{
		decimal result = 0m;
		decimal power = 1m;
		for (var i = 0; i < coeffs.Count; i++)
		{
			result += coeffs[i] * power;
			power *= x;
		}

		return result;
	}

	private static decimal CalcStd(IReadOnlyList<decimal> values, decimal[] coeffs)
	{
		var n = values.Count;
		if (n == 0)
			return 0m;

		decimal sum = 0m;
		for (var i = 0; i < n; i++)
		{
			var fitted = PolyEval(coeffs, i);
			var diff = values[i] - fitted;
			sum += diff * diff;
		}

		return (decimal)Math.Sqrt((double)(sum / n));
	}
}