@@ -114,7 +114,7 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout(rows: Vec<LayoutItem>) -> LayoutItem {
|
pub fn layout(rows: Vec<LayoutItem>) -> LayoutItem {
|
||||||
LayoutItem::Columns {
|
LayoutItem::Rows {
|
||||||
unit: Size::Percent(100),
|
unit: Size::Percent(100),
|
||||||
items: rows,
|
items: rows,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub struct Context {
|
|||||||
pub(crate) tx: Sender<Box<dyn Any + Send + Sync>>,
|
pub(crate) tx: Sender<Box<dyn Any + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct State<T> {
|
pub struct State<T> {
|
||||||
pub value: Arc<Mutex<T>>,
|
pub value: Arc<Mutex<T>>,
|
||||||
tx: Sender<Box<dyn Any + Send + Sync>>,
|
tx: Sender<Box<dyn Any + Send + Sync>>,
|
||||||
@@ -46,6 +47,15 @@ impl<T: std::fmt::Display> State<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for State<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
State {
|
||||||
|
value: self.value.clone(),
|
||||||
|
tx: self.tx.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T> std::ops::Deref for StateGuard<'a, T> {
|
impl<'a, T> std::ops::Deref for StateGuard<'a, T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
|
|||||||
@@ -18,3 +18,22 @@ impl Widget for Center {
|
|||||||
scope.draw_text((x, y), &self.0);
|
scope.draw_text((x, y), &self.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct End(pub String);
|
||||||
|
|
||||||
|
impl Widget for End {
|
||||||
|
fn render(&self, scope: &mut crate::render::RenderScope) {
|
||||||
|
let mut width = 0;
|
||||||
|
let mut height = 0;
|
||||||
|
|
||||||
|
for line in self.0.lines() {
|
||||||
|
height += 1;
|
||||||
|
width = width.max(line.len() as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = scope.rect.width - width;
|
||||||
|
let y = scope.rect.height - height;
|
||||||
|
|
||||||
|
scope.draw_text((x, y), &self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::{unit::Rect, widget::Widget};
|
||||||
|
|
||||||
|
pub struct Dynamic<W: Widget>(pub fn(Rect) -> W);
|
||||||
|
|
||||||
|
impl<W: Widget> Widget for Dynamic<W> {
|
||||||
|
fn render(&self, scope: &mut crate::render::RenderScope) {
|
||||||
|
(self.0)(scope.rect).render(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::widget::Widget;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InputState {
|
||||||
|
pub text: String,
|
||||||
|
pub cursor: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
text: String::new(),
|
||||||
|
cursor: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "❘");
|
||||||
|
scope.draw_text(0, format!("{}{text}", self.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState {
|
||||||
|
pub fn handle_event(&mut self, event: &Event) -> bool {
|
||||||
|
match event {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Left,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if self.cursor > 0 {
|
||||||
|
self.cursor -= 1;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Right,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if self.cursor < self.text.len() {
|
||||||
|
self.cursor += 1;
|
||||||
|
}
|
||||||
|
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(c),
|
||||||
|
modifiers,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if !modifiers.contains(KeyModifiers::CONTROL)
|
||||||
|
&& !modifiers.contains(KeyModifiers::ALT)
|
||||||
|
{
|
||||||
|
self.text.insert(self.cursor, *c);
|
||||||
|
self.cursor += c.len_utf8();
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::Paste(content) => {
|
||||||
|
self.text.insert_str(self.cursor, content);
|
||||||
|
self.cursor += content.len();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
pub mod center;
|
pub mod align;
|
||||||
pub mod outline;
|
pub mod outline;
|
||||||
pub mod spaced;
|
pub mod spaced;
|
||||||
|
pub mod dynamic;
|
||||||
|
pub mod input;
|
||||||
|
|
||||||
use crate::render::RenderScope;
|
use crate::render::RenderScope;
|
||||||
|
|
||||||
|
|||||||
@@ -23,3 +23,33 @@ impl Widget for Outline {
|
|||||||
scope.draw_text((scope.rect.width - 1, scope.rect.height - 1), "┘");
|
scope.draw_text((scope.rect.width - 1, scope.rect.height - 1), "┘");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TVLine;
|
||||||
|
|
||||||
|
impl Widget for TVLine {
|
||||||
|
fn render(&self, scope: &mut crate::render::RenderScope) {
|
||||||
|
scope.draw_text(0, "─".repeat(scope.rect.width as usize));
|
||||||
|
|
||||||
|
scope.draw_text(0, "┌");
|
||||||
|
scope.draw_text((scope.rect.width - 1, 0), "┐");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BVLine;
|
||||||
|
|
||||||
|
impl Widget for BVLine {
|
||||||
|
fn render(&self, scope: &mut crate::render::RenderScope) {
|
||||||
|
scope.draw_text(0, "─".repeat(scope.rect.width as usize));
|
||||||
|
|
||||||
|
scope.draw_text(0, "└");
|
||||||
|
scope.draw_text((scope.rect.width - 1, 0), "┘");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VLine;
|
||||||
|
|
||||||
|
impl Widget for VLine {
|
||||||
|
fn render(&self, scope: &mut crate::render::RenderScope) {
|
||||||
|
scope.draw_text(0, "─".repeat(scope.rect.width as usize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
use crate::types::{Account, ActivePosition, WatchListItem};
|
||||||
|
|
||||||
|
pub trait Formatted {
|
||||||
|
fn get_formatted(&self) -> Vec<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Formatted for WatchListItem {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()),
|
||||||
|
format_f64(self.price),
|
||||||
|
format!(
|
||||||
|
"{} {}",
|
||||||
|
if self.trend.is_sign_positive() {
|
||||||
|
"\x1b[32m▲\x1b[0m"
|
||||||
|
} else {
|
||||||
|
"\x1b[31m▼\x1b[0m"
|
||||||
|
},
|
||||||
|
format_f64(self.trend.abs())
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Formatted for ActivePosition {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()),
|
||||||
|
format_f64(self.amount),
|
||||||
|
format!(
|
||||||
|
"{} {}",
|
||||||
|
if self.profit.is_sign_positive() {
|
||||||
|
"\x1b[32m▲\x1b[0m"
|
||||||
|
} else {
|
||||||
|
"\x1b[31m▼\x1b[0m"
|
||||||
|
},
|
||||||
|
format_f64(self.profit.abs())
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Property(&'static str, String);
|
||||||
|
|
||||||
|
impl Formatted for Account {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Formatted for Property {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![format!("\x1b[91m{}\x1b[0m", self.0), self.1.clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Formatted> Formatted for Vec<T> {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
|
||||||
|
let rows: Vec<Vec<String>> = self.iter().map(|v| v.get_formatted()).collect();
|
||||||
|
|
||||||
|
if rows.is_empty() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
let column_count = rows.iter().map(|r| r.len()).max().unwrap_or(0);
|
||||||
|
|
||||||
|
let mut widths = vec![0; column_count];
|
||||||
|
|
||||||
|
for row in &rows {
|
||||||
|
for (i, col) in row.iter().enumerate() {
|
||||||
|
widths[i] = widths[i].max(col.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
let mut row_out = String::new();
|
||||||
|
|
||||||
|
for (col_idx, col) in row.iter().enumerate() {
|
||||||
|
let width = widths[col_idx];
|
||||||
|
|
||||||
|
row_out.push_str(&format!("{:<width$}", col, width = width));
|
||||||
|
|
||||||
|
if col_idx + 1 != row.len() {
|
||||||
|
row_out.push_str(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(row_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_padding(mut items: Vec<String>) -> Vec<String> {
|
||||||
|
for item in &mut items {
|
||||||
|
item.insert(0, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_f64(value: f64) -> String {
|
||||||
|
let val = value.to_string();
|
||||||
|
let parts: Vec<&str> = val.split('.').collect();
|
||||||
|
|
||||||
|
let int = parts[0].to_string();
|
||||||
|
let negative = int.starts_with('-');
|
||||||
|
|
||||||
|
let start = if negative { 1 } else { 0 };
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
for (i, c) in int[start..].chars().rev().enumerate() {
|
||||||
|
if i > 0 && i % 3 == 0 {
|
||||||
|
result.push(',');
|
||||||
|
}
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut formatted: String = result.chars().rev().collect();
|
||||||
|
|
||||||
|
if negative {
|
||||||
|
formatted.insert(0, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts.len() > 1 {
|
||||||
|
formatted.push('.');
|
||||||
|
formatted.push_str(parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted
|
||||||
|
}
|
||||||
+173
-16
@@ -1,57 +1,214 @@
|
|||||||
|
pub mod formatting;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use pulse_ui::{
|
use pulse_ui::{
|
||||||
App, layout::{LayoutItem, layout}, state::{Refresh, State}, unit::Size, widget::{outline::Outline, spaced::{SpacedColumns, SpacedRows}},
|
App,
|
||||||
|
layout::{LayoutItem, layout},
|
||||||
|
state::{Refresh, State},
|
||||||
|
unit::Size,
|
||||||
|
widget::{
|
||||||
|
align::Center,
|
||||||
|
input::{Input, InputState},
|
||||||
|
outline::{Outline, VLine},
|
||||||
|
spaced::SpacedColumns,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
formatting::{Formatted, apply_padding},
|
||||||
|
types::{Account, ActivePosition, WatchListItem},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PulseTradeApp {
|
pub struct PulseTradeApp {
|
||||||
count: State<i32>,
|
command: State<InputState>,
|
||||||
|
watch_list: State<Vec<WatchListItem>>,
|
||||||
|
active_positions: State<Vec<ActivePosition>>,
|
||||||
|
account: State<Account>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App for 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 {
|
||||||
|
symbol: "BTC".to_string(),
|
||||||
|
price: 118_402.12,
|
||||||
|
trend: 0.82,
|
||||||
|
});
|
||||||
|
watch_list.push(WatchListItem {
|
||||||
|
symbol: "ETH".to_string(),
|
||||||
|
price: 3_912.48,
|
||||||
|
trend: -0.41,
|
||||||
|
});
|
||||||
|
watch_list.push(WatchListItem {
|
||||||
|
symbol: "SOL".to_string(),
|
||||||
|
price: 182.91,
|
||||||
|
trend: 2.18,
|
||||||
|
});
|
||||||
|
watch_list.push(WatchListItem {
|
||||||
|
symbol: "XRP".to_string(),
|
||||||
|
price: 2.84,
|
||||||
|
trend: 1.22,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut active_positions = self.active_positions.lock().await;
|
||||||
|
|
||||||
|
active_positions.push(ActivePosition {
|
||||||
|
symbol: "BTC".to_string(),
|
||||||
|
profit: 125.50,
|
||||||
|
amount: 0.25,
|
||||||
|
});
|
||||||
|
active_positions.push(ActivePosition {
|
||||||
|
symbol: "ETH".to_string(),
|
||||||
|
profit: -32.75,
|
||||||
|
amount: 1.0,
|
||||||
|
});
|
||||||
|
active_positions.push(ActivePosition {
|
||||||
|
symbol: "SOL".to_string(),
|
||||||
|
profit: 84.20,
|
||||||
|
amount: 5.0,
|
||||||
|
});
|
||||||
|
active_positions.push(ActivePosition {
|
||||||
|
symbol: "XRP".to_string(),
|
||||||
|
profit: 12.30,
|
||||||
|
amount: 0.75,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box<dyn Any + Send + Sync>) {
|
async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box<dyn Any + Send + Sync>) {
|
||||||
if let Some(Refresh) = event.downcast_ref() {
|
if let Some(Refresh) = event.downcast_ref() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.count.lock().await += 1;
|
if let Some(event) = event.downcast_ref() {
|
||||||
|
if self.command.value.lock().await.handle_event(event) {
|
||||||
if *self.count.lock().await > 10 {
|
return;
|
||||||
ctx.close().await;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.close().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn layout(&self) -> pulse_ui::layout::LayoutItem {
|
async fn layout(&self) -> pulse_ui::layout::LayoutItem {
|
||||||
layout(vec![LayoutItem::Frame {
|
LayoutItem::Frame {
|
||||||
padding: 1,
|
padding: 1,
|
||||||
item: Box::new(LayoutItem::Widget(Size::Flex(1))),
|
item: Box::new(layout(vec![
|
||||||
}])
|
LayoutItem::Columns {
|
||||||
|
unit: Size::Fixed(1),
|
||||||
|
items: vec![
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Account status
|
||||||
|
LayoutItem::Spacing(Size::Fixed(1)),
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
// Live status
|
||||||
|
LayoutItem::Spacing(Size::Fixed(1)),
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
// Event logs
|
||||||
|
LayoutItem::Spacing(Size::Fixed(1)),
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
// Input
|
||||||
|
LayoutItem::Spacing(Size::Fixed(1)),
|
||||||
|
LayoutItem::Widget(Size::Fixed(1)),
|
||||||
|
])),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(&mut self, layout: pulse_ui::layout::Allocation) {
|
async fn render(&mut self, layout: pulse_ui::layout::Allocation) {
|
||||||
layout.draw_frame(0, Outline);
|
layout.draw_frame(0, Outline);
|
||||||
|
layout.draw(0, format!(" PULSE TRADER v0.1.0"));
|
||||||
|
layout.draw(1, Center("LIVE".to_string()));
|
||||||
|
layout.draw(2, Center(format!("14:32:51 UTC")));
|
||||||
|
layout.draw_frame(3, VLine);
|
||||||
|
layout.draw_frame(4, VLine);
|
||||||
|
layout.draw_frame(5, VLine);
|
||||||
|
layout.draw_frame(6, VLine);
|
||||||
|
|
||||||
layout.draw(
|
layout.draw(
|
||||||
0,
|
3,
|
||||||
SpacedColumns(vec![
|
SpacedColumns(vec![
|
||||||
(LayoutItem::Widget(Size::Flex(1)), Box::new("one")),
|
|
||||||
(LayoutItem::Widget(Size::Flex(1)), Box::new("two")),
|
|
||||||
(LayoutItem::Widget(Size::Flex(1)), Box::new("three")),
|
|
||||||
(
|
(
|
||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
Box::new(self.count.display().await),
|
Box::new(format!(
|
||||||
|
" WATCHLIST\n{}",
|
||||||
|
apply_padding(self.watch_list.lock().await.get_formatted()).join("\n")
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
Box::new(format!(
|
||||||
|
" ACTIVE POSITIONS\n{}",
|
||||||
|
apply_padding(self.active_positions.lock().await.get_formatted())
|
||||||
|
.join("\n")
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
Box::new(format![
|
||||||
|
" ACCOUNT\n{}",
|
||||||
|
apply_padding(self.account.lock().await.get_formatted()).join("\n")
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
layout.draw(
|
||||||
|
4,
|
||||||
|
SpacedColumns(vec![
|
||||||
|
(
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
Box::new(vec![" ACTIVE STRATEGIES"].join("\n")),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
Box::new(
|
||||||
|
vec![
|
||||||
|
" SIGNALS",
|
||||||
|
" BUY BTC LIM 118,800.12",
|
||||||
|
" BUY BTC STL 118,000.00",
|
||||||
|
" BUY BTC TAP 120,000.00",
|
||||||
|
]
|
||||||
|
.join("\n"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
|
Box::new(
|
||||||
|
vec![
|
||||||
|
" SYSTEM",
|
||||||
|
" Feed: Connected",
|
||||||
|
" Exchange: Binance",
|
||||||
|
" DEX: DEX SCREENER",
|
||||||
|
" Latency: 18 ms",
|
||||||
|
]
|
||||||
|
.join("\n"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
layout.draw(6, Input(" > ", &*self.command.lock().await));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
pulse_ui::run(|ctx| PulseTradeApp {
|
pulse_ui::run(|ctx| PulseTradeApp {
|
||||||
count: ctx.use_state(0),
|
command: ctx.use_state(InputState::new()),
|
||||||
|
watch_list: ctx.use_state(Vec::new()),
|
||||||
|
active_positions: 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,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
pub struct WatchListItem {
|
||||||
|
pub symbol: String,
|
||||||
|
pub price: f64,
|
||||||
|
pub trend: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActivePosition {
|
||||||
|
pub symbol: String,
|
||||||
|
pub profit: f64,
|
||||||
|
pub amount: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Account {
|
||||||
|
pub equity: f64,
|
||||||
|
pub liquid: f64,
|
||||||
|
pub unreal: f64,
|
||||||
|
pub realized: f64,
|
||||||
|
pub margin: f64,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user