"""Slot domain data classes (REQ-001, REQ-002).""" from __future__ import annotations from dataclasses import dataclass, field from datetime import date, datetime from decimal import Decimal from typing import Optional from uuid import UUID from trex_core.domain.slot.state import SlotState @dataclass class Slot: """Single daily_slot row. Matches the slot column contract recorded in dcy-001 부록 A and docs/data-model.md (trex-core stores this in-memory for now; a future persistence layer must satisfy the same contract). """ slot_id: UUID trading_date: date status: SlotState symbol: Optional[str] = None qty: Optional[float] = None avg_price: Optional[float] = None # dcy-012 / REQ-002: side-agnostic "current pending order limit price" # (renamed from entry_price). Set on pending entry (buy/sell limit), cleared # on pending exit (→HOLDING, →CLOSED) so a buy limit never leaks into a sell # order (issue #10). None => market order at dispatch. limit_price: Optional[float] = None allocated_capital: Optional[float] = None order_id: Optional[str] = None strategy_id: Optional[str] = None broker_order_id: Optional[str] = None # BAT-003 / REQ-011 (trex-orbit lineage) portfolio_id: Optional[str] = None # BAT-006 / REQ-025 (trex-orbit lineage) sell_strategy_id: Optional[str] = None # BAT-008 / REQ-047 (trex-orbit lineage) forced_close_at: Optional[datetime] = None # BAT-014 / REQ-066 (trex-orbit lineage) stop_loss_price: Optional[Decimal] = None # REQ-035 / BAT-010 take_profit_price: Optional[Decimal] = None # REQ-035 / BAT-010 # dcy-019 / REQ-002 (BAT-025): slot-scoped cumulative entry count. +1 on each # allocation transition (idle/closed → pending_buy); drives admission slot # selection (lowest entry_count first). Distinct from the symbol-scoped # daily_symbol_entries counter (REQ-002 AC-3) — different key/purpose. entry_count: int = 0 created_at: Optional[datetime] = None updated_at: Optional[datetime] = None # Canonical column contract for `Slot` (dcy-001 OQ-2 / FR-5). # test_contract.py asserts this matches `dataclasses.fields(Slot)` so any # field add/remove fails loudly. Type mapping lives in the docs (dcy-001 부록 A # / docs/data-model.md), not here — names/presence only. EXPECTED_SLOT_FIELDS: frozenset[str] = frozenset({ "slot_id", "trading_date", "status", "symbol", "qty", "avg_price", "limit_price", "allocated_capital", "order_id", "strategy_id", "broker_order_id", "portfolio_id", "sell_strategy_id", "forced_close_at", "stop_loss_price", # REQ-035 "take_profit_price", # REQ-035 "entry_count", # dcy-019 / REQ-002 (BAT-025) "created_at", "updated_at", }) @dataclass(frozen=True) class SlotCapacity: """Snapshot of slot capacity returned by compute_slot_capacity (REQ-002 AC-6).""" idle_count: int holding_count: int pending_count: int total_slots: int scarcity_ratio: float @dataclass(frozen=True) class SlotTransitionRecord: """One append-only row in slot_transition_log (REQ-001, REQ-002).""" slot_id: UUID from_state: SlotState to_state: SlotState ts: datetime reason: str actor: str extra: dict = field(default_factory=dict)