use crate::client::AkShareClient;
use crate::error::{Error, Result};
use crate::types::Row;
impl AkShareClient {
pub async fn futures_contract_info_cffex(&self, date: &str) -> Result<Vec<Row>> {
let year = &date[..6];
let day = &date[6..];
let url = format!("http://www.cffex.com.cn/sj/jycs/{}/{}/index.xml", year, day);
let body = self
.get(&url)
.header("User-Agent", "Mozilla/5.0")
.send()
.await?
.text()
.await?;
let mut items = Vec::new();
let records: Vec<&str> = body.split("<INDEX>").collect();
for record in &records[1..] {
let end = record.find("</INDEX>").unwrap_or(record.len());
let record_text = &record[..end];
let mut r = Row::new();
for tag in &[
"TRADING_DAY",
"PRODUCT_ID",
"INSTRUMENT_ID",
"INSTRUMENT_MONTH",
"BASIS_PRICE",
"OPEN_DATE",
"END_TRADING_DAY",
"UPPER_VALUE",
"LOWER_VALUE",
"UPPERLIMITPRICE",
"LOWERLIMITPRICE",
"LONG_LIMIT",
] {
let open_tag = format!("<{}>", tag);
let close_tag = format!("</{}>", tag);
if let Some(start) = record_text.find(&open_tag) {
let value_start = start + open_tag.len();
if let Some(end_pos) = record_text[value_start..].find(&close_tag) {
let value = &record_text[value_start..value_start + end_pos];
let key = match *tag {
"TRADING_DAY" => "trading_day",
"PRODUCT_ID" => "product_id",
"INSTRUMENT_ID" => "instrument_id",
"INSTRUMENT_MONTH" => "instrument_month",
"BASIS_PRICE" => "basis_price",
"OPEN_DATE" => "open_date",
"END_TRADING_DAY" => "end_trading_day",
"UPPER_VALUE" => "upper_value",
"LOWER_VALUE" => "lower_value",
"UPPERLIMITPRICE" => "upper_limit_price",
"LOWERLIMITPRICE" => "lower_limit_price",
"LONG_LIMIT" => "long_limit",
_ => continue,
};
r.insert(key.into(), serde_json::json!(value));
}
}
}
if !r.is_empty() {
items.push(r);
}
}
Ok(items)
}
pub async fn futures_contract_info_czce(&self, date: &str) -> Result<Vec<Row>> {
let url = format!(
"http://www.czce.com.cn/cn/DFSStaticFiles/Future/{}/FutureDataReferenceData.xml",
date
);
let body = self
.get(&url)
.header("User-Agent", "Mozilla/5.0")
.header("Host", "www.czce.com.cn")
.send()
.await?
.text()
.await?;
let mut items = Vec::new();
let records: Vec<&str> = body.split("<Contract>").collect();
for record in &records[1..] {
let end = record.find("</Contract>").unwrap_or(record.len());
let record_text = &record[..end];
let mut r = Row::new();
for tag in &[
"Name",
"CtrCd",
"PrdCd",
"PrdTp",
"ExchCd",
"SegTp",
"TrdHrs",
"TrdCtyCd",
"TrdCcyCd",
"ClrngCcyCd",
"ExpiryTime",
"SettleTp",
"Duration",
"TckSz",
"TckVal",
"CtrSz",
"MsrmntUnt",
"MaxOrdSz",
"MnthPosLmt",
"MinBlckTrdSz",
"CesrEaaFl",
"FlexElgblFl",
"ListCy",
"DlvryNtcDt",
"FrstTrdDt",
"LstTrdDt",
"DlvrySettleDt",
"MnthCd",
"YrCd",
"LstDlvryDt",
"LstDlvryDtBoard",
"DlvryMnth",
"Margin",
"PxLim",
"FeeCcy",
"TrdFee",
"FeeCollectionType",
"DlvryFee",
"IntraDayTrdFee",
"TradingLimit",
] {
let open_tag = format!("<{}>", tag);
let close_tag = format!("</{}>", tag);
if let Some(start) = record_text.find(&open_tag) {
let value_start = start + open_tag.len();
if let Some(end_pos) = record_text[value_start..].find(&close_tag) {
let value = &record_text[value_start..value_start + end_pos];
r.insert(tag.to_string(), serde_json::json!(value));
}
}
}
if !r.is_empty() {
items.push(r);
}
}
Ok(items)
}
pub async fn futures_contract_info_dce(&self) -> Result<Vec<Row>> {
let url = "http://www.dce.com.cn/dcereport/publicweb/tradepara/contractInfo";
let payload = serde_json::json!({
"lang": "zh",
"tradeType": "1",
"varietyId": "all",
});
let body = self.post(url).json(&payload).send().await?.text().await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["data"].as_array().cloned().unwrap_or_default();
let mut items = Vec::new();
for mut row in rows {
let mut r = Row::new();
r.insert(
"variety".into(),
row.as_object_mut()
.and_then(|m| m.remove("variety"))
.unwrap_or_default(),
);
r.insert(
"contract_id".into(),
row.as_object_mut()
.and_then(|m| m.remove("contractId"))
.unwrap_or_default(),
);
r.insert(
"unit".into(),
row.as_object_mut()
.and_then(|m| m.remove("unit"))
.unwrap_or_default(),
);
r.insert(
"tick".into(),
row.as_object_mut()
.and_then(|m| m.remove("tick"))
.unwrap_or_default(),
);
r.insert(
"start_trade_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("startTradeDate"))
.unwrap_or_default(),
);
r.insert(
"end_trade_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("endTradeDate"))
.unwrap_or_default(),
);
r.insert(
"end_delivery_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("endDeliveryDate"))
.unwrap_or_default(),
);
items.push(r);
}
Ok(items)
}
pub async fn futures_contract_info_gfex(&self) -> Result<Vec<Row>> {
let url = "http://www.gfex.com.cn/u/interfacesWebTtQueryContractInfo/loadList";
let body = self
.post(url)
.query(&[("variety", ""), ("trade_type", "0")])
.header("User-Agent", "Mozilla/5.0")
.send()
.await?
.text()
.await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["data"].as_array().cloned().unwrap_or_default();
let mut items = Vec::new();
for mut row in rows {
let mut r = Row::new();
r.insert(
"variety".into(),
row.as_object_mut()
.and_then(|m| m.remove("variety"))
.unwrap_or_default(),
);
r.insert(
"contract_id".into(),
row.as_object_mut()
.and_then(|m| m.remove("contractId"))
.unwrap_or_default(),
);
r.insert(
"unit".into(),
row.as_object_mut()
.and_then(|m| m.remove("unit"))
.unwrap_or_default(),
);
r.insert(
"tick".into(),
row.as_object_mut()
.and_then(|m| m.remove("tick"))
.unwrap_or_default(),
);
r.insert(
"start_trade_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("startTradeDate"))
.unwrap_or_default(),
);
r.insert(
"end_trade_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("endTradeDate"))
.unwrap_or_default(),
);
r.insert(
"end_delivery_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("endDeliveryDate0"))
.unwrap_or_default(),
);
items.push(r);
}
Ok(items)
}
pub async fn futures_contract_info_ine(&self, date: &str) -> Result<Vec<Row>> {
let url = format!(
"https://www.ine.cn/data/busiparamdata/future/ContractBaseInfo{}.dat",
date
);
let body = self
.get(&url)
.query(&[("rnd", "0.8312696798757147")])
.header("User-Agent", "Mozilla/5.0")
.send()
.await?
.text()
.await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["ContractBaseInfo"]
.as_array()
.cloned()
.unwrap_or_default();
let mut items = Vec::new();
for mut row in rows {
let mut r = Row::new();
r.insert(
"instrument_id".into(),
row.as_object_mut()
.and_then(|m| m.remove("INSTRUMENTID"))
.unwrap_or_default(),
);
r.insert(
"open_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("OPENDATE"))
.unwrap_or_default(),
);
r.insert(
"expire_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("EXPIREDATE"))
.unwrap_or_default(),
);
r.insert(
"start_delivery_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("STARTDELIVDATE"))
.unwrap_or_default(),
);
r.insert(
"end_delivery_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("ENDDELIVDATE"))
.unwrap_or_default(),
);
r.insert(
"basis_price".into(),
row.as_object_mut()
.and_then(|m| m.remove("BASISPRICE"))
.unwrap_or_default(),
);
r.insert(
"trading_day".into(),
row.as_object_mut()
.and_then(|m| m.remove("TRADINGDAY"))
.unwrap_or_default(),
);
items.push(r);
}
Ok(items)
}
pub async fn futures_contract_info_shfe(&self, date: &str) -> Result<Vec<Row>> {
let url = format!(
"https://www.shfe.com.cn/data/busiparamdata/future/ContractBaseInfo{}.dat",
date
);
let body = self
.get(&url)
.header("User-Agent", "Mozilla/5.0")
.send()
.await?
.text()
.await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["ContractBaseInfo"]
.as_array()
.cloned()
.unwrap_or_default();
let mut items = Vec::new();
for mut row in rows {
let mut r = Row::new();
r.insert(
"instrument_id".into(),
row.as_object_mut()
.and_then(|m| m.remove("INSTRUMENTID"))
.unwrap_or_default(),
);
r.insert(
"open_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("OPENDATE"))
.unwrap_or_default(),
);
r.insert(
"expire_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("EXPIREDATE"))
.unwrap_or_default(),
);
r.insert(
"start_delivery_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("STARTDELIVDATE"))
.unwrap_or_default(),
);
r.insert(
"end_delivery_date".into(),
row.as_object_mut()
.and_then(|m| m.remove("ENDDELIVDATE"))
.unwrap_or_default(),
);
r.insert(
"basis_price".into(),
row.as_object_mut()
.and_then(|m| m.remove("BASISPRICE"))
.unwrap_or_default(),
);
r.insert(
"trading_day".into(),
row.as_object_mut()
.and_then(|m| m.remove("TRADINGDAY"))
.unwrap_or_default(),
);
if let Some(update_date) = data.get("update_date") {
r.insert("update_date".into(), update_date.clone());
}
items.push(r);
}
Ok(items)
}
pub async fn futures_hog_core(&self, symbol: &str) -> Result<Vec<Row>> {
let ptype = match symbol {
"外三元" => "1",
"内三元" => "2",
"土杂猪" => "3",
_ => {
return Err(Error::invalid_input(format!(
"unsupported hog core symbol: {}",
symbol
)));
}
};
let url = "https://xt.yangzhu.vip/data/getzhujiahitsdata";
let body = self
.post(url)
.query(&[("ptype", ptype), ("areano", "-1"), ("datetype", "0")])
.send()
.await?
.text()
.await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["data"].as_array().cloned().unwrap_or_default();
let mut items = Vec::new();
for row in rows {
let Some(arr) = row.as_array() else {
continue;
};
if arr.len() < 2 {
continue;
}
let mut r = Row::new();
r.insert("date".into(), arr[1].clone());
r.insert("value".into(), arr[0].clone());
items.push(r);
}
Ok(items)
}
pub async fn futures_hog_cost(&self, symbol: &str) -> Result<Vec<Row>> {
let (ptype, url) = match symbol {
"玉米" => ("4", "https://xt.yangzhu.vip/data/getzhujiahitsdata"),
"豆粕" => ("5", "https://xt.yangzhu.vip/data/getzhujiahitsdata"),
"二元母猪价格" => ("1", "https://xt.yangzhu.vip/data/getmapdata"),
"仔猪价格" => ("2", "https://xt.yangzhu.vip/data/getmapdata"),
_ => {
return Err(Error::invalid_input(format!(
"unsupported hog cost symbol: {}",
symbol
)));
}
};
let body = self
.post(url)
.query(&[("ptype", ptype), ("areano", "-1")])
.send()
.await?
.text()
.await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["data"].as_array().cloned().unwrap_or_default();
let mut items = Vec::new();
for row in rows {
let Some(arr) = row.as_array() else {
continue;
};
let mut r = Row::new();
if symbol == "玉米" || symbol == "豆粕" {
if arr.len() >= 2 {
r.insert("date".into(), arr[1].clone());
r.insert("value".into(), arr[0].clone());
}
} else {
if arr.len() >= 2 {
r.insert("date".into(), arr[0].clone());
r.insert("value".into(), arr[1].clone());
}
}
if !r.is_empty() {
items.push(r);
}
}
Ok(items)
}
pub async fn futures_hog_supply(&self, symbol: &str) -> Result<Vec<Row>> {
let ptype = match symbol {
"猪肉批发价" => "3",
"储备冻猪肉" => "4",
"饲料原料数据" => "5",
"白条肉" => "6",
"生猪产能" => "7",
"育肥猪" => "9",
"肉类价格指数" => "10",
"猪粮比价" => "11",
_ => {
return Err(Error::invalid_input(format!(
"unsupported hog supply symbol: {}",
symbol
)));
}
};
let url = "https://xt.yangzhu.vip/data/getmapdata";
let body = self
.post(url)
.query(&[("ptype", ptype), ("areano", "-1")])
.send()
.await?
.text()
.await?;
let data: serde_json::Value = serde_json::from_str(&body)?;
let rows = data["data"].as_array().cloned().unwrap_or_default();
let mut items = Vec::new();
for row in rows {
let Some(arr) = row.as_array() else {
continue;
};
let mut r = Row::new();
match symbol {
"猪肉批发价" | "肉类价格指数" => {
if arr.len() >= 3 {
r.insert("date".into(), arr[0].clone());
r.insert("value".into(), arr[2].clone());
}
}
"饲料原料数据" => {
if arr.len() >= 5 {
r.insert("period".into(), arr[0].clone());
r.insert("soybean_import".into(), arr[1].clone());
r.insert("soybean_area".into(), arr[2].clone());
r.insert("corn_import".into(), arr[3].clone());
r.insert("corn_area".into(), arr[4].clone());
}
}
"白条肉" => {
if arr.len() >= 4 {
r.insert("period".into(), arr[0].clone());
r.insert("price".into(), arr[1].clone());
r.insert("mom".into(), arr[2].clone());
r.insert("yoy".into(), arr[3].clone());
}
}
"生猪产能" => {
if arr.len() >= 5 {
r.insert("period".into(), arr[0].clone());
r.insert("sow_inventory".into(), arr[1].clone());
r.insert("pork_output".into(), arr[2].clone());
r.insert("hog_inventory".into(), arr[3].clone());
r.insert("hog_output".into(), arr[4].clone());
}
}
"育肥猪" => {
if arr.len() >= 2 {
r.insert("date".into(), arr[0].clone());
r.insert("value".into(), arr[1].clone());
}
}
_ => {
if arr.len() >= 2 {
r.insert("date".into(), arr[0].clone());
r.insert("value".into(), arr[1].clone());
}
}
}
if !r.is_empty() {
items.push(r);
}
}
Ok(items)
}
pub async fn futures_display_main_sina(&self) -> Result<Vec<Row>> {
let mut all_items = Vec::new();
for exchange in &["dce", "czce", "shfe", "cffex", "gfex"] {
let items = self.match_main_contract_sina(exchange).await?;
all_items.extend(items);
}
Ok(all_items)
}
async fn match_main_contract_sina(&self, exchange: &str) -> Result<Vec<Row>> {
let symbols = self.zh_subscribe_exchange_symbol(exchange).await?;
let mut items = Vec::new();
for sym_entry in &symbols {
let node = match sym_entry.get("mark").and_then(|v| v.as_str()) {
Some(n) => n,
None => continue,
};
let url = "https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQFuturesData";
let body = self
.get(url)
.query(&[
("page", "1"),
("sort", "position"),
("asc", "0"),
("node", node),
("base", "futures"),
])
.header("Referer", "https://vip.stock.finance.sina.com.cn/")
.send()
.await?
.text()
.await?;
let data: Vec<serde_json::Value> = match serde_json::from_str(&body) {
Ok(d) => d,
Err(_) => continue,
};
for entry in &data {
let name = entry["name"].as_str().unwrap_or("");
let symbol = entry["symbol"].as_str().unwrap_or("");
if name.contains("连续") && symbol.ends_with('0') {
let mut r = Row::new();
r.insert("exchange".into(), serde_json::json!(exchange));
r.insert("symbol".into(), serde_json::json!(symbol));
r.insert("name".into(), serde_json::json!(name));
items.push(r);
break;
}
}
}
Ok(items)
}
async fn zh_subscribe_exchange_symbol(&self, exchange: &str) -> Result<Vec<Row>> {
let url = "https://vip.stock.finance.sina.com.cn/quotes_service/view/js/qihuohangqing.js";
let body = self
.get(url)
.header("Referer", "https://vip.stock.finance.sina.com.cn/")
.send()
.await?
.text()
.await?;
let json_str = body
.find('{')
.and_then(|start| {
body[start..]
.find('}')
.map(|end| &body[start..start + end + 1])
})
.ok_or_else(|| Error::decode("sina exchange symbols: invalid JS response"))?;
let data: serde_json::Value = serde_json::from_str(json_str)
.map_err(|_| Error::decode("sina exchange symbols: JSON parse error"))?;
let arr = data[exchange].as_array().cloned().unwrap_or_default();
let mut items = Vec::new();
for entry in arr.iter().skip(1) {
if let Some(arr_entry) = entry.as_array() {
if arr_entry.len() >= 2 {
let mut r = Row::new();
r.insert("symbol".into(), arr_entry[0].clone());
r.insert("mark".into(), arr_entry[1].clone());
items.push(r);
}
}
}
Ok(items)
}
pub async fn futures_main_sina_derivative(
&self,
symbol: &str,
start_date: &str,
end_date: &str,
) -> Result<Vec<Row>> {
let date = "20210817";
let date_formatted = format!("{}_{}_{}", &date[..4], &date[4..6], &date[6..8]);
let url = format!(
"https://stock2.finance.sina.com.cn/futures/api/jsonp.php/var%20_{}{}=/InnerFuturesNewService.getDailyKLine?symbol={}&_={}",
symbol, date_formatted, symbol, date_formatted
);
let body = self
.get(&url)
.header("Referer", "https://finance.sina.com.cn")
.send()
.await?
.text()
.await?;
let json_str = body
.find("[[")
.and_then(|start| {
body[start..]
.rfind("]]")
.map(|end| &body[start..start + end + 2])
})
.ok_or_else(|| Error::decode("sina main derivative: invalid JSONP response"))?;
let rows: Vec<Vec<serde_json::Value>> = serde_json::from_str(json_str)
.map_err(|_| Error::decode("sina main derivative: JSON parse error"))?;
let start_dt = chrono::NaiveDate::parse_from_str(start_date, "%Y%m%d")
.unwrap_or(chrono::NaiveDate::MIN);
let end_dt =
chrono::NaiveDate::parse_from_str(end_date, "%Y%m%d").unwrap_or(chrono::NaiveDate::MAX);
let mut items = Vec::new();
for row in rows {
if row.len() < 8 {
continue;
}
let date_str = row[0].as_str().unwrap_or("");
let dt = chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d");
if let Ok(dt) = dt {
if dt < start_dt || dt > end_dt {
continue;
}
}
let mut r = Row::new();
r.insert("date".into(), row[0].clone());
r.insert("open".into(), row[1].clone());
r.insert("high".into(), row[2].clone());
r.insert("low".into(), row[3].clone());
r.insert("close".into(), row[4].clone());
r.insert("volume".into(), row[5].clone());
r.insert("hold".into(), row[6].clone());
r.insert("settle".into(), row[7].clone());
items.push(r);
}
Ok(items)
}
pub async fn futures_spot_sys(&self, symbol: &str, indicator: &str) -> Result<Vec<Row>> {
let mut items = Vec::new();
let mut r = Row::new();
r.insert("symbol".into(), serde_json::json!(symbol));
r.insert("indicator".into(), serde_json::json!(indicator));
r.insert("source".into(), serde_json::json!("100ppi.com"));
r.insert(
"note".into(),
serde_json::json!("requires HTML scraping from 100ppi.com"),
);
items.push(r);
Ok(items)
}
}