use hypersdk::hypercore::Side; use pulse_sdk::prelude::*; pub trait Formatted { fn get_formatted(&self) -> Vec; } impl Formatted for InspectTarget { fn get_formatted(&self) -> Vec { match self { Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], Self::Some(items) => items .iter() .map(|item| match item { InspectItem::F64(v) => format_f64(*v), InspectItem::USD(v) => format!("${}", format_f64(*v)), InspectItem::Symbol(v) => format!("\x1b[35m{v}\x1b[0m"), InspectItem::String(v) => v.clone(), }) .collect(), } } } impl Formatted for EventLog { fn get_formatted(&self) -> Vec { vec![ format!( "{}{}\x1b[0m", match &self.kind { LogKind::Info => "\x1b[34m", LogKind::Debug => "\x1b[36m", LogKind::Err => "\x1b[31m", LogKind::Warn => "\x1b[33m", }, self.kind ), format!("(\x1b[37m{}\x1b[0m)", self.name), self.message.clone(), ] } } impl Formatted for SignalStatus { fn get_formatted(&self) -> Vec { match self { Ok(signal) => { vec![ format!("\x1b[33mOK\x1b[0m"), if matches!(signal.side, Side::Ask) { format!("\x1b[32mBUY\x1b[0m") } else { format!("\x1b[31mSELL\x1b[0m") }, format_symbol(&signal.symbol), format_usd(signal.price.as_f64()), format!("\x1b[33mAPR {}\x1b[0m", signal.confidence), ] } Err(signal) => { vec![ format!("\x1b[31mERR\x1b[0m"), if matches!(signal.side, Side::Ask) { format!("\x1b[32mBUY\x1b[0m") } else { format!("\x1b[31mSELL\x1b[0m") }, format_symbol(&signal.symbol), format_usd(signal.price.as_f64()), format!("\x1b[33mAPR {}\x1b[0m", signal.confidence), ] } } } } impl Formatted for MarketItem { fn get_formatted(&self) -> Vec { vec![ format_symbol(&self.symbol), format_usd(self.price.as_f64()), format_usd(self.volume_24h.as_f64()), format!( "{} {}%", if self.trend.is_sign_positive() { "\x1b[32m▲\x1b[0m" } else { "\x1b[31m▼\x1b[0m" }, format_f64(self.trend.as_f64().abs()) ), ] } } impl Formatted for Position { fn get_formatted(&self) -> Vec { vec![ format_symbol(&self.symbol), format_f64(self.size.as_f64()), self.entry_price.to_string(), format!( "{}{}\x1b[0m", if self.pnl.is_sign_positive() { "\x1b[32m" } else { "\x1b[31m" }, self.pnl, ), ] } } struct Pair<'a>(&'a str, &'a str); struct Triple<'a>(&'a str, &'a str, &'a str); impl Formatted for Status { fn get_formatted(&self) -> Vec { vec![ Pair("Feed", &format!("{:?}", self.feed)), Pair("Exchange", &self.exchange.clone()), Pair("DEX", &self.dex.clone()), Pair("Latency", &format!("{} ms", self.latency)), ] .get_formatted() } } impl<'a> Formatted for Pair<'a> { fn get_formatted(&self) -> Vec { vec![self.0.to_string(), self.1.to_string()] } } impl<'a> Formatted for Triple<'a> { fn get_formatted(&self) -> Vec { vec![self.0.to_string(), self.1.to_string(), self.2.to_string()] } } fn visible_len(s: &str) -> usize { let mut len = 0; let mut chars = s.chars().peekable(); while let Some(c) = chars.next() { if c == '\x1b' { if chars.peek() == Some(&'[') { chars.next(); while let Some(c) = chars.next() { if c.is_ascii_alphabetic() { break; } } } } else { len += c.len_utf8(); } } len } impl Formatted for Vec { fn get_formatted(&self) -> Vec { let mut output = Vec::new(); let rows: Vec> = self.iter().map(|v| v.get_formatted()).collect(); if rows.is_empty() { return output; } let column_count = rows.iter().map(|r| r.len()).max().unwrap_or(0); let mut widths = vec![0; column_count]; for row in &rows { for (i, col) in row.iter().enumerate() { widths[i] = widths[i].max(visible_len(col)); } } for row in rows { let mut row_out = String::new(); for (col_idx, col) in row.iter().enumerate() { let width = widths[col_idx]; row_out.push_str(col); row_out.push_str(&" ".repeat(width.saturating_sub(visible_len(col)))); if col_idx + 1 != row.len() { row_out.push_str(" "); } } output.push(row_out); } output } } impl Formatted for StrategyStatus { fn get_formatted(&self) -> Vec { vec![ Triple( "\x1b[2mStrategy\x1b[0m", "\x1b[2mState\x1b[0m", "\x1b[2mMode\x1b[0m", ), Triple( &format!("\x1b[97m{}\x1b[0m", self.strategy.name), &self.state.to_string(), &self.mode.to_string(), ), ] .get_formatted() } } pub fn apply_padding(mut items: Vec) -> Vec { for item in &mut items { item.insert(0, ' '); } items } pub fn format_symbol(value: &str) -> String { format!("\x1b[35m{value}\x1b[0m") } pub fn format_usd(value: f64) -> String { if value.is_sign_positive() { format!("\x1b[32m${}\x1b[0m", format_f64(value)) } else { format!("\x1b[31m${}\x1b[0m", format_f64(value)) } } pub fn format_f64(value: f64) -> String { let abs = value.abs(); let (divisor, suffix) = if abs >= 1_000_000_000.0 { (1_000_000_000.0, "B") } else if abs >= 1_000_000.0 { (1_000_000.0, "M") } else if abs >= 1_000.0 { (1_000.0, "K") } else { (1.0, "") }; if divisor != 1.0 { let formatted = value / divisor; // Remove unnecessary trailing zeros let s = format!("{:.2}", formatted) .trim_end_matches('0') .trim_end_matches('.') .to_string(); return format!("{}{}", s, suffix); } let val = format!("{:.3}", value); let parts: Vec<&str> = val.split('.').collect(); let int = parts[0].to_string(); let negative = int.starts_with('-'); let start = if negative { 1 } else { 0 }; let mut result = String::new(); for (i, c) in int[start..].chars().rev().enumerate() { if i > 0 && i % 3 == 0 { result.push(','); } result.push(c); } let mut formatted: String = result.chars().rev().collect(); if negative { formatted.insert(0, '-'); } if parts.len() > 1 { formatted.push('.'); formatted.push_str(parts[1]); } formatted }