chore: finalize broker qty metadata consistency
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
30bcbe1d12
commit
f394f1560d
@@ -89,9 +89,9 @@
|
|||||||
"status": "inactive | pending_buy | pending_sell | holding | error",
|
"status": "inactive | pending_buy | pending_sell | holding | error",
|
||||||
"symbol": "str | null",
|
"symbol": "str | null",
|
||||||
"qty": "float | null", // holding: broker authority / unavailable=null
|
"qty": "float | null", // holding: broker authority / unavailable=null
|
||||||
"qty_source": "broker | unavailable", // holding-only metadata
|
"qty_source": "broker | unavailable | null", // non-holding=null
|
||||||
"broker_quantity_available": "bool", // holding-only metadata
|
"broker_quantity_available": "bool", // non-holding=false
|
||||||
"broker_quantity_captured_at": "ISO-8601 | null", // holding-only metadata
|
"broker_quantity_captured_at": "ISO-8601 | null", // non-holding=null
|
||||||
"slot_quantity": "float | null", // core daily_slot mirror (diagnostic)
|
"slot_quantity": "float | null", // core daily_slot mirror (diagnostic)
|
||||||
"asset_quantity": "float | null", // hub authority (REQ-062)
|
"asset_quantity": "float | null", // hub authority (REQ-062)
|
||||||
"avg_price": "float | null",
|
"avg_price": "float | null",
|
||||||
@@ -124,9 +124,9 @@
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `slot_id`, `status`, `avg_price`, `entry_price`, `forced_close_at`, `universe_detached` | slot (core) | 슬롯 row 직접 |
|
| `slot_id`, `status`, `avg_price`, `entry_price`, `forced_close_at`, `universe_detached` | slot (core) | 슬롯 row 직접 |
|
||||||
| `qty` | broker account snapshot | holding 슬롯에서만 authoritative. unavailable/stale 시 null |
|
| `qty` | broker account snapshot | holding 슬롯에서만 authoritative. unavailable/stale 시 null |
|
||||||
| `qty_source` | 파생(metadata) | holding 슬롯: `broker` \| `unavailable` |
|
| `qty_source` | 파생(metadata) | holding 슬롯: `broker` \| `unavailable`; non-holding은 null |
|
||||||
| `broker_quantity_available` | 파생(metadata) | holding 슬롯 broker qty freshness 통과 여부 |
|
| `broker_quantity_available` | 파생(metadata) | holding 슬롯 broker qty freshness 통과 여부; non-holding=false |
|
||||||
| `broker_quantity_captured_at` | broker account snapshot | holding 슬롯 broker qty capture 시각 |
|
| `broker_quantity_captured_at` | broker account snapshot | holding 슬롯 broker qty capture 시각; non-holding=null |
|
||||||
| `slot_quantity` | slot (core) | 기존 daily_slot.qty mirror(진단용) |
|
| `slot_quantity` | slot (core) | 기존 daily_slot.qty mirror(진단용) |
|
||||||
| `symbol`, `portfolio_id` | slot (core) | 자산 바인딩 |
|
| `symbol`, `portfolio_id` | slot (core) | 자산 바인딩 |
|
||||||
| `asset_quantity` | hub (SnapshotStore) | portfolio-hub 권위값 (REQ-062) |
|
| `asset_quantity` | hub (SnapshotStore) | portfolio-hub 권위값 (REQ-062) |
|
||||||
|
|||||||
@@ -222,12 +222,15 @@ class AccountSnapshotService:
|
|||||||
|
|
||||||
Unlike ``holdings_for_sell()``, this read path never performs a live broker
|
Unlike ``holdings_for_sell()``, this read path never performs a live broker
|
||||||
call and therefore cannot mutate broker state or mask snapshot staleness.
|
call and therefore cannot mutate broker state or mask snapshot staleness.
|
||||||
|
Freshness uses the same ``sell_max_age_sec`` policy (`self._sell_max_age`)
|
||||||
|
as pending-sell quantity protection so strategy reads and sell dispatch
|
||||||
|
share one stale-snapshot threshold.
|
||||||
"""
|
"""
|
||||||
snapshot = self._load_current()
|
snapshot = self._load_current()
|
||||||
self._age(snapshot.holdings_captured_at, self._sell_max_age)
|
|
||||||
captured_at = snapshot.holdings_captured_at
|
captured_at = snapshot.holdings_captured_at
|
||||||
if captured_at is None:
|
if captured_at is None:
|
||||||
raise AccountSnapshotUnavailable("not_ready")
|
raise AccountSnapshotUnavailable("not_ready")
|
||||||
|
self._age(captured_at, self._sell_max_age)
|
||||||
return list(snapshot.holdings), captured_at
|
return list(snapshot.holdings), captured_at
|
||||||
|
|
||||||
def buy_guard(
|
def buy_guard(
|
||||||
|
|||||||
@@ -273,18 +273,19 @@ def make_slots_rpc_handler(
|
|||||||
except AccountSnapshotUnavailable:
|
except AccountSnapshotUnavailable:
|
||||||
broker_qty_available = False
|
broker_qty_available = False
|
||||||
|
|
||||||
for row, slot in zip(slot_rows, slots):
|
for row, slot in zip(slot_rows, slots, strict=True):
|
||||||
row["slot_quantity"] = row.get("qty")
|
row["slot_quantity"] = row.get("qty")
|
||||||
|
row["qty_source"] = None
|
||||||
|
row["broker_quantity_available"] = False
|
||||||
|
row["broker_quantity_captured_at"] = None
|
||||||
if slot.status.value != "holding":
|
if slot.status.value != "holding":
|
||||||
continue
|
continue
|
||||||
row["broker_quantity_available"] = broker_qty_available
|
|
||||||
row["broker_quantity_captured_at"] = (
|
|
||||||
broker_qty_captured_at.isoformat() if broker_qty_captured_at is not None else None
|
|
||||||
)
|
|
||||||
if broker_qty_available:
|
if broker_qty_available:
|
||||||
broker_qty = int(broker_qty_by_symbol.get(str(slot.symbol), 0))
|
broker_qty = int(broker_qty_by_symbol.get(str(slot.symbol), 0))
|
||||||
row["qty"] = float(broker_qty)
|
row["qty"] = float(broker_qty)
|
||||||
row["qty_source"] = "broker"
|
row["qty_source"] = "broker"
|
||||||
|
row["broker_quantity_available"] = True
|
||||||
|
row["broker_quantity_captured_at"] = broker_qty_captured_at.isoformat()
|
||||||
else:
|
else:
|
||||||
row["qty"] = None
|
row["qty"] = None
|
||||||
row["qty_source"] = "unavailable"
|
row["qty_source"] = "unavailable"
|
||||||
@@ -296,7 +297,7 @@ def make_slots_rpc_handler(
|
|||||||
slot_ids = [s.slot_id for s in slots]
|
slot_ids = [s.slot_id for s in slots]
|
||||||
occ = {s.slot_id: getattr(s, "occupancy_seq", 0) for s in slots}
|
occ = {s.slot_id: getattr(s, "occupancy_seq", 0) for s in slots}
|
||||||
state_map = strategy_state_store.get_for_slots(slot_ids, occ)
|
state_map = strategy_state_store.get_for_slots(slot_ids, occ)
|
||||||
for row, s in zip(slot_rows, slots):
|
for row, s in zip(slot_rows, slots, strict=True):
|
||||||
entries = state_map.get(s.slot_id)
|
entries = state_map.get(s.slot_id)
|
||||||
if entries:
|
if entries:
|
||||||
row["strategy_state"] = {
|
row["strategy_state"] = {
|
||||||
|
|||||||
Reference in New Issue
Block a user