Fix hedge option PnL match by parsing opened_at as Asia/Shanghai.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-21 10:21:52 +08:00
parent 67a09b1de8
commit 60ff45f098
3 changed files with 40 additions and 5 deletions
+8 -3
View File
@@ -1,13 +1,17 @@
"""对冲计划结算辅助:到期内在价值与期权腿收口.""" """对冲计划结算辅助:到期内在价值与期权腿收口."""
from __future__ import annotations from __future__ import annotations
import os
import time import time
from datetime import datetime, timezone from datetime import datetime
from typing import Any, Callable, Optional from typing import Any, Callable, Optional
from zoneinfo import ZoneInfo
from lib.exchange.okx_options_lib import normalize_option_exp_ms, resolve_option_close_from_history from lib.exchange.okx_options_lib import normalize_option_exp_ms, resolve_option_close_from_history
from lib.hedge_plan.hedge_plan_calc_lib import option_expiry_pnl from lib.hedge_plan.hedge_plan_calc_lib import option_expiry_pnl
_APP_TZ = ZoneInfo((os.getenv("APP_TIMEZONE") or os.getenv("TZ") or "Asia/Shanghai").strip() or "Asia/Shanghai")
def _sf(v: Any) -> Optional[float]: def _sf(v: Any) -> Optional[float]:
try: try:
@@ -64,14 +68,15 @@ def all_option_legs_expired(legs: list[dict[str, Any]], *, now_ms: Optional[int]
def _parse_opened_ms(raw: Any) -> Optional[int]: def _parse_opened_ms(raw: Any) -> Optional[int]:
"""墙钟开仓时间 → UTC ms.库内时间为业务时区(默认 Asia/Shanghai),不可当 UTC."""
if raw is None or raw == "": if raw is None or raw == "":
return None return None
s = str(raw).strip() s = str(raw).strip()
if not s: if not s:
return None return None
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%f"): for fmt, ln in (("%Y-%m-%d %H:%M:%S", 19), ("%Y-%m-%d %H:%M:%f", 26), ("%Y-%m-%d %H:%M", 16)):
try: try:
dt = datetime.strptime(s[:26], fmt).replace(tzinfo=timezone.utc) dt = datetime.strptime(s[:ln], fmt).replace(tzinfo=_APP_TZ)
return int(dt.timestamp() * 1000) return int(dt.timestamp() * 1000)
except ValueError: except ValueError:
continue continue
+7 -2
View File
@@ -1,13 +1,17 @@
"""期权持仓监控:浮盈翻倍微信提醒 + 平仓/到期状态同步.""" """期权持仓监控:浮盈翻倍微信提醒 + 平仓/到期状态同步."""
from __future__ import annotations from __future__ import annotations
import os
import sqlite3 import sqlite3
import time import time
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, Callable from typing import Any, Callable
from zoneinfo import ZoneInfo
from lib.exchange.okx_options_lib import normalize_option_exp_ms, resolve_option_close_from_history from lib.exchange.okx_options_lib import normalize_option_exp_ms, resolve_option_close_from_history
_APP_TZ = ZoneInfo((os.getenv("APP_TIMEZONE") or os.getenv("TZ") or "Asia/Shanghai").strip() or "Asia/Shanghai")
def _safe_float(v: Any) -> float | None: def _safe_float(v: Any) -> float | None:
if v is None: if v is None:
@@ -121,14 +125,15 @@ def run_options_profit_alerts(
def _created_at_ms(created_at: Any) -> int | None: def _created_at_ms(created_at: Any) -> int | None:
"""墙钟 created_at → UTC ms.库内时间为业务时区(默认 Asia/Shanghai),不可当 UTC."""
if not created_at: if not created_at:
return None return None
raw = str(created_at).strip() raw = str(created_at).strip()
if not raw: if not raw:
return None return None
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%f"): for fmt, ln in (("%Y-%m-%d %H:%M:%S", 19), ("%Y-%m-%d %H:%M:%f", 26), ("%Y-%m-%d %H:%M", 16)):
try: try:
dt = datetime.strptime(raw[:26], fmt).replace(tzinfo=timezone.utc) dt = datetime.strptime(raw[:ln], fmt).replace(tzinfo=_APP_TZ)
return int(dt.timestamp() * 1000) return int(dt.timestamp() * 1000)
except ValueError: except ValueError:
continue continue
+25
View File
@@ -6,6 +6,7 @@ import unittest
from lib.hedge_plan.hedge_plan_db import get_plan, init_hedge_plan_tables, insert_leg, insert_plan from lib.hedge_plan.hedge_plan_db import get_plan, init_hedge_plan_tables, insert_leg, insert_plan
from lib.hedge_plan.hedge_plan_settle_lib import ( from lib.hedge_plan.hedge_plan_settle_lib import (
_parse_opened_ms,
backfill_hedge_option_legs_realized_pnl, backfill_hedge_option_legs_realized_pnl,
resolve_option_leg_realized_pnl, resolve_option_leg_realized_pnl,
) )
@@ -32,6 +33,30 @@ class HedgeExchangePnlTest(unittest.TestCase):
self.assertEqual(src, "exchange") self.assertEqual(src, "exchange")
self.assertEqual(pnl, 12.0) self.assertEqual(pnl, 12.0)
def test_opened_at_beijing_wall_clock_not_utc(self):
# 北京 09:19 开仓 → UTC 01:19; 到期结算北京 16:00:31 = UTC 08:00:31
open_ms = _parse_opened_ms("2026-07-20 09:19:30")
self.assertEqual(open_ms, 1784510370000)
leg = {
"inst_id": "ETH-USD_UM-260720-1870-C",
"opened_at": "2026-07-20 09:19:30",
"premium": 6.9,
}
hist = [
{
"instId": "ETH-USD_UM-260720-1870-C",
"uTime": "1784534431512",
"realizedPnl": "-7.1812815",
"pnl": "-6.9",
"type": "2",
}
]
pnl, src = resolve_option_leg_realized_pnl(
leg=leg, hist_rows=hist, fallback=-6.9
)
self.assertEqual(src, "exchange")
self.assertAlmostEqual(pnl, -7.1813, places=4)
def test_backfill_updates_plan_total(self): def test_backfill_updates_plan_total(self):
conn = sqlite3.connect(":memory:") conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row