use std::collections::{HashMap, HashSet};
use solana_pubkey::Pubkey;
use thiserror::Error;
use tokio::sync::{mpsc, oneshot};
use crate::phoenix_rise_math::TraderPortfolioMargin;
use crate::types::http_error::PhoenixHttpError;
use crate::types::market_state::Market;
use crate::types::metadata::PhoenixMetadata;
use crate::types::subscription_key::SubscriptionKey;
use crate::types::trader_state::Trader;
use crate::types::ws_error::PhoenixWsError;
use crate::types::{
AllMidsData, CandleData, FundingRateMessage, L2BookUpdate, MarketStatsUpdate, Timeframe,
TraderStateServerMessage, TradesMessage,
};
pub type ClientSubscriptionId = u64;
#[derive(Debug, Error)]
pub enum PhoenixClientError {
#[error("WebSocket error: {0}")]
WebSocket(PhoenixWsError),
#[error("HTTP error: {0}")]
Http(PhoenixHttpError),
#[error("Client is shutting down")]
Shutdown,
#[error("Failed to send command")]
SendFailed,
#[error("Failed to receive response")]
ResponseDropped,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PhoenixSubscription {
Key(SubscriptionKey),
Market {
symbol: String,
candle_timeframes: Vec<Timeframe>,
include_trades: bool,
},
TraderMargin {
authority: Pubkey,
trader_pda_index: u8,
subaccount_index: u8,
market_symbols: Vec<String>,
},
}
impl PhoenixSubscription {
pub fn market(symbol: impl Into<String>) -> Self {
Self::Market {
symbol: symbol.into().to_ascii_uppercase(),
candle_timeframes: Vec::new(),
include_trades: false,
}
}
pub fn trader_margin(authority: Pubkey, trader_pda_index: u8) -> Self {
Self::TraderMargin {
authority,
trader_pda_index,
subaccount_index: 0,
market_symbols: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub enum MarginTrigger {
Trader(TraderStateServerMessage),
Market(MarketStatsUpdate),
}
#[derive(Debug, Clone)]
pub enum PhoenixClientEvent {
MarketUpdate {
symbol: String,
prev_market: Option<Market>,
update: MarketStatsUpdate,
},
OrderbookUpdate {
symbol: String,
prev_market: Option<Market>,
update: L2BookUpdate,
},
TraderUpdate {
key: SubscriptionKey,
prev_trader: Option<Trader>,
update: TraderStateServerMessage,
},
MidsUpdate {
prev_mids: HashMap<String, f64>,
update: AllMidsData,
},
FundingRateUpdate {
symbol: String,
prev_funding_rate: Option<FundingRateMessage>,
update: FundingRateMessage,
},
CandleUpdate {
symbol: String,
timeframe: Timeframe,
prev_candle: Option<CandleData>,
update: CandleData,
},
TradesUpdate {
symbol: String,
prev_trades: Option<TradesMessage>,
update: TradesMessage,
},
MarginUpdate {
trader_key: SubscriptionKey,
trigger: MarginTrigger,
margin: Option<TraderPortfolioMargin>,
metadata: PhoenixMetadata,
prev_trader: Option<Trader>,
},
}
pub enum ClientCommand {
Subscribe {
subscription: PhoenixSubscription,
response_tx: oneshot::Sender<
Result<
(
ClientSubscriptionId,
mpsc::UnboundedReceiver<PhoenixClientEvent>,
),
PhoenixClientError,
>,
>,
},
Unsubscribe {
subscription_id: ClientSubscriptionId,
},
Shutdown,
}
pub struct PhoenixClientSubscriptionHandle {
pub cmd_tx: mpsc::UnboundedSender<ClientCommand>,
pub subscription_id: ClientSubscriptionId,
}
impl Drop for PhoenixClientSubscriptionHandle {
fn drop(&mut self) {
let _ = self.cmd_tx.send(ClientCommand::Unsubscribe {
subscription_id: self.subscription_id,
});
}
}
pub struct LogicalSubscription {
pub subscription: PhoenixSubscription,
pub dependencies: HashSet<SubscriptionKey>,
pub event_tx: mpsc::UnboundedSender<PhoenixClientEvent>,
}
pub struct RuntimeState {
pub metadata: PhoenixMetadata,
pub markets: HashMap<String, Market>,
pub traders: HashMap<SubscriptionKey, Trader>,
pub mids: HashMap<String, f64>,
pub funding_rates: HashMap<String, FundingRateMessage>,
pub candles: HashMap<(String, Timeframe), CandleData>,
pub trades: HashMap<String, TradesMessage>,
}
impl RuntimeState {
pub fn new(metadata: PhoenixMetadata) -> Self {
Self {
metadata,
markets: HashMap::new(),
traders: HashMap::new(),
mids: HashMap::new(),
funding_rates: HashMap::new(),
candles: HashMap::new(),
trades: HashMap::new(),
}
}
}