mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 18:04:20 +08:00
- 新增 easy_tdx.ccpm 模块(独立数据源,标准库 urllib 零依赖):
官网 /sj/ccpm/{YYYYMM}/{DD}/{品种}.xml,交易日约 16:15 发布,
单文件含全部合约 × 三类排名(datatypeid 0=成交量/1=持买单/2=持卖单)× 前 20 名会员
- 协议要点:?id= 为 0~99 随机防缓存参数可省略;非交易日 302→error_404,
禁用 urllib 自动重定向并翻译为 CcpmNoDataError(区别于网络错误 CcpmError);
仅 http 可用;历史可回溯至 2012 年
- 8 品种:IF/IH/IC/IM 股指 + TS/TF/T/TL 国债;latest_rank() 自动回溯最近交易日;
按日不可变 → ~/.easy_tdx/cache/ccpm/ 落盘缓存,历史二次查询零网络
- CLI:easy-tdx ccpm IF [--date] [--table] [--refresh] [--no-cache],all=全品种
- API:GET /ccpm/products(品种科普元数据)+ GET /ccpm/rank?product&date
(404=非交易日/未发布,refresh 强制重抓)
- WebUI「期货持仓排名」页(行情组):品种下拉+日期+自动回溯开关+一键采集,
合约页签自动标注主力,前 20 合计多/空/净持仓概览,三组排名并排表格
- 三段新手科普:「这是什么数据」「品种一览」「多单空单加减仓怎么看」
(强调排名看不出套保还是投机,空单多 ≠ 看空市场)
- 测试 20 例(mock HTTP 零网络):解析/缓存/302 语义/回溯/路由/CLI
99 lines
2.9 KiB
Python
99 lines
2.9 KiB
Python
"""中金所成交持仓排名命令(独立数据源,无需 TDX 服务器)。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import click
|
|
|
|
if TYPE_CHECKING:
|
|
import pandas as pd
|
|
|
|
#: 表格模式下的中文列名(JSON/CSV 保持英文机器友好列名)
|
|
_COLUMN_LABELS = {
|
|
"trading_day": "交易日",
|
|
"product": "品种",
|
|
"instrument": "合约",
|
|
"rank": "排名",
|
|
"vol_member": "成交量·会员",
|
|
"vol": "成交量(手)",
|
|
"vol_chg": "增减",
|
|
"long_member": "持买单·会员",
|
|
"long_pos": "持买单量(手)",
|
|
"long_chg": "增减2",
|
|
"short_member": "持卖单·会员",
|
|
"short_pos": "持卖单量(手)",
|
|
"short_chg": "增减3",
|
|
}
|
|
|
|
|
|
@click.command("ccpm")
|
|
@click.argument("product", default="IF")
|
|
@click.option(
|
|
"--date",
|
|
"trade_date",
|
|
default=None,
|
|
help="交易日 YYYY-MM-DD(缺省自动回溯到最近有数据的交易日)",
|
|
)
|
|
@click.option("--table", "use_table", is_flag=True, help="表格输出")
|
|
@click.option("--output", "output_fmt", type=click.Choice(["json", "table", "csv"]), default="json")
|
|
@click.option("--refresh", is_flag=True, help="忽略本地缓存,强制重新抓取")
|
|
@click.option("--no-cache", is_flag=True, help="本次不读也不写本地缓存")
|
|
def ccpm(
|
|
product: str,
|
|
trade_date: str | None,
|
|
use_table: bool,
|
|
output_fmt: str,
|
|
refresh: bool,
|
|
no_cache: bool,
|
|
) -> None:
|
|
"""获取中金所成交持仓排名(官网每日收盘后约 16:15 发布,前 20 名会员)。
|
|
|
|
\b
|
|
品种代码:
|
|
IF 沪深300 IH 上证50 IC 中证500 IM 中证1000
|
|
TS 2年国债 TF 5年国债 T 10年国债 TL 30年国债
|
|
all = 一次抓取全部 8 个品种
|
|
|
|
\b
|
|
示例:
|
|
|
|
easy-tdx ccpm IF --table
|
|
|
|
easy-tdx ccpm IF --date 2026-09-02
|
|
|
|
easy-tdx ccpm all --date 2026-08-28 --table
|
|
"""
|
|
from ..ccpm import PRODUCT_CODES, CcpmClient, CcpmError
|
|
from .output import print_error, print_output
|
|
|
|
products = PRODUCT_CODES if product.strip().lower() == "all" else [product.strip().upper()]
|
|
client = CcpmClient(use_cache=not no_cache)
|
|
frames = []
|
|
try:
|
|
for p in products:
|
|
if trade_date:
|
|
frames.append(client.get_rank(p, trade_date, refresh=refresh))
|
|
else:
|
|
frames.append(client.latest_rank(p, refresh=refresh))
|
|
except (CcpmError, ValueError) as e:
|
|
print_error(str(e))
|
|
raise SystemExit(1) from e
|
|
|
|
import pandas as pd
|
|
|
|
df = pd.concat(frames, ignore_index=True) if len(frames) > 1 else frames[0]
|
|
|
|
fmt = "table" if use_table else output_fmt
|
|
if fmt == "table":
|
|
click.echo(_render_table(df))
|
|
else:
|
|
print_output(df, fmt)
|
|
|
|
|
|
def _render_table(df: pd.DataFrame) -> str:
|
|
"""中文表头 + 不截断会员名的表格渲染。"""
|
|
from .output import _render_table_full
|
|
|
|
return _render_table_full(df.rename(columns=_COLUMN_LABELS))
|