feat: persist unmanaged position lifecycle state
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
ae2e598209
commit
fe51d211d9
@@ -11,6 +11,8 @@ Subcommands:
|
||||
cancel <slot_id> — cancel a pending order (control RPC; safeguarded)
|
||||
sync — sync slot qty to broker ground truth (direct-DB; REQ-105)
|
||||
verify — re-check C-case reconcile-warning (direct-DB; REQ-106)
|
||||
unmanaged list — query durable unmanaged-position lifecycle state
|
||||
unmanaged record — record explicit operator disposition + evidence reference
|
||||
ledger backfill — evidence-backed historical ledger recovery
|
||||
ledger skips list — query durable skipped-ledger records
|
||||
ledger skips resolve — explicitly resolve a skipped-ledger record
|
||||
@@ -640,6 +642,37 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
)
|
||||
_add_common(p_verify)
|
||||
|
||||
p_unmanaged = sub.add_parser(
|
||||
"unmanaged",
|
||||
help="List or annotate durable unmanaged broker positions (direct-DB)",
|
||||
)
|
||||
p_unmanaged_sub = p_unmanaged.add_subparsers(dest="unmanaged_cmd", required=True)
|
||||
p_unmanaged_list = p_unmanaged_sub.add_parser(
|
||||
"list",
|
||||
help="List unmanaged broker positions with lifecycle state",
|
||||
)
|
||||
p_unmanaged_list.add_argument(
|
||||
"--status",
|
||||
choices=("active", "resolved", "all"),
|
||||
default="active",
|
||||
)
|
||||
p_unmanaged_list.add_argument("--symbol", default=None)
|
||||
_add_common(p_unmanaged_list)
|
||||
|
||||
p_unmanaged_record = p_unmanaged_sub.add_parser(
|
||||
"record",
|
||||
help="Record an explicit operator disposition for an unmanaged position",
|
||||
)
|
||||
p_unmanaged_record.add_argument("symbol")
|
||||
p_unmanaged_record.add_argument(
|
||||
"action",
|
||||
choices=("investigating", "approved_exception", "managed_inclusion"),
|
||||
)
|
||||
p_unmanaged_record.add_argument("--evidence-ref", required=True)
|
||||
p_unmanaged_record.add_argument("--note", default="")
|
||||
p_unmanaged_record.add_argument("--actor", default="operator")
|
||||
_add_common(p_unmanaged_record)
|
||||
|
||||
p_ledger = sub.add_parser(
|
||||
"ledger",
|
||||
help="Ledger operator tools (direct-DB enqueue + immediate writer path)",
|
||||
@@ -971,6 +1004,73 @@ def _cmd_verify(args, cfg) -> int:
|
||||
return exit_code
|
||||
|
||||
|
||||
def _cmd_unmanaged_list(args, cfg) -> int:
|
||||
from trex_core.adapters.portfolio.unmanaged_position_store import (
|
||||
PostgreSQLUnmanagedPositionStore,
|
||||
)
|
||||
|
||||
instance = _resolve_instance(args)
|
||||
states = PostgreSQLUnmanagedPositionStore(cfg.dsn).list_states(
|
||||
instance_key=instance,
|
||||
lifecycle_state=args.status,
|
||||
symbol=args.symbol,
|
||||
)
|
||||
payload = {
|
||||
"instance": instance,
|
||||
"status": args.status,
|
||||
"count": len(states),
|
||||
"states": states,
|
||||
}
|
||||
if args.json:
|
||||
_emit(payload, as_json=True)
|
||||
else:
|
||||
print(
|
||||
f"instance={instance} status={args.status} count={len(states)} "
|
||||
f"symbol={args.symbol or '*'}"
|
||||
)
|
||||
for state in states:
|
||||
print(
|
||||
f" {state['symbol']}\tqty={state['quantity']}\t"
|
||||
f"lifecycle={state['lifecycle_state']}\t"
|
||||
f"first_seen={state['first_seen_at']}\tlast_seen={state['last_seen_at']}\t"
|
||||
f"resolved_at={state['resolved_at']}\t"
|
||||
f"operator_action={state.get('operator_action') or '-'}\t"
|
||||
f"evidence_ref={state.get('evidence_ref') or '-'}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_unmanaged_record(args, cfg) -> int:
|
||||
from trex_core.adapters.portfolio.unmanaged_position_store import (
|
||||
PostgreSQLUnmanagedPositionStore,
|
||||
)
|
||||
|
||||
instance = _resolve_instance(args)
|
||||
state = PostgreSQLUnmanagedPositionStore(cfg.dsn).record_operator_action(
|
||||
instance_key=instance,
|
||||
symbol=args.symbol,
|
||||
action=args.action,
|
||||
evidence_ref=args.evidence_ref,
|
||||
actor=args.actor,
|
||||
note=args.note,
|
||||
)
|
||||
if state is None:
|
||||
_fail(
|
||||
f"unmanaged position not found: instance={instance} symbol={args.symbol}",
|
||||
args.json,
|
||||
)
|
||||
return 1
|
||||
payload = {"result": "recorded", "state": state}
|
||||
if args.json:
|
||||
_emit(payload, as_json=True)
|
||||
else:
|
||||
print(
|
||||
f"recorded unmanaged position instance={instance} symbol={args.symbol} "
|
||||
f"action={args.action} evidence_ref={args.evidence_ref}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_report(args, cfg) -> int:
|
||||
"""REQ-088/110/111: holdings / today's fills / WAC realized PnL / pending — direct-DB."""
|
||||
import psycopg
|
||||
@@ -1434,6 +1534,10 @@ def main(argv: list[str] | None = None, *, rpc=None, confirm=None) -> int:
|
||||
# REQ-106 (BAT-051): verify — direct-DB, C-case re-check (read-centric).
|
||||
if args.cmd == "verify":
|
||||
return _cmd_verify(args, cfg)
|
||||
if args.cmd == "unmanaged" and getattr(args, "unmanaged_cmd", None) == "list":
|
||||
return _cmd_unmanaged_list(args, cfg)
|
||||
if args.cmd == "unmanaged" and getattr(args, "unmanaged_cmd", None) == "record":
|
||||
return _cmd_unmanaged_record(args, cfg)
|
||||
|
||||
if args.cmd == "ledger" and getattr(args, "ledger_cmd", None) == "backfill":
|
||||
return _cmd_ledger_backfill(args, cfg, confirm)
|
||||
|
||||
Reference in New Issue
Block a user