python案例能否识别盘口异常变动?

wen python案例 1

Python 识别盘口异常变动案例

可以,Python 非常适合做盘口(Level-2 行情)异常监控,下面给你一套可运行的思路 + 代码框架

python案例能否识别盘口异常变动?


什么是"盘口异常"

常见的异常类型:

类型 特征
大单挂撤 买卖档位出现远超均值的挂单,然后快速撤掉
挂单失衡 买一~买五 vs 卖一~卖五 量级严重不平衡
频繁撤单 单位时间内撤单次数异常
吃单加速 主动性成交突然放大(大单扫货/砸盘)
冰山单 某价位持续成交但挂单量几乎不变
价差异常 spread 突然放大/收窄

数据源准备

常见渠道:

  • A股:QMT、掘金、聚宽、Tushare Pro、Level-2 行情(付费)
  • 期货:CTP、vnpy
  • 加密:Binance/OKX WebSocket(免费,最适合练手)

下面示例用 JSON 快照格式,通用:

snapshot = {
    "symbol": "BTCUSDT",
    "ts": 1730000000.123,
    "bids": [[100.0, 5.2], [99.9, 3.1], ...],   # [价, 量]
    "asks": [[100.1, 4.8], [100.2, 2.0], ...],
}

完整案例代码

import time
import statistics
from collections import deque, defaultdict
class OrderBookMonitor:
    def __init__(self, window=60, depth=5):
        self.window = window          # 滚动窗口(秒)
        self.depth = depth
        self.history = deque()        # 保存快照历史
        self.bid_vol_hist = deque(maxlen=100)
        self.ask_vol_hist = deque(maxlen=100)
        self.cancel_count = defaultdict(int)
        self.last_book = None
    # ---------- 工具 ----------
    @staticmethod
    def _sum_vol(levels, n):
        return sum(v for _, v in levels[:n])
    def _imbalance(self, bids, asks):
        bv = self._sum_vol(bids, self.depth)
        av = self._sum_vol(asks, self.depth)
        if bv + av == 0:
            return 0
        return (bv - av) / (bv + av)   # [-1, 1]
    # ---------- 核心检测 ----------
    def on_snapshot(self, snap):
        bids, asks = snap["bids"], snap["asks"]
        now = snap["ts"]
        # 1. 买卖失衡
        imb = self._imbalance(bids, asks)
        if abs(imb) > 0.6:
            self._alert(now, "IMBALANCE", f"imbalance={imb:.2f}")
        # 2. 大单挂单(相对历史均值)
        b1 = bids[0][1] if bids else 0
        a1 = asks[0][1] if asks else 0
        if len(self.bid_vol_hist) > 20:
            b_mean = statistics.mean(self.bid_vol_hist)
            a_mean = statistics.mean(self.ask_vol_hist)
            if b_mean > 0 and b1 > b_mean * 5:
                self._alert(now, "BIG_BID", f"b1={b1:.2f} mean={b_mean:.2f}")
            if a_mean > 0 and a1 > a_mean * 5:
                self._alert(now, "BIG_ASK", f"a1={a1:.2f} mean={a_mean:.2f}")
        # 3. 撤单检测(对比上一快照)
        if self.last_book:
            prev_bids = dict(self.last_book["bids"][:self.depth])
            for p, v in bids[:self.depth]:
                prev_v = prev_bids.get(p, 0)
                if prev_v > 0 and v < prev_v * 0.3:   # 挂单被撤 70%+
                    self._alert(now, "CANCEL_BID",
                                f"price={p} {prev_v}->{v}")
        # 4. 价差异常
        if bids and asks:
            spread = asks[0][0] - bids[0][0]
            mid = (asks[0][0] + bids[0][0]) / 2
            rel = spread / mid if mid else 0
            if rel > 0.005:   # 0.5% 价差(阈值按品种调)
                self._alert(now, "WIDE_SPREAD", f"spread={rel:.4%}")
        # 5. 更新历史
        self.bid_vol_hist.append(b1)
        self.ask_vol_hist.append(a1)
        self.last_book = snap
    def _alert(self, ts, kind, msg):
        print(f"[{ts:.3f}] 🚨 {kind}: {msg}")
# ---------- 模拟数据 ----------
if __name__ == "__main__":
    mon = OrderBookMonitor()
    # 正常盘口
    for i in range(30):
        mon.on_snapshot({
            "ts": time.time(),
            "bids": [[100.0 - j*0.01, 2.0] for j in range(5)],
            "asks": [[100.01 + j*0.01, 2.0] for j in range(5)],
        })
    # 突然出现大买单 + 失衡
    mon.on_snapshot({
        "ts": time.time(),
        "bids": [[100.0, 50.0]] + [[99.99 - j*0.01, 2.0] for j in range(4)],
        "asks": [[100.01 + j*0.01, 2.0] for j in range(5)],
    })
    # 立刻撤掉
    mon.on_snapshot({
        "ts": time.time(),
        "bids": [[100.0, 0.5]] + [[99.99 - j*0.01, 2.0] for j in range(4)],
        "asks": [[100.01 + j*0.01, 2.0] for j in range(5)],
    })

输出示例:

[1730000000.123] 🚨 BIG_BID: b1=50.00 mean=2.00
[1730000000.123] 🚨 IMBALANCE: imbalance=0.67
[1730000010.456] 🚨 CANCEL_BID: price=100.0 50.0->0.5

进阶方向

接真实数据(示例:Binance WebSocket)

import asyncio, json, websockets
async def run():
    url = "wss://stream.binance.com:9443/ws/btcusdt@depth20@100ms"
    mon = OrderBookMonitor()
    async with websockets.connect(url) as ws:
        while True:
            data = json.loads(await ws.recv())
            snap = {
                "ts": time.time() * 1000,
                "bids": [[float(p), float(q)] for p, q in data["bids"]],
                "asks": [[float(p), float(q)] for p, q in data["asks"]],
            }
            mon.on_snapshot(snap)
asyncio.run(run())

用统计模型替代硬阈值

  • Z-Score 检测z = (x - μ) / σ|z| > 3 视为异常
  • EWMA 平滑:对盘口量做指数加权平均,检测偏离
  • Isolation Forest / LOF:多维度(失衡、撤单率、价差)联合打分
  • 滑窗分位数:>P99 或 <P1 报警
import numpy as np
def zscore_alert(series, x, thresh=3):
    if len(series) < 30: return False
    mu, sd = np.mean(series), np.std(series)
    if sd == 0: return False
    return abs((x - mu) / sd) > thresh

工程化建议

  • 异步 + 环形缓冲:用 asyncio + deque(maxlen=N) 保证低延迟
  • 去重:同一异常 500ms 内只报一次,否则会刷屏
  • 落库:异常写入 InfluxDB / ClickHouse,方便回测复核
  • 回测:录一天 Level-2 tick,离线调参

注意点

  1. A股 Level-2 有"逐笔委托"和"逐笔成交"两个流,撤单信息在逐笔委托里(type='C'),比只盯快照准得多。
  2. 不同品种阈值差异大:BTC 和螺纹钢的"大单"完全不是一个量级,要按品种标准化。
  3. 闪单(fleeting order):挂单存活 <100ms 的,需要用逐笔数据才能抓到,快照流会漏。
  4. 合规:A股程序化交易有报备要求,实盘接入前确认券商的限制。

需要我帮你针对某个具体市场(A股 Level-2 / 期货 CTP / 币安)写一个完整可跑的版本吗?告诉我数据源和想要检测的异常类型即可。

抱歉,评论功能暂时关闭!