mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-05-31 16:43:36 +00:00
shock: defining new lab environment and formulation
This commit is contained in:
297
lab/outlet/protocols.py
Normal file
297
lab/outlet/protocols.py
Normal file
@@ -0,0 +1,297 @@
|
||||
"""
|
||||
Protocol definitions for pluggable simulator components.
|
||||
|
||||
This module defines the interfaces (Protocols) that allow swapping different
|
||||
implementations for each stage of the Quote -> Arrival -> Execution -> Position
|
||||
pipeline. All protocols use structural subtyping (duck typing).
|
||||
|
||||
Protocols:
|
||||
Mechanism: How quotes translate to executions (posted price, two-sided, auction)
|
||||
ArrivalModel: How opportunities arrive (Poisson, Hawkes, sessions)
|
||||
ExecutionModel: Acceptance probability given quote (elasticity, intensity)
|
||||
PositionModel: Inventory/position management and censorship
|
||||
MarketModel: Competitor/market dynamics
|
||||
ObservationBuilder: Constructs agent observations with censoring
|
||||
Objective: Computes reward from metrics
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Protocol, Any, TYPE_CHECKING
|
||||
import numpy as np
|
||||
if TYPE_CHECKING:
|
||||
from .types import (Quote, Opportunity, Execution, InstrumentSet, StepLogs,
|
||||
StepMetrics, HiddenState, Observation, MarketState)
|
||||
from .constants import LogLevel
|
||||
|
||||
class Mechanism(Protocol):
|
||||
"""Defines how quotes translate to executions.
|
||||
|
||||
The Mechanism is the core abstraction that differentiates pricing domains:
|
||||
- PostedPrice: single price, buyer decides to purchase or not
|
||||
- TwoSided: bid/ask spread, execution depends on distance from mid
|
||||
- Auction: reserve price affects win probability and clearing price
|
||||
|
||||
Methods:
|
||||
apply_quote: Enforce constraints and return valid quote
|
||||
process_opportunity: Determine execution given opportunity and quote
|
||||
"""
|
||||
def apply_quote(self, quote: Quote, instruments: InstrumentSet,
|
||||
rng: np.random.Generator) -> Quote:
|
||||
"""Apply mechanism-specific constraints to a quote.
|
||||
|
||||
Args:
|
||||
quote: Raw quote from policy
|
||||
instruments: Current instrument set with costs/refs
|
||||
rng: Random generator for stochastic constraints
|
||||
|
||||
Returns:
|
||||
Constrained quote satisfying mechanism rules (min margin, max delta, etc.)
|
||||
"""
|
||||
...
|
||||
|
||||
def process_opportunity(self, opp: Opportunity, quote: Quote,
|
||||
instruments: InstrumentSet, market: MarketState | None,
|
||||
rng: np.random.Generator) -> Execution | None:
|
||||
"""Process an opportunity against the current quote.
|
||||
|
||||
Args:
|
||||
opp: Incoming opportunity (session, order, request)
|
||||
quote: Current posted quote
|
||||
instruments: Instrument set
|
||||
market: Current market state (competitor prices, mid-prices)
|
||||
rng: Random generator
|
||||
|
||||
Returns:
|
||||
Execution if opportunity converts, None otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
class ArrivalModel(Protocol):
|
||||
"""Generates opportunities (demand arrivals) for each step.
|
||||
|
||||
Different arrival models capture different demand dynamics:
|
||||
- Poisson: constant rate, memoryless
|
||||
- Hawkes: self-exciting, clustered arrivals
|
||||
- Session: retail browsing with multi-product views
|
||||
|
||||
Methods:
|
||||
sample: Generate opportunities for a time interval
|
||||
"""
|
||||
def sample(self, t: float, dt: float, instruments: InstrumentSet,
|
||||
market: MarketState | None, hidden: HiddenState,
|
||||
rng: np.random.Generator) -> list[Opportunity]:
|
||||
"""Sample opportunities for time interval [t, t+dt).
|
||||
|
||||
Args:
|
||||
t: Current time
|
||||
dt: Time interval length
|
||||
instruments: Available instruments
|
||||
market: Current market state
|
||||
hidden: Hidden state (contains demand intensity, contamination)
|
||||
rng: Random generator
|
||||
|
||||
Returns:
|
||||
List of opportunities arriving in this interval
|
||||
"""
|
||||
...
|
||||
|
||||
class ExecutionModel(Protocol):
|
||||
"""Computes acceptance/execution probability given quote and context.
|
||||
|
||||
Different models capture different demand responses:
|
||||
- Elasticity: price sensitivity with competitor cross-effects
|
||||
- Intensity: distance-based fill probability (market making)
|
||||
- Logit: discrete choice model
|
||||
|
||||
Methods:
|
||||
prob: Compute acceptance probability
|
||||
uncensor: Estimate true demand from censored fills
|
||||
"""
|
||||
def prob(self, opp: Opportunity, quote: Quote, instruments: InstrumentSet,
|
||||
market: MarketState | None, rng: np.random.Generator) -> float:
|
||||
"""Compute probability that opportunity accepts the quote.
|
||||
|
||||
Args:
|
||||
opp: Opportunity to evaluate
|
||||
quote: Current quote
|
||||
instruments: Instrument set
|
||||
market: Market state (competitor prices affect cross-elasticity)
|
||||
rng: Random generator
|
||||
|
||||
Returns:
|
||||
Probability in [0, 1] that opportunity executes
|
||||
"""
|
||||
...
|
||||
|
||||
def uncensor(self, fills: np.ndarray, instruments: InstrumentSet,
|
||||
context: dict[str, Any] | None = None) -> np.ndarray:
|
||||
"""Estimate true demand from censored fills.
|
||||
|
||||
Used for demand estimation research under inventory censorship.
|
||||
|
||||
Args:
|
||||
fills: Observed (censored) fill counts
|
||||
instruments: Instrument set
|
||||
context: Additional context (exposures, prices shown)
|
||||
|
||||
Returns:
|
||||
Estimated true demand counts
|
||||
"""
|
||||
...
|
||||
|
||||
class PositionModel(Protocol):
|
||||
"""Manages inventory (retail) or position (finance).
|
||||
|
||||
Handles:
|
||||
- Position constraints and censorship
|
||||
- Holding costs (retail) or inventory risk (finance)
|
||||
- Replenishment and order receipt
|
||||
|
||||
Methods:
|
||||
reset: Initialize position state
|
||||
available: Query available capacity for a trade
|
||||
apply_execution: Censor execution by available position
|
||||
step: Process time-based updates (replenishment, holding cost)
|
||||
|
||||
Properties:
|
||||
position: Current position vector
|
||||
holding_cost: Cost incurred this step from holding position
|
||||
"""
|
||||
def reset(self, instruments: InstrumentSet, rng: np.random.Generator) -> None:
|
||||
"""Initialize position state for new episode."""
|
||||
...
|
||||
|
||||
def available(self, instrument_id: int, side: Any) -> float:
|
||||
"""Query available capacity for a trade.
|
||||
|
||||
Args:
|
||||
instrument_id: Which instrument
|
||||
side: BUY or SELL
|
||||
|
||||
Returns:
|
||||
Maximum tradeable size given current position
|
||||
"""
|
||||
...
|
||||
|
||||
def apply_execution(self, exe: Execution) -> Execution:
|
||||
"""Apply position constraints to an execution.
|
||||
|
||||
Args:
|
||||
exe: Proposed execution with size_requested
|
||||
|
||||
Returns:
|
||||
Censored execution with size_filled <= available capacity
|
||||
"""
|
||||
...
|
||||
|
||||
def step(self, t: float) -> None:
|
||||
"""Process time-based position updates.
|
||||
|
||||
Handles replenishment receipt, holding cost calculation, etc.
|
||||
"""
|
||||
...
|
||||
|
||||
@property
|
||||
def position(self) -> np.ndarray:
|
||||
"""Current position vector (positive=long/inventory, negative=short)."""
|
||||
...
|
||||
|
||||
@property
|
||||
def holding_cost(self) -> float:
|
||||
"""Holding cost incurred this step."""
|
||||
...
|
||||
|
||||
class MarketModel(Protocol):
|
||||
"""Models external market dynamics and competitor behavior.
|
||||
|
||||
For retail: competitor price dynamics (static, reactive, stochastic)
|
||||
For finance: mid-price process (GBM, mean-reverting)
|
||||
|
||||
Methods:
|
||||
step: Update market state given agent's quotes
|
||||
"""
|
||||
def step(self, t: float, self_quotes: Quote, hidden: HiddenState,
|
||||
rng: np.random.Generator) -> MarketState:
|
||||
"""Update market state for this timestep.
|
||||
|
||||
Args:
|
||||
t: Current time
|
||||
self_quotes: Agent's current quotes (competitors may react)
|
||||
hidden: Hidden state (regime info)
|
||||
rng: Random generator
|
||||
|
||||
Returns:
|
||||
Updated market state with competitor prices, mid-prices, volatility
|
||||
"""
|
||||
...
|
||||
|
||||
class ObservationBuilder(Protocol):
|
||||
"""Constructs agent observations with appropriate censoring.
|
||||
|
||||
Critical for research: ensures agent only sees censored fills,
|
||||
never true demand (which goes in info dict).
|
||||
|
||||
Methods:
|
||||
build: Construct observation from step data
|
||||
"""
|
||||
def build(self, quote: Quote, instruments: InstrumentSet, logs: StepLogs,
|
||||
metrics: StepMetrics, market: MarketState | None,
|
||||
hidden: HiddenState, mask_demand: bool, t: int) -> Observation:
|
||||
"""Build observation for agent.
|
||||
|
||||
Args:
|
||||
quote: Current quote
|
||||
instruments: Instrument set with positions
|
||||
logs: Step logs with true_demand and censored_fills
|
||||
metrics: Computed metrics
|
||||
market: Market state
|
||||
hidden: Hidden state (not included in obs)
|
||||
mask_demand: If True, exclude true demand from observation
|
||||
t: Current timestep
|
||||
|
||||
Returns:
|
||||
Observation containing only observable quantities
|
||||
"""
|
||||
...
|
||||
|
||||
class Objective(Protocol):
|
||||
"""Computes reward from step metrics.
|
||||
|
||||
Supports composite objectives with weighted terms:
|
||||
- PnL (profit)
|
||||
- Position costs (holding, inventory risk)
|
||||
- Lost opportunity (stockouts)
|
||||
- Volatility penalty (UX)
|
||||
- Spread capture (market making)
|
||||
|
||||
Methods:
|
||||
reward: Compute scalar reward
|
||||
breakdown: Get per-term contribution for analysis
|
||||
"""
|
||||
def reward(self, quote: Quote, instruments: InstrumentSet,
|
||||
metrics: StepMetrics, hidden: HiddenState,
|
||||
obs: Observation) -> float:
|
||||
"""Compute scalar reward for this step.
|
||||
|
||||
Args:
|
||||
quote: Current quote
|
||||
instruments: Instrument set
|
||||
metrics: Step metrics (pnl, costs, etc.)
|
||||
hidden: Hidden state
|
||||
obs: Agent observation
|
||||
|
||||
Returns:
|
||||
Scalar reward value
|
||||
"""
|
||||
...
|
||||
|
||||
def breakdown(self, quote: Quote, instruments: InstrumentSet,
|
||||
metrics: StepMetrics, hidden: HiddenState,
|
||||
obs: Observation) -> dict[str, float]:
|
||||
"""Get reward breakdown by component.
|
||||
|
||||
Useful for analyzing which terms dominate the reward.
|
||||
|
||||
Returns:
|
||||
Dict mapping term names to their contributions
|
||||
"""
|
||||
...
|
||||
Reference in New Issue
Block a user