Fix risk
This commit is contained in:
@@ -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)]
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user