use crate::risk::PortfolioLimits;
use crate::types::trading::OrderRequest;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
pub const MODEL_VERSION: &str = "equity-bbo-v2";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaperAccountConfig {
pub initial_cash: Decimal,
pub limits: PortfolioLimits,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PaperClockMode {
#[default]
Replay,
Wall,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaperConfig {
pub model_version: String,
#[serde(default)]
pub clock: PaperClockMode,
pub accounts: BTreeMap<String, PaperAccountConfig>,
pub execution_latency_ms: i64,
pub cancel_latency_ms: i64,
pub quote_max_age_ms: i64,
pub slippage_bps: Decimal,
pub market_collar_bps: Decimal,
pub fee_per_share: Decimal,
#[serde(default, skip_serializing_if = "Decimal::is_zero")]
pub fee_bps: Decimal,
#[serde(default, skip_serializing_if = "Decimal::is_zero")]
pub sell_fee_bps: Decimal,
#[serde(default, skip_serializing_if = "Decimal::is_zero")]
pub sell_fee_per_share: Decimal,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sell_fee_per_share_cap: Option<Decimal>,
pub max_working_orders: usize,
#[serde(default)]
pub settlement_required: bool,
#[serde(default)]
pub settlement_calendar: BTreeMap<String, i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "source", rename_all = "snake_case", deny_unknown_fields)]
pub enum PaperEventSource {
Local,
SchwabRest,
Authority { subject: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaperEvent {
pub sequence: u64,
pub at_ms: i64,
pub kind: PaperEventKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum PaperEventKind {
Session { session_id: String, open: bool },
Quote {
symbol: String,
source_at_ms: i64,
bid: Decimal,
ask: Decimal,
bid_size: Decimal,
ask_size: Decimal,
},
Dividend {
action_id: String,
symbol: String,
cash_per_share: Decimal,
pay_at_ms: i64,
},
Split {
action_id: String,
symbol: String,
numerator: u32,
denominator: u32,
cash_in_lieu_price: Decimal,
pay_at_ms: i64,
},
Clock,
Disconnect,
Fault { fault: PaperFault },
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PaperFault {
RejectNext,
LoseNextAcknowledgement,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaperOrderStatus {
Working,
PendingCancel,
Filled,
Canceled,
Replaced,
Expired,
Rejected,
}
impl PaperOrderStatus {
pub fn is_open(self) -> bool {
matches!(self, Self::Working | Self::PendingCancel)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperOrder {
pub order_id: String,
pub account: String,
pub request: OrderRequest,
pub status: PaperOrderStatus,
pub filled_quantity: Decimal,
pub remaining_quantity: Decimal,
pub accepted_at_ms: i64,
pub accepted_sequence: u64,
pub eligible_at_ms: i64,
pub session_id: String,
pub cancel_at_ms: Option<i64>,
pub price_bound: Decimal,
pub reserved_cash: Decimal,
pub replaces: Option<String>,
#[serde(default)]
pub triggered: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PaperPosition {
pub quantity: Decimal,
pub cost_basis: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingCash {
pub source: String,
pub amount: Decimal,
pub available_at_ms: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperAccount {
pub cash: Decimal,
pub realized_pnl: Decimal,
pub fees: Decimal,
pub positions: BTreeMap<String, PaperPosition>,
#[serde(default)]
pub pending_cash: Vec<PendingCash>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_mark: Option<SessionMark>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_mark: Option<SessionMark>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionMark {
pub session_id: String,
pub equity: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperFill {
pub order_id: String,
pub event_sequence: u64,
pub at_ms: i64,
pub quantity: Decimal,
pub price: Decimal,
pub fee: Decimal,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaperQuote {
pub symbol: String,
pub source_at_ms: i64,
pub bid: Decimal,
pub ask: Decimal,
pub fresh: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperSnapshot {
pub mode: String,
pub clock: PaperClockMode,
pub model_version: String,
pub account: String,
pub at_ms: i64,
pub event_sequence: u64,
pub ledger: PaperAccount,
pub reserved_cash: Decimal,
pub available_cash: Decimal,
pub unsettled_cash: Decimal,
pub equity: Option<Decimal>,
pub unrealized_pnl: Option<Decimal>,
pub daily_loss: Option<Decimal>,
pub working_orders: Vec<PaperOrder>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperEventResult {
pub mode: String,
pub duplicate: bool,
pub sequence: u64,
pub fills: Vec<PaperFill>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub(super) struct Clock {
pub sequence: u64,
pub at_ms: i64,
pub session_id: String,
pub open: bool,
pub fault: Option<PaperFault>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(super) struct Quote {
pub source_at_ms: i64,
pub bid: Decimal,
pub ask: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "command", rename_all = "snake_case", deny_unknown_fields)]
pub enum PaperCommand {
Archive {
destination: std::path::PathBuf,
through_sequence: u64,
},
Verify,
Event {
event: PaperEvent,
},
Place {
account: String,
order: OrderRequest,
},
Replace {
account: String,
order_id: String,
order: OrderRequest,
},
Cancel {
account: String,
order_id: String,
},
Snapshot {
account: String,
},
Halt {
reason: String,
},
Resume,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperIntegrityReport {
pub model_version: String,
pub accounts: usize,
pub orders: usize,
pub event_sequence: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperArchiveReport {
pub through_sequence: u64,
pub compacted: usize,
pub archive_path: std::path::PathBuf,
pub sha256: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaperCapturedCommand {
pub index: u64,
pub command: PaperCommand,
pub outcome: PaperCapturedOutcome,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case", deny_unknown_fields)]
pub enum PaperCapturedOutcome {
Success { result: serde_json::Value },
InjectedRejection,
LostAcknowledgement { order_id: String },
}