adding openfigi as identifier for company data

This commit is contained in:
2025-11-25 22:18:57 +01:00
parent eeae94e041
commit 9db4320c40

View File

@@ -1,6 +1,6 @@
// src/corporate/openfigi.rs // src/corporate/openfigi.rs
use super::{types::*}; use super::{types::*};
use reqwest::{Client as HttpClient, StatusCode}; use reqwest::Client as HttpClient;
use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::header::{HeaderMap, HeaderValue};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -46,16 +46,12 @@ impl OpenFigiClient {
let mut all_figis = Vec::new(); let mut all_figis = Vec::new();
let chunk_size = if self.has_key { 100 } else { 5 }; let chunk_size = if self.has_key { 100 } else { 5 };
for (chunk_idx, chunk) in isins.chunks(chunk_size).enumerate() { for chunk in isins.chunks(chunk_size) {
let mut retries = 0;
let mut success = false;
while retries < 3 && !success {
let jobs: Vec<Value> = chunk.iter() let jobs: Vec<Value> = chunk.iter()
.map(|isin| json!({ .map(|isin| json!({
"idType": "ID_ISIN", "idType": "ID_ISIN",
"idValue": isin, "idValue": isin,
"marketSecDes": "Equity", "marketSecDes": "Equity", // Pre-filter to equities
})) }))
.collect(); .collect();
@@ -67,12 +63,32 @@ impl OpenFigiClient {
.await?; .await?;
let status = resp.status(); let status = resp.status();
println!(" → OpenFIGI batch {}/{}: status {}", chunk_idx + 1, isins.len() / chunk_size + 1, status); let headers = resp.headers().clone();
let body = resp.text().await.unwrap_or_default();
match status { if status.is_client_error() || status.is_server_error() {
StatusCode::OK => { if status == 401 {
let results: Vec<Value> = resp.json().await?; return Err(anyhow::anyhow!("Invalid OpenFIGI API key: {}", body));
let mut chunk_figis = Vec::new(); } else if status == 413 {
return Err(anyhow::anyhow!("Payload too large—reduce chunk size: {}", body));
} else if status == 429 {
let reset = headers
.get("ratelimit-reset")
.and_then(|v| v.to_str().ok())
.unwrap_or("10")
.parse::<u64>()
.unwrap_or(10);
println!("Rate limited—backing off {}s", reset);
sleep(Duration::from_secs(reset.max(10))).await;
continue;
}
return Err(anyhow::anyhow!("OpenFIGI error {}: {}", status, body));
}
// JSON aus dem *Body-String* parsen
let results: Vec<Value> = serde_json::from_str(&body)?;
for (job, result) in chunk.iter().zip(results) { for (job, result) in chunk.iter().zip(results) {
if let Some(data) = result["data"].as_array() { if let Some(data) = result["data"].as_array() {
for item in data { for item in data {
@@ -82,60 +98,22 @@ impl OpenFigiClient {
(sec_type.contains("Stock") || sec_type.contains("Share") || sec_type.contains("Equity") || (sec_type.contains("Stock") || sec_type.contains("Share") || sec_type.contains("Equity") ||
sec_type.contains("Common") || sec_type.contains("Preferred") || sec_type == "ADR" || sec_type == "GDR") { sec_type.contains("Common") || sec_type.contains("Preferred") || sec_type == "ADR" || sec_type == "GDR") {
if let Some(figi) = item["figi"].as_str() { if let Some(figi) = item["figi"].as_str() {
chunk_figis.push(figi.to_string()); all_figis.push(figi.to_string());
} }
} }
} }
}
}
// Rate limit respect: 6s between requests with key
if self.has_key {
sleep(Duration::from_secs(6)).await;
} else { } else {
println!(" → Warning: No 'data' in response for ISIN {}", job); sleep(Duration::from_millis(500)).await; // Slower without key
}
}
all_figis.extend(chunk_figis);
success = true;
}
StatusCode::TOO_MANY_REQUESTS => { // 429
if let Some(reset_header) = resp.headers().get("ratelimit-reset") {
if let Ok(reset_secs) = reset_header.to_str().unwrap_or("10").parse::<u64>() {
println!(" → Rate limited (429) — backing off {}s", reset_secs);
sleep(Duration::from_secs(reset_secs.max(10))).await;
}
} else {
sleep(Duration::from_secs(30)).await; // Default backoff
}
retries += 1;
}
StatusCode::UNAUTHORIZED => { // 401
return Err(anyhow::anyhow!("Invalid OpenFIGI API key — check .env"));
}
StatusCode::PAYLOAD_TOO_LARGE => { // 413
println!(" → Payload too large (413) — reducing chunk size for next try");
// Reduce chunk_size dynamically (stub: retry with half size)
sleep(Duration::from_secs(5)).await;
retries += 1;
}
_ if status.is_server_error() => { // 5xx
println!(" → Server error {} — retrying in {}s", status, 3u64.pow(retries as u32));
sleep(Duration::from_secs(3u64.pow(retries as u32))).await;
retries += 1;
}
_ => { // 4xx client errors (not retryable)
let text = resp.text().await.unwrap_or_default();
return Err(anyhow::anyhow!("OpenFIGI client error {}: {}", status, text));
}
} }
} }
if !success { all_figis.dedup(); // Unique FIGIs per LEI
println!(" → Failed chunk {} after 3 retries — skipping {} ISINs", chunk_idx + 1, chunk.len());
// Don't crash — continue with partial results
}
// Inter-batch delay (respect limits)
sleep(if self.has_key { Duration::from_secs(3) } else { Duration::from_millis(1000) }).await; // Safer: 20s/min effective
}
all_figis.dedup();
println!(" → Mapped {} unique equity FIGIs from {} ISINs", all_figis.len(), isins.len());
Ok(all_figis) Ok(all_figis)
} }
} }