在 GitHub 上查看
抛物线SAR斐波挂单策略
概述
抛物线SAR斐波挂单策略移植自 MetaTrader 4 专家顾问 FT_0tk80i9uw4ep_Parabolic。原始机器人利用快慢两套抛物线SAR指标以及斐波回撤水平,在关键回调位置挂出限价单。C# 版本完整保留了分批挂单逻辑、内置的保本和移动止损机制以及可选的交易时段过滤,从而在使用收盘K线时复现 EA 的行为。
策略逻辑
信号准备
- 双抛物线SAR对齐 —— 同一周期内同时计算快、慢两条 SAR。快SAR充当提前预警,慢SAR负责确认。当快SAR跳到价格上方而慢SAR仍在价格下方时,触发潜在做多准备;当快SAR跌到价格下方而慢SAR还在价格上方时,触发潜在做空准备。一旦慢SAR穿越价格,对应的准备信号立即清除。
- 摆动识别 —— 策略在可配置的
Bar Search 窗口内查询最高价和最低价,以重现 EA 中的 MaximumMinimum 辅助函数。上一根收盘K线的高点或低点提供另一端极值,用于锚定斐波计算。
挂单与管理
- 斐波挂单 —— 当两条SAR站在价格同侧且准备信号已激活时,策略会在摆动区间的 50% 斐波位置(
Entry Fibonacci %)挂出限价单。保护性止损在摆动极值之外增加指定点数,止盈位于扩展的斐波投射(Target Fibonacci %)。只有当当前价格、计划的止损以及目标之间彼此至少相距五个最小跳动点时,订单才会被接受,与 EA 的 Point*5 过滤一致。
- 自动撤单 —— 只要快SAR重新越过价格,对应方向的挂单立即撤销,避免在错误的市场阶段入场。某个方向的挂单成交后,另一方向的挂单也会被取消。
风险控制
- 初始止损与止盈 —— 挂单成交后立即应用预先计算的止损和止盈价位,模拟 EA 中订单自带的SL/TP。
- 保本移动 —— 当
Break Even (points) 大于零时,盈利达到设定点数后,止损会移动至入场价上方一个最小跳动(做空则为下方),对应 EA 的 BBU 功能。
- 移动止损 —— 启用
Trailing Stop (points) 后,止损以固定距离跟随价格运行;只有当新的止损较旧止损至少改善 Trailing Step (points) 指定的点数时才会调整,复现 EA 的 TrailingShag 逻辑。
- 手动平仓触发 —— 当收盘价触及计算出的止损或止盈水平时,通过市价单立即平仓,以模拟 MT4 的自动执行效果。
交易时段过滤
- 可选交易窗口 —— 勾选
Use Time Filter 后,仅在 Start Hour 与 Stop Hour(包含端点)的交易时间内允许生成新挂单。即使超出时段,保本、移动止损和出场逻辑仍持续运行,与 MQL 实现保持一致。
参数
- Use Time Filter —— 是否启用交易时段限制。
- Start Hour / Stop Hour —— 启用时段过滤后,允许交易的起止小时(交易所时间)。
- Fast SAR Step / Fast SAR Max —— 快速抛物线SAR的加速因子与最大加速。
- Slow SAR Step / Slow SAR Max —— 慢速抛物线SAR的加速因子与最大加速。
- Bar Search —— 参与摆动高低点计算的K线数量。
- Offset (points) —— 计算止损时相对摆动极值外扩的点数。
- Entry Fibonacci % —— 用于限价挂单的斐波百分比(0–200+)。
- Target Fibonacci % —— 用于止盈投射的斐波百分比。
- Break Even (points) —— 达到指定盈利点数后,将止损移至入场价±一个跳动。设为
0 可关闭。
- Trailing Stop (points) —— 价格与移动止损之间的距离。设为
0 关闭移动止损。
- Trailing Step (points) —— 移动止损每次更新所需的最小收益改进。
- Candle Type —— 指标与摆动计算所使用的K线周期。
- Volume —— 继承自 StockSharp
Strategy 的基础下单手数(默认 0.1)。
其他说明
- 所有以点数表示的参数都会自动乘以品种的价格步长。对于五位小数外汇品种、指数等资产,无需手动调整即可复用 EA 的默认设置。
- 策略仅处理所订阅周期的收盘K线,完整还原 EA 的逐K线执行方式。
- 本策略目前只有 C# 版本,API 包中未提供 Python 版本。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Parabolic SAR strategy with Fibonacci retracement-based targets.
/// Enters on SAR flip, uses Highest/Lowest range for Fibonacci levels.
/// </summary>
public class ParabolicSarFiboLimitsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _lookback;
private decimal _prevSar;
private bool _hasPrevSar;
private decimal _entryPrice;
public ParabolicSarFiboLimitsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(3).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_lookback = Param(nameof(Lookback), 20)
.SetDisplay("Lookback", "Period for Highest/Lowest range.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSar = 0;
_hasPrevSar = false;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSar = 0;
_hasPrevSar = false;
_entryPrice = 0;
var sar = new ParabolicSar();
var highest = new Highest { Length = Lookback };
var lowest = new Lowest { Length = Lookback };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sar, highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal sarValue, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var range = highestValue - lowestValue;
// SAR flip detection
var sarBelow = sarValue < close;
var prevSarBelow = _hasPrevSar && _prevSar < close;
var sarAbove = sarValue > close;
var prevSarAbove = _hasPrevSar && _prevSar > close;
// Fibonacci levels from the range
var fib382 = lowestValue + range * 0.382m;
var fib618 = lowestValue + range * 0.618m;
// Position management
if (Position > 0)
{
// Exit at 61.8% Fibonacci or SAR flip above
if (close >= fib618 || sarAbove)
{
SellMarket();
}
}
else if (Position < 0)
{
// Exit at 38.2% Fibonacci or SAR flip below
if (close <= fib382 || sarBelow)
{
BuyMarket();
}
}
// Entry on SAR flip with range confirmation
if (Position == 0 && _hasPrevSar && range > 0)
{
if (sarBelow && !prevSarBelow && close > fib382)
{
// SAR flipped below price - bullish
_entryPrice = close;
BuyMarket();
}
else if (sarAbove && !prevSarAbove && close < fib618)
{
// SAR flipped above price - bearish
_entryPrice = close;
SellMarket();
}
}
_prevSar = sarValue;
_hasPrevSar = true;
}
}
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, CandleStates
from StockSharp.Algo.Indicators import ParabolicSar, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_fibo_limits_strategy(Strategy):
def __init__(self):
super(parabolic_sar_fibo_limits_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(3))).SetDisplay("Candle Type", "Timeframe", "General")
self._lookback = self.Param("Lookback", 20).SetDisplay("Lookback", "Period for Highest/Lowest range", "Indicators")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(parabolic_sar_fibo_limits_strategy, self).OnReseted()
self._prev_sar = 0
self._has_prev_sar = False
self._entry_price = 0
def OnStarted2(self, time):
super(parabolic_sar_fibo_limits_strategy, self).OnStarted2(time)
self._prev_sar = 0
self._has_prev_sar = False
self._entry_price = 0
sar = ParabolicSar()
highest = Highest()
highest.Length = self._lookback.Value
lowest = Lowest()
lowest.Length = self._lookback.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(sar, highest, lowest, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
def OnProcess(self, candle, sar_value, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
rng = highest_value - lowest_value
sar_below = sar_value < close
prev_sar_below = self._has_prev_sar and self._prev_sar < close
sar_above = sar_value > close
prev_sar_above = self._has_prev_sar and self._prev_sar > close
fib382 = lowest_value + rng * 0.382
fib618 = lowest_value + rng * 0.618
if self.Position > 0:
if close >= fib618 or sar_above:
self.SellMarket()
elif self.Position < 0:
if close <= fib382 or sar_below:
self.BuyMarket()
if self.Position == 0 and self._has_prev_sar and rng > 0:
if sar_below and not prev_sar_below and close > fib382:
self._entry_price = close
self.BuyMarket()
elif sar_above and not prev_sar_above and close < fib618:
self._entry_price = close
self.SellMarket()
self._prev_sar = sar_value
self._has_prev_sar = True
def CreateClone(self):
return parabolic_sar_fibo_limits_strategy()