Parabolic SAR Bug5 策略
概述
Parabolic SAR Bug5 策略使用抛物线 SAR 指标识别价格反转。当价格上穿 SAR 时开多头,下穿 SAR 时开空头。策略可以反转交易方向,在 SAR 翻转时平掉已有仓位,并提供止损、止盈和跟踪止损。
入场规则
- 当价格上穿 SAR 且没有多头仓位时买入。
- 当价格下穿 SAR 且没有空头仓位时卖出。
- 如果启用
Reverse,信号方向反转。
出场规则
- 若启用
SarClose,当 SAR 发出相反信号时平仓。 - 固定止损和止盈目标。
- 若启用
Trailing,止损将根据进场后的最高价或最低价移动。
参数
| 参数 | 说明 |
|---|---|
Step |
Parabolic SAR 的初始加速因子。 |
Maximum |
Parabolic SAR 的最大加速因子。 |
StopLossPoints |
止损距离(点)。 |
TakeProfitPoints |
止盈距离(点)。 |
Trailing |
是否启用跟踪止损。 |
TrailPoints |
跟踪止损距离(点)。 |
Reverse |
反转交易方向。 |
SarClose |
SAR 翻转时是否平仓。 |
CandleType |
处理的 K 线周期。 |
using System;
using System.Linq;
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>
/// Parabolic SAR strategy with optional reversal and trailing stop.
/// Based on conversion of MQL script pSAR_bug_5.
/// </summary>
public class ParabolicSarBug5Strategy : Strategy
{
private readonly StrategyParam<decimal> _step;
private readonly StrategyParam<decimal> _maximum;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _trailing;
private readonly StrategyParam<decimal> _trailPoints;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<bool> _sarClose;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevSar;
private bool _prevAbove;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _highestPrice;
private decimal _lowestPrice;
/// <summary>
/// Parabolic SAR acceleration factor.
/// </summary>
public decimal Step { get => _step.Value; set => _step.Value = value; }
/// <summary>
/// Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal Maximum { get => _maximum.Value; set => _maximum.Value = value; }
/// <summary>
/// Stop loss distance in points.
/// </summary>
public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
/// <summary>
/// Take profit distance in points.
/// </summary>
public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
/// <summary>
/// Enable trailing stop.
/// </summary>
public bool Trailing { get => _trailing.Value; set => _trailing.Value = value; }
/// <summary>
/// Trailing distance in points.
/// </summary>
public decimal TrailPoints { get => _trailPoints.Value; set => _trailPoints.Value = value; }
/// <summary>
/// Reverse trading direction.
/// </summary>
public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
/// <summary>
/// Close position on SAR switch.
/// </summary>
public bool SarClose { get => _sarClose.Value; set => _sarClose.Value = value; }
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public ParabolicSarBug5Strategy()
{
_step = Param(nameof(Step), 0.001m)
.SetDisplay("Step", "Initial acceleration factor", "Indicators");
_maximum = Param(nameof(Maximum), 0.2m)
.SetDisplay("Maximum", "Maximum acceleration factor", "Indicators");
_stopLossPoints = Param(nameof(StopLossPoints), 90m)
.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
.SetDisplay("Take Profit", "Take profit distance in points", "Risk");
_trailing = Param(nameof(Trailing), false)
.SetDisplay("Use Trailing", "Enable trailing stop", "Risk");
_trailPoints = Param(nameof(TrailPoints), 10m)
.SetDisplay("Trail Points", "Trailing distance in points", "Risk");
_reverse = Param(nameof(Reverse), false)
.SetDisplay("Reverse", "Reverse trading direction", "General");
_sarClose = Param(nameof(SarClose), true)
.SetDisplay("SAR Close", "Close position on SAR switch", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSar = 0m;
_prevAbove = false;
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
_highestPrice = 0m;
_lowestPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var psar = new ParabolicSar
{
Acceleration = Step,
AccelerationMax = Maximum
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(psar, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, psar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal sar)
{
if (candle.State != CandleStates.Finished)
return;
var priceAbove = candle.ClosePrice > sar;
var crossing = _prevSar > 0 && priceAbove != _prevAbove;
if (crossing)
{
var isBuySignal = priceAbove;
if (Reverse)
isBuySignal = !isBuySignal;
if (isBuySignal && Position <= 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_takePrice = _entryPrice + TakeProfitPoints;
_stopPrice = _entryPrice - StopLossPoints;
_highestPrice = candle.HighPrice;
}
else if (!isBuySignal && Position >= 0)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_takePrice = _entryPrice - TakeProfitPoints;
_stopPrice = _entryPrice + StopLossPoints;
_lowestPrice = candle.LowPrice;
}
}
if (Position > 0 && _entryPrice is decimal)
{
_highestPrice = Math.Max(_highestPrice, candle.HighPrice);
if (Trailing)
_stopPrice = Math.Max(_stopPrice ?? 0m, _highestPrice - TrailPoints);
if (_stopPrice is decimal sl && candle.LowPrice <= sl)
{
SellMarket();
ResetState();
}
else if (_takePrice is decimal tp && candle.HighPrice >= tp)
{
SellMarket();
ResetState();
}
}
else if (Position < 0 && _entryPrice is decimal)
{
_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);
if (Trailing)
_stopPrice = Math.Min(_stopPrice ?? decimal.MaxValue, _lowestPrice + TrailPoints);
if (_stopPrice is decimal sl && candle.HighPrice >= sl)
{
BuyMarket();
ResetState();
}
else if (_takePrice is decimal tp && candle.LowPrice <= tp)
{
BuyMarket();
ResetState();
}
}
_prevSar = sar;
_prevAbove = priceAbove;
}
private void ResetState()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
_highestPrice = 0m;
_lowestPrice = 0m;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_bug5_strategy(Strategy):
def __init__(self):
super(parabolic_sar_bug5_strategy, self).__init__()
self._step = self.Param("Step", 0.001) \
.SetDisplay("Step", "Initial acceleration factor", "Indicators")
self._maximum = self.Param("Maximum", 0.2) \
.SetDisplay("Maximum", "Maximum acceleration factor", "Indicators")
self._stop_loss_points = self.Param("StopLossPoints", 90.0) \
.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 20.0) \
.SetDisplay("Take Profit", "Take profit distance in points", "Risk")
self._trailing = self.Param("Trailing", False) \
.SetDisplay("Use Trailing", "Enable trailing stop", "Risk")
self._trail_points = self.Param("TrailPoints", 10.0) \
.SetDisplay("Trail Points", "Trailing distance in points", "Risk")
self._reverse = self.Param("Reverse", False) \
.SetDisplay("Reverse", "Reverse trading direction", "General")
self._sar_close = self.Param("SarClose", True) \
.SetDisplay("SAR Close", "Close position on SAR switch", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_sar = 0.0
self._prev_above = False
self._entry_price = None
self._stop_price = None
self._take_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
@property
def step(self):
return self._step.Value
@property
def maximum(self):
return self._maximum.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
@property
def trailing(self):
return self._trailing.Value
@property
def trail_points(self):
return self._trail_points.Value
@property
def reverse(self):
return self._reverse.Value
@property
def sar_close(self):
return self._sar_close.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(parabolic_sar_bug5_strategy, self).OnReseted()
self._prev_sar = 0.0
self._prev_above = False
self._entry_price = None
self._stop_price = None
self._take_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
def OnStarted2(self, time):
super(parabolic_sar_bug5_strategy, self).OnStarted2(time)
psar = ParabolicSar()
psar.Acceleration = self.step
psar.AccelerationMax = self.maximum
self._prev_sar = 0.0
self._prev_above = False
self._entry_price = None
self._stop_price = None
self._take_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(psar, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, psar)
self.DrawOwnTrades(area)
def on_process(self, candle, sar):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sar_f = float(sar)
price_above = close > sar_f
crossing = self._prev_sar > 0 and price_above != self._prev_above
if crossing:
is_buy_signal = price_above
if self.reverse:
is_buy_signal = not is_buy_signal
if is_buy_signal and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._take_price = close + self.take_profit_points
self._stop_price = close - self.stop_loss_points
self._highest_price = float(candle.HighPrice)
elif not is_buy_signal and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._take_price = close - self.take_profit_points
self._stop_price = close + self.stop_loss_points
self._lowest_price = float(candle.LowPrice)
if self.Position > 0 and self._entry_price is not None:
self._highest_price = max(self._highest_price, float(candle.HighPrice))
if self.trailing:
self._stop_price = max(self._stop_price if self._stop_price is not None else 0, self._highest_price - self.trail_points)
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
self._reset_state()
elif self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket()
self._reset_state()
elif self.Position < 0 and self._entry_price is not None:
self._lowest_price = min(self._lowest_price, float(candle.LowPrice))
if self.trailing:
self._stop_price = min(self._stop_price if self._stop_price is not None else 1e18, self._lowest_price + self.trail_points)
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
self._reset_state()
elif self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket()
self._reset_state()
self._prev_sar = sar_f
self._prev_above = price_above
def _reset_state(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
def CreateClone(self):
return parabolic_sar_bug5_strategy()