Merge pull request #14 from selimaj-dev/strategy-ui-and-manifest
Strategy UI and manifest
This commit is contained in:
Generated
+1
@@ -3907,6 +3907,7 @@ name = "pulse-wire"
|
|||||||
version = "0.1.0-alpha.0"
|
version = "0.1.0-alpha.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pulse-macros",
|
"pulse-macros",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+2
-1
@@ -21,7 +21,7 @@ hypersdk = "0.2.14"
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
rand = "0.8.7"
|
rand = "0.8.7"
|
||||||
toml = "1.1.3"
|
toml = "1.1.3"
|
||||||
serde = { version = "1.0.229", features = ["serde_derive"] }
|
serde = { workspace = true }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["pulse-macros", "pulse-ui", "pulse-wire"]
|
members = ["pulse-macros", "pulse-ui", "pulse-wire"]
|
||||||
@@ -33,6 +33,7 @@ pulse-wire = { path = "pulse-wire", version = "0.1.0-alpha.0" }
|
|||||||
|
|
||||||
tokio = "1.52.3"
|
tokio = "1.52.3"
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
|
serde = { version = "1.0.229", features = ["serde_derive"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "pulse-trader"
|
name = "pulse-trader"
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pulse-macros = { workspace = true }
|
pulse-macros = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use pulse_macros::pwp;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PulseWire,
|
PulseWire,
|
||||||
units::{Symbol, USD},
|
units::{Direction, Symbol, USD},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[pwp]
|
#[pwp]
|
||||||
@@ -13,16 +13,11 @@ pub enum MarketTrend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[pwp]
|
#[pwp]
|
||||||
pub enum Volatility {
|
pub enum LogKind {
|
||||||
Low,
|
Info,
|
||||||
Medium,
|
Warn,
|
||||||
High,
|
Err,
|
||||||
}
|
Debug,
|
||||||
|
|
||||||
#[pwp]
|
|
||||||
pub enum Direction {
|
|
||||||
Buy,
|
|
||||||
Sell,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pwp]
|
#[pwp]
|
||||||
@@ -36,14 +31,6 @@ pub struct Signal {
|
|||||||
pub stop_loss: USD,
|
pub stop_loss: USD,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pwp]
|
|
||||||
pub enum LogKind {
|
|
||||||
Info,
|
|
||||||
Warn,
|
|
||||||
Err,
|
|
||||||
Debug,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pwp]
|
#[pwp]
|
||||||
pub struct EventLog {
|
pub struct EventLog {
|
||||||
pub kind: LogKind,
|
pub kind: LogKind,
|
||||||
@@ -59,15 +46,6 @@ pub struct Position {
|
|||||||
pub profit: USD,
|
pub profit: USD,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Direction {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Buy => write!(f, "BUY"),
|
|
||||||
Self::Sell => write!(f, "SELL"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -78,15 +56,6 @@ impl std::fmt::Display for MarketTrend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for LogKind {
|
impl std::fmt::Display for LogKind {
|
||||||
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 {
|
||||||
|
|||||||
@@ -2,11 +2,15 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub mod general;
|
pub mod general;
|
||||||
|
pub mod strategy;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
pub mod units;
|
pub mod units;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
pub use crate::PulseWire;
|
||||||
pub use crate::general::*;
|
pub use crate::general::*;
|
||||||
|
pub use crate::server_path;
|
||||||
|
pub use crate::strategy::*;
|
||||||
pub use crate::terminal::*;
|
pub use crate::terminal::*;
|
||||||
pub use crate::units::*;
|
pub use crate::units::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::{PulseWire, units::TimeFrame};
|
||||||
|
use pulse_macros::pwp;
|
||||||
|
|
||||||
|
#[pwp]
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct StrategyManifest {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
author: String,
|
||||||
|
version: String,
|
||||||
|
|
||||||
|
timeframes: Vec<TimeFrame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pwp]
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct RiskManifest {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
author: String,
|
||||||
|
version: String,
|
||||||
|
|
||||||
|
max_loss: u8,
|
||||||
|
cooldown: TimeFrame,
|
||||||
|
}
|
||||||
+45
-10
@@ -1,7 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
PulseWire,
|
PulseWire,
|
||||||
general::{EventLog, MarketTrend, Position, Signal, Volatility},
|
general::{EventLog, MarketTrend, Position, Signal},
|
||||||
units::{Symbol, USD},
|
strategy::{RiskManifest, StrategyManifest},
|
||||||
|
units::{Symbol, TimeFrame, USD, Volatility},
|
||||||
};
|
};
|
||||||
use pulse_macros::pwp;
|
use pulse_macros::pwp;
|
||||||
|
|
||||||
@@ -13,8 +14,8 @@ pub enum TerminalServerMessage {
|
|||||||
// Positions
|
// Positions
|
||||||
PositionsUpdated(Vec<Position>),
|
PositionsUpdated(Vec<Position>),
|
||||||
|
|
||||||
// Overview
|
// Strategy
|
||||||
OverviewUpdated(MarketOverview),
|
StrategyUpdated(Strategy),
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
SignalsUpdated(Vec<Signal>),
|
SignalsUpdated(Vec<Signal>),
|
||||||
@@ -86,18 +87,33 @@ pub enum InspectItem {
|
|||||||
|
|
||||||
#[pwp]
|
#[pwp]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
feed: Feed,
|
feed: Mode,
|
||||||
exchange: String,
|
exchange: String,
|
||||||
dex: String,
|
dex: String,
|
||||||
latency: u16,
|
latency: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pwp]
|
#[pwp]
|
||||||
pub enum Feed {
|
pub enum Mode {
|
||||||
Connected,
|
Auto,
|
||||||
Disconnected,
|
Manual,
|
||||||
Connecting,
|
}
|
||||||
Failed,
|
|
||||||
|
#[pwp]
|
||||||
|
pub enum ItemState {
|
||||||
|
Running,
|
||||||
|
Off,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pwp]
|
||||||
|
pub struct Strategy {
|
||||||
|
strategy: StrategyManifest,
|
||||||
|
risk: RiskManifest,
|
||||||
|
|
||||||
|
mode: Mode,
|
||||||
|
state: ItemState,
|
||||||
|
cooldown: TimeFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for AlertLevel {
|
impl std::fmt::Display for AlertLevel {
|
||||||
@@ -109,3 +125,22 @@ impl std::fmt::Display for AlertLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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::Off => write!(f, "\x1b[90mOFF\x1b[0m"),
|
||||||
|
Self::Error => write!(f, "\x1b[91mERROR\x1b[0m"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use pulse_macros::pwp;
|
||||||
|
|
||||||
use crate::PulseWire;
|
use crate::PulseWire;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -6,6 +8,55 @@ pub struct Symbol(pub String);
|
|||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct USD(pub f64);
|
pub struct USD(pub f64);
|
||||||
|
|
||||||
|
#[pwp]
|
||||||
|
#[derive(Copy, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub enum TimeFrame {
|
||||||
|
#[serde(rename = "1m")]
|
||||||
|
M1,
|
||||||
|
#[serde(rename = "3m")]
|
||||||
|
M3,
|
||||||
|
#[serde(rename = "5m")]
|
||||||
|
M5,
|
||||||
|
#[serde(rename = "15m")]
|
||||||
|
M15,
|
||||||
|
#[serde(rename = "30m")]
|
||||||
|
M30,
|
||||||
|
|
||||||
|
#[serde(rename = "1h")]
|
||||||
|
H1,
|
||||||
|
#[serde(rename = "2h")]
|
||||||
|
H2,
|
||||||
|
#[serde(rename = "4h")]
|
||||||
|
H4,
|
||||||
|
#[serde(rename = "8h")]
|
||||||
|
H8,
|
||||||
|
#[serde(rename = "12h")]
|
||||||
|
H12,
|
||||||
|
|
||||||
|
#[serde(rename = "1d")]
|
||||||
|
D1,
|
||||||
|
#[serde(rename = "3d")]
|
||||||
|
D3,
|
||||||
|
|
||||||
|
#[serde(rename = "1w")]
|
||||||
|
W1,
|
||||||
|
#[serde(rename = "1M")]
|
||||||
|
Month1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pwp]
|
||||||
|
pub enum Direction {
|
||||||
|
Buy,
|
||||||
|
Sell,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pwp]
|
||||||
|
pub enum Volatility {
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Symbol {
|
impl std::fmt::Display for Symbol {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "\x1b[35m{}\x1b[0m", self.0)
|
write!(f, "\x1b[35m{}\x1b[0m", self.0)
|
||||||
@@ -96,3 +147,46 @@ pub fn format_f64(value: f64) -> String {
|
|||||||
|
|
||||||
formatted
|
formatted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TimeFrame {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::M1 => "1m",
|
||||||
|
Self::M3 => "3m",
|
||||||
|
Self::M5 => "5m",
|
||||||
|
Self::M15 => "15m",
|
||||||
|
Self::M30 => "30m",
|
||||||
|
|
||||||
|
Self::H1 => "1h",
|
||||||
|
Self::H2 => "2h",
|
||||||
|
Self::H4 => "4h",
|
||||||
|
Self::H8 => "8h",
|
||||||
|
Self::H12 => "12h",
|
||||||
|
|
||||||
|
Self::D1 => "1d",
|
||||||
|
Self::D3 => "3d",
|
||||||
|
|
||||||
|
Self::W1 => "1w",
|
||||||
|
Self::Month1 => "1M",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Direction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Buy => write!(f, "BUY"),
|
||||||
|
Self::Sell => write!(f, "SELL"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use pulse_wire::{
|
|
||||||
general::Position,
|
|
||||||
units::{Symbol, USD},
|
|
||||||
};
|
|
||||||
use tokio::{sync::Mutex, task::JoinHandle};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
store::{accounts::AccountList, config::Config},
|
store::{accounts::AccountList, config::Config},
|
||||||
terminal::TerminalServer,
|
terminal::TerminalServer,
|
||||||
};
|
};
|
||||||
|
use pulse_wire::prelude::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::{sync::Mutex, task::JoinHandle};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
|
|||||||
+2
-6
@@ -1,10 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use pulse_wire::prelude::*;
|
||||||
|
|
||||||
use pulse_wire::{
|
|
||||||
terminal::MarketItem,
|
|
||||||
units::{Symbol, USD},
|
|
||||||
};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn number(value: &Value, field: &str) -> Result<f64, String> {
|
fn number(value: &Value, field: &str) -> Result<f64, String> {
|
||||||
let raw = value[field]
|
let raw = value[field]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
use crate::engine::Engine;
|
||||||
|
use pulse_wire::prelude::*;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use pulse_wire::{PulseWire, general::{EventLog, LogKind}, terminal::TerminalClientMessage};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::{
|
net::{
|
||||||
@@ -13,8 +13,6 @@ use tokio::{
|
|||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::engine::Engine;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TerminalServer {
|
pub struct TerminalServer {
|
||||||
clients: Mutex<HashMap<usize, OwnedWriteHalf>>,
|
clients: Mutex<HashMap<usize, OwnedWriteHalf>>,
|
||||||
|
|||||||
+89
-19
@@ -1,9 +1,4 @@
|
|||||||
use pulse_wire::terminal::{
|
use pulse_wire::prelude::*;
|
||||||
Alert, AlertLevel, InspectItem, InspectTarget, MarketItem, MarketOverview, Status,
|
|
||||||
};
|
|
||||||
|
|
||||||
use pulse_wire::general::{Direction, EventLog, LogKind, Position, Signal};
|
|
||||||
use pulse_wire::units::format_f64;
|
|
||||||
|
|
||||||
pub trait Formatted {
|
pub trait Formatted {
|
||||||
fn get_formatted(&self) -> Vec<String>;
|
fn get_formatted(&self) -> Vec<String>;
|
||||||
@@ -115,16 +110,18 @@ impl Formatted for Position {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Property(&'static str, String);
|
struct Pair<'a>(&'a str, &'a str);
|
||||||
|
|
||||||
|
struct Triple<'a>(&'a str, &'a str, &'a str);
|
||||||
|
|
||||||
impl Formatted for MarketOverview {
|
impl Formatted for MarketOverview {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
let mut o = vec![
|
let mut o = vec![
|
||||||
Property("TREND", format!("{}", self.trend)),
|
Pair("TREND", &format!("{}", self.trend)),
|
||||||
Property("VOLATILITY", format!("{}", self.volatility)),
|
Pair("VOLATILITY", &format!("{}", self.volatility)),
|
||||||
Property(
|
Pair(
|
||||||
"PRESSURE",
|
"PRESSURE",
|
||||||
if self.pressure.is_sign_positive() {
|
&if self.pressure.is_sign_positive() {
|
||||||
format!("\x1b[32m{:.3}\x1b[0m", self.pressure)
|
format!("\x1b[32m{:.3}\x1b[0m", self.pressure)
|
||||||
} else {
|
} else {
|
||||||
format!("\x1b[31m{:.3}\x1b[0m", self.pressure)
|
format!("\x1b[31m{:.3}\x1b[0m", self.pressure)
|
||||||
@@ -145,21 +142,50 @@ impl Formatted for MarketOverview {
|
|||||||
impl Formatted for Status {
|
impl Formatted for Status {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
vec![
|
vec![
|
||||||
Property("Feed", format!("{:?}", self.feed)),
|
Pair("Feed", &format!("{:?}", self.feed)),
|
||||||
Property("Exchange", self.exchange.clone()),
|
Pair("Exchange", &self.exchange.clone()),
|
||||||
Property("DEX", self.dex.clone()),
|
Pair("DEX", &self.dex.clone()),
|
||||||
Property("Latency", format!("{} ms", self.latency)),
|
Pair("Latency", &format!("{} ms", self.latency)),
|
||||||
]
|
]
|
||||||
.get_formatted()
|
.get_formatted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Formatted for Property {
|
impl<'a> Formatted for Pair<'a> {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
vec![format!("\x1b[91m{}\x1b[0m", self.0), self.1.clone()]
|
vec![self.0.to_string(), self.1.to_string()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Formatted for Triple<'a> {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![self.0.to_string(), self.1.to_string(), self.2.to_string()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible_len(s: &str) -> usize {
|
||||||
|
let mut len = 0;
|
||||||
|
let mut chars = s.chars().peekable();
|
||||||
|
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if c == '\x1b' {
|
||||||
|
if chars.peek() == Some(&'[') {
|
||||||
|
chars.next();
|
||||||
|
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if c.is_ascii_alphabetic() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
len += c.len_utf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Formatted> Formatted for Vec<T> {
|
impl<T: Formatted> Formatted for Vec<T> {
|
||||||
fn get_formatted(&self) -> Vec<String> {
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
@@ -176,7 +202,7 @@ impl<T: Formatted> Formatted for Vec<T> {
|
|||||||
|
|
||||||
for row in &rows {
|
for row in &rows {
|
||||||
for (i, col) in row.iter().enumerate() {
|
for (i, col) in row.iter().enumerate() {
|
||||||
widths[i] = widths[i].max(col.len());
|
widths[i] = widths[i].max(visible_len(col));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +212,8 @@ impl<T: Formatted> Formatted for Vec<T> {
|
|||||||
for (col_idx, col) in row.iter().enumerate() {
|
for (col_idx, col) in row.iter().enumerate() {
|
||||||
let width = widths[col_idx];
|
let width = widths[col_idx];
|
||||||
|
|
||||||
row_out.push_str(&format!("{:<width$}", col, width = width));
|
row_out.push_str(col);
|
||||||
|
row_out.push_str(&" ".repeat(width.saturating_sub(visible_len(col))));
|
||||||
|
|
||||||
if col_idx + 1 != row.len() {
|
if col_idx + 1 != row.len() {
|
||||||
row_out.push_str(" ");
|
row_out.push_str(" ");
|
||||||
@@ -200,6 +227,49 @@ impl<T: Formatted> Formatted for Vec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Formatted for Strategy {
|
||||||
|
fn get_formatted(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
Triple(
|
||||||
|
"\x1b[2mName\x1b[0m",
|
||||||
|
"\x1b[2mRisk\x1b[0m",
|
||||||
|
"\x1b[2mStrat Ver\x1b[0m",
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
&format!("\x1b[97m{}\x1b[0m", self.strategy.name),
|
||||||
|
&format!("\x1b[93m{}\x1b[0m", self.risk.name),
|
||||||
|
&format!("\x1b[90m{}\x1b[0m", self.strategy.version),
|
||||||
|
),
|
||||||
|
Triple("", "", ""),
|
||||||
|
Triple(
|
||||||
|
"\x1b[2mMode\x1b[0m",
|
||||||
|
"\x1b[2mState\x1b[0m",
|
||||||
|
"\x1b[2mRisk Ver\x1b[0m",
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
&self.mode.to_string(),
|
||||||
|
&self.state.to_string(),
|
||||||
|
&format!("\x1b[90m{}\x1b[0m", self.risk.version),
|
||||||
|
),
|
||||||
|
Triple("", "", ""),
|
||||||
|
Triple(
|
||||||
|
"\x1b[2mCooldown\x1b[0m",
|
||||||
|
"\x1b[2mTimeframes\x1b[0m",
|
||||||
|
"\x1b[2mMax loss\x1b[0m",
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
&format!(
|
||||||
|
"\x1b[96m{:?}\x1b[0m (\x1b[90m{:?} rec\x1b[0m)",
|
||||||
|
self.cooldown, self.risk.cooldown
|
||||||
|
),
|
||||||
|
&format!("\x1b[96m{:?}\x1b[0m", self.strategy.timeframes),
|
||||||
|
&format!("\x1b[93m{}%\x1b[0m", self.risk.max_loss),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.get_formatted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_padding(mut items: Vec<String>) -> Vec<String> {
|
pub fn apply_padding(mut items: Vec<String>) -> Vec<String> {
|
||||||
for item in &mut items {
|
for item in &mut items {
|
||||||
item.insert(0, ' ');
|
item.insert(0, ' ');
|
||||||
|
|||||||
+32
-16
@@ -21,10 +21,7 @@ use pulse_ui::{
|
|||||||
|
|
||||||
use crate::formatting::{Formatted, apply_padding};
|
use crate::formatting::{Formatted, apply_padding};
|
||||||
|
|
||||||
use pulse_wire::{
|
use pulse_wire::prelude::*;
|
||||||
general::{EventLog, Position, Signal},
|
|
||||||
terminal::{InspectTarget, MarketItem, MarketOverview, Status},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct PulseTradeApp {
|
pub struct PulseTradeApp {
|
||||||
sock: Option<terminal::TerminalClient>,
|
sock: Option<terminal::TerminalClient>,
|
||||||
@@ -32,7 +29,7 @@ pub struct PulseTradeApp {
|
|||||||
command: State<InputState>,
|
command: State<InputState>,
|
||||||
scroll: State<ScrollState<7>>,
|
scroll: State<ScrollState<7>>,
|
||||||
|
|
||||||
market_overview: State<Option<MarketOverview>>,
|
strategy: State<Option<Strategy>>,
|
||||||
status: State<Option<Status>>,
|
status: State<Option<Status>>,
|
||||||
|
|
||||||
watch_list: State<Vec<MarketItem>>,
|
watch_list: State<Vec<MarketItem>>,
|
||||||
@@ -136,15 +133,7 @@ impl App for PulseTradeApp {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
Box::new(
|
Box::new(advanced_draw(&self.scroll, 2, "SIGNALS", &self.signals).await),
|
||||||
advanced_option_draw(
|
|
||||||
&self.scroll,
|
|
||||||
2,
|
|
||||||
"MARKET OVERVIEW",
|
|
||||||
&self.market_overview,
|
|
||||||
)
|
|
||||||
.await,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@@ -154,7 +143,9 @@ impl App for PulseTradeApp {
|
|||||||
SpacedColumns(vec![
|
SpacedColumns(vec![
|
||||||
(
|
(
|
||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
Box::new(advanced_draw(&self.scroll, 3, "SIGNALS", &self.signals).await),
|
Box::new(
|
||||||
|
advanced_option_draw(&self.scroll, 3, "STRATEGY", &self.strategy).await,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
LayoutItem::Widget(Size::Flex(1)),
|
LayoutItem::Widget(Size::Flex(1)),
|
||||||
@@ -190,7 +181,32 @@ 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),
|
||||||
market_overview: ctx.use_state(None),
|
strategy: ctx.use_state(Some(Strategy {
|
||||||
|
strategy: StrategyManifest {
|
||||||
|
name: "Liquidity Sweep".to_string(),
|
||||||
|
description:
|
||||||
|
"Detects liquidity grabs around key support and resistance levels."
|
||||||
|
.to_string(),
|
||||||
|
author: "Klesty Selimaj".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
|
||||||
|
timeframes: vec![TimeFrame::M5, TimeFrame::M15, TimeFrame::H1],
|
||||||
|
},
|
||||||
|
|
||||||
|
risk: RiskManifest {
|
||||||
|
name: "Aggressive".to_string(),
|
||||||
|
description: "High-risk profile with larger position sizing.".to_string(),
|
||||||
|
author: "Klesty Selimaj".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
|
||||||
|
max_loss: 5,
|
||||||
|
cooldown: TimeFrame::M15,
|
||||||
|
},
|
||||||
|
|
||||||
|
mode: Mode::Auto,
|
||||||
|
state: ItemState::Running,
|
||||||
|
cooldown: TimeFrame::M15,
|
||||||
|
})),
|
||||||
status: ctx.use_state(None),
|
status: ctx.use_state(None),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use pulse_wire::{PulseWire, server_path, terminal::TerminalServerMessage};
|
use pulse_wire::prelude::*;
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::{
|
net::{
|
||||||
@@ -47,7 +48,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.market_overview.clone();
|
let market_overview = app.strategy.clone();
|
||||||
let status = app.status.clone();
|
let status = app.status.clone();
|
||||||
let inspect = app.inspect.clone();
|
let inspect = app.inspect.clone();
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ impl TerminalClient {
|
|||||||
*active_positions.lock().await = v;
|
*active_positions.lock().await = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminalServerMessage::OverviewUpdated(v) => {
|
TerminalServerMessage::StrategyUpdated(v) => {
|
||||||
*market_overview.lock().await = Some(v);
|
*market_overview.lock().await = Some(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user