feat: add bounded KIS account read recovery

This commit is contained in:
jordan
2026-07-29 17:40:01 +09:00
parent 3dd8eefc92
commit 0266992238
29 changed files with 970 additions and 26 deletions
+2
View File
@@ -27,6 +27,8 @@ def test_account_split_and_mock_tr_ids():
assert b._tr_submit("buy") == "VTTC0012U"
assert b._tr_submit("sell") == "VTTC0011U"
assert b.name() == "kis-mock"
assert len(b.account_scope_hash()) == 64
assert "12345678" not in b.account_scope_hash()
def test_live_base_url_requires_allow_live():
+36
View File
@@ -1,4 +1,5 @@
from decimal import Decimal
from datetime import datetime, timezone
import pytest
@@ -14,6 +15,10 @@ from trex_core.application.broker_port import (
FillStatus,
OrderAck,
)
from trex_core.application.account_snapshot import (
AccountSnapshotService,
InMemoryAccountSnapshotStore,
)
class FakeBroker:
@@ -36,6 +41,12 @@ class FakeBroker:
def get_balance(self):
return self.balance
def account_scope_hash(self):
return "c" * 64
def get_available_cash(self, *, price_check_code="005930"):
return 1_000_000
def get_current_price(self, symbol, market="J"):
return Decimal("1")
@@ -90,3 +101,28 @@ def test_sell_cannot_reduce_below_strategy_owned_zero(tmp_path):
side="sell",
quantity=1,
)
def test_account_snapshot_keeps_manual_holdings_outside_strategy_scope(tmp_path):
ownership = StrategyOwnershipStore(tmp_path / "ownership.json")
ownership.apply_fill(
execution_id="buy-1",
symbol="005930",
side="buy",
quantity=2,
)
broker = OwnershipAwareBroker(
FakeBroker([BalanceItem("005930", 21), BalanceItem("000660", 1)]),
ownership,
)
snapshots = InMemoryAccountSnapshotStore()
service = AccountSnapshotService(
broker=broker,
store=snapshots,
clock=lambda: datetime(2026, 7, 29, tzinfo=timezone.utc),
)
assert service.refresh()
snapshot = snapshots.load("c" * 64)
assert snapshot is not None
assert snapshot.holdings == (BalanceItem("005930", 2),)
@@ -0,0 +1,37 @@
"""PostgreSQL account snapshot persistence contract."""
from __future__ import annotations
from datetime import datetime, timezone
from trex_core.adapters.persistence.account_snapshot import (
PostgreSQLAccountSnapshotStore,
)
from trex_core.application.account_snapshot import AccountSnapshot
from trex_core.application.broker_port import BalanceItem
def test_round_trip_keeps_independent_capture_times(pg_clean):
store = PostgreSQLAccountSnapshotStore(pg_clean)
holdings_at = datetime(2026, 7, 29, 0, 0, tzinfo=timezone.utc)
cash_at = datetime(2026, 7, 28, 23, 59, tzinfo=timezone.utc)
expected = AccountSnapshot(
account_hash="d" * 64,
holdings=(BalanceItem("005930", 2),),
holdings_captured_at=holdings_at,
available_cash=300_000,
cash_captured_at=cash_at,
)
store.save(expected)
assert store.load(expected.account_hash) == expected
def test_refresh_guard_is_account_scoped_and_released(pg_clean):
store = PostgreSQLAccountSnapshotStore(pg_clean)
with store.refresh_guard("e" * 64) as first:
assert first is True
with store.refresh_guard("e" * 64) as second:
assert second is False
with store.refresh_guard("e" * 64) as after_release:
assert after_release is True
@@ -18,6 +18,7 @@ _EXPECTED_TABLES = {
"intent_log",
"ledger_outbox", # BAT-006 / REQ-019
"snapshot_pause", # BAT-008 / REQ-032
"broker_account_snapshot", # dcy-041 / GitHub #45
}