在 GitHub 上查看
GPF TCPivotLimit 策略
概述
GPF TCPivotLimit 策略 在 StockSharp 中重现了 MetaTrader 4 专家顾问 gpfTCPivotLimit.mq4。策略基于1 小时K线,利用经典的日内 Pivot 枢轴位寻找反转。每天开盘时先记录上一交易日的最高价、最低价和收盘价,并据此计算 Pivot、三层阻力位(R1–R3)以及三层支撑位(S1–S3)。新的交易日开始后,策略检查最近两根已完成的小时K线,判断是否出现对枢轴位的假突破,并在相反方向开仓。
运行逻辑
- 枢轴计算:当检测到新的一天时,使用上一日数据计算:
Pivot = (High + Low + Close) / 3
R1 = 2 × Pivot − Low,S1 = 2 × Pivot − High
R2 = Pivot + (High − Low),S2 = Pivot − (High − Low)
R3 = High + 2 × (Pivot − Low),S3 = Low − 2 × (High − Pivot)
- 入场确认:在新的一天里观察
t-2 与 t-1 两根已完成的K线:
- 若
t-2 的最高价突破(或收盘价触及)所选阻力位,但开盘价低于该阻力,同时 t-1 收于阻力之下,则开空单。
- 若
t-2 的最低价跌破(或收盘价触及)所选支撑位,但开盘价高于该支撑,同时 t-1 收于支撑之上,则开多单。
- 目标组合:参数
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 |
风控处理:每根完成的K线都会检查止损与止盈。可选的追踪止损与原始 EA 的行为一致:当浮动利润超过设定距离时,止损沿着价格移动。若开启 CloseAtSessionEnd,在平台时间 23:00 处强制平仓。
仓位调整:MetaTrader 参数 isFloatLots 在本策略中对应 UseDynamicVolume。启用后,如果出现连续亏损,会按照 DrawdownFactor 与 RiskPercentage 自动降低下次下单的手数。
参数说明
| 名称 |
说明 |
默认值 |
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class gpf_tcp_pivot_limit_strategy(Strategy):
"""
GPF TCP Pivot Limit: Donchian channel midline crossover.
Buys when close crosses above channel midpoint.
Sells when close crosses below channel midpoint.
"""
def __init__(self):
super(gpf_tcp_pivot_limit_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20) \
.SetDisplay("Channel Period", "Pivot lookback", "Indicators")
self._ema_period = self.Param("EmaPeriod", 14) \
.SetDisplay("EMA Period", "EMA filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(gpf_tcp_pivot_limit_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(gpf_tcp_pivot_limit_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self._channel_period.Value
lowest = Lowest()
lowest.Length = self._channel_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, self._process_candle).Start()
def _process_candle(self, candle, highest_val, lowest_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
h = float(highest_val)
l = float(lowest_val)
mid = (h + l) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_close = close
self._prev_mid = mid
return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
self.BuyMarket()
self._cooldown = 2
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
self.SellMarket()
self._cooldown = 2
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return gpf_tcp_pivot_limit_strategy()