Parabolic SAR Hurst Filter
Parabolic SAR Hurst Filter 策略基于 Parabolic SAR Hurst Filter。
测试表明年均收益约为 82%,该策略在股票市场表现最佳。
当 Parabolic confirms filtered entries 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 SarAccelerationFactor, SarMaxAccelerationFactor 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
SarAccelerationFactor = 0.02mSarMaxAccelerationFactor = 0.2mHurstPeriod = 100CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: Parabolic, Hurst
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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>
/// Parabolic SAR with Hurst Filter Strategy.
/// Enters a position when price crosses SAR and Hurst exponent indicates a persistent trend.
/// </summary>
public class ParabolicSarHurstStrategy : Strategy
{
private readonly StrategyParam<decimal> _sarAccelerationFactor;
private readonly StrategyParam<decimal> _sarMaxAccelerationFactor;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private ParabolicSar _parabolicSar = null!;
private HurstExponent _hurstIndicator = null!;
private decimal _prevSarValue;
private decimal _hurstValue;
private bool? _prevPriceAboveSar;
private int _cooldownRemaining;
/// <summary>
/// Parabolic SAR acceleration factor.
/// </summary>
public decimal SarAccelerationFactor
{
get => _sarAccelerationFactor.Value;
set => _sarAccelerationFactor.Value = value;
}
/// <summary>
/// Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal SarMaxAccelerationFactor
{
get => _sarMaxAccelerationFactor.Value;
set => _sarMaxAccelerationFactor.Value = value;
}
/// <summary>
/// Hurst exponent calculation period.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of closed candles to wait before a new SAR crossover entry.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public ParabolicSarHurstStrategy()
{
_sarAccelerationFactor = Param(nameof(SarAccelerationFactor), 0.02m)
.SetRange(0.01m, 0.2m)
.SetDisplay("SAR Acceleration Factor", "Initial acceleration factor for Parabolic SAR", "SAR Settings")
.SetOptimize(0.01m, 0.1m, 0.01m);
_sarMaxAccelerationFactor = Param(nameof(SarMaxAccelerationFactor), 0.2m)
.SetRange(0.05m, 0.5m)
.SetDisplay("SAR Max Acceleration Factor", "Maximum acceleration factor for Parabolic SAR", "SAR Settings")
.SetOptimize(0.1m, 0.3m, 0.05m);
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetRange(20, 200)
.SetDisplay("Hurst Period", "Period for Hurst exponent calculation", "Hurst Settings")
.SetOptimize(50, 150, 25);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new SAR crossover entry", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_parabolicSar = null!;
_hurstIndicator = null!;
_prevSarValue = 0;
_hurstValue = 0.5m;
_prevPriceAboveSar = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_parabolicSar = new ParabolicSar
{
Acceleration = SarAccelerationFactor,
AccelerationMax = SarMaxAccelerationFactor
};
_hurstIndicator = new HurstExponent
{
Length = HurstPeriod
};
// Create subscription for candles
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
// Start position protection
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _parabolicSar);
DrawIndicator(area, _hurstIndicator);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var sarValue = _parabolicSar.Process(new CandleIndicatorValue(_parabolicSar, candle));
var hurstValue = _hurstIndicator.Process(new CandleIndicatorValue(_hurstIndicator, candle));
if (!_parabolicSar.IsFormed || !_hurstIndicator.IsFormed || sarValue.IsEmpty || hurstValue.IsEmpty)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sarPrice = sarValue.ToDecimal();
_hurstValue = hurstValue.ToDecimal();
var currentSarValue = sarPrice;
var priceAboveSar = candle.ClosePrice > sarPrice;
if (_prevPriceAboveSar is null || _prevSarValue == 0)
{
_prevSarValue = currentSarValue;
_prevPriceAboveSar = priceAboveSar;
return;
}
if (_hurstValue > 0.55m)
{
var bullishCross = !_prevPriceAboveSar.Value && priceAboveSar;
var bearishCross = _prevPriceAboveSar.Value && !priceAboveSar;
if (_cooldownRemaining == 0 && bullishCross && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && bearishCross && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = SignalCooldownBars;
}
}
else
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
}
_prevSarValue = currentSarValue;
_prevPriceAboveSar = priceAboveSar;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ParabolicSar, HurstExponent, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_hurst_strategy(Strategy):
"""
Parabolic SAR with Hurst Filter Strategy.
Enters a position when price crosses SAR and Hurst exponent indicates a persistent trend.
"""
def __init__(self):
super(parabolic_sar_hurst_strategy, self).__init__()
self._sar_acceleration_factor = self.Param("SarAccelerationFactor", 0.02) \
.SetRange(0.01, 0.2) \
.SetDisplay("SAR Acceleration Factor", "Initial acceleration factor for Parabolic SAR", "SAR Settings") \
.SetCanOptimize(True) \
.SetOptimize(0.01, 0.1, 0.01)
self._sar_max_acceleration_factor = self.Param("SarMaxAccelerationFactor", 0.2) \
.SetRange(0.05, 0.5) \
.SetDisplay("SAR Max Acceleration Factor", "Maximum acceleration factor for Parabolic SAR", "SAR Settings") \
.SetCanOptimize(True) \
.SetOptimize(0.1, 0.3, 0.05)
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetRange(20, 200) \
.SetDisplay("Hurst Period", "Period for Hurst exponent calculation", "Hurst Settings") \
.SetCanOptimize(True) \
.SetOptimize(50, 150, 25)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 4) \
.SetNotNegative() \
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new SAR crossover entry", "General")
self._parabolic_sar = None
self._hurst_indicator = None
self._prev_sar_value = 0.0
self._hurst_value = 0.5
self._prev_price_above_sar = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(parabolic_sar_hurst_strategy, self).OnReseted()
self._parabolic_sar = None
self._hurst_indicator = None
self._prev_sar_value = 0.0
self._hurst_value = 0.5
self._prev_price_above_sar = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(parabolic_sar_hurst_strategy, self).OnStarted2(time)
self._parabolic_sar = ParabolicSar()
self._parabolic_sar.Acceleration = Decimal(self._sar_acceleration_factor.Value)
self._parabolic_sar.AccelerationMax = Decimal(self._sar_max_acceleration_factor.Value)
self._hurst_indicator = HurstExponent()
self._hurst_indicator.Length = int(self._hurst_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._parabolic_sar)
self.DrawIndicator(area, self._hurst_indicator)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
sar_val = self._parabolic_sar.Process(CandleIndicatorValue(self._parabolic_sar, candle))
hurst_val = self._hurst_indicator.Process(CandleIndicatorValue(self._hurst_indicator, candle))
if not self._parabolic_sar.IsFormed or not self._hurst_indicator.IsFormed or sar_val.IsEmpty or hurst_val.IsEmpty:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sar_price = float(sar_val)
self._hurst_value = float(hurst_val)
current_sar_value = sar_price
price_above_sar = float(candle.ClosePrice) > sar_price
if self._prev_price_above_sar is None or self._prev_sar_value == 0.0:
self._prev_sar_value = current_sar_value
self._prev_price_above_sar = price_above_sar
return
cooldown_bars = int(self._signal_cooldown_bars.Value)
if self._hurst_value > 0.55:
bullish_cross = not self._prev_price_above_sar and price_above_sar
bearish_cross = self._prev_price_above_sar and not price_above_sar
if self._cooldown_remaining == 0 and bullish_cross and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = cooldown_bars
elif self._cooldown_remaining == 0 and bearish_cross and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = cooldown_bars
else:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._prev_sar_value = current_sar_value
self._prev_price_above_sar = price_above_sar
def CreateClone(self):
return parabolic_sar_hurst_strategy()