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}