|
@@ -71,16 +71,32 @@ def check_cash_flow(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
def _holdings(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
def _holdings(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
|
|
|
+ """TRS005 按通道拆行;清算持仓按证券+渠道聚合。先按对齐键汇总报表再对清算。"""
|
|
|
|
|
+
|
|
|
|
|
+ grouped: dict[tuple[str, str | None, str | None, str | None], list[Mapping[str, Any]]] = {}
|
|
|
for account, row in report_rows(ctx, T_TRS005):
|
|
for account, row in report_rows(ctx, T_TRS005):
|
|
|
|
|
+ key = (
|
|
|
|
|
+ account,
|
|
|
|
|
+ _text(row.get("sec_code")),
|
|
|
|
|
+ _text(row.get("trade_channel")),
|
|
|
|
|
+ _text(row.get("settle_ccy")),
|
|
|
|
|
+ )
|
|
|
|
|
+ grouped.setdefault(key, []).append(row)
|
|
|
|
|
+ for (account, sec_code, _channel, _ccy), rows in sorted(grouped.items(), key=lambda item: item[0]):
|
|
|
user = ctx.users[account]
|
|
user = ctx.users[account]
|
|
|
- key, positions, live = _position_source(ctx, user)
|
|
|
|
|
|
|
+ source_key, positions, live = _position_source(ctx, user)
|
|
|
where = where_text(T_TRS005, account, None)
|
|
where = where_text(T_TRS005, account, None)
|
|
|
- aligned = _align(row, positions, ("sec_code", "trade_channel", "settle_ccy"))
|
|
|
|
|
|
|
+ if sec_code:
|
|
|
|
|
+ where = f"{where} / {sec_code}" if where else sec_code
|
|
|
|
|
+ # 用组内首行对齐清算持仓;同键多通道行的数量与账面成本先加总再比。
|
|
|
|
|
+ sample = rows[0]
|
|
|
|
|
+ aligned = _align(sample, positions, ("sec_code", "trade_channel", "settle_ccy"))
|
|
|
|
|
+ merged = _merged_holding_report(rows, sample)
|
|
|
_position_measure(
|
|
_position_measure(
|
|
|
lines,
|
|
lines,
|
|
|
- row,
|
|
|
|
|
|
|
+ merged,
|
|
|
where,
|
|
where,
|
|
|
- key,
|
|
|
|
|
|
|
+ source_key,
|
|
|
aligned,
|
|
aligned,
|
|
|
"position_qty",
|
|
"position_qty",
|
|
|
"持仓数量",
|
|
"持仓数量",
|
|
@@ -90,9 +106,9 @@ def _holdings(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
|
)
|
|
)
|
|
|
_position_measure(
|
|
_position_measure(
|
|
|
lines,
|
|
lines,
|
|
|
- row,
|
|
|
|
|
|
|
+ merged,
|
|
|
where,
|
|
where,
|
|
|
- key,
|
|
|
|
|
|
|
+ source_key,
|
|
|
aligned,
|
|
aligned,
|
|
|
"book_cost_amount",
|
|
"book_cost_amount",
|
|
|
"持仓成本金额",
|
|
"持仓成本金额",
|
|
@@ -103,6 +119,27 @@ def _holdings(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _merged_holding_report(
|
|
|
|
|
+ rows: Sequence[Mapping[str, Any]],
|
|
|
|
|
+ sample: Mapping[str, Any],
|
|
|
|
|
+) -> dict[str, Any]:
|
|
|
|
|
+ """同对齐键的多通道报表行合成一行,数量与账面成本取合计。"""
|
|
|
|
|
+
|
|
|
|
|
+ merged = dict(sample)
|
|
|
|
|
+ if all("position_qty" in row for row in rows):
|
|
|
|
|
+ merged["position_qty"] = _sum(rows, "position_qty")
|
|
|
|
|
+ else:
|
|
|
|
|
+ merged.pop("position_qty", None)
|
|
|
|
|
+ if all("position_cost_settle" in row for row in rows):
|
|
|
|
|
+ merged["position_cost_settle"] = _sum(rows, "position_cost_settle")
|
|
|
|
|
+ elif any("position_cost_settle" in row for row in rows):
|
|
|
|
|
+ # 部分行缺列:保留键且值为空,emit 记缺失,避免静默少加。
|
|
|
|
|
+ merged["position_cost_settle"] = None
|
|
|
|
|
+ else:
|
|
|
|
|
+ merged.pop("position_cost_settle", None)
|
|
|
|
|
+ return merged
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _position_source(
|
|
def _position_source(
|
|
|
ctx: TrsContext,
|
|
ctx: TrsContext,
|
|
|
user: UserView,
|
|
user: UserView,
|
|
@@ -597,7 +634,7 @@ def _in_out(ctx: TrsContext, lines: list[CheckLine]) -> None:
|
|
|
if id(record) in used:
|
|
if id(record) in used:
|
|
|
continue
|
|
continue
|
|
|
where = where_text(T_TRS009, account, None)
|
|
where = where_text(T_TRS009, account, None)
|
|
|
- calc_text, calc_value, _force = _named("fund_in_out_record", "amount", record, SCALE_AMT)
|
|
|
|
|
|
|
+ calc_text, calc_value, _force = _signed_in_out_amount(record)
|
|
|
emit(
|
|
emit(
|
|
|
lines,
|
|
lines,
|
|
|
formula="金额 = 出入金流水的同号列",
|
|
formula="金额 = 出入金流水的同号列",
|
|
@@ -628,7 +665,8 @@ def _in_out_amount(
|
|
|
elif "amount" not in record:
|
|
elif "amount" not in record:
|
|
|
calc_text, calc_value, force = "fund_in_out_record 没有同号列 amount", None, None
|
|
calc_text, calc_value, force = "fund_in_out_record 没有同号列 amount", None, None
|
|
|
else:
|
|
else:
|
|
|
- calc_text, calc_value, force = _named("fund_in_out_record", "amount", record, SCALE_AMT)
|
|
|
|
|
|
|
+ # 流水存绝对值+direction;报表 Amount 入正出负。按方向赋符号后再比。
|
|
|
|
|
+ calc_text, calc_value, force = _signed_in_out_amount(record)
|
|
|
emit(
|
|
emit(
|
|
|
lines,
|
|
lines,
|
|
|
formula="金额 = 出入金流水的同号列",
|
|
formula="金额 = 出入金流水的同号列",
|
|
@@ -645,6 +683,38 @@ def _in_out_amount(
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _signed_in_out_amount(record: Mapping[str, Any]) -> tuple[str, Decimal | None, str | None]:
|
|
|
|
|
+ """流水 amount 按 direction/dw 转成与报表一致的带符号金额。"""
|
|
|
|
|
+
|
|
|
|
|
+ present, number = present_number(record, "amount")
|
|
|
|
|
+ if not present or number is None:
|
|
|
|
|
+ return "fund_in_out_record.amount 缺失", None, None
|
|
|
|
|
+ direction = _text(record.get("direction")) if "direction" in record else _text(record.get("dw"))
|
|
|
|
|
+ signed = _apply_in_out_sign(number, direction)
|
|
|
|
|
+ if direction in {"out", "W", "w"}:
|
|
|
|
|
+ note = "出金取负"
|
|
|
|
|
+ elif direction in {"in", "D", "d"}:
|
|
|
|
|
+ note = "入金取正"
|
|
|
|
|
+ else:
|
|
|
|
|
+ note = "方向未知,沿用原值"
|
|
|
|
|
+ return (
|
|
|
|
|
+ f"fund_in_out_record.amount = {format_decimal(signed, SCALE_AMT)}({note})",
|
|
|
|
|
+ signed,
|
|
|
|
|
+ None,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _apply_in_out_sign(amount: Decimal, direction: str | None) -> Decimal:
|
|
|
|
|
+ """入金为正、出金为负;与 TRS009 原型 Amount 符号规则一致。"""
|
|
|
|
|
+
|
|
|
|
|
+ magnitude = abs(amount)
|
|
|
|
|
+ if direction in {"out", "W", "w"}:
|
|
|
|
|
+ return -magnitude
|
|
|
|
|
+ if direction in {"in", "D", "d"}:
|
|
|
|
|
+ return magnitude
|
|
|
|
|
+ return amount
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _in_out_direction(
|
|
def _in_out_direction(
|
|
|
lines: list[CheckLine],
|
|
lines: list[CheckLine],
|
|
|
row: Mapping[str, Any],
|
|
row: Mapping[str, Any],
|