adding openfigi as identifier for company data
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// src/corporate/openfigi.rs
|
||||
use super::{types::*};
|
||||
use reqwest::{Client as HttpClient, StatusCode};
|
||||
use reqwest::Client as HttpClient;
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -46,16 +46,12 @@ impl OpenFigiClient {
|
||||
let mut all_figis = Vec::new();
|
||||
let chunk_size = if self.has_key { 100 } else { 5 };
|
||||
|
||||
for (chunk_idx, chunk) in isins.chunks(chunk_size).enumerate() {
|
||||
let mut retries = 0;
|
||||
let mut success = false;
|
||||
|
||||
while retries < 3 && !success {
|
||||
for chunk in isins.chunks(chunk_size) {
|
||||
let jobs: Vec<Value> = chunk.iter()
|
||||
.map(|isin| json!({
|
||||
"idType": "ID_ISIN",
|
||||
"idValue": isin,
|
||||
"marketSecDes": "Equity",
|
||||
"marketSecDes": "Equity", // Pre-filter to equities
|
||||
}))
|
||||
.collect();
|
||||
|
||||
@@ -67,12 +63,32 @@ impl OpenFigiClient {
|
||||
.await?;
|
||||
|
||||
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 {
|
||||
StatusCode::OK => {
|
||||
let results: Vec<Value> = resp.json().await?;
|
||||
let mut chunk_figis = Vec::new();
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
if status == 401 {
|
||||
return Err(anyhow::anyhow!("Invalid OpenFIGI API key: {}", body));
|
||||
} 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) {
|
||||
if let Some(data) = result["data"].as_array() {
|
||||
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("Common") || sec_type.contains("Preferred") || sec_type == "ADR" || sec_type == "GDR") {
|
||||
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 {
|
||||
println!(" → Warning: No 'data' in response for ISIN {}", job);
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
sleep(Duration::from_millis(500)).await; // Slower without key
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
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());
|
||||
all_figis.dedup(); // Unique FIGIs per LEI
|
||||
Ok(all_figis)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user