""" Action model — compact menu for simultaneous-move tree search. Bad: enumerate every price tick x every quantity x every TTL x every TIF. Good: 8-24 meaningful actions per player, 3-12 per counterparty role. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Mapping, Optional, Tuple from malkhut.state import ActionKind, AgentRole, OrderType, Side @dataclass(frozen=True, slots=True) class FulfilmentAction: """One atomic action candidate.""" kind: ActionKind side: Optional[Side] order_type: Optional[OrderType] price_ticks_from_best: int qty_fraction: float ttl_ms: int cancel_order_id: Optional[str] = None reduce_only: bool = False post_only: bool = False metadata: Mapping[str, Any] = field(default_factory=dict) @dataclass(frozen=True, slots=True) class CounterpartyAction: """Adversarially useful aggregate actions that alter book/fill outcomes.""" role: AgentRole kind: ActionKind side: Optional[Side] price_ticks_from_best: int qty_fraction_of_top: float toxicity: float = 0.0 metadata: Mapping[str, Any] = field(default_factory=dict) JointAction = Tuple[Any, ...] # (our_action, cp_action_1, cp_action_2, ...) @dataclass(frozen=True, slots=True) class PlannedPolicy: """Output of the planner: distribution over actions + selected action.""" actions: Tuple[FulfilmentAction, ...] probabilities: Tuple[float, ...] selected_action: FulfilmentAction diagnostics: Mapping[str, Any] @dataclass(frozen=True, slots=True) class RiskDecision: approved: bool action: Optional[FulfilmentAction] reason: str adjusted: bool = False