+38
-7
@@ -26,23 +26,54 @@ impl LayoutItem {
|
|||||||
|
|
||||||
let allocation = if is_row { alloc.height } else { alloc.width };
|
let allocation = if is_row { alloc.height } else { alloc.width };
|
||||||
|
|
||||||
let remaining = allocation
|
let fixed_total: u16 = items
|
||||||
- items
|
.iter()
|
||||||
.iter()
|
.map(|item| item.get_size().get_fixed(allocation))
|
||||||
.map(|item| item.get_size().get_fixed(allocation))
|
.sum();
|
||||||
.sum::<u16>();
|
|
||||||
|
let remaining = allocation.saturating_sub(fixed_total);
|
||||||
|
|
||||||
let total_weight = items
|
let total_weight = items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.get_size().get_flex())
|
.map(|item| item.get_size().get_flex())
|
||||||
.sum::<u16>();
|
.sum::<u16>();
|
||||||
|
|
||||||
|
let base_allocs: Vec<u16> = items
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.get_size().get(allocation, remaining, total_weight))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let flex_alloc_total: u16 = items
|
||||||
|
.iter()
|
||||||
|
.zip(base_allocs.iter())
|
||||||
|
.filter(|(item, _)| item.get_size().get_flex() > 0)
|
||||||
|
.map(|(_, a)| a)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let gap = remaining.saturating_sub(flex_alloc_total);
|
||||||
|
|
||||||
|
let fill_count = items
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.get_size().is_fill())
|
||||||
|
.count() as u16;
|
||||||
|
|
||||||
|
let gap_per_fill = if fill_count > 0 { gap / fill_count } else { 0 };
|
||||||
|
let gap_remainder = if fill_count > 0 { gap % fill_count } else { 0 };
|
||||||
|
|
||||||
let mut curr = if is_row { alloc.y } else { alloc.x };
|
let mut curr = if is_row { alloc.y } else { alloc.x };
|
||||||
|
let mut fill_idx = 0u16;
|
||||||
|
|
||||||
for item in items {
|
for (i, item) in items.iter().enumerate() {
|
||||||
let size = item.get_size();
|
let size = item.get_size();
|
||||||
|
let mut unit_alloc = base_allocs[i];
|
||||||
|
|
||||||
let unit_alloc = size.get(allocation, remaining, total_weight);
|
if size.is_fill() {
|
||||||
|
unit_alloc += gap_per_fill;
|
||||||
|
if fill_idx == 0 {
|
||||||
|
unit_alloc += gap_remainder;
|
||||||
|
}
|
||||||
|
fill_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let item_alloc = if is_row {
|
let item_alloc = if is_row {
|
||||||
Rect {
|
Rect {
|
||||||
|
|||||||
+14
-1
@@ -9,6 +9,7 @@ pub enum Size {
|
|||||||
Fixed(u16),
|
Fixed(u16),
|
||||||
Flex(u16),
|
Flex(u16),
|
||||||
Percent(u16),
|
Percent(u16),
|
||||||
|
Fill,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -20,17 +21,22 @@ pub struct Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Size {
|
impl Size {
|
||||||
|
pub fn is_fill(&self) -> bool {
|
||||||
|
matches!(self, Self::Fill)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_fixed(&self, alloc: u16) -> u16 {
|
pub fn get_fixed(&self, alloc: u16) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
Self::Fixed(v) => *v,
|
Self::Fixed(v) => *v,
|
||||||
Self::Percent(v) => alloc * (*v) / 100,
|
Self::Percent(v) => alloc * (*v) / 100,
|
||||||
Self::Flex(_) => 0,
|
Self::Flex(_) | Self::Fill => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_flex(&self) -> u16 {
|
pub fn get_flex(&self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
Self::Flex(f) => *f,
|
Self::Flex(f) => *f,
|
||||||
|
Self::Fill => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +52,13 @@ impl Size {
|
|||||||
remaining * v / total_weight
|
remaining * v / total_weight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self::Fill => {
|
||||||
|
if total_weight == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
remaining / total_weight
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,41 @@ impl InputState {
|
|||||||
true
|
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 {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char(c),
|
code: KeyCode::Char(c),
|
||||||
modifiers,
|
modifiers,
|
||||||
|
|||||||
+51
-3
@@ -1,13 +1,49 @@
|
|||||||
use crate::types::{Account, ActivePosition, WatchListItem};
|
use crate::types::{
|
||||||
|
Account, ActivePosition, EventLog, LogKind, Signal, SignalKind, System, WatchListItem,
|
||||||
|
};
|
||||||
|
|
||||||
pub trait Formatted {
|
pub trait Formatted {
|
||||||
fn get_formatted(&self) -> Vec<String>;
|
fn get_formatted(&self) -> Vec<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Formatted for EventLog {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
format!(
|
||||||
|
"{}{:?}\x1b[0m",
|
||||||
|
match &self.kind {
|
||||||
|
LogKind::INFO => "\x1b[34m",
|
||||||
|
LogKind::DEBUG => "\x1b[36m",
|
||||||
|
LogKind::ERR => "\x1b[31m",
|
||||||
|
LogKind::WARN => "\x1b[33m",
|
||||||
|
},
|
||||||
|
self.kind
|
||||||
|
),
|
||||||
|
format!("(\x1b[37m{}\x1b[0m)", self.name),
|
||||||
|
self.message.clone(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Formatted for Signal {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
if matches!(self.kind, SignalKind::BUY) {
|
||||||
|
format!("\x1b[32m{:?}\x1b[0m", self.kind)
|
||||||
|
} else {
|
||||||
|
format!("\x1b[31m{:?}\x1b[0m", self.kind)
|
||||||
|
},
|
||||||
|
format!("\x1b[35m{}\x1b[0m", self.symbol),
|
||||||
|
format!("\x1b[34m{:?}\x1b[0m", self.param),
|
||||||
|
format_f64(self.price),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Formatted for WatchListItem {
|
impl Formatted for WatchListItem {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
vec![
|
vec![
|
||||||
format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()),
|
format!("\x1b[35m{}\x1b[0m", self.symbol),
|
||||||
format_f64(self.price),
|
format_f64(self.price),
|
||||||
format!(
|
format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
@@ -25,7 +61,7 @@ impl Formatted for WatchListItem {
|
|||||||
impl Formatted for ActivePosition {
|
impl Formatted for ActivePosition {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
vec![
|
vec![
|
||||||
format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()),
|
format!("\x1b[35m{}\x1b[0m", self.symbol),
|
||||||
format_f64(self.amount),
|
format_f64(self.amount),
|
||||||
format!(
|
format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
@@ -55,6 +91,18 @@ impl Formatted for Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Formatted for System {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
Property("Feed", format!("{:?}", self.feed)),
|
||||||
|
Property("Exchange", self.exchange.clone()),
|
||||||
|
Property("DEX", self.dex.clone()),
|
||||||
|
Property("Latency", format!("{} ms", self.latency)),
|
||||||
|
]
|
||||||
|
.get_formatted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Formatted for Property {
|
impl Formatted for Property {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
vec![format!("\x1b[91m{}\x1b[0m", self.0), self.1.clone()]
|
vec![format!("\x1b[91m{}\x1b[0m", self.0), self.1.clone()]
|
||||||
|
|||||||
+52
-21
@@ -18,14 +18,17 @@ use pulse_ui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
formatting::{Formatted, apply_padding},
|
formatting::{Formatted, apply_padding},
|
||||||
types::{Account, ActivePosition, WatchListItem},
|
types::{Account, ActivePosition, EventLog, Signal, System, WatchListItem},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PulseTradeApp {
|
pub struct PulseTradeApp {
|
||||||
command: State<InputState>,
|
command: State<InputState>,
|
||||||
watch_list: State<Vec<WatchListItem>>,
|
watch_list: State<Vec<WatchListItem>>,
|
||||||
active_positions: State<Vec<ActivePosition>>,
|
active_positions: State<Vec<ActivePosition>>,
|
||||||
|
logs: State<Vec<EventLog>>,
|
||||||
|
signals: State<Vec<Signal>>,
|
||||||
account: State<Account>,
|
account: State<Account>,
|
||||||
|
system: State<System>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App for PulseTradeApp {
|
impl App for PulseTradeApp {
|
||||||
@@ -75,6 +78,29 @@ impl App for PulseTradeApp {
|
|||||||
profit: 12.30,
|
profit: 12.30,
|
||||||
amount: 0.75,
|
amount: 0.75,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut signals = self.signals.lock().await;
|
||||||
|
|
||||||
|
signals.push(Signal {
|
||||||
|
kind: types::SignalKind::BUY,
|
||||||
|
symbol: "BTC".to_string(),
|
||||||
|
param: types::SignalParameter::LIM,
|
||||||
|
price: 118_800.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
signals.push(Signal {
|
||||||
|
kind: types::SignalKind::BUY,
|
||||||
|
symbol: "BTC".to_string(),
|
||||||
|
param: types::SignalParameter::TAP,
|
||||||
|
price: 120_000.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
signals.push(Signal {
|
||||||
|
kind: types::SignalKind::BUY,
|
||||||
|
symbol: "BTC".to_string(),
|
||||||
|
param: types::SignalParameter::STL,
|
||||||
|
price: 118_000.0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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>) {
|
||||||
@@ -111,7 +137,7 @@ impl App for PulseTradeApp {
|
|||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
// Event logs
|
// Event logs
|
||||||
LayoutItem::Spacing(Size::Fixed(1)),
|
LayoutItem::Spacing(Size::Fixed(1)),
|
||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Fill),
|
||||||
// Input
|
// Input
|
||||||
LayoutItem::Spacing(Size::Fixed(1)),
|
LayoutItem::Spacing(Size::Fixed(1)),
|
||||||
LayoutItem::Widget(Size::Fixed(1)),
|
LayoutItem::Widget(Size::Fixed(1)),
|
||||||
@@ -166,32 +192,29 @@ impl App for PulseTradeApp {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
Box::new(
|
Box::new(format!(
|
||||||
vec![
|
" SIGNALS\n{}",
|
||||||
" SIGNALS",
|
apply_padding(self.signals.lock().await.get_formatted()).join("\n")
|
||||||
" 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)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
Box::new(
|
Box::new(format![
|
||||||
vec![
|
" SYSTEM\n{}",
|
||||||
" SYSTEM",
|
apply_padding(self.system.lock().await.get_formatted()).join("\n")
|
||||||
" Feed: Connected",
|
]),
|
||||||
" Exchange: Binance",
|
|
||||||
" DEX: DEX SCREENER",
|
|
||||||
" Latency: 18 ms",
|
|
||||||
]
|
|
||||||
.join("\n"),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
layout.draw(
|
||||||
|
5,
|
||||||
|
format![
|
||||||
|
" EVENT LOGS\n{}",
|
||||||
|
apply_padding(self.logs.lock().await.get_formatted()).join("\n")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
layout.draw(6, Input(" > ", &*self.command.lock().await));
|
layout.draw(6, Input(" > ", &*self.command.lock().await));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,6 +225,8 @@ async fn main() {
|
|||||||
command: ctx.use_state(InputState::new()),
|
command: ctx.use_state(InputState::new()),
|
||||||
watch_list: ctx.use_state(Vec::new()),
|
watch_list: ctx.use_state(Vec::new()),
|
||||||
active_positions: ctx.use_state(Vec::new()),
|
active_positions: ctx.use_state(Vec::new()),
|
||||||
|
signals: ctx.use_state(Vec::new()),
|
||||||
|
logs: ctx.use_state(Vec::new()),
|
||||||
account: ctx.use_state(Account {
|
account: ctx.use_state(Account {
|
||||||
equity: 25_483.21,
|
equity: 25_483.21,
|
||||||
liquid: 11_928.43,
|
liquid: 11_928.43,
|
||||||
@@ -209,6 +234,12 @@ async fn main() {
|
|||||||
realized: 2_182.49,
|
realized: 2_182.49,
|
||||||
margin: 0.0,
|
margin: 0.0,
|
||||||
}),
|
}),
|
||||||
|
system: ctx.use_state(System {
|
||||||
|
feed: types::Feed::Connected,
|
||||||
|
exchange: "Binance".to_string(),
|
||||||
|
dex: "DEX SCREENER".to_string(),
|
||||||
|
latency: 18,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,3 +17,54 @@ pub struct Account {
|
|||||||
pub realized: f64,
|
pub realized: f64,
|
||||||
pub margin: f64,
|
pub margin: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Feed {
|
||||||
|
Connected,
|
||||||
|
Disconnected,
|
||||||
|
Connecting,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct System {
|
||||||
|
pub feed: Feed,
|
||||||
|
pub exchange: String,
|
||||||
|
pub dex: String,
|
||||||
|
pub latency: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SignalKind {
|
||||||
|
BUY,
|
||||||
|
SELL,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SignalParameter {
|
||||||
|
LIM,
|
||||||
|
STL,
|
||||||
|
TAP,
|
||||||
|
CHK,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Signal {
|
||||||
|
pub kind: SignalKind,
|
||||||
|
pub symbol: String,
|
||||||
|
pub param: SignalParameter,
|
||||||
|
pub price: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum LogKind {
|
||||||
|
INFO,
|
||||||
|
WARN,
|
||||||
|
ERR,
|
||||||
|
DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EventLog {
|
||||||
|
pub kind: LogKind,
|
||||||
|
pub name: &'static str,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user