use crate::{fetch, DataError};
use pine_core::{Data, DataProvider, Ohlcv, ProviderError, SymInfo, Timeframe};
use serde::{de, Deserialize, Deserializer};
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct KrakenSource;
impl KrakenSource {
pub fn new() -> Self {
Self
}
}
#[derive(Debug, Deserialize)]
struct HttpResult {
#[serde(default)]
error: Vec<String>,
result: Option<OhlcResult>,
}
#[derive(Debug, Deserialize)]
struct OhlcResult {
#[allow(dead_code)]
last: i64,
#[serde(flatten)]
pairs: HashMap<String, Vec<Candle>>,
}
#[derive(Debug, Deserialize)]
struct Candle(
i64,
#[serde(deserialize_with = "quoted")] f64,
#[serde(deserialize_with = "quoted")] f64,
#[serde(deserialize_with = "quoted")] f64,
#[serde(deserialize_with = "quoted")] f64,
#[serde(deserialize_with = "quoted")] f64,
#[serde(deserialize_with = "quoted")] f64,
i64,
);
impl From<Candle> for Ohlcv {
fn from(candle: Candle) -> Self {
let Candle(time, open, high, low, close, _vwap, volume, _count) = candle;
Ohlcv {
time: time * 1000,
open,
high,
low,
close,
volume,
}
}
}
fn quoted<'de, D: Deserializer<'de>>(deserializer: D) -> Result<f64, D::Error> {
<&str>::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
impl DataProvider for KrakenSource {
fn request(&self, symbol: &str, timeframe: Timeframe) -> Result<Data, ProviderError> {
let pair = symbol.to_uppercase();
let minutes = timeframe.as_minutes().unwrap_or(60);
let url = format!("https://api.kraken.com/0/public/OHLC?pair={pair}&interval={minutes}");
let body = fetch(&url)?;
let bad = |message: String| DataError::Provider {
provider: "kraken",
message,
};
let response: HttpResult =
serde_json::from_str(&body).map_err(|e| bad(format!("{e}: {body:.200}")))?;
if !response.error.is_empty() {
return Err(bad(response.error.join(", ")).into());
}
let candles = response
.result
.and_then(|result| result.pairs.into_values().next())
.ok_or_else(|| bad(format!("no candles for {pair}")))?;
let rows = candles.into_iter().map(Ohlcv::from);
Ok(Data::from_ohlcv(rows).with_syminfo(SymInfo {
ticker: pair.clone(),
tickerid: format!("KRAKEN:{pair}"),
prefix: "KRAKEN".to_string(),
type_: "crypto".to_string(),
..SymInfo::default()
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kraken() {
let data = KrakenSource::new()
.request("XBTUSD", "60".parse().unwrap())
.unwrap();
assert_ne!(data.bars.len(), 0);
}
}