Adds OperatorCommandEvent + a closed command vocabulary (flatten_all, flatten_symbol, pause_entries, resume_entries). POST /api/command emits it (token-required: 503 when DASHBOARD_TOKEN unset — commands never run unauthenticated); the live loop's new 5s "command" step consumes them via OperatorCommandConsumer with three replay-safety layers (in-session nonce set, persisted operator_command_done reload, 10-min staleness window). flatten_all routes through close_session DIRECTLY (never step_eod_flatten) so it cannot trip plan 003's daily-restart exit. Pause is checked at the top of BOTH request_approvals_for_ready_signals AND submit_breakout_now (missing either leaks pause on that path). qabot gains an authorized-chat-only /flatten slash command (prose "flatten" still refuses via _ACTION_WORDS). Snapshot exposes broker truth + command_bus + a defensive experiment ledger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
404 lines
16 KiB
Python
404 lines
16 KiB
Python
"""Event schema definitions for Milestone 2.
|
|
|
|
All canonical event types that can be emitted by replay, paper, and live modes.
|
|
"""
|
|
|
|
from enum import Enum
|
|
from datetime import datetime
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class EventMode(str, Enum):
|
|
"""Runtime modes for events."""
|
|
|
|
REPLAY = "replay"
|
|
PAPER = "paper"
|
|
LIVE = "live"
|
|
|
|
|
|
class EventType(str, Enum):
|
|
"""Canonical event types."""
|
|
|
|
SYMBOL_DISCOVERED = "symbol_discovered"
|
|
SYMBOL_STATE_CHANGED = "symbol_state_changed"
|
|
CRITERIA_EVALUATED = "criteria_evaluated"
|
|
SIGNAL_READY = "signal_ready"
|
|
SIGNAL_BLOCKED = "signal_blocked"
|
|
ORDER_SUBMITTED = "order_submitted"
|
|
ORDER_APPROVAL_REQUESTED = "order_approval_requested"
|
|
ORDER_APPROVED = "order_approved"
|
|
ORDER_REJECTED = "order_rejected"
|
|
ORDER_FILLED = "order_filled"
|
|
ORDER_CANCELLED = "order_cancelled"
|
|
POSITION_OPENED = "position_opened"
|
|
POSITION_CLOSED = "position_closed"
|
|
ACCOUNT_SUMMARY_UPDATED = "account_summary_updated"
|
|
ACCOUNT_POSITIONS_UPDATED = "account_positions_updated"
|
|
ACCOUNT_ORDERS_UPDATED = "account_orders_updated"
|
|
RISK_RULE_TRIGGERED = "risk_rule_triggered"
|
|
BROKER_HEALTH_CHANGED = "broker_health_changed"
|
|
SOURCE_HEALTH_CHANGED = "source_health_changed"
|
|
SESSION_SUMMARY = "session_summary"
|
|
MODULE_TICK = "module_tick"
|
|
DATA_VALIDATION = "data_validation"
|
|
SYMBOL_EVALUATION_DETAIL = "symbol_evaluation_detail"
|
|
ENTRY_GATE_EVALUATED = "entry_gate_evaluated"
|
|
EXPERIMENT_VERDICT = "experiment_verdict"
|
|
OPERATOR_COMMAND = "operator_command"
|
|
OPERATOR_COMMAND_DONE = "operator_command_done"
|
|
|
|
|
|
# The complete operator command vocabulary — the ONLY commands the bus accepts.
|
|
# Deliberately closed: adding a parameterized command must redo the audit-trail +
|
|
# replay-safety analysis (see plan 007 maintenance notes).
|
|
OPERATOR_COMMANDS: frozenset[str] = frozenset(
|
|
{"flatten_all", "flatten_symbol", "pause_entries", "resume_entries"}
|
|
)
|
|
|
|
|
|
class BaseEvent(BaseModel):
|
|
"""Base event with common fields."""
|
|
|
|
timestamp: datetime = Field(description="Event timestamp")
|
|
mode: EventMode = Field(description="Runtime mode (replay/paper/live)")
|
|
event_type: EventType = Field(description="Event type identifier")
|
|
correlation_id: str | None = Field(
|
|
default=None, description="Correlation ID for causal chain"
|
|
)
|
|
message: str = Field(description="Human-readable message")
|
|
payload: dict = Field(default_factory=dict, description="Structured event data")
|
|
|
|
|
|
class SymbolDiscoveredEvent(BaseEvent):
|
|
"""Event when a symbol is discovered."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.SYMBOL_DISCOVERED, description="Event type identifier"
|
|
)
|
|
symbol: str = Field(description="Ticker symbol")
|
|
symbol_data: dict = Field(default_factory=dict, description="Symbol metadata")
|
|
|
|
|
|
class SymbolStateChangedEvent(BaseEvent):
|
|
"""Event when symbol state changes."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.SYMBOL_STATE_CHANGED, description="Event type identifier"
|
|
)
|
|
symbol: str = Field(description="Ticker symbol")
|
|
previous_state: str | None = Field(default=None, description="Previous state")
|
|
new_state: str = Field(description="New state")
|
|
state_reason: str | None = Field(
|
|
default=None, description="Reason for state change"
|
|
)
|
|
|
|
|
|
class CriteriaEvaluatedEvent(BaseEvent):
|
|
"""Event when criteria are evaluated."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.CRITERIA_EVALUATED, description="Event type identifier"
|
|
)
|
|
symbol: str = Field(description="Ticker symbol")
|
|
criteria_results: dict = Field(description="Criteria evaluation results")
|
|
total_criteria: int = Field(description="Total number of criteria")
|
|
passed_criteria: int = Field(description="Number of criteria passed")
|
|
success_score_pct: float = Field(description="Success score percentage")
|
|
|
|
|
|
class SignalReadyEvent(BaseEvent):
|
|
"""Event when signal is ready."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.SIGNAL_READY, description="Event type identifier"
|
|
)
|
|
symbol: str = Field(description="Ticker symbol")
|
|
signal_type: str = Field(description="Type of signal (e.g., first_pullback)")
|
|
confidence: float = Field(description="Signal confidence score")
|
|
signal_data: dict = Field(default_factory=dict, description="Execution metadata")
|
|
|
|
|
|
class SignalBlockedEvent(BaseEvent):
|
|
"""Event when signal is blocked."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.SIGNAL_BLOCKED, description="Event type identifier"
|
|
)
|
|
symbol: str = Field(description="Ticker symbol")
|
|
blocking_reason: str = Field(description="Reason signal is blocked")
|
|
unmet_criteria: list[str] = Field(
|
|
default_factory=list, description="List of unmet criteria"
|
|
)
|
|
|
|
|
|
class OrderSubmittedEvent(BaseEvent):
|
|
"""Event when order is submitted."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.ORDER_SUBMITTED, description="Event type identifier"
|
|
)
|
|
order_id: str = Field(description="Order ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
side: str = Field(description="Buy or sell")
|
|
quantity: int = Field(description="Order quantity")
|
|
price: float = Field(description="Order price")
|
|
|
|
|
|
class OrderApprovalRequestedEvent(BaseEvent):
|
|
"""Event when order approval is requested."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.ORDER_APPROVAL_REQUESTED, description="Event type identifier"
|
|
)
|
|
order_id: str = Field(description="Order ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
requested_by: str = Field(description="Who requested approval")
|
|
approval_mode: str = Field(description="Approval mode (auto/manual)")
|
|
execution_mode: str = Field(description="Target execution mode")
|
|
execution_request: dict = Field(
|
|
default_factory=dict, description="Pending execution payload"
|
|
)
|
|
|
|
|
|
class OrderApprovedEvent(BaseEvent):
|
|
"""Event when order is approved."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.ORDER_APPROVED, description="Event type identifier"
|
|
)
|
|
order_id: str = Field(description="Order ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
approved_by: str = Field(description="Who approved the order")
|
|
approval_notes: str | None = Field(default=None, description="Approval notes")
|
|
|
|
|
|
class OrderRejectedEvent(BaseEvent):
|
|
"""Event when order is rejected."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.ORDER_REJECTED, description="Event type identifier"
|
|
)
|
|
order_id: str = Field(description="Order ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
rejected_by: str = Field(description="Who rejected the order")
|
|
rejection_reason: str = Field(description="Reason for rejection")
|
|
|
|
|
|
class OrderFilledEvent(BaseEvent):
|
|
"""Event when order is filled."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.ORDER_FILLED, description="Event type identifier"
|
|
)
|
|
order_id: str = Field(description="Order ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
fill_price: float = Field(description="Fill price")
|
|
fill_quantity: int = Field(description="Filled quantity")
|
|
slippage: float | None = Field(default=None, description="Slippage amount")
|
|
|
|
|
|
class OrderCancelledEvent(BaseEvent):
|
|
event_type: EventType = Field(
|
|
default=EventType.ORDER_CANCELLED, description="Event type identifier"
|
|
)
|
|
order_id: str = Field(description="Order ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
cancel_reason: str | None = Field(default=None, description="Cancel reason")
|
|
|
|
|
|
class PositionOpenedEvent(BaseEvent):
|
|
"""Event when position is opened."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.POSITION_OPENED, description="Event type identifier"
|
|
)
|
|
position_id: str = Field(description="Position ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
entry_price: float = Field(description="Entry price")
|
|
quantity: int = Field(description="Position quantity")
|
|
stop_loss_price: float = Field(description="Stop loss price")
|
|
|
|
|
|
class PositionClosedEvent(BaseEvent):
|
|
"""Event when position is closed."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.POSITION_CLOSED, description="Event type identifier"
|
|
)
|
|
position_id: str = Field(description="Position ID")
|
|
symbol: str = Field(description="Ticker symbol")
|
|
exit_price: float = Field(description="Exit price")
|
|
exit_reason: str = Field(description="Reason for position close")
|
|
realized_pnl: float = Field(description="Realized profit/loss")
|
|
# entry context for the trade journal's R-multiple (query_session_pnl reads
|
|
# these from the payload; optional so older/other emitters stay valid).
|
|
entry_price: float = Field(default=0.0, description="Average entry price")
|
|
stop_loss_price: float | None = Field(
|
|
default=None, description="Protective stop level at entry (R baseline)")
|
|
side: str = Field(default="buy", description="Entry side: buy=long, sell=short")
|
|
quantity: float = Field(default=0.0, description="Shares closed")
|
|
|
|
|
|
class AccountSummaryUpdatedEvent(BaseEvent):
|
|
event_type: EventType = Field(
|
|
default=EventType.ACCOUNT_SUMMARY_UPDATED,
|
|
description="Event type identifier",
|
|
)
|
|
broker_name: str = Field(description="Broker identifier")
|
|
account_id: str = Field(description="Broker account identifier")
|
|
account_desc: str = Field(description="Display name for account")
|
|
total_equity: float = Field(description="Total account equity")
|
|
cash_balance: float = Field(description="Available cash balance")
|
|
buying_power: float = Field(description="Buying power")
|
|
net_liquidating_value: float = Field(description="Net liquidation value")
|
|
last_equity: float = Field(
|
|
default=0.0, description="Prior-session close equity (day P&L baseline)"
|
|
)
|
|
|
|
|
|
class AccountPositionsUpdatedEvent(BaseEvent):
|
|
event_type: EventType = Field(
|
|
default=EventType.ACCOUNT_POSITIONS_UPDATED,
|
|
description="Event type identifier",
|
|
)
|
|
broker_name: str = Field(description="Broker identifier")
|
|
account_id: str = Field(description="Broker account identifier")
|
|
positions: list[dict] = Field(description="Serialized account positions")
|
|
|
|
|
|
class AccountOrdersUpdatedEvent(BaseEvent):
|
|
event_type: EventType = Field(
|
|
default=EventType.ACCOUNT_ORDERS_UPDATED,
|
|
description="Event type identifier",
|
|
)
|
|
broker_name: str = Field(description="Broker identifier")
|
|
account_id: str = Field(description="Broker account identifier")
|
|
orders: list[dict] = Field(description="Serialized account orders")
|
|
|
|
|
|
class RiskRuleTriggeredEvent(BaseEvent):
|
|
"""Event when risk rule is triggered."""
|
|
|
|
event_type: EventType = EventType.RISK_RULE_TRIGGERED
|
|
rule_type: str = Field(description="Type of risk rule")
|
|
rule_value: float = Field(description="Value that triggered the rule")
|
|
current_state: dict = Field(description="Current system state when triggered")
|
|
action_taken: str = Field(description="Action taken in response")
|
|
|
|
|
|
class EntryGateEvaluatedEvent(BaseEvent):
|
|
"""Decision receipt for one entry-gate evaluation (plan 005). One event per
|
|
final pass/reject on either path — the raw material for 'why didn't it trade
|
|
X?' queries and plan 006 replay. High-volume diagnostic: purged after 14 days.
|
|
``gates`` maps each evaluated gate -> {value, threshold, outcome}."""
|
|
|
|
event_type: EventType = EventType.ENTRY_GATE_EVALUATED
|
|
symbol: str = Field(description="Ticker symbol")
|
|
source: str = Field(description="Entry path: 'auto' (slow) or 'fast'")
|
|
passed: bool = Field(description="Whether all gates passed")
|
|
reject_reason: str | None = Field(
|
|
default=None, description="First failing gate, or None on a pass"
|
|
)
|
|
gates: dict = Field(
|
|
default_factory=dict, description="Per-gate {value, threshold, outcome}"
|
|
)
|
|
|
|
|
|
class OperatorCommandEvent(BaseEvent):
|
|
"""A guarded operator kill-switch command (plan 007). Emitted by the dashboard
|
|
command endpoint or the qabot ``/flatten``; consumed by the live loop's 5s
|
|
guard, which executes it through the existing protective-close helpers and
|
|
echoes an ``operator_command_done`` event (same schema, ``result`` set) so a
|
|
restart never re-runs it. ``nonce`` (a uuid) is the idempotency key."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.OPERATOR_COMMAND, description="Event type identifier"
|
|
)
|
|
command: str = Field(description="One of OPERATOR_COMMANDS")
|
|
symbol: str | None = Field(default=None, description="Target symbol (flatten_symbol only)")
|
|
requested_by: str = Field(default="operator", description="Who issued the command")
|
|
nonce: str = Field(description="Idempotency key (uuid) — deduped by the consumer")
|
|
result: str | None = Field(
|
|
default=None, description="Set on the operator_command_done echo (ok / error text)"
|
|
)
|
|
|
|
|
|
class BrokerHealthChangedEvent(BaseEvent):
|
|
"""Event when broker health changes."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.BROKER_HEALTH_CHANGED, description="Event type identifier"
|
|
)
|
|
broker_name: str = Field(description="Broker identifier")
|
|
previous_health: str = Field(description="Previous health status")
|
|
new_health: str = Field(description="New health status")
|
|
health_reason: str | None = Field(
|
|
default=None, description="Reason for health change"
|
|
)
|
|
|
|
|
|
class SourceHealthChangedEvent(BaseEvent):
|
|
"""Event when source health changes."""
|
|
|
|
event_type: EventType = EventType.SOURCE_HEALTH_CHANGED
|
|
source_name: str = Field(description="Source identifier")
|
|
source_type: str = Field(description="Source type (broker/market data/news)")
|
|
previous_health: str = Field(description="Previous health status")
|
|
new_health: str = Field(description="New health status")
|
|
health_reason: str | None = Field(
|
|
default=None, description="Reason for health change"
|
|
)
|
|
|
|
|
|
class SessionSummaryEvent(BaseEvent):
|
|
event_type: EventType = Field(default=EventType.SESSION_SUMMARY)
|
|
session_id: str = Field(description="Unique session identifier")
|
|
status: str = Field(description="Session status")
|
|
summary_data: dict = Field(default_factory=dict, description="Session summary data")
|
|
|
|
|
|
class ModuleTickEvent(BaseEvent):
|
|
"""Event for module execution telemetry."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.MODULE_TICK, description="Event type identifier"
|
|
)
|
|
module: str = Field(description="Module name (ingestion, research, evaluation, etc.)")
|
|
stage: str = Field(description="Stage (started, processing, completed, failed)")
|
|
duration_ms: float = Field(description="Execution duration in milliseconds")
|
|
input_count: int = Field(description="Number of inputs processed")
|
|
output_count: int = Field(description="Number of outputs produced")
|
|
metrics: dict = Field(default_factory=dict, description="Custom module metrics")
|
|
errors: list[dict] = Field(default_factory=list, description="Any errors that occurred")
|
|
|
|
|
|
class DataValidationEvent(BaseEvent):
|
|
"""Event for data quality checkpoints."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.DATA_VALIDATION, description="Event type identifier"
|
|
)
|
|
module: str = Field(description="Module performing validation")
|
|
validation_type: str = Field(
|
|
description="Type of validation (schema, checksum, business_rules)"
|
|
)
|
|
valid: bool = Field(description="Whether validation passed")
|
|
details: dict = Field(default_factory=dict, description="Validation details")
|
|
|
|
|
|
class SymbolEvaluationDetailEvent(BaseEvent):
|
|
"""Event for detailed symbol evaluation metrics at each stage."""
|
|
|
|
event_type: EventType = Field(
|
|
default=EventType.SYMBOL_EVALUATION_DETAIL, description="Event type identifier"
|
|
)
|
|
symbol: str = Field(description="Ticker symbol")
|
|
stage: str = Field(
|
|
description="Evaluation stage (bars_loaded, gap_checked, rvol_calculated, etc.)"
|
|
)
|
|
metrics: dict = Field(description="Metrics at this stage")
|
|
passed: bool = Field(description="Whether this stage passed")
|
|
blocking_reason: str | None = Field(
|
|
default=None, description="If failed, why it failed"
|
|
)
|