ig_client/model/utils.rs
1/******************************************************************************
2 Author: Joaquín Béjar García
3 Email: jb@taunais.com
4 Date: 20/10/25
5******************************************************************************/
6use crate::prelude::{
7 AppError, Client, IgResult, MarketData, MarketNavigationResponse, MarketNode, MarketService,
8};
9use std::future::Future;
10use std::pin::Pin;
11use tracing::{debug, error, info};
12
13/// Builds a market hierarchy recursively by traversing the navigation tree
14///
15/// # Arguments
16/// * `client` - Reference to the IG client
17/// * `node_id` - Optional node ID to start from (None for root)
18/// * `depth` - Current depth in the hierarchy (used to prevent infinite recursion)
19///
20/// # Returns
21/// Vector of MarketNode representing the hierarchy at this level
22pub fn build_market_hierarchy<'a>(
23 client: &'a Client,
24 node_id: Option<&'a str>,
25 depth: usize,
26) -> Pin<Box<dyn Future<Output = IgResult<Vec<MarketNode>>> + 'a>> {
27 Box::pin(async move {
28 // Limit the depth to avoid infinite loops
29 if depth > 7 {
30 debug!("Reached maximum depth of 5, stopping recursion");
31 return Ok(Vec::new());
32 }
33
34 // Get the nodes and markets at the current level
35 let navigation: MarketNavigationResponse = match node_id {
36 Some(id) => {
37 debug!("Getting navigation node: {}", id);
38 match client.get_market_navigation_node(id).await {
39 Ok(response) => {
40 debug!(
41 "Response received for node {}: {} nodes, {} markets",
42 id,
43 response.nodes.len(),
44 response.markets.len()
45 );
46 response
47 }
48 Err(e) => {
49 error!("Error getting node {}: {:?}", id, e);
50 // If we hit a rate limit, return empty results instead of failing
51 if matches!(e, AppError::RateLimitExceeded | AppError::Unexpected(_)) {
52 info!("Rate limit or API error encountered, returning partial results");
53 return Ok(Vec::new());
54 }
55 return Err(e);
56 }
57 }
58 }
59 None => {
60 debug!("Getting top-level navigation nodes");
61 match client.get_market_navigation().await {
62 Ok(response) => {
63 debug!(
64 "Response received for top-level nodes: {} nodes, {} markets",
65 response.nodes.len(),
66 response.markets.len()
67 );
68 response
69 }
70 Err(e) => {
71 error!("Error getting top-level nodes: {:?}", e);
72 return Err(e);
73 }
74 }
75 }
76 };
77
78 let mut nodes = Vec::new();
79
80 // Process all nodes at this level
81 let nodes_to_process = navigation.nodes;
82
83 // Process nodes sequentially with rate limiting
84 // This is important to respect the API rate limits
85 // By processing nodes sequentially, we allow the rate limiter
86 // to properly control the flow of requests
87 for node in nodes_to_process.into_iter() {
88 // Recursively get the children of this node
89 match build_market_hierarchy(client, Some(&node.id), depth + 1).await {
90 Ok(children) => {
91 info!("Adding node {} with {} children", node.name, children.len());
92 nodes.push(MarketNode {
93 id: node.id.clone(),
94 name: node.name.clone(),
95 children,
96 markets: Vec::new(),
97 });
98 }
99 Err(e) => {
100 error!("Error building hierarchy for node {}: {:?}", node.id, e);
101 // Continuar con otros nodos incluso si uno falla
102 if depth < 7 {
103 nodes.push(MarketNode {
104 id: node.id.clone(),
105 name: format!("{} (error: {})", node.name, e),
106 children: Vec::new(),
107 markets: Vec::new(),
108 });
109 }
110 }
111 }
112 }
113
114 // Process all markets in this node
115 let markets_to_process = navigation.markets;
116 for market in markets_to_process {
117 debug!("Adding market: {}", market.instrument_name);
118 nodes.push(MarketNode {
119 id: market.epic.clone(),
120 name: market.instrument_name.clone(),
121 children: Vec::new(),
122 markets: vec![market],
123 });
124 }
125
126 Ok(nodes)
127 })
128}
129
130/// Recursively extract all markets from the hierarchy into a flat list
131pub fn extract_markets_from_hierarchy(nodes: &[MarketNode]) -> Vec<MarketData> {
132 let mut all_markets = Vec::new();
133
134 for node in nodes {
135 // Add markets from this node
136 all_markets.extend(node.markets.clone());
137
138 // Recursively add markets from child nodes
139 if !node.children.is_empty() {
140 all_markets.extend(extract_markets_from_hierarchy(&node.children));
141 }
142 }
143
144 all_markets
145}