在 GitHub 上查看

GPF TCPivotLimit 策略

概述

GPF TCPivotLimit 策略 在 StockSharp 中重现了 MetaTrader 4 专家顾问 gpfTCPivotLimit.mq4。策略基于1 小时K线,利用经典的日内 Pivot 枢轴位寻找反转。每天开盘时先记录上一交易日的最高价、最低价和收盘价,并据此计算 Pivot、三层阻力位(R1–R3)以及三层支撑位(S1–S3)。新的交易日开始后,策略检查最近两根已完成的小时K线,判断是否出现对枢轴位的假突破,并在相反方向开仓。

运行逻辑

  1. 枢轴计算:当检测到新的一天时,使用上一日数据计算:
    • Pivot = (High + Low + Close) / 3
    • R1 = 2 × Pivot − LowS1 = 2 × Pivot − High
    • R2 = Pivot + (High − Low)S2 = Pivot − (High − Low)
    • R3 = High + 2 × (Pivot − Low)S3 = Low − 2 × (High − Pivot)
  2. 入场确认:在新的一天里观察 t-2t-1 两根已完成的K线:
    • t-2 的最高价突破(或收盘价触及)所选阻力位,但开盘价低于该阻力,同时 t-1 收于阻力之下,则开空单。
    • t-2 的最低价跌破(或收盘价触及)所选支撑位,但开盘价高于该支撑,同时 t-1 收于支撑之上,则开多单。
  3. 目标组合:参数 TargetMode 完全复制原始 EA 的五种止盈/止损组合,对应关系如下表。
TargetMode 多头入场 多头止损 多头目标 空头入场 空头止损 空头目标
1 S1 S2 R1 R1 R2 S1
2 S1 S2 R2 R1 R2 S2
3 S2 S3 R1 R2 R3 S1
4 S2 S3 R2 R2 R3 S2
5 S2 S3 R3 R2 R3 S3
  1. 风控处理:每根完成的K线都会检查止损与止盈。可选的追踪止损与原始 EA 的行为一致:当浮动利润超过设定距离时,止损沿着价格移动。若开启 CloseAtSessionEnd,在平台时间 23:00 处强制平仓。

  2. 仓位调整:MetaTrader 参数 isFloatLots 在本策略中对应 UseDynamicVolume。启用后,如果出现连续亏损,会按照 DrawdownFactorRiskPercentage 自动降低下次下单的手数。

参数说明

名称 说明 默认值
BaseVolume 在风险调整前下单使用的基础手数。 1
UseDynamicVolume 连续亏损(超过1次)后自动减少手数。 false
RiskPercentage 单笔交易的参考风险比率(原 EA 的 MaxR),用于缩放基础手数。 0.02
DrawdownFactor 连续亏损时的缩量系数(原 EA 的 DcF)。 3
TargetMode 选择上述五种枢轴组合之一(原 EA 的 TgtProfit)。 1
TrailingPoints 追踪止损距离,以最小价格变动单位表示,0 表示关闭。 30
CloseAtSessionEnd 若为 true,在 23:00 蜡烛收盘时平掉持仓。 false
LogSignals 将 Pivot 值、进出场事件写入日志,替代原策略的邮件通知。 false
CandleType 使用的K线类型(默认为 1 小时K线)。 TimeFrameCandleMessage(1h)

补充说明

  • 策略始终发送市价单,未实现任何挂单逻辑,与原版 EA 保持一致。
  • 止损/止盈触发时采用市价平仓,以便兼容所有 StockSharp 连接器。
  • 追踪止损依赖于证券的 PriceStep。若行情未提供最小跳动值,则追踪功能自动停用。
  • 原 EA 的邮件提醒通过 LogSignals 参数替换为日志输出,便于在不同运行环境中调试。
using System;



using StockSharp.Algo.Indicators;

using StockSharp.Algo.Strategies;

using StockSharp.BusinessEntities;

using StockSharp.Messages;



namespace StockSharp.Samples.Strategies;



public class GpfTcpPivotLimitStrategy : Strategy

{

	private readonly StrategyParam<int> _channelPeriod;

	private readonly StrategyParam<int> _emaPeriod;

	private readonly StrategyParam<DataType> _candleType;



	private decimal _prevClose; private decimal _prevMid; private bool _hasPrev;

	private int _cooldown;



	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }

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



	public GpfTcpPivotLimitStrategy()

	{

		_channelPeriod = Param(nameof(ChannelPeriod), 20).SetDisplay("Channel Period", "Pivot lookback", "Indicators");

		_emaPeriod = Param(nameof(EmaPeriod), 14).SetDisplay("EMA Period", "EMA filter", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");

	}



	/// <inheritdoc />

	protected override void OnReseted()

	{

		base.OnReseted();

		_prevClose = default;

		_prevMid = default;

		_hasPrev = default;

		_cooldown = default;

	}



	/// <inheritdoc />

	protected override void OnStarted2(DateTime time)

	{

		base.OnStarted2(time);

		_hasPrev = false;

		var highest = new Highest { Length = ChannelPeriod };

		var lowest = new Lowest { Length = ChannelPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription.Bind(highest, lowest, ProcessCandle).Start();

	}



	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)

	{

		if (candle.State != CandleStates.Finished) return;

		if (!IsFormedAndOnlineAndAllowTrading()) return;

		var close = candle.ClosePrice;

		var mid = (highest + lowest) / 2;

		if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }

		if (_cooldown > 0)

		{

			_cooldown--;

			_prevClose = close; _prevMid = mid;

			return;

		}



		if (_prevClose <= _prevMid && close > mid && Position <= 0)

		{

			var volume = Volume + Math.Abs(Position);

			BuyMarket(volume);

			_cooldown = 2;

		}

		else if (_prevClose >= _prevMid && close < mid && Position >= 0)

		{

			var volume = Volume + Math.Abs(Position);

			SellMarket(volume);

			_cooldown = 2;

		}

		_prevClose = close; _prevMid = mid;

	}

}