在 GitHub 上查看
TCPivot Session Stop 策略
概述
TCPivot Session Stop 策略直接移植自 MetaTrader 4 智能交易系统 gpfTCPivotStop。它围绕上一交易日计算出的经典枢轴点进行交易,核心逻辑如下:
- 根据上一日的最高价、最低价和收盘价计算枢轴点以及三层支撑/阻力位。
- 当当前收盘价向上突破枢轴点时做多,向下跌破时做空。
- 按突破方向开仓,并将止损、止盈设置在所选的枢轴层级。
- 可选地在指定的交易时段开始时强制平仓,以复现原始策略的日内退出机制。
实现基于 StockSharp 高级 API,仓位规模使用基类 Strategy 的 Volume 属性控制。
参数
| 名称 |
说明 |
默认值 |
TargetLevel |
用作止损/止盈的枢轴层级(1、2 或 3)。 |
1 |
CloseAtSessionStart |
启用后,在设置的小时开始时平掉持仓。 |
false |
SessionCloseHour |
配合 CloseAtSessionStart 使用的小时(0-23)。 |
0 |
CandleType |
产生交易信号的K线周期。 |
H1 |
交易逻辑
- 订阅配置周期的K线作为信号源,同时订阅日线用于枢轴计算。
- 每根日线收盘后计算经典枢轴:
Pivot = (High + Low + Close) / 3
R1 = 2 * Pivot - Low,S1 = 2 * Pivot - High
R2 = Pivot + (R1 - S1),S2 = Pivot - (R1 - S1)
R3 = High + 2 * (Pivot - Low),S3 = Low - 2 * (High - Pivot)
- 每根信号K线收盘时:
- 若启用
CloseAtSessionStart 且开盘时间等于 SessionCloseHour,立即平仓。
- 若空仓且上一根收盘价在枢轴下方、当前收盘价上穿枢轴,则做多并应用所选层级的止损/止盈。
- 若空仓且上一根收盘价在枢轴上方、当前收盘价下破枢轴,则做空并应用镜像的止损/止盈。
- 若已有持仓,当收盘价触及相应止损或止盈时平仓。
备注
- 调用
StartProtection() 可以接入平台的风险控制模块,具体的止损/止盈判定在策略内部完成。
- 原始 MT4 版本包含邮件通知和基于风险的浮动手数,本移植版本未包含此功能。如有需要,请结合 StockSharp 的通知及资金管理组件使用。
- 原策略的
isTradeDay 选项在午夜平仓,可通过 CloseAtSessionStart 与 SessionCloseHour=0 的组合复现。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class TcpPivotSessionStopStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast; private decimal _prevSlow; private bool _hasPrev;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TcpPivotSessionStopStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 7).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = default;
_prevSlow = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fast; _prevSlow = slow;
return;
}
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 2;
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 2;
}
_prevFast = fast; _prevSlow = slow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class tcp_pivot_session_stop_strategy(Strategy):
def __init__(self):
super(tcp_pivot_session_stop_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 7).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False; self._cooldown = 0
@property
def fast_period(self): return self._fast_period.Value
@property
def slow_period(self): return self._slow_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(tcp_pivot_session_stop_strategy, self).OnReseted()
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False; self._cooldown = 0
def OnStarted2(self, time):
super(tcp_pivot_session_stop_strategy, self).OnStarted2(time)
self._has_prev = False; self._cooldown = 0
fast = ExponentialMovingAverage(); fast.Length = self.fast_period
slow = ExponentialMovingAverage(); slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished: return
if not self.IsFormedAndOnlineAndAllowTrading(): return
fast_val = float(fast); slow_val = float(slow)
if not self._has_prev:
self._prev_fast = fast_val; self._prev_slow = slow_val; self._has_prev = True; return
if self._cooldown > 0:
self._cooldown -= 1; self._prev_fast = fast_val; self._prev_slow = slow_val; return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume); self._cooldown = 2
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume); self._cooldown = 2
self._prev_fast = fast_val; self._prev_slow = slow_val
def CreateClone(self): return tcp_pivot_session_stop_strategy()