From f9ab9b7712970878d4bc85546e56980e80bd779e Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 16:19:48 +0200 Subject: [PATCH 1/9] Improved title and input --- pulse-ui/src/widget/input.rs | 27 +++++++++++++++++++++++++-- src/main.rs | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pulse-ui/src/widget/input.rs b/pulse-ui/src/widget/input.rs index 051763e..8393f7e 100644 --- a/pulse-ui/src/widget/input.rs +++ b/pulse-ui/src/widget/input.rs @@ -22,7 +22,12 @@ pub struct Input<'a>(pub &'a str, pub &'a InputState); impl<'a> Widget for Input<'a> { fn render(&self, scope: &mut crate::render::RenderScope) { let mut text = self.1.text.clone(); - text.insert_str(self.1.cursor, "❘"); + if self.1.cursor == text.len() { + text.insert_str(self.1.cursor, "\x1b[47m \x1b[0m"); + } else { + text.insert_str(self.1.cursor + 1, "\x1b[0m"); + text.insert_str(self.1.cursor, "\x1b[47m\x1b[30m"); + } scope.draw_text(0, format!("{}{text}", self.0)); } } @@ -90,7 +95,25 @@ impl InputState { .. }) => { if self.cursor < self.text.len() { - let prev = self.text[..self.cursor+1] + let prev = self.text[..self.cursor + 1] + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap(); + + self.text.remove(prev); + self.cursor = prev; + } + true + } + + Event::Key(KeyEvent { + code: KeyCode::Char('d'), + modifiers, + .. + }) if modifiers.contains(KeyModifiers::CONTROL) => { + if self.cursor < self.text.len() { + let prev = self.text[..self.cursor + 1] .char_indices() .last() .map(|(i, _)| i) diff --git a/src/main.rs b/src/main.rs index d990a7c..3af7ee3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -147,7 +147,7 @@ impl App for PulseTradeApp { async fn render(&mut self, layout: pulse_ui::layout::Allocation) { layout.draw_frame(0, Outline); - layout.draw(0, format!(" PULSE TRADER v0.1.0")); + layout.draw(0, format!("  PULSE TRADER v{}", env!("CARGO_PKG_VERSION"))); layout.draw(1, Center("LIVE".to_string())); layout.draw(2, Center(format!("14:32:51 UTC"))); layout.draw_frame(3, VLine); From 6214f13293fd63a2dae4073b204b6746aa35aa79 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 16:49:02 +0200 Subject: [PATCH 2/9] Added actual time --- Cargo.lock | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 36 +++++--- 3 files changed, 272 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 609c1a1..a6cda8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,62 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "bitflags" version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -23,6 +67,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossterm" version = "0.29.0" @@ -91,6 +141,71 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.186" @@ -136,6 +251,21 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "parking_lot" version = "0.12.5" @@ -178,6 +308,7 @@ dependencies = [ name = "pulse-trader" version = "0.1.0-alpha.0" dependencies = [ + "chrono", "pulse-ui", "tokio", ] @@ -230,6 +361,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -242,6 +379,12 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -273,6 +416,12 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.2" @@ -329,6 +478,51 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -351,12 +545,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 772db70..3c627d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0-alpha.0" edition = "2024" [dependencies] +chrono = "0.4.45" pulse-ui = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/src/main.rs b/src/main.rs index 3af7ee3..587c3d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,16 +3,10 @@ pub mod types; use std::any::Any; +use chrono::{Local, Utc}; use pulse_ui::{ - App, - layout::{LayoutItem, layout}, - state::{Refresh, State}, - unit::Size, - widget::{ - align::Center, - input::{Input, InputState}, - outline::{Outline, VLine}, - spaced::SpacedColumns, + App, layout::{LayoutItem, layout}, state::{Refresh, State}, unit::Size, widget::{ + align::{Center, End}, input::{Input, InputState}, outline::{Outline, VLine}, spaced::SpacedColumns, }, }; @@ -101,6 +95,14 @@ impl App for PulseTradeApp { param: types::SignalParameter::STL, price: 118_000.0, }); + + let mut logs = self.logs.lock().await; + + logs.push(EventLog { + kind: types::LogKind::WARN, + name: "pulse.init", + message: "We're still not done yet ;)".to_string(), + }); } async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box) { @@ -147,9 +149,19 @@ impl App for PulseTradeApp { async fn render(&mut self, layout: pulse_ui::layout::Allocation) { layout.draw_frame(0, Outline); - layout.draw(0, format!("  PULSE TRADER v{}", env!("CARGO_PKG_VERSION"))); - layout.draw(1, Center("LIVE".to_string())); - layout.draw(2, Center(format!("14:32:51 UTC"))); + layout.draw( + 0, + format!("  PULSE TRADER v{}", env!("CARGO_PKG_VERSION")), + ); + // layout.draw(1, Center("LIVE".to_string())); + layout.draw( + 2, + End(format!( + "{} UTC ({} Local)", + Utc::now().format("%H:%M"), + Local::now().format("%H:%M") + )), + ); layout.draw_frame(3, VLine); layout.draw_frame(4, VLine); layout.draw_frame(5, VLine); From 9ef47f748319fb30024268ee77897823a6f925d9 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 17:17:08 +0200 Subject: [PATCH 3/9] Cross platform keybind handling --- pulse-ui/src/widget/input.rs | 148 ++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/pulse-ui/src/widget/input.rs b/pulse-ui/src/widget/input.rs index 8393f7e..f774d94 100644 --- a/pulse-ui/src/widget/input.rs +++ b/pulse-ui/src/widget/input.rs @@ -1,3 +1,53 @@ +#[cfg(target_os = "macos")] +mod platform { + use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + + pub fn is_backspace(event: &KeyEvent) -> bool { + matches!(event.code, KeyCode::Backspace) + || matches!( + event, + KeyEvent { + code: KeyCode::Char('h'), + modifiers, + .. + } if modifiers.contains(KeyModifiers::CONTROL) + ) + } + + pub fn is_delete(event: &KeyEvent) -> bool { + matches!(event.code, KeyCode::Delete) + || matches!( + event, + KeyEvent { + code: KeyCode::Char('d'), + modifiers, + .. + } if modifiers.contains(KeyModifiers::CONTROL) + ) + } +} + +#[cfg(not(target_os = "macos"))] +mod platform { + use crossterm::event::{KeyCode, KeyEvent}; + + pub fn is_backspace(event: &KeyEvent) -> bool { + matches!(event.code, KeyCode::Backspace) + || matches!( + event, + KeyEvent { + code: KeyCode::Char('h'), + modifiers, + .. + } if modifiers.contains(KeyModifiers::CONTROL) + ) + } + + pub fn is_delete(event: &KeyEvent) -> bool { + matches!(event.code, KeyCode::Delete) + } +} + use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use crate::widget::Widget; @@ -35,6 +85,34 @@ impl<'a> Widget for Input<'a> { impl InputState { pub fn handle_event(&mut self, event: &Event) -> bool { match event { + Event::Key(key) if platform::is_backspace(key) => { + if self.cursor > 0 { + let prev = self.text[..self.cursor] + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap(); + + self.text.remove(prev); + self.cursor = prev; + } + true + } + + Event::Key(key) if platform::is_delete(key) => { + if self.cursor < self.text.len() { + let prev = self.text[..self.cursor + 1] + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap(); + + self.text.remove(prev); + self.cursor = prev; + } + true + } + Event::Key(KeyEvent { code: KeyCode::Left, .. @@ -55,76 +133,6 @@ impl InputState { true } - Event::Key(KeyEvent { - code: KeyCode::Backspace, - .. - }) => { - if self.cursor > 0 { - let prev = self.text[..self.cursor] - .char_indices() - .last() - .map(|(i, _)| i) - .unwrap(); - - self.text.remove(prev); - self.cursor = prev; - } - true - } - - Event::Key(KeyEvent { - code: KeyCode::Char('h'), - modifiers, - .. - }) if modifiers.contains(KeyModifiers::CONTROL) => { - if self.cursor > 0 { - let prev = self.text[..self.cursor] - .char_indices() - .last() - .map(|(i, _)| i) - .unwrap(); - - self.text.remove(prev); - self.cursor = prev; - } - true - } - - Event::Key(KeyEvent { - code: KeyCode::Delete, - .. - }) => { - if self.cursor < self.text.len() { - let prev = self.text[..self.cursor + 1] - .char_indices() - .last() - .map(|(i, _)| i) - .unwrap(); - - self.text.remove(prev); - self.cursor = prev; - } - true - } - - Event::Key(KeyEvent { - code: KeyCode::Char('d'), - modifiers, - .. - }) if modifiers.contains(KeyModifiers::CONTROL) => { - if self.cursor < self.text.len() { - let prev = self.text[..self.cursor + 1] - .char_indices() - .last() - .map(|(i, _)| i) - .unwrap(); - - self.text.remove(prev); - self.cursor = prev; - } - true - } - Event::Key(KeyEvent { code: KeyCode::Char(c), modifiers, From e5086439a1e29e9e89bfd9b3528726301877cf3d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 20:11:25 +0200 Subject: [PATCH 4/9] Updated layout --- src/formatting.rs | 15 ++++----------- src/main.rs | 43 ++++++++++++++++++++++--------------------- src/types.rs | 10 ++-------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index 00b677d..db0936f 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,5 +1,5 @@ use crate::types::{ - Account, ActivePosition, EventLog, LogKind, Signal, SignalKind, System, WatchListItem, + ActivePosition, EventLog, LogKind, MarketOverview, Signal, SignalKind, Status, WatchListItem, }; pub trait Formatted { @@ -78,20 +78,13 @@ impl Formatted for ActivePosition { pub struct Property(&'static str, String); -impl Formatted for Account { +impl Formatted for MarketOverview { fn get_formatted(&self) -> Vec { - vec![ - Property("Equity", format_f64(self.equity)), - Property("Liquid", format_f64(self.liquid)), - Property("Unreal", format_f64(self.unreal)), - Property("Realized", format_f64(self.realized)), - Property("Margin", format_f64(self.margin)), - ] - .get_formatted() + vec![Property("Equity", format_f64(1.0))].get_formatted() } } -impl Formatted for System { +impl Formatted for Status { fn get_formatted(&self) -> Vec { vec![ Property("Feed", format!("{:?}", self.feed)), diff --git a/src/main.rs b/src/main.rs index 587c3d6..42bf9c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,14 +5,21 @@ use std::any::Any; use chrono::{Local, Utc}; use pulse_ui::{ - App, layout::{LayoutItem, layout}, state::{Refresh, State}, unit::Size, widget::{ - align::{Center, End}, input::{Input, InputState}, outline::{Outline, VLine}, spaced::SpacedColumns, + App, + layout::{LayoutItem, layout}, + state::{Refresh, State}, + unit::Size, + widget::{ + align::End, + input::{Input, InputState}, + outline::{Outline, VLine}, + spaced::SpacedColumns, }, }; use crate::{ formatting::{Formatted, apply_padding}, - types::{Account, ActivePosition, EventLog, Signal, System, WatchListItem}, + types::{ActivePosition, EventLog, MarketOverview, Signal, Status, WatchListItem}, }; pub struct PulseTradeApp { @@ -21,8 +28,8 @@ pub struct PulseTradeApp { active_positions: State>, logs: State>, signals: State>, - account: State, - system: State, + market_overview: State, + status: State, } impl App for PulseTradeApp { @@ -188,8 +195,8 @@ impl App for PulseTradeApp { ( LayoutItem::Widget(Size::Flex(1)), Box::new(format![ - " ACCOUNT\n{}", - apply_padding(self.account.lock().await.get_formatted()).join("\n") + " MARKET OVERVIEW\n{}", + apply_padding(self.market_overview.lock().await.get_formatted()).join("\n") ]), ), ]), @@ -198,10 +205,6 @@ impl App for PulseTradeApp { layout.draw( 4, SpacedColumns(vec![ - ( - LayoutItem::Widget(Size::Flex(1)), - Box::new(vec![" ACTIVE STRATEGIES"].join("\n")), - ), ( LayoutItem::Widget(Size::Flex(1)), Box::new(format!( @@ -209,11 +212,15 @@ impl App for PulseTradeApp { apply_padding(self.signals.lock().await.get_formatted()).join("\n") )), ), + ( + LayoutItem::Widget(Size::Flex(1)), + Box::new(vec![" INSPECTOR"].join("\n")), + ), ( LayoutItem::Widget(Size::Flex(1)), Box::new(format![ - " SYSTEM\n{}", - apply_padding(self.system.lock().await.get_formatted()).join("\n") + " STATUS\n{}", + apply_padding(self.status.lock().await.get_formatted()).join("\n") ]), ), ]), @@ -239,14 +246,8 @@ async fn main() { active_positions: ctx.use_state(Vec::new()), signals: ctx.use_state(Vec::new()), logs: ctx.use_state(Vec::new()), - account: ctx.use_state(Account { - equity: 25_483.21, - liquid: 11_928.43, - unreal: 483.12, - realized: 2_182.49, - margin: 0.0, - }), - system: ctx.use_state(System { + market_overview: ctx.use_state(MarketOverview {}), + status: ctx.use_state(Status { feed: types::Feed::Connected, exchange: "Binance".to_string(), dex: "DEX SCREENER".to_string(), diff --git a/src/types.rs b/src/types.rs index 758d400..27832d1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -10,13 +10,7 @@ pub struct ActivePosition { pub amount: f64, } -pub struct Account { - pub equity: f64, - pub liquid: f64, - pub unreal: f64, - pub realized: f64, - pub margin: f64, -} +pub struct MarketOverview {} #[derive(Debug, Clone, Copy)] pub enum Feed { @@ -26,7 +20,7 @@ pub enum Feed { Failed, } -pub struct System { +pub struct Status { pub feed: Feed, pub exchange: String, pub dex: String, From a4f8e07be6e20652369f5211e22262db5f0870dd Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 21:39:08 +0200 Subject: [PATCH 5/9] Market overview --- pulse-ui/src/widget/spaced.rs | 34 +++++++++++++++++++++++++++ src/formatting.rs | 34 +++++++++++++++++++++++++-- src/main.rs | 43 +++++++++++++++++++++++++++-------- src/types.rs | 35 +++++++++++++++++++++++++++- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/pulse-ui/src/widget/spaced.rs b/pulse-ui/src/widget/spaced.rs index 8e28ce3..4623bfa 100644 --- a/pulse-ui/src/widget/spaced.rs +++ b/pulse-ui/src/widget/spaced.rs @@ -67,3 +67,37 @@ impl Widget for SpacedColumns { } } } + +pub struct SpacedRows2(pub Vec<(LayoutItem, Box)>); + +impl Widget for SpacedRows2 { + fn render(&self, scope: &mut crate::render::RenderScope) { + let alloc = scope.rect.allocate(&crate::layout::LayoutItem::Rows { + unit: Percent(100), + items: self.0.iter().map(|v| v.0.clone()).collect(), + }); + + for i in 0..alloc.widgets.len() { + let item = &self.0[i]; + + alloc.draw(i, &*item.1); + } + } +} + +pub struct SpacedColumns2(pub Vec<(LayoutItem, Box)>); + +impl Widget for SpacedColumns2 { + fn render(&self, scope: &mut crate::render::RenderScope) { + let alloc = scope.rect.allocate(&crate::layout::LayoutItem::Columns { + unit: Percent(100), + items: self.0.iter().map(|v| v.0.clone()).collect(), + }); + + for i in 0..alloc.widgets.len() { + let item = &self.0[i]; + + alloc.draw(i, &*item.1); + } + } +} diff --git a/src/formatting.rs b/src/formatting.rs index db0936f..13b7b8f 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,11 +1,29 @@ use crate::types::{ - ActivePosition, EventLog, LogKind, MarketOverview, Signal, SignalKind, Status, WatchListItem, + ActivePosition, Alert, AlertLevel, EventLog, LogKind, MarketOverview, Signal, SignalKind, + Status, WatchListItem, }; pub trait Formatted { fn get_formatted(&self) -> Vec; } +impl Formatted for Alert { + fn get_formatted(&self) -> Vec { + vec![ + format!( + "{}{:?}\x1b[0m", + match &self.level { + AlertLevel::L => "\x1b[34m", + AlertLevel::M => "\x1b[33m", + AlertLevel::H => "\x1b[31m", + }, + self.level + ), + self.message.clone(), + ] + } +} + impl Formatted for EventLog { fn get_formatted(&self) -> Vec { vec![ @@ -80,7 +98,19 @@ pub struct Property(&'static str, String); impl Formatted for MarketOverview { fn get_formatted(&self) -> Vec { - vec![Property("Equity", format_f64(1.0))].get_formatted() + vec![ + Property("TREND", format!("{:?}", self.trend)), + Property("VOLATILITY", format!("{:?}", self.volatility)), + Property( + "PRESSURE", + if self.pressure.is_sign_positive() { + format!("\x1b[32m{:.3}\x1b[0m", self.pressure) + } else { + format!("\x1b[31m{:.3}\x1b[0m", self.pressure) + }, + ), + ] + .get_formatted() } } diff --git a/src/main.rs b/src/main.rs index 42bf9c3..746ad93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,13 +13,13 @@ use pulse_ui::{ align::End, input::{Input, InputState}, outline::{Outline, VLine}, - spaced::SpacedColumns, + spaced::{SpacedColumns, SpacedColumns2, SpacedRows2}, }, }; use crate::{ formatting::{Formatted, apply_padding}, - types::{ActivePosition, EventLog, MarketOverview, Signal, Status, WatchListItem}, + types::{ActivePosition, Alert, EventLog, MarketOverview, Signal, Status, WatchListItem}, }; pub struct PulseTradeApp { @@ -33,7 +33,7 @@ pub struct PulseTradeApp { } impl App for PulseTradeApp { - async fn init(&mut self, ctx: &pulse_ui::state::Context) { + async fn init(&mut self, _ctx: &pulse_ui::state::Context) { let mut watch_list = self.watch_list.lock().await; watch_list.push(WatchListItem { @@ -110,6 +110,23 @@ impl App for PulseTradeApp { name: "pulse.init", message: "We're still not done yet ;)".to_string(), }); + + let mut market_overview = self.market_overview.lock().await; + + market_overview.alerts.push(Alert { + level: types::AlertLevel::H, + message: "BTC funding rate elevated".to_string(), + }); + + market_overview.alerts.push(Alert { + level: types::AlertLevel::M, + message: "Market volatility increasing".to_string(), + }); + + market_overview.alerts.push(Alert { + level: types::AlertLevel::L, + message: "ETH volatility returning to normal".to_string(), + }); } async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box) { @@ -192,13 +209,14 @@ impl App for PulseTradeApp { .join("\n") )), ), - ( - LayoutItem::Widget(Size::Flex(1)), + (LayoutItem::Widget(Size::Flex(1)), { + let mo = self.market_overview.lock().await; Box::new(format![ - " MARKET OVERVIEW\n{}", - apply_padding(self.market_overview.lock().await.get_formatted()).join("\n") - ]), - ), + " MARKET OVERVIEW\n{}\n\n{}", + apply_padding(mo.get_formatted()).join("\n"), + apply_padding(mo.alerts.get_formatted(),).join("\n") + ]) + }), ]), ); @@ -246,7 +264,12 @@ async fn main() { active_positions: ctx.use_state(Vec::new()), signals: ctx.use_state(Vec::new()), logs: ctx.use_state(Vec::new()), - market_overview: ctx.use_state(MarketOverview {}), + market_overview: ctx.use_state(MarketOverview { + trend: types::MarketTrend::Bullish, + volatility: types::Volatility::High, + pressure: 0.324, + alerts: Vec::new(), + }), status: ctx.use_state(Status { feed: types::Feed::Connected, exchange: "Binance".to_string(), diff --git a/src/types.rs b/src/types.rs index 27832d1..9f9f1b3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -10,7 +10,27 @@ pub struct ActivePosition { pub amount: f64, } -pub struct MarketOverview {} +#[derive(Debug, Clone, Copy)] +pub enum MarketTrend { + Bullish, + Bearish, + Neutral, +} + +#[derive(Debug, Clone, Copy)] +pub enum Volatility { + Low, + Medium, + High, +} + +pub struct MarketOverview { + pub trend: MarketTrend, + pub volatility: Volatility, + pub pressure: f64, + + pub alerts: Vec, +} #[derive(Debug, Clone, Copy)] pub enum Feed { @@ -62,3 +82,16 @@ pub struct EventLog { pub name: &'static str, pub message: String, } + +#[derive(Debug, Clone, Copy)] +pub enum AlertLevel { + H, + M, + L, +} + +#[derive(Debug, Clone)] +pub struct Alert { + pub level: AlertLevel, + pub message: String, +} From 278a660d5a58eaa9f9c1ee6d6bcac25e6dc0dbe7 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 22:10:26 +0200 Subject: [PATCH 6/9] Working inspector --- src/formatting.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 14 ++++++++--- src/types.rs | 35 +++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index 13b7b8f..fa8aded 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,12 +1,69 @@ use crate::types::{ - ActivePosition, Alert, AlertLevel, EventLog, LogKind, MarketOverview, Signal, SignalKind, - Status, WatchListItem, + ActivePosition, Alert, AlertLevel, EventLog, InspectTarget, LogKind, MarketOverview, Signal, + SignalKind, Status, WatchListItem, }; pub trait Formatted { fn get_formatted(&self) -> Vec; } +impl Formatted for InspectTarget { + fn get_formatted(&self) -> Vec { + match self { + Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], + + Self::Symbol { + symbol, + price, + trend, + market_trend, + volatility, + pressure, + alerts, + } => vec![ + Property("Symbol", format!("\x1b[35m{}\x1b[0m", symbol)), + Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(*price))), + Property("Trend", format!("{}", format_f64(*trend))), + Property("Market", format!("{:?}", market_trend)), + Property("Volatility", format!("{:?}", volatility)), + Property("Pressure", format!("{:+.2}%", pressure * 100.0)), + Property("Alerts", format!("{}", alerts.len())), + ] + .get_formatted(), + + Self::Position { + symbol, + profit, + amount, + } => vec![ + Property("Symbol", format!("\x1b[35m{}\x1b[0m", symbol)), + Property("Profit", format!("{:+.2}", profit)), + Property("Amount", format!("{}", amount)), + ] + .get_formatted(), + + Self::Signal { + kind, + symbol, + param, + price, + } => vec![ + Property("Type", format!("{:?}", kind)), + Property("Symbol", format!("\x1b[35m{}\x1b[0m", symbol)), + Property("Parameter", format!("{:?}", param)), + Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(*price))), + ] + .get_formatted(), + + Self::Alert { level, message } => vec![ + Property("Level", format!("{:?}", level)), + Property("Message", message.clone()), + ] + .get_formatted(), + } + } +} + impl Formatted for Alert { fn get_formatted(&self) -> Vec { vec![ diff --git a/src/main.rs b/src/main.rs index 746ad93..57033ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,13 +13,16 @@ use pulse_ui::{ align::End, input::{Input, InputState}, outline::{Outline, VLine}, - spaced::{SpacedColumns, SpacedColumns2, SpacedRows2}, + spaced::SpacedColumns, }, }; use crate::{ formatting::{Formatted, apply_padding}, - types::{ActivePosition, Alert, EventLog, MarketOverview, Signal, Status, WatchListItem}, + types::{ + ActivePosition, Alert, EventLog, InspectTarget, MarketOverview, Signal, Status, + WatchListItem, + }, }; pub struct PulseTradeApp { @@ -30,6 +33,7 @@ pub struct PulseTradeApp { signals: State>, market_overview: State, status: State, + inspect: State, } impl App for PulseTradeApp { @@ -232,7 +236,10 @@ impl App for PulseTradeApp { ), ( LayoutItem::Widget(Size::Flex(1)), - Box::new(vec![" INSPECTOR"].join("\n")), + Box::new(format!( + " INSPECTOR\n{}", + apply_padding(self.inspect.lock().await.get_formatted()).join("\n") + )), ), ( LayoutItem::Widget(Size::Flex(1)), @@ -264,6 +271,7 @@ async fn main() { active_positions: ctx.use_state(Vec::new()), signals: ctx.use_state(Vec::new()), logs: ctx.use_state(Vec::new()), + inspect: ctx.use_state(InspectTarget::None), market_overview: ctx.use_state(MarketOverview { trend: types::MarketTrend::Bullish, volatility: types::Volatility::High, diff --git a/src/types.rs b/src/types.rs index 9f9f1b3..f060b54 100644 --- a/src/types.rs +++ b/src/types.rs @@ -95,3 +95,38 @@ pub struct Alert { pub level: AlertLevel, pub message: String, } + +#[derive(Debug, Clone)] +pub enum InspectTarget { + None, + + Symbol { + symbol: String, + price: f64, + trend: f64, + + market_trend: MarketTrend, + volatility: Volatility, + pressure: f64, + + alerts: Vec, + }, + + Position { + symbol: String, + profit: f64, + amount: f64, + }, + + Signal { + kind: SignalKind, + symbol: String, + param: SignalParameter, + price: f64, + }, + + Alert { + level: AlertLevel, + message: String, + }, +} From feb9166c30bc93f9640e9f45faf81f9a36b5f27d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 22:32:49 +0200 Subject: [PATCH 7/9] Working command system --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 13 +++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6cda8f..44c8c16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,6 +309,7 @@ name = "pulse-trader" version = "0.1.0-alpha.0" dependencies = [ "chrono", + "crossterm", "pulse-ui", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 3c627d6..4ea732d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" chrono = "0.4.45" pulse-ui = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +crossterm = { workspace = true } [workspace] members = ["pulse-ui"] diff --git a/src/main.rs b/src/main.rs index 57033ff..35882a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod command; pub mod formatting; pub mod types; @@ -141,10 +142,18 @@ impl App for PulseTradeApp { if let Some(event) = event.downcast_ref() { if self.command.value.lock().await.handle_event(event) { return; + } else if let crossterm::event::Event::Key(key) = event { + if key.code.is_enter() { + let mut command = self.command.lock().await; + let command_text = command.text.clone(); + command.cursor = 0; + command.text.clear(); + + drop(command); + self.execute_command(ctx, command_text.trim()).await; + } } } - - ctx.close().await; } async fn layout(&self) -> pulse_ui::layout::LayoutItem { From 12c17279f8b1ed847ff8c9600f14394d22c63e75 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 22:35:32 +0200 Subject: [PATCH 8/9] Working command system --- src/command.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/command.rs diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..29ce0a3 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,29 @@ +use crate::{PulseTradeApp, types::EventLog}; + +impl PulseTradeApp { + pub async fn execute_command(&mut self, ctx: &pulse_ui::state::Context, command: &str) { + if command.is_empty() { + return; + } + + let (command, args) = if let Some((command, args)) = command.split_once(" ") { + (command, args.split(" ").collect()) + } else { + (command, Vec::new()) + }; + + match command { + "exit" | "quit" | "leave" => { + ctx.close().await; + } + + _ => { + self.logs.lock().await.push(EventLog { + kind: crate::types::LogKind::ERR, + name: "cmd", + message: format!("Command '{}' not found", command), + }); + } + } + } +} From 9c7b492ac9aa7ff0e1b7862b1caa061bdc4b1710 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 22:53:18 +0200 Subject: [PATCH 9/9] Tidy up --- src/command.rs | 4 +- src/formatting.rs | 101 +++++++++++++++++-------------------- src/main.rs | 34 ++++++------- src/types.rs | 125 ++++++++++++++++++++++++++++++---------------- 4 files changed, 146 insertions(+), 118 deletions(-) diff --git a/src/command.rs b/src/command.rs index 29ce0a3..352f1a1 100644 --- a/src/command.rs +++ b/src/command.rs @@ -6,7 +6,7 @@ impl PulseTradeApp { return; } - let (command, args) = if let Some((command, args)) = command.split_once(" ") { + let (command, _args) = if let Some((command, args)) = command.split_once(" ") { (command, args.split(" ").collect()) } else { (command, Vec::new()) @@ -19,7 +19,7 @@ impl PulseTradeApp { _ => { self.logs.lock().await.push(EventLog { - kind: crate::types::LogKind::ERR, + kind: crate::types::LogKind::Err, name: "cmd", message: format!("Command '{}' not found", command), }); diff --git a/src/formatting.rs b/src/formatting.rs index fa8aded..d8b1557 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -12,52 +12,41 @@ impl Formatted for InspectTarget { match self { Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], - Self::Symbol { - symbol, - price, - trend, - market_trend, - volatility, - pressure, - alerts, - } => vec![ - Property("Symbol", format!("\x1b[35m{}\x1b[0m", symbol)), - Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(*price))), - Property("Trend", format!("{}", format_f64(*trend))), - Property("Market", format!("{:?}", market_trend)), - Property("Volatility", format!("{:?}", volatility)), - Property("Pressure", format!("{:+.2}%", pressure * 100.0)), - Property("Alerts", format!("{}", alerts.len())), + Self::Symbol(boxed) => { + let (watch, overview) = &**boxed; + vec![ + Property("Symbol", format!("\x1b[35m{}\x1b[0m", watch.symbol)), + Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(watch.price))), + Property("Trend", format!("{}", format_f64(watch.trend))), + Property("Market", format!("{}", overview.trend)), + Property("Volatility", format!("{}", overview.volatility)), + Property( + "Pressure", + format!("{:+.2}%", overview.pressure * 100.0), + ), + Property("Alerts", format!("{}", overview.alerts.len())), + ] + .get_formatted() + } + + Self::Position(pos) => vec![ + Property("Symbol", format!("\x1b[35m{}\x1b[0m", pos.symbol)), + Property("Profit", format!("{:+.2}", pos.profit)), + Property("Amount", format!("{}", pos.amount)), ] .get_formatted(), - Self::Position { - symbol, - profit, - amount, - } => vec![ - Property("Symbol", format!("\x1b[35m{}\x1b[0m", symbol)), - Property("Profit", format!("{:+.2}", profit)), - Property("Amount", format!("{}", amount)), + Self::Signal(sig) => vec![ + Property("Type", format!("{}", sig.kind)), + Property("Symbol", format!("\x1b[35m{}\x1b[0m", sig.symbol)), + Property("Parameter", format!("{}", sig.param)), + Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(sig.price))), ] .get_formatted(), - Self::Signal { - kind, - symbol, - param, - price, - } => vec![ - Property("Type", format!("{:?}", kind)), - Property("Symbol", format!("\x1b[35m{}\x1b[0m", symbol)), - Property("Parameter", format!("{:?}", param)), - Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(*price))), - ] - .get_formatted(), - - Self::Alert { level, message } => vec![ - Property("Level", format!("{:?}", level)), - Property("Message", message.clone()), + Self::Alert(alert) => vec![ + Property("Level", format!("{}", alert.level)), + Property("Message", alert.message.clone()), ] .get_formatted(), } @@ -68,11 +57,11 @@ impl Formatted for Alert { fn get_formatted(&self) -> Vec { vec![ format!( - "{}{:?}\x1b[0m", + "{}{}\x1b[0m", match &self.level { - AlertLevel::L => "\x1b[34m", - AlertLevel::M => "\x1b[33m", - AlertLevel::H => "\x1b[31m", + AlertLevel::Low => "\x1b[34m", + AlertLevel::Medium => "\x1b[33m", + AlertLevel::High => "\x1b[31m", }, self.level ), @@ -85,12 +74,12 @@ impl Formatted for EventLog { fn get_formatted(&self) -> Vec { vec![ format!( - "{}{:?}\x1b[0m", + "{}{}\x1b[0m", match &self.kind { - LogKind::INFO => "\x1b[34m", - LogKind::DEBUG => "\x1b[36m", - LogKind::ERR => "\x1b[31m", - LogKind::WARN => "\x1b[33m", + LogKind::Info => "\x1b[34m", + LogKind::Debug => "\x1b[36m", + LogKind::Err => "\x1b[31m", + LogKind::Warn => "\x1b[33m", }, self.kind ), @@ -103,13 +92,13 @@ impl Formatted for EventLog { impl Formatted for Signal { fn get_formatted(&self) -> Vec { vec![ - if matches!(self.kind, SignalKind::BUY) { - format!("\x1b[32m{:?}\x1b[0m", self.kind) + if matches!(self.kind, SignalKind::Buy) { + format!("\x1b[32m{}\x1b[0m", self.kind) } else { - format!("\x1b[31m{:?}\x1b[0m", self.kind) + format!("\x1b[31m{}\x1b[0m", self.kind) }, format!("\x1b[35m{}\x1b[0m", self.symbol), - format!("\x1b[34m{:?}\x1b[0m", self.param), + format!("\x1b[34m{}\x1b[0m", self.param), format_f64(self.price), ] } @@ -151,13 +140,13 @@ impl Formatted for ActivePosition { } } -pub struct Property(&'static str, String); +struct Property(&'static str, String); impl Formatted for MarketOverview { fn get_formatted(&self) -> Vec { vec![ - Property("TREND", format!("{:?}", self.trend)), - Property("VOLATILITY", format!("{:?}", self.volatility)), + Property("TREND", format!("{}", self.trend)), + Property("VOLATILITY", format!("{}", self.volatility)), Property( "PRESSURE", if self.pressure.is_sign_positive() { diff --git a/src/main.rs b/src/main.rs index 35882a4..1fd204c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,30 +88,30 @@ impl App for PulseTradeApp { let mut signals = self.signals.lock().await; signals.push(Signal { - kind: types::SignalKind::BUY, + kind: types::SignalKind::Buy, symbol: "BTC".to_string(), - param: types::SignalParameter::LIM, + param: types::SignalParameter::Lim, price: 118_800.0, }); signals.push(Signal { - kind: types::SignalKind::BUY, + kind: types::SignalKind::Buy, symbol: "BTC".to_string(), - param: types::SignalParameter::TAP, + param: types::SignalParameter::Tap, price: 120_000.0, }); signals.push(Signal { - kind: types::SignalKind::BUY, + kind: types::SignalKind::Buy, symbol: "BTC".to_string(), - param: types::SignalParameter::STL, + param: types::SignalParameter::Stl, price: 118_000.0, }); let mut logs = self.logs.lock().await; logs.push(EventLog { - kind: types::LogKind::WARN, + kind: types::LogKind::Warn, name: "pulse.init", message: "We're still not done yet ;)".to_string(), }); @@ -119,17 +119,17 @@ impl App for PulseTradeApp { let mut market_overview = self.market_overview.lock().await; market_overview.alerts.push(Alert { - level: types::AlertLevel::H, + level: types::AlertLevel::High, message: "BTC funding rate elevated".to_string(), }); market_overview.alerts.push(Alert { - level: types::AlertLevel::M, + level: types::AlertLevel::Medium, message: "Market volatility increasing".to_string(), }); market_overview.alerts.push(Alert { - level: types::AlertLevel::L, + level: types::AlertLevel::Low, message: "ETH volatility returning to normal".to_string(), }); } @@ -224,11 +224,11 @@ impl App for PulseTradeApp { ), (LayoutItem::Widget(Size::Flex(1)), { let mo = self.market_overview.lock().await; - Box::new(format![ + Box::new(format!( " MARKET OVERVIEW\n{}\n\n{}", apply_padding(mo.get_formatted()).join("\n"), - apply_padding(mo.alerts.get_formatted(),).join("\n") - ]) + apply_padding(mo.alerts.get_formatted()).join("\n") + )) }), ]), ); @@ -252,20 +252,20 @@ impl App for PulseTradeApp { ), ( LayoutItem::Widget(Size::Flex(1)), - Box::new(format![ + Box::new(format!( " STATUS\n{}", apply_padding(self.status.lock().await.get_formatted()).join("\n") - ]), + )), ), ]), ); layout.draw( 5, - format![ + format!( " EVENT LOGS\n{}", apply_padding(self.logs.lock().await.get_formatted()).join("\n") - ], + ), ); layout.draw(6, Input(" > ", &*self.command.lock().await)); diff --git a/src/types.rs b/src/types.rs index f060b54..2cde269 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,9 +1,11 @@ +#[derive(Debug, Clone)] pub struct WatchListItem { pub symbol: String, pub price: f64, pub trend: f64, } +#[derive(Debug, Clone)] pub struct ActivePosition { pub symbol: String, pub profit: f64, @@ -17,6 +19,16 @@ pub enum MarketTrend { Neutral, } +impl std::fmt::Display for MarketTrend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bullish => write!(f, "Bullish"), + Self::Bearish => write!(f, "Bearish"), + Self::Neutral => write!(f, "Neutral"), + } + } +} + #[derive(Debug, Clone, Copy)] pub enum Volatility { Low, @@ -24,6 +36,17 @@ pub enum Volatility { High, } +impl std::fmt::Display for Volatility { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Low => write!(f, "Low"), + Self::Medium => write!(f, "Medium"), + Self::High => write!(f, "High"), + } + } +} + +#[derive(Debug, Clone)] pub struct MarketOverview { pub trend: MarketTrend, pub volatility: Volatility, @@ -49,18 +72,39 @@ pub struct Status { #[derive(Debug, Clone, Copy)] pub enum SignalKind { - BUY, - SELL, + Buy, + Sell, +} + +impl std::fmt::Display for SignalKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Buy => write!(f, "BUY"), + Self::Sell => write!(f, "SELL"), + } + } } #[derive(Debug, Clone, Copy)] pub enum SignalParameter { - LIM, - STL, - TAP, - CHK, + Lim, + Stl, + Tap, + Chk, } +impl std::fmt::Display for SignalParameter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Lim => write!(f, "LIM"), + Self::Stl => write!(f, "STL"), + Self::Tap => write!(f, "TAP"), + Self::Chk => write!(f, "CHK"), + } + } +} + +#[derive(Debug, Clone)] pub struct Signal { pub kind: SignalKind, pub symbol: String, @@ -70,10 +114,21 @@ pub struct Signal { #[derive(Debug, Clone, Copy)] pub enum LogKind { - INFO, - WARN, - ERR, - DEBUG, + Info, + Warn, + Err, + Debug, +} + +impl std::fmt::Display for LogKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Info => write!(f, "INFO"), + Self::Warn => write!(f, "WARN"), + Self::Err => write!(f, "ERR"), + Self::Debug => write!(f, "DEBUG"), + } + } } #[derive(Debug, Clone)] @@ -85,9 +140,19 @@ pub struct EventLog { #[derive(Debug, Clone, Copy)] pub enum AlertLevel { - H, - M, - L, + High, + Medium, + Low, +} + +impl std::fmt::Display for AlertLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::High => write!(f, "H"), + Self::Medium => write!(f, "M"), + Self::Low => write!(f, "L"), + } + } } #[derive(Debug, Clone)] @@ -99,34 +164,8 @@ pub struct Alert { #[derive(Debug, Clone)] pub enum InspectTarget { None, - - Symbol { - symbol: String, - price: f64, - trend: f64, - - market_trend: MarketTrend, - volatility: Volatility, - pressure: f64, - - alerts: Vec, - }, - - Position { - symbol: String, - profit: f64, - amount: f64, - }, - - Signal { - kind: SignalKind, - symbol: String, - param: SignalParameter, - price: f64, - }, - - Alert { - level: AlertLevel, - message: String, - }, + Symbol(Box<(WatchListItem, MarketOverview)>), + Position(ActivePosition), + Signal(Signal), + Alert(Alert), }