Com protocol

This commit is contained in:
2026-07-22 14:17:07 +02:00
parent 5affe1adf0
commit 23a1949676
11 changed files with 329 additions and 141 deletions
Generated
+10
View File
@@ -304,12 +304,22 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulse-macros"
version = "0.1.0-alpha.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pulse-trader"
version = "0.1.0-alpha.0"
dependencies = [
"chrono",
"crossterm",
"pulse-macros",
"pulse-ui",
"tokio",
]
+3 -1
View File
@@ -6,13 +6,15 @@ edition = "2024"
[dependencies]
chrono = "0.4.45"
pulse-ui = { workspace = true }
pulse-macros = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
crossterm = { workspace = true }
[workspace]
members = ["pulse-ui"]
members = ["pulse-macros", "pulse-ui"]
[workspace.dependencies]
pulse-macros = { path = "pulse-macros", version = "0.1.0-alpha.0" }
pulse-ui = { path = "pulse-ui", version = "0.1.0-alpha.0" }
tokio = "1.52.3"
crossterm = "0.29.0"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "pulse-macros"
version = "0.1.0-alpha.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"
+61
View File
@@ -0,0 +1,61 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{Fields, ItemStruct, parse_macro_input};
#[proc_macro_attribute]
pub fn p_com(_: TokenStream, item: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(item as ItemStruct);
let name = &input.ident;
match &mut input.fields {
Fields::Named(fields) => {
for field in fields.named.iter_mut() {
field.vis = syn::Visibility::Public(syn::token::Pub::default());
}
}
_ => {
return syn::Error::new_spanned(input, "p_com only supports structs with named fields")
.to_compile_error()
.into();
}
}
let fields = match &input.fields {
Fields::Named(fields) => &fields.named,
_ => {
return syn::Error::new_spanned(input, "p_com only supports structs with named fields")
.to_compile_error()
.into();
}
};
let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap());
let field_names2 = fields.iter().map(|f| f.ident.as_ref().unwrap());
let field_types = fields.iter().map(|f| &f.ty);
TokenStream::from(quote! {
#[derive(Debug, Clone)]
#input
impl PulseCom for #name {
fn to_com(&self) -> Vec<u8> {
let mut vec = Vec::new();
#(
vec.extend(self.#field_names.to_com());
)*
vec
}
fn from_com(com: &mut Vec<u8>) -> Self {
Self {
#(
#field_names2: <#field_types>::from_com(com),
)*
}
}
}
})
}
+1
View File
@@ -1 +1,2 @@
include!("../pc.rs");
include!("../ptc.rs");
+105
View File
@@ -0,0 +1,105 @@
pub trait PulseCom {
fn to_com(&self) -> Vec<u8>;
fn from_com(_com: &mut Vec<u8>) -> Self;
}
impl<T: PulseCom> PulseCom for Vec<T> {
fn to_com(&self) -> Vec<u8> {
let mut vec = Vec::new();
vec.extend_from_slice(&(self.len() as u32).to_le_bytes());
for item in self {
vec.extend(item.to_com());
}
vec
}
fn from_com(com: &mut Vec<u8>) -> Self {
let len_bytes: [u8; 4] = com.drain(..4).collect::<Vec<_>>().try_into().unwrap();
let len = u32::from_le_bytes(len_bytes) as usize;
let mut result = Vec::with_capacity(len);
for _ in 0..len {
result.push(T::from_com(com));
}
result
}
}
impl PulseCom for String {
fn to_com(&self) -> Vec<u8> {
let bytes = self.as_bytes();
let mut out = Vec::with_capacity(4 + bytes.len());
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(bytes);
out
}
fn from_com(com: &mut Vec<u8>) -> Self {
let len = u32::from_le_bytes(com[..4].try_into().unwrap()) as usize;
com.drain(..4);
let bytes: Vec<u8> = com.drain(..len).collect();
String::from_utf8(bytes).unwrap()
}
}
macro_rules! int_com {
($t:ty) => {
impl PulseCom for $t {
fn to_com(&self) -> Vec<u8> {
self.to_le_bytes().to_vec()
}
fn from_com(com: &mut Vec<u8>) -> Self {
const N: usize = std::mem::size_of::<$t>();
let bytes: [u8; N] = com.drain(..N).collect::<Vec<_>>().try_into().unwrap();
<$t>::from_le_bytes(bytes)
}
}
};
}
int_com!(i8);
int_com!(i16);
int_com!(i32);
int_com!(i64);
int_com!(isize);
int_com!(u8);
int_com!(u16);
int_com!(u32);
int_com!(u64);
int_com!(usize);
int_com!(f64);
int_com!(f32);
#[macro_export]
macro_rules! p_com {
(struct $name:ident { $($n:ident: $v:ty),* $(,)? }) => {
#[derive(Debug, Clone)]
pub struct $name { $(pub $n: $v),* }
impl PulseCom for $name {
fn to_com(&self) -> Vec<u8> {
let mut vec = Vec::new();
$(vec.extend(self.$n.to_com());)*
vec
}
fn from_com(com: &mut Vec<u8>) -> Self {
Self {
$($n: <$v>::from_com(com),)*
}
}
}
};
}
+124 -121
View File
@@ -1,24 +1,117 @@
#[derive(Debug, Clone)]
pub struct WatchListItem {
pub symbol: String,
pub price: f64,
pub trend: f64,
use pulse_macros::p_com;
#[p_com]
struct WatchListItem {
symbol: String,
price: f64,
trend: f64,
}
#[derive(Debug, Clone)]
pub struct ActivePosition {
pub symbol: String,
pub profit: f64,
pub amount: f64,
#[p_com]
struct ActivePosition {
symbol: String,
profit: f64,
amount: f64,
}
#[derive(Debug, Clone, Copy)]
pub enum MarketTrend {
#[p_com]
enum MarketTrend {
Bullish,
Bearish,
Neutral,
}
#[p_com]
enum Volatility {
Low,
Medium,
High,
}
#[p_com]
struct MarketOverview {
trend: MarketTrend,
volatility: Volatility,
pressure: f64,
alerts: Vec<Alert>,
}
#[p_com]
enum Feed {
Connected,
Disconnected,
Connecting,
Failed,
}
#[p_com]
struct Status {
feed: Feed,
exchange: String,
dex: String,
latency: u16,
}
#[p_com]
enum SignalKind {
Buy,
Sell,
}
#[p_com]
enum SignalParameter {
Lim,
Stl,
Tap,
Chk,
}
#[p_com]
struct Signal {
kind: SignalKind,
symbol: String,
param: SignalParameter,
price: f64,
}
#[p_com]
enum LogKind {
Info,
Warn,
Err,
Debug,
}
#[p_com]
struct EventLog {
kind: LogKind,
name: String,
message: String,
}
#[p_com]
enum AlertLevel {
High,
Medium,
Low,
}
#[p_com]
struct Alert {
level: AlertLevel,
message: String,
}
#[p_com]
enum InspectTarget {
None,
Symbol(WatchListItem),
Position(ActivePosition),
Signal(Signal),
Alert(Alert),
}
impl std::fmt::Display for MarketTrend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -29,11 +122,14 @@ impl std::fmt::Display for MarketTrend {
}
}
#[derive(Debug, Clone, Copy)]
pub enum Volatility {
Low,
Medium,
High,
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"),
}
}
}
impl std::fmt::Display for Volatility {
@@ -46,80 +142,6 @@ impl std::fmt::Display for Volatility {
}
}
#[derive(Debug, Clone)]
pub struct MarketOverview {
pub trend: MarketTrend,
pub volatility: Volatility,
pub pressure: f64,
pub alerts: Vec<Alert>,
}
#[derive(Debug, Clone, Copy)]
pub enum Feed {
Connected,
Disconnected,
Connecting,
Failed,
}
pub struct Status {
pub feed: Feed,
pub exchange: String,
pub dex: String,
pub latency: u16,
}
#[derive(Debug, Clone, Copy)]
pub enum SignalKind {
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,
}
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,
pub param: SignalParameter,
pub price: f64,
}
#[derive(Debug, Clone, Copy)]
pub enum LogKind {
Info,
Warn,
Err,
Debug,
}
impl std::fmt::Display for LogKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -131,41 +153,22 @@ impl std::fmt::Display for LogKind {
}
}
#[derive(Debug, Clone)]
pub struct EventLog {
pub kind: LogKind,
pub name: &'static str,
pub message: String,
}
#[derive(Debug, Clone, Copy)]
pub enum AlertLevel {
High,
Medium,
Low,
}
impl std::fmt::Display for AlertLevel {
impl std::fmt::Display for SignalKind {
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"),
Self::Buy => write!(f, "BUY"),
Self::Sell => write!(f, "SELL"),
}
}
}
#[derive(Debug, Clone)]
pub struct Alert {
pub level: AlertLevel,
pub message: String,
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 enum InspectTarget {
None,
Symbol(Box<(WatchListItem, MarketOverview)>),
Position(ActivePosition),
Signal(Signal),
Alert(Alert),
}
+1 -1
View File
@@ -20,7 +20,7 @@ impl PulseTradeApp {
_ => {
self.logs.lock().await.push(EventLog {
kind: crate::ptc::LogKind::Err,
name: "cmd",
name: "cmd".to_string(),
message: format!("Command '{}' not found", command),
});
}
+5 -12
View File
@@ -12,22 +12,15 @@ impl Formatted for InspectTarget {
match self {
Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()],
Self::Symbol(boxed) => {
let (watch, overview) = &**boxed;
vec![
Self::Symbol(watch) => 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),
"Price",
format!("\x1b[96m{}\x1b[0m", format_f64(watch.price)),
),
Property("Alerts", format!("{}", overview.alerts.len())),
Property("Trend", format!("{}", format_f64(watch.trend))),
]
.get_formatted()
}
.get_formatted(),
Self::Position(pos) => vec![
Property("Symbol", format!("\x1b[35m{}\x1b[0m", pos.symbol)),
+1 -1
View File
@@ -112,7 +112,7 @@ impl App for PulseTradeApp {
logs.push(EventLog {
kind: ptc::LogKind::Warn,
name: "pulse.init",
name: "pulse.init".to_string(),
message: "We're still not done yet ;)".to_string(),
});
+1
View File
@@ -1 +1,2 @@
include!("../pc.rs");
include!("../ptc.rs");