1use {
2 crate::{
3 gamma::MarketInfo,
4 rtds::RTDSMessage,
5 websocket::{OrderUpdate, OrderbookUpdate, PriceUpdate, TradeUpdate, WebSocketMessage},
6 },
7 chrono::DateTime,
8 colored::*,
9};
10
11pub struct MarketUpdateFormatter;
12
13pub struct RTDSFormatter;
14
15impl MarketUpdateFormatter {
16 pub fn format_message(msg: &WebSocketMessage, market_info: Option<&MarketInfo>) -> String {
17 match msg {
18 WebSocketMessage::Orderbook(update) => Self::format_orderbook(update, market_info),
19 WebSocketMessage::Trade(update) => Self::format_trade(update, market_info),
20 WebSocketMessage::Order(update) => Self::format_order(update, market_info),
21 WebSocketMessage::Price(update) => Self::format_price(update, market_info),
22 WebSocketMessage::Error(err) => {
23 format!("{} {}", "ERROR".red().bold(), err.error.red())
24 },
25 WebSocketMessage::Subscribed(sub) => {
26 format!("{} {}", "✓ SUBSCRIBED".green().bold(), sub.message.green())
27 },
28 WebSocketMessage::Unknown => "Unknown message type".yellow().to_string(),
29 }
30 }
31
32 fn format_orderbook(update: &OrderbookUpdate, market_info: Option<&MarketInfo>) -> String {
33 let title = market_info
34 .map(|m| format!("{} - {}", m.event_title.bold(), m.market_question))
35 .unwrap_or_else(|| "Unknown Market".to_string());
36
37 let timestamp = update
38 .timestamp
39 .and_then(|ts| DateTime::from_timestamp(ts / 1000, 0))
40 .map(|dt| dt.format("%H:%M:%S").to_string())
41 .unwrap_or_else(|| "now".to_string());
42
43 let mut output = format!(
44 "\n{} {} {}\n",
45 "📊 ORDERBOOK".cyan().bold(),
46 timestamp.dimmed(),
47 title
48 );
49
50 if let Some(best_bid) = update.bids.first() {
52 output.push_str(&format!(
53 " {} {} @ {}\n",
54 "BID".green().bold(),
55 best_bid.size.bright_green(),
56 best_bid.price.bright_green()
57 ));
58 }
59
60 if let Some(best_ask) = update.asks.first() {
61 output.push_str(&format!(
62 " {} {} @ {}\n",
63 "ASK".red().bold(),
64 best_ask.size.bright_red(),
65 best_ask.price.bright_red()
66 ));
67 }
68
69 if let (Some(bid), Some(ask)) = (update.bids.first(), update.asks.first())
71 && let (Ok(bid_price), Ok(ask_price)) =
72 (bid.price.parse::<f64>(), ask.price.parse::<f64>())
73 {
74 let spread = ask_price - bid_price;
75 let spread_pct = (spread / bid_price) * 100.0;
76 output.push_str(&format!(
77 " {} {:.4} ({:.2}%)\n",
78 "SPREAD".yellow(),
79 spread,
80 spread_pct
81 ));
82 }
83
84 output
85 }
86
87 fn format_trade(update: &TradeUpdate, market_info: Option<&MarketInfo>) -> String {
88 let title = market_info
89 .map(|m| format!("{} - {}", m.event_title.bold(), m.market_question))
90 .unwrap_or_else(|| "Unknown Market".to_string());
91
92 let timestamp = update
93 .timestamp
94 .and_then(|ts| DateTime::from_timestamp(ts / 1000, 0))
95 .map(|dt| dt.format("%H:%M:%S").to_string())
96 .unwrap_or_else(|| "now".to_string());
97
98 let side_color = if update.side == "buy" {
99 "🟢 BUY".green().bold()
100 } else {
101 "🔴 SELL".red().bold()
102 };
103
104 format!(
105 "\n{} {} {} {} @ {} - {}\n",
106 "💸 TRADE".bright_yellow().bold(),
107 timestamp.dimmed(),
108 side_color,
109 update.size.bright_white(),
110 update.price.bright_white().bold(),
111 title
112 )
113 }
114
115 fn format_order(update: &OrderUpdate, market_info: Option<&MarketInfo>) -> String {
116 let title = market_info
117 .map(|m| format!("{} - {}", m.event_title.bold(), m.market_question))
118 .unwrap_or_else(|| "Unknown Market".to_string());
119
120 let timestamp = update
121 .timestamp
122 .and_then(|ts| DateTime::from_timestamp(ts / 1000, 0))
123 .map(|dt| dt.format("%H:%M:%S").to_string())
124 .unwrap_or_else(|| "now".to_string());
125
126 let status_color = match update.status.as_str() {
127 "open" => "OPEN".green(),
128 "filled" => "FILLED".bright_green(),
129 "cancelled" => "CANCELLED".red(),
130 _ => update.status.as_str().yellow(),
131 };
132
133 format!(
134 "\n{} {} {} {} @ {} - {} - {}\n",
135 "📝 ORDER".blue().bold(),
136 timestamp.dimmed(),
137 status_color.bold(),
138 update.size,
139 update.price,
140 update.side.to_uppercase(),
141 title
142 )
143 }
144
145 fn format_price(update: &PriceUpdate, market_info: Option<&MarketInfo>) -> String {
146 let title = market_info
147 .map(|m| format!("{} - {}", m.event_title.bold(), m.market_question))
148 .unwrap_or_else(|| "Unknown Market".to_string());
149
150 let timestamp = update
151 .timestamp
152 .and_then(|ts| DateTime::from_timestamp(ts / 1000, 0))
153 .map(|dt| dt.format("%H:%M:%S").to_string())
154 .unwrap_or_else(|| "now".to_string());
155
156 format!(
157 "\n{} {} Price: {} - {}\n",
158 "💰 PRICE".magenta().bold(),
159 timestamp.dimmed(),
160 update.price.bright_white().bold(),
161 title
162 )
163 }
164}
165
166impl RTDSFormatter {
167 pub fn format_message(msg: &RTDSMessage) -> String {
168 let timestamp = DateTime::from_timestamp(msg.payload.timestamp, 0)
169 .map(|dt| dt.format("%H:%M:%S").to_string())
170 .unwrap_or_else(|| "now".to_string());
171
172 let side_color = if msg.payload.side == "BUY" {
173 "🟢 BUY".green().bold()
174 } else {
175 "🔴 SELL".red().bold()
176 };
177
178 let outcome_color = if msg.payload.outcome == "Yes" {
179 msg.payload.outcome.bright_green()
180 } else {
181 msg.payload.outcome.bright_red()
182 };
183
184 let rounded_shares = (msg.payload.size * 100.0).round() / 100.0;
186
187 let total_value = msg.payload.price * msg.payload.size;
189
190 format!(
191 "\n{} {} {} {} @ ${:.4} ({} shares, ${:.2}) - {} - {}\n User: {} ({})\n",
192 "💸 TRADE".bright_yellow().bold(),
193 timestamp.dimmed(),
194 side_color,
195 outcome_color.bold(),
196 msg.payload.price,
197 rounded_shares,
198 total_value,
199 msg.payload.title.bold(),
200 msg.payload.event_slug.dimmed(),
201 msg.payload.name.bright_white(),
202 msg.payload.pseudonym.dimmed()
203 )
204 }
205}