1use crate::model::{currency::Currency, instrument::InstrumentKind};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Ticker {
13 pub instrument_name: String,
15 pub timestamp: i64,
17 pub best_bid_price: Option<f64>,
19 pub best_bid_amount: Option<f64>,
21 pub best_ask_price: Option<f64>,
23 pub best_ask_amount: Option<f64>,
25 pub last_price: Option<f64>,
27 pub mark_price: Option<f64>,
29 pub index_price: Option<f64>,
31 pub open_interest: f64,
33 pub volume_24h: f64,
35 pub volume_usd_24h: f64,
37 pub price_change_24h: f64,
39 pub high_24h: Option<f64>,
41 pub low_24h: Option<f64>,
43 pub underlying_price: Option<f64>,
45 pub underlying_index: Option<String>,
47 pub instrument_kind: Option<InstrumentKind>,
49 pub current_funding: Option<f64>,
51 pub funding_8h: Option<f64>,
53 pub iv: Option<f64>,
55 pub greeks: Option<Greeks>,
57 pub interest_rate: Option<f64>,
59}
60
61impl Ticker {
62 pub fn spread(&self) -> Option<f64> {
64 match (self.best_ask_price, self.best_bid_price) {
65 (Some(ask), Some(bid)) => Some(ask - bid),
66 _ => None,
67 }
68 }
69
70 pub fn mid_price(&self) -> Option<f64> {
72 match (self.best_ask_price, self.best_bid_price) {
73 (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
74 _ => None,
75 }
76 }
77
78 pub fn spread_percentage(&self) -> Option<f64> {
80 match (self.spread(), self.mid_price()) {
81 (Some(spread), Some(mid)) if mid != 0.0 => Some((spread / mid) * 100.0),
82 _ => None,
83 }
84 }
85
86 pub fn has_valid_spread(&self) -> bool {
88 self.best_bid_price.is_some() && self.best_ask_price.is_some()
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct OrderBookEntry {
95 pub price: f64,
97 pub amount: f64,
99}
100
101impl OrderBookEntry {
102 pub fn new(price: f64, amount: f64) -> Self {
104 Self { price, amount }
105 }
106
107 pub fn notional(&self) -> f64 {
109 self.price * self.amount
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct OrderBook {
116 pub instrument_name: String,
118 pub timestamp: i64,
120 pub bids: Vec<OrderBookEntry>,
122 pub asks: Vec<OrderBookEntry>,
124 pub change_id: u64,
126 pub prev_change_id: Option<u64>,
128}
129
130impl OrderBook {
131 pub fn new(instrument_name: String, timestamp: i64, change_id: u64) -> Self {
133 Self {
134 instrument_name,
135 timestamp,
136 bids: Vec::new(),
137 asks: Vec::new(),
138 change_id,
139 prev_change_id: None,
140 }
141 }
142
143 pub fn best_bid(&self) -> Option<f64> {
145 self.bids.first().map(|entry| entry.price)
146 }
147
148 pub fn best_ask(&self) -> Option<f64> {
150 self.asks.first().map(|entry| entry.price)
151 }
152
153 pub fn spread(&self) -> Option<f64> {
155 match (self.best_ask(), self.best_bid()) {
156 (Some(ask), Some(bid)) => Some(ask - bid),
157 _ => None,
158 }
159 }
160
161 pub fn mid_price(&self) -> Option<f64> {
163 match (self.best_ask(), self.best_bid()) {
164 (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
165 _ => None,
166 }
167 }
168
169 pub fn total_bid_volume(&self) -> f64 {
171 self.bids.iter().map(|entry| entry.amount).sum()
172 }
173
174 pub fn total_ask_volume(&self) -> f64 {
176 self.asks.iter().map(|entry| entry.amount).sum()
177 }
178
179 pub fn volume_at_price(&self, price: f64, is_bid: bool) -> f64 {
181 let levels = if is_bid { &self.bids } else { &self.asks };
182 levels
183 .iter()
184 .find(|entry| (entry.price - price).abs() < f64::EPSILON)
185 .map(|entry| entry.amount)
186 .unwrap_or(0.0)
187 }
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Greeks {
193 pub delta: f64,
195 pub gamma: f64,
197 pub theta: f64,
199 pub vega: f64,
201 pub rho: Option<f64>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct MarketStats {
208 pub currency: Currency,
210 pub volume_24h: f64,
212 pub volume_change_24h: f64,
214 pub price_change_24h: f64,
216 pub high_24h: f64,
218 pub low_24h: f64,
220 pub active_instruments: u32,
222 pub total_open_interest: f64,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct Candle {
229 pub timestamp: i64,
231 pub open: f64,
233 pub high: f64,
235 pub low: f64,
237 pub close: f64,
239 pub volume: f64,
241 pub trades: Option<u64>,
243}
244
245impl Candle {
246 pub fn is_bullish(&self) -> bool {
248 self.close > self.open
249 }
250
251 pub fn is_bearish(&self) -> bool {
253 self.close < self.open
254 }
255
256 pub fn body_size(&self) -> f64 {
258 (self.close - self.open).abs()
259 }
260
261 pub fn upper_shadow(&self) -> f64 {
263 self.high - self.close.max(self.open)
264 }
265
266 pub fn lower_shadow(&self) -> f64 {
268 self.close.min(self.open) - self.low
269 }
270
271 pub fn range(&self) -> f64 {
273 self.high - self.low
274 }
275}