v_exchanges/
other_types.rs

1use jiff::Timestamp;
2use serde::Deserialize;
3use v_utils::{Percent, prelude::*, trades::Pair};
4
5#[derive(Clone, Copy, Debug, Default, derive_more::Deref, derive_more::DerefMut, Deserialize, Serialize)]
6pub struct Lsr {
7	pub time: Timestamp,
8	#[deref_mut]
9	#[deref]
10	pub long: Percent,
11}
12//Q: couldn't decide if `short()` and `long()` should return `f64` or `Percent`. Postponing the decision.
13impl Lsr {
14	pub fn ratio(&self) -> f64 {
15		*self.long / self.short()
16	}
17
18	/// Percentage of short positions
19	pub fn short(&self) -> f64 {
20		1.0 - *self.long
21	}
22
23	/// Percentage of long positions. // here only for consistency with `short`
24	pub fn long(&self) -> f64 {
25		*self.long
26	}
27}
28impl From<f64> for Lsr {
29	fn from(f: f64) -> Self {
30		Self {
31			time: Timestamp::default(),
32			long: Percent::from(f),
33		}
34	}
35}
36
37#[derive(Clone, Debug, Default, derive_more::Deref, derive_more::DerefMut, Deserialize, Serialize)]
38pub struct Lsrs {
39	#[deref_mut]
40	#[deref]
41	pub values: Vec<Lsr>,
42	pub pair: Pair,
43}
44impl Lsrs {
45	pub const CHANGE_STR_LEN: usize = 26;
46	const MAX_LEN_BASE: usize = 9;
47
48	pub fn values(&self) -> &[Lsr] {
49		&self.values
50	}
51
52	pub fn last(&self) -> Result<&Lsr> {
53		self.values.last().ok_or_else(|| eyre!("Lsrs is empty"))
54	}
55
56	fn format_pair(&self) -> String {
57		let s = match self.pair.quote().as_ref() {
58			"USDT" => self.pair.base().to_string(),
59			_ => self.pair.to_string(),
60		};
61		format!("{:<width$}", s, width = Self::MAX_LEN_BASE)
62	}
63
64	pub fn display_short(&self) -> Result<String> {
65		Ok(format!("{}: {:.2}", self.format_pair(), self.last()?.long()))
66	}
67
68	pub fn display_change(&self) -> Result<String> {
69		let diff = NowThen::new(*self.last()?.long, *self.first().expect("can't be empty, otherwise `last()` would have had panicked").long);
70		let s = format!("{}: {:<12}", self.format_pair(), diff.to_string()); // `to_string`s are required because rust is dumb as of today and will fuck with padding (2024/01/16)
71		Ok(format!("{:<width$}", s, width = Self::CHANGE_STR_LEN))
72	}
73}
74
75#[cfg(test)]
76mod tests {
77	use std::sync::OnceLock;
78	static INIT: OnceLock<()> = OnceLock::new();
79	use super::*;
80
81	fn init() -> (Lsrs, Lsrs) {
82		if INIT.get().is_none() {
83			let _ = INIT.set(());
84			color_eyre::install().unwrap();
85		}
86		(
87			Lsrs {
88				values: vec![0.4, 0.5, 0.6, 0.55].into_iter().map(Lsr::from).collect(),
89				pair: Pair::from(("BTC", "USDT")),
90			},
91			Lsrs {
92				values: vec![0.9, 0.6, 0.6, 0.7].into_iter().map(Lsr::from).collect(),
93				pair: Pair::from(("TRUMP", "SOL")),
94			},
95		)
96	}
97
98	#[test]
99	fn display_short_usdt_pair() {
100		let lsrs = init();
101		insta::assert_snapshot!(lsrs.0.display_short().unwrap(), @"BTC      : 0.55");
102	}
103
104	#[test]
105	fn display_short_non_usdt_pair() {
106		let lsrs = init();
107		insta::assert_snapshot!(lsrs.1.display_short().unwrap(), @"TRUMP-SOL: 0.70");
108	}
109
110	#[test]
111	fn display_change() {
112		let lsrs = init();
113		insta::assert_snapshot!(lsrs.0.display_change().unwrap(), @"BTC      : 0.55+0.15");
114	}
115
116	#[test]
117	fn display_change_non_usdt() {
118		let lsrs = init();
119		insta::assert_snapshot!(lsrs.1.display_change().unwrap(), @"TRUMP-SOL: 0.7-0.2");
120	}
121}