Files
stock/backend/scripts/test_trades_parser.py
2026-08-16 00:05:26 +08:00

114 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""交割单解析器离线自测:不碰数据库,直接调 app.trades.parse_statement。
覆盖四类真实导出格式 + 边界行(转账/配号/利息跳过、费用合计列去重、日期多格式)。
运行uv run python scripts/test_trades_parser.py
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from app.trades import parse_statement # noqa: E402
FAIL: list[str] = []
def check(name: str, cond: bool, detail: str = "") -> None:
mark = "ok " if cond else "FAIL"
print(f"[{mark}] {name}{('' + detail) if detail and not cond else ''}")
if not cond:
FAIL.append(name)
# ---------- 1) 通达信式GBK + 制表符 + 标题行在前 ----------
tdx = (
"交割单\n"
"股东账号: A123456789 起始日期: 20240102 终止日期: 20240105 币种: 人民币\n"
"\t交割日期\t业务名称\t证券代码\t证券名称\t成交价格\t成交数量\t成交金额\t手续费\t印花税\t过户费\t发生金额\t资金余额\t合同号\n"
"\t20240102\t证券买入\t600519\t贵州茅台\t1680.00\t100\t168000.00\t5.00\t0.00\t1.68\t-168006.68\t200000.00\t1000001\n"
"\t20240102\t银行转存\t\t\t\t\t\t\t\t\t50000.00\t250000.00\t\n"
"\t20240103\t证券卖出\t600519\t贵州茅台\t1700.50\t100\t170050.00\t5.00\t170.05\t1.70\t169873.25\t419873.25\t1000002\n"
"\t20240105\t利息归本\t\t\t\t\t\t\t\t\t1.25\t419874.50\t\n"
)
r = parse_statement(tdx.encode("gbk"), "交割单.txt")
check("tdx: 2 笔成交", len(r.trades) == 2, f"got {len(r.trades)}")
check("tdx: 跳过 2 行非交易", r.skipped_other == 2, f"got {r.skipped_other}")
t0, t1 = r.trades[0], r.trades[1]
check("tdx: 日期/代码/后缀", (t0.trade_date.isoformat(), t0.ts_code) == ("2024-01-02", "600519.SH"), f"{t0.trade_date} {t0.ts_code}")
check("tdx: 买入方向+费用合计", t0.direction == "buy" and abs(t0.fee - 6.68) < 1e-9, f"{t0.direction} fee={t0.fee}")
check("tdx: 卖出费用含印花税", t1.direction == "sell" and abs(t1.fee - 176.75) < 1e-9, f"fee={t1.fee}")
check("tdx: 金额取绝对值", t0.amount == 168000.0, f"amount={t0.amount}")
# ---------- 2) 恒生柜台式UTF-8 CSV交收日期/交易类别/费用合计 ----------
hs = (
"序号,交收日期,证券代码,证券名称,交易类别,成交价格,成交数量,证券余额,成交金额,资金发生数,资金余额,流水序号,业务标志,业务名称,发生金额,后资金额,货币类别,费用合计,净佣金,规费,印花税,过户费,合同号\n"
"1,2024-06-07,000858,五粮液,证券买入,132.50,200,200,26500.00,-26505.80,73494.20,1,0101,证券买入,-26505.80,73494.20,人民币,5.80,4.20,1.60,0.00,0.00,66778001\n"
"2,2024-06-07,,,\t,,,,5120.00,78614.20,2,2041,银行转存,5120.00,78614.20,人民币,0,0,0,0,0,\n"
"3,2024-06-10,000858,五粮液,证券卖出,135.00,200,0,27000.00,26975.30,105589.50,3,0102,证券卖出,26975.30,105589.50,人民币,24.70,4.20,1.60,18.90,0.00,66779001\n"
)
r2 = parse_statement(hs.encode("utf-8"), "hsi.csv")
check("hs: 2 笔成交", len(r2.trades) == 2, f"got {len(r2.trades)}")
check("hs: 费用合计不重复累加", abs(r2.trades[1].fee - 24.70) < 1e-9, f"fee={r2.trades[1].fee}")
check("hs: 深市后缀", r2.trades[0].ts_code == "000858.SZ", r2.trades[0].ts_code)
check("hs: 日期 YYYY-MM-DD", r2.trades[0].trade_date.isoformat() == "2024-06-07")
# ---------- 3) HTML 伪 .xls同花顺导出常见真身 ----------
html = """<html><head><meta charset="gbk"></head><body>
<table>
<tr><td>客户姓名</td><td>测试</td></tr>
<tr><td>成交日期</td><td>业务名称</td><td>证券代码</td><td>证券名称</td><td>成交价格</td><td>成交数量</td><td>成交金额</td><td>手续费</td></tr>
<tr><td>2024/03/15</td><td>证券买入</td><td>300750</td><td>宁德时代</td><td>182.30</td><td>300</td><td>54,690.00</td><td>16.41</td></tr>
<tr><td>2024/03/18</td><td>证券卖出</td><td>300750</td><td>宁德时代</td><td>185.00</td><td>300</td><td>55,500.00</td><td>5.55</td></tr>
</table></body></html>"""
r3 = parse_statement(html.encode("gbk"), "jiaogedan.xls")
check("html: 2 笔成交", len(r3.trades) == 2, f"got {len(r3.trades)}")
check("html: 千分位金额", r3.trades[0].amount == 54690.0, f"{r3.trades[0].amount}")
check("html: 创业板后缀", r3.trades[0].ts_code == "300750.SZ", r3.trades[0].ts_code)
check("html: 斜杠日期", r3.trades[1].trade_date.isoformat() == "2024-03-18")
# ---------- 4) 无业务名称列:发生金额正负判方向(招商式) ----------
zh = (
"证券名称,成交日期,成交价格,成交数量,发生金额,资金余额,合同编号\n"
"贵州茅台,20240102,1680.00,100,-168005.00,200000.00,SZ1000001\n"
"贵州茅台,20240103,1700.50,100,170049.50,370049.50,SZ1000002\n"
)
r4 = parse_statement(zh.encode("utf-8"), "zszs.csv")
check("sign: 2 笔成交", len(r4.trades) == 2, f"got {len(r4.trades)}")
check("sign: 负金额=买入", (r4.trades[0].direction, r4.trades[1].direction) == ("buy", "sell"),
f"{r4.trades[0].direction}/{r4.trades[1].direction}")
# ---------- 5) xlsxopenpyxl 内存构造) ----------
import io # noqa: E402
from openpyxl import Workbook # noqa: E402
wb = Workbook()
ws = wb.active
ws.append(["对账单", None, None])
ws.append(["成交日期", "业务名称", "证券代码", "证券名称", "成交均价", "成交股数", "成交金额", "佣金", "过户费"])
from datetime import datetime as dt # noqa: E402
ws.append([dt(2024, 2, 28, 14, 35, 0), "证券买入", "688981", "中芯国际", 52.80, 200, 10560.00, 2.50, 1.06])
ws.append([dt(2024, 3, 1, 9, 31, 0), "证券卖出", "688981", "中芯国际", 54.10, 200, 10820.00, 2.50, 1.06])
buf = io.BytesIO()
wb.save(buf)
r5 = parse_statement(buf.getvalue(), "sm.xlsx")
check("xlsx: 2 笔成交", len(r5.trades) == 2, f"got {len(r5.trades)}")
check("xlsx: datetime 日期", r5.trades[0].trade_date.isoformat() == "2024-02-28")
check("xlsx: 科创板后缀", r5.trades[0].ts_code == "688981.SH", r5.trades[0].ts_code)
check("xlsx: 佣金+过户费", abs(r5.trades[0].fee - 3.56) < 1e-9, f"fee={r5.trades[0].fee}")
# ---------- 6) 错误分支 ----------
from fastapi import HTTPException # noqa: E402
try:
parse_statement("随便一串不是交割单的文字,1,2,3".encode("utf-8"), "x.csv")
check("garbage: 应 422", False)
except HTTPException as e:
check("garbage: 422", e.status_code == 422)
print()
if FAIL:
print(f"FAIL {len(FAIL)}: {FAIL}")
sys.exit(1)
print("PASS: 交割单解析器全部用例通过")