three_commas_types/bots/
stats.rs

1use rust_decimal::Decimal;
2use serde::Deserialize;
3use smol_str::SmolStr;
4use std::{
5  collections::{hash_map, HashMap},
6  iter::FusedIterator,
7};
8
9#[derive(Debug, Deserialize, Clone)]
10pub struct BotStats {
11  overall_stats: HashMap<SmolStr, Decimal>,
12  today_stats: HashMap<SmolStr, Decimal>,
13  profits_in_usd: ProfitsInUsd,
14}
15
16impl BotStats {
17  pub fn overall(&self) -> TokenValues {
18    TokenValues {
19      values: &self.overall_stats,
20    }
21  }
22
23  pub fn today(&self) -> TokenValues {
24    TokenValues {
25      values: &self.today_stats,
26    }
27  }
28
29  pub fn overall_usd_profit(&self) -> Decimal {
30    self.profits_in_usd.overall_usd_profit
31  }
32
33  pub fn today_usd_profit(&self) -> Decimal {
34    self.profits_in_usd.today_usd_profit
35  }
36
37  pub fn active_deals_usd_profit(&self) -> Decimal {
38    self.profits_in_usd.active_deals_usd_profit
39  }
40}
41
42#[derive(Clone)]
43pub struct TokenValues<'a> {
44  values: &'a HashMap<SmolStr, Decimal>,
45}
46
47impl<'a> TokenValues<'a> {
48  pub fn get(&self, token: &str) -> Option<Decimal> {
49    self.values.get(token).copied()
50  }
51
52  pub fn tokens(&self) -> impl Iterator<Item = &'a str> {
53    self.values.keys().map(|k| &**k)
54  }
55
56  pub fn iter(&self) -> TokenValuesIter<'a> {
57    TokenValuesIter {
58      iter: self.values.iter(),
59    }
60  }
61}
62
63impl<'a> IntoIterator for TokenValues<'a> {
64  type Item = (&'a str, Decimal);
65  type IntoIter = TokenValuesIter<'a>;
66
67  fn into_iter(self) -> Self::IntoIter {
68    TokenValuesIter {
69      iter: self.values.iter(),
70    }
71  }
72}
73
74#[derive(Clone)]
75pub struct TokenValuesIter<'a> {
76  iter: hash_map::Iter<'a, SmolStr, Decimal>,
77}
78
79impl<'a> Iterator for TokenValuesIter<'a> {
80  type Item = (&'a str, Decimal);
81
82  fn next(&mut self) -> Option<Self::Item> {
83    self.iter.next().map(|(k, v)| (&**k, *v))
84  }
85
86  #[inline]
87  fn size_hint(&self) -> (usize, Option<usize>) {
88    self.iter.size_hint()
89  }
90}
91
92impl<'a> ExactSizeIterator for TokenValuesIter<'a> {
93  #[inline]
94  fn len(&self) -> usize {
95    self.iter.len()
96  }
97}
98
99impl<'a> FusedIterator for TokenValuesIter<'a> {}
100
101#[derive(Debug, Deserialize, Clone)]
102struct ProfitsInUsd {
103  overall_usd_profit: Decimal,
104  today_usd_profit: Decimal,
105  active_deals_usd_profit: Decimal,
106}
107
108#[cfg(test)]
109mod tests {
110  use super::*;
111  use std::str::FromStr;
112
113  #[test]
114  fn deserialize() {
115    let json = r###"
116    {
117      "overall_stats": {
118        "USDT": "79.77234945",
119        "BTC": "0.00074279"
120      },
121      "today_stats": {
122        "BTC": "0.00006813",
123        "USDT": "10.96327719"
124      },
125      "profits_in_usd": {
126        "overall_usd_profit": 123.49,
127        "today_usd_profit": 14.97,
128        "active_deals_usd_profit": -8.52
129      }
130    }
131    "###;
132
133    let stats: BotStats = serde_json::from_str(json).expect("deserialize successfully");
134    assert_eq!(stats.overall_stats.len(), 2);
135    assert_eq!(
136      stats.overall_stats.get("USDT"),
137      Some(&Decimal::from_str("79.77234945").unwrap())
138    );
139    assert_eq!(
140      stats.overall_stats.get("BTC"),
141      Some(&Decimal::from_str("0.00074279").unwrap())
142    );
143    assert_eq!(
144      stats.today_stats.get("USDT"),
145      Some(&Decimal::from_str("10.96327719").unwrap())
146    );
147    assert_eq!(
148      stats.today_stats.get("BTC"),
149      Some(&Decimal::from_str("0.00006813").unwrap())
150    );
151    assert_eq!(
152      stats.profits_in_usd.overall_usd_profit,
153      Decimal::from_str("123.49").unwrap()
154    );
155    assert_eq!(
156      stats.profits_in_usd.today_usd_profit,
157      Decimal::from_str("14.97").unwrap()
158    );
159    assert_eq!(
160      stats.profits_in_usd.active_deals_usd_profit,
161      Decimal::from_str("-8.52").unwrap()
162    );
163  }
164}