use crate::client::AkShareClient;
use crate::error::{Error, Result};
use crate::types::{QvixDailyPoint, QvixMinPoint};
use crate::util::parse_f64_safe;
const COLS_50ETF: [usize; 4] = [1, 2, 3, 4];
const COLS_300ETF: [usize; 4] = [9, 10, 11, 12];
const COLS_300INDEX: [usize; 4] = [17, 18, 19, 20];
const COLS_1000INDEX: [usize; 4] = [25, 26, 27, 28];
const COLS_500ETF: [usize; 4] = [67, 68, 69, 70];
const COLS_CYB: [usize; 4] = [71, 72, 73, 74];
const COLS_100ETF: [usize; 4] = [75, 76, 77, 78];
const COLS_50INDEX: [usize; 4] = [79, 80, 81, 82];
const COLS_KCB: [usize; 4] = [83, 84, 85, 86];
async fn fetch_qvix_daily(
client: &AkShareClient,
cols: &[usize; 4],
) -> Result<Vec<QvixDailyPoint>> {
let body = crate::util::send_and_check(client.get("http://1.optbbs.com/d/csv/d/k.csv"))
.await?
.text()
.await
.map_err(Error::from)?;
let mut points = Vec::new();
for (i, line) in body.lines().enumerate() {
if i == 0 {
continue; }
let fields: Vec<&str> = line.split(',').collect();
if fields.len() <= cols[3] {
continue;
}
let date = fields[0].trim().to_string();
if date.is_empty() {
continue;
}
points.push(QvixDailyPoint {
date,
open: parse_f64_safe(fields[cols[0]]),
high: parse_f64_safe(fields[cols[1]]),
low: parse_f64_safe(fields[cols[2]]),
close: parse_f64_safe(fields[cols[3]]),
});
}
if points.is_empty() {
return Err(Error::not_found("optbbs returned no daily QVIX data"));
}
Ok(points)
}
async fn fetch_qvix_min(client: &AkShareClient, url: &str) -> Result<Vec<QvixMinPoint>> {
let body = crate::util::send_and_check(client.get(url))
.await?
.text()
.await
.map_err(Error::from)?;
let mut points = Vec::new();
for (i, line) in body.lines().enumerate() {
if i == 0 {
continue;
}
let fields: Vec<&str> = line.split(',').collect();
if fields.len() < 2 {
continue;
}
let time = fields[0].trim().to_string();
let qvix = parse_f64_safe(fields[1]);
if time.is_empty() {
continue;
}
points.push(QvixMinPoint { time, qvix });
}
if points.is_empty() {
return Err(Error::not_found("optbbs returned no intraday QVIX data"));
}
Ok(points)
}
macro_rules! qvix_daily {
($(#[$meta:meta])* $name:ident, $cols:expr) => {
$(#[$meta])*
pub async fn $name(&self) -> Result<Vec<QvixDailyPoint>> {
fetch_qvix_daily(self, $cols).await
}
};
}
macro_rules! qvix_min {
($(#[$meta:meta])* $name:ident, $url:expr) => {
$(#[$meta])*
pub async fn $name(&self) -> Result<Vec<QvixMinPoint>> {
fetch_qvix_min(self, $url).await
}
};
}
impl AkShareClient {
qvix_daily!( index_option_50etf_qvix, &COLS_50ETF);
qvix_daily!( index_option_300etf_qvix, &COLS_300ETF);
qvix_daily!( index_option_500etf_qvix, &COLS_500ETF);
qvix_daily!( index_option_cyb_qvix, &COLS_CYB);
qvix_daily!( index_option_kcb_qvix, &COLS_KCB);
qvix_daily!( index_option_100etf_qvix, &COLS_100ETF);
qvix_daily!( index_option_300index_qvix, &COLS_300INDEX);
qvix_daily!( index_option_1000index_qvix, &COLS_1000INDEX);
qvix_daily!( index_option_50index_qvix, &COLS_50INDEX);
qvix_min!( index_option_50etf_min_qvix, "http://1.optbbs.com/d/csv/d/vix50.csv");
qvix_min!( index_option_300etf_min_qvix, "http://1.optbbs.com/d/csv/d/vix300.csv");
qvix_min!( index_option_500etf_min_qvix, "http://1.optbbs.com/d/csv/d/vix500.csv");
qvix_min!( index_option_cyb_min_qvix, "http://1.optbbs.com/d/csv/d/vixcyb.csv");
qvix_min!( index_option_kcb_min_qvix, "http://1.optbbs.com/d/csv/d/vixkcb.csv");
qvix_min!( index_option_100etf_min_qvix, "http://1.optbbs.com/d/csv/d/vix100.csv");
qvix_min!( index_option_300index_min_qvix, "http://1.optbbs.com/d/csv/d/vixindex.csv");
qvix_min!( index_option_1000index_min_qvix, "http://1.optbbs.com/d/csv/d/vixindex1000.csv");
qvix_min!( index_option_50index_min_qvix, "http://1.optbbs.com/d/csv/d/vix50index.csv");
}
#[cfg(test)]
mod tests {
#[test]
fn test_qvix_col_indices() {
assert_eq!(super::COLS_50ETF, [1, 2, 3, 4]);
assert_eq!(super::COLS_300ETF, [9, 10, 11, 12]);
assert_eq!(super::COLS_KCB, [83, 84, 85, 86]);
}
}