Prechádzať zdrojové kódy

refactor#三线比例公式说明换成中文

hefuzheng 1 týždeň pred
rodič
commit
8157385209

BIN
code/post_clear_check/src/post_clear_check/__pycache__/chain.cpython-312.pyc


BIN
code/post_clear_check/src/post_clear_check/__pycache__/order_rules.cpython-312.pyc


+ 6 - 6
code/post_clear_check/src/post_clear_check/chain.py

@@ -306,9 +306,9 @@ GATE_MISSING_TABLE = "业务表应存在"
 _HEDGE_ROOT = _design(_CLEARING_PLAN, "5.4 TRS 核对、订单和交收计算")
 
 _BUY_FORMULAS: Mapping[str, str] = {
-    "distance_warning": _design(_CHAIN_DESIGN, "4.2 买入执行"),
-    "distance_danger": _design(_CHAIN_DESIGN, "4.2 买入执行"),
-    "distance_force": _design(_CHAIN_DESIGN, "4.2 买入执行"),
+    "距预警比例": _design(_CHAIN_DESIGN, "4.2 买入执行"),
+    "距危险比例": _design(_CHAIN_DESIGN, "4.2 买入执行"),
+    "距强平比例": _design(_CHAIN_DESIGN, "4.2 买入执行"),
     "对冲记录成交股数": _HEDGE_ROOT,
     "累计成交股数": _excel("买入订单", 14, "累计成交股数"),
     "对冲含费成交均价": _excel("买入订单", 15, "对冲含费成交均价"),
@@ -327,9 +327,9 @@ _BUY_FORMULAS: Mapping[str, str] = {
 }
 
 _SELL_FORMULAS: Mapping[str, str] = {
-    "distance_warning": _design(_CHAIN_DESIGN, "4.3 卖出执行"),
-    "distance_danger": _design(_CHAIN_DESIGN, "4.3 卖出执行"),
-    "distance_force": _design(_CHAIN_DESIGN, "4.3 卖出执行"),
+    "距预警比例": _design(_CHAIN_DESIGN, "4.3 卖出执行"),
+    "距危险比例": _design(_CHAIN_DESIGN, "4.3 卖出执行"),
+    "距强平比例": _design(_CHAIN_DESIGN, "4.3 卖出执行"),
     "对冲记录成交股数": _HEDGE_ROOT,
     "累计成交股数": _excel("卖出订单", 12, "累计成交股数"),
     "对冲含费成交均价": _excel("卖出订单", 13, "对冲含费成交均价"),

+ 19 - 9
code/post_clear_check/src/post_clear_check/order_rules.py

@@ -165,31 +165,35 @@ def _append_note_distance_lines(
     note: Mapping[str, Any] | None,
     ambiguous: bool,
 ) -> None:
-    """用已发布清算票据的最新价和价格线复算三档距离比例。"""
+    """用已发布清算票据的最新价和价格线复算三档距离比例。
+
+    卡片公式用中文;库列仍是英文。× 100 把比值写成百分数(单位 %),不是 × 100%。
+    """
 
     label = row_label(note) if note is not None else "票据"
     latest = as_decimal(None if note is None else note.get("latest_px"))
+    # 库列名、价格线列名、卡片左侧字段名、价格线中文名。
     fields = (
-        ("distance_warning", "warn_price"),
-        ("distance_danger", "danger_price"),
-        ("distance_force", "force_price"),
+        ("distance_warning", "warn_price", "距预警比例", "预警价"),
+        ("distance_danger", "danger_price", "距危险比例", "危险价"),
+        ("distance_force", "force_price", "距强平比例", "强平价"),
     )
-    for distance_column, price_column in fields:
+    for distance_column, price_column, distance_name, price_name in fields:
         price = as_decimal(None if note is None else note.get(price_column))
         calc = (
             None
             if latest is None or price is None or latest == 0
             else quantize((latest - price) / latest * Decimal(100), SCALE_AMT)
         )
-        formula = f"{distance_column} = (latest_px − {price_column}) / latest_px × 100%"
+        formula = f"{distance_name} = (最新价 − {price_name}) / 最新价 × 100"
         if latest == 0:
-            calc_text = "latest_px 为 0,无法计算"
+            calc_text = "最新价为 0,无法计算"
         elif latest is None or price is None:
-            calc_text = f"latest_px 或 {price_column} 缺失,无法计算"
+            calc_text = f"最新价或{price_name}缺失,无法计算"
         else:
             calc_text = (
                 f"({format_decimal(latest, SCALE_PX)} - {format_decimal(price, SCALE_PX)}) / "
-                f"{format_decimal(latest, SCALE_PX)} × 100% = {format_decimal(calc, SCALE_AMT)}%"
+                f"{format_decimal(latest, SCALE_PX)} × 100 = {format_decimal(calc, SCALE_AMT)}%"
             )
         cell = read_note_cell(note, distance_column, (), {})
         if ambiguous:
@@ -1394,6 +1398,9 @@ def _shared_formulas() -> list[str]:
 
 def _buy_formulas() -> list[str]:
     return [
+        "距预警比例 = (最新价 − 预警价) / 最新价 × 100",
+        "距危险比例 = (最新价 − 危险价) / 最新价 × 100",
+        "距强平比例 = (最新价 − 强平价) / 最新价 × 100",
         "执行订单名义本金(结算币种) = 执行订单名义本金(交易币种)按结算汇率换算",
         "订单买入股数 = 执行订单名义本金(交易币种) / 订单成交均价",
         "订单成交均价 = 对冲含费成交均价",
@@ -1404,6 +1411,9 @@ def _buy_formulas() -> list[str]:
 
 def _sell_formulas() -> list[str]:
     return [
+        "距预警比例 = (最新价 − 预警价) / 最新价 × 100",
+        "距危险比例 = (最新价 − 危险价) / 最新价 × 100",
+        "距强平比例 = (最新价 − 强平价) / 最新价 × 100",
         "执行名义本金(交易币种) = 对冲完成且累计成交股数为 0 时为 0,否则为订单名义本金(交易币种)",
         "订单卖出股数 = 比例 × 当前票据股票数量",
         "卖出票据面值 = 比例 × 当前票据面值",

BIN
code/post_clear_check/tests/__pycache__/test_order_rules.cpython-312-pytest-9.1.1.pyc


+ 18 - 10
code/post_clear_check/tests/test_order_rules.py

@@ -129,7 +129,7 @@ def test_sell_final_pnl_uses_the_same_row() -> None:
 
 
 def test_note_distance_ratios_use_cleared_prices_and_round_half_up() -> None:
-    """风控距离比例用票据清算结果重算,按百分数保留两位。"""
+    """风控距离比例用票据清算结果重算,按百分数保留两位;卡片公式用中文且 × 100。"""
 
     note = {
         "id": 31,
@@ -143,16 +143,22 @@ def test_note_distance_ratios_use_cleared_prices_and_round_half_up() -> None:
         "distance_force": Decimal("30.02"),
     }
     lines = check_order(_order(_sub(), notes=(note,)))
-    warning = _one(lines, "distance_warning")
+    warning = _one(lines, "距预警比例")
     assert warning.verdict == "correct"
     assert warning.calc_value == Decimal("10.01")
     assert warning.check_value == Decimal("10.01")
-    danger = _one(lines, "distance_danger")
+    assert warning.formula_text == "距预警比例 = (最新价 − 预警价) / 最新价 × 100"
+    assert warning.calc_text == "(200.000000 - 179.990000) / 200.000000 × 100 = 10.01%"
+    assert "100%" not in warning.formula_text
+    assert "× 100%" not in warning.calc_text
+    danger = _one(lines, "距危险比例")
     assert danger.verdict == "correct"
     assert danger.calc_value == Decimal("20.01")
-    force = _one(lines, "distance_force")
+    assert danger.formula_text == "距危险比例 = (最新价 − 危险价) / 最新价 × 100"
+    force = _one(lines, "距强平比例")
     assert force.verdict == "correct"
     assert force.calc_value == Decimal("30.02")
+    assert force.formula_text == "距强平比例 = (最新价 − 强平价) / 最新价 × 100"
 
 
 def test_note_distance_ratio_mismatch_is_error_and_zero_latest_is_suspect() -> None:
@@ -167,14 +173,15 @@ def test_note_distance_ratio_mismatch_is_error_and_zero_latest_is_suspect() -> N
         "distance_danger": Decimal("20.00"),
         "distance_force": Decimal("30.00"),
     }
-    warning = _one(check_order(_order(_sub(), notes=(note,))), "distance_warning")
+    warning = _one(check_order(_order(_sub(), notes=(note,))), "距预警比例")
     assert warning.verdict == "error"
     assert warning.calc_value == Decimal("10.00")
 
     note["latest_px"] = Decimal("0")
-    warning = _one(check_order(_order(_sub(), notes=(note,))), "distance_warning")
+    warning = _one(check_order(_order(_sub(), notes=(note,))), "距预警比例")
     assert warning.verdict == "suspect"
     assert warning.calc_value is None
+    assert warning.calc_text == "最新价为 0,无法计算"
 
 
 def test_sell_order_checks_note_distance_ratios() -> None:
@@ -193,9 +200,9 @@ def test_sell_order_checks_note_distance_ratios() -> None:
         }
     )
     lines = check_order(order)
-    assert _one(lines, "distance_warning").verdict == "correct"
-    assert _one(lines, "distance_danger").verdict == "correct"
-    assert _one(lines, "distance_force").verdict == "correct"
+    assert _one(lines, "距预警比例").verdict == "correct"
+    assert _one(lines, "距危险比例").verdict == "correct"
+    assert _one(lines, "距强平比例").verdict == "correct"
 
 
 def test_sell_ratio_without_exec_notional_is_not_checked() -> None:
@@ -315,7 +322,8 @@ def test_force_and_maturity_use_sell_formulas() -> None:
     force = _formulas(_sell_order(direction="buy", op_type="force"))
     maturity = _formulas(_sell_order(direction="sell", op_type="maturity"))
     assert sell == force == maturity
-    assert all("强平" not in text and "到期" not in text for text in force)
+    # 不因 op_type 另造「强平…」「到期…」公式;距强平比例是三线字段名,不算另造。
+    assert all(not text.startswith(("强平", "到期")) for text in force)
     assert not any(text.startswith("票据发行价") for text in force)
     assert any(text.startswith("最终盈亏") for text in force)
     buy = _formulas(_order(_issue_sub()))

+ 1 - 1
doc/业务链路核对设计.md

@@ -128,7 +128,7 @@
 | T日订单结算 | `work/3`、`work/4`、`cleared/0` 都必须有该子单 | 结算口径列一致 | 研发设计 `docs/tmp/data_check_tool/doc/清算步骤核对改造方案.md`「5.1 发行端(6 步)」 |
 | 当前投影 | 实时子单必须存在 | 实时结算口径列等于 `cleared`。执行事实列由执行事实节点核对,清算不回写这两列 | 研发设计 `docs/tmp/data_check_tool/doc/清算步骤核对改造方案.md`「5.1 发行端(6 步)」 |
 | 报表行 | 两端 OTC003 必须有该主订单的行 | 报表列等于 `cleared` 子单。不用回写 `after_val`。发行端总金额不含承销费用,不再另出「含承销费用」的可疑行;销售端承销费用等于已发布销售订单的 `exec_underwrite_fee_settle`,发行子单没有这一列 | 产品原型 OTC003(见 4.13) |
-| 风控距离比例 | `cleared/0` 的 `note_sub_a` 必须有唯一关联票据 | `distance_warning = (latest_px − warn_price) / latest_px × 100%`、`distance_danger = (latest_px − danger_price) / latest_px × 100%`、`distance_force = (latest_px − force_price) / latest_px × 100%`,分别四舍五入到 2 位后与清算结果中的比例比较。最新价为 0 时无法计算,比例或输入缺失按订单规则记结论 | 本设计确认的风控距离比例公式;读取发行端步骤 5 发布的 `cleared/0` |
+| 风控距离比例 | `cleared/0` 的 `note_sub_a` 必须有唯一关联票据 | `距预警比例 = (最新价 − 预警价) / 最新价 × 100`、`距危险比例 = (最新价 − 危险价) / 最新价 × 100`、`距强平比例 = (最新价 − 强平价) / 最新价 × 100`;结果按百分数记(单位 %,如 10.01 表示 10.01%),四舍五入到 2 位后与清算结果中的比例比较。最新价为 0 时无法计算,比例或输入缺失按订单规则记结论。库列仍是 `distance_warning` / `distance_danger` / `distance_force` 与 `latest_px` / `warn_price` / `danger_price` / `force_price` | 本设计确认的风控距离比例公式;读取发行端步骤 5 发布的 `cleared/0` |
 | 对冲仓位 | 有清算副本时必须出现 | 股数、已结算实现盈亏等于本批 `hedge_position` 清算副本按票据汇总。没有副本时不读实时持仓,仍记可疑 | 研发设计发行端 OTC003 用清算副本 `hedge_position` |
 
 ### 4.3 卖出执行

+ 1 - 1
doc/使用说明.md

@@ -149,7 +149,7 @@ python -m post_clear_check
    - OTC001 的钱包比对和现金可用读 T 日已发布候选派生出的钱包(`kind=cleared`,当日买卖先置 0,再算现金余额、冻结和可用)。科目都是正数;报表买入待交收展示为负值,比较和代入公式时取相反数。没有已发布候选或缺本币种行时这几条记可疑。账户资产、资金变动、资金交收的步骤节点另读销售 `kind=input` 与 `kind=cleared`:第 3、4 步用 input 重算后与 cleared 比,不读 `pre_clearing`。input 里已经有该订单 posted 的解冻凭证时,不再把交易冻结扣第二次。重放 input 流水按主键 id 升序,不按查询返回的幂等哈希顺序。发行和销售日报只读 `status=active`,同一营业日的 superseded、deprecated 旧行不核对。等值港币资产净值按现金可用、冻结、票据估值各自换算后量化到分再相加(与销售报表一致);整笔资产净值推不出唯一方向时改用三项总和推断。乘、除量化到分后结果相同时,方向视为已确定。汇率为 1 的同币种订单结算汇率与名义本金换算记正确,不再因乘除同值记可疑。钱包只重算本批 `input` 与 `cleared` 的 T 日结果,不读实时钱包。发行交收台账读 `work/4`,`cleared/0` 应与它一致,不读实时台账。日终 `daycut_fund_eod` 只在核对门禁上与这份候选比,不一致才记错误。
    - 同一 IN 列表超过 500 个值时分几条语句读。
    - OTC007、OTC008 行按票据号挂到订单上,只核业务类型(`buy_note`、`sell_note`)与主订单方向一致、交易日相同的行。同一票据另一张订单的行不算进来;哪张已读主单都对不上的行按「有行但无法对应主订单编号」记可疑。
-   - 主订单买入、卖出执行链路用已发布 `cleared/0` 的 `note_sub_a` 核对三个风控距离比例:分别按 `(latest_px − warn_price) / latest_px × 100%`、`(latest_px − danger_price) / latest_px × 100%`、`(latest_px − force_price) / latest_px × 100%` 计算,四舍五入到 2 位后比较。`latest_px` 为 0 时无法计算。
+   - 主订单买入、卖出执行链路用已发布 `cleared/0` 的 `note_sub_a` 核对三个风控距离比例:卡片上写 `距预警比例 = (最新价 − 预警价) / 最新价 × 100`、`距危险比例 = (最新价 − 危险价) / 最新价 × 100`、`距强平比例 = (最新价 − 强平价) / 最新价 × 100`;结果按百分数记(单位 %),四舍五入到 2 位后比较。最新价为 0 时无法计算。
 6. 结论行在调用核对函数的地方挂到业务链路(见下表),再按产生来源收成最多四张方向卡写入自有库,发给飞书。顺序是订单链路、发行报表、销售报表、TRS 报表。没有结论行的方向不发。一张卡上有多条时,总结论取最严重的一档:`error` 高于 `suspect`,`suspect` 高于 `correct`。方向卡主题是错误时,正确行仍写在正文里。`check_run` 上的三个计数是方向卡张数,不是结论行数。门禁没开跑时仍只落清算批次卡,不发四张空卡。
 
 方向卡的 `card_kind` 是 `dimension`,`chain_name` 是 `order_chain`、`issue_report`、`sales_report`、`trs_report`,`biz_key` 是营业日。结论行上的内部链路代码不改。没挂链或归属键为空的行仍是未归错误,放进产生它的那个方向。发行门禁写进发行报表的批次门禁,销售、TRS 同理。订单卡的摘要只引用三端门禁有没有差异。链路的口径和节点以 `doc/业务链路核对设计.md` 为准。