This commit is contained in:
2026-08-02 05:32:11 +02:00
parent f2d6d9a8b9
commit c18dceb291
6 changed files with 60 additions and 37 deletions
+14 -1
View File
@@ -1,4 +1,7 @@
use hypersdk::{dec, hypercore::Side}; use hypersdk::{
dec,
hypercore::{Cloid, Side},
};
use rust_decimal::Decimal; use rust_decimal::Decimal;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -44,7 +47,17 @@ pub struct Signal {
pub confidence: u8, pub confidence: u8,
pub entry_price: Decimal, pub entry_price: Decimal,
pub take_profit: Decimal, pub take_profit: Decimal,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct EngineOrder {
pub risk_equity: Decimal,
pub size: Decimal,
pub stop_loss: Decimal, pub stop_loss: Decimal,
pub entry: Cloid,
pub take_profit: Cloid,
pub stop_loss_id: Cloid,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+2 -2
View File
@@ -1,10 +1,10 @@
use crate::{ use crate::{
general::{EngineStatus, EventLog, Signal}, general::{EngineOrder, EngineStatus, EventLog, Signal},
strategy::StrategyManifest, strategy::StrategyManifest,
}; };
use hypersdk::Decimal; use hypersdk::Decimal;
pub type SignalStatus = Result<(Signal, Option<Decimal>), (Signal, Option<Decimal>)>; pub type SignalStatus = Result<(Signal, Option<EngineOrder>), (Signal, Option<EngineOrder>)>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum TerminalServerMessage { pub enum TerminalServerMessage {
+13 -15
View File
@@ -1,9 +1,6 @@
use hypersdk::{ use hypersdk::hypercore::{
Decimal, self, BatchOrder, NonceHandler, OrderRequest, OrderResponseStatus, OrderTypePlacement, Side,
hypercore::{ TimeInForce,
self, BatchOrder, NonceHandler, OrderRequest, OrderResponseStatus, OrderTypePlacement,
Side, TimeInForce,
},
}; };
use pulse_sdk::prelude::*; use pulse_sdk::prelude::*;
@@ -12,7 +9,7 @@ use crate::engine::Engine;
impl Engine { impl Engine {
pub async fn execute_signal( pub async fn execute_signal(
&self, &self,
size: &mut Option<Decimal>, size: &mut Option<EngineOrder>,
signal: &Signal, signal: &Signal,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let client = hypercore::mainnet(); let client = hypercore::mainnet();
@@ -38,7 +35,7 @@ impl Engine {
let engine_order = self.risk_engine.create_order(signal).await?; let engine_order = self.risk_engine.create_order(signal).await?;
*size = Some(engine_order.size); *size = Some(engine_order);
let order = BatchOrder { let order = BatchOrder {
orders: vec![ orders: vec![
@@ -55,7 +52,7 @@ impl Engine {
}, },
OrderRequest { OrderRequest {
asset: asset_id, asset: asset_id,
is_buy: matches!(signal.side, Side::Bid), is_buy: !matches!(signal.side, Side::Bid),
limit_px: signal.entry_price, limit_px: signal.entry_price,
sz: engine_order.size, sz: engine_order.size,
reduce_only: true, reduce_only: true,
@@ -68,19 +65,19 @@ impl Engine {
}, },
OrderRequest { OrderRequest {
asset: asset_id, asset: asset_id,
is_buy: matches!(signal.side, Side::Bid), is_buy: !matches!(signal.side, Side::Bid),
limit_px: signal.entry_price, limit_px: signal.entry_price,
sz: engine_order.size, sz: engine_order.size,
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, trigger_px: engine_order.stop_loss,
tpsl: hypercore::TpSl::Sl, tpsl: hypercore::TpSl::Sl,
}, },
cloid: engine_order.stop_loss, cloid: engine_order.stop_loss_id,
}, },
], ],
grouping: hypercore::OrderGrouping::Na, grouping: hypercore::OrderGrouping::NormalTpsl,
builder: None, builder: None,
}; };
@@ -95,9 +92,10 @@ impl Engine {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"order rejected by HyperLiquid: {}", "order rejected by HyperLiquid: {}",
o.into_iter() o.into_iter()
.filter_map(|res| { .enumerate()
.filter_map(|(i, res)| {
if let OrderResponseStatus::Error(e) = res { if let OrderResponseStatus::Error(e) = res {
Some(e) Some(format!("{i}={e}"))
} else { } else {
None None
} }
+20 -14
View File
@@ -4,7 +4,7 @@ use futures::StreamExt;
use hypersdk::{ use hypersdk::{
Decimal, Decimal,
hypercore::{ hypercore::{
self, Cloid, self, Cloid, Side,
ws::{ConnectionHandle, ConnectionStream, Event}, ws::{ConnectionHandle, ConnectionStream, Event},
}, },
}; };
@@ -13,16 +13,6 @@ use tokio::sync::Mutex;
use crate::{engine::Engine, store::config::RiskConfig}; use crate::{engine::Engine, store::config::RiskConfig};
#[derive(Debug, Clone)]
pub struct EngineOrder {
pub risk_equity: Decimal,
pub size: Decimal,
pub entry: Cloid,
pub take_profit: Cloid,
pub stop_loss: Cloid,
}
pub struct RiskState { pub struct RiskState {
pub starting_equity: Decimal, pub starting_equity: Decimal,
pub pnl: Decimal, pub pnl: Decimal,
@@ -85,7 +75,7 @@ impl RiskEngine {
let mut rm = Vec::new(); let mut rm = Vec::new();
for (i, order_ids) in orders.iter().enumerate() { for (i, order_ids) in orders.iter().enumerate() {
if order_ids.stop_loss == cloid { if order_ids.stop_loss_id == cloid {
if order.status.is_filled() { if order.status.is_filled() {
rm.push(i); rm.push(i);
} }
@@ -154,12 +144,28 @@ impl RiskEngine {
.risk_per_trade .risk_per_trade
.get(state.starting_equity); .get(state.starting_equity);
let size = self
.get_risk_config()
.await?
.size_per_trade
.get(state.starting_equity);
let sl_distance = risk / (size / signal.entry_price);
Ok(EngineOrder { Ok(EngineOrder {
entry: Cloid::random(), entry: Cloid::random(),
take_profit: Cloid::random(), take_profit: Cloid::random(),
stop_loss: Cloid::random(), stop_loss_id: Cloid::random(),
size: size.round_dp(3),
risk_equity: risk, risk_equity: risk,
size: (risk / (signal.entry_price - signal.stop_loss).abs()).round_dp(3),
stop_loss: if matches!(signal.side, Side::Ask) {
signal.entry_price + sl_distance
} else {
signal.entry_price - sl_distance
}
.round_dp(3),
}) })
} }
+2
View File
@@ -4,6 +4,7 @@ use std::collections::HashMap;
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct RiskConfig { pub struct RiskConfig {
pub size_per_trade: Allocation,
pub risk_per_trade: Allocation, pub risk_per_trade: Allocation,
pub max_open_positions: u32, pub max_open_positions: u32,
pub max_daily_loss: Allocation, pub max_daily_loss: Allocation,
@@ -28,6 +29,7 @@ pub struct ConfigManager {
impl Default for RiskConfig { impl Default for RiskConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
size_per_trade: Allocation::Percent(20.into()),
risk_per_trade: Allocation::Percent(10.into()), risk_per_trade: Allocation::Percent(10.into()),
max_daily_loss: Allocation::Percent(20.into()), max_daily_loss: Allocation::Percent(20.into()),
max_open_positions: 1, max_open_positions: 1,
+9 -5
View File
@@ -55,8 +55,8 @@ impl Formatted for EventLog {
impl Formatted for SignalStatus { impl Formatted for SignalStatus {
fn get_formatted(&self) -> Vec<String> { fn get_formatted(&self) -> Vec<String> {
match self { match self {
Ok((signal, size)) | Err((signal, size)) => { Ok((signal, order)) | Err((signal, order)) => {
vec![ let mut base = vec![
if matches!(self, Ok(_)) { if matches!(self, Ok(_)) {
format!("\x1b[34mOK\x1b[0m") format!("\x1b[34mOK\x1b[0m")
} else { } else {
@@ -67,12 +67,16 @@ impl Formatted for SignalStatus {
} else { } else {
format!("\x1b[31mSELL\x1b[0m") format!("\x1b[31mSELL\x1b[0m")
}, },
size.map(|v| format_f64(v.as_f64())).unwrap_or_default(),
format_symbol(&signal.symbol), format_symbol(&signal.symbol),
format_usd(signal.entry_price.as_f64()), format_usd(signal.entry_price.as_f64()),
format_usd(signal.take_profit.as_f64()), format_usd(signal.take_profit.as_f64()),
format_usd_reverse(signal.stop_loss.as_f64()), ];
]
if let Some(order) = order {
base.extend(vec![format_usd_reverse(order.stop_loss.as_f64())]);
}
base
} }
} }
} }