Pārlūkot izejas kodu

fix a fetch op. from binance api with hist. data limiter
-- adjust the requested range if needed to get max available data
-- add missing timeframe supports on open interest fetch
-- some chores to hide more internals from child states

Berke 9 mēneši atpakaļ
vecāks
revīzija
f7ce619819

+ 26 - 2
src/data_providers/binance.rs

@@ -1070,6 +1070,8 @@ struct DeOpenInterest {
     pub sum: f32,
 }
 
+const THIRTY_DAYS_MS: i64 = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
+
 pub async fn fetch_historical_oi(
     ticker: Ticker, 
     range: Option<(i64, i64)>,
@@ -1081,6 +1083,7 @@ pub async fn fetch_historical_oi(
         Timeframe::M15 => "15m",
         Timeframe::M30 => "30m",
         Timeframe::H1 => "1h",
+        Timeframe::H2 => "2h",
         Timeframe::H4 => "4h",
         _ => {
             let err_msg = format!("Unsupported timeframe for open interest: {}", period);
@@ -1095,12 +1098,33 @@ pub async fn fetch_historical_oi(
     );
 
     if let Some((start, end)) = range {
+        // This API seems to be limited to 30 days of historical data
+        let thirty_days_ago = std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .expect("Could not get system time")
+            .as_millis() as i64 - THIRTY_DAYS_MS;
+        
+        if end < thirty_days_ago {
+            let err_msg = format!(
+                "Requested end time {} is before available data (30 days is the API limit)", end
+            );
+            log::error!("{}", err_msg);
+            return Err(StreamError::UnknownError(err_msg));
+        }
+
+        let adjusted_start = if start < thirty_days_ago {
+            log::warn!("Adjusting start time from {} to {} (30 days limit)", start, thirty_days_ago);
+            thirty_days_ago
+        } else {
+            start
+        };
+
         let interval_ms = period.to_milliseconds() as i64;
-        let num_intervals = ((end - start) / interval_ms).min(500);
+        let num_intervals = ((end - adjusted_start) / interval_ms).min(500);
 
         if num_intervals > 1 {
             url.push_str(&format!(
-                "&startTime={start}&endTime={end}&limit={num_intervals}"
+                "&startTime={adjusted_start}&endTime={end}&limit={num_intervals}"
             ));
         } else {
             url.push_str("&limit=200");

+ 1 - 0
src/data_providers/bybit.rs

@@ -501,6 +501,7 @@ pub async fn fetch_historical_oi(
         Timeframe::M15 => "15min",
         Timeframe::M30 => "30min",
         Timeframe::H1 => "1h",
+        Timeframe::H2 => "2h",
         Timeframe::H4 => "4h",
         _ => {
             let err_msg = format!("Unsupported timeframe for open interest: {}", period);

+ 28 - 21
src/main.rs

@@ -134,18 +134,18 @@ enum Message {
 
 struct State {
     theme: Theme,
+    main_window: Window,
+    timezone: UserTimezone,
+    layout_locked: bool,
+    confirmation_dialog: Option<String>,
     layouts: HashMap<layout::LayoutId, Dashboard>,
     last_active_layout: layout::LayoutId,
-    main_window: Window,
     active_modal: DashboardModal,
     sidebar_location: Sidebar,
     notification: Option<Notification>,
-    ticker_info_map: HashMap<Exchange, HashMap<Ticker, Option<TickerInfo>>>,
     show_tickers_dashboard: bool,
     tickers_table: TickersTable,
-    confirmation_dialog: Option<String>,
-    layout_locked: bool,
-    timezone: UserTimezone,
+    tickers_info: HashMap<Exchange, HashMap<Ticker, Option<TickerInfo>>>,
 }
 
 #[allow(dead_code)]
@@ -158,12 +158,12 @@ impl State {
 
         let last_active_layout = saved_state.last_active_layout;
 
-        let mut ticker_info_map = HashMap::new();
+        let mut tickers_info = HashMap::new();
 
         let exchange_fetch_tasks = {
             Exchange::MARKET_TYPES.iter()
                 .flat_map(|(exchange, market_type)| {
-                    ticker_info_map.insert(*exchange, HashMap::new());
+                    tickers_info.insert(*exchange, HashMap::new());
                     
                     let ticksizes_task = match exchange {
                         Exchange::BinanceFutures | Exchange::BinanceSpot => {
@@ -196,7 +196,7 @@ impl State {
                 main_window: Window::new(main_window),
                 active_modal: DashboardModal::None,
                 notification: None,
-                ticker_info_map,
+                tickers_info,
                 show_tickers_dashboard: false,
                 sidebar_location: saved_state.sidebar,
                 tickers_table: TickersTable::new(saved_state.favorited_tickers),
@@ -218,12 +218,7 @@ impl State {
         match message {
             Message::SetTickersInfo(exchange, tickers_info) => {
                 log::info!("Received tickers info for {exchange}, len: {}", tickers_info.len());
-
-                self.ticker_info_map.insert(exchange, tickers_info);
-
-                self.layouts.values_mut().for_each(|dashboard| {
-                    dashboard.set_tickers_info(self.ticker_info_map.clone());
-                });
+                self.tickers_info.insert(exchange, tickers_info);
             }
             Message::MarketWsEvent(exchange, event) => {
                 let main_window_id = self.main_window.id;
@@ -467,15 +462,27 @@ impl State {
                 if let tickers_table::Message::TickerSelected(ticker, exchange, content) = message {
                     let main_window_id = self.main_window.id;
 
-                    let command = self
-                        .get_mut_dashboard(self.last_active_layout)
-                        .init_pane_task(main_window_id, ticker, exchange, &content);
+                    let ticker_info = self.tickers_info.get(&exchange).and_then(|info| {
+                        info.get(&ticker).cloned().flatten()
+                    });
 
-                    return Task::batch(vec![command.map(Message::Dashboard)]);
-                } else {
-                    let command = self.tickers_table.update(message);
+                    if let Some(ticker_info) = ticker_info {
+                        let task = self
+                            .get_mut_dashboard(self.last_active_layout)
+                            .init_pane_task(main_window_id, (ticker, ticker_info), exchange, &content);
 
-                    return Task::batch(vec![command.map(Message::TickersTable)]);
+                        return task.map(Message::Dashboard);
+                    } else {
+                        return Task::done(Message::ErrorOccurred(InternalError::Fetch(
+                            format!(
+                                "Couldn't find ticker info for {ticker} on {exchange}, try restarting the app"
+                            ),
+                        )));
+                    }
+                } else {
+                    return self.tickers_table
+                        .update(message)
+                        .map(Message::TickersTable);
                 }
             }
             Message::SetTimezone(tz) => {

+ 8 - 38
src/screen/dashboard.rs

@@ -88,7 +88,6 @@ pub struct Dashboard {
     pub popout: HashMap<window::Id, (pane_grid::State<PaneState>, (Point, Size))>,
     pub pane_streams: HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>,
     notification_manager: NotificationManager,
-    tickers_info: HashMap<Exchange, HashMap<Ticker, Option<TickerInfo>>>,
     pub trade_fetch_enabled: bool,
 }
 
@@ -105,7 +104,6 @@ impl Dashboard {
             focus: None,
             pane_streams: HashMap::new(),
             notification_manager: NotificationManager::new(),
-            tickers_info: HashMap::new(),
             popout: HashMap::new(),
             trade_fetch_enabled: false,
         }
@@ -181,7 +179,6 @@ impl Dashboard {
             focus: None,
             pane_streams: HashMap::new(),
             notification_manager: NotificationManager::new(),
-            tickers_info: HashMap::new(),
             popout,
             trade_fetch_enabled,
         }
@@ -331,7 +328,13 @@ impl Dashboard {
                             main_window.id,
                         );
                     }
-                    pane::Message::InitPaneContent(window, content_str, is_pane, pane_stream) => {
+                    pane::Message::InitPaneContent(
+                        window, 
+                        content_str, 
+                        is_pane, 
+                        pane_stream, 
+                        ticker_info,
+                    ) => {
                         let pane;
                         if let Some(parent_pane) = is_pane {
                             pane = parent_pane;
@@ -343,15 +346,6 @@ impl Dashboard {
                             Task::done(Message::ErrorOccurred(window, Some(pane), err))
                         };
 
-                        let ticker_info = match self.get_ticker_info(&pane_stream) {
-                            Some(info) => info,
-                            None => {
-                                return err_occurred(DashboardError::PaneSet(
-                                    "No ticker info found".to_string(),
-                                ));
-                            }
-                        };
-
                         // set pane's stream and content identifiers
                         if let Some(pane_state) = self.get_mut_pane(main_window.id, window, pane) {
                             if let Err(err) = pane_state.set_content(
@@ -1035,30 +1029,6 @@ impl Dashboard {
         }
     }
 
-    pub fn set_tickers_info(
-        &mut self,
-        tickers_info_map: HashMap<Exchange, HashMap<Ticker, Option<TickerInfo>>>,
-    ) {
-        self.tickers_info = tickers_info_map;
-    }
-
-    fn get_ticker_info(&self, pane_stream: &[StreamType]) -> Option<TickerInfo> {
-        pane_stream
-            .iter()
-            .filter_map(|stream| match stream {
-                StreamType::Kline {exchange, ticker, ..}
-                | StreamType::DepthAndTrades { exchange, ticker } => {
-                    self.tickers_info.get(exchange).and_then(|exchange_map| {
-                        exchange_map
-                            .get(ticker)
-                            .and_then(|ticker_info| *ticker_info)
-                    })
-                }
-                _ => None,
-            })
-            .next()
-    }
-
     fn set_pane_ticksize(
         &mut self,
         main_window: window::Id,
@@ -1187,7 +1157,7 @@ impl Dashboard {
     pub fn init_pane_task(
         &mut self,
         main_window: window::Id,
-        ticker: Ticker,
+        ticker: (Ticker, TickerInfo),
         exchange: Exchange,
         content: &str,
     ) -> Task<Message> {

+ 7 - 6
src/screen/dashboard/pane.rs

@@ -35,7 +35,7 @@ pub enum Message {
     TicksizeSelected(TickMultiplier, pane_grid::Pane),
     TimeframeSelected(Timeframe, pane_grid::Pane),
     ToggleModal(pane_grid::Pane, PaneModal),
-    InitPaneContent(window::Id, String, Option<pane_grid::Pane>, Vec<StreamType>),
+    InitPaneContent(window::Id, String, Option<pane_grid::Pane>, Vec<StreamType>, TickerInfo),
     ReplacePane(pane_grid::Pane),
     ChartUserUpdate(pane_grid::Pane, charts::Message),
     SliderChanged(pane_grid::Pane, f32, bool),
@@ -117,13 +117,13 @@ impl PaneState {
         &mut self,
         content: &str,
         exchange: Exchange,
-        ticker: Ticker,
+        ticker: (Ticker, TickerInfo),
         pane: pane_grid::Pane,
         window: window::Id,
     ) -> Task<Message> {
         let streams = match content {
             "heatmap" | "time&sales" => {
-                vec![StreamType::DepthAndTrades { exchange, ticker }]
+                vec![StreamType::DepthAndTrades { exchange, ticker: ticker.0 }]
             }
             "footprint" => {
                 let timeframe = self
@@ -132,10 +132,10 @@ impl PaneState {
                     .unwrap_or(Timeframe::M5);
 
                 vec![
-                    StreamType::DepthAndTrades { exchange, ticker },
+                    StreamType::DepthAndTrades { exchange, ticker: ticker.0 },
                     StreamType::Kline {
                         exchange,
-                        ticker,
+                        ticker: ticker.0,
                         timeframe,
                     },
                 ]
@@ -148,7 +148,7 @@ impl PaneState {
 
                 vec![StreamType::Kline {
                     exchange,
-                    ticker,
+                    ticker: ticker.0,
                     timeframe,
                 }]
             }
@@ -162,6 +162,7 @@ impl PaneState {
             content.to_string(),
             Some(pane),
             streams,
+            ticker.1,
         ))
     }