This commit is contained in:
2026-07-30 04:44:21 +02:00
parent 8dcf6b2a41
commit 87e3c2b964
8 changed files with 95 additions and 236 deletions
+9 -11
View File
@@ -1,7 +1,6 @@
use hypersdk::hypercore::Side;
use rust_decimal::Decimal; use rust_decimal::Decimal;
use crate::units::{Direction, Symbol, USD};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum MarketTrend { pub enum MarketTrend {
Bullish, Bullish,
@@ -20,12 +19,11 @@ pub enum LogKind {
#[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,
pub kind: Direction, pub side: Side,
pub confidence: f32, pub confidence: f32,
pub size: Decimal, pub price: Decimal,
pub price: USD, pub take_profit: Decimal,
pub take_profit: USD, pub stop_loss: Decimal,
pub stop_loss: USD,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -37,10 +35,10 @@ pub struct EventLog {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Position { pub struct Position {
pub symbol: Symbol, pub symbol: String,
pub size: f64, pub size: Decimal,
pub entry_price: USD, pub entry_price: Decimal,
pub profit: USD, pub pnl: Decimal,
} }
impl std::fmt::Display for MarketTrend { impl std::fmt::Display for MarketTrend {
+1 -3
View File
@@ -1,7 +1,6 @@
pub mod general; pub mod general;
pub mod strategy; pub mod strategy;
pub mod terminal; pub mod terminal;
pub mod units;
pub use hypersdk; pub use hypersdk;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -10,10 +9,9 @@ use crate::strategy::StrategyEngineMessage;
pub mod prelude { pub mod prelude {
pub use crate::general::*; pub use crate::general::*;
pub use crate::strategy::*;
pub use crate::server_path; pub use crate::server_path;
pub use crate::strategy::*;
pub use crate::terminal::*; pub use crate::terminal::*;
pub use crate::units::*;
pub use hypersdk; pub use hypersdk;
pub use postcard; pub use postcard;
+4 -42
View File
@@ -1,7 +1,6 @@
use crate::{ use crate::{
general::{EventLog, MarketTrend, Position, Signal}, general::{EventLog, Position, Signal},
strategy::StrategyManifest, strategy::StrategyManifest,
units::{Symbol, USD, Volatility},
}; };
use hypersdk::{Decimal, hypercore::CandleInterval}; use hypersdk::{Decimal, hypercore::CandleInterval};
@@ -39,37 +38,10 @@ pub enum TerminalClientMessage {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MarketItem { pub struct MarketItem {
pub symbol: Symbol, pub symbol: String,
pub price: USD, pub price: Decimal,
pub trend: Decimal, pub trend: Decimal,
pub volume_24h: USD, pub volume_24h: Decimal,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MarketOverview {
pub trend: MarketTrend,
pub volatility: Volatility,
pub pressure: f64,
pub alerts: Vec<Alert>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum AlertLevel {
High,
Medium,
Low,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Alert {
pub level: AlertLevel,
pub message: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Balance {
pub asset: String,
pub amount: f64,
pub value: f64,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -116,16 +88,6 @@ pub struct Strategy {
pub cooldown: CandleInterval, pub cooldown: CandleInterval,
} }
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 Mode { impl std::fmt::Display for Mode {
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 {
-110
View File
@@ -1,110 +0,0 @@
use rust_decimal::Decimal;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Symbol(pub String);
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct USD(pub Decimal);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Direction {
Buy,
Sell,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Volatility {
Low,
Medium,
High,
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\x1b[35m{}\x1b[0m", self.0)
}
}
impl std::fmt::Display for USD {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.is_sign_positive() {
write!(f, "\x1b[32m${}\x1b[0m", format_f64(self.0.as_f64()))
} else {
write!(f, "\x1b[31m${}\x1b[0m", format_f64(self.0.as_f64()))
}
}
}
pub fn format_f64(value: f64) -> String {
let abs = value.abs();
let (divisor, suffix) = if abs >= 1_000_000_000.0 {
(1_000_000_000.0, "B")
} else if abs >= 1_000_000.0 {
(1_000_000.0, "M")
} else if abs >= 1_000.0 {
(1_000.0, "K")
} else {
(1.0, "")
};
if divisor != 1.0 {
let formatted = value / divisor;
// Remove unnecessary trailing zeros
let s = format!("{:.2}", formatted)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string();
return format!("{}{}", s, suffix);
}
let val = format!("{:.3}", value);
let parts: Vec<&str> = val.split('.').collect();
let int = parts[0].to_string();
let negative = int.starts_with('-');
let start = if negative { 1 } else { 0 };
let mut result = String::new();
for (i, c) in int[start..].chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
let mut formatted: String = result.chars().rev().collect();
if negative {
formatted.insert(0, '-');
}
if parts.len() > 1 {
formatted.push('.');
formatted.push_str(parts[1]);
}
formatted
}
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"),
}
}
}
+12 -12
View File
@@ -1,4 +1,4 @@
use hypersdk::hypercore::{self, BatchOrder, OrderRequest, OrderTypePlacement, TimeInForce}; use hypersdk::hypercore::{self, BatchOrder, OrderRequest, OrderTypePlacement, Side, TimeInForce};
use pulse_sdk::prelude::*; use pulse_sdk::prelude::*;
use crate::engine::Engine; use crate::engine::Engine;
@@ -34,9 +34,9 @@ impl Engine {
orders: vec![ orders: vec![
OrderRequest { OrderRequest {
asset: asset_id, asset: asset_id,
is_buy: matches!(signal.kind, Direction::Buy), is_buy: matches!(signal.side, Side::Bid),
limit_px: signal.price.0, limit_px: signal.price,
sz: signal.size, sz: 0.into(),
reduce_only: false, reduce_only: false,
order_type: OrderTypePlacement::Limit { order_type: OrderTypePlacement::Limit {
tif: TimeInForce::Gtc, tif: TimeInForce::Gtc,
@@ -45,26 +45,26 @@ impl Engine {
}, },
OrderRequest { OrderRequest {
asset: asset_id, asset: asset_id,
is_buy: matches!(signal.kind, Direction::Buy), is_buy: matches!(signal.side, Side::Bid),
limit_px: signal.price.0, limit_px: signal.price,
sz: signal.size, sz: 0.into(),
reduce_only: true, reduce_only: true,
order_type: OrderTypePlacement::Trigger { order_type: OrderTypePlacement::Trigger {
is_market: true, is_market: true,
trigger_px: signal.take_profit.0, trigger_px: signal.take_profit,
tpsl: hypercore::TpSl::Tp, tpsl: hypercore::TpSl::Tp,
}, },
cloid: Default::default(), cloid: Default::default(),
}, },
OrderRequest { OrderRequest {
asset: asset_id, asset: asset_id,
is_buy: matches!(signal.kind, Direction::Buy), is_buy: matches!(signal.side, Side::Bid),
limit_px: signal.price.0, limit_px: signal.price,
sz: signal.size, sz: 0.into(),
reduce_only: true, reduce_only: true,
order_type: OrderTypePlacement::Trigger { order_type: OrderTypePlacement::Trigger {
is_market: true, is_market: true,
trigger_px: signal.stop_loss.0, trigger_px: signal.stop_loss,
tpsl: hypercore::TpSl::Sl, tpsl: hypercore::TpSl::Sl,
}, },
cloid: Default::default(), cloid: Default::default(),
+4 -7
View File
@@ -104,13 +104,10 @@ impl Engine {
.asset_positions .asset_positions
.into_iter() .into_iter()
.map(|position| Position { .map(|position| Position {
symbol: Symbol(position.position.coin), symbol: position.position.coin,
size: position.position.szi.as_f64(), size: position.position.szi,
entry_price: USD(position entry_price: position.position.entry_px.unwrap_or_default(),
.position pnl: position.position.unrealized_pnl,
.entry_px
.unwrap_or_default()),
profit: USD(position.position.unrealized_pnl),
}) })
.collect(), .collect(),
)) ))
+4 -4
View File
@@ -1,5 +1,5 @@
use rust_decimal::Decimal;
use pulse_sdk::prelude::*; use pulse_sdk::prelude::*;
use rust_decimal::Decimal;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
@@ -80,9 +80,9 @@ pub async fn fetch_watch_list(
} }
items.push(MarketItem { items.push(MarketItem {
symbol: Symbol(symbol.clone()), symbol: symbol.clone(),
price: USD(price), price: price,
volume_24h: USD(volume_24h), volume_24h: volume_24h,
trend: ((price / previous_day_price) - <Decimal as From<i32>>::from(1)) trend: ((price / previous_day_price) - <Decimal as From<i32>>::from(1))
* <Decimal as From<i32>>::from(100), * <Decimal as From<i32>>::from(100),
}); });
+61 -47
View File
@@ -1,3 +1,4 @@
use hypersdk::hypercore::Side;
use pulse_sdk::prelude::*; use pulse_sdk::prelude::*;
pub trait Formatted { pub trait Formatted {
@@ -22,23 +23,6 @@ impl Formatted for InspectTarget {
} }
} }
impl Formatted for Alert {
fn get_formatted(&self) -> Vec<String> {
vec![
format!(
"{}{}\x1b[0m",
match &self.level {
AlertLevel::Low => "\x1b[34m",
AlertLevel::Medium => "\x1b[33m",
AlertLevel::High => "\x1b[31m",
},
self.level
),
self.message.clone(),
]
}
}
impl Formatted for EventLog { impl Formatted for EventLog {
fn get_formatted(&self) -> Vec<String> { fn get_formatted(&self) -> Vec<String> {
vec![ vec![
@@ -64,7 +48,7 @@ impl Formatted for SignalStatus {
Ok(signal) => { Ok(signal) => {
vec![ vec![
format!("\x1b[33mOK\x1b[0m"), format!("\x1b[33mOK\x1b[0m"),
if matches!(signal.kind, Direction::Buy) { if matches!(signal.side, Side::Ask) {
format!("\x1b[32mBUY\x1b[0m") format!("\x1b[32mBUY\x1b[0m")
} else { } else {
format!("\x1b[31mSELL\x1b[0m") format!("\x1b[31mSELL\x1b[0m")
@@ -78,7 +62,7 @@ impl Formatted for SignalStatus {
Err(signal) => { Err(signal) => {
vec![ vec![
format!("\x1b[31mERR\x1b[0m"), format!("\x1b[31mERR\x1b[0m"),
if matches!(signal.kind, Direction::Buy) { if matches!(signal.side, Side::Ask) {
format!("\x1b[32mBUY\x1b[0m") format!("\x1b[32mBUY\x1b[0m")
} else { } else {
format!("\x1b[31mSELL\x1b[0m") format!("\x1b[31mSELL\x1b[0m")
@@ -115,16 +99,16 @@ impl Formatted for Position {
fn get_formatted(&self) -> Vec<String> { fn get_formatted(&self) -> Vec<String> {
vec![ vec![
self.symbol.to_string(), self.symbol.to_string(),
format_f64(self.size), format_f64(self.size.as_f64()),
self.entry_price.to_string(), self.entry_price.to_string(),
format!( format!(
"{}{}\x1b[0m", "{}{}\x1b[0m",
if self.profit.0.is_sign_positive() { if self.pnl.is_sign_positive() {
"\x1b[32m" "\x1b[32m"
} else { } else {
"\x1b[31m" "\x1b[31m"
}, },
self.profit, self.pnl,
), ),
] ]
} }
@@ -134,31 +118,6 @@ 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 MarketOverview {
fn get_formatted(&self) -> Vec<String> {
let mut o = vec![
Pair("TREND", &format!("{}", self.trend)),
Pair("VOLATILITY", &format!("{}", self.volatility)),
Pair(
"PRESSURE",
&if self.pressure.is_sign_positive() {
format!("\x1b[32m{:.3}\x1b[0m", self.pressure)
} else {
format!("\x1b[31m{:.3}\x1b[0m", self.pressure)
},
),
]
.get_formatted();
o.push(format!(
"\n{}",
apply_padding(self.alerts.get_formatted()).join("\n")
));
o
}
}
impl Formatted for Status { impl Formatted for Status {
fn get_formatted(&self) -> Vec<String> { fn get_formatted(&self) -> Vec<String> {
vec![ vec![
@@ -272,3 +231,58 @@ pub fn apply_padding(mut items: Vec<String>) -> Vec<String> {
items items
} }
pub fn format_f64(value: f64) -> String {
let abs = value.abs();
let (divisor, suffix) = if abs >= 1_000_000_000.0 {
(1_000_000_000.0, "B")
} else if abs >= 1_000_000.0 {
(1_000_000.0, "M")
} else if abs >= 1_000.0 {
(1_000.0, "K")
} else {
(1.0, "")
};
if divisor != 1.0 {
let formatted = value / divisor;
// Remove unnecessary trailing zeros
let s = format!("{:.2}", formatted)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string();
return format!("{}{}", s, suffix);
}
let val = format!("{:.3}", value);
let parts: Vec<&str> = val.split('.').collect();
let int = parts[0].to_string();
let negative = int.starts_with('-');
let start = if negative { 1 } else { 0 };
let mut result = String::new();
for (i, c) in int[start..].chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
let mut formatted: String = result.chars().rev().collect();
if negative {
formatted.insert(0, '-');
}
if parts.len() > 1 {
formatted.push('.');
formatted.push_str(parts[1]);
}
formatted
}