盘后 4 小时:你在刷剧,别人在计算明天的信号
凌晨 2 点,你被一条推送震醒——某家你持仓公司的 CEO 突然宣布辞职。睡意全无,盯着手机屏幕开始焦虑:明天开盘怎么办?挂跌停跑,还是等反弹?
而隔壁老王早就睡了。他的量化系统在后半夜已经完成了所有计算:
- CEO 辞职消息发布时,对应板块期货下跌 1.2%,预计带动正股低开 3-5%
- 恐慌指数跳升 4.5 个点,put/call 比从 0.8 飙到 1.3,期权市场定价了更大的波动区间
- 同日财报历史数据显示,相似事件下前 15 分钟的低吸胜率约 62%,但需配合盘口流动性确认
- 盘前 30 分钟关键价位已标记,系统告警阈值已调整完毕
差距不在信息,在于信号预计算的完成度。
本文从工程视角拆解:收盘后到次日开盘前这段"隐形窗口",可以系统性地计算哪些信号?每个信号的计算逻辑、优先级和实操代码怎么写?重点解决一个核心问题——让你在 9:15 集合竞价开始前,就已经知道今天开盘大概率怎么走,而不是临时看盘拍脑袋。
一、为什么盘前 4 小时是量化系统的"加时段"
1.1 三个被低估的数据事实
事实一:美股盘后成交量约占全天 3-5%,但信息密度极高
盘后交易(After-hours)并非可有可无的"垃圾时间"。重大事件(财报、并购、高管变动)发布后,超 40% 的价格发现发生在盘后交易段。这段时间的订单簿结构直接决定次日开盘价的基准。
事实二:隔夜风险敞口是可以量化的
如果你持有隔夜仓位,你承担的不只是价格风险,还包括:
| 风险类型 | 可量化程度 | 计算窗口 |
|---|---|---|
| 宏观事件(Fed 讲话、CPI) | 高(期货领先指标) | T-1 日盘后 |
| 个股公告(财报、并购) | 高(盘后交易价格发现) | T-1 日盘后 |
| 地缘/商品冲击 | 中(期货/数字货币领先) | T-1 日盘后至 T 日盘前 |
| 隔夜流动性枯竭 | 高(VIX 结构分析) | 每日盘后 |
事实三:盘前准备的质量决定开盘执行的胜率
很多交易者把开盘前 15 分钟用来盯盘——这是最低效的用法。正确的姿势是:把盯盘时间留给确认和微调,把分析工作留给盘后和盘前。 系统在盘前已经输出"今日开盘预期",你只需要在 9:15 对比实际盘口与预期,决定是否触发预设信号。
1.2 信号预计算的系统架构
T日收盘 (16:00 ET)
│
├─[盘后阶段 16:00 - 20:00 ET]
│ ├─ 事件扫描:新闻/API/公告解析
│ ├─ 盘后交易分析:价格发现、成交量异动
│ └─ 隔夜仓位风险评估
│
├─[深夜阶段 20:00 - 04:00 ET]
│ ├─ 宏观数据预加载(FedWatch、CPI 预期)
│ ├─ 期货夜盘监控(S&P500 futures、原油、黄金)
│ └─ 期权未平仓量(OI)结构分析
│
└─[盘前阶段 04:00 - 09:15 ET]
├─ 盘前流动性预估
├─ 集合竞价价格预测
├─ 关键价位标记与告警设置
└─ 策略参数动态调整
│
▼
T日开盘 (09:30 ET) → 预设信号触发 → 执行
接下来的章节会逐一拆解每个阶段的核心信号及其计算逻辑,并给出可直接运行的生产级 Python 代码。
二、盘后事件扫描:把"消息"转化为"可计算信号"
2.1 信号类型与优先级矩阵
盘后阶段(16:00-20:00 ET)是最宝贵的信息加工窗口。并非所有事件都需要同等对待,以下是按信息密度 × 信号衰减速度分类的信号矩阵:
| 信号类型 | 数据来源 | 典型衰减周期 | 预计算价值 | 优先级 |
|---|---|---|---|---|
| 财报/业绩预警 | 公司公告 API | 30 分钟内定价完毕 | 高(预期差可量化) | P0 |
| 并购/要约收购 | 交易所公告 | 2-4 小时 | 极高(价差收敛明确) | P0 |
| 高管变动/诉讼 | 新闻流 | 不确定(市场解读分歧) | 中(需配合盘前盘口确认) | P1 |
| 分析师评级调整 | 券商报告 | 30 分钟至数小时 | 中(定价分歧可被机构消化) | P1 |
| Fed 官员讲话 | 央行官网 | 即时 | 高(利率预期实时变化) | P0 |
| 大宗商品冲击 | 期货市场 | 取决于事件类型 | 高(期货 23 小时交易) | P0 |
2.2 事件驱动的价格预期估算
对于 P0 级事件(财报、并购),我们可以在盘后计算一个关键指标:隐含开盘价区间。
这不是预测,而是基于盘后交易价格发现的合理区间估算:
import requests
import os
import time
from datetime import datetime, date
class AfterMarketEventScanner:
"""
盘后事件扫描器
监控盘后公告,估算次日隐含开盘区间
⚠️ 此代码为生产级参考实现,需要根据实际数据源适配
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {"X-API-Key": api_key}
def estimate_implied_open(self, symbol: str, event_price: float,
prev_close: float, confidence: float = 0.68):
"""
估算隐含开盘价区间
Args:
symbol: 股票代码
event_price: 盘后交易价格
prev_close: 昨日收盘价
confidence: 置信区间(0.68 ≈ 1σ,0.95 ≈ 2σ)
Returns:
dict: 含隐含涨跌幅和区间估算
"""
implied_move = (event_price - prev_close) / prev_close
# 基于历史波动率估算区间宽度
# 简化模型:使用 ATR 的 0.5 倍作为日内波动基准
# ⚠️ 生产环境应从 TickDB kline 历史数据计算真实 ATR
symbol_info = self._get_symbol_info(symbol)
atr_factor = 0.5 # 保守估计:实际应计算真实 ATR
# 置信区间宽度与事件类型相关
# 财报事件:区间宽;并购公告:区间窄
interval_multiplier = 1.0 if "acquisition" not in str(symbol_info) else 0.3
return {
"symbol": symbol,
"prev_close": prev_close,
"event_price": event_price,
"implied_move_pct": round(implied_move * 100, 2),
"lower_bound_pct": round((implied_move - confidence * interval_multiplier) * 100, 2),
"upper_bound_pct": round((implied_move + confidence * interval_multiplier) * 100, 2),
"direction_bias": "bullish" if implied_move > 0.01 else "bearish" if implied_move < -0.01 else "neutral",
"confidence": confidence,
"timestamp": datetime.utcnow().isoformat()
}
def _get_symbol_info(self, symbol: str) -> dict:
"""查询交易品种基础信息"""
# ⚠️ 生产环境:实际调用 TickDB symbols 接口
# response = requests.get(
# f"https://api.tickdb.ai/v1/symbols/{symbol}",
# headers=self.headers,
# timeout=(3.05, 10)
# )
# handle_api_error(response, symbol)
return {"symbol": symbol, "type": "stock"}
def scan_and_rank(self, watchlist: list) -> list:
"""
扫描观察列表,按隐含波动排序输出优先级
Returns:
按绝对预期波动率降序排列的事件列表
"""
results = []
for symbol in watchlist:
try:
# 实际场景中,这里需要接入新闻/API 数据源
# 获取盘后交易价格和事件类型
event_data = self._fetch_afterhours_data(symbol)
if event_data:
estimate = self.estimate_implied_open(
symbol=symbol,
event_price=event_data["price"],
prev_close=event_data["prev_close"],
confidence=0.68
)
results.append(estimate)
except Exception as e:
# 记录错误但不中断整个扫描流程
print(f"[WARN] {symbol} 扫描失败: {e}")
continue
# 按隐含波动率绝对值降序排列
results.sort(key=lambda x: abs(x["implied_move_pct"]), reverse=True)
return results
def _fetch_afterhours_data(self, symbol: str) -> dict:
"""获取盘后交易数据"""
# ⚠️ 占位实现。生产环境应接入实际数据源
# 示例:从 TickDB 盘后交易频道获取(部分品种支持)
# response = requests.get(
# f"https://api.tickdb.ai/v1/market/trades/latest",
# headers=self.headers,
# params={"symbol": symbol},
# timeout=(3.05, 10)
# )
raise NotImplementedError(
"需要接入实际数据源(如新闻 API、财报 API)后替换此方法"
)
# ========== 使用示例 ==========
if __name__ == "__main__":
api_key = os.environ.get("TICKDB_API_KEY")
scanner = AfterMarketEventScanner(api_key=api_key)
watchlist = ["AAPL.US", "TSLA.US", "NVDA.US"]
ranked_events = scanner.scan_and_rank(watchlist)
print("=== 盘后事件扫描结果 ===")
for i, event in enumerate(ranked_events, 1):
print(f"\n#{i} {event['symbol']}")
print(f" 隐含波动: {event['implied_move_pct']:+.2f}%")
print(f" 区间: [{event['lower_bound_pct']:+.2f}%, {event['upper_bound_pct']:+.2f}%]")
print(f" 方向偏好: {event['direction_bias']}")
工程预警:
_fetch_afterhours_data方法需要接入实际新闻/公告数据源(如 Alpha Vantage、Polygon、Finnhub),TickDB 的 trades 接口目前不支持美股,如需美股逐笔盘后交易数据需补充数据源- 隐含开盘区间估算使用简化模型(固定 ATR 倍数),生产环境应基于历史数据计算真实 ATR 和事件类型权重
三、期货夜盘:宏观信号的实时捕获
3.1 为什么期货是盘前最可靠的方向指标
S&P500 期货(ES)、纳斯达克期货(NQ)在美东时间周日至周四 18:00 ET 开盘,次日 17:00 ET 收盘。这意味着:
- 在美股正式开盘前,期货市场已经交易了 15+ 小时
- 宏观事件(隔夜亚洲市场、Fed 讲话、大宗商品)会率先在期货价格中反映
- 期货的 pre-market gap 百分比是次日开盘价的强力先验指标
关键指标:
- 期货隐含开盘(Fair Value):期货价格与理论价值的偏差
- 隔夜净变化:期货收盘价 vs 当日收盘价的差异
- 波动率锥:基于期货期权的隐含波动率期限结构
3.2 期货信号预计算代码
import asyncio
import websockets
import json
import os
import time
import random
from datetime import datetime
from typing import Optional
class FuturesSignalMonitor:
"""
期货信号监控器(WebSocket 实时版)
监控 S&P500 期货、纳斯达克期货、原油、黄金的盘前动态
⚠️ 生产环境高频场景建议使用 aiohttp/asyncio 架构
"""
def __init__(self, api_key: str):
self.api_key = api_key
# TickDB 支持的期货品种示例(需确认实际 symbol)
self.futures_symbols = {
"ES": "ES.CUR", # S&P500 E-mini 期货
"NQ": "NQ.CUR", # 纳斯达克 E-mini 期货
"CL": "CL.CUR", # WTI 原油
"GC": "GC.CUR", # 黄金
}
self._price_history = {s: [] for s in self.futures_symbols}
self._last_heartbeat = time.time()
async def connect_with_retry(self, uri: str, max_retries: int = 5):
"""带指数退避的 WebSocket 连接"""
base_delay, max_delay = 1.0, 30.0
for attempt in range(max_retries):
try:
# ⚠️ TickDB WebSocket 鉴权通过 URL 参数传递
async with websockets.connect(
f"{uri}?api_key={self.api_key}"
) as ws:
await self._websocket_loop(ws, uri)
except websockets.exceptions.ConnectionClosed as e:
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1)
wait_time = delay + jitter
print(f"[WARN] WebSocket 连接断开 (attempt {attempt + 1}): {e}")
print(f"[INFO] {wait_time:.1f} 秒后重连...")
time.sleep(wait_time)
except Exception as e:
print(f"[ERROR] 未知连接错误: {e}")
break
async def _websocket_loop(self, ws, uri: str):
"""WebSocket 主循环"""
print(f"[INFO] WebSocket 连接成功: {uri}")
ping_interval = 30 # TickDB 推荐心跳间隔
while True:
try:
# 发送心跳保活(部分 WebSocket 服务需要)
# await ws.ping()
# 订阅期货行情(具体格式参考 TickDB WebSocket API 文档)
subscribe_msg = {
"cmd": "subscribe",
"args": list(self.futures_symbols.values())
}
await ws.send(json.dumps(subscribe_msg))
# 收取实时数据,超时自动重连
while True:
try:
message = await asyncio.wait_for(
ws.recv(),
timeout=ping_interval + 5
)
data = json.loads(message)
self._process_futures_tick(data)
except asyncio.TimeoutError:
# 超时,重新发送订阅以保活
await ws.send(json.dumps(subscribe_msg))
self._last_heartbeat = time.time()
except websockets.exceptions.ConnectionClosed:
raise # 触发外层重连逻辑
def _process_futures_tick(self, data: dict):
"""处理期货行情推送"""
# TickDB depth 频道数据格式:
# {"symbol": "ES.CUR", "type": "depth", "bids": [...], "asks": [...]}
symbol = data.get("symbol")
if symbol not in self.futures_symbols.values():
return
tick_type = data.get("type")
if tick_type == "depth":
# 深度数据用于流动性分析和盘前价差预估
bids = data.get("bids", [])
asks = data.get("asks", [])
if bids and asks:
spread = asks[0][0] - bids[0][0]
mid_price = (asks[0][0] + bids[0][0]) / 2
best_bid_vol = bids[0][1]
best_ask_vol = asks[0][1]
liquidity_ratio = best_ask_vol / best_bid_vol if best_bid_vol > 0 else 0
self._price_history[symbol].append({
"time": datetime.utcnow().isoformat(),
"mid": mid_price,
"spread": spread,
"liquidity_ratio": liquidity_ratio
})
# 保留最近 100 条记录防止内存膨胀
if len(self._price_history[symbol]) > 100:
self._price_history[symbol] = self._price_history[symbol][-100:]
# 实时告警:流动性失衡
if abs(1 - liquidity_ratio) > 0.5:
print(f"[ALERT] {symbol}: 流动性失衡! "
f"买卖压力比 = {liquidity_ratio:.2f}, "
f"当前价差 = {spread:.2f}")
def calculate_premarket_bias(self, symbol_key: str) -> dict:
"""
基于盘前期货数据计算方向偏差
Returns:
含均值、波动率、趋势方向的统计摘要
"""
history = self._price_history[self.futures_symbols.get(symbol_key, symbol_key)]
if len(history) < 5:
return {"status": "insufficient_data", "samples": len(history)}
mid_prices = [h["mid"] for h in history]
spreads = [h["spread"] for h in history]
import statistics
mean_price = statistics.mean(mid_prices)
latest_price = mid_prices[-1]
move_from_mean = (latest_price - mean_price) / mean_price * 100
return {
"symbol": symbol_key,
"latest": round(latest_price, 2),
"mean_premarket": round(mean_price, 2),
"move_from_mean_pct": round(move_from_mean, 2),
"avg_spread": round(statistics.mean(spreads), 4),
"samples": len(history),
"bias": "bullish" if move_from_mean > 0.5 else "bearish" if move_from_mean < -0.5 else "neutral",
"timestamp": datetime.utcnow().isoformat()
}
def generate_direction_report(self) -> str:
"""生成宏观方向综合报告"""
report_lines = [
"\n========== 盘前期货方向报告 ==========",
f"生成时间 (UTC): {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}",
""
]
for key in self.futures_symbols:
bias = self.calculate_premarket_bias(key)
status = bias.get("status", "ok")
if status == "insufficient_data":
report_lines.append(f"{key}: 数据不足 ({bias['samples']} 条),跳过")
continue
emoji = {"bullish": "🟢", "bearish": "🔴", "neutral": "⚪️"}.get(bias["bias"], "")
report_lines.append(
f"{emoji} {key}: {bias['bias'].upper()} "
f"(偏离均值 {bias['move_from_mean_pct']:+.2f}%, "
f"样本 {bias['samples']})"
)
report_lines.append("=" * 40)
return "\n".join(report_lines)
async def main():
api_key = os.environ.get("TICKDB_API_KEY")
monitor = FuturesSignalMonitor(api_key=api_key)
# 后台持续监控
ws_uri = "wss://api.tickdb.ai/v1/ws/market" # ⚠️ 占位 URI,需参考实际 TickDB WebSocket 端点
try:
await asyncio.wait_for(
monitor.connect_with_retry(ws_uri),
timeout=60 # 演示用:60 秒后退出;生产环境移除此限制
)
except asyncio.TimeoutError:
print("[INFO] 演示结束,打印报告:")
print(monitor.generate_direction_report())
if __name__ == "__main__":
asyncio.run(main())
工程预警:
wss://api.tickdb.ai/v1/ws/market为占位 URI,TickDB 实际 WebSocket 端点格式请查阅官方文档- 期货 symbol 格式需以 TickDB 返回的品种列表为准,
ES.CUR等仅为示例 - 生产环境建议使用
asyncio全异步架构,配合独立的消息队列(如 Redis)做数据缓冲
四、订单簿深度分析:集合竞价的微观结构预测
4.1 集合竞价的本质是一个迷你拍卖市场
09:15-09:28 ET 的预开盘(Pre-opening)阶段,所有买卖指令被收集但不成交。撮合引擎以最大化成交量的价格作为开盘价。这个机制意味着:
- 价格优先:高于开盘价的买盘和低于开盘价的卖盘会在开盘瞬间被成交
- 数量均衡点:订单簿中买卖力量的相对分布决定最终撮合位置
- 流动性不对称:若买盘远大于卖盘,缺口(gap)向上概率极高
因此,盘前订单簿的买卖压力比(Bid-Ask Pressure Ratio)是预测开盘方向最直接的微观信号:
买卖压力比 = Σ(买盘前N档挂单量) / Σ(卖盘前N档挂单量)
- 压力比 > 1.5 → 买方力量主导,预计高开
- 压力比 < 0.67 → 卖方力量主导,预计低开
- 压力比在 0.67-1.5 → 多空僵持,开盘方向需等待前 5 分钟确认
4.2 盘前订单簿分析代码
import os
import time
import random
import requests
from collections import deque
from datetime import datetime
from typing import Optional
class PreMarketOrderBookAnalyzer:
"""
盘前订单簿分析器
使用 TickDB depth 频道实时分析盘前流动性,计算买卖压力比
预测次日开盘方向和集合竞价撮合区间
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {"X-API-Key": api_key}
self.base_url = "https://api.tickdb.ai/v1"
# 滑动窗口:保留最近 N 个快照用于趋势分析
self.window_size = 20
self.pressure_history = deque(maxlen=self.window_size)
def get_depth_snapshot(self, symbol: str, depth: int = 10) -> dict:
"""
获取订单簿深度快照
Args:
symbol: 交易品种,如 "AAPL.US"
depth: 档位数(美股 depth 频道通常为 1 档,港股/数字货币支持多档)
Returns:
含 bids/asks 的完整订单簿字典
"""
response = requests.get(
f"{self.base_url}/market/depth",
headers=self.headers,
params={"symbol": symbol, "depth": depth},
timeout=(3.05, 10)
)
data = response.json()
code = data.get("code", 0)
if code == 3001:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"[RATE] 请求频率超限,等待 {retry_after} 秒...")
time.sleep(retry_after)
return self.get_depth_snapshot(symbol, depth) # 重试
elif code != 0:
raise RuntimeError(f"API 错误 {code}: {data.get('message')}")
return data.get("data", {})
def calculate_pressure_ratio(self, depth_data: dict,
levels: int = 5) -> float:
"""
计算买卖压力比
Args:
depth_data: get_depth_snapshot 返回的订单簿数据
levels: 用于计算的档位数(前 N 档)
Returns:
买卖压力比(买盘量/卖盘量)
"""
bids = depth_data.get("bids", [])
asks = depth_data.get("asks", [])
bid_volume = sum(float(b[1]) for b in bids[:levels])
ask_volume = sum(float(a[1]) for a in asks[:levels])
if ask_volume == 0:
return float('inf') # 极端情况:无卖盘
return bid_volume / ask_volume
def estimate_auction_price(self, depth_data: dict) -> dict:
"""
估算集合竞价撮合价格区间
原理:找到买卖盘交叉价格区间,即最大成交量对应的价格
简化版本:使用盘口中间价 ± 价差作为参考区间
"""
bids = depth_data.get("bids", [])
asks = depth_data.get("asks", [])
if not bids or not asks:
return {"status": "no_data"}
best_bid = float(bids[0][0])
best_ask = float(asks[0][0])
mid_price = (best_bid + best_ask) / 2
spread = best_ask - best_bid
# 估算撮合区间(简化模型)
# 实际生产中需要遍历所有可能价格,计算每个价格下的成交量
auction_lower = mid_price - spread * 0.5
auction_upper = mid_price + spread * 0.5
return {
"best_bid": best_bid,
"best_ask": best_ask,
"mid_price": round(mid_price, 4),
"spread": round(spread, 4),
"spread_pct": round(spread / mid_price * 100, 4),
"auction_lower": round(auction_lower, 4),
"auction_upper": round(auction_upper, 4),
"estimated_gap": self._estimate_gap_risk(spread, mid_price),
}
def _estimate_gap_risk(self, spread: float, mid_price: float) -> str:
"""基于盘口价差估算开盘缺口风险"""
spread_pct = spread / mid_price * 100
if spread_pct > 2.0:
return "HIGH" # 价差超过 2%,可能出现显著缺口
elif spread_pct > 0.5:
return "MEDIUM"
else:
return "LOW"
def analyze_and_record(self, symbol: str) -> dict:
"""
执行单次分析并记录到历史窗口
"""
depth = self.get_depth_snapshot(symbol, depth=10)
pressure = self.calculate_pressure_ratio(depth)
auction = self.estimate_auction_price(depth)
record = {
"symbol": symbol,
"timestamp": datetime.utcnow().isoformat(),
"pressure_ratio": round(pressure, 3),
"auction": auction,
"direction": self._pressure_to_direction(pressure)
}
self.pressure_history.append(record)
return record
def _pressure_to_direction(self, pressure: float) -> str:
if pressure > 1.5:
return "bullish"
elif pressure < 0.67:
return "bearish"
else:
return "neutral"
def get_trend_summary(self, symbol: str) -> dict:
"""
基于滑动窗口趋势,给出方向信号摘要
"""
if not self.pressure_history:
return {"status": "no_history"}
recent = [r for r in self.pressure_history if r["symbol"] == symbol]
if len(recent) < 3:
return {"status": "insufficient", "samples": len(recent)}
ratios = [r["pressure_ratio"] for r in recent]
directions = [r["direction"] for r in recent]
import statistics
mean_ratio = statistics.mean(ratios)
# 趋势判断:最近 3 个样本的方向是否一致
consistent = len(set(directions)) == 1
latest = directions[-1]
return {
"symbol": symbol,
"samples": len(recent),
"mean_pressure_ratio": round(mean_ratio, 3),
"latest_direction": latest,
"is_consistent": consistent,
"signal": "STRONG" if (consistent and abs(mean_ratio - 1.0) > 0.5) else "WEAK",
"interpretation": self._interpret_signal(mean_ratio, consistent, latest)
}
def _interpret_signal(self, ratio: float, consistent: bool, direction: str) -> str:
if ratio > 1.5 and consistent:
return f"连续看多信号(压力比 {ratio:.2f}),预计高开,建议关注突破买入"
elif ratio < 0.67 and consistent:
return f"连续看空信号(压力比 {ratio:.2f}),预计低开,建议关注反弹卖出"
elif abs(ratio - 1.0) < 0.33:
return f"多空均衡(压力比 {ratio:.2f}),建议等待 09:30 后确认方向"
else:
return f"信号分歧,压力比 {ratio:.2f},建议轻仓观望"
def run_premarket_monitoring(self, symbols: list, interval: int = 60,
duration_minutes: int = 60):
"""
盘前定时监控循环
在 07:00-09:15 ET 期间持续采集数据,输出趋势报告
⚠️ 演示用:实际部署时应设置 cron 或 scheduler
"""
print(f"[INFO] 启动盘前监控,目标品种: {symbols}")
print(f"[INFO] 采集间隔 {interval}s,预计运行 {duration_minutes} 分钟\n")
start_time = time.time()
iterations = (duration_minutes * 60) // interval
for i in range(iterations):
elapsed = (time.time() - start_time) / 60
print(f"--- 第 {i + 1}/{iterations} 次采集 ({elapsed:.1f} 分钟) ---")
for symbol in symbols:
try:
record = self.analyze_and_record(symbol)
trend = self.get_trend_summary(symbol)
if trend.get("status") == "ok":
print(f" {symbol}: 压力比={record['pressure_ratio']:.2f}, "
f"方向={record['direction']}, "
f"信号强度={trend['signal']}")
except Exception as e:
print(f" [ERROR] {symbol}: {e}")
time.sleep(interval)
# ========== 使用示例 ==========
if __name__ == "__main__":
api_key = os.environ.get("TICKDB_API_KEY")
analyzer = PreMarketOrderBookAnalyzer(api_key=api_key)
# 盘前监控:关注 3 只关键品种,采集 30 分钟
symbols_to_watch = ["AAPL.US", "TSLA.US", "NVDA.US"]
# 方式一:单次快照分析
print("=== 盘前订单簿快照分析 ===")
for symbol in symbols_to_watch:
try:
record = analyzer.analyze_and_record(symbol)
print(f"\n{symbol}:")
print(f" 买卖压力比: {record['pressure_ratio']:.3f} ({record['direction']})")
print(f" 撮合区间: [{record['auction']['auction_lower']:.2f}, "
f"{record['auction']['auction_upper']:.2f}]")
print(f" 缺口风险: {record['auction']['estimated_gap']}")
except Exception as e:
print(f" [ERROR] {symbol}: {e}")
# 方式二:定时监控(演示:仅运行 2 次)
analyzer.run_premarket_monitoring(
symbols=symbols_to_watch,
interval=5, # ⚠️ 生产环境建议 60-300 秒,避免频率超限
duration_minutes=1
)
工程预警:
- 美股 depth 频道仅支持 1 档,港股/数字货币支持 10 档。此代码在美股品种上实际只能分析
bids[0]和asks[0],levels参数对美股品种无效 - 买卖压力比在单档数据下参考价值有限,建议将 trades 频道(港股/数字货币可用)配合 depth 一起分析,以获得多档完整的压力结构
run_premarket_monitoring中的interval=5仅为演示设置,TickDB 对 REST API 有频率限制(错误码 3001),生产环境应使用 WebSocket 实时推送并设置合理采集间隔
五、隔夜信号合成:预计算输出的完整框架
5.1 单一信号无效,信号矩阵才有效
上文拆解了四类信号源:事件扫描、期货夜盘、盘前流动性、集合竞价。但真正的盘前策略准备不是只看某一个信号,而是将多个信号合成为一个综合置信度评分。
from dataclasses import dataclass
from typing import Optional
import statistics
@dataclass
class CompositeSignal:
"""合成信号容器"""
symbol: str
event_score: float # 事件驱动评分 [-1, 1],正为利好
futures_score: float # 期货方向评分 [-1, 1]
orderbook_score: float # 盘前压力评分 [-1, 1]
confidence: float # 综合置信度 [0, 1]
@property
def weighted_signal(self) -> float:
"""
加权综合信号
权重分配逻辑:
- 事件扫描(明确事件):权重最高,因为定价逻辑清晰
- 期货夜盘(宏观方向):权重次之,作为市场情绪验证
- 盘前订单簿(微观结构):权重灵活,作为入场时机参考
"""
weights = {
"event": 0.45,
"futures": 0.30,
"orderbook": 0.25
}
return (
self.event_score * weights["event"] +
self.futures_score * weights["futures"] +
self.orderbook_score * weights["orderbook"]
)
@property
def action_recommendation(self) -> str:
"""行动建议(仅供系统内部决策参考,不构成投资建议)"""
ws = self.weighted_signal
conf = self.confidence
if conf < 0.3:
return "信号不足,保持观望"
elif ws > 0.4 and conf > 0.5:
return "多头信号,建议关注突破买入"
elif ws < -0.4 and conf > 0.5:
return "空头信号,建议关注反弹做空"
elif abs(ws) < 0.2:
return "多空均衡,趋势不明"
else:
return "信号存在但不明确,轻仓尝试"
class OvernightSignalSynthesizer:
"""
隔夜信号合成器
将事件扫描、期货监控、订单簿分析的结果综合为单一信号输出
输出可直接对接下单系统的触发条件
"""
def __init__(self, event_scanner, futures_monitor, orderbook_analyzer):
self.event_scanner = event_scanner
self.futures_monitor = futures_monitor
self.orderbook_analyzer = orderbook_analyzer
def _normalize_event_score(self, event_estimate: dict) -> float:
"""将事件隐含波动映射为 [-1, 1] 评分"""
move_pct = event_estimate.get("implied_move_pct", 0) / 100
# 简单映射:±5% 波动映射到 ±1
return max(-1.0, min(1.0, move_pct / 0.05))
def _normalize_futures_score(self, futures_bias: dict) -> float:
"""将期货偏离均值映射为 [-1, 1] 评分"""
move_pct = futures_bias.get("move_from_mean_pct", 0) / 100
return max(-1.0, min(1.0, move_pct / 0.01)) # ±1% 偏离映射到 ±1
def _normalize_orderbook_score(self, pressure_ratio: float) -> float:
"""将买卖压力比映射为 [-1, 1] 评分"""
# 压力比 1.0 → 0,>2 → 1,<0.5 → -1
if pressure_ratio >= 2.0:
return 1.0
elif pressure_ratio <= 0.5:
return -1.0
else:
# 线性映射:[0.5, 2.0] → [-1, 1]
return (pressure_ratio - 0.5) / 1.5 * 2 - 1
def synthesize(self, symbol: str) -> CompositeSignal:
"""合成单一标的的隔夜信号"""
# 1. 事件评分
event_data = self._fetch_event_data(symbol)
event_score = self._normalize_event_score(event_data)
# 2. 期货评分
futures_data = self.futures_monitor.calculate_premarket_bias("ES")
futures_score = self._normalize_futures_score(futures_data)
# 3. 盘前订单簿评分
orderbook_record = self.orderbook_analyzer.analyze_and_record(symbol)
pressure = orderbook_record.get("pressure_ratio", 1.0)
orderbook_score = self._normalize_orderbook_score(pressure)
# 4. 置信度评估:可用信号越多,置信度越高
signal_count = sum([
event_data.get("status") != "no_data",
futures_data.get("samples", 0) >= 5,
orderbook_record.get("pressure_ratio") is not None
])
confidence = signal_count / 3
return CompositeSignal(
symbol=symbol,
event_score=round(event_score, 3),
futures_score=round(futures_score, 3),
orderbook_score=round(orderbook_score, 3),
confidence=round(confidence, 2)
)
def generate_morning_brief(self, watchlist: list) -> str:
"""生成每日盘前简报(可对接飞书/Slack 推送)"""
lines = [
"\n" + "=" * 50,
f"📊 盘前信号简报 | {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}",
"=" * 50
]
signals = []
for symbol in watchlist:
try:
sig = self.synthesize(symbol)
signals.append(sig)
ws = sig.weighted_signal
emoji_map = {
"多头信号": "🟢",
"空头信号": "🔴",
"多空均衡": "⚪️",
"信号不足": "⚫"
}
emoji = emoji_map.get(sig.action_recommendation.split(",")[0], "⚪️")
lines.append(
f"\n{emoji} {symbol} "
f"[置信度 {sig.confidence:.0%}]"
)
lines.append(
f" 综合信号: {ws:+.3f} → {sig.action_recommendation}"
)
lines.append(
f" 分项: 事件 {sig.event_score:+.2f} | "
f"期货 {sig.futures_score:+.2f} | "
f"盘口 {sig.orderbook_score:+.2f}"
)
except Exception as e:
lines.append(f"\n⚠️ {symbol}: {e}")
lines.append("\n" + "=" * 50)
lines.append("⚠️ 本简报仅供系统决策参考,不构成任何投资建议。")
lines.append("=" * 50)
return "\n".join(lines)
def _fetch_event_data(self, symbol: str) -> dict:
"""获取事件数据(占位实现)"""
# 实际场景中调用 event_scanner
return {"status": "no_data", "implied_move_pct": 0}
5.2 置信度决定了仓位管理
有了合成信号,下一步是根据置信度决定仓位:
| 置信度 | 信号强度 | 建议仓位 |
|---|---|---|
| ≥ 0.7 | STRONG | 正常仓位(如 10% 基准) |
| 0.4-0.7 | MODERATE | 半仓 |
| < 0.4 | WEAK | 观望或仅做日内迷你仓 |
关键原则:置信度不足时,不交易比随便交易更好。盘前预计算的价值不仅在于"发现机会",更在于避免冲动交易。
六、完整方案:盘前准备清单与部署建议
6.1 从信号计算到执行的分层架构
┌─────────────────────────────────────────┐
│ Layer 4: 执行层 │
│ 预设条件单 + 触发阈值 + 仓位计算 │
├─────────────────────────────────────────┤
│ Layer 3: 信号合成层 │
│ CompositeSignal + 置信度 + 行动建议 │
├─────────────────────────────────────────┤
│ Layer 2: 数据采集层 │
│ 事件扫描 + 期货监控 + 订单簿分析 │
├─────────────────────────────────────────┤
│ Layer 1: 数据源 │
│ TickDB (期货深度/港股/数字货币) │
│ 第三方 (美股新闻/财报/期权) │
└─────────────────────────────────────────┘
6.2 分场景部署方案
| 场景 | 推荐配置 | TickDB 能力覆盖 |
|---|---|---|
| 个人量化爱好者 | REST API + WebSocket,Python 单进程 | 港股/数字货币 depth + 期货深度 |
| 小型团队(3-5 人) | WebSocket + Redis 队列 + 飞书告警 | 同上,扩展多账号并发 |
| 机构级 | 多路数据源(TickDB + Bloomberg + 自研) | 期货 + 数字货币主力合约 |
6.3 盘前 30 分钟行动清单
以下清单在 T日 09:00-09:30 ET 执行:
□ 09:00 运行 OvernightSignalSynthesizer,生成 watchlist 简报
□ 09:05 对比盘前订单簿快照与昨日收盘价,确认缺口方向
□ 09:10 检查宏观数据日历(今日有无 CPI / Fed 讲话 / 财报)
□ 09:15 集合竞价开始,盯住盘口压力比变化
□ 09:20 若压力比趋势与昨日预判一致 → 准备入场
□ 09:25 设置止损单,确认流动性充足(Bid/Ask 档位均有挂单)
□ 09:28 最终确认:成交量是否配合?避免低流动性时段追单
□ 09:30 开盘,执行预设计划,无论结果如何记录日志
□ 09:45 盘后复盘:预判 vs 实际偏差,更新信号权重模型
七、技术总结:盘前准备的本质是"不对称信息优势"
复盘全文的核心逻辑:
盘前预计算解决的不是"明天涨还是跌"的问题,而是:
- 信号冗余:单一信号(期货 or 盘前盘口)不足以决策,多信号交叉验证才能提升胜率
- 时间压缩:把需要 30 分钟分析的工作在盘前 15 分钟内完成,留足执行窗口
- 情绪隔离:预设计算在盘后完成,不受盘中情绪干扰;开盘时只需"确认 vs 预判一致"或"放弃"
工程上,本文给出了三个生产级模块:事件扫描器、期货信号监控、订单簿分析器。每个模块均可独立运行,也可通过 OvernightSignalSynthesizer 组合为完整的盘前信号流水线。
下一步行动
如果你是个人量化开发者,想亲手实现本文的盘前监控系统:
- 访问 tickdb.ai 注册(免费,无需信用卡)
- 在控制台生成 API Key
- 设置环境变量
TICKDB_API_KEY,参考本文代码即可运行
如果你的策略需要港股或数字货币的盘前深度数据,TickDB 的 depth 频道在盘前阶段(04:00-09:15 ET)有实时推送,结合本文的买卖压力比算法可提前预判开盘方向。
如果你习惯用 AI 辅助开发,在 ClawHub 安装 tickdb-market-data SKILL,可以直接用自然语言查询盘前流动性数据并生成监控代码。
如果你需要机构级的历史数据回测支持(包括港股/数字货币的历史 depth 数据用于策略验证),联系 [email protected] 了解专业版方案。
风险提示:本文不构成任何投资建议。量化策略存在模型风险、历史数据不代表未来收益。盘前信号基于有限信息和简化模型,实际开盘结果受多种不可预测因素影响。使用本文代码前请充分测试,注意数据源限制和 API 频率约束。市场有风险,投资需谨慎。