EUR/USD Mehrschichtige Statistische Regressions-Strategie
Strategie, die mehrere lineare Regressionschichten verwendet, um die Trendrichtung bei EUR/USD zu schätzen. Sie berechnet kurze, mittlere und lange Regressionen, validiert diese durch R²- und Steigungsschwellenwerte und handelt in Richtung des gewichteten Ensembles.
Details
- Einstiegskriterien:
- Long: gewichtete Steigung > 0 und Zuverlässigkeit > 0.5
- Short: gewichtete Steigung < 0 und Zuverlässigkeit > 0.5
- Long/Short: Beide
- Ausstiegskriterien: Umkehr bei entgegengesetztem Signal
- Stops: Tagesverlustschutz
- Standardwerte:
ShortLength= 20MediumLength= 50LongLength= 100MinRSquared= 0.45mSlopeThreshold= 0.00005mWeightShort= 0.4mWeightMedium= 0.35mWeightLong= 0.25mPositionSizePct= 50mMaxDailyLossPct= 12mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filter:
- Kategorie: Trend
- Richtung: Beide
- Indikatoren: Linear Regression
- Stops: Ja
- Komplexität: Fortgeschritten
- Zeitrahmen: Mittelfristig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Risikolevel: Mittel
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EUR/USD multi-layer statistical regression strategy.
/// Short, medium and long linear regressions each yield a slope and an R². A layer counts only when its R² reaches MinRSquared
/// and its absolute slope reaches SlopeThreshold. The weighted slope of the valid layers gives the direction and the weighted R²
/// of the valid layers relative to all weights gives the reliability; with reliability above 0.5 the strategy goes long on a
/// positive slope and short on a negative one, reversing an opposite position. Trading stops for the rest of the day once the
/// day's loss reaches MaxDailyLossPct of the account value.
/// </summary>
public class EurUsdMultiLayerStatisticalRegressionStrategy : Strategy
{
private const decimal _minReliability = 0.5m;
private readonly StrategyParam<int> _shortLength;
private readonly StrategyParam<int> _mediumLength;
private readonly StrategyParam<int> _longLength;
private readonly StrategyParam<decimal> _minRSquared;
private readonly StrategyParam<decimal> _slopeThreshold;
private readonly StrategyParam<decimal> _weightShort;
private readonly StrategyParam<decimal> _weightMedium;
private readonly StrategyParam<decimal> _weightLong;
private readonly StrategyParam<decimal> _positionSizePct;
private readonly StrategyParam<decimal> _maxDailyLossPct;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private decimal _dayStartPnL;
private decimal _dayStartValue;
private bool _dayLossReached;
/// <summary>
/// Length of the short regression.
/// </summary>
public int ShortLength
{
get => _shortLength.Value;
set => _shortLength.Value = value;
}
/// <summary>
/// Length of the medium regression.
/// </summary>
public int MediumLength
{
get => _mediumLength.Value;
set => _mediumLength.Value = value;
}
/// <summary>
/// Length of the long regression.
/// </summary>
public int LongLength
{
get => _longLength.Value;
set => _longLength.Value = value;
}
/// <summary>
/// Minimum R² for a layer to count.
/// </summary>
public decimal MinRSquared
{
get => _minRSquared.Value;
set => _minRSquared.Value = value;
}
/// <summary>
/// Minimum absolute slope for a layer to count.
/// </summary>
public decimal SlopeThreshold
{
get => _slopeThreshold.Value;
set => _slopeThreshold.Value = value;
}
/// <summary>
/// Weight of the short regression.
/// </summary>
public decimal WeightShort
{
get => _weightShort.Value;
set => _weightShort.Value = value;
}
/// <summary>
/// Weight of the medium regression.
/// </summary>
public decimal WeightMedium
{
get => _weightMedium.Value;
set => _weightMedium.Value = value;
}
/// <summary>
/// Weight of the long regression.
/// </summary>
public decimal WeightLong
{
get => _weightLong.Value;
set => _weightLong.Value = value;
}
/// <summary>
/// Position size in percent of equity (informational).
/// </summary>
public decimal PositionSizePct
{
get => _positionSizePct.Value;
set => _positionSizePct.Value = value;
}
/// <summary>
/// Daily loss limit in percent of the account value.
/// </summary>
public decimal MaxDailyLossPct
{
get => _maxDailyLossPct.Value;
set => _maxDailyLossPct.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EurUsdMultiLayerStatisticalRegressionStrategy()
{
_shortLength = Param(nameof(ShortLength), 20)
.SetGreaterThanZero()
.SetDisplay("Short Length", "Length of the short regression", "Regression");
_mediumLength = Param(nameof(MediumLength), 50)
.SetGreaterThanZero()
.SetDisplay("Medium Length", "Length of the medium regression", "Regression");
_longLength = Param(nameof(LongLength), 100)
.SetGreaterThanZero()
.SetDisplay("Long Length", "Length of the long regression", "Regression");
_minRSquared = Param(nameof(MinRSquared), 0.45m)
.SetDisplay("Min R²", "Minimum R² for a layer to count", "Validation");
_slopeThreshold = Param(nameof(SlopeThreshold), 0.00005m)
.SetNotNegative()
.SetDisplay("Slope Threshold", "Minimum absolute slope for a layer to count", "Validation");
_weightShort = Param(nameof(WeightShort), 0.4m)
.SetNotNegative()
.SetDisplay("Short Weight", "Weight of the short regression", "Ensemble");
_weightMedium = Param(nameof(WeightMedium), 0.35m)
.SetNotNegative()
.SetDisplay("Medium Weight", "Weight of the medium regression", "Ensemble");
_weightLong = Param(nameof(WeightLong), 0.25m)
.SetNotNegative()
.SetDisplay("Long Weight", "Weight of the long regression", "Ensemble");
_positionSizePct = Param(nameof(PositionSizePct), 50m)
.SetNotNegative()
.SetDisplay("Position Size %", "Position size in percent of equity", "Risk");
_maxDailyLossPct = Param(nameof(MaxDailyLossPct), 12m)
.SetNotNegative()
.SetDisplay("Max Daily Loss %", "Daily loss limit in percent of the account value", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
ResetDay();
}
private void ResetDay()
{
_currentDay = default;
_dayStartPnL = 0m;
_dayStartValue = 0m;
_dayLossReached = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetDay();
var shortReg = new LinearRegression { Length = ShortLength };
var mediumReg = new LinearRegression { Length = MediumLength };
var longReg = new LinearRegression { Length = LongLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(shortReg, mediumReg, longReg, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortReg);
DrawIndicator(area, mediumReg);
DrawIndicator(area, longReg);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue shortValue, IIndicatorValue mediumValue, IIndicatorValue longValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!shortValue.IsFormed || !mediumValue.IsFormed || !longValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (UpdateDailyLoss(candle.OpenTime.Date))
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
return;
}
var totalWeight = WeightShort + WeightMedium + WeightLong;
if (totalWeight <= 0)
return;
var validWeight = 0m;
var weightedSlope = 0m;
var weightedR2 = 0m;
AddLayer(shortValue, WeightShort, ref validWeight, ref weightedSlope, ref weightedR2);
AddLayer(mediumValue, WeightMedium, ref validWeight, ref weightedSlope, ref weightedR2);
AddLayer(longValue, WeightLong, ref validWeight, ref weightedSlope, ref weightedR2);
if (validWeight <= 0)
return;
var slope = weightedSlope / validWeight;
var reliability = weightedR2 / totalWeight;
if (reliability <= _minReliability)
return;
if (slope > 0 && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (slope < 0 && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
private void AddLayer(IIndicatorValue value, decimal weight, ref decimal validWeight, ref decimal weightedSlope, ref decimal weightedR2)
{
if (value is not ILinearRegressionValue { LinearRegSlope: decimal slope, RSquared: decimal r2 })
return;
if (r2 < MinRSquared || Math.Abs(slope) < SlopeThreshold)
return;
validWeight += weight;
weightedSlope += weight * slope;
weightedR2 += weight * r2;
}
private bool UpdateDailyLoss(DateTime day)
{
if (_currentDay != day)
{
_currentDay = day;
_dayStartPnL = PnL;
_dayStartValue = Portfolio?.CurrentValue ?? 0m;
_dayLossReached = false;
}
// Without a known account value there is nothing to take the percentage of.
if (_dayStartValue > 0 && MaxDailyLossPct > 0 && PnL - _dayStartPnL <= -_dayStartValue * MaxDailyLossPct / 100m)
_dayLossReached = true;
return _dayLossReached;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import LinearRegression
from StockSharp.Algo.Strategies import Strategy
MIN_RELIABILITY = 0.5
class eur_usd_multi_layer_statistical_regression_strategy(Strategy):
"""
EUR/USD multi-layer statistical regression strategy.
Short, medium and long linear regressions each yield a slope and an R². A layer counts only when its R² reaches MinRSquared
and its absolute slope reaches SlopeThreshold. The weighted slope of the valid layers gives the direction and the weighted R²
of the valid layers relative to all weights gives the reliability; with reliability above 0.5 the strategy goes long on a
positive slope and short on a negative one, reversing an opposite position. Trading stops for the rest of the day once the
day's loss reaches MaxDailyLossPct of the account value.
"""
def __init__(self):
super(eur_usd_multi_layer_statistical_regression_strategy, self).__init__()
self._short_length = self.Param("ShortLength", 20).SetGreaterThanZero().SetDisplay("Short Length", "Length of the short regression", "Regression")
self._medium_length = self.Param("MediumLength", 50).SetGreaterThanZero().SetDisplay("Medium Length", "Length of the medium regression", "Regression")
self._long_length = self.Param("LongLength", 100).SetGreaterThanZero().SetDisplay("Long Length", "Length of the long regression", "Regression")
self._min_r_squared = self.Param("MinRSquared", 0.45).SetDisplay("Min R²", "Minimum R² for a layer to count", "Validation")
self._slope_threshold = self.Param("SlopeThreshold", 0.00005).SetNotNegative().SetDisplay("Slope Threshold", "Minimum absolute slope for a layer to count", "Validation")
self._weight_short = self.Param("WeightShort", 0.4).SetNotNegative().SetDisplay("Short Weight", "Weight of the short regression", "Ensemble")
self._weight_medium = self.Param("WeightMedium", 0.35).SetNotNegative().SetDisplay("Medium Weight", "Weight of the medium regression", "Ensemble")
self._weight_long = self.Param("WeightLong", 0.25).SetNotNegative().SetDisplay("Long Weight", "Weight of the long regression", "Ensemble")
self._position_size_pct = self.Param("PositionSizePct", 50.0).SetNotNegative().SetDisplay("Position Size %", "Position size in percent of equity", "Risk")
self._max_daily_loss_pct = self.Param("MaxDailyLossPct", 12.0).SetNotNegative().SetDisplay("Max Daily Loss %", "Daily loss limit in percent of the account value", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_day()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_day(self):
self._current_day = None
self._day_start_pnl = 0.0
self._day_start_value = 0.0
self._day_loss_reached = False
def OnReseted(self):
super(eur_usd_multi_layer_statistical_regression_strategy, self).OnReseted()
self._reset_day()
def OnStarted2(self, time):
super(eur_usd_multi_layer_statistical_regression_strategy, self).OnStarted2(time)
self._reset_day()
short_reg = LinearRegression()
short_reg.Length = self._short_length.Value
medium_reg = LinearRegression()
medium_reg.Length = self._medium_length.Value
long_reg = LinearRegression()
long_reg.Length = self._long_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(short_reg, medium_reg, long_reg, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_reg)
self.DrawIndicator(area, medium_reg)
self.DrawIndicator(area, long_reg)
self.DrawOwnTrades(area)
def _process_candle(self, candle, short_value, medium_value, long_value):
if candle.State != CandleStates.Finished:
return
if not short_value.IsFormed or not medium_value.IsFormed or not long_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._update_daily_loss(candle.OpenTime.Date):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
return
weights = (float(self._weight_short.Value), float(self._weight_medium.Value), float(self._weight_long.Value))
total_weight = sum(weights)
if total_weight <= 0:
return
valid_weight = 0.0
weighted_slope = 0.0
weighted_r2 = 0.0
for value, weight in zip((short_value, medium_value, long_value), weights):
slope = value.LinearRegSlope
r2 = value.RSquared
if slope is None or r2 is None:
continue
slope = float(slope)
r2 = float(r2)
if r2 < float(self._min_r_squared.Value) or abs(slope) < float(self._slope_threshold.Value):
continue
valid_weight += weight
weighted_slope += weight * slope
weighted_r2 += weight * r2
if valid_weight <= 0:
return
slope = weighted_slope / valid_weight
reliability = weighted_r2 / total_weight
if reliability <= MIN_RELIABILITY:
return
if slope > 0 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif slope < 0 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def _update_daily_loss(self, day):
pnl = float(self.PnL)
if self._current_day != day:
self._current_day = day
self._day_start_pnl = pnl
self._day_start_value = float(self.Portfolio.CurrentValue) if self.Portfolio is not None and self.Portfolio.CurrentValue is not None else 0.0
self._day_loss_reached = False
# Without a known account value there is nothing to take the percentage of.
limit = float(self._max_daily_loss_pct.Value)
if self._day_start_value > 0 and limit > 0 and pnl - self._day_start_pnl <= -self._day_start_value * limit / 100.0:
self._day_loss_reached = True
return self._day_loss_reached
def CreateClone(self):
return eur_usd_multi_layer_statistical_regression_strategy()