| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553 |
- pub mod pane;
- use futures::TryFutureExt;
- pub use pane::{PaneState, PaneContent, PaneSettings};
- use crate::{
- charts::{
- candlestick::CandlestickChart, footprint::FootprintChart, Message as ChartMessage
- },
- data_providers::{
- binance, bybit, fetcher::FetchRange, Depth, Exchange, Kline, OpenInterest, TickMultiplier, Ticker, TickerInfo, Timeframe, Trade
- },
- screen::InfoType,
- style,
- window::{self, Window},
- StreamType,
- };
- use super::{
- create_notis_column, modal::dashboard_notification, DashboardError, Notification,
- NotificationManager, UserTimezone,
- };
- use std::{
- collections::{HashMap, HashSet},
- vec,
- };
- use iced::{
- widget::{
- center, container,
- pane_grid::{self, Configuration},
- PaneGrid,
- },
- Element, Length, Point, Size, Task, Vector,
- };
- #[derive(Debug, Clone)]
- pub enum Message {
- Pane(window::Id, pane::Message),
- SavePopoutSpecs(HashMap<window::Id, (Point, Size)>),
- ResetLayout,
- ErrorOccurred(window::Id, Option<pane_grid::Pane>, DashboardError),
- ClearLastNotification(window::Id, pane_grid::Pane),
- ClearLastGlobalNotification,
- LayoutFetchAll,
- RefreshStreams,
- // Kline fetching
- FetchEvent(
- Option<uuid::Uuid>,
- Result<Vec<Kline>, String>,
- StreamType,
- pane_grid::Pane,
- window::Id,
- ),
- OIFetchEvent(
- Option<uuid::Uuid>,
- Result<Vec<OpenInterest>, String>,
- StreamType,
- pane_grid::Pane,
- window::Id,
- ),
- DistributeFetchedKlines(StreamType, Result<Vec<Kline>, String>),
- ChartMessage(pane_grid::Pane, window::Id, ChartMessage),
- // Batched trade fetching
- FetchTrades(
- window::Id,
- pane_grid::Pane,
- i64,
- i64,
- StreamType,
- ),
- DistributeFetchedTrades(
- window::Id,
- pane_grid::Pane,
- Vec<Trade>,
- StreamType,
- i64,
- ),
- }
- pub struct Dashboard {
- pub panes: pane_grid::State<PaneState>,
- pub focus: Option<(window::Id, pane_grid::Pane)>,
- pub popout: HashMap<window::Id, (pane_grid::State<PaneState>, (Point, Size))>,
- pub layout_lock: bool,
- pub pane_streams: HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>,
- notification_manager: NotificationManager,
- tickers_info: HashMap<Exchange, HashMap<Ticker, Option<TickerInfo>>>,
- timezone: UserTimezone,
- pub trade_fetch_enabled: bool,
- }
- impl Default for Dashboard {
- fn default() -> Self {
- Self::empty()
- }
- }
- impl Dashboard {
- fn empty() -> Self {
- Self {
- panes: pane_grid::State::with_configuration(Self::default_pane_config()),
- focus: None,
- layout_lock: false,
- pane_streams: HashMap::new(),
- notification_manager: NotificationManager::new(),
- tickers_info: HashMap::new(),
- popout: HashMap::new(),
- timezone: UserTimezone::default(),
- trade_fetch_enabled: false,
- }
- }
- fn default_pane_config() -> Configuration<PaneState> {
- Configuration::Split {
- axis: pane_grid::Axis::Vertical,
- ratio: 0.8,
- a: Box::new(Configuration::Split {
- axis: pane_grid::Axis::Horizontal,
- ratio: 0.4,
- a: Box::new(Configuration::Split {
- axis: pane_grid::Axis::Vertical,
- ratio: 0.5,
- a: Box::new(Configuration::Pane(PaneState {
- modal: pane::PaneModal::None,
- stream: vec![],
- content: PaneContent::Starter,
- settings: PaneSettings::default(),
- })),
- b: Box::new(Configuration::Pane(PaneState {
- modal: pane::PaneModal::None,
- stream: vec![],
- content: PaneContent::Starter,
- settings: PaneSettings::default(),
- })),
- }),
- b: Box::new(Configuration::Split {
- axis: pane_grid::Axis::Vertical,
- ratio: 0.5,
- a: Box::new(Configuration::Pane(PaneState {
- modal: pane::PaneModal::None,
- stream: vec![],
- content: PaneContent::Starter,
- settings: PaneSettings::default(),
- })),
- b: Box::new(Configuration::Pane(PaneState {
- modal: pane::PaneModal::None,
- stream: vec![],
- content: PaneContent::Starter,
- settings: PaneSettings::default(),
- })),
- }),
- }),
- b: Box::new(Configuration::Pane(PaneState {
- modal: pane::PaneModal::None,
- stream: vec![],
- content: PaneContent::Starter,
- settings: PaneSettings::default(),
- })),
- }
- }
- pub fn from_config(
- panes: Configuration<PaneState>,
- popout_windows: Vec<(Configuration<PaneState>, (Point, Size))>,
- trade_fetch_enabled: bool,
- ) -> Self {
- let panes = pane_grid::State::with_configuration(panes);
- let mut popout = HashMap::new();
- for (pane, specs) in popout_windows {
- popout.insert(
- window::Id::unique(),
- (pane_grid::State::with_configuration(pane), specs),
- );
- }
- Self {
- panes,
- focus: None,
- layout_lock: false,
- pane_streams: HashMap::new(),
- notification_manager: NotificationManager::new(),
- tickers_info: HashMap::new(),
- popout,
- timezone: UserTimezone::default(),
- trade_fetch_enabled,
- }
- }
- pub fn load_layout(&mut self) -> Task<Message> {
- let mut open_popouts_tasks: Vec<Task<Message>> = vec![];
- let mut new_popout: Vec<(
- iced::window::Id,
- (pane_grid::State<PaneState>, (Point, Size)),
- )> = Vec::new();
- let mut keys_to_remove: Vec<(iced::window::Id, (Point, Size))> = Vec::new();
- for (old_window_id, (_, specs)) in &self.popout {
- keys_to_remove.push((*old_window_id, *specs));
- }
- // remove keys and open new windows
- for (old_window_id, (pos, size)) in keys_to_remove {
- let (window, task) = window::open(window::Settings {
- position: window::Position::Specific(pos),
- size,
- exit_on_close_request: false,
- ..window::settings()
- });
- open_popouts_tasks.push(task.then(|_| Task::none()));
- if let Some((removed_pane, specs)) = self.popout.remove(&old_window_id) {
- new_popout.push((window, (removed_pane, specs)));
- }
- }
- // assign new windows to old panes
- for (window, (pane, specs)) in new_popout {
- self.popout.insert(window, (pane, specs));
- }
- Task::batch(open_popouts_tasks).chain(Task::batch(vec![
- Task::done(Message::RefreshStreams),
- Task::done(Message::LayoutFetchAll),
- ]))
- }
- pub fn reset_layout(&mut self) -> Task<Message> {
- Task::done(Message::ResetLayout)
- }
- pub fn update(&mut self, message: Message, main_window: &Window) -> Task<Message> {
- match message {
- Message::ResetLayout => {
- self.panes = pane_grid::State::with_configuration(Self::default_pane_config());
- self.focus = None;
- self.layout_lock = false;
- (self.popout, self.pane_streams) = (HashMap::new(), HashMap::new());
- }
- Message::SavePopoutSpecs(specs) => {
- for (window_id, (position, size)) in specs {
- if let Some((_, specs)) = self.popout.get_mut(&window_id) {
- *specs = (position, size);
- }
- }
- }
- Message::ClearLastNotification(window, pane) => {
- self.notification_manager.remove_last(&window, &pane);
- }
- Message::ClearLastGlobalNotification => {
- self.notification_manager.global_notifications.pop();
- }
- Message::ErrorOccurred(window, pane, err) => {
- if let Some(pane) = pane {
- self.notification_manager.handle_error(window, pane, err);
- return Task::perform(
- async { std::thread::sleep(std::time::Duration::from_secs(15)) },
- move |()| Message::ClearLastNotification(window, pane),
- );
- }
- }
- Message::Pane(window, message) => {
- match message {
- pane::Message::PaneClicked(pane) => {
- self.focus = Some((window, pane));
- }
- pane::Message::PaneResized(pane_grid::ResizeEvent { split, ratio }) => {
- self.panes.resize(split, ratio);
- }
- pane::Message::PaneDragged(event) => {
- if let pane_grid::DragEvent::Dropped { pane, target } = event {
- self.panes.drop(pane, target);
- self.focus = None;
- }
- }
- pane::Message::SplitPane(axis, pane) => {
- let focus_pane = if let Some((new_pane, _)) = self.panes.split(
- axis,
- pane,
- PaneState::new(vec![], PaneSettings::default()),
- ) {
- Some(new_pane)
- } else {
- None
- };
- if Some(focus_pane).is_some() {
- self.focus = Some((window, focus_pane.unwrap()));
- }
- }
- pane::Message::ClosePane(pane) => {
- if let Some((_, sibling)) = self.panes.close(pane) {
- self.focus = Some((window, sibling));
- }
- }
- pane::Message::MaximizePane(pane) => {
- self.panes.maximize(pane);
- }
- pane::Message::Restore => {
- self.panes.restore();
- }
- pane::Message::ReplacePane(pane) => {
- if let Some(pane) = self.panes.get_mut(pane) {
- *pane = PaneState::new(vec![], PaneSettings::default());
- }
- }
- pane::Message::ToggleModal(pane, modal_type) => {
- if let Some(pane) = self.get_mut_pane(main_window.id, window, pane) {
- if modal_type == pane.modal {
- pane.modal = pane::PaneModal::None;
- } else {
- pane.modal = modal_type;
- }
- };
- }
- pane::Message::ChartUserUpdate(pane, chart_message) => {
- return self.update_chart_state(
- pane,
- window,
- &chart_message,
- main_window.id,
- );
- }
- pane::Message::SliderChanged(pane, value, is_trade_filter) => {
- return self.set_pane_size_filter(
- window,
- pane,
- value,
- is_trade_filter,
- main_window.id,
- );
- }
- pane::Message::InitPaneContent(window, content_str, is_pane, pane_stream) => {
- let pane;
- if let Some(parent_pane) = is_pane {
- pane = parent_pane;
- } else {
- pane = self.panes.iter().next().map(|(pane, _)| *pane).unwrap();
- }
- let err_occurred = |err| {
- Task::done(Message::ErrorOccurred(window, Some(pane), err))
- };
- let timezone = self.timezone;
- 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(
- ticker_info,
- &content_str,
- timezone
- ) {
- return err_occurred(err);
- }
- } else {
- return err_occurred(DashboardError::PaneSet(
- "No pane found".to_string()
- ));
- }
- // prepare unique streams for websocket
- for stream in &pane_stream {
- match stream {
- StreamType::Kline {
- exchange, ticker, ..
- }
- | StreamType::DepthAndTrades { exchange, ticker } => {
- self.pane_streams
- .entry(*exchange)
- .or_default()
- .entry(*ticker)
- .or_default()
- .insert(*stream);
- }
- _ => {}
- }
- }
- log::info!("{:?}", &self.pane_streams);
- // get fetch tasks for pane's content
- if ["footprint", "candlestick", "heatmap"]
- .contains(&content_str.as_str())
- {
- for stream in &pane_stream {
- if let StreamType::Kline { .. } = stream {
- if ["candlestick", "footprint"]
- .contains(&content_str.as_str())
- {
- return get_kline_fetch_task(
- window, pane, *stream, None, None,
- );
- }
- }
- }
- }
- }
- pane::Message::TimeframeSelected(timeframe, pane) => {
- self.notification_manager.clear(&window, &pane);
- match self.set_pane_timeframe(main_window.id, window, pane, timeframe) {
- Ok(stream_type) => {
- if let StreamType::Kline { .. } = stream_type {
- let task = get_kline_fetch_task(
- window,
- pane,
- *stream_type,
- None,
- None,
- );
- self.notification_manager.push(
- window,
- pane,
- Notification::Info(InfoType::FetchingKlines),
- );
- return Task::done(Message::RefreshStreams)
- .chain(task);
- }
- }
- Err(err) => {
- return Task::done(
- Message::ErrorOccurred(window, Some(pane), err)
- );
- }
- }
- }
- pane::Message::TicksizeSelected(tick_multiply, pane) => {
- self.notification_manager.clear(&window, &pane);
- return self.set_pane_ticksize(main_window.id, window, pane, tick_multiply);
- }
- pane::Message::Popout => return self.popout_pane(main_window),
- pane::Message::Merge => return self.merge_pane(main_window),
- pane::Message::ToggleIndicator(pane, indicator_str) => {
- if let Some(pane_state) = self.get_mut_pane(main_window.id, window, pane) {
- pane_state.content.toggle_indicator(indicator_str);
- }
- }
- pane::Message::HideNotification(pane, notification) => {
- self.notification_manager.find_and_remove(window, pane, notification);
- }
- }
- }
- Message::FetchEvent(req_id, klines, pane_stream, pane_id, window) => {
- self.notification_manager.remove_info_type(
- window,
- &pane_id,
- &InfoType::FetchingKlines,
- );
- match klines {
- Ok(klines) => {
- if let StreamType::Kline { timeframe, .. } = pane_stream {
- let timezone = self.timezone;
- if let Some(pane_state) =
- self.get_mut_pane(main_window.id, window, pane_id)
- {
- pane_state.insert_klines_vec(req_id, timeframe, &klines, timezone);
- }
- }
- }
- Err(err) => {
- return Task::done(Message::ErrorOccurred(
- window,
- Some(pane_id),
- DashboardError::Fetch(err)
- ));
- }
- }
- }
- Message::OIFetchEvent(req_id, oi, pane_stream, pane_id, window) => {
- self.notification_manager.remove_info_type(
- window,
- &pane_id,
- &InfoType::FetchingOI,
- );
- if let Some(pane_state) =
- self.get_mut_pane(main_window.id, window, pane_id)
- {
- match oi {
- Ok(oi) => {
- if let StreamType::Kline { .. } = pane_stream {
- pane_state.insert_oi_vec(req_id, oi);
- }
- }
- Err(err) => {
- return Task::done(Message::ErrorOccurred(
- window,
- Some(pane_id),
- DashboardError::Fetch(err),
- ))
- }
- }
- }
- }
- Message::LayoutFetchAll => {
- let mut fetched_panes = vec![];
- self.iter_all_panes(main_window.id)
- .for_each(|(window, pane, pane_state)| match pane_state.content {
- PaneContent::Candlestick(_, _) | PaneContent::Footprint(_, _) => {
- fetched_panes.push((window, pane));
- }
- _ => {}
- });
- for (window, pane) in fetched_panes {
- self.notification_manager.push(
- window,
- pane,
- Notification::Info(InfoType::FetchingKlines),
- );
- }
- return Task::batch(klines_fetch_all_task(&self.pane_streams));
- }
- Message::DistributeFetchedKlines(stream_type, klines) => match klines {
- Ok(klines) => {
- let mut inserted_panes = vec![];
- let timezone = self.timezone;
- self.iter_all_panes_mut(main_window.id)
- .for_each(|(window, pane, state)| {
- if state.matches_stream(&stream_type) {
- if let StreamType::Kline { timeframe, .. } = stream_type {
- match &mut state.content {
- PaneContent::Candlestick(chart, indicators) => {
- let tick_size = chart.get_tick_size();
- *chart = CandlestickChart::new(
- chart.get_chart_layout(),
- klines.clone(),
- timeframe,
- tick_size,
- timezone,
- indicators,
- );
- }
- PaneContent::Footprint(chart, indicators) => {
- let (raw_trades, tick_size) =
- (chart.get_raw_trades(), chart.get_tick_size());
- *chart = FootprintChart::new(
- chart.get_chart_layout(),
- timeframe,
- tick_size,
- klines.clone(),
- raw_trades,
- timezone,
- indicators,
- );
- }
- _ => {}
- }
- inserted_panes.push((window, pane));
- }
- }
- });
- for (window, pane) in inserted_panes {
- self.notification_manager.remove_info_type(
- window,
- &pane,
- &InfoType::FetchingKlines,
- );
- }
- }
- Err(err) => {
- log::error!("{err}");
- }
- }
- Message::FetchTrades(
- window_id,
- pane,
- from_time,
- to_time,
- stream_type,
- ) => {
- if let StreamType::DepthAndTrades { exchange, ticker } = stream_type {
- if exchange == Exchange::BinanceFutures || exchange == Exchange::BinanceSpot {
- return Task::perform(
- binance::fetch_trades(ticker, from_time),
- move |result| match result {
- Ok(trades) => Message::DistributeFetchedTrades(
- window_id,
- pane,
- trades,
- stream_type,
- to_time,
- ),
- Err(err) => Message::ErrorOccurred(
- window_id,
- Some(pane),
- DashboardError::Fetch(err.to_string()),
- ),
- },
- );
- } else {
- self.notification_manager.remove_info_type(
- window_id,
- &pane,
- &InfoType::FetchingTrades(0),
- );
- return Task::done(Message::ErrorOccurred(
- window_id,
- Some(pane),
- DashboardError::Fetch(format!(
- "No trade fetch support for {exchange:?}"
- )),
- ));
- }
- }
- }
- Message::DistributeFetchedTrades(
- window_id,
- pane,
- trades,
- stream_type,
- to_time,
- ) => {
- let last_trade_time = trades.last()
- .map_or(0, |trade| trade.time);
- self.notification_manager.increment_fetching_trades(
- window_id,
- &pane,
- trades.len(),
- );
- if last_trade_time < to_time {
- match self.insert_fetched_trades(
- main_window.id,
- window_id,
- pane,
- &trades,
- false,
- ) {
- Ok(_) => {
- return Task::done(Message::FetchTrades(
- window_id,
- pane,
- last_trade_time,
- to_time,
- stream_type,
- ));
- }
- Err(err) => {
- self.notification_manager.remove_info_type(
- window_id,
- &pane,
- &InfoType::FetchingTrades(0),
- );
- return Task::done(
- Message::ErrorOccurred(window_id, Some(pane), err)
- );
- }
- }
- } else {
- self.notification_manager.remove_info_type(
- window_id,
- &pane,
- &InfoType::FetchingTrades(0),
- );
- match self.insert_fetched_trades(
- main_window.id,
- window_id,
- pane,
- &trades,
- true,
- ) {
- Ok(_) => {}
- Err(err) => {
- return Task::done(
- Message::ErrorOccurred(window_id, Some(pane), err)
- );
- }
- }
- }
- }
- Message::RefreshStreams => {
- self.pane_streams = self.get_all_diff_streams(main_window.id);
- }
- Message::ChartMessage(pane, window, message) => {
- if let ChartMessage::NewDataRange(req_id, fetch) = message {
- match fetch {
- FetchRange::Kline(from, to) => {
- let kline_stream = self
- .get_pane(main_window.id, window, pane)
- .and_then(|pane| {
- pane.stream
- .iter()
- .find(|stream| matches!(stream, StreamType::Kline { .. }))
- });
- if let Some(stream) = kline_stream {
- let stream = *stream;
- self.notification_manager.push(
- window,
- pane,
- Notification::Info(InfoType::FetchingKlines),
- );
- return get_kline_fetch_task(
- window,
- pane,
- stream,
- Some(req_id),
- Some((from, to)),
- );
- }
- }
- FetchRange::OpenInterest(from, to) => {
- let kline_stream = self
- .get_pane(main_window.id, window, pane)
- .and_then(|pane| {
- pane.stream
- .iter()
- .find(|stream| matches!(stream, StreamType::Kline { .. }))
- });
- if let Some(stream) = kline_stream {
- let stream = *stream;
- self.notification_manager.push(
- window,
- pane,
- Notification::Info(InfoType::FetchingOI),
- );
-
- return get_oi_fetch_task(
- window,
- pane,
- stream,
- Some(req_id),
- Some((from, to)),
- );
- }
- }
- FetchRange::Trades(from, to) => {
- if !self.trade_fetch_enabled {
- return Task::none();
- }
- let trade_stream = self
- .get_pane(main_window.id, window, pane)
- .and_then(|pane| {
- pane.stream.iter().find(|stream| {
- matches!(stream, StreamType::DepthAndTrades { .. })
- })
- });
- if let Some(stream) = trade_stream {
- let stream = *stream;
- self.notification_manager.push(
- window,
- pane,
- Notification::Info(InfoType::FetchingTrades(0)),
- );
- return Task::done(Message::FetchTrades(
- window,
- pane,
- from,
- to,
- stream,
- ));
- }
- }
- }
- }
- }
- }
- Task::none()
- }
- fn new_pane(
- &mut self,
- axis: pane_grid::Axis,
- main_window: &Window,
- pane_state: Option<PaneState>,
- ) -> Task<Message> {
- if self
- .focus
- .filter(|(window, _)| *window == main_window.id)
- .is_some()
- {
- // If there is any focused pane on main window, split it
- return self.split_pane(axis, main_window);
- } else {
- // If there is no focused pane, split the last pane or create a new empty grid
- let pane = self.panes.iter().last().map(|(pane, _)| pane).copied();
- if let Some(pane) = pane {
- let result = self.panes.split(
- axis,
- pane,
- pane_state.unwrap_or(PaneState::new(vec![], PaneSettings::default())),
- );
- if let Some((pane, _)) = result {
- return self.focus_pane(main_window.id, pane);
- }
- } else {
- let (state, pane) = pane_grid::State::new(
- pane_state.unwrap_or(PaneState::new(vec![], PaneSettings::default())),
- );
- self.panes = state;
- return self.focus_pane(main_window.id, pane);
- }
- }
- Task::none()
- }
- fn focus_pane(&mut self, window: window::Id, pane: pane_grid::Pane) -> Task<Message> {
- if self.focus != Some((window, pane)) {
- self.focus = Some((window, pane));
- }
- Task::none()
- }
- fn split_pane(&mut self, axis: pane_grid::Axis, main_window: &Window) -> Task<Message> {
- if let Some((window, pane)) = self.focus {
- if window == main_window.id {
- let result =
- self.panes
- .split(axis, pane, PaneState::new(vec![], PaneSettings::default()));
- if let Some((pane, _)) = result {
- return self.focus_pane(main_window.id, pane);
- }
- }
- }
- Task::none()
- }
- fn popout_pane(&mut self, main_window: &Window) -> Task<Message> {
- if let Some((_, id)) = self.focus.take() {
- if let Some((pane, _)) = self.panes.close(id) {
- let (window, task) = window::open(window::Settings {
- position: main_window
- .position
- .map(|point| window::Position::Specific(point + Vector::new(20.0, 20.0)))
- .unwrap_or_default(),
- exit_on_close_request: false,
- ..window::settings()
- });
- let (state, id) = pane_grid::State::new(pane);
- self.popout.insert(
- window,
- (state, (Point::new(0.0, 0.0), Size::new(1024.0, 768.0))),
- );
- return task.then(move |window| {
- Task::done(Message::Pane(window, pane::Message::PaneClicked(id)))
- });
- }
- }
- Task::none()
- }
- fn merge_pane(&mut self, main_window: &Window) -> Task<Message> {
- if let Some((window, pane)) = self.focus.take() {
- if let Some(pane_state) = self
- .popout
- .remove(&window)
- .and_then(|(mut panes, _)| panes.panes.remove(&pane))
- {
- let task =
- self.new_pane(pane_grid::Axis::Horizontal, main_window, Some(pane_state));
- return Task::batch(vec![window::close(window), task]);
- }
- }
- Task::none()
- }
- fn get_pane(
- &self,
- main_window: window::Id,
- window: window::Id,
- pane: pane_grid::Pane,
- ) -> Option<&PaneState> {
- if main_window == window {
- self.panes.get(pane)
- } else {
- self.popout
- .get(&window)
- .and_then(|(panes, _)| panes.get(pane))
- }
- }
- fn get_mut_pane(
- &mut self,
- main_window: window::Id,
- window: window::Id,
- pane: pane_grid::Pane,
- ) -> Option<&mut PaneState> {
- if main_window == window {
- self.panes.get_mut(pane)
- } else {
- self.popout
- .get_mut(&window)
- .and_then(|(panes, _)| panes.get_mut(pane))
- }
- }
- fn iter_all_panes(
- &self,
- main_window: window::Id,
- ) -> impl Iterator<Item = (window::Id, pane_grid::Pane, &PaneState)> {
- self.panes
- .iter()
- .map(move |(pane, state)| (main_window, *pane, state))
- .chain(self.popout.iter().flat_map(|(window_id, (panes, _))| {
- panes.iter().map(|(pane, state)| (*window_id, *pane, state))
- }))
- }
- fn iter_all_panes_mut(
- &mut self,
- main_window: window::Id,
- ) -> impl Iterator<Item = (window::Id, pane_grid::Pane, &mut PaneState)> {
- self.panes
- .iter_mut()
- .map(move |(pane, state)| (main_window, *pane, state))
- .chain(self.popout.iter_mut().flat_map(|(window_id, (panes, _))| {
- panes
- .iter_mut()
- .map(|(pane, state)| (*window_id, *pane, state))
- }))
- }
- pub fn view<'a>(&'a self, main_window: &'a Window) -> Element<'_, Message> {
- let focus = self.focus;
- let pane_locked = self.layout_lock;
- let mut pane_grid = PaneGrid::new(&self.panes, |id, pane, maximized| {
- let is_focused = !pane_locked && focus == Some((main_window.id, id));
- pane.view(
- id,
- self.panes.len(),
- is_focused,
- maximized,
- main_window.id,
- main_window,
- self.notification_manager.get(&main_window.id, &id),
- )
- })
- .spacing(6)
- .style(style::pane_grid);
- if !pane_locked {
- pane_grid = pane_grid
- .on_click(pane::Message::PaneClicked)
- .on_resize(8, pane::Message::PaneResized)
- .on_drag(pane::Message::PaneDragged);
- }
- let pane_grid: Element<_> = pane_grid.into();
- let base = container(pane_grid.map(move |message| Message::Pane(main_window.id, message)));
- if !self.notification_manager.global_notifications.is_empty() {
- dashboard_notification(
- base,
- create_notis_column(
- &self.notification_manager.global_notifications,
- Message::ClearLastGlobalNotification,
- ),
- )
- } else {
- base.into()
- }
- }
- pub fn view_window<'a>(
- &'a self,
- window: window::Id,
- main_window: &'a Window,
- ) -> Element<'a, Message> {
- if let Some((state, _)) = self.popout.get(&window) {
- let content = container({
- let mut pane_grid = PaneGrid::new(state, |id, pane, _maximized| {
- let is_focused = self.focus == Some((window, id));
- pane.view(
- id,
- state.len(),
- is_focused,
- false,
- window,
- main_window,
- self.notification_manager.get(&window, &id),
- )
- });
- if !self.layout_lock {
- pane_grid = pane_grid.on_click(pane::Message::PaneClicked);
- }
- pane_grid
- })
- .width(Length::Fill)
- .height(Length::Fill)
- .padding(8);
- return Element::new(content).map(move |message| Message::Pane(window, message));
- } else {
- return Element::new(center("No pane found for window"))
- .map(move |message| Message::Pane(window, message));
- }
- }
- 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,
- window: window::Id,
- pane: pane_grid::Pane,
- new_tick_multiply: TickMultiplier,
- ) -> Task<Message> {
- if let Some(pane_state) = self.get_mut_pane(main_window, window, pane) {
- pane_state.settings.tick_multiply = Some(new_tick_multiply);
- if let Some(ticker_info) = pane_state.settings.ticker_info {
- match pane_state.content {
- PaneContent::Footprint(ref mut chart, _) => {
- chart.change_tick_size(
- new_tick_multiply.multiply_with_min_tick_size(ticker_info),
- );
- chart.reset_request_handler();
- }
- PaneContent::Heatmap(ref mut chart, _) => {
- chart.change_tick_size(
- new_tick_multiply.multiply_with_min_tick_size(ticker_info),
- );
- }
- _ => {
- return Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::PaneSet(
- "No chart found to change ticksize".to_string(),
- ),
- ));
- }
- }
- } else {
- return Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::PaneSet("No min ticksize found".to_string()),
- ));
- }
- } else {
- return Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::PaneSet("No pane found to change ticksize".to_string()),
- ));
- }
- Task::none()
- }
- fn set_pane_timeframe(
- &mut self,
- main_window: window::Id,
- window: window::Id,
- pane: pane_grid::Pane,
- new_timeframe: Timeframe,
- ) -> Result<&StreamType, DashboardError> {
- if let Some(pane_state) = self.get_mut_pane(main_window, window, pane) {
- pane_state.settings.selected_timeframe = Some(new_timeframe);
- if let Some(stream_type) = pane_state
- .stream
- .iter_mut()
- .find(|stream_type| matches!(stream_type, StreamType::Kline { .. }))
- {
- if let StreamType::Kline { timeframe, .. } = stream_type {
- *timeframe = new_timeframe;
- }
- match &mut pane_state.content {
- PaneContent::Candlestick(chart, _) => {
- chart.set_loading_state(true);
- return Ok(stream_type);
- }
- PaneContent::Footprint(chart, _) => {
- chart.set_loading_state(true);
- return Ok(stream_type);
- }
- _ => {}
- }
- }
- }
- Err(DashboardError::Unknown(
- "Couldn't get the pane to change its timeframe".to_string(),
- ))
- }
- fn set_pane_size_filter(
- &mut self,
- window: window::Id,
- pane: pane_grid::Pane,
- new_size_filter: f32,
- is_trade_filter: bool,
- main_window: window::Id,
- ) -> Task<Message> {
- if let Some(pane_state) = self.get_mut_pane(main_window, window, pane) {
- pane_state.settings.trade_size_filter = Some(new_size_filter);
- match pane_state.content {
- PaneContent::Heatmap(ref mut chart, _) => {
- chart.set_size_filter(new_size_filter, is_trade_filter);
- }
- PaneContent::TimeAndSales(ref mut chart) => {
- chart.set_size_filter(new_size_filter);
- }
- _ => {
- return Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::Unknown("No chart found to set size filter".to_string()),
- ));
- }
- }
- Task::none()
- } else {
- Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::Unknown("No pane found to set size filter".to_string()),
- ))
- }
- }
- pub fn init_pane_task(
- &mut self,
- main_window: window::Id,
- ticker: Ticker,
- exchange: Exchange,
- content: &str,
- ) -> Task<Message> {
- if let Some((window, selected_pane)) = self.focus {
- if let Some(pane_state) = self.get_mut_pane(main_window, window, selected_pane) {
- return pane_state
- .init_content_task(content, exchange, ticker, selected_pane, window)
- .map(move |message| Message::Pane(window, message));
- }
- } else {
- self.notification_manager
- .global_notifications
- .push(Notification::Warn("Select a pane first".to_string()));
- return Task::perform(
- async { std::thread::sleep(std::time::Duration::from_secs(8)) },
- move |()| Message::ClearLastGlobalNotification,
- );
- }
- Task::none()
- }
- pub fn toggle_trade_fetch(&mut self, is_enabled: bool, main_window: &Window) {
- self.trade_fetch_enabled = is_enabled;
- self.iter_all_panes_mut(main_window.id)
- .for_each(|(_, _, pane_state)| {
- if let PaneContent::Footprint(chart, _) = &mut pane_state.content {
- chart.reset_request_handler();
- }
- });
- }
- fn insert_fetched_trades(
- &mut self,
- main_window: window::Id,
- window: window::Id,
- pane: pane_grid::Pane,
- trades: &[Trade],
- is_batches_done: bool,
- ) -> Result<(), DashboardError> {
- self.get_mut_pane(main_window, window, pane)
- .map_or_else(
- || Err(
- DashboardError::Unknown("Couldnt get the pane for fetched trades".to_string())
- ),
- |pane_state| match &mut pane_state.content {
- PaneContent::Footprint(chart, _) => {
- chart.insert_trades(trades.to_owned(), is_batches_done);
- Ok(())
- }
- _ => Err(
- DashboardError::Unknown("No matching chart found for fetched trades".to_string())
- ),
- }
- )
- }
- pub fn update_latest_klines(
- &mut self,
- stream_type: &StreamType,
- kline: &Kline,
- main_window: window::Id,
- ) -> Task<Message> {
- let mut tasks = vec![];
- let mut found_match = false;
- self.iter_all_panes_mut(main_window)
- .for_each(|(window, pane, pane_state)| {
- if pane_state.matches_stream(stream_type) {
- match &mut pane_state.content {
- PaneContent::Candlestick(chart, _) => tasks.push(
- chart
- .update_latest_kline(kline)
- .map(move |message| Message::ChartMessage(pane, window, message)),
- ),
- PaneContent::Footprint(chart, _) => tasks.push(
- chart
- .update_latest_kline(kline)
- .map(move |message| Message::ChartMessage(pane, window, message)),
- ),
- _ => {}
- }
- found_match = true;
- }
- });
- if !found_match {
- log::error!("No matching pane found for the stream: {stream_type:?}");
- tasks.push(Task::done(Message::RefreshStreams));
- }
- Task::batch(tasks)
- }
- pub fn update_depth_and_trades(
- &mut self,
- stream_type: &StreamType,
- depth_update_t: i64,
- depth: Depth,
- trades_buffer: Vec<Trade>,
- main_window: window::Id,
- ) -> Task<Message> {
- let mut found_match = false;
- let trades_buffer = trades_buffer.into_boxed_slice();
- self.iter_all_panes_mut(main_window)
- .for_each(|(_, _, pane_state)| {
- if pane_state.matches_stream(stream_type) {
- match &mut pane_state.content {
- PaneContent::Heatmap(chart, _) => {
- chart.insert_datapoint(&trades_buffer, depth_update_t, &depth);
- }
- PaneContent::Footprint(chart, _) => {
- chart.insert_datapoint(&trades_buffer, depth_update_t);
- }
- PaneContent::TimeAndSales(chart) => {
- chart.update(&trades_buffer);
- }
- _ => {
- log::error!("No chart found for the stream: {stream_type:?}");
- }
- }
- found_match = true;
- }
- });
- if found_match {
- Task::none()
- } else {
- log::error!("No matching pane found for the stream: {stream_type:?}");
- Task::done(Message::RefreshStreams)
- }
- }
- fn update_chart_state(
- &mut self,
- pane: pane_grid::Pane,
- window: window::Id,
- chart_message: &ChartMessage,
- main_window: window::Id,
- ) -> Task<Message> {
- if let Some(pane_state) = self.get_mut_pane(main_window, window, pane) {
- match pane_state.content {
- PaneContent::Heatmap(ref mut chart, _) => chart
- .update(chart_message)
- .map(move |message| Message::ChartMessage(pane, window, message)),
- PaneContent::Footprint(ref mut chart, _) => chart
- .update(chart_message)
- .map(move |message| Message::ChartMessage(pane, window, message)),
- PaneContent::Candlestick(ref mut chart, _) => chart
- .update(chart_message)
- .map(move |message| Message::ChartMessage(pane, window, message)),
- _ => Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::Unknown("No chart found".to_string()),
- )),
- }
- } else {
- Task::done(Message::ErrorOccurred(
- window,
- Some(pane),
- DashboardError::Unknown("No pane found to update its state".to_string()),
- ))
- }
- }
- fn get_all_diff_streams(
- &mut self,
- main_window: window::Id,
- ) -> HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>> {
- let mut pane_streams = HashMap::new();
- self.iter_all_panes_mut(main_window)
- .for_each(|(_, _, pane_state)| {
- for stream_type in &pane_state.stream {
- match stream_type {
- StreamType::Kline {
- exchange,
- ticker,
- timeframe,
- } => {
- let exchange = *exchange;
- let ticker = *ticker;
- let timeframe = *timeframe;
- let exchange_map =
- pane_streams.entry(exchange).or_insert(HashMap::new());
- let ticker_map = exchange_map.entry(ticker).or_insert(HashSet::new());
- ticker_map.insert(StreamType::Kline {
- exchange,
- ticker,
- timeframe,
- });
- }
- StreamType::DepthAndTrades { exchange, ticker } => {
- let exchange = *exchange;
- let ticker = *ticker;
- let exchange_map =
- pane_streams.entry(exchange).or_insert(HashMap::new());
- let ticker_map = exchange_map.entry(ticker).or_insert(HashSet::new());
- ticker_map.insert(StreamType::DepthAndTrades { exchange, ticker });
- }
- _ => {}
- }
- }
- });
- self.pane_streams.clone_from(&pane_streams);
- pane_streams
- }
- pub fn get_timezone(&self) -> UserTimezone {
- self.timezone
- }
- pub fn set_timezone(&mut self, main_window: window::Id, timezone: UserTimezone) {
- self.timezone = timezone;
- self.iter_all_panes_mut(main_window)
- .for_each(|(_, _, pane)| {
- pane.content.change_timezone(timezone);
- });
- }
- }
- fn get_oi_fetch_task(
- window_id: window::Id,
- pane: pane_grid::Pane,
- stream: StreamType,
- req_id: Option<uuid::Uuid>,
- from_to_time: Option<(i64, i64)>,
- ) -> Task<Message> {
- match stream {
- StreamType::Kline {
- exchange,
- ticker,
- timeframe,
- } => match exchange {
- Exchange::BinanceFutures => Task::perform(
- binance::fetch_historical_oi(ticker, from_to_time, timeframe)
- .map_err(|err| format!("{err}")),
- move |oi| Message::OIFetchEvent(req_id, oi, stream, pane, window_id),
- ),
- Exchange::BybitLinear => Task::perform(
- bybit::fetch_historical_oi(ticker, from_to_time, timeframe)
- .map_err(|err| format!("{err}")),
- move |oi| Message::OIFetchEvent(req_id, oi, stream, pane, window_id),
- ),
- _ => {
- log::error!("No OI fetch support for {exchange:?}");
- Task::none()
- },
- },
- _ => Task::none(),
- }
- }
- fn get_kline_fetch_task(
- window_id: window::Id,
- pane: pane_grid::Pane,
- stream: StreamType,
- req_id: Option<uuid::Uuid>,
- range: Option<(i64, i64)>,
- ) -> Task<Message> {
- match stream {
- StreamType::Kline {
- exchange,
- ticker,
- timeframe,
- } => match exchange {
- Exchange::BinanceFutures | Exchange::BinanceSpot => Task::perform(
- binance::fetch_klines(ticker, timeframe, range)
- .map_err(|err| format!("{err}")),
- move |klines| Message::FetchEvent(req_id, klines, stream, pane, window_id),
- ),
- Exchange::BybitLinear | Exchange::BybitSpot => Task::perform(
- bybit::fetch_klines(ticker, timeframe, range)
- .map_err(|err| format!("{err}")),
- move |klines| Message::FetchEvent(req_id, klines, stream, pane, window_id),
- ),
- },
- _ => Task::none(),
- }
- }
- fn klines_fetch_all_task(
- streams: &HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>,
- ) -> Vec<Task<Message>> {
- let mut tasks: Vec<Task<Message>> = vec![];
- for (exchange, stream) in streams {
- let mut kline_fetches = Vec::new();
- for stream_types in stream.values() {
- for stream_type in stream_types {
- if let StreamType::Kline {
- ticker, timeframe, ..
- } = stream_type
- {
- kline_fetches.push((*ticker, *timeframe));
- }
- }
- }
- for (ticker, timeframe) in kline_fetches {
- let (ticker, timeframe) = (ticker, timeframe);
- let exchange = *exchange;
- match exchange {
- Exchange::BinanceFutures | Exchange::BinanceSpot => {
- let fetch_klines = Task::perform(
- binance::fetch_klines(ticker, timeframe, None)
- .map_err(|err| format!("{err}")),
- move |klines| {
- Message::DistributeFetchedKlines(
- StreamType::Kline {
- exchange,
- ticker,
- timeframe,
- },
- klines,
- )
- },
- );
- tasks.push(fetch_klines);
- }
- Exchange::BybitLinear | Exchange::BybitSpot => {
- let fetch_klines = Task::perform(
- bybit::fetch_klines(ticker, timeframe, None)
- .map_err(|err| format!("{err}")),
- move |klines| {
- Message::DistributeFetchedKlines(
- StreamType::Kline {
- exchange,
- ticker,
- timeframe,
- },
- klines,
- )
- },
- );
- tasks.push(fetch_klines);
- }
- }
- }
- }
- tasks
- }
|