在 GitHub 上查看

抛物线SAR Bug 3策略

概述

Parabolic SAR Bug 3 Strategy 是位于 MQL/9786 中 MetaTrader 4 智能交易系统 pSAR_bug_3.mq4 的 StockSharp 高级接口移植版本。系统专注于捕捉抛物线SAR第一次翻转到价格另一侧的信号。当SAR落到收盘价下方时,策略会在平掉空头头寸后建立多头;当SAR跳到收盘价上方时,立即翻转为做空。每一笔交易都使用以抛物线SAR点数表示的固定止损和止盈距离,并乘以与原始MQL程序相同的倍数。

交易逻辑

  1. 行情与指标:策略订阅可配置的K线类型(默认15分钟)并绑定一个可调加速参数的抛物线SAR指标。
  2. 状态跟踪:第一根完整K线之后,代码会保存SAR位于收盘价上方还是下方的状态。随后每根K线都会将新的状态与前一根比较,以检测指标翻转。
  3. 做多规则:当SAR从收盘价上方翻到下方时,策略发送市价单,既平掉已有的空头仓位,又建立配置数量的多头仓位,并立即计算保护性的止损和止盈价位。
  4. 做空规则:当SAR从收盘价下方翻到上方时,策略执行相反的流程:平掉多头并建立空头。
  5. 离场方式:每根完成的K线都会将最高价和最低价与存储的保护价比较。一旦触发止损或止盈,系统立即发送市价单平仓,从而模拟MetaTrader中经纪商侧的保护单。

风险控制

  • 止损和止盈距离通过 StopLossPointsTakeProfitPoints 乘以 StopMultiplier 以及交易品种的 PriceStep(若缺省则使用 0.0001)来换算。
  • 只有在 IsFormedAndOnlineAndAllowTrading() 确认行情订阅已就绪并允许交易时才会发送市价单。
  • 当仓位方向发生改变时,会清除旧方向的保护价,避免过期的触发。

参数

名称 默认值 说明
TradeVolume 0.1 下单手数,同时会同步到基础的 Strategy.Volume 属性。
StopLossPoints 90 以抛物线SAR点数表示的止损距离,之后会乘以 StopMultiplier 和最小报价步长。
TakeProfitPoints 20 以抛物线SAR点数表示的止盈距离,之后会乘以 StopMultiplier 和最小报价步长。
StopMultiplier 10 再现MetaTrader中 StopMult 输入参数的倍数,用于兼容带有分数点报价的经纪商。
SarStep 0.02 抛物线SAR的初始加速因子。
SarMaximum 0.2 抛物线SAR的最大加速因子。
CandleType 15分钟 用于指标计算和信号识别的K线类型。

转换说明

  • MetaTrader 版本在开反向仓位之前会先平仓。StockSharp 版本通过发送一笔同时平仓并建立新仓的市价单来达到同样的效果。
  • 经纪商侧的止损/止盈通过监控K线的最高价和最低价并在触发后发送市价单来模拟。
  • StopMultiplier 参数接受任意正值,默认 10,与原始代码注释中建议的数值一致。
  • 按照需求说明,本次转换不包含Python版本。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR Bug 3: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class ParabolicSarBug3Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

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

	public ParabolicSarBug3Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 9)
			.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI 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 RsiLength { get => _rsiLength.Value; set => _rsiLength.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 rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, rsi, 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 rsiVal, 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) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

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