|
|
@@ -1,11 +1,13 @@
|
|
|
#![windows_subsystem = "windows"]
|
|
|
|
|
|
mod data_providers;
|
|
|
-use data_providers::binance::{user_data, market_data};
|
|
|
+use data_providers::binance::market_data;
|
|
|
+use data_providers::{binance, bybit};
|
|
|
mod charts;
|
|
|
use charts::custom_line::{self, CustomLine};
|
|
|
use charts::heatmap::{self, Heatmap};
|
|
|
use charts::footprint::{self, Footprint};
|
|
|
+use iced::event;
|
|
|
|
|
|
use std::vec;
|
|
|
use chrono::{NaiveDateTime, DateTime, Utc};
|
|
|
@@ -21,6 +23,28 @@ use iced::widget::{
|
|
|
};
|
|
|
use futures::TryFutureExt;
|
|
|
|
|
|
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
+pub enum Exchange {
|
|
|
+ BinanceFutures,
|
|
|
+ BybitLinear,
|
|
|
+}
|
|
|
+
|
|
|
+impl std::fmt::Display for Exchange {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ write!(
|
|
|
+ f,
|
|
|
+ "{}",
|
|
|
+ match self {
|
|
|
+ Exchange::BinanceFutures => "Binance Futures",
|
|
|
+ Exchange::BybitLinear => "Bybit Linear",
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+impl Exchange {
|
|
|
+ const ALL: [Exchange; 2] = [Exchange::BinanceFutures, Exchange::BybitLinear];
|
|
|
+}
|
|
|
+
|
|
|
impl std::fmt::Display for Ticker {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
@@ -105,18 +129,29 @@ impl From<Icon> for char {
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
-enum WsState {
|
|
|
- Connected(market_data::Connection),
|
|
|
+enum BinanceWsState {
|
|
|
+ Connected(binance::market_data::Connection),
|
|
|
+ Disconnected,
|
|
|
+}
|
|
|
+impl Default for BinanceWsState {
|
|
|
+ fn default() -> Self {
|
|
|
+ Self::Disconnected
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+enum BybitWsState {
|
|
|
+ Connected(bybit::market_data::Connection),
|
|
|
Disconnected,
|
|
|
}
|
|
|
-impl Default for WsState {
|
|
|
+impl Default for BybitWsState {
|
|
|
fn default() -> Self {
|
|
|
Self::Disconnected
|
|
|
}
|
|
|
}
|
|
|
|
|
|
enum UserWsState {
|
|
|
- Connected(user_data::Connection),
|
|
|
+ Connected(binance::user_data::Connection),
|
|
|
Disconnected,
|
|
|
}
|
|
|
impl Default for UserWsState {
|
|
|
@@ -173,6 +208,12 @@ impl Default for State {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+pub enum MarketEvents {
|
|
|
+ Binance(binance::market_data::Event),
|
|
|
+ Bybit(bybit::market_data::Event),
|
|
|
+}
|
|
|
+
|
|
|
#[derive(Debug, Clone)]
|
|
|
pub enum Message {
|
|
|
Debug(String),
|
|
|
@@ -187,10 +228,10 @@ pub enum Message {
|
|
|
UserKeyError,
|
|
|
TickerSelected(Ticker),
|
|
|
TimeframeSelected(Timeframe, pane_grid::Pane),
|
|
|
- ExchangeSelected(&'static str),
|
|
|
- MarketWsEvent(market_data::Event),
|
|
|
+ ExchangeSelected(Exchange),
|
|
|
+ MarketWsEvent(MarketEvents),
|
|
|
WsToggle,
|
|
|
- FetchEvent(Result<Vec<market_data::Kline>, std::string::String>, PaneId, Timeframe),
|
|
|
+ FetchEvent(Result<Vec<binance::market_data::Kline>, std::string::String>, PaneId, Timeframe),
|
|
|
|
|
|
// Pane grid
|
|
|
Split(pane_grid::Axis, pane_grid::Pane, PaneId),
|
|
|
@@ -217,6 +258,8 @@ pub enum Message {
|
|
|
|
|
|
TicksizeSelected(u16),
|
|
|
SetMinTickSize(f32),
|
|
|
+
|
|
|
+ ErrorOccurred(String),
|
|
|
}
|
|
|
|
|
|
struct State {
|
|
|
@@ -231,8 +274,11 @@ struct State {
|
|
|
// data streams
|
|
|
listen_key: Option<String>,
|
|
|
selected_ticker: Option<Ticker>,
|
|
|
- selected_exchange: Option<&'static str>,
|
|
|
- ws_state: WsState,
|
|
|
+ selected_exchange: Option<Exchange>,
|
|
|
+
|
|
|
+ binance_ws_state: BinanceWsState,
|
|
|
+ bybit_ws_state: BybitWsState,
|
|
|
+
|
|
|
user_ws_state: UserWsState,
|
|
|
ws_running: bool,
|
|
|
|
|
|
@@ -324,8 +370,9 @@ impl State {
|
|
|
|
|
|
listen_key: None,
|
|
|
selected_ticker: None,
|
|
|
- selected_exchange: Some("Binance Futures"),
|
|
|
- ws_state: WsState::Disconnected,
|
|
|
+ selected_exchange: Some(Exchange::BinanceFutures),
|
|
|
+ binance_ws_state: BinanceWsState::Disconnected,
|
|
|
+ bybit_ws_state: BybitWsState::Disconnected,
|
|
|
user_ws_state: UserWsState::Disconnected,
|
|
|
ws_running: false,
|
|
|
panes,
|
|
|
@@ -412,24 +459,60 @@ impl State {
|
|
|
|
|
|
self.kline_stream = false;
|
|
|
|
|
|
- let mut Tasks = vec![];
|
|
|
- let mut dropped_streams = vec![];
|
|
|
+ let mut tasks = vec![];
|
|
|
|
|
|
if let Some(pane) = self.panes.panes.get_mut(&pane) {
|
|
|
let pane_id = pane.id;
|
|
|
|
|
|
pane.stream.1 = Some(timeframe);
|
|
|
-
|
|
|
- let fetch_klines = Task::perform(
|
|
|
- market_data::fetch_klines(*selected_ticker, timeframe)
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
- move |klines| {
|
|
|
- Message::FetchEvent(klines, pane_id, timeframe)
|
|
|
- });
|
|
|
-
|
|
|
- dropped_streams.push(pane.id);
|
|
|
-
|
|
|
- Tasks.push(fetch_klines);
|
|
|
+
|
|
|
+ match self.selected_exchange {
|
|
|
+ Some(Exchange::BinanceFutures) => {
|
|
|
+ let fetch_klines = Task::perform(
|
|
|
+ market_data::fetch_klines(*selected_ticker, timeframe)
|
|
|
+ .map_err(|err| format!("{err}")),
|
|
|
+ move |klines| {
|
|
|
+ Message::FetchEvent(klines, pane_id, timeframe)
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ tasks.push(fetch_klines);
|
|
|
+ },
|
|
|
+ Some(Exchange::BybitLinear) => {
|
|
|
+ let fetch_klines: Task<Message> = Task::perform(
|
|
|
+ bybit::market_data::fetch_klines(self.selected_ticker.unwrap_or(Ticker::BTCUSDT), timeframe)
|
|
|
+ .map_err(|err| format!("{err}")),
|
|
|
+ move |klines: Result<Vec<bybit::market_data::Kline>, String>| {
|
|
|
+
|
|
|
+ match klines {
|
|
|
+ Ok(klines) => {
|
|
|
+ let binance_klines: Vec<market_data::Kline> = klines.iter().map(|kline| {
|
|
|
+ market_data::Kline {
|
|
|
+ time: kline.time,
|
|
|
+ open: kline.open,
|
|
|
+ high: kline.high,
|
|
|
+ low: kline.low,
|
|
|
+ close: kline.close,
|
|
|
+ volume: kline.volume,
|
|
|
+ taker_buy_base_asset_volume: -1.0,
|
|
|
+ }
|
|
|
+ }).collect();
|
|
|
+
|
|
|
+ Message::FetchEvent(Ok(binance_klines), pane_id, timeframe)
|
|
|
+ },
|
|
|
+ Err(err) => {
|
|
|
+ Message::Debug(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ tasks.push(fetch_klines);
|
|
|
+ },
|
|
|
+ None => {
|
|
|
+ eprintln!("No exchange selected");
|
|
|
+ }
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// sleep to drop existent stream and create new one
|
|
|
@@ -439,9 +522,9 @@ impl State {
|
|
|
},
|
|
|
move |()| Message::CutTheKlineStream
|
|
|
);
|
|
|
- Tasks.push(remove_active_stream);
|
|
|
+ tasks.push(remove_active_stream);
|
|
|
|
|
|
- Task::batch(Tasks)
|
|
|
+ Task::batch(tasks)
|
|
|
},
|
|
|
Message::ExchangeSelected(exchange) => {
|
|
|
self.selected_exchange = Some(exchange);
|
|
|
@@ -462,36 +545,74 @@ impl State {
|
|
|
}
|
|
|
if pane_state.id == PaneId::FootprintChart {
|
|
|
let fetch_ticksize: Task<Message> = Task::perform(
|
|
|
- market_data::fetch_ticksize(self.selected_ticker.unwrap_or(Ticker::BTCUSDT))
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
- move |ticksize: Result<f32, String>| {
|
|
|
- Message::SetMinTickSize(ticksize.unwrap_or(1.0))
|
|
|
+ bybit::market_data::fetch_ticksize(self.selected_ticker.unwrap_or(Ticker::BTCUSDT)),
|
|
|
+ move |result| match result {
|
|
|
+ Ok(ticksize) => Message::SetMinTickSize(ticksize),
|
|
|
+ Err(err) => {
|
|
|
+ Message::ErrorOccurred(err.to_string())
|
|
|
+ }
|
|
|
}
|
|
|
);
|
|
|
tasks.push(fetch_ticksize);
|
|
|
}
|
|
|
|
|
|
- let selected_timeframe: Timeframe = match pane_state.stream.1 {
|
|
|
- Some(timeframe) => timeframe,
|
|
|
- None => Timeframe::M1,
|
|
|
- };
|
|
|
-
|
|
|
- let pane_id: PaneId = pane_state.id;
|
|
|
-
|
|
|
- let fetch_klines: Task<Message> = Task::perform(
|
|
|
- market_data::fetch_klines(self.selected_ticker.unwrap_or(Ticker::BTCUSDT), selected_timeframe)
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
- move |klines: Result<Vec<market_data::Kline>, String>| {
|
|
|
- Message::FetchEvent(klines, pane_id, selected_timeframe)
|
|
|
+ if let Some(selected_timeframe) = pane_state.stream.1 {
|
|
|
+
|
|
|
+ let pane_id: PaneId = pane_state.id;
|
|
|
+
|
|
|
+ match self.selected_exchange {
|
|
|
+ Some(Exchange::BinanceFutures) => {
|
|
|
+ let fetch_klines: Task<Message> = Task::perform(
|
|
|
+ market_data::fetch_klines(self.selected_ticker.unwrap_or(Ticker::BTCUSDT), selected_timeframe)
|
|
|
+ .map_err(|err| format!("{err}")),
|
|
|
+ move |klines: Result<Vec<market_data::Kline>, String>| {
|
|
|
+ Message::FetchEvent(klines, pane_id, selected_timeframe)
|
|
|
+ }
|
|
|
+ );
|
|
|
+ tasks.push(fetch_klines);
|
|
|
+ },
|
|
|
+ Some(Exchange::BybitLinear) => {
|
|
|
+ let fetch_klines: Task<Message> = Task::perform(
|
|
|
+ bybit::market_data::fetch_klines(self.selected_ticker.unwrap_or(Ticker::BTCUSDT), selected_timeframe)
|
|
|
+ .map_err(|err| format!("{err}")),
|
|
|
+ move |klines: Result<Vec<bybit::market_data::Kline>, String>| {
|
|
|
+
|
|
|
+ match klines {
|
|
|
+ Ok(klines) => {
|
|
|
+ let binance_klines: Vec<market_data::Kline> = klines.iter().map(|kline| {
|
|
|
+ market_data::Kline {
|
|
|
+ time: kline.time,
|
|
|
+ open: kline.open,
|
|
|
+ high: kline.high,
|
|
|
+ low: kline.low,
|
|
|
+ close: kline.close,
|
|
|
+ volume: kline.volume,
|
|
|
+ taker_buy_base_asset_volume: -1.0,
|
|
|
+ }
|
|
|
+ }).collect();
|
|
|
+
|
|
|
+ Message::FetchEvent(Ok(binance_klines), pane_id, selected_timeframe)
|
|
|
+ },
|
|
|
+ Err(err) => {
|
|
|
+ Message::Debug(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ tasks.push(fetch_klines);
|
|
|
+ },
|
|
|
+ None => {
|
|
|
+ eprintln!("No exchange selected");
|
|
|
+ }
|
|
|
}
|
|
|
- );
|
|
|
- tasks.push(fetch_klines);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
Task::batch(tasks)
|
|
|
|
|
|
} else {
|
|
|
- self.ws_state = WsState::Disconnected;
|
|
|
+ self.binance_ws_state = BinanceWsState::Disconnected;
|
|
|
+ self.bybit_ws_state = BybitWsState::Disconnected;
|
|
|
|
|
|
self.heatmap_chart = None;
|
|
|
self.candlestick_chart = None;
|
|
|
@@ -514,7 +635,7 @@ impl State {
|
|
|
},
|
|
|
PaneId::FootprintChart => {
|
|
|
if let Some(heatmap_chart) = &mut self.heatmap_chart {
|
|
|
- let copied_trades = heatmap_chart.get_raw_trades();
|
|
|
+ let copied_trades: Vec<Trade> = heatmap_chart.get_raw_trades();
|
|
|
|
|
|
let mut klines_raw: Vec<(i64, f32, f32, f32, f32, f32, f32)> = vec![];
|
|
|
for kline in &klines {
|
|
|
@@ -524,10 +645,6 @@ impl State {
|
|
|
klines_raw.push((kline.time as i64, kline.open, kline.high, kline.low, kline.close, buy_volume, sell_volume));
|
|
|
}
|
|
|
|
|
|
- // get the latest 20 klines
|
|
|
- let copied_klines: Vec<(i64, f32, f32, f32, f32, f32, f32)> =
|
|
|
- klines_raw.iter().rev().take(20).rev().copied().collect::<Vec<(i64, f32, f32, f32, f32, f32, f32)>>();
|
|
|
-
|
|
|
let timeframe_u16: u16 = match timeframe {
|
|
|
Timeframe::M1 => 1,
|
|
|
Timeframe::M3 => 3,
|
|
|
@@ -538,7 +655,7 @@ impl State {
|
|
|
|
|
|
let tick_size = self.tick_multiply as f32 * self.min_tick_size.unwrap_or_default();
|
|
|
|
|
|
- self.footprint_chart = Some(Footprint::new(timeframe_u16, tick_size, copied_klines, copied_trades));
|
|
|
+ self.footprint_chart = Some(Footprint::new(timeframe_u16, tick_size, klines_raw, copied_trades));
|
|
|
}
|
|
|
},
|
|
|
_ => {}
|
|
|
@@ -553,47 +670,132 @@ impl State {
|
|
|
},
|
|
|
Message::MarketWsEvent(event) => {
|
|
|
match event {
|
|
|
- market_data::Event::Connected(connection) => {
|
|
|
- self.ws_state = WsState::Connected(connection);
|
|
|
- }
|
|
|
- market_data::Event::Disconnected => {
|
|
|
- self.ws_state = WsState::Disconnected;
|
|
|
- }
|
|
|
- market_data::Event::DepthReceived(depth_update, depth, trades_buffer) => {
|
|
|
- if let Some(time_and_sales) = &mut self.time_and_sales {
|
|
|
- time_and_sales.update(&trades_buffer);
|
|
|
- }
|
|
|
-
|
|
|
- let trades_buffer_clone = trades_buffer.clone();
|
|
|
-
|
|
|
- if let Some(chart) = &mut self.heatmap_chart {
|
|
|
- chart.insert_datapoint(trades_buffer, depth_update, depth);
|
|
|
- }
|
|
|
- if let Some(chart) = &mut self.footprint_chart {
|
|
|
- chart.insert_datapoint(trades_buffer_clone, depth_update);
|
|
|
+ MarketEvents::Binance(event) => match event {
|
|
|
+ binance::market_data::Event::Connected(connection) => {
|
|
|
+ self.binance_ws_state = BinanceWsState::Connected(connection);
|
|
|
}
|
|
|
- }
|
|
|
- market_data::Event::KlineReceived(kline, timeframe) => {
|
|
|
- for (pane, pane_state) in self.panes.iter() {
|
|
|
- if let Some(selected_timeframe) = pane_state.stream.1 {
|
|
|
- if selected_timeframe == timeframe {
|
|
|
- match pane_state.id {
|
|
|
- PaneId::CandlestickChart => {
|
|
|
- if let Some(candlestick_chart) = &mut self.candlestick_chart {
|
|
|
- candlestick_chart.insert_datapoint(&kline);
|
|
|
- }
|
|
|
- },
|
|
|
- PaneId::CustomChart => {
|
|
|
- if let Some(custom_line) = &mut self.custom_line {
|
|
|
- custom_line.insert_datapoint(&kline);
|
|
|
- }
|
|
|
- },
|
|
|
- PaneId::FootprintChart => {
|
|
|
- if let Some(footprint_chart) = &mut self.footprint_chart {
|
|
|
- footprint_chart.update_latest_kline(&kline);
|
|
|
- }
|
|
|
- },
|
|
|
- _ => {}
|
|
|
+ binance::market_data::Event::Disconnected => {
|
|
|
+ self.binance_ws_state = BinanceWsState::Disconnected;
|
|
|
+ }
|
|
|
+ binance::market_data::Event::DepthReceived(depth_update, depth, trades_buffer) => {
|
|
|
+ if let Some(time_and_sales) = &mut self.time_and_sales {
|
|
|
+ time_and_sales.update(&trades_buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ let trades_buffer_clone = trades_buffer.clone();
|
|
|
+
|
|
|
+ if let Some(chart) = &mut self.heatmap_chart {
|
|
|
+ chart.insert_datapoint(trades_buffer, depth_update, depth);
|
|
|
+ }
|
|
|
+ if let Some(chart) = &mut self.footprint_chart {
|
|
|
+ chart.insert_datapoint(trades_buffer_clone, depth_update);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ binance::market_data::Event::KlineReceived(kline, timeframe) => {
|
|
|
+ for (_, pane_state) in self.panes.iter() {
|
|
|
+ if let Some(selected_timeframe) = pane_state.stream.1 {
|
|
|
+ if selected_timeframe == timeframe {
|
|
|
+ match pane_state.id {
|
|
|
+ PaneId::CandlestickChart => {
|
|
|
+ if let Some(candlestick_chart) = &mut self.candlestick_chart {
|
|
|
+ candlestick_chart.insert_datapoint(&kline);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ PaneId::CustomChart => {
|
|
|
+ if let Some(custom_line) = &mut self.custom_line {
|
|
|
+ custom_line.insert_datapoint(&kline);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ PaneId::FootprintChart => {
|
|
|
+ if let Some(footprint_chart) = &mut self.footprint_chart {
|
|
|
+ footprint_chart.update_latest_kline(&kline);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ MarketEvents::Bybit(event) => match event {
|
|
|
+ bybit::market_data::Event::Connected(connection) => {
|
|
|
+ self.bybit_ws_state = BybitWsState::Connected(connection);
|
|
|
+
|
|
|
+ println!("Bybit connected");
|
|
|
+ }
|
|
|
+ bybit::market_data::Event::Disconnected => {
|
|
|
+ self.bybit_ws_state = BybitWsState::Disconnected;
|
|
|
+
|
|
|
+ println!("Bybit disconnected");
|
|
|
+ }
|
|
|
+ bybit::market_data::Event::DepthReceived(depth_update, depth, trades_buffer) => {
|
|
|
+
|
|
|
+ // convert bybit trade to binance trade
|
|
|
+ let mut binance_trades: Vec<binance::market_data::Trade> = vec![];
|
|
|
+
|
|
|
+ for trade in trades_buffer.iter() {
|
|
|
+ let binance_trade = binance::market_data::Trade {
|
|
|
+ price: trade.price,
|
|
|
+ qty: trade.qty,
|
|
|
+ time: trade.time,
|
|
|
+ is_sell: trade.is_sell,
|
|
|
+ };
|
|
|
+ binance_trades.push(binance_trade);
|
|
|
+ }
|
|
|
+
|
|
|
+ let local_depth = binance::market_data::LocalDepthCache {
|
|
|
+ time: depth.time,
|
|
|
+ bids: depth.bids.iter().map(|order| binance::market_data::Order { price: order.price, qty: order.qty }).collect(),
|
|
|
+ asks: depth.asks.iter().map(|order| binance::market_data::Order { price: order.price, qty: order.qty }).collect(),
|
|
|
+ };
|
|
|
+
|
|
|
+ let trades_clone = binance_trades.clone();
|
|
|
+
|
|
|
+ if let Some(time_and_sales) = &mut self.time_and_sales {
|
|
|
+ time_and_sales.update(&binance_trades);
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some(chart) = &mut self.heatmap_chart {
|
|
|
+ chart.insert_datapoint(binance_trades, depth_update, local_depth);
|
|
|
+ }
|
|
|
+ if let Some(chart) = &mut self.footprint_chart {
|
|
|
+ chart.insert_datapoint(trades_clone, depth_update);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ bybit::market_data::Event::KlineReceived(kline, timeframe) => {
|
|
|
+ for (_, pane_state) in self.panes.iter() {
|
|
|
+ if let Some(selected_timeframe) = pane_state.stream.1 {
|
|
|
+ if selected_timeframe == timeframe {
|
|
|
+ let binance_kline = binance::market_data::Kline {
|
|
|
+ time: kline.time,
|
|
|
+ open: kline.open,
|
|
|
+ high: kline.high,
|
|
|
+ low: kline.low,
|
|
|
+ close: kline.close,
|
|
|
+ volume: kline.volume,
|
|
|
+ taker_buy_base_asset_volume: -1.0,
|
|
|
+ };
|
|
|
+
|
|
|
+ match pane_state.id {
|
|
|
+ PaneId::CandlestickChart => {
|
|
|
+ if let Some(candlestick_chart) = &mut self.candlestick_chart {
|
|
|
+ candlestick_chart.insert_datapoint(&binance_kline);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ PaneId::CustomChart => {
|
|
|
+ if let Some(custom_line) = &mut self.custom_line {
|
|
|
+ custom_line.insert_datapoint(&binance_kline);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ PaneId::FootprintChart => {
|
|
|
+ if let Some(footprint_chart) = &mut self.footprint_chart {
|
|
|
+ footprint_chart.update_latest_kline(&binance_kline);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -731,6 +933,11 @@ impl State {
|
|
|
self.show_layout_modal = false;
|
|
|
Task::none()
|
|
|
},
|
|
|
+
|
|
|
+ Message::ErrorOccurred(err) => {
|
|
|
+ eprintln!("{err}");
|
|
|
+ Task::none()
|
|
|
+ },
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -852,7 +1059,7 @@ impl State {
|
|
|
).placeholder("Choose a ticker...");
|
|
|
|
|
|
let exchange_selector = pick_list(
|
|
|
- &["Binance Futures"][..],
|
|
|
+ &Exchange::ALL[..],
|
|
|
self.selected_exchange,
|
|
|
Message::ExchangeSelected,
|
|
|
).placeholder("Choose an exchange...");
|
|
|
@@ -932,21 +1139,54 @@ impl State {
|
|
|
|
|
|
if self.ws_running {
|
|
|
if let Some(ticker) = &self.selected_ticker {
|
|
|
- let binance_market_stream = market_data::connect_market_stream(*ticker).map(Message::MarketWsEvent);
|
|
|
- subscriptions.push(binance_market_stream);
|
|
|
+ match self.selected_exchange {
|
|
|
+ Some(Exchange::BinanceFutures) => {
|
|
|
+ let binance_market_stream: Subscription<Message> = binance::market_data::connect_market_stream(*ticker)
|
|
|
+ .map(|arg0: binance::market_data::Event| Message::MarketWsEvent(MarketEvents::Binance(arg0)));
|
|
|
|
|
|
- let mut streams: Vec<(Ticker, Timeframe)> = vec![];
|
|
|
+ subscriptions.push(binance_market_stream);
|
|
|
|
|
|
- for (_, pane_state) in self.panes.iter() {
|
|
|
- let ticker = pane_state.stream.0.unwrap_or(Ticker::BTCUSDT);
|
|
|
- let timeframe = pane_state.stream.1.unwrap_or(Timeframe::M1);
|
|
|
+ let mut streams: Vec<(Ticker, Timeframe)> = vec![];
|
|
|
|
|
|
- streams.push((ticker, timeframe));
|
|
|
- }
|
|
|
+ for (_, pane_state) in self.panes.iter() {
|
|
|
+ if let (Some(ticker), Some(timeframe)) = (pane_state.stream.0, pane_state.stream.1) {
|
|
|
+ streams.push((ticker, timeframe));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if !streams.is_empty() && self.kline_stream {
|
|
|
+ let binance_kline_streams: Subscription<Message> = binance::market_data::connect_kline_stream(streams)
|
|
|
+ .map(|arg0: binance::market_data::Event| Message::MarketWsEvent(MarketEvents::Binance(arg0)));
|
|
|
+
|
|
|
+ subscriptions.push(binance_kline_streams);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ Some(Exchange::BybitLinear) => {
|
|
|
+ let bybit_market_stream: Subscription<Message> = bybit::market_data::connect_market_stream(*ticker)
|
|
|
+ .map(|arg0: bybit::market_data::Event| Message::MarketWsEvent(MarketEvents::Bybit(arg0)));
|
|
|
+
|
|
|
+ subscriptions.push(bybit_market_stream);
|
|
|
+
|
|
|
+ let mut streams: Vec<(Ticker, Timeframe)> = vec![];
|
|
|
+
|
|
|
+ for (_, pane_state) in self.panes.iter() {
|
|
|
+ if let (Some(ticker), Some(timeframe)) = (pane_state.stream.0, pane_state.stream.1) {
|
|
|
+ streams.push((ticker, timeframe));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if !streams.is_empty() && self.kline_stream {
|
|
|
+ let bybit_kline_streams: Subscription<Message> = bybit::market_data::connect_kline_stream(streams)
|
|
|
+ .map(|arg0: bybit::market_data::Event| Message::MarketWsEvent(MarketEvents::Bybit(arg0)));
|
|
|
+
|
|
|
+ subscriptions.push(bybit_kline_streams);
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- if !streams.is_empty() && self.kline_stream {
|
|
|
- let binance_kline_streams = market_data::connect_kline_stream(streams).map(Message::MarketWsEvent);
|
|
|
- subscriptions.push(binance_kline_streams);
|
|
|
+ None => {
|
|
|
+ println!("No exchange selected");
|
|
|
+ },
|
|
|
}
|
|
|
}
|
|
|
}
|