在 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 HourStop 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;
	}
}