在 GitHub 上查看

Pinbar 反转策略

基于原始 MQL 智能交易系统 PINBAR.mq4(目录 MQL/22269)转换。策略在主时间框识别针形线反转,并通过更高时间框的动量与 MACD 滤波确认信号。所有逻辑均使用 StockSharp 的高级 API 实现。

交易逻辑

  • 主时间框:可配置的蜡烛类型,用于识别价格行为形态。
  • 趋势时间框:可配置的更高时间框蜡烛,用于确认动量与 MACD 趋势。
  • 针形线识别:当实体占全幅的比例较小且一端影线明显更长(阈值可调)时判定为针形线。
  • 趋势过滤:快 EMA 必须高于(或低于)慢 EMA,模拟原策略中的 LWMA 过滤条件。
  • 动量确认:趋势时间框的动量在最近三根蜡烛中至少一次超过阈值(多头为正、空头为负)。
  • MACD 确认:趋势时间框的 MACD 必须高于(多头)或低于(空头)信号线,对应原代码中的月线 MACD 检查。
  • 分形过滤:维护最近五根蜡烛的高低点,仅在检测到新的多头/空头分形且未被价格破坏时允许入场。
  • 风险控制:包含百分比止损、止盈、保本触发/偏移以及跟踪止损。当任一条件触发或跟踪价位被突破时退出。

入场规则

多头

  1. 主时间框最后一根蜡烛形成多头针形线(下影线长、实体小)。
  2. 快 EMA > 慢 EMA。
  3. 趋势时间框动量或其前两次数值之一高于阈值。
  4. MACD 高于信号线。
  5. 最近生成多头分形且尚未被价格跌破。
  6. 当前无持仓或持有空头(空头将被反向)。

空头

  1. 主时间框最后一根蜡烛形成空头针形线(上影线长、实体小)。
  2. 快 EMA < 慢 EMA。
  3. 趋势时间框动量或其前两次数值之一低于阈值的相反方向。
  4. MACD 低于信号线。
  5. 最近生成空头分形且尚未被价格突破。
  6. 当前无持仓或持有多头(多头将被反向)。

离场规则

  • 止损与止盈按入场价的百分比计算。
  • 价格达到保本触发比例后,止损移动到入场价并加/减可配置偏移。
  • 达到跟踪启动比例后,激活跟踪止损并按设定距离滑动。
  • 反向信号会直接反向持仓。

参数

名称 默认值 说明
CandleType 15 分钟蜡烛 识别形态的主时间框。
TrendCandleType 1 小时蜡烛 动量与 MACD 滤波所用时间框。
FastMaLength 6 快速 EMA 周期,对应原策略快速 LWMA。
SlowMaLength 85 慢速 EMA 周期,对应原策略慢速 LWMA。
MomentumLength 14 趋势时间框动量周期。
MomentumThreshold 0.1 动量确认所需的最小绝对值。
MacdFastLength 12 MACD 快速 EMA 周期。
MacdSlowLength 26 MACD 慢速 EMA 周期。
MacdSignalLength 9 MACD 信号线 EMA 周期。
BodyToRangeRatio 0.3 实体相对于全幅的最大比例。
WickRatio 0.6 判定针形线所需的最小主影线比例。
StopLossPercent 2 百分比止损幅度。
TakeProfitPercent 4 百分比止盈幅度。
BreakEvenTriggerPercent 1.5 触发保本移动所需的盈利比例。
BreakEvenOffsetPercent 0.2 保本止损额外偏移。
TrailingActivationPercent 2.5 启动跟踪止损的盈利比例。
TrailingDistancePercent 1 跟踪止损与价格的距离。

备注

  • 默认交易量为 1,可在策略属性中调整以适配不同资金规模。
  • 当价格突破记录的分形价位时,分形条件会自动重置,需等待新的分形形成。
  • 关键参数预设了优化区间,方便在 StockSharp Designer 中回测与调优。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class PinbarReversal2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private decimal? _prevFast, _prevSlow;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public PinbarReversal2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 6).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 25).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = null; _prevSlow = null;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
		if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;
		_prevFast = fast; _prevSlow = slow;
		if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
	}
}