From 5f1b5c9aada1d3d44ce308eb9e151940790c4fcc Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 29 Jul 2026 04:00:58 +0200 Subject: [PATCH] better Signals --- pulse-sdk/src/terminal.rs | 8 ++- src/engine/engine/execution.rs | 98 +++++++++++++++++++++++++++ src/engine/engine/mod.rs | 3 + src/engine/engine/plugin.rs | 120 +++++++-------------------------- src/terminal/formatting.rs | 74 +++++++++++++++++--- src/terminal/main.rs | 2 +- src/terminal/terminal.rs | 2 +- 7 files changed, 197 insertions(+), 110 deletions(-) create mode 100644 src/engine/engine/execution.rs diff --git a/pulse-sdk/src/terminal.rs b/pulse-sdk/src/terminal.rs index 625af89..1998607 100644 --- a/pulse-sdk/src/terminal.rs +++ b/pulse-sdk/src/terminal.rs @@ -1,10 +1,12 @@ use crate::{ - general::{EventLog, MarketTrend, Position, Signal}, - plugin::{RiskManifest, StrategyManifest}, + general::{EventLog, MarketTrend, Position}, + plugin::{RiskManifest, RiskSignal, StrategyManifest}, units::{Symbol, USD, Volatility}, }; use hypersdk::{Decimal, hypercore::CandleInterval}; +pub type SignalStatus = Result; + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum TerminalServerMessage { // WatchList @@ -17,7 +19,7 @@ pub enum TerminalServerMessage { StrategyUpdated(Strategy), // Signals - SignalsUpdated(Vec), + SignalsUpdated(Vec), // Inspector Inspect(InspectTarget), diff --git a/src/engine/engine/execution.rs b/src/engine/engine/execution.rs new file mode 100644 index 0000000..9d56b4e --- /dev/null +++ b/src/engine/engine/execution.rs @@ -0,0 +1,98 @@ +use hypersdk::hypercore::{self, BatchOrder, OrderRequest, OrderTypePlacement, TimeInForce}; +use pulse_sdk::prelude::*; + +use crate::engine::Engine; + +impl Engine { + pub async fn execute_signal(&self, signal: &Signal) -> tokio::io::Result<()> { + let client = hypercore::mainnet(); + let accounts = self.accounts.lock().await; + + if let Some(acc) = accounts.get_active() { + let Some(asset_id) = self + .watch_list + .lock() + .await + .name_to_index + .get(&signal.symbol) + .cloned() + else { + self.terminal_server + .error( + "self::order", + &format!( + "Invalid Symbol: {:?}, Unable to get asset id", + signal.symbol + ), + ) + .await?; + + return Ok(()); + }; + + let order = BatchOrder { + orders: vec![ + OrderRequest { + asset: asset_id, + is_buy: matches!(signal.kind, Direction::Buy), + limit_px: signal.price.0, + sz: signal.size, + reduce_only: false, + order_type: OrderTypePlacement::Limit { + tif: TimeInForce::Gtc, + }, + cloid: Default::default(), + }, + OrderRequest { + asset: asset_id, + is_buy: matches!(signal.kind, Direction::Buy), + limit_px: signal.price.0, + sz: signal.size, + reduce_only: true, + order_type: OrderTypePlacement::Trigger { + is_market: true, + trigger_px: signal.take_profit.0, + tpsl: hypercore::TpSl::Tp, + }, + cloid: Default::default(), + }, + OrderRequest { + asset: asset_id, + is_buy: matches!(signal.kind, Direction::Buy), + limit_px: signal.price.0, + sz: signal.size, + reduce_only: true, + order_type: OrderTypePlacement::Trigger { + is_market: true, + trigger_px: signal.stop_loss.0, + tpsl: hypercore::TpSl::Sl, + }, + cloid: Default::default(), + }, + ], + grouping: hypercore::OrderGrouping::Na, + builder: None, + }; + + let nonce = chrono::Utc::now().timestamp_millis() as u64; + + match client + .place(&acc.private_key.0, order, nonce, None, None) + .await + { + Ok(_) => {} + Err(e) => { + self.terminal_server + .error("self::order", &e.to_string()) + .await?; + } + } + } else { + self.terminal_server + .error("Engine::order", "Unable to get active account") + .await?; + } + + Ok(()) + } +} diff --git a/src/engine/engine/mod.rs b/src/engine/engine/mod.rs index 48382f4..f728c80 100644 --- a/src/engine/engine/mod.rs +++ b/src/engine/engine/mod.rs @@ -1,5 +1,6 @@ pub mod command; pub mod plugin; +pub mod execution; use crate::{ engine::plugin::StrategyEngine, @@ -23,6 +24,7 @@ pub struct Engine { pub config: Arc>, pub accounts: Arc>, pub watch_list: Arc>, + pub signals: Arc>>, } impl Engine { @@ -43,6 +45,7 @@ impl Engine { name_to_index: HashMap::new(), items: Vec::new(), })), + signals: Arc::new(Mutex::new(Vec::new())), })) } diff --git a/src/engine/engine/plugin.rs b/src/engine/engine/plugin.rs index dd8574f..1e5dd28 100644 --- a/src/engine/engine/plugin.rs +++ b/src/engine/engine/plugin.rs @@ -1,7 +1,4 @@ -use hypersdk::hypercore::{ - self, BatchOrder, CandleInterval, OrderRequest, OrderTypePlacement, Subscription, TimeInForce, - WebSocket, -}; +use hypersdk::hypercore::{self, CandleInterval, Subscription, WebSocket}; use pulse_sdk::prelude::*; use std::{ collections::HashSet, @@ -181,104 +178,37 @@ impl StrategyEngine { engine.terminal_server.log_raw(log).await?; } - Some(RiskMessage::Signal(RiskSignal::Approve(signal))) => { - let client = hypercore::mainnet(); - let accounts = engine.accounts.lock().await; + Some(RiskMessage::Signal(ref sig @ RiskSignal::Approve(ref signal))) => { + match engine.execute_signal(signal).await { + Ok(_) => engine.signals.lock().await.push(Ok(sig.clone())), + Err(e) => { + engine.signals.lock().await.push(Err(sig.clone())); - if let Some(acc) = accounts.get_active() { - let Some(asset_id) = engine - .watch_list - .lock() - .await - .name_to_index - .get(&signal.symbol) - .cloned() - else { engine .terminal_server - .error( - "Engine::order", - &format!( - "Invalid Symbol: {:?}, Unable to get asset id", - signal.symbol - ), - ) - .await?; - - continue; - }; - - let order = BatchOrder { - orders: vec![ - OrderRequest { - asset: asset_id, - is_buy: matches!(signal.kind, Direction::Buy), - limit_px: signal.price.0, - sz: signal.size, - reduce_only: false, - order_type: OrderTypePlacement::Limit { - tif: TimeInForce::Gtc, - }, - cloid: Default::default(), - }, - OrderRequest { - asset: asset_id, - is_buy: matches!(signal.kind, Direction::Buy), - limit_px: signal.price.0, - sz: signal.size, - reduce_only: true, - order_type: OrderTypePlacement::Trigger { - is_market: true, - trigger_px: signal.take_profit.0, - tpsl: hypercore::TpSl::Tp, - }, - cloid: Default::default(), - }, - OrderRequest { - asset: asset_id, - is_buy: matches!(signal.kind, Direction::Buy), - limit_px: signal.price.0, - sz: signal.size, - reduce_only: true, - order_type: OrderTypePlacement::Trigger { - is_market: true, - trigger_px: signal.stop_loss.0, - tpsl: hypercore::TpSl::Sl, - }, - cloid: Default::default(), - }, - ], - grouping: hypercore::OrderGrouping::Na, - builder: None, - }; - - let nonce = chrono::Utc::now().timestamp_millis() as u64; - - match client - .place(&acc.private_key.0, order, nonce, None, None) - .await - { - Ok(_) => {} - Err(e) => { - engine - .terminal_server - .error("Engine::order", &e.to_string()) - .await?; - } + .error("signal", &format!("Failed to execute signal: {e}")) + .await? } - } else { - engine - .terminal_server - .error("Engine::order", "Unable to get active account") - .await?; } + + engine + .terminal_server + .broadcast(TerminalServerMessage::SignalsUpdated( + engine.signals.lock().await.clone(), + )) + .await?; } - Some(RiskMessage::Signal(RiskSignal::Reject { - signal, - rejection_confidence, - reason, - })) => {} + Some(RiskMessage::Signal(reject)) => { + engine.signals.lock().await.push(Ok(reject)); + + engine + .terminal_server + .broadcast(TerminalServerMessage::SignalsUpdated( + engine.signals.lock().await.clone(), + )) + .await?; + } } } } diff --git a/src/terminal/formatting.rs b/src/terminal/formatting.rs index 58d4439..2a2744b 100644 --- a/src/terminal/formatting.rs +++ b/src/terminal/formatting.rs @@ -58,17 +58,71 @@ impl Formatted for EventLog { } } -impl Formatted for Signal { +impl Formatted for SignalStatus { fn get_formatted(&self) -> Vec { - vec![ - if matches!(self.kind, Direction::Buy) { - format!("\x1b[32m{}\x1b[0m", self.kind) - } else { - format!("\x1b[31m{}\x1b[0m", self.kind) - }, - format!("\x1b[35m{}\x1b[0m", self.symbol), - self.price.to_string(), - ] + match self { + Ok(RiskSignal::Approve(signal)) => { + vec![ + format!("\x1b[33mOK\x1b[0m"), + if matches!(signal.kind, Direction::Buy) { + format!("\x1b[32mBUY\x1b[0m") + } else { + format!("\x1b[31mSELL\x1b[0m") + }, + format!("\x1b[35m{}\x1b[0m", signal.symbol), + signal.price.to_string(), + format!("\x1b[33mAPR {}\x1b[0m", signal.confidence), + ] + } + Ok(RiskSignal::Reject { + signal, + rejection_confidence, + reason, + }) => { + vec![ + format!("\x1b[33mOK\x1b[0m"), + if matches!(signal.side, Direction::Buy) { + format!("\x1b[32mBUY\x1b[0m") + } else { + format!("\x1b[31mSELL\x1b[0m") + }, + format!("\x1b[35m{}\x1b[0m", signal.symbol), + format!("\x1b[31mREJ {}\x1b[0m", rejection_confidence), + reason.to_owned(), + ] + } + + Err(RiskSignal::Approve(signal)) => { + vec![ + format!("\x1b[31mERR\x1b[0m"), + if matches!(signal.kind, Direction::Buy) { + format!("\x1b[32mBUY\x1b[0m") + } else { + format!("\x1b[31mSELL\x1b[0m") + }, + format!("\x1b[35m{}\x1b[0m", signal.symbol), + signal.price.to_string(), + format!("\x1b[33mAPR {}\x1b[0m", signal.confidence), + ] + } + Err(RiskSignal::Reject { + signal, + rejection_confidence, + reason, + }) => { + vec![ + format!("\x1b[31mERR\x1b[0m"), + if matches!(signal.side, Direction::Buy) { + format!("\x1b[32mBUY\x1b[0m") + } else { + format!("\x1b[31mSELL\x1b[0m") + }, + format!("\x1b[35m{}\x1b[0m", signal.symbol), + format!("\x1b[31mREJ {}\x1b[0m", rejection_confidence), + reason.to_owned(), + ] + } + } } } diff --git a/src/terminal/main.rs b/src/terminal/main.rs index 25ae2f5..e751356 100644 --- a/src/terminal/main.rs +++ b/src/terminal/main.rs @@ -35,7 +35,7 @@ pub struct PulseTradeApp { watch_list: State>, active_positions: State>, logs: State>, - signals: State>, + signals: State>, inspect: State, } diff --git a/src/terminal/terminal.rs b/src/terminal/terminal.rs index 3657ce2..b032f9d 100644 --- a/src/terminal/terminal.rs +++ b/src/terminal/terminal.rs @@ -75,7 +75,7 @@ impl TerminalClient { watch_list: State>, active_positions: State>, logs: State>, - signals: State>, + signals: State>, market_overview: State>, status: State>, inspect: State,