|
|
@@ -14,11 +14,13 @@ from typing import Any
|
|
|
from post_clear_check.issue_common import as_date, on_business_date, present_number
|
|
|
from post_clear_check.order_cell import CheckLine, biz_text, finish
|
|
|
from post_clear_check.order_decimal import (
|
|
|
+ SCALE_PX,
|
|
|
VERDICT_CORRECT,
|
|
|
VERDICT_ERROR,
|
|
|
VERDICT_SUSPECT,
|
|
|
as_decimal,
|
|
|
format_decimal,
|
|
|
+ quantize,
|
|
|
)
|
|
|
from post_clear_check.trs_common import (
|
|
|
SCALE_AMT,
|
|
|
@@ -413,38 +415,165 @@ def _fact_amount(
|
|
|
|
|
|
|
|
|
def _realized(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
|
- for account, row in report_rows(ctx, T_TRS008):
|
|
|
- user = ctx.users[account]
|
|
|
- key, sources = rows_with_column(
|
|
|
- user,
|
|
|
- "realized_pnl_settle",
|
|
|
- ("clr_trade_fill", "trade_fill", "clr_trade_order", "trade_order"),
|
|
|
- )
|
|
|
- sources = _align(row, sources, ("sec_code", "settle_ccy"))
|
|
|
- order_no = linked_order(ctx, row) or _order_from_sources(ctx, sources)
|
|
|
- where = where_text(T_TRS008, account, order_no)
|
|
|
- if key is None or not sources:
|
|
|
- calc_text, calc_value = "取数结果没有同号列 realized_pnl_settle", None
|
|
|
- else:
|
|
|
- calc_value = _sum(sources, "realized_pnl_settle")
|
|
|
- calc_text = (
|
|
|
- f"{key}.realized_pnl_settle 缺失"
|
|
|
- if calc_value is None
|
|
|
- else f"{key}.realized_pnl_settle = {format_decimal(calc_value, SCALE_AMT)}"
|
|
|
+ """TRS008 一行对应一笔叶子卖出子单的成交已实现盈亏之和,不是按证券加总。"""
|
|
|
+
|
|
|
+ grouped = _by_account(report_rows(ctx, T_TRS008))
|
|
|
+ for account in sorted(set(grouped) | set(ctx.users)):
|
|
|
+ user = ctx.users.get(account)
|
|
|
+ if user is None:
|
|
|
+ continue
|
|
|
+ reports = grouped.get(account, [])
|
|
|
+ facts = _realized_facts(user)
|
|
|
+ used: set[int] = set()
|
|
|
+ for row in reports:
|
|
|
+ fact = _match_realized(row, facts, used)
|
|
|
+ where = where_text(
|
|
|
+ T_TRS008,
|
|
|
+ account,
|
|
|
+ linked_order(ctx, row) or (_text(fact.get("entrust_no")) if fact else None),
|
|
|
)
|
|
|
- emit(
|
|
|
- lines,
|
|
|
- formula="已实现盈亏 = 平仓事实的同号列",
|
|
|
- basis="平仓已实现盈亏等于平仓事实的盈亏",
|
|
|
- calc_text=calc_text,
|
|
|
- calc_value=calc_value,
|
|
|
- report_name=TRS008_NAME,
|
|
|
- column="实现盈亏",
|
|
|
- row=row,
|
|
|
- field="realized_pnl_settle",
|
|
|
- where=where,
|
|
|
- scale=SCALE_AMT,
|
|
|
+ if not facts and not user.has("clr_trade_fill") and not user.has("trade_fill"):
|
|
|
+ calc_text, calc_value = "取数结果没有同号列 realized_pnl_settle", None
|
|
|
+ elif fact is None:
|
|
|
+ calc_text, calc_value = "没有对上的平仓事实", None
|
|
|
+ else:
|
|
|
+ calc_value = as_decimal(fact.get("realized_pnl_settle"))
|
|
|
+ calc_text = (
|
|
|
+ f"{fact.get('_source')}.realized_pnl_settle 缺失"
|
|
|
+ if calc_value is None
|
|
|
+ else (
|
|
|
+ f"{fact.get('_source')}.realized_pnl_settle = "
|
|
|
+ f"{format_decimal(calc_value, SCALE_AMT)}"
|
|
|
+ )
|
|
|
+ )
|
|
|
+ emit(
|
|
|
+ lines,
|
|
|
+ formula="已实现盈亏 = 平仓事实的同号列",
|
|
|
+ basis="平仓已实现盈亏等于该叶子卖出子单成交回报的盈亏之和",
|
|
|
+ calc_text=calc_text,
|
|
|
+ calc_value=calc_value,
|
|
|
+ report_name=TRS008_NAME,
|
|
|
+ column="实现盈亏",
|
|
|
+ row=row,
|
|
|
+ field="realized_pnl_settle",
|
|
|
+ where=where,
|
|
|
+ scale=SCALE_AMT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def _realized_facts(user: UserView) -> list[dict[str, Any]]:
|
|
|
+ """按清算报表口径:叶子卖出子单聚合成交已实现盈亏。"""
|
|
|
+
|
|
|
+ order_key = "clr_trade_order" if user.has("clr_trade_order") else "trade_order"
|
|
|
+ fill_key = "clr_trade_fill" if user.has("clr_trade_fill") else "trade_fill"
|
|
|
+ if not user.has(order_key) or not user.has(fill_key):
|
|
|
+ return []
|
|
|
+ orders = user.rows(order_key)
|
|
|
+ fills = user.rows(fill_key)
|
|
|
+ parent_ids = {
|
|
|
+ int(order["parent_order_id"])
|
|
|
+ for order in orders
|
|
|
+ if order.get("parent_order_id") is not None
|
|
|
+ }
|
|
|
+ fills_by_entrust: dict[str, list[Mapping[str, Any]]] = {}
|
|
|
+ for fill in fills:
|
|
|
+ entrust = _text(fill.get("entrust_no"))
|
|
|
+ if entrust is not None:
|
|
|
+ fills_by_entrust.setdefault(entrust, []).append(fill)
|
|
|
+ facts: list[dict[str, Any]] = []
|
|
|
+ for order in orders:
|
|
|
+ # 方向枚举在库里是 sell;大小写兼容测试注入。
|
|
|
+ direction = _text(order.get("direction"))
|
|
|
+ if direction is None or direction.lower() != "sell":
|
|
|
+ continue
|
|
|
+ qty = as_decimal(order.get("filled_qty"))
|
|
|
+ if qty is None or qty <= 0:
|
|
|
+ continue
|
|
|
+ src_id = order.get("src_id", order.get("id"))
|
|
|
+ try:
|
|
|
+ if src_id is not None and int(src_id) in parent_ids:
|
|
|
+ continue
|
|
|
+ except (TypeError, ValueError):
|
|
|
+ pass
|
|
|
+ entrust = _text(order.get("entrust_no"))
|
|
|
+ if entrust is None:
|
|
|
+ continue
|
|
|
+ group = fills_by_entrust.get(entrust, [])
|
|
|
+ if not group:
|
|
|
+ continue
|
|
|
+ if any("realized_pnl_settle" not in fill for fill in group):
|
|
|
+ continue
|
|
|
+ pnl = _sum(group, "realized_pnl_settle")
|
|
|
+ facts.append(
|
|
|
+ {
|
|
|
+ "entrust_no": entrust,
|
|
|
+ "sec_code": order.get("sec_code"),
|
|
|
+ "filled_qty": qty,
|
|
|
+ "filled_price": order.get("filled_avg_px"),
|
|
|
+ "deal_date": order.get("trade_date"),
|
|
|
+ "realized_pnl_settle": pnl,
|
|
|
+ "_source": fill_key,
|
|
|
+ }
|
|
|
)
|
|
|
+ return facts
|
|
|
+
|
|
|
+
|
|
|
+def _match_realized(
|
|
|
+ row: Mapping[str, Any],
|
|
|
+ facts: Sequence[Mapping[str, Any]],
|
|
|
+ used: set[int],
|
|
|
+) -> Mapping[str, Any] | None:
|
|
|
+ """报表行对叶子卖出事实。同证券同数量同均价可能多笔,按实现盈亏再收窄并各用一次。"""
|
|
|
+
|
|
|
+ candidates = [
|
|
|
+ fact
|
|
|
+ for fact in facts
|
|
|
+ if id(fact) not in used
|
|
|
+ and _same_token(fact.get("sec_code"), row.get("sec_code"))
|
|
|
+ and _same_qty(fact.get("filled_qty"), row.get("filled_qty"))
|
|
|
+ and _same_price(fact.get("filled_price"), row.get("filled_price"))
|
|
|
+ and _same_day(fact.get("deal_date"), row.get("deal_date"))
|
|
|
+ ]
|
|
|
+ if not candidates:
|
|
|
+ return None
|
|
|
+ report_pnl = as_decimal(row.get("realized_pnl_settle"))
|
|
|
+ if report_pnl is not None:
|
|
|
+ exact = [
|
|
|
+ fact
|
|
|
+ for fact in candidates
|
|
|
+ if as_decimal(fact.get("realized_pnl_settle")) is not None
|
|
|
+ and quantize(as_decimal(fact.get("realized_pnl_settle")), SCALE_AMT)
|
|
|
+ == quantize(report_pnl, SCALE_AMT)
|
|
|
+ ]
|
|
|
+ if exact:
|
|
|
+ candidates = exact
|
|
|
+ chosen = candidates[0]
|
|
|
+ used.add(id(chosen))
|
|
|
+ return chosen
|
|
|
+
|
|
|
+
|
|
|
+def _same_token(left: object, right: object) -> bool:
|
|
|
+ return _text(left) == _text(right) if _text(left) is not None and _text(right) is not None else left == right
|
|
|
+
|
|
|
+
|
|
|
+def _same_qty(left: object, right: object) -> bool:
|
|
|
+ a, b = as_decimal(left), as_decimal(right)
|
|
|
+ if a is None or b is None:
|
|
|
+ return left == right
|
|
|
+ return quantize(a, SCALE_QTY) == quantize(b, SCALE_QTY)
|
|
|
+
|
|
|
+
|
|
|
+def _same_price(left: object, right: object) -> bool:
|
|
|
+ a, b = as_decimal(left), as_decimal(right)
|
|
|
+ if a is None or b is None:
|
|
|
+ return left == right
|
|
|
+ return quantize(a, SCALE_PX) == quantize(b, SCALE_PX)
|
|
|
+
|
|
|
+
|
|
|
+def _same_day(left: object, right: object) -> bool:
|
|
|
+ if left is None or right is None:
|
|
|
+ return True
|
|
|
+ return as_date(left) == as_date(right)
|
|
|
|
|
|
|
|
|
def _in_out(ctx: TrsContext, lines: list[CheckLine]) -> None:
|