Parabolic Sar Volume Strategy 策略 (中文)
该文档的中文版本尚未完成,详细说明请参阅英文版 README.md。
测试表明年均收益约为 151%,该策略在股票市场表现最佳。
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>
/// Strategy that combines Parabolic SAR with volume confirmation.
/// Enters trades when price crosses the Parabolic SAR with above-average volume.
/// </summary>
public class ParabolicSarVolumeStrategy : Strategy
{
private readonly StrategyParam<decimal> _acceleration;
private readonly StrategyParam<decimal> _maxAcceleration;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ParabolicSar _parabolicSar;
private VolumeIndicator _volumeIndicator;
private SimpleMovingAverage _volumeAverage;
private decimal _prevSar;
private decimal _currentAvgVolume;
private bool _prevPriceAboveSar;
private int _cooldown;
/// <summary>
/// Parabolic SAR acceleration factor.
/// </summary>
public decimal Acceleration
{
get => _acceleration.Value;
set => _acceleration.Value = value;
}
/// <summary>
/// Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal MaxAcceleration
{
get => _maxAcceleration.Value;
set => _maxAcceleration.Value = value;
}
/// <summary>
/// Period for volume moving average.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ParabolicSarVolumeStrategy"/>.
/// </summary>
public ParabolicSarVolumeStrategy()
{
_acceleration = Param(nameof(Acceleration), 0.02m)
.SetRange(0.01m, 0.1m)
.SetDisplay("SAR Acceleration", "Starting acceleration factor", "Indicators");
_maxAcceleration = Param(nameof(MaxAcceleration), 0.2m)
.SetRange(0.1m, 0.5m)
.SetDisplay("SAR Max Acceleration", "Maximum acceleration factor", "Indicators");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetRange(10, 50)
.SetDisplay("Volume Period", "Period for volume moving average", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 30)
.SetRange(1, 100)
.SetDisplay("Cooldown Bars", "Bars between entries", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_prevSar = 0;
_currentAvgVolume = 0;
_prevPriceAboveSar = false;
_cooldown = 0;
_parabolicSar = null;
_volumeIndicator = null;
_volumeAverage = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_parabolicSar = new ParabolicSar
{
Acceleration = Acceleration,
AccelerationMax = MaxAcceleration
};
_volumeIndicator = new VolumeIndicator();
_volumeAverage = new SMA
{
Length = VolumePeriod
};
// Create candle subscription
var subscription = SubscribeCandles(CandleType);
// Binding for Parabolic SAR indicator
subscription
.Bind(_parabolicSar, _volumeIndicator, ProcessIndicators)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _parabolicSar);
var volumeArea = CreateChartArea();
if (volumeArea != null)
{
DrawIndicator(volumeArea, _volumeIndicator);
DrawIndicator(volumeArea, _volumeAverage);
}
DrawOwnTrades(area);
}
}
private void ProcessIndicators(ICandleMessage candle, decimal sarValue, decimal volumeValue)
{
var avgValue = _volumeAverage.Process(new DecimalIndicatorValue(_volumeAverage, volumeValue, candle.ServerTime));
if (avgValue == null)
return;
_currentAvgVolume = avgValue.ToDecimal();
if (_currentAvgVolume <= 0)
return;
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Wait until strategy and indicators are ready
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Get current price and volume
var currentPrice = candle.ClosePrice;
var currentVolume = candle.TotalVolume;
var isPriceAboveSar = currentPrice > sarValue;
// Determine if volume is above average
var isHighVolume = currentVolume > _currentAvgVolume * 1.5m;
if (_cooldown > 0)
{
_cooldown--;
_prevSar = sarValue;
_prevPriceAboveSar = isPriceAboveSar;
return;
}
// Check for SAR crossover with volume confirmation
// Bullish crossover: Price crosses above SAR with high volume
if (isPriceAboveSar && !_prevPriceAboveSar && isHighVolume && Position <= 0)
{
CancelActiveOrders();
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = CooldownBars;
}
// Bearish crossover: Price crosses below SAR with high volume
else if (!isPriceAboveSar && _prevPriceAboveSar && isHighVolume && Position >= 0)
{
CancelActiveOrders();
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = CooldownBars;
}
// Exit signals based on SAR crossover (without volume confirmation)
else if ((Position > 0 && !isPriceAboveSar) || (Position < 0 && isPriceAboveSar))
{
ClosePosition();
_cooldown = CooldownBars;
}
// Update previous values for next candle
_prevSar = sarValue;
_prevPriceAboveSar = isPriceAboveSar;
}
}
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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ParabolicSar, VolumeIndicator, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class parabolic_sar_volume_strategy(Strategy):
"""
Strategy that combines Parabolic SAR with volume confirmation.
"""
def __init__(self):
super(parabolic_sar_volume_strategy, self).__init__()
self._acceleration = self.Param("Acceleration", 0.02) \
.SetRange(0.01, 0.1) \
.SetDisplay("SAR Acceleration", "Starting acceleration factor", "Indicators")
self._maxAcceleration = self.Param("MaxAcceleration", 0.2) \
.SetRange(0.1, 0.5) \
.SetDisplay("SAR Max Acceleration", "Maximum acceleration factor", "Indicators")
self._volumePeriod = self.Param("VolumePeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("Volume Period", "Period for volume moving average", "Indicators")
self._cooldownBars = self.Param("CooldownBars", 30) \
.SetRange(1, 100) \
.SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._candleType = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._parabolicSar = None
self._volumeIndicator = None
self._volumeAverage = None
self._prevSar = 0.0
self._currentAvgVolume = 0.0
self._prevPriceAboveSar = False
self._cooldown = 0
@property
def CandleType(self):
return self._candleType.Value
def OnStarted2(self, time):
super(parabolic_sar_volume_strategy, self).OnStarted2(time)
self._prevSar = 0.0
self._currentAvgVolume = 0.0
self._prevPriceAboveSar = False
self._cooldown = 0
self._parabolicSar = ParabolicSar()
self._parabolicSar.Acceleration = self._acceleration.Value
self._parabolicSar.AccelerationMax = self._maxAcceleration.Value
self._volumeIndicator = VolumeIndicator()
self._volumeAverage = SimpleMovingAverage()
self._volumeAverage.Length = self._volumePeriod.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._parabolicSar, self._volumeIndicator, self.ProcessIndicators).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._parabolicSar)
volumeArea = self.CreateChartArea()
if volumeArea is not None:
self.DrawIndicator(volumeArea, self._volumeIndicator)
self.DrawIndicator(volumeArea, self._volumeAverage)
self.DrawOwnTrades(area)
def OnReseted(self):
super(parabolic_sar_volume_strategy, self).OnReseted()
self._prevSar = 0.0
self._currentAvgVolume = 0.0
self._prevPriceAboveSar = False
self._cooldown = 0
self._parabolicSar = None
self._volumeIndicator = None
self._volumeAverage = None
def ProcessIndicators(self, candle, sarValue, volumeValue):
# Process volume average
avgResult = process_float(self._volumeAverage, volumeValue, candle.ServerTime, True)
if avgResult is None:
return
self._currentAvgVolume = float(avgResult)
if self._currentAvgVolume <= 0:
return
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
currentPrice = float(candle.ClosePrice)
currentVolume = float(candle.TotalVolume)
isPriceAboveSar = currentPrice > float(sarValue)
# Volume must be 1.5x above average
isHighVolume = currentVolume > self._currentAvgVolume * 1.5
if self._cooldown > 0:
self._cooldown -= 1
self._prevSar = float(sarValue)
self._prevPriceAboveSar = isPriceAboveSar
return
cooldown = int(self._cooldownBars.Value)
# Bullish crossover
if isPriceAboveSar and not self._prevPriceAboveSar and isHighVolume and self.Position <= 0:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = cooldown
# Bearish crossover
elif not isPriceAboveSar and self._prevPriceAboveSar and isHighVolume and self.Position >= 0:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = cooldown
# Exit signals
elif (self.Position > 0 and not isPriceAboveSar) or (self.Position < 0 and isPriceAboveSar):
self.ClosePosition()
self._cooldown = cooldown
self._prevSar = float(sarValue)
self._prevPriceAboveSar = isPriceAboveSar
def CreateClone(self):
return parabolic_sar_volume_strategy()