termichart_data/
exchange.rs1use termichart_core::{Candle, Point};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum Exchange {
20 Binance,
22 CoinGecko,
24}
25
26pub struct ExchangeClient {
31 exchange: Exchange,
32}
33
34impl ExchangeClient {
35 pub fn new(exchange: Exchange) -> Self {
37 Self { exchange }
38 }
39
40 pub fn binance() -> Self {
42 Self::new(Exchange::Binance)
43 }
44
45 pub fn coingecko() -> Self {
47 Self::new(Exchange::CoinGecko)
48 }
49
50 pub fn fetch_candles(&self, symbol: &str, interval: &str, limit: usize) -> Option<Vec<Candle>> {
61 match self.exchange {
62 Exchange::Binance => fetch_binance_klines(symbol, interval, limit),
63 Exchange::CoinGecko => fetch_coingecko_ohlc(symbol, interval),
64 }
65 }
66
67 pub fn fetch_prices(&self, symbol: &str, interval: &str, limit: usize) -> Option<Vec<Point>> {
75 match self.exchange {
76 Exchange::Binance => {
77 let candles = fetch_binance_klines(symbol, interval, limit)?;
78 Some(
79 candles
80 .into_iter()
81 .map(|c| Point { x: c.time, y: c.close })
82 .collect(),
83 )
84 }
85 Exchange::CoinGecko => fetch_coingecko_prices(symbol, interval),
86 }
87 }
88
89 pub fn fetch_spot_price(&self, symbol: &str) -> Option<f64> {
94 match self.exchange {
95 Exchange::Binance => fetch_binance_spot(symbol),
96 Exchange::CoinGecko => fetch_coingecko_spot(symbol),
97 }
98 }
99}
100
101fn fetch_binance_klines(symbol: &str, interval: &str, limit: usize) -> Option<Vec<Candle>> {
110 let url = format!(
111 "https://api.binance.com/api/v3/klines?symbol={}&interval={}&limit={}",
112 symbol, interval, limit,
113 );
114 let resp = ureq::get(&url).call().ok()?;
115 let body: serde_json::Value = resp.into_json().ok()?;
116 let arr = body.as_array()?;
117
118 let mut candles = Vec::with_capacity(arr.len());
119 for kline in arr {
120 let k = kline.as_array()?;
121 let time = k[0].as_f64()? / 1000.0; let open = k[1].as_str()?.parse::<f64>().ok()?;
123 let high = k[2].as_str()?.parse::<f64>().ok()?;
124 let low = k[3].as_str()?.parse::<f64>().ok()?;
125 let close = k[4].as_str()?.parse::<f64>().ok()?;
126 let volume = k[5].as_str()?.parse::<f64>().ok()?;
127 candles.push(Candle {
128 time,
129 open,
130 high,
131 low,
132 close,
133 volume,
134 });
135 }
136 Some(candles)
137}
138
139fn fetch_binance_spot(symbol: &str) -> Option<f64> {
143 let url = format!(
144 "https://api.binance.com/api/v3/ticker/price?symbol={}",
145 symbol,
146 );
147 let resp = ureq::get(&url).call().ok()?;
148 let body: serde_json::Value = resp.into_json().ok()?;
149 body["price"].as_str()?.parse::<f64>().ok()
150}
151
152fn fetch_coingecko_ohlc(coin_id: &str, days: &str) -> Option<Vec<Candle>> {
161 let url = format!(
162 "https://api.coingecko.com/api/v3/coins/{}/ohlc?vs_currency=usd&days={}",
163 coin_id, days,
164 );
165 let resp = ureq::get(&url).call().ok()?;
166 let body: serde_json::Value = resp.into_json().ok()?;
167 let arr = body.as_array()?;
168
169 let mut candles = Vec::with_capacity(arr.len());
170 for item in arr {
171 let row = item.as_array()?;
172 let time = row[0].as_f64()? / 1000.0; let open = row[1].as_f64()?;
174 let high = row[2].as_f64()?;
175 let low = row[3].as_f64()?;
176 let close = row[4].as_f64()?;
177 candles.push(Candle {
178 time,
179 open,
180 high,
181 low,
182 close,
183 volume: 0.0, });
185 }
186 Some(candles)
187}
188
189fn fetch_coingecko_prices(coin_id: &str, days: &str) -> Option<Vec<Point>> {
194 let url = format!(
195 "https://api.coingecko.com/api/v3/coins/{}/market_chart?vs_currency=usd&days={}",
196 coin_id, days,
197 );
198 let resp = ureq::get(&url).call().ok()?;
199 let body: serde_json::Value = resp.into_json().ok()?;
200 let prices = body["prices"].as_array()?;
201
202 let mut points = Vec::with_capacity(prices.len());
203 for entry in prices {
204 let pair = entry.as_array()?;
205 let x = pair[0].as_f64()? / 1000.0; let y = pair[1].as_f64()?;
207 points.push(Point { x, y });
208 }
209 Some(points)
210}
211
212fn fetch_coingecko_spot(coin_id: &str) -> Option<f64> {
216 let url = format!(
217 "https://api.coingecko.com/api/v3/simple/price?ids={}&vs_currencies=usd",
218 coin_id,
219 );
220 let resp = ureq::get(&url).call().ok()?;
221 let body: serde_json::Value = resp.into_json().ok()?;
222 body[coin_id]["usd"].as_f64()
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_exchange_client_constructors() {
231 let b = ExchangeClient::binance();
232 assert_eq!(b.exchange, Exchange::Binance);
233
234 let c = ExchangeClient::coingecko();
235 assert_eq!(c.exchange, Exchange::CoinGecko);
236
237 let n = ExchangeClient::new(Exchange::Binance);
238 assert_eq!(n.exchange, Exchange::Binance);
239 }
240}