E 回帰チャネル戦略
概要
E 回帰チャネル戦略 は、StockSharp の高レベル戦略 API を使用して、MetaTrader の「e-Regr」エキスパート アドバイザーを再現します。多項式回帰曲線を最近の終値に当てはめ、残差標準偏差から等距離バンドを構築し、価格がそれらのエンベロープを突き抜けたときに反応します。この戦略は、オプションの保護ストップ、日次ボラティリティフィルター、日中取引ウィンドウを備えた平均回帰取引向けに設計されています。
取引ロジック
Candle Typeで指定されたメインのタイムフレームをサブスクライブし、最後のRegression Lengthの終値で多項式回帰チャネルを計算します。- 中央のバンドは回帰フィットです。上部と下部のバンドは、
Std Dev Multiplierに残差標準偏差を乗算したものだけシフトされます。 - ローソク足の終値がミドルバンドを上抜けたら、既存のロングポジションを閉じます。終値がそれを下回ったときにショートポジションを閉じます。
- 現在のローソク足の安値が下側のバンドに触れるか、下側のバンドを下回ったときに、ロングポジションをオープンします(既存のショートエクスポージャーをクローズした後)。
- 現在のローソク足の高値が上部バンドに触れるか、それを突破したときに(長時間露光を平坦化した後)ショートポジションをオープンします。
- 必要に応じて、価格が取引に十分有利に動いたら、
Trailing ActivationとTrailing Distanceを使用してオープン ポジションを追跡します。 - 以前の日次ローソク足の範囲が
Daily Range Filterのしきい値を超えている場合、または現在時刻が[Trade Start, Trade End)の枠外にある場合は、新しいエントリをスキップします。
パラメーター
Volume– すべての市場エントリーに使用される注文サイズ (ネットポジションは反転する前にフラット化されます)。Trade Start/Trade End– 毎日の取引ウィンドウ。夜間の範囲(例: 21:00 ~ 02:00)をサポートします。Regression Length– 多項式回帰フィットに使用されるローソク足の数。Degree– 回帰モデルに適用される多項式の次数 (1 ~ 6)。Std Dev Multiplier– バンドを形成するために回帰残差標準偏差に適用される乗数。Enable Trailing– トレーリングストップ管理を切り替えます。Trailing Activation– トレーリングが開始されるまでに必要な有利な動きのポイント数。Trailing Distance– 後続がアクティブになると維持される後続バッファ (ポイント単位)。Stop Loss– ポイント単位の保護停止距離 (0 は自動停止を無効にします)。Take Profit– ポイント単位の保護利益ターゲット距離 (0 は自動ターゲットを無効にします)。Daily Range Filter– ポイント単位で表される、前の日次ローソク足の最大許容範囲。Candle Type– 主要価格シリーズの時間枠 (デフォルトの 30 分の時間枠)。
デフォルト設定
Volume= 0.1Trade Start= 03:00Trade End= 21:20Regression Length= 250 バーDegree= 3Std Dev Multiplier= 1.0Enable Trailing= 偽Trailing Activation= 30 ポイントTrailing Distance= 30 ポイントStop Loss= 0 ポイント (無効)Take Profit= 0 ポイント (無効)Daily Range Filter= 150 ポイントCandle Type= 30 分のキャンドル
追加メモ
- この戦略では、すべての決定に最新の完成したローソク足を使用し、同じバー内で複数回取引することはありません。
- トレーリングストップは、価格が内部で計算されたトレーリングレベルに達したときに、市場ごとにポジションをクローズします。
- 前日の変動が大きすぎる場合(設定されたフィルターを超える範囲)、既存のポジションはクローズされ、バーの残りの部分では新規エントリーが保留されます。
- 回帰チャネルは更新のたびにチャート上に再描画され、中間、上部、下部のバンドを視覚化しやすくなります。
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));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from collections import deque
class e_regression_channel_strategy(Strategy):
"""
Polynomial regression channel strategy. Calculates a regression midline with
standard deviation bands and trades mean reversion between the bands and midline.
"""
def __init__(self):
super(e_regression_channel_strategy, self).__init__()
self._regression_length = self.Param("RegressionLength", 100) \
.SetDisplay("Regression Length", "Number of bars used for regression", "Regression")
self._degree = self.Param("Degree", 3) \
.SetDisplay("Degree", "Polynomial degree for the regression", "Regression")
self._std_multiplier = self.Param("StdDevMultiplier", 1.0) \
.SetDisplay("Std Dev Multiplier", "Width multiplier for the regression bands", "Regression")
self._stop_loss_points = self.Param("StopLossPoints", 500.0) \
.SetDisplay("Stop Loss", "Protective stop in absolute points (0 disables)", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0) \
.SetDisplay("Take Profit", "Target in absolute points (0 disables)", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Primary candle type used for trading", "General")
self._closes = deque()
self._previous_mid = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(e_regression_channel_strategy, self).OnReseted()
self._closes.clear()
self._previous_mid = None
def OnStarted2(self, time):
super(e_regression_channel_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
length = self._regression_length.Value
ema.Length = max(2, length)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
tp = self._take_profit_points.Value
sl = self._stop_loss_points.Value
tp_unit = Unit(float(tp), UnitTypes.Absolute) if tp > 0 else None
sl_unit = Unit(float(sl), UnitTypes.Absolute) if sl > 0 else None
if tp_unit is not None or sl_unit is not None:
self.StartProtection(tp_unit, sl_unit)
def _process_candle(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
reg_len = self._regression_length.Value
self._closes.append(close)
if len(self._closes) > reg_len:
self._closes.popleft()
if len(self._closes) < reg_len:
return
prices = list(self._closes)
coeffs = self._poly_fit(prices, self._degree.Value)
current_index = len(prices) - 1
mid = self._poly_eval(coeffs, current_index)
std = self._calc_std(prices, coeffs) * self._std_multiplier.Value
upper = mid + std
lower = mid - std
if self.Position > 0 and close >= mid:
self.SellMarket()
self._previous_mid = mid
return
if self.Position < 0 and close <= mid:
self.BuyMarket()
self._previous_mid = mid
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._previous_mid = mid
return
if float(candle.LowPrice) <= lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif float(candle.HighPrice) >= upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._previous_mid = mid
@staticmethod
def _poly_fit(y, degree):
n = len(y)
actual_degree = min(degree, max(1, n - 1))
size = actual_degree + 1
matrix = [[0.0] * (size + 1) for _ in range(size)]
for row in range(size):
for col in range(size):
s = 0.0
for i in range(n):
s += i ** (row + col)
matrix[row][col] = s
s_y = 0.0
for i in range(n):
s_y += y[i] * (i ** row)
matrix[row][size] = s_y
for i in range(size):
pivot = matrix[i][i]
if pivot == 0.0:
for k in range(i + 1, size):
if matrix[k][i] != 0.0:
matrix[i], matrix[k] = matrix[k], matrix[i]
pivot = matrix[i][i]
break
if pivot == 0.0:
continue
for j in range(i, size + 1):
matrix[i][j] /= pivot
for row in range(size):
if row == i:
continue
factor = matrix[row][i]
if factor == 0.0:
continue
for col in range(i, size + 1):
matrix[row][col] -= factor * matrix[i][col]
return [matrix[i][size] for i in range(size)]
@staticmethod
def _poly_eval(coeffs, x):
result = 0.0
power = 1.0
for c in coeffs:
result += c * power
power *= x
return result
@staticmethod
def _calc_std(values, coeffs):
n = len(values)
if n == 0:
return 0.0
s = 0.0
for i in range(n):
fitted = e_regression_channel_strategy._poly_eval(coeffs, i)
diff = values[i] - fitted
s += diff * diff
return math.sqrt(s / n)
def CreateClone(self):
return e_regression_channel_strategy()