dashboard.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. pub mod pane;
  2. use pane::SerializablePane;
  3. pub use pane::{Uuid, PaneState, PaneContent, PaneSettings};
  4. use serde::{Deserialize, Serialize};
  5. use crate::{
  6. charts::{candlestick::CandlestickChart, footprint::FootprintChart, Message}, data_providers::{
  7. Depth, Exchange, Kline, TickMultiplier, Ticker, Timeframe, Trade
  8. }, StreamType
  9. };
  10. use std::{collections::{HashMap, HashSet}, io::Read, rc::Rc};
  11. use iced::{widget::pane_grid::{self, Configuration}, Point, Size};
  12. pub struct Dashboard {
  13. pub panes: pane_grid::State<PaneState>,
  14. pub focus: Option<pane_grid::Pane>,
  15. pub pane_lock: bool,
  16. pub show_layout_modal: bool,
  17. }
  18. impl Dashboard {
  19. pub fn empty() -> Self {
  20. let pane_config: Configuration<PaneState> = Configuration::Split {
  21. axis: pane_grid::Axis::Vertical,
  22. ratio: 0.8,
  23. a: Box::new(Configuration::Split {
  24. axis: pane_grid::Axis::Horizontal,
  25. ratio: 0.4,
  26. a: Box::new(Configuration::Split {
  27. axis: pane_grid::Axis::Vertical,
  28. ratio: 0.5,
  29. a: Box::new(Configuration::Pane(
  30. PaneState {
  31. id: Uuid::new_v4(),
  32. show_modal: false,
  33. stream: vec![],
  34. content: PaneContent::Starter,
  35. settings: PaneSettings::default(),
  36. })
  37. ),
  38. b: Box::new(Configuration::Pane(
  39. PaneState {
  40. id: Uuid::new_v4(),
  41. show_modal: false,
  42. stream: vec![],
  43. content: PaneContent::Starter,
  44. settings: PaneSettings::default(),
  45. })
  46. ),
  47. }),
  48. b: Box::new(Configuration::Split {
  49. axis: pane_grid::Axis::Vertical,
  50. ratio: 0.5,
  51. a: Box::new(Configuration::Pane(
  52. PaneState {
  53. id: Uuid::new_v4(),
  54. show_modal: false,
  55. stream: vec![],
  56. content: PaneContent::Starter,
  57. settings: PaneSettings::default(),
  58. })
  59. ),
  60. b: Box::new(Configuration::Pane(
  61. PaneState {
  62. id: Uuid::new_v4(),
  63. show_modal: false,
  64. stream: vec![],
  65. content: PaneContent::Starter,
  66. settings: PaneSettings::default(),
  67. })
  68. ),
  69. }),
  70. }),
  71. b: Box::new(Configuration::Pane(
  72. PaneState {
  73. id: Uuid::new_v4(),
  74. show_modal: false,
  75. stream: vec![],
  76. content: PaneContent::Starter,
  77. settings: PaneSettings::default(),
  78. })
  79. ),
  80. };
  81. Self {
  82. panes: pane_grid::State::with_configuration(pane_config),
  83. focus: None,
  84. pane_lock: false,
  85. show_layout_modal: false,
  86. }
  87. }
  88. pub fn from_config(panes: Configuration<PaneState>) -> Self {
  89. Self {
  90. panes: pane_grid::State::with_configuration(panes),
  91. focus: None,
  92. pane_lock: false,
  93. show_layout_modal: false,
  94. }
  95. }
  96. pub fn replace_new_pane(&mut self, pane: pane_grid::Pane) {
  97. if let Some(pane) = self.panes.get_mut(pane) {
  98. *pane = PaneState::new(Uuid::new_v4(), vec![], PaneSettings::default());
  99. }
  100. }
  101. pub fn update_chart_state(&mut self, pane_id: Uuid, message: Message) -> Result<(), &str> {
  102. for (_, pane_state) in self.panes.iter_mut() {
  103. if pane_state.id == pane_id {
  104. match pane_state.content {
  105. PaneContent::Heatmap(ref mut chart) => {
  106. chart.update(&message);
  107. return Ok(());
  108. },
  109. PaneContent::Footprint(ref mut chart) => {
  110. chart.update(&message);
  111. return Ok(());
  112. },
  113. PaneContent::Candlestick(ref mut chart) => {
  114. chart.update(&message);
  115. return Ok(());
  116. },
  117. _ => {
  118. return Err("No chart found");
  119. }
  120. }
  121. }
  122. }
  123. Err("No pane found")
  124. }
  125. pub fn get_pane_stream_mut(&mut self, pane_id: Uuid) -> Result<&mut Vec<StreamType>, &str> {
  126. for (_, pane_state) in self.panes.iter_mut() {
  127. if pane_state.id == pane_id {
  128. return Ok(&mut pane_state.stream);
  129. }
  130. }
  131. Err("No pane found")
  132. }
  133. pub fn get_pane_settings_mut(&mut self, pane_id: Uuid) -> Result<&mut PaneSettings, &str> {
  134. for (_, pane_state) in self.panes.iter_mut() {
  135. if pane_state.id == pane_id {
  136. return Ok(&mut pane_state.settings);
  137. }
  138. }
  139. Err("No pane found")
  140. }
  141. pub fn set_pane_content(&mut self, pane_id: Uuid, content: PaneContent) -> Result<(), &str> {
  142. for (_, pane_state) in self.panes.iter_mut() {
  143. if pane_state.id == pane_id {
  144. pane_state.content = content;
  145. return Ok(());
  146. }
  147. }
  148. Err("No pane found")
  149. }
  150. pub fn pane_change_ticksize(&mut self, pane_id: Uuid, new_tick_multiply: TickMultiplier) -> Result<(), &str> {
  151. for (_, pane_state) in self.panes.iter_mut() {
  152. if pane_state.id == pane_id {
  153. pane_state.settings.tick_multiply = Some(new_tick_multiply);
  154. if let Some(min_tick_size) = pane_state.settings.min_tick_size {
  155. match pane_state.content {
  156. PaneContent::Footprint(ref mut chart) => {
  157. chart.change_tick_size(
  158. new_tick_multiply.multiply_with_min_tick_size(min_tick_size)
  159. );
  160. return Ok(());
  161. },
  162. PaneContent::Heatmap(ref mut chart) => {
  163. chart.change_tick_size(
  164. new_tick_multiply.multiply_with_min_tick_size(min_tick_size)
  165. );
  166. return Ok(());
  167. },
  168. _ => {
  169. return Err("No footprint chart found");
  170. }
  171. }
  172. } else {
  173. return Err("No min tick size found");
  174. }
  175. }
  176. }
  177. Err("No pane found")
  178. }
  179. pub fn pane_change_timeframe(&mut self, pane_id: Uuid, new_timeframe: Timeframe) -> Result<&StreamType, &str> {
  180. for (_, pane_state) in self.panes.iter_mut() {
  181. if pane_state.id == pane_id {
  182. pane_state.settings.selected_timeframe = Some(new_timeframe);
  183. for stream_type in pane_state.stream.iter_mut() {
  184. match stream_type {
  185. StreamType::Kline { timeframe, .. } => {
  186. *timeframe = new_timeframe;
  187. match pane_state.content {
  188. PaneContent::Candlestick(_) => {
  189. return Ok(stream_type);
  190. },
  191. PaneContent::Footprint(_) => {
  192. return Ok(stream_type);
  193. },
  194. _ => {}
  195. }
  196. },
  197. _ => {}
  198. }
  199. }
  200. }
  201. }
  202. Err("No pane found")
  203. }
  204. pub fn pane_set_size_filter(&mut self, pane_id: Uuid, new_size_filter: f32) -> Result<(), &str> {
  205. for (_, pane_state) in self.panes.iter_mut() {
  206. if pane_state.id == pane_id {
  207. match pane_state.content {
  208. PaneContent::Heatmap(ref mut chart) => {
  209. chart.set_size_filter(new_size_filter);
  210. return Ok(());
  211. },
  212. PaneContent::TimeAndSales(ref mut chart) => {
  213. chart.set_size_filter(new_size_filter);
  214. return Ok(());
  215. },
  216. _ => {
  217. return Err("No footprint chart found");
  218. }
  219. }
  220. }
  221. }
  222. Err("No pane found")
  223. }
  224. pub fn insert_klines_vec(&mut self, stream_type: &StreamType, klines: &Vec<Kline>, pane_id: Uuid) {
  225. for (_, pane_state) in self.panes.iter_mut() {
  226. if pane_state.id == pane_id {
  227. match stream_type {
  228. StreamType::Kline { timeframe, .. } => {
  229. let timeframe_u16 = timeframe.to_minutes();
  230. match &mut pane_state.content {
  231. PaneContent::Candlestick(chart) => {
  232. *chart = CandlestickChart::new(klines.to_vec(), timeframe_u16);
  233. },
  234. PaneContent::Footprint(chart) => {
  235. let raw_trades = chart.get_raw_trades();
  236. let tick_size = chart.get_tick_size();
  237. *chart = FootprintChart::new(timeframe_u16, tick_size, klines.to_vec(), raw_trades);
  238. },
  239. _ => {}
  240. }
  241. },
  242. _ => {}
  243. }
  244. }
  245. }
  246. }
  247. pub fn find_and_insert_klines(&mut self, stream_type: &StreamType, klines: &Vec<Kline>) -> Result<(), &str> {
  248. let mut found_match = false;
  249. for (_, pane_state) in self.panes.iter_mut() {
  250. if pane_state.matches_stream(&stream_type) {
  251. match stream_type {
  252. StreamType::Kline { timeframe, .. } => {
  253. let timeframe_u16 = timeframe.to_minutes();
  254. match &mut pane_state.content {
  255. PaneContent::Candlestick(chart) => {
  256. *chart = CandlestickChart::new(klines.to_vec(), timeframe_u16);
  257. found_match = true;
  258. },
  259. PaneContent::Footprint(chart) => {
  260. let raw_trades = chart.get_raw_trades();
  261. let tick_size = chart.get_tick_size();
  262. *chart = FootprintChart::new(timeframe_u16, tick_size, klines.to_vec(), raw_trades);
  263. found_match = true;
  264. },
  265. _ => {}
  266. }
  267. },
  268. _ => {}
  269. }
  270. }
  271. }
  272. if found_match {
  273. Ok(())
  274. } else {
  275. Err("No matching pane found for the stream")
  276. }
  277. }
  278. pub fn find_and_insert_ticksizes(&mut self, stream_type: &StreamType, tick_sizes: f32) -> Result<(), &str> {
  279. let mut found_match = false;
  280. for (_, pane_state) in self.panes.iter_mut() {
  281. if pane_state.matches_stream(&stream_type) {
  282. match &mut pane_state.content {
  283. PaneContent::Footprint(_) => {
  284. pane_state.settings.min_tick_size = Some(tick_sizes);
  285. found_match = true;
  286. },
  287. PaneContent::Heatmap(_) => {
  288. pane_state.settings.min_tick_size = Some(tick_sizes);
  289. found_match = true;
  290. },
  291. _ => {}
  292. }
  293. }
  294. }
  295. if found_match {
  296. Ok(())
  297. } else {
  298. Err("No matching pane found for the stream")
  299. }
  300. }
  301. pub fn update_latest_klines(&mut self, stream_type: &StreamType, kline: &Kline) -> Result<(), &str> {
  302. let mut found_match = false;
  303. for (_, pane_state) in self.panes.iter_mut() {
  304. if pane_state.matches_stream(&stream_type) {
  305. match &mut pane_state.content {
  306. PaneContent::Candlestick(chart) => chart.update_latest_kline(kline),
  307. PaneContent::Footprint(chart) => chart.update_latest_kline(kline),
  308. _ => {}
  309. }
  310. found_match = true;
  311. }
  312. }
  313. if found_match {
  314. Ok(())
  315. } else {
  316. Err("No matching pane found for the stream")
  317. }
  318. }
  319. pub fn update_depth_and_trades(&mut self, stream_type: StreamType, depth_update_t: i64, depth: Depth, trades_buffer: Vec<Trade>) -> Result<(), &str> {
  320. let mut found_match = false;
  321. let depth = Rc::new(depth);
  322. let trades_buffer = trades_buffer.into_boxed_slice();
  323. for (_, pane_state) in self.panes.iter_mut() {
  324. if pane_state.matches_stream(&stream_type) {
  325. match &mut pane_state.content {
  326. PaneContent::Heatmap(chart) => {
  327. chart.insert_datapoint(&trades_buffer, depth_update_t, Rc::clone(&depth));
  328. },
  329. PaneContent::Footprint(chart) => {
  330. chart.insert_datapoint(&trades_buffer, depth_update_t);
  331. },
  332. PaneContent::TimeAndSales(chart) => {
  333. chart.update(&trades_buffer);
  334. },
  335. _ => {}
  336. }
  337. found_match = true;
  338. }
  339. }
  340. if found_match {
  341. Ok(())
  342. } else {
  343. Err("No matching pane found for the stream")
  344. }
  345. }
  346. pub fn get_all_diff_streams(&self) -> HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>> {
  347. let mut pane_streams = HashMap::new();
  348. for (_, pane_state) in self.panes.iter() {
  349. for stream_type in &pane_state.stream {
  350. match stream_type {
  351. StreamType::Kline { exchange, ticker, timeframe } => {
  352. let exchange = exchange.clone();
  353. let ticker = ticker.clone();
  354. let timeframe = timeframe.clone();
  355. let exchange_map = pane_streams.entry(exchange.clone()).or_insert(HashMap::new());
  356. let ticker_map = exchange_map.entry(ticker).or_insert(HashSet::new());
  357. ticker_map.insert(StreamType::Kline { exchange, ticker, timeframe });
  358. },
  359. StreamType::DepthAndTrades { exchange, ticker } => {
  360. let exchange = exchange.clone();
  361. let ticker = ticker.clone();
  362. let exchange_map = pane_streams.entry(exchange).or_insert(HashMap::new());
  363. let ticker_map = exchange_map.entry(ticker).or_insert(HashSet::new());
  364. ticker_map.insert(StreamType::DepthAndTrades { exchange, ticker });
  365. },
  366. _ => {}
  367. }
  368. }
  369. }
  370. pane_streams
  371. }
  372. }
  373. impl Default for Dashboard {
  374. fn default() -> Self {
  375. Self::empty()
  376. }
  377. }
  378. #[derive(Debug, Clone, Deserialize, Serialize)]
  379. pub struct SerializableDashboard {
  380. pub pane: SerializablePane,
  381. }
  382. impl<'a> From<&'a Dashboard> for SerializableDashboard {
  383. fn from(dashboard: &'a Dashboard) -> Self {
  384. use pane_grid::Node;
  385. fn from_layout(panes: &pane_grid::State<PaneState>, node: pane_grid::Node) -> SerializablePane {
  386. match node {
  387. Node::Split {
  388. axis, ratio, a, b, ..
  389. } => SerializablePane::Split {
  390. axis: match axis {
  391. pane_grid::Axis::Horizontal => pane::Axis::Horizontal,
  392. pane_grid::Axis::Vertical => pane::Axis::Vertical,
  393. },
  394. ratio,
  395. a: Box::new(from_layout(panes, *a)),
  396. b: Box::new(from_layout(panes, *b)),
  397. },
  398. Node::Pane(pane) => panes
  399. .get(pane)
  400. .map(SerializablePane::from)
  401. .unwrap_or(SerializablePane::Starter),
  402. }
  403. }
  404. let layout = dashboard.panes.layout().clone();
  405. SerializableDashboard {
  406. pane: from_layout(&dashboard.panes, layout),
  407. }
  408. }
  409. }
  410. impl Default for SerializableDashboard {
  411. fn default() -> Self {
  412. Self {
  413. pane: SerializablePane::Starter,
  414. }
  415. }
  416. }
  417. pub struct SavedState {
  418. pub layouts: HashMap<LayoutId, Dashboard>,
  419. pub last_active_layout: LayoutId,
  420. pub window_size: Option<(f32, f32)>,
  421. pub window_position: Option<(f32, f32)>,
  422. }
  423. impl Default for SavedState {
  424. fn default() -> Self {
  425. let mut layouts = HashMap::new();
  426. layouts.insert(LayoutId::Layout1, Dashboard::default());
  427. layouts.insert(LayoutId::Layout2, Dashboard::default());
  428. layouts.insert(LayoutId::Layout3, Dashboard::default());
  429. layouts.insert(LayoutId::Layout4, Dashboard::default());
  430. SavedState {
  431. layouts,
  432. last_active_layout: LayoutId::Layout1,
  433. window_size: None,
  434. window_position: None,
  435. }
  436. }
  437. }
  438. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
  439. pub enum LayoutId {
  440. Layout1,
  441. Layout2,
  442. Layout3,
  443. Layout4,
  444. }
  445. impl std::fmt::Display for LayoutId {
  446. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  447. match self {
  448. LayoutId::Layout1 => write!(f, "Layout 1"),
  449. LayoutId::Layout2 => write!(f, "Layout 2"),
  450. LayoutId::Layout3 => write!(f, "Layout 3"),
  451. LayoutId::Layout4 => write!(f, "Layout 4"),
  452. }
  453. }
  454. }
  455. impl LayoutId {
  456. pub const ALL: [LayoutId; 4] = [LayoutId::Layout1, LayoutId::Layout2, LayoutId::Layout3, LayoutId::Layout4];
  457. }
  458. #[derive(Debug, Clone, Deserialize, Serialize)]
  459. pub struct SerializableState {
  460. pub layouts: HashMap<LayoutId, SerializableDashboard>,
  461. pub last_active_layout: LayoutId,
  462. pub window_size: Option<(f32, f32)>,
  463. pub window_position: Option<(f32, f32)>,
  464. }
  465. impl SerializableState {
  466. pub fn from_parts(
  467. layouts: HashMap<LayoutId, SerializableDashboard>,
  468. last_active_layout: LayoutId,
  469. size: Option<Size>,
  470. position: Option<Point>,
  471. ) -> Self {
  472. SerializableState {
  473. layouts,
  474. last_active_layout,
  475. window_size: size.map(|s| (s.width, s.height)),
  476. window_position: position.map(|p| (p.x, p.y)),
  477. }
  478. }
  479. }
  480. use std::fs::File;
  481. use std::io::Write;
  482. use std::path::Path;
  483. pub fn write_json_to_file(json: &str, file_path: &str) -> std::io::Result<()> {
  484. let path = Path::new(file_path);
  485. let mut file = File::create(path)?;
  486. file.write_all(json.as_bytes())?;
  487. Ok(())
  488. }
  489. pub fn read_layout_from_file(file_path: &str) -> Result<SerializableState, Box<dyn std::error::Error>> {
  490. let path = Path::new(file_path);
  491. let mut file = File::open(path)?;
  492. let mut contents = String::new();
  493. file.read_to_string(&mut contents)?;
  494. Ok(serde_json::from_str(&contents)?)
  495. }