1use crate::model::instrument::InstrumentKind;
8use serde::{Deserialize, Serialize};
9
10use crate::{impl_json_debug_pretty, impl_json_display};
11#[derive(Clone, Serialize, Deserialize)]
13pub struct Ticker {
14 pub instrument_name: String,
16 pub timestamp: i64,
18 pub best_bid_price: Option<f64>,
20 pub best_bid_amount: Option<f64>,
22 pub best_ask_price: Option<f64>,
24 pub best_ask_amount: Option<f64>,
26 pub last_price: Option<f64>,
28 pub mark_price: Option<f64>,
30 pub index_price: Option<f64>,
32 pub open_interest: f64,
34 pub volume_24h: f64,
36 pub volume_usd_24h: f64,
38 pub price_change_24h: f64,
40 pub high_24h: Option<f64>,
42 pub low_24h: Option<f64>,
44 pub underlying_price: Option<f64>,
46 pub underlying_index: Option<String>,
48 pub instrument_kind: Option<InstrumentKind>,
50 pub current_funding: Option<f64>,
52 pub funding_8h: Option<f64>,
54 pub iv: Option<f64>,
56 pub greeks: Option<Greeks>,
58 pub interest_rate: Option<f64>,
60}
61
62impl Ticker {
63 pub fn spread(&self) -> Option<f64> {
65 match (self.best_ask_price, self.best_bid_price) {
66 (Some(ask), Some(bid)) => Some(ask - bid),
67 _ => None,
68 }
69 }
70
71 pub fn mid_price(&self) -> Option<f64> {
73 match (self.best_ask_price, self.best_bid_price) {
74 (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
75 _ => None,
76 }
77 }
78
79 pub fn spread_percentage(&self) -> Option<f64> {
81 match (self.spread(), self.mid_price()) {
82 (Some(spread), Some(mid)) if mid != 0.0 => Some((spread / mid) * 100.0),
83 _ => None,
84 }
85 }
86
87 pub fn has_valid_spread(&self) -> bool {
89 self.best_bid_price.is_some() && self.best_ask_price.is_some()
90 }
91}
92
93#[derive(Clone, Serialize, Deserialize)]
95pub struct OrderBookEntry {
96 pub price: f64,
98 pub amount: f64,
100}
101
102impl OrderBookEntry {
103 pub fn new(price: f64, amount: f64) -> Self {
105 Self { price, amount }
106 }
107
108 pub fn notional(&self) -> f64 {
110 self.price * self.amount
111 }
112}
113
114#[derive(Clone, Serialize, Deserialize)]
116pub struct OrderBook {
117 pub instrument_name: String,
119 pub timestamp: i64,
121 pub bids: Vec<OrderBookEntry>,
123 pub asks: Vec<OrderBookEntry>,
125 pub change_id: u64,
127 pub prev_change_id: Option<u64>,
129}
130
131impl OrderBook {
132 pub fn new(instrument_name: String, timestamp: i64, change_id: u64) -> Self {
134 Self {
135 instrument_name,
136 timestamp,
137 bids: Vec::new(),
138 asks: Vec::new(),
139 change_id,
140 prev_change_id: None,
141 }
142 }
143
144 pub fn best_bid(&self) -> Option<f64> {
146 self.bids.first().map(|entry| entry.price)
147 }
148
149 pub fn best_ask(&self) -> Option<f64> {
151 self.asks.first().map(|entry| entry.price)
152 }
153
154 pub fn spread(&self) -> Option<f64> {
156 match (self.best_ask(), self.best_bid()) {
157 (Some(ask), Some(bid)) => Some(ask - bid),
158 _ => None,
159 }
160 }
161
162 pub fn mid_price(&self) -> Option<f64> {
164 match (self.best_ask(), self.best_bid()) {
165 (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
166 _ => None,
167 }
168 }
169
170 pub fn total_bid_volume(&self) -> f64 {
172 self.bids.iter().map(|entry| entry.amount).sum()
173 }
174
175 pub fn total_ask_volume(&self) -> f64 {
177 self.asks.iter().map(|entry| entry.amount).sum()
178 }
179
180 pub fn volume_at_price(&self, price: f64, is_bid: bool) -> f64 {
182 let levels = if is_bid { &self.bids } else { &self.asks };
183 levels
184 .iter()
185 .find(|entry| (entry.price - price).abs() < f64::EPSILON)
186 .map(|entry| entry.amount)
187 .unwrap_or(0.0)
188 }
189}
190
191#[derive(Clone, Serialize, Deserialize)]
193pub struct Greeks {
194 pub delta: f64,
196 pub gamma: f64,
198 pub theta: f64,
200 pub vega: f64,
202 pub rho: Option<f64>,
204}
205
206#[derive(Clone, Serialize, Deserialize)]
208pub struct MarketStats {
209 pub currency: String,
211 pub volume_24h: f64,
213 pub volume_change_24h: f64,
215 pub price_change_24h: f64,
217 pub high_24h: f64,
219 pub low_24h: f64,
221 pub active_instruments: u32,
223 pub total_open_interest: f64,
225}
226
227#[derive(Clone, Serialize, Deserialize)]
229pub struct Candle {
230 pub timestamp: i64,
232 pub open: f64,
234 pub high: f64,
236 pub low: f64,
238 pub close: f64,
240 pub volume: f64,
242 pub trades: Option<u64>,
244}
245
246impl Candle {
247 pub fn is_bullish(&self) -> bool {
249 self.close > self.open
250 }
251
252 pub fn is_bearish(&self) -> bool {
254 self.close < self.open
255 }
256
257 pub fn body_size(&self) -> f64 {
259 (self.close - self.open).abs()
260 }
261
262 pub fn upper_shadow(&self) -> f64 {
264 self.high - self.close.max(self.open)
265 }
266
267 pub fn lower_shadow(&self) -> f64 {
269 self.close.min(self.open) - self.low
270 }
271
272 pub fn range(&self) -> f64 {
274 self.high - self.low
275 }
276}
277
278impl_json_debug_pretty!(
280 Ticker,
281 OrderBookEntry,
282 OrderBook,
283 Greeks,
284 MarketStats,
285 Candle
286);
287
288impl_json_display!(
290 Ticker,
291 OrderBookEntry,
292 OrderBook,
293 Greeks,
294 MarketStats,
295 Candle
296);