Merge pull request #20 from selimaj-dev/status-config-ui

Status, config, and UI
This commit is contained in:
2026-07-31 16:30:01 +02:00
committed by GitHub
11 changed files with 332 additions and 208 deletions
+40
View File
@@ -16,6 +16,20 @@ pub enum LogKind {
Debug, Debug,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ItemState {
Starting,
Running,
Stopped,
Error,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Mode {
Auto,
Manual,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Signal { pub struct Signal {
pub symbol: String, pub symbol: String,
@@ -41,6 +55,12 @@ pub struct Position {
pub pnl: Decimal, pub pnl: Decimal,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EngineStatus {
pub strategy_mode: Mode,
pub strategy_state: ItemState,
}
impl std::fmt::Display for MarketTrend { impl std::fmt::Display for MarketTrend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@@ -61,3 +81,23 @@ impl std::fmt::Display for LogKind {
} }
} }
} }
impl std::fmt::Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Auto => write!(f, "\x1b[96mAUTO\x1b[0m"),
Self::Manual => write!(f, "\x1b[97mMANUAL\x1b[0m"),
}
}
}
impl std::fmt::Display for ItemState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Starting => write!(f, "\x1b[93mSTARTING\x1b[0m"),
Self::Running => write!(f, "\x1b[92mRUNNING\x1b[0m"),
Self::Stopped => write!(f, "\x1b[90mSTOPPED\x1b[0m"),
Self::Error => write!(f, "\x1b[91mERROR\x1b[0m"),
}
}
}
+13 -54
View File
@@ -1,8 +1,8 @@
use crate::{ use crate::{
general::{EventLog, Position, Signal}, general::{EngineStatus, EventLog, Position, Signal},
strategy::StrategyManifest, strategy::StrategyManifest,
}; };
use hypersdk::{Decimal, hypercore::CandleInterval}; use hypersdk::Decimal;
pub type SignalStatus = Result<Signal, Signal>; pub type SignalStatus = Result<Signal, Signal>;
@@ -14,8 +14,8 @@ pub enum TerminalServerMessage {
// Positions // Positions
PositionsUpdated(Vec<Position>), PositionsUpdated(Vec<Position>),
// Strategy // Configuration
StrategyUpdated(StrategyStatus), ConfigUpdated(EngineConfig),
// Signals // Signals
SignalsUpdated(Vec<SignalStatus>), SignalsUpdated(Vec<SignalStatus>),
@@ -24,7 +24,7 @@ pub enum TerminalServerMessage {
Inspect(InspectTarget), Inspect(InspectTarget),
// Status // Status
StatusUpdated(Status), StatusUpdated(EngineStatus),
// Logs // Logs
SetLogs(Vec<EventLog>), SetLogs(Vec<EventLog>),
@@ -47,62 +47,21 @@ pub struct MarketItem {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum InspectTarget { pub enum InspectTarget {
None, None,
Some(Vec<InspectItem>), Some(Vec<InspectRow>),
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InspectRow(pub InspectItem, pub InspectItem, pub InspectItem);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum InspectItem { pub enum InspectItem {
String(String), Name(String),
Symbol(String), Symbol(String),
USD(f64), USD(Decimal),
F64(f64), F64(Decimal),
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Status { pub struct EngineConfig {
pub feed: Mode,
pub exchange: String,
pub dex: String,
pub latency: u16,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Mode {
Auto,
Manual,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ItemState {
Running,
Stopped,
Error,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StrategyStatus {
pub strategy: StrategyManifest, pub strategy: StrategyManifest,
pub mode: Mode,
pub state: ItemState,
pub cooldown: CandleInterval,
}
impl std::fmt::Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Auto => write!(f, "\x1b[96mAUTO\x1b[0m"),
Self::Manual => write!(f, "\x1b[97mMANUAL\x1b[0m"),
}
}
}
impl std::fmt::Display for ItemState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Running => write!(f, "\x1b[92mRUNNING\x1b[0m"),
Self::Stopped => write!(f, "\x1b[90mSTOPPED\x1b[0m"),
Self::Error => write!(f, "\x1b[91mERROR\x1b[0m"),
}
}
} }
+56 -31
View File
@@ -15,58 +15,79 @@ pub struct RenderScope {
draw_instructions: Vec<Instr>, draw_instructions: Vec<Instr>,
} }
struct RenderTextResult {
text: String,
line_len: u16,
lines_len: u16,
}
impl RenderTextResult {
fn insert_newline(&mut self, height: u16) -> bool {
if self.lines_len >= height {
return true;
}
self.text.push('\n');
self.line_len = 0;
self.lines_len += 1;
false
}
fn insert(&mut self, c: char) {
self.text.push(c);
self.line_len += 1;
}
}
impl RenderScope { impl RenderScope {
pub fn draw_text<P: Into<Point>, T: Display>(&mut self, at: P, text: T) -> u16 { pub fn draw_text<P: Into<Point>, T: Display>(&mut self, at: P, text: T) -> u16 {
if self.rect.width == 0 || self.rect.height == 0 {
return 0;
}
let point: Point = at.into(); let point: Point = at.into();
let lines = text let text = text.to_string();
.to_string()
.lines() let mut chars = text.chars().peekable();
.map(|line| {
let mut chars = line.chars().peekable(); let mut result = RenderTextResult {
let mut new_line = String::new(); text: String::new(),
let mut len = 0; line_len: 0,
lines_len: 1,
};
while let Some(c) = chars.next() { while let Some(c) = chars.next() {
if c == '\x1b' && chars.peek() == Some(&'[') { if c == '\x1b' && chars.peek() == Some(&'[') {
new_line.push(c); result.text.push(c);
while let Some(c) = chars.next() { while let Some(c) = chars.next() {
new_line.push(c); result.text.push(c);
if c.is_ascii_alphabetic() { if c.is_ascii_alphabetic() {
break; break;
} }
} }
} else if c == '\n' {
continue; if result.insert_newline(self.rect.height) {
break;
} }
} else {
result.insert(c);
new_line.push(c); if result.line_len >= self.rect.width {
len += 1; if result.insert_newline(self.rect.height) {
break;
if len >= self.rect.width as usize { }
new_line.push('\n'); }
len = 0;
} }
} }
new_line
})
.collect::<Vec<_>>()
.join("\n");
let lines = lines
.lines()
.take((self.rect.height - point.y) as usize)
.collect::<Vec<_>>();
let lines_len = lines.len();
self.draw_instructions self.draw_instructions
.push(Instr::DrawText(point, lines.join("\n"))); .push(Instr::DrawText(point, result.text));
lines_len as u16 result.lines_len
} }
} }
@@ -123,6 +144,10 @@ impl RenderScope {
match inst { match inst {
Instr::DrawText(point, text) => { Instr::DrawText(point, text) => {
for (i, line) in text.lines().enumerate() { for (i, line) in text.lines().enumerate() {
if i as u16 + point.y >= self.rect.height {
continue;
}
print_at( print_at(
self.rect.x + point.x, self.rect.x + point.x,
self.rect.y + point.y + i as u16, self.rect.y + point.y + i as u16,
+7 -10
View File
@@ -10,20 +10,17 @@ pub struct ScrollText {
impl Widget for ScrollText { impl Widget for ScrollText {
fn render(&self, scope: &mut crate::render::RenderScope) { fn render(&self, scope: &mut crate::render::RenderScope) {
scope.draw_text(0, &self.title); let title_lines = scope.draw_text(0, &self.title);
let title_lines = self.title.lines().count(); let text = self
let mut y = title_lines as u16;
for line in self
.text .text
.lines() .lines()
.skip(self.scroll) .skip(self.scroll)
.take(scope.rect.height as usize - title_lines) .map(|v| v.trim_end())
{ .collect::<Vec<_>>()
y += scope.draw_text((0, y), line); .join("\n");
}
scope.draw_text((0, title_lines), text);
} }
} }
+44 -25
View File
@@ -22,10 +22,16 @@ impl Engine {
match args[0] { match args[0] {
"reload" => { "reload" => {
*self.config.lock().await = Config::new().await?; *self.config.lock().await = Config::new().await?;
self.terminal_server
.info("engine::config", "successfully reloaded")
.await?;
} }
"save" => { "save" => {
self.config.lock().await.save().await?; self.config.lock().await.save().await?;
self.terminal_server
.info("engine::config", "successfully saved")
.await?;
} }
"set" => { "set" => {
@@ -49,6 +55,12 @@ impl Engine {
self.terminal_server self.terminal_server
.info("config::set", "watchlist set successfully, use `config save` to persist changes") .info("config::set", "watchlist set successfully, use `config save` to persist changes")
.await?; .await?;
self.terminal_server
.broadcast(TerminalServerMessage::ConfigUpdated(
self.get_config_status().await,
))
.await?;
} }
"strategy" | "strat" | "sg" => { "strategy" | "strat" | "sg" => {
@@ -62,7 +74,7 @@ impl Engine {
return self return self
.terminal_server .terminal_server
.error( .error(
"config::set::strategy", "config::set[strategy]",
&format!("Non existent strategy `{id}`"), &format!("Non existent strategy `{id}`"),
) )
.await; .await;
@@ -75,23 +87,24 @@ impl Engine {
self.terminal_server self.terminal_server
.info("config::set", "strategy set successfully, use `config save` to persist changes") .info("config::set", "strategy set successfully, use `config save` to persist changes")
.await?; .await?;
self.terminal_server
.broadcast(TerminalServerMessage::ConfigUpdated(
self.get_config_status().await,
))
.await?;
} }
"cooldown" | "cool" | "cd" => { "cooldown" | "cool" | "cd" => {
set_cfg!(cooldown, { set_cfg!(cooldown, {
self.config.lock().await.cooldown = cooldown; self.config.lock().await.cooldown = cooldown;
self.terminal_server.broadcast(
pulse_sdk::terminal::TerminalServerMessage::StrategyUpdated(StrategyStatus {
strategy: self.strategy_engine.strategy.lock().await.manifest.clone(),
mode: Mode::Auto,
state: ItemState::Running,
cooldown,
}),
)
.await?;
}); });
self.terminal_server
.broadcast(TerminalServerMessage::ConfigUpdated(
self.get_config_status().await,
))
.await?;
} }
_ => { _ => {
@@ -101,25 +114,25 @@ impl Engine {
} }
} }
} else { } else {
self.invalid_command_usage("config").await?; self.invalid_command_usage("engine::config").await?;
} }
} }
_ => { _ => {
self.invalid_command_usage("config").await?; self.invalid_command_usage("engine::config").await?;
} }
} }
} }
"account" | "acc" => { "account" | "acc" => {
if args.len() == 0 { if args.len() == 0 {
return self.invalid_command_usage("account man").await; return self.invalid_command_usage("engine::account").await;
} }
match args[0] { match args[0] {
"list" | "ls" => { "list" | "ls" => {
self.terminal_server self.terminal_server
.info("account man", "ACCOUNT LIST") .info("engine::account", "ACCOUNT LIST")
.await?; .await?;
let accounts = self.accounts.lock().await; let accounts = self.accounts.lock().await;
@@ -127,7 +140,7 @@ impl Engine {
for (name, acc) in &accounts.accounts { for (name, acc) in &accounts.accounts {
self.terminal_server self.terminal_server
.info( .info(
"account man", "engine::account",
&if name == &accounts.active { &if name == &accounts.active {
format!( format!(
"{} (active) -> {}", "{} (active) -> {}",
@@ -144,7 +157,7 @@ impl Engine {
"use" | "set" => { "use" | "set" => {
if args.len() < 2 { if args.len() < 2 {
return self.invalid_command_usage("account man").await; return self.invalid_command_usage("engine::account").await;
} }
let new_active = args[1]; let new_active = args[1];
@@ -154,7 +167,10 @@ impl Engine {
if !accounts.accounts.contains_key(new_active) { if !accounts.accounts.contains_key(new_active) {
return self return self
.terminal_server .terminal_server
.error("account man", &format!("Account not found ({new_active})")) .error(
"engine::account",
&format!("Account not found ({new_active})"),
)
.await; .await;
} }
@@ -162,14 +178,14 @@ impl Engine {
self.terminal_server self.terminal_server
.info( .info(
"account man", "engine::account",
&format!("Account set to {new_active} successfully!"), &format!("Account set to {new_active} successfully!"),
) )
.await?; .await?;
} }
_ => { _ => {
return self.invalid_command_usage("account man").await; return self.invalid_command_usage("engine::account").await;
} }
} }
} }
@@ -179,6 +195,10 @@ impl Engine {
return self.invalid_command_usage("strategy").await; return self.invalid_command_usage("strategy").await;
}; };
match strategy_command {
"start" => {}
_ => {
self.strategy_engine self.strategy_engine
.send(&StrategyEngineMessage::Command { .send(&StrategyEngineMessage::Command {
command: strategy_command.to_owned(), command: strategy_command.to_owned(),
@@ -186,13 +206,12 @@ impl Engine {
}) })
.await?; .await?;
} }
}
}
_ => { _ => {
self.terminal_server self.terminal_server
.error( .error("engine::cmd", &format!("Command '{}' not found", command))
"Command executor",
&format!("Command '{}' not found", command),
)
.await?; .await?;
} }
} }
+38 -4
View File
@@ -28,6 +28,9 @@ pub struct Engine {
// data // data
pub config: Arc<Mutex<Config>>, pub config: Arc<Mutex<Config>>,
pub accounts: Arc<Mutex<AccountList>>, pub accounts: Arc<Mutex<AccountList>>,
// live data / status
pub status: Arc<Mutex<EngineStatus>>,
pub watch_list: Arc<Mutex<WatchList>>, pub watch_list: Arc<Mutex<WatchList>>,
pub signals: Arc<Mutex<Vec<SignalStatus>>>, pub signals: Arc<Mutex<Vec<SignalStatus>>>,
} }
@@ -44,11 +47,20 @@ impl Engine {
let config = Arc::new(Mutex::new(config)); let config = Arc::new(Mutex::new(config));
Ok(Arc::new_cyclic(|engine| Self { Ok(Arc::new_cyclic(|engine| Self {
config, // engine
accounts,
ws_stream: Arc::new(Mutex::new(ws_stream)),
terminal_server: TerminalServer::new(engine.clone()), terminal_server: TerminalServer::new(engine.clone()),
strategy_engine: strategy.initialize(engine.clone()), strategy_engine: strategy.initialize(engine.clone()),
ws_stream: Arc::new(Mutex::new(ws_stream)),
// data
config,
accounts,
// live data / status
status: Arc::new(Mutex::new(EngineStatus {
strategy_mode: Mode::Auto,
strategy_state: ItemState::Stopped,
})),
watch_list: Arc::new(Mutex::new(WatchList { watch_list: Arc::new(Mutex::new(WatchList {
name_to_index: HashMap::new(), name_to_index: HashMap::new(),
items: Vec::new(), items: Vec::new(),
@@ -61,11 +73,33 @@ impl Engine {
/// When a strategy is reloaded it restarts automatically /// When a strategy is reloaded it restarts automatically
pub async fn run(&self) -> anyhow::Result<()> { pub async fn run(&self) -> anyhow::Result<()> {
loop { loop {
self.terminal_server
.info("engine::main", "Strategy starting")
.await?;
self.status.lock().await.strategy_state = ItemState::Starting;
self.update_status().await?;
self.strategy_engine.run().await?; self.strategy_engine.run().await?;
self.terminal_server self.terminal_server
.info("engine::main", "Strategy stopped, restarting") .info("engine::main", "Strategy stopped")
.await?; .await?;
} }
} }
pub async fn get_config_status(&self) -> EngineConfig {
EngineConfig {
strategy: self.strategy_engine.strategy.lock().await.manifest.clone(),
}
}
pub async fn update_status(&self) -> tokio::io::Result<()> {
self.terminal_server
.broadcast(TerminalServerMessage::StatusUpdated(
self.status.lock().await.clone(),
))
.await
}
} }
+8 -2
View File
@@ -93,6 +93,14 @@ impl StrategyEngine {
self.send(&StrategyEngineMessage::Initialize).await?; self.send(&StrategyEngineMessage::Initialize).await?;
engine
.terminal_server
.info("engine::main", "Strategy Started")
.await?;
engine.status.lock().await.strategy_state = ItemState::Running;
engine.update_status().await?;
loop { loop {
match StrategyChild::read(&mut stdout).await? { match StrategyChild::read(&mut stdout).await? {
None => {} None => {}
@@ -158,8 +166,6 @@ impl StrategyEngine {
.unwrap() .unwrap()
.as_millis() as u64; .as_millis() as u64;
println!("getting candlestick");
let interval_ms = match interval { let interval_ms = match interval {
CandleInterval::OneMinute => 60_000, CandleInterval::OneMinute => 60_000,
CandleInterval::ThreeMinutes => 3 * 60_000, CandleInterval::ThreeMinutes => 3 * 60_000,
+54 -21
View File
@@ -45,7 +45,11 @@ impl TerminalServer {
let listener = UnixListener::bind(&path)?; let listener = UnixListener::bind(&path)?;
println!("Terminal server listening on {:?}", path); self.info(
"engine::terminal",
&format!("Terminal server listening on {:?}", path),
)
.await?;
loop { loop {
let (stream, _) = listener.accept().await?; let (stream, _) = listener.accept().await?;
@@ -60,7 +64,23 @@ impl TerminalServer {
tokio::spawn(async move { tokio::spawn(async move {
if let Err(err) = s.handle_client(&id, reader).await { if let Err(err) = s.handle_client(&id, reader).await {
eprintln!("Terminal connection error: {err}"); if matches!(err.kind(), tokio::io::ErrorKind::UnexpectedEof) {
Self::print_log(&EventLog {
kind: LogKind::Err,
name: "engine::terminal".to_string(),
message: format!("Terminal connection error: {err}"),
});
return Ok(());
}
s.error(
"engine::terminal",
&format!("Terminal connection error: {err}"),
)
.await
} else {
Ok(())
} }
}); });
} }
@@ -71,18 +91,13 @@ impl TerminalServer {
self.send_to( self.send_to(
id, id,
pulse_sdk::terminal::TerminalServerMessage::StrategyUpdated(StrategyStatus { TerminalServerMessage::ConfigUpdated(engine.get_config_status().await),
strategy: engine )
.strategy_engine .await?;
.strategy
.lock() self.send_to(
.await id,
.manifest TerminalServerMessage::StatusUpdated(engine.status.lock().await.clone()),
.clone(),
mode: Mode::Auto,
state: ItemState::Running,
cooldown: engine.config.lock().await.cooldown,
}),
) )
.await?; .await?;
@@ -147,7 +162,12 @@ impl TerminalServer {
for (id, client) in clients.iter_mut() { for (id, client) in clients.iter_mut() {
if let Err(e) = Self::send_to_client(client, &msg).await { if let Err(e) = Self::send_to_client(client, &msg).await {
remove_clients.push(*id); remove_clients.push(*id);
println!("{e:?}");
Self::print_log(&EventLog {
kind: LogKind::Err,
name: "engine::terminal".to_string(),
message: format!("Failed to broadcast to client: {e}"),
});
} }
} }
@@ -264,19 +284,32 @@ impl TerminalServer {
name: &str, name: &str,
message: &str, message: &str,
) -> tokio::io::Result<()> { ) -> tokio::io::Result<()> {
let log = EventLog { self.log_raw(EventLog {
kind, kind,
name: name.to_string(), name: name.to_string(),
message: message.to_string(), message: message.to_string(),
}; })
self.logs.lock().await.push(log.clone());
self.broadcast(pulse_sdk::terminal::TerminalServerMessage::AddLog(log))
.await .await
} }
pub fn print_log(log: &EventLog) {
println!(
"[{}{}\x1b[0m] (\x1b[37m{}\x1b[0m) {}",
match &log.kind {
LogKind::Info => "\x1b[34m",
LogKind::Debug => "\x1b[36m",
LogKind::Err => "\x1b[31m",
LogKind::Warn => "\x1b[33m",
},
log.kind,
log.name,
log.message
);
}
pub async fn log_raw(self: &Arc<Self>, log: EventLog) -> tokio::io::Result<()> { pub async fn log_raw(self: &Arc<Self>, log: EventLog) -> tokio::io::Result<()> {
Self::print_log(&log);
self.logs.lock().await.push(log.clone()); self.logs.lock().await.push(log.clone());
self.broadcast(pulse_sdk::terminal::TerminalServerMessage::AddLog(log)) self.broadcast(pulse_sdk::terminal::TerminalServerMessage::AddLog(log))
+47 -35
View File
@@ -10,15 +10,24 @@ impl Formatted for InspectTarget {
match self { match self {
Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()],
Self::Some(items) => items Self::Some(items) => {
let items = items
.iter() .iter()
.map(|item| match item { .map(|row| {
InspectItem::F64(v) => format_f64(*v), (
InspectItem::USD(v) => format!("${}", format_f64(*v)), format_inspect_item(&row.0),
InspectItem::Symbol(v) => format!("\x1b[35m{v}\x1b[0m"), format_inspect_item(&row.1),
InspectItem::String(v) => v.clone(), format_inspect_item(&row.2),
)
}) })
.collect(), .collect::<Vec<_>>();
items
.iter()
.map(|(a, b, c)| Triple(a, b, c))
.collect::<Vec<_>>()
.get_formatted()
}
} }
} }
} }
@@ -114,28 +123,8 @@ impl Formatted for Position {
} }
} }
struct Pair<'a>(&'a str, &'a str);
struct Triple<'a>(&'a str, &'a str, &'a str); struct Triple<'a>(&'a str, &'a str, &'a str);
impl Formatted for Status {
fn get_formatted(&self) -> Vec<String> {
vec![
Pair("Feed", &format!("{:?}", self.feed)),
Pair("Exchange", &self.exchange.clone()),
Pair("DEX", &self.dex.clone()),
Pair("Latency", &format!("{} ms", self.latency)),
]
.get_formatted()
}
}
impl<'a> Formatted for Pair<'a> {
fn get_formatted(&self) -> Vec<String> {
vec![self.0.to_string(), self.1.to_string()]
}
}
impl<'a> Formatted for Triple<'a> { impl<'a> Formatted for Triple<'a> {
fn get_formatted(&self) -> Vec<String> { fn get_formatted(&self) -> Vec<String> {
vec![self.0.to_string(), self.1.to_string(), self.2.to_string()] vec![self.0.to_string(), self.1.to_string(), self.2.to_string()]
@@ -206,19 +195,33 @@ impl<T: Formatted> Formatted for Vec<T> {
} }
} }
impl Formatted for StrategyStatus { impl Formatted for EngineStatus {
fn get_formatted(&self) -> Vec<String> {
vec![
Triple(
"\x1b[2mState\x1b[0m",
"\x1b[2mMode\x1b[0m",
"\x1b[2m..\x1b[0m",
),
Triple(
&self.strategy_state.to_string(),
&self.strategy_mode.to_string(),
"..",
),
]
.get_formatted()
}
}
impl Formatted for EngineConfig {
fn get_formatted(&self) -> Vec<String> { fn get_formatted(&self) -> Vec<String> {
vec![ vec![
Triple( Triple(
"\x1b[2mStrategy\x1b[0m", "\x1b[2mStrategy\x1b[0m",
"\x1b[2mState\x1b[0m", "\x1b[2m..\x1b[0m",
"\x1b[2mMode\x1b[0m", "\x1b[2m..\x1b[0m",
),
Triple(
&format!("\x1b[97m{}\x1b[0m", self.strategy.name),
&self.state.to_string(),
&self.mode.to_string(),
), ),
Triple(&self.strategy.name, "..", ".."),
] ]
.get_formatted() .get_formatted()
} }
@@ -232,6 +235,15 @@ pub fn apply_padding(mut items: Vec<String>) -> Vec<String> {
items items
} }
pub fn format_inspect_item(item: &InspectItem) -> String {
match item {
InspectItem::F64(v) => format_f64(v.as_f64()),
InspectItem::USD(v) => format!("${}", format_f64(v.as_f64())),
InspectItem::Symbol(v) => format!("\x1b[35m{v}\x1b[0m"),
InspectItem::Name(v) => v.clone(),
}
}
pub fn format_symbol(value: &str) -> String { pub fn format_symbol(value: &str) -> String {
format!("\x1b[35m{value}\x1b[0m") format!("\x1b[35m{value}\x1b[0m")
} }
+4 -5
View File
@@ -29,8 +29,8 @@ pub struct PulseTradeApp {
command: State<InputState>, command: State<InputState>,
scroll: State<ScrollState<7>>, scroll: State<ScrollState<7>>,
strategy: State<Option<StrategyStatus>>, config: State<Option<EngineConfig>>,
status: State<Option<Status>>, status: State<Option<EngineStatus>>,
watch_list: State<Vec<MarketItem>>, watch_list: State<Vec<MarketItem>>,
active_positions: State<Vec<Position>>, active_positions: State<Vec<Position>>,
@@ -144,8 +144,7 @@ impl App for PulseTradeApp {
( (
LayoutItem::Widget(Size::Flex(1)), LayoutItem::Widget(Size::Flex(1)),
Box::new( Box::new(
advanced_option_draw(&self.scroll, 3, "CONFIGURATION", &self.strategy) advanced_option_draw(&self.scroll, 3, "CONFIGURATION", &self.config).await,
.await,
), ),
), ),
( (
@@ -182,7 +181,7 @@ async fn main() -> tokio::io::Result<()> {
signals: ctx.use_state(Vec::new()), signals: ctx.use_state(Vec::new()),
logs: ctx.use_state(Vec::new()), logs: ctx.use_state(Vec::new()),
inspect: ctx.use_state(InspectTarget::None), inspect: ctx.use_state(InspectTarget::None),
strategy: ctx.use_state(None), config: ctx.use_state(None),
status: ctx.use_state(None), status: ctx.use_state(None),
}) })
}) })
+5 -5
View File
@@ -49,7 +49,7 @@ impl TerminalClient {
let active_positions = app.active_positions.clone(); let active_positions = app.active_positions.clone();
let logs = app.logs.clone(); let logs = app.logs.clone();
let signals = app.signals.clone(); let signals = app.signals.clone();
let market_overview = app.strategy.clone(); let market_overview = app.config.clone();
let status = app.status.clone(); let status = app.status.clone();
let inspect = app.inspect.clone(); let inspect = app.inspect.clone();
@@ -82,8 +82,8 @@ impl TerminalClient {
active_positions: State<Vec<Position>>, active_positions: State<Vec<Position>>,
logs: State<Vec<EventLog>>, logs: State<Vec<EventLog>>,
signals: State<Vec<SignalStatus>>, signals: State<Vec<SignalStatus>>,
market_overview: State<Option<StrategyStatus>>, config: State<Option<EngineConfig>>,
status: State<Option<Status>>, status: State<Option<EngineStatus>>,
inspect: State<InspectTarget>, inspect: State<InspectTarget>,
) -> tokio::io::Result<()> { ) -> tokio::io::Result<()> {
loop { loop {
@@ -111,8 +111,8 @@ impl TerminalClient {
*active_positions.lock().await = v; *active_positions.lock().await = v;
} }
TerminalServerMessage::StrategyUpdated(v) => { TerminalServerMessage::ConfigUpdated(v) => {
*market_overview.lock().await = Some(v); *config.lock().await = Some(v);
} }
TerminalServerMessage::SignalsUpdated(v) => { TerminalServerMessage::SignalsUpdated(v) => {