在 GitHub 上查看

抛物线 SAR 报警策略

概述

该策略是 MetaTrader 4 智能交易程序 pSAR_alert.mq4 在 StockSharp 平台上的移植版。原始脚本只会在抛物线 SAR 指标从价格上方翻转到下方(或反向)时播放提示音。本转换保留了相同的判断逻辑,并将提示行为升级为真实的市价委托,使信号能够在 StockSharp 中自动执行。

交易逻辑

  • 策略订阅所配置的K线类型,并以默认的加速因子 0.02 与最大加速 0.2 运行抛物线 SAR 指标。
  • 每根收盘完成的K线都会计算对应的抛物线 SAR 数值,并同时记录上一根K线的 SAR / 收盘价组合。
  • 如果上一根K线收盘价在 SAR 上方,而当前收盘价落在 SAR 下方,说明指标向上翻转,策略将开仓做空(或反手平多)。
  • 如果上一根K线收盘价在 SAR 下方,而当前收盘价升至 SAR 上方,说明指标向下翻转,策略将开仓做多(或反手平空)。
  • 下单手数按照策略基础手数加上当前持仓的绝对值计算,确保反手交易能够完全平掉旧仓后再建立新仓。
  • 启动时调用 StartProtection(),在持仓期间遇到断线等突发情况时由 StockSharp 内置保护模块接管。

参数

参数 默认值 说明
AccelerationFactor 0.02 抛物线 SAR 的初始加速步长,决定指标跟随价格的速度。
MaxAccelerationFactor 0.2 加速步长的上限,限制指标在强趋势中的追踪速度。
CandleType 5 分钟 用于驱动指标的数据类型,可更换为不同周期或其他蜡烛表示方式。

所有参数都通过 StrategyParam<T> 暴露,可直接在 StockSharp Designer 中参与优化。

指标运行流程

  1. 使用 SubscribeCandles 订阅所需的K线流。
  2. 将数据流与 ParabolicSar 指标绑定,让 StockSharp 自动推送更新。
  3. 在回调中比较当前 SAR 值与收盘价,并保存上一根K线的 SAR / 收盘价组合。
  4. 根据 SAR 是否从上向下(看涨翻转)或从下向上(看跌翻转)切换来检测信号。
  5. 分别调用 BuyMarketSellMarket 执行交易,同时记录日志便于回溯。

使用建议

  • 仅在K线收盘后触发信号,可避免未收盘时产生的伪信号。
  • 默认参数再现了 MQL 脚本的行为,如需调整灵敏度可修改加速因子设置。
  • 建议用于趋势相对清晰的品种,抛物线 SAR 翻转在波动较大的环境中可能频繁反复。
  • 如果图表区域可用,策略会自动绘制K线、抛物线 SAR 轨迹以及自身成交,方便实时观察。

文件结构

  • CS/ParabolicSarCrossoverAlertStrategy.cs – 策略的 C# 实现。
  • README.md – 英文说明。
  • README_zh.md – 本中文说明。
  • README_ru.md – 俄文说明。
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR Crossover: EMA crossover with ATR stops.
/// </summary>
public class ParabolicSarCrossoverAlertStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public ParabolicSarCrossoverAlertStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}