线性回归斜率触发策略
概述
该策略利用线性回归斜率指标及其派生的触发线来识别趋势变化。当触发线向上穿越斜率线时开多仓;当触发线向下穿越斜率线时开空仓。出现反向信号时平掉现有仓位。策略灵感来自原始 MQL5 策略“Exp_LinearRegSlopeV2”。
指标逻辑
- 在蜡烛收盘价上按可配置周期计算 线性回归斜率。
- 通过公式
2 * slope - slope[Shift]计算 触发线,其中slope[Shift]为若干根之前的斜率值。 - 触发线与斜率线的交叉即为交易信号。
交易规则
- 做多: 触发线上穿斜率线且允许做多。
- 做空: 触发线下穿斜率线且允许做空。
- 平多: 斜率线高于触发线。
- 平空: 触发线高于斜率线。
参数
SlopeLength– 计算线性回归斜率的周期。TriggerShift– 计算触发线时使用的回溯柱数。EnableLong– 是否允许做多。EnableShort– 是否允许做空。TakeProfitPercent– 按进场价百分比设置的止盈。StopLossPercent– 按进场价百分比设置的止损。CandleType– 策略使用的蜡烛时间框架。
备注
- 策略仅在完成的蜡烛上运行。
- 通过
StartProtection实现基于百分比的固定止盈和止损。 - 请确保有足够的历史数据供指标形成。
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on a smoothed slope line and trigger line.
/// </summary>
public class LinearRegressionSlopeTriggerStrategy : Strategy
{
private readonly StrategyParam<int> _slopeLength;
private readonly StrategyParam<int> _triggerShift;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly ExponentialMovingAverage _trendLine = new();
private readonly SimpleMovingAverage _triggerLine = new();
private decimal _previousTrendValue;
private decimal _previousSlope;
private decimal _previousTrigger;
private bool _isInitialized;
private int _barsSinceTrade;
/// <summary>
/// Period for calculating the smoothed trend line.
/// </summary>
public int SlopeLength
{
get => _slopeLength.Value;
set => _slopeLength.Value = value;
}
/// <summary>
/// Number of bars used for trigger smoothing.
/// </summary>
public int TriggerShift
{
get => _triggerShift.Value;
set => _triggerShift.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Take-profit percentage from entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Stop-loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="LinearRegressionSlopeTriggerStrategy"/>.
/// </summary>
public LinearRegressionSlopeTriggerStrategy()
{
_slopeLength = Param(nameof(SlopeLength), 12)
.SetGreaterThanZero()
.SetDisplay("Slope Length", "Period for the smoothed trend line", "Indicator")
.SetOptimize(5, 30, 1);
_triggerShift = Param(nameof(TriggerShift), 2)
.SetGreaterThanZero()
.SetDisplay("Trigger Shift", "Bars used for trigger smoothing", "Indicator")
.SetOptimize(1, 5, 1);
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow long trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow short trades", "Trading");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 5m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk Management")
.SetOptimize(2m, 10m, 1m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
.SetOptimize(1m, 5m, 1m);
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trendLine.Reset();
_trendLine.Length = 1;
_triggerLine.Reset();
_triggerLine.Length = 1;
_previousTrendValue = 0m;
_previousSlope = 0m;
_previousTrigger = 0m;
_isInitialized = false;
_barsSinceTrade = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_trendLine.Length = SlopeLength;
_triggerLine.Length = TriggerShift;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(
new Unit(TakeProfitPercent, UnitTypes.Percent),
new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var trendValue = _trendLine.Process(new DecimalIndicatorValue(_trendLine, candle.ClosePrice, candle.ServerTime) { IsFinal = true }).ToDecimal();
if (!_trendLine.IsFormed)
return;
if (!_isInitialized)
{
_previousTrendValue = trendValue;
_isInitialized = true;
return;
}
var slope = trendValue - _previousTrendValue;
var trigger = _triggerLine.Process(new DecimalIndicatorValue(_triggerLine, slope, candle.ServerTime) { IsFinal = true }).ToDecimal();
if (!_triggerLine.IsFormed)
{
_previousTrendValue = trendValue;
_previousSlope = slope;
_previousTrigger = slope;
return;
}
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
var buySignal = _previousTrigger <= _previousSlope && trigger > slope && slope > 0m;
var sellSignal = _previousTrigger >= _previousSlope && trigger < slope && slope < 0m;
var closeLong = slope >= 0m && trigger < slope;
var closeShort = slope <= 0m && trigger > slope;
if (_barsSinceTrade >= CooldownBars && Position == 0)
{
if (buySignal && EnableLong)
{
BuyMarket();
_barsSinceTrade = 0;
}
else if (sellSignal && EnableShort)
{
SellMarket();
_barsSinceTrade = 0;
}
}
_previousTrendValue = trendValue;
_previousSlope = slope;
_previousTrigger = trigger;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class linear_regression_slope_trigger_strategy(Strategy):
def __init__(self):
super(linear_regression_slope_trigger_strategy, self).__init__()
self._slope_length = self.Param("SlopeLength", 12) \
.SetDisplay("Slope Length", "Period for the smoothed trend line", "Indicator")
self._trigger_shift = self.Param("TriggerShift", 2) \
.SetDisplay("Trigger Shift", "Bars used for trigger smoothing", "Indicator")
self._enable_long = self.Param("EnableLong", True) \
.SetDisplay("Enable Long", "Allow long trades", "Trading")
self._enable_short = self.Param("EnableShort", True) \
.SetDisplay("Enable Short", "Allow short trades", "Trading")
self._take_profit_percent = self.Param("TakeProfitPercent", 5.0) \
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk Management")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._trend_line = ExponentialMovingAverage()
self._trigger_line = SimpleMovingAverage()
self._previous_trend_value = 0.0
self._previous_slope = 0.0
self._previous_trigger = 0.0
self._is_initialized = False
self._bars_since_trade = 0
@property
def SlopeLength(self):
return self._slope_length.Value
@SlopeLength.setter
def SlopeLength(self, value):
self._slope_length.Value = value
@property
def TriggerShift(self):
return self._trigger_shift.Value
@TriggerShift.setter
def TriggerShift(self, value):
self._trigger_shift.Value = value
@property
def EnableLong(self):
return self._enable_long.Value
@EnableLong.setter
def EnableLong(self, value):
self._enable_long.Value = value
@property
def EnableShort(self):
return self._enable_short.Value
@EnableShort.setter
def EnableShort(self, value):
self._enable_short.Value = value
@property
def TakeProfitPercent(self):
return self._take_profit_percent.Value
@TakeProfitPercent.setter
def TakeProfitPercent(self, value):
self._take_profit_percent.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(linear_regression_slope_trigger_strategy, self).OnStarted2(time)
self._trend_line.Length = self.SlopeLength
self._trigger_line.Length = self.TriggerShift
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfitPercent, UnitTypes.Percent),
Unit(self.StopLossPercent, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
trend_value = float(process_float(self._trend_line, candle.ClosePrice, candle.ServerTime, True))
if not self._trend_line.IsFormed:
return
if not self._is_initialized:
self._previous_trend_value = trend_value
self._is_initialized = True
return
slope = trend_value - self._previous_trend_value
trigger = float(process_float(self._trigger_line, slope, candle.ServerTime, True))
if not self._trigger_line.IsFormed:
self._previous_trend_value = trend_value
self._previous_slope = slope
self._previous_trigger = slope
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
buy_signal = self._previous_trigger <= self._previous_slope and trigger > slope and slope > 0.0
sell_signal = self._previous_trigger >= self._previous_slope and trigger < slope and slope < 0.0
if self._bars_since_trade >= self.CooldownBars and self.Position == 0:
if buy_signal and self.EnableLong:
self.BuyMarket()
self._bars_since_trade = 0
elif sell_signal and self.EnableShort:
self.SellMarket()
self._bars_since_trade = 0
self._previous_trend_value = trend_value
self._previous_slope = slope
self._previous_trigger = trigger
def OnReseted(self):
super(linear_regression_slope_trigger_strategy, self).OnReseted()
self._trend_line.Reset()
self._trigger_line.Reset()
self._previous_trend_value = 0.0
self._previous_slope = 0.0
self._previous_trigger = 0.0
self._is_initialized = False
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return linear_regression_slope_trigger_strategy()