Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
WALLET_ADDRESS=0000000000000000000000000000000000000000
PRIVATE_KEY=

USE_MAINNET=false
ALLOW_ORDER=false

SYMBOLS=ETH,BTC
QUOTE_ASSET=USDC

MAX_ORDER_USD=25
MAX_POSITION_USD=100

LOG_LEVEL=info
88 changes: 88 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ anyhow = "1.0.102"
async-trait = "0.1.89"
ethers = "2.0.14"
hyperliquid_rust_sdk = "0.6.0"
rand = "0.8.5"
serde = "1.0.228"
tokio = { version = "1.52.1", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter", "fmt", "json"] }
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# rust-exec

A proof-of-concept to explore exchange connectivity with [Hyperliquid](hyperliquid.xyz) and trade workflow using the Rust language, [tokio](https://github.com/tokio-rs/tokio) and [hyperliquid_rust_sdk](https://github.com/hyperliquid-dex/hyperliquid-rust-sdk).
A proof-of-concept trade execution layer in Rust, exploring exchange connectivity and trade workflow with [Hyperliquid](hyperliquid.xyz) using [tokio](https://github.com/tokio-rs/tokio) and [hyperliquid_rust_sdk](https://github.com/hyperliquid-dex/hyperliquid-rust-sdk).

## Features

- Rust/Tokio async exchange connectivity
- Hyperliquid L2 market data ingestion
- exchange abstraction via trait
- shared state propagation using watch channels
- shared state propagation using mpsc & watch channels
- dummy weights collection
- dry-run execution loop
- pre-trade risk check framework
- testnet integration tests
3 changes: 0 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::clone;

#[derive(clone::Clone)]
pub struct Config {
pub hl_base_url: String,
pub wallet_address: String,
pub private_key: Option<String>,

Expand All @@ -19,7 +18,6 @@ pub struct Config {

impl Config {
pub fn from_env() -> anyhow::Result<Self> {
let hl_base_url = std::env::var("HL_BASE_URL")?;
let wallet_address = std::env::var("WALLET_ADDRESS")?;
let private_key = std::env::var("PRIVATE_KEY").ok();

Expand All @@ -45,7 +43,6 @@ impl Config {
let log_level = std::env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string());

Ok(Self {
hl_base_url,
wallet_address,
private_key,
symbols,
Expand Down
30 changes: 30 additions & 0 deletions src/exchanges/hyperliquid/balances.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use ethers::types::H160;
use hyperliquid_rust_sdk::InfoClient;

use crate::wallet::WalletHolding;

pub async fn get_wallet_holdings(
info_client: &InfoClient,
wallet_address: H160,
) -> anyhow::Result<Vec<WalletHolding>> {
let user_state = info_client.user_state(wallet_address).await?;

Ok(user_state
.asset_positions
.into_iter()
.map(|asset_position| {
let position = asset_position.position;

WalletHolding {
coin: position.coin,
total: position.szi,
entry_px: position.entry_px,
leverage: Some(position.leverage.value.to_string()),
unrealized_pnl: Some(position.unrealized_pnl),
realised_pnl: None,
funding_unlocked: Some(position.cum_funding.since_open),
collateral: Some(position.margin_used),
}
})
.collect())
}
26 changes: 3 additions & 23 deletions src/exchanges/hyperliquid/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
wallet::WalletHolding,
};

use super::market_data;
use super::{balances, market_data, orders};

pub struct HyperliquidClient {
base_url: BaseUrl,
Expand Down Expand Up @@ -65,30 +65,10 @@ impl ExchangeClient for HyperliquidClient {
}

async fn get_wallet_holdings(&self) -> anyhow::Result<Vec<WalletHolding>> {
let address = self.wallet_address;
let user_state = self.info_client.user_state(address).await?;

Ok(user_state
.asset_positions
.into_iter()
.map(|asset_position| {
let position = asset_position.position;

WalletHolding {
coin: position.coin,
total: position.szi,
entry_px: position.entry_px,
leverage: Some(position.leverage.value.to_string()),
unrealized_pnl: Some(position.unrealized_pnl),
realised_pnl: None,
funding_unlocked: Some(position.cum_funding.since_open),
collateral: Some(position.margin_used),
}
})
.collect())
balances::get_wallet_holdings(&self.info_client, self.wallet_address).await
}

async fn place_order(&self, order: Order) -> anyhow::Result<Order> {
Ok(order)
orders::place_order(self.exchange_client.as_ref(), order).await
}
}
19 changes: 19 additions & 0 deletions src/exchanges/hyperliquid/market_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use tokio::{
sync::{mpsc::unbounded_channel, watch::Sender},
time::Instant,
};
use tracing::{debug, info};

use crate::market_data::{MarketSnapshot, MarketState};

Expand Down Expand Up @@ -43,10 +44,20 @@ pub async fn subscribe(
symbols: Vec<String>,
market_tx: Sender<MarketState>,
) -> anyhow::Result<()> {
info!(
symbol_count = symbols.len(),
"opening hyperliquid market data stream"
);

let mut info_client = InfoClient::new(None, Some(base_url)).await?;
let (sender, mut receiver) = unbounded_channel();

for symbol in symbols {
info!(
symbol = %symbol,
"subscribing to hyperliquid l2 book"
);

info_client
.subscribe(Subscription::L2Book { coin: symbol }, sender.clone())
.await?;
Expand All @@ -63,11 +74,19 @@ pub async fn subscribe(

market_state.update(snapshot_from_l2_data(&l2_book.data)?);

debug!(
symbol = %l2_book.data.coin,
"received hyperliquid l2 book update"
);

if market_tx.send(market_state.clone()).is_err() {
info!("market data receiver dropped");
break;
}
}

info!("hyperliquid market data stream closed");

Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions src/exchanges/hyperliquid/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod balances;
mod client;
mod market_data;
mod orders;

pub use client::HyperliquidClient;
Loading
Loading