Files
group_xinghuo_jinrong/scripts/dev/calc_convert_demo.py
T
GaoYiYuan_0626 ea9a132a40 docs: 基金转换(convert)设计阶段成果入库
此前 AIcoding 第 1~3 步的产出仅存在于本地磁盘、未纳入版本库,本次一次性入库,
补齐「设计可追溯」。本提交为纯文档 + 一个实算脚本,**代码零改动**,接口契约与表结构不变。

入库 6 个文件:

- docs/PRD/PRD-基金转换交易.md(v0.9 · 需求权威:FR-C1~C16 / §4 表结构 / §5 接口 / §9 验收)
- docs/项目框架设计/架构设计-基金转换交易.md(v1.0 · 实现依据:D1~D20 / §5 事务补偿 /
  §11.1 DB 账号分离 / §15 任务映射 T-0~T-13 与 §15.1 依赖拓扑)
- docs/项目框架设计/基金转换-审查意见处置表.md(33 条外审处置 + §九 用户拍板记录)
- docs/项目框架设计/评审待办-风控主架构与基金转换.md(评审逐条判定 + 核实证据)
- docs/交接文档-基金转换.md(本线交接入口,读完即可开工)
- scripts/dev/calc_convert_demo.py(PRD §5.3 主示例实算回填脚本——派生值禁手算)

门控:PRD 已定稿并经独立评审闭环(M-7 已满足);架构 v1.0 独立评审 13 条建议 0 悬空。
下一步:AIcoding 第 4 步「产出开发计划」,输入为架构 §15 / §15.1。
2026-09-10 11:29:55 +08:00

99 lines
4.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.
# -*- coding: utf-8 -*-
"""基金转换主示例实算(PRD §5.3 数值来源)。
用途:PRD §5.3 响应示例中的**所有派生金额/份额**,必须由本脚本算出后回填,
**禁止手算**——v0.7 曾两次因手算与口径不符被外部审查质疑(见处置表 T-2b / 自检第 9 问)。
公式与 PRD §2.1 / §2.1.1 / §2.1.3 / §2.5 完全一致:
转出金额 = Σ(各批次份额 × 转出净值)
赎回费 = Σ(各批次金额 × 该批适用赎回费率) # 逐批次先舍入、后求和
转换金额 = 转出金额 − 赎回费
补差费(B) = Max[ 转换金额×转入费率/(1+转入费率)
− 转换金额×转出费率/(1+转出费率), 0 ] # 价外法两端差(本期默认)
补差费(A) = 转换金额 × max(转入费率−转出费率,0) / (1+补差费率) # 费率差法(配置保留)
转入金额 = 转换金额 − 补差费
转入份额 = 转入金额 ÷ 转入净值 # 2 位,ROUND_HALF_UP
运行:<托管 Python> scripts/dev/calc_convert_demo.py
"""
from decimal import Decimal, ROUND_HALF_UP
TWO = Decimal("0.01")
FOUR = Decimal("0.0001")
def q2(x: Decimal) -> Decimal:
"""金额/份额统一保留 2 位、四舍五入(显式 ROUND_HALF_UP,禁用默认 HALF_EVEN)。"""
return x.quantize(TWO, rounding=ROUND_HALF_UP)
def main() -> None:
# —— 主示例输入(与 §5.3 / §4.3 种子一致)——
lots = [
# (份额, 持有天数, 该批赎回费率) —— 费率来自 §2.1.3 分档表
(Decimal("30000"), 100, Decimal("0.0050")), # [30,180) 日 → 0.5%
(Decimal("20000"), 3, Decimal("0.0150")), # [0,7) 日 → 1.5%
]
nav_out = Decimal("1.03") # PROD-110022 债基 转出净值
nav_in = Decimal("0.95") # PROD-005827 主动偏股 转入净值
fee_out = Decimal("0.0030") # 转出端申购费率(债基,22 号文 §8 上限 0.3%)
fee_in = Decimal("0.0080") # 转入端申购费率(主动偏股,上限 0.8%)
out_amount = Decimal("0")
redeem_fee = Decimal("0")
detail = []
for qty, days, rate in lots:
amt = q2(qty * nav_out)
fee = q2(amt * rate)
out_amount += amt
redeem_fee += fee
detail.append((qty, days, rate, amt, fee))
convert_amount = q2(out_amount - redeem_fee)
# —— 口径 B:价外法两端差(本期默认)——
sub_in = convert_amount * fee_in / (Decimal("1") + fee_in)
sub_out = convert_amount * fee_out / (Decimal("1") + fee_out)
diff_b = q2(max(sub_in - sub_out, Decimal("0")))
in_amount = q2(convert_amount - diff_b)
theoretical = in_amount / nav_in
in_qty = q2(theoretical)
rounding_diff = theoretical - in_qty
# —— 口径 A:费率差法(配置 convert_diff_fee_mode='rate_diff' 保留)——
rate_diff = max(fee_in - fee_out, Decimal("0"))
diff_a = q2(convert_amount * rate_diff / (Decimal("1") + rate_diff))
in_qty_a = q2(q2(convert_amount - diff_a) / nav_in)
# —— 同费率对照(补差 = 0),用于 §9 第 19 条 ——
same_rate = Decimal("0.0030")
diff_same = q2(max(convert_amount * same_rate / (Decimal("1") + same_rate)
- convert_amount * same_rate / (Decimal("1") + same_rate),
Decimal("0")))
in_amount_same = q2(convert_amount - diff_same)
in_qty_same = q2(in_amount_same / nav_in)
print("=== 主示例(口径 B)===")
for qty, days, rate, amt, fee in detail:
print(f"批次 {qty} 份 / {days} 天 / 费率 {rate} -> amount={amt} fee={fee}")
print(f"out_amount = {out_amount}")
print(f"redeem_fee = {redeem_fee}")
print(f"convert_amount = {convert_amount}")
print(f" (转入端申购费) = {sub_in.quantize(FOUR)} -> q2 {q2(sub_in)}")
print(f" (转出端申购费) = {sub_out.quantize(FOUR)} -> q2 {q2(sub_out)}")
print(f"diff_fee(B) = {diff_b}")
print(f"in_amount = {in_amount}")
print(f"in_qty = {in_qty}")
print(f"rounding_diff = {rounding_diff.quantize(FOUR)} (理论 {theoretical.quantize(FOUR)} - 实际 {in_qty})")
print()
print("=== 对照:口径 A(费率差法)===")
print(f"diff_fee(A) = {diff_a}")
print(f"in_qty(A) = {in_qty_a}")
print()
print("=== 同费率对照(两端 0.0030,补差 = 0)===")
print(f"diff_fee={diff_same} in_amount={in_amount_same} in_qty={in_qty_same}")
if __name__ == "__main__":
main()