T-13 补修:verify_convert_lots/apply 字面造数日期改真库日历链式回推(用户指出硬编码 · 全仓排查留痕)
This commit is contained in:
@@ -26,7 +26,7 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import threading
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, time as dtime, timedelta
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from pathlib import Path
|
||||
|
||||
@@ -50,7 +50,9 @@ from app.service.convert.calc import ( # noqa: E402
|
||||
lot_fee,
|
||||
plan_lots,
|
||||
)
|
||||
from app.repository.core_ro import CoreReadOnlyRepository # noqa: E402
|
||||
from app.service.convert.errors import LotConflict # noqa: E402
|
||||
from app.service.convert.trading_calendar import previous_biz_day # noqa: E402
|
||||
from app.service.convert.fee import pick_fee_rate # noqa: E402
|
||||
from app.service.convert.types import FeeRule, Lot # noqa: E402
|
||||
from app.utils.db import dispose_engines, get_engine # noqa: E402
|
||||
@@ -58,8 +60,29 @@ from app.utils.db import dispose_engines, get_engine # noqa: E402
|
||||
CUSTOMER = "CUST-T6M"
|
||||
PROD_OUT = "PROD-T6MO"
|
||||
PROD_IN = "PROD-T6MI"
|
||||
TRADE_AT = datetime(2026, 9, 4, 10, 0, 0)
|
||||
TRADE_DATE = date(2026, 9, 4)
|
||||
# 造数日期在 main() 里按真库交易日历链式回推动态确定(T-12 模式:不猜自然日)。
|
||||
# 相对关系与原固定基准(2026-09-04)一致 —— hold_days 是自然日语义,档位断言不变:
|
||||
# TRADE_DATE = 回推 3 个交易日;A1 批次 = −34 自然日(档 [30,180));
|
||||
# A2 批次 = −3 自然日(档 [0,7));转入批次 confirmed_at = +1 自然日。
|
||||
TRADE_AT: "datetime | None" = None
|
||||
TRADE_DATE: "date | None" = None
|
||||
_A1_AT: "datetime | None" = None
|
||||
_A2_AT: "datetime | None" = None
|
||||
_IN_CONFIRMED_AT: "datetime | None" = None
|
||||
|
||||
|
||||
def _init_dates(admin) -> None:
|
||||
"""按真库交易日历把造数日期锚定到确定过去的历史开市日(必须在 seed 前调用)。"""
|
||||
global TRADE_AT, TRADE_DATE, _A1_AT, _A2_AT, _IN_CONFIRMED_AT
|
||||
is_open = CoreReadOnlyRepository(engine=admin).is_open
|
||||
d = date.today()
|
||||
for _ in range(3):
|
||||
d = previous_biz_day(d, is_open)
|
||||
TRADE_DATE = d
|
||||
TRADE_AT = datetime.combine(d, dtime(10, 0))
|
||||
_A1_AT = datetime.combine(d - timedelta(days=34), dtime(10, 0))
|
||||
_A2_AT = datetime.combine(d - timedelta(days=3), dtime(10, 0))
|
||||
_IN_CONFIRMED_AT = datetime.combine(d + timedelta(days=1), dtime(10, 0))
|
||||
OUT_RATE = Decimal("0.0030")
|
||||
IN_RATE = Decimal("0.0080")
|
||||
IN_NAV = Decimal("0.9500")
|
||||
@@ -127,8 +150,8 @@ def seed(engine) -> None:
|
||||
{"p": PROD_IN, "nav": str(IN_NAV), "d": TRADE_DATE},
|
||||
)
|
||||
for lot_id, qty, nav, confirmed in [
|
||||
("LOT-T6M-A1", "100", "1.0300", datetime(2026, 8, 1, 10, 0, 0)),
|
||||
("LOT-T6M-A2", "50", "1.0000", datetime(2026, 9, 1, 10, 0, 0)),
|
||||
("LOT-T6M-A1", "100", "1.0300", _A1_AT),
|
||||
("LOT-T6M-A2", "50", "1.0000", _A2_AT),
|
||||
]:
|
||||
conn.execute(
|
||||
text(
|
||||
@@ -217,7 +240,7 @@ def build_input(engine, group_id: str, requested: str, in_lot_id: str = "LOT-T6M
|
||||
in_nav=IN_NAV,
|
||||
in_nav_date=TRADE_DATE,
|
||||
in_lot_id=in_lot_id,
|
||||
in_confirmed_at=datetime(2026, 9, 5, 10, 0, 0),
|
||||
in_confirmed_at=_IN_CONFIRMED_AT,
|
||||
charges=tuple(charges),
|
||||
)
|
||||
|
||||
@@ -229,6 +252,7 @@ def q1(engine, sql: str, **params):
|
||||
|
||||
def main() -> int:
|
||||
admin = get_engine(settings.mysql_core_database, "admin")
|
||||
_init_dates(admin)
|
||||
repo = ConvertCoreRepository() # 真实路径:role="rw"(账号未配时回退 mysql_user)
|
||||
try:
|
||||
cleanup(admin)
|
||||
|
||||
@@ -25,7 +25,7 @@ TEXT 绑定参数转数值 → 不加就扣 0;见开发计划 §B.8 第 11 条
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, time as dtime, timedelta
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from pathlib import Path
|
||||
|
||||
@@ -39,14 +39,41 @@ from app.gateway.gateway_repository import GatewayRepository # noqa: E402
|
||||
from app.gateway.trade_gateway import _maintain_lots # noqa: E402
|
||||
from app.repository.core_ro import CoreReadOnlyRepository # noqa: E402
|
||||
from app.service.convert.lot_bootstrap import bootstrap_lots # noqa: E402
|
||||
from app.service.convert.trading_calendar import previous_biz_day # noqa: E402
|
||||
from app.utils.db import dispose_engines, get_engine # noqa: E402
|
||||
|
||||
CUSTOMER = "CUST-LOTT"
|
||||
PROD_SUB = "PROD-LOTTA" # A 组:申购建批次
|
||||
PROD_BOOT = "PROD-LOTTB" # D 组:无批次有持仓 → D8 兜底补建
|
||||
PROD_REDEEM = "PROD-LOTTC" # B/C 组:赎回 FIFO 扣减
|
||||
NAV_DATE = date(2026, 9, 4)
|
||||
TRADE_AT = datetime(2026, 9, 4, 14, 0, 0)
|
||||
# 造数日期在 main() 里按真库交易日历链式回推动态确定(T-12 模式:不猜自然日,
|
||||
# 对任意假期安排与日期漂移鲁棒)。相对关系与原固定基准(2026-09-04)一致:
|
||||
# TRADE_DATE = 回推 3 个交易日;C1/C2 批次 = 再回推 3/2 个交易日(保证 T+2 可扣);
|
||||
# D 组持仓 as_of = 回推 34 个自然日(hold_days 为自然日语义,档位断言不变)。
|
||||
NAV_DATE: "date | None" = None
|
||||
TRADE_AT: "datetime | None" = None
|
||||
_C1_AT: "datetime | None" = None
|
||||
_C2_AT: "datetime | None" = None
|
||||
_BOOT_AS_OF: "date | None" = None
|
||||
|
||||
|
||||
def _init_dates(admin) -> None:
|
||||
"""按真库交易日历把造数日期锚定到确定过去的历史开市日(必须在 seed 前调用)。"""
|
||||
global NAV_DATE, TRADE_AT, _C1_AT, _C2_AT, _BOOT_AS_OF
|
||||
is_open = CoreReadOnlyRepository(engine=admin).is_open
|
||||
d = date.today()
|
||||
for _ in range(3):
|
||||
d = previous_biz_day(d, is_open)
|
||||
NAV_DATE = d
|
||||
TRADE_AT = datetime.combine(d, dtime(10, 0))
|
||||
c1 = c2 = d
|
||||
for _ in range(3):
|
||||
c1 = previous_biz_day(c1, is_open)
|
||||
for _ in range(2):
|
||||
c2 = previous_biz_day(c2, is_open)
|
||||
_C1_AT = datetime.combine(c1, dtime(10, 0))
|
||||
_C2_AT = datetime.combine(c2, dtime(10, 0))
|
||||
_BOOT_AS_OF = d - timedelta(days=34)
|
||||
|
||||
_passed = 0
|
||||
_failed = 0
|
||||
@@ -108,8 +135,8 @@ def seed(engine) -> None:
|
||||
)
|
||||
# C 组:两个已存在批次(FIFO 扣减用)
|
||||
for lot_id, qty, nav, confirmed in [
|
||||
("LOT-LOTT-C1", "100", "1.0000", datetime(2026, 9, 1, 10, 0, 0)),
|
||||
("LOT-LOTT-C2", "50", "1.1000", datetime(2026, 9, 2, 10, 0, 0)),
|
||||
("LOT-LOTT-C1", "100", "1.0000", _C1_AT),
|
||||
("LOT-LOTT-C2", "50", "1.1000", _C2_AT),
|
||||
]:
|
||||
conn.execute(
|
||||
text(
|
||||
@@ -127,7 +154,7 @@ def seed(engine) -> None:
|
||||
"INSERT INTO core_holding (customer_id, product_id, qty, cost_amount, "
|
||||
"market_value, pnl_pct, as_of) VALUES (:c, :p, :q, :cost, :cost, 0, :d)"
|
||||
),
|
||||
{"c": CUSTOMER, "p": pid, "q": qty, "cost": cost, "d": date(2026, 8, 1)},
|
||||
{"c": CUSTOMER, "p": pid, "q": qty, "cost": cost, "d": _BOOT_AS_OF},
|
||||
)
|
||||
|
||||
|
||||
@@ -183,7 +210,7 @@ def assert_subscribe(admin, writer, core) -> None:
|
||||
"confirmed_at = T",
|
||||
str(q1(admin, "SELECT confirmed_at FROM core_share_lot WHERE customer_id = :c AND product_id = :p",
|
||||
c=CUSTOMER, p=PROD_SUB)),
|
||||
"2026-09-04 14:00:00",
|
||||
str(TRADE_AT),
|
||||
)
|
||||
|
||||
|
||||
@@ -310,6 +337,7 @@ def assert_rebuild_lots(admin) -> None:
|
||||
|
||||
def main() -> int:
|
||||
admin = get_engine(settings.mysql_core_database, "admin")
|
||||
_init_dates(admin)
|
||||
try:
|
||||
with admin.connect() as conn:
|
||||
conn.execute(text("SELECT 1"))
|
||||
|
||||
Reference in New Issue
Block a user