fetcher: support new api

This commit is contained in:
nym21
2025-06-07 13:48:07 +02:00
parent fb3d8521cd
commit fa958b59bd
6 changed files with 194 additions and 182 deletions
+17 -1
View File
@@ -1,4 +1,7 @@
use std::ops::Add; use std::{
fmt,
ops::{Add, Rem},
};
use serde::Serialize; use serde::Serialize;
// use color_eyre::eyre::eyre; // use color_eyre::eyre::eyre;
@@ -77,3 +80,16 @@ impl CheckedSub for DateIndex {
self.0.checked_sub(rhs.0).map(Self) self.0.checked_sub(rhs.0).map(Self)
} }
} }
impl Rem<usize> for DateIndex {
type Output = Self;
fn rem(self, rhs: usize) -> Self::Output {
Self(self.0 % rhs as u16)
}
}
impl fmt::Display for DateIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
+6 -2
View File
@@ -1,11 +1,15 @@
use brk_core::Date; use brk_core::{Date, Height};
use brk_fetcher::Fetcher; use brk_fetcher::{BRK, Fetcher};
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
brk_logger::init(None); brk_logger::init(None);
let mut brk = BRK::default();
dbg!(brk.get_from_height(Height::new(900_000))?);
dbg!(brk.get_from_date(Date::new(2025, 6, 7))?);
let mut fetcher = Fetcher::import(None)?; let mut fetcher = Fetcher::import(None)?;
dbg!(fetcher.get_date(Date::new(2025, 6, 5))?); dbg!(fetcher.get_date(Date::new(2025, 6, 5))?);
+143
View File
@@ -0,0 +1,143 @@
use std::collections::BTreeMap;
use brk_core::{Cents, CheckedSub, Date, DateIndex, Height, OHLCCents};
use color_eyre::eyre::{ContextCompat, eyre};
use log::info;
use serde_json::Value;
use crate::{Close, Dollars, High, Low, Open, fetchers::retry};
#[derive(Default, Clone)]
pub struct BRK {
height_to_ohlc: BTreeMap<Height, Vec<OHLCCents>>,
dateindex_to_ohlc: BTreeMap<DateIndex, Vec<OHLCCents>>,
}
const API_URL: &str = "https://bitcoinresearchkit.org/api";
const RETRIES: usize = 10;
const CHUNK_SIZE: usize = 10_000;
impl BRK {
pub fn get_from_height(&mut self, height: Height) -> color_eyre::Result<OHLCCents> {
let key = height.checked_sub(height % CHUNK_SIZE).unwrap();
#[allow(clippy::map_entry)]
if !self.height_to_ohlc.contains_key(&key)
|| ((key + self.height_to_ohlc.get(&key).unwrap().len()) <= height)
{
self.height_to_ohlc.insert(
key,
Self::fetch_height_prices(key).inspect_err(|e| {
dbg!(e);
})?,
);
}
self.height_to_ohlc
.get(&key)
.unwrap()
.get(usize::from(height.checked_sub(key).unwrap()))
.cloned()
.ok_or(eyre!("Couldn't find height in kibo"))
}
fn fetch_height_prices(height: Height) -> color_eyre::Result<Vec<OHLCCents>> {
info!("Fetching Kibo height {height} prices...");
retry(
|_| {
let url = format!(
"{API_URL}/query?index=height&values=ohlc&from={}&to={}",
height,
height + CHUNK_SIZE
);
let body: Value = minreq::get(url).send()?.json()?;
body.as_array()
.context("Expect to be an array")?
.iter()
.map(Self::value_to_ohlc)
.collect::<Result<Vec<_>, _>>()
},
30,
RETRIES,
)
}
pub fn get_from_date(&mut self, date: Date) -> color_eyre::Result<OHLCCents> {
let dateindex = DateIndex::try_from(date)?;
let key = dateindex.checked_sub(dateindex % CHUNK_SIZE).unwrap();
#[allow(clippy::map_entry)]
if !self.dateindex_to_ohlc.contains_key(&key)
|| ((key + self.dateindex_to_ohlc.get(&key).unwrap().len()) <= dateindex)
{
self.dateindex_to_ohlc.insert(
key,
Self::fetch_date_prices(key).inspect_err(|e| {
dbg!(e);
})?,
);
}
self.dateindex_to_ohlc
.get(&key)
.unwrap()
.get(usize::from(dateindex.checked_sub(key).unwrap()))
.cloned()
.ok_or(eyre!("Couldn't find date in kibo"))
}
fn fetch_date_prices(dateindex: DateIndex) -> color_eyre::Result<Vec<OHLCCents>> {
info!("Fetching Kibo dateindex {dateindex} prices...");
retry(
|_| {
let url = format!(
"{API_URL}/query?index=dateindex&values=ohlc&from={}&to={}",
dateindex,
dateindex + CHUNK_SIZE
);
let body: Value = minreq::get(url).send()?.json()?;
body.as_array()
.context("Expect to be an array")?
.iter()
.map(Self::value_to_ohlc)
.collect::<Result<Vec<_>, _>>()
},
30,
RETRIES,
)
}
fn value_to_ohlc(value: &Value) -> color_eyre::Result<OHLCCents> {
let ohlc = value.as_array().context("Expect as_array to work")?;
let get_value = |index: usize| -> color_eyre::Result<_> {
Ok(Cents::from(Dollars::from(
ohlc.get(index)
.context("Expect index key to work")?
.as_f64()
.context("Expect as_f64 to work")?,
)))
};
Ok(OHLCCents::from((
Open::new(get_value(0)?),
High::new(get_value(1)?),
Low::new(get_value(2)?),
Close::new(get_value(3)?),
)))
}
pub fn clear(&mut self) {
self.height_to_ohlc.clear();
self.dateindex_to_ohlc.clear();
}
}
-155
View File
@@ -1,155 +0,0 @@
use std::{collections::BTreeMap, str::FromStr};
use brk_core::{CheckedSub, Date, Height, OHLCCents};
use color_eyre::eyre::{ContextCompat, eyre};
use log::info;
use serde_json::Value;
use crate::{Cents, Close, Dollars, High, Low, Open, fetchers::retry};
#[derive(Default, Clone)]
pub struct Kibo {
height_to_ohlc_vec: BTreeMap<Height, Vec<OHLCCents>>,
year_to_date_to_ohlc: BTreeMap<u16, BTreeMap<Date, OHLCCents>>,
}
const KIBO_OFFICIAL_URL: &str = "https://kibo.money/api";
const RETRIES: usize = 10;
impl Kibo {
pub fn get_from_height(&mut self, height: Height) -> color_eyre::Result<OHLCCents> {
let key = height.checked_sub(height % 10_000).unwrap_or_default();
#[allow(clippy::map_entry)]
if !self.height_to_ohlc_vec.contains_key(&key)
|| ((key + self.height_to_ohlc_vec.get(&key).unwrap().len()) <= height)
{
self.height_to_ohlc_vec.insert(
key,
Self::fetch_height_prices(key).inspect_err(|e| {
dbg!(e);
})?,
);
}
self.height_to_ohlc_vec
.get(&key)
.unwrap()
.get(usize::from(height.checked_sub(key).unwrap()))
.cloned()
.ok_or(eyre!("Couldn't find height in kibo"))
}
fn fetch_height_prices(height: Height) -> color_eyre::Result<Vec<OHLCCents>> {
info!("Fetching Kibo height {height} prices...");
retry(
|_| {
let url = format!("{KIBO_OFFICIAL_URL}/height-to-price?chunk={}", height);
let body: Value = minreq::get(url).send()?.json()?;
body.as_object()
.context("Expect to be an object")?
.get("dataset")
.context("Expect object to have dataset")?
.as_object()
.context("Expect to be an object")?
.get("map")
.context("Expect to have map")?
.as_array()
.context("Expect to be an array")?
.iter()
.map(Self::value_to_ohlc)
.collect::<Result<Vec<_>, _>>()
},
30,
RETRIES,
)
}
pub fn get_from_date(&mut self, date: &Date) -> color_eyre::Result<OHLCCents> {
let year = date.year();
#[allow(clippy::map_entry)]
if !self.year_to_date_to_ohlc.contains_key(&year)
|| self
.year_to_date_to_ohlc
.get(&year)
.unwrap()
.last_key_value()
.unwrap()
.0
<= date
{
self.year_to_date_to_ohlc
.insert(year, Self::fetch_date_prices(year)?);
}
self.year_to_date_to_ohlc
.get(&year)
.unwrap()
.get(date)
.cloned()
.ok_or(eyre!("Couldn't find date in kibo"))
}
fn fetch_date_prices(year: u16) -> color_eyre::Result<BTreeMap<Date, OHLCCents>> {
info!("Fetching Kibo date {year} prices...");
retry(
|_| {
let body: Value =
minreq::get(format!("{KIBO_OFFICIAL_URL}/date-to-price?chunk={}", year))
.send()?
.json()?;
body.as_object()
.context("Expect to be an object")?
.get("dataset")
.context("Expect object to have dataset")?
.as_object()
.context("Expect to be an object")?
.get("map")
.context("Expect to have map")?
.as_object()
.context("Expect to be an object")?
.iter()
.map(|(serialized_date, value)| -> color_eyre::Result<_> {
let date =
Date::from(jiff::civil::Date::from_str(serialized_date).unwrap());
Ok((date, Self::value_to_ohlc(value)?))
})
.collect::<Result<BTreeMap<_, _>, _>>()
},
30,
RETRIES,
)
}
fn value_to_ohlc(value: &Value) -> color_eyre::Result<OHLCCents> {
let ohlc = value.as_object().context("Expect as_object to work")?;
let get_value = |key: &str| -> color_eyre::Result<_> {
Ok(Cents::from(Dollars::from(
ohlc.get(key)
.context("Expect get key to work")?
.as_f64()
.context("Expect as_f64 to work")?,
)))
};
Ok(OHLCCents::from((
Open::new(get_value("open")?),
High::new(get_value("high")?),
Low::new(get_value("low")?),
Close::new(get_value("close")?),
)))
}
pub fn clear(&mut self) {
self.height_to_ohlc_vec.clear();
self.year_to_date_to_ohlc.clear();
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
mod binance; mod binance;
// mod kibo; mod brk;
mod kraken; mod kraken;
mod retry; mod retry;
pub use binance::*; pub use binance::*;
// pub use kibo::*; pub use brk::*;
pub use kraken::*; pub use kraken::*;
use retry::*; use retry::*;
+26 -22
View File
@@ -10,7 +10,7 @@ use color_eyre::eyre::Error;
mod fetchers; mod fetchers;
use fetchers::*; pub use fetchers::*;
use log::info; use log::info;
const TRIES: usize = 12 * 60; const TRIES: usize = 12 * 60;
@@ -19,7 +19,7 @@ const TRIES: usize = 12 * 60;
pub struct Fetcher { pub struct Fetcher {
binance: Binance, binance: Binance,
kraken: Kraken, kraken: Kraken,
// kibo: Kibo, brk: BRK,
} }
impl Fetcher { impl Fetcher {
@@ -31,7 +31,7 @@ impl Fetcher {
Ok(Self { Ok(Self {
binance: Binance::init(hars_path), binance: Binance::init(hars_path),
kraken: Kraken::default(), kraken: Kraken::default(),
// kibo: Kibo::default(), brk: BRK::default(),
}) })
} }
@@ -46,6 +46,10 @@ impl Fetcher {
// eprintln!("{e}"); // eprintln!("{e}");
self.kraken.get_from_1d(&date) self.kraken.get_from_1d(&date)
}) })
.or_else(|_| {
// eprintln!("{e}");
self.brk.get_from_date(date)
})
.or_else(|e| { .or_else(|e| {
sleep(Duration::from_secs(60)); sleep(Duration::from_secs(60));
@@ -94,28 +98,28 @@ impl Fetcher {
.get_from_1mn(timestamp, previous_timestamp) .get_from_1mn(timestamp, previous_timestamp)
.unwrap_or_else(|_report| { .unwrap_or_else(|_report| {
// // eprintln!("{_report}"); // // eprintln!("{_report}");
// self.kibo.get_from_height(height).unwrap_or_else(|_report| { self.brk.get_from_height(height).unwrap_or_else(|_report| {
// eprintln!("{_report}"); // eprintln!("{_report}");
sleep(Duration::from_secs(60)); sleep(Duration::from_secs(60));
if tries < TRIES { if tries < TRIES {
self.clear(); self.clear();
info!("Retrying to fetch height prices..."); info!("Retrying to fetch height prices...");
// dbg!((height, timestamp, previous_timestamp)); // dbg!((height, timestamp, previous_timestamp));
return self return self
.get_height_(height, timestamp, previous_timestamp, tries + 1) .get_height_(height, timestamp, previous_timestamp, tries + 1)
.unwrap(); .unwrap();
} }
info!("Failed to fetch height prices"); info!("Failed to fetch height prices");
let date = Date::from(timestamp); let date = Date::from(timestamp);
// eprintln!("{e}"); // eprintln!("{e}");
panic!( panic!(
" "
Can't find the price for: height: {height} - date: {date} Can't find the price for: height: {height} - date: {date}
1mn APIs are limited to the last 16 hours for Binance's and the last 10 hours for Kraken's 1mn APIs are limited to the last 16 hours for Binance's and the last 10 hours for Kraken's
How to fix this: How to fix this:
@@ -130,8 +134,8 @@ How to fix this:
8. Export to a har file (if there is no explicit button, click on the cog button) 8. Export to a har file (if there is no explicit button, click on the cog button)
9. Move the file to 'parser/imports/binance.har' 9. Move the file to 'parser/imports/binance.har'
" "
) )
// }) })
}) })
}); });
@@ -182,7 +186,7 @@ How to fix this:
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.binance.clear(); self.binance.clear();
// self.kibo.clear(); self.brk.clear();
self.kraken.clear(); self.kraken.clear();
} }
} }