102 lines
5.4 KiB
Python
102 lines
5.4 KiB
Python
"""交割单解析器:四类真实导出格式 + 边界(转账/配号/利息跳过、费用合计去重、日期多格式)。
|
||
|
||
移植自 scripts/test_trades_parser.py(已删),不碰数据库,直接调 app.trades.parse_statement。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import io
|
||
from datetime import datetime as dt
|
||
|
||
import pytest
|
||
from fastapi import HTTPException
|
||
from openpyxl import Workbook
|
||
|
||
from app.trades import parse_statement
|
||
|
||
|
||
def test_tdx_gbk_tabs():
|
||
"""通达信式: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")
|
||
assert len(r.trades) == 2
|
||
assert r.skipped_other == 2
|
||
t0, t1 = r.trades[0], r.trades[1]
|
||
assert (t0.trade_date.isoformat(), t0.ts_code) == ("2024-01-02", "600519.SH")
|
||
assert t0.direction == "buy" and abs(t0.fee - 6.68) < 1e-9
|
||
assert t1.direction == "sell" and abs(t1.fee - 176.75) < 1e-9
|
||
assert t0.amount == 168000.0
|
||
|
||
|
||
def test_hengsheng_csv_fee_total():
|
||
"""恒生柜台式: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"
|
||
)
|
||
r = parse_statement(hs.encode("utf-8"), "hsi.csv")
|
||
assert len(r.trades) == 2
|
||
assert abs(r.trades[1].fee - 24.70) < 1e-9
|
||
assert r.trades[0].ts_code == "000858.SZ"
|
||
assert r.trades[0].trade_date.isoformat() == "2024-06-07"
|
||
|
||
|
||
def test_html_pseudo_xls():
|
||
"""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>"""
|
||
r = parse_statement(html.encode("gbk"), "jiaogedan.xls")
|
||
assert len(r.trades) == 2
|
||
assert r.trades[0].amount == 54690.0
|
||
assert r.trades[0].ts_code == "300750.SZ"
|
||
assert r.trades[1].trade_date.isoformat() == "2024-03-18"
|
||
|
||
|
||
def test_amount_sign_direction():
|
||
"""无业务名称列(招商式):发生金额正负判方向。"""
|
||
zh = (
|
||
"证券名称,成交日期,成交价格,成交数量,发生金额,资金余额,合同编号\n"
|
||
"贵州茅台,20240102,1680.00,100,-168005.00,200000.00,SZ1000001\n"
|
||
"贵州茅台,20240103,1700.50,100,170049.50,370049.50,SZ1000002\n"
|
||
)
|
||
r = parse_statement(zh.encode("utf-8"), "zszs.csv")
|
||
assert len(r.trades) == 2
|
||
assert (r.trades[0].direction, r.trades[1].direction) == ("buy", "sell")
|
||
|
||
|
||
def test_xlsx_openpyxl():
|
||
"""xlsx(openpyxl 内存构造):datetime 日期、科创板后缀、佣金+过户费合计。"""
|
||
wb = Workbook()
|
||
ws = wb.active
|
||
ws.append(["对账单", None, None])
|
||
ws.append(["成交日期", "业务名称", "证券代码", "证券名称", "成交均价", "成交股数", "成交金额", "佣金", "过户费"])
|
||
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)
|
||
r = parse_statement(buf.getvalue(), "sm.xlsx")
|
||
assert len(r.trades) == 2
|
||
assert r.trades[0].trade_date.isoformat() == "2024-02-28"
|
||
assert r.trades[0].ts_code == "688981.SH"
|
||
assert abs(r.trades[0].fee - 3.56) < 1e-9
|
||
|
||
|
||
def test_garbage_input_422():
|
||
with pytest.raises(HTTPException) as ei:
|
||
parse_statement("随便一串不是交割单的文字,1,2,3".encode("utf-8"), "x.csv")
|
||
assert ei.value.status_code == 422
|