use serde::Deserialize;
use crate::client::AkShareClient;
use crate::error::{Error, Result};
use crate::types::FuturesSnapshot;
use crate::util::today_iso;
#[derive(Debug, Deserialize)]
struct ClistEnvelope {
data: Option<ClistData>,
}
#[derive(Debug, Deserialize)]
struct ClistData {
diff: Option<Vec<ClistItem>>,
}
#[derive(Debug, Deserialize)]
struct ClistItem {
#[serde(rename = "f12")]
code: Option<String>,
#[serde(rename = "f14")]
name: Option<String>,
#[serde(rename = "f2")]
close: Option<f64>,
#[serde(rename = "f3")]
change_pct: Option<f64>,
#[serde(rename = "f5")]
volume: Option<f64>,
#[serde(rename = "f10")]
open_interest: Option<f64>,
}
const FUTURES_FS: &str = "m:113,m:114,m:115,m:8,m:142,m:225";
impl AkShareClient {
pub async fn futures_spot_prices(&self, limit: usize) -> Result<Vec<FuturesSnapshot>> {
let pz = limit.clamp(1, 500).to_string();
let response = self
.get("https://push2.eastmoney.com/api/qt/clist/get")
.query(&[
("pn", "1"),
("pz", pz.as_str()),
("po", "1"),
("np", "1"),
("fltt", "2"),
("invt", "2"),
("fid", "f3"),
("fs", FUTURES_FS),
("fields", "f12,f14,f2,f3,f5,f10"),
])
.send()
.await
.map_err(Error::from)?
.error_for_status()
.map_err(Error::from)?;
let payload: ClistEnvelope = response.json().await.map_err(Error::from)?;
let today = today_iso();
let items = payload
.data
.and_then(|d| d.diff)
.unwrap_or_default()
.into_iter()
.filter_map(|item| {
let code = item.code?;
if code.is_empty() {
return None;
}
Some(FuturesSnapshot {
symbol: code,
name: item.name.unwrap_or_else(|| "未知合约".to_string()),
date: today.clone(),
close: item.close.unwrap_or(0.0),
change_pct: item.change_pct.unwrap_or(0.0),
volume: item.volume.unwrap_or(0.0),
open_interest: item.open_interest.unwrap_or(0.0),
settlement_price: None,
})
})
.collect::<Vec<_>>();
if items.is_empty() {
return Err(Error::not_found(
"eastmoney returned no futures spot prices",
));
}
Ok(items)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_futures_fs_constant() {
assert!(FUTURES_FS.contains("m:113"));
assert!(FUTURES_FS.contains("m:114"));
assert!(FUTURES_FS.contains("m:115"));
assert!(FUTURES_FS.contains("m:8"));
}
}