Parabolic SAR Sentiment Divergence
Parabolic SAR Sentiment Divergence 策略基于 Parabolic SAR Sentiment Divergence。
测试表明年均收益约为 127%,该策略在股票市场表现最佳。
当 Parabolic confirms divergence setups 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 StartAf, MaxAf 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
StartAf = 0.02mMaxAf = 0.2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: Parabolic, Divergence
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (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 strategy with sentiment divergence.
/// </summary>
public class ParabolicSarSentimentDivergenceStrategy : Strategy
{
private readonly StrategyParam<decimal> _startAf;
private readonly StrategyParam<decimal> _maxAf;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ParabolicSar _parabolicSar;
private decimal _prevPrice;
private bool _prevAboveSar;
private bool _isFirstCandle = true;
private int _cooldownRemaining;
/// <summary>
/// SAR Starting acceleration factor.
/// </summary>
public decimal StartAf
{
get => _startAf.Value;
set => _startAf.Value = value;
}
/// <summary>
/// SAR Maximum acceleration factor.
/// </summary>
public decimal MaxAf
{
get => _maxAf.Value;
set => _maxAf.Value = value;
}
/// <summary>
/// Closed candles to wait before another position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="ParabolicSarSentimentDivergenceStrategy"/>.
/// </summary>
public ParabolicSarSentimentDivergenceStrategy()
{
_startAf = Param(nameof(StartAf), 0.02m)
.SetRange(0.01m, 0.1m)
.SetDisplay("Starting AF", "Starting acceleration factor for Parabolic SAR", "SAR Parameters");
_maxAf = Param(nameof(MaxAf), 0.2m)
.SetRange(0.1m, 0.5m)
.SetDisplay("Maximum AF", "Maximum acceleration factor for Parabolic SAR", "SAR Parameters");
_cooldownBars = Param(nameof(CooldownBars), 24)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General");
_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();
_parabolicSar = null;
_prevPrice = default;
_prevAboveSar = default;
_isFirstCandle = true;
_cooldownRemaining = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicator
_parabolicSar = new ParabolicSar
{
Acceleration = StartAf,
AccelerationMax = MaxAf,
};
// Create subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicator and processor
subscription
.Bind(_parabolicSar, ProcessCandle)
.Start();
// Setup visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _parabolicSar);
DrawOwnTrades(area);
}
// Start position protection
StartProtection(
new Unit(2, UnitTypes.Percent), // Take profit 2%
new Unit(2, UnitTypes.Percent), // Stop loss 2%
true // Use trailing stop
);
}
private void ProcessCandle(ICandleMessage candle, decimal sarPrice)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
var priceAboveSar = price > sarPrice;
if (_isFirstCandle)
{
_prevPrice = price;
_prevAboveSar = priceAboveSar;
_isFirstCandle = false;
return;
}
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var bullishFlip = !_prevAboveSar && priceAboveSar;
var bearishFlip = _prevAboveSar && !priceAboveSar;
if (_cooldownRemaining == 0 && Position == 0)
{
if (bullishFlip)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (bearishFlip)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevPrice = price;
_prevAboveSar = priceAboveSar;
}
private decimal GetSentiment(ICandleMessage candle)
{
var totalRange = candle.HighPrice - candle.LowPrice;
if (totalRange <= 0)
return 0m;
var body = candle.ClosePrice - candle.OpenPrice;
var bodyRatio = body / totalRange;
var rangeRatio = totalRange / Math.Max(candle.OpenPrice, 1m);
var sentiment = (bodyRatio * 0.7m) + (Math.Sign(body) * Math.Min(0.3m, rangeRatio * 10m));
return Math.Max(-1m, Math.Min(1m, sentiment));
}
}
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
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_sentiment_divergence_strategy(Strategy):
"""Parabolic SAR strategy with sentiment divergence."""
def __init__(self):
super(parabolic_sar_sentiment_divergence_strategy, self).__init__()
self._start_af = self.Param("StartAf", 0.02) \
.SetRange(0.01, 0.1) \
.SetDisplay("Starting AF", "Starting acceleration factor for Parabolic SAR", "SAR Parameters")
self._max_af = self.Param("MaxAf", 0.2) \
.SetRange(0.1, 0.5) \
.SetDisplay("Maximum AF", "Maximum acceleration factor for Parabolic SAR", "SAR Parameters")
self._cooldown_bars = self.Param("CooldownBars", 24) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_sentiment = 0.0
self._prev_price = 0.0
self._prev_above_sar = False
self._is_first_candle = True
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_sentiment_divergence_strategy, self).OnReseted()
self._prev_sentiment = 0.0
self._prev_price = 0.0
self._prev_above_sar = False
self._is_first_candle = True
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(parabolic_sar_sentiment_divergence_strategy, self).OnStarted2(time)
sar = ParabolicSar()
sar.Acceleration = Decimal(float(self._start_af.Value))
sar.AccelerationMax = Decimal(float(self._max_af.Value))
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sar, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(2, UnitTypes.Percent),
True
)
def ProcessCandle(self, candle, sar_price):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
sar = float(sar_price)
price_above_sar = price > sar
if self._is_first_candle:
self._prev_price = price
self._prev_above_sar = price_above_sar
self._is_first_candle = False
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
cooldown = int(self._cooldown_bars.Value)
bullish_flip = (not self._prev_above_sar) and price_above_sar
bearish_flip = self._prev_above_sar and (not price_above_sar)
if self._cooldown_remaining == 0 and self.Position == 0:
if bullish_flip:
self.BuyMarket()
self._cooldown_remaining = cooldown
elif bearish_flip:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_price = price
self._prev_above_sar = price_above_sar
def CreateClone(self):
return parabolic_sar_sentiment_divergence_strategy()