use std::sync::{Arc, Mutex};
use std::time::Duration;
use chrono::{DateTime, FixedOffset, Utc};
use std::collections::HashMap;
use hyperliquid_backtest::prelude::*;
struct SimpleSmaStrategy {
name: String,
short_period: usize,
long_period: usize,
short_values: Vec<f64>,
long_values: Vec<f64>,
signals: HashMap<String, Signal>,
last_prices: HashMap<String, f64>,
}
impl SimpleSmaStrategy {
fn new(short_period: usize, long_period: usize) -> Self {
Self {
name: format!("SMA {}/{} Crossover", short_period, long_period),
short_period,
long_period,
short_values: Vec::new(),
long_values: Vec::new(),
signals: HashMap::new(),
last_prices: HashMap::new(),
}
}
fn calculate_sma(&self, values: &[f64], period: usize) -> Option<f64> {
if values.len() < period {
return None;
}
let sum: f64 = values.iter().rev().take(period).sum();
Some(sum / period as f64)
}
}
impl TradingStrategy for SimpleSmaStrategy {
fn name(&self) -> &str {
&self.name
}
fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String> {
let symbol = &data.symbol;
let price = data.price;
self.last_prices.insert(symbol.clone(), price);
self.short_values.push(price);
let max_period = self.short_period.max(self.long_period);
if self.short_values.len() > max_period * 2 {
self.short_values.remove(0);
}
let short_sma = self.calculate_sma(&self.short_values, self.short_period);
let long_sma = self.calculate_sma(&self.short_values, self.long_period);
let mut orders = Vec::new();
if let (Some(short), Some(long)) = (short_sma, long_sma) {
let now = Utc::now().with_timezone(&FixedOffset::east(0));
let signal_direction = if short > long {
SignalDirection::Buy
} else if short < long {
SignalDirection::Sell
} else {
SignalDirection::Neutral
};
let signal = Signal {
symbol: symbol.clone(),
direction: signal_direction,
strength: 1.0,
timestamp: now,
metadata: HashMap::new(),
};
let previous_signal = self.signals.get(symbol);
let signal_changed = match previous_signal {
Some(prev) => prev.direction != signal.direction,
None => true,
};
self.signals.insert(symbol.clone(), signal.clone());
if signal_changed {
match signal.direction {
SignalDirection::Buy => {
orders.push(OrderRequest::market(symbol, OrderSide::Buy, 1.0));
},
SignalDirection::Sell => {
orders.push(OrderRequest::market(symbol, OrderSide::Sell, 1.0));
},
_ => {}
}
}
}
Ok(orders)
}
fn on_order_fill(&mut self, _fill: &OrderResult) -> Result<(), String> {
Ok(())
}
fn on_funding_payment(&mut self, _payment: &FundingPayment) -> Result<(), String> {
Ok(())
}
fn get_current_signals(&self) -> HashMap<String, Signal> {
self.signals.clone()
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logger();
println!("Hyperliquid Paper Trading Example");
println!("================================");
let slippage_config = SlippageConfig {
base_slippage_pct: 0.0005, volume_impact_factor: 0.1, volatility_impact_factor: 0.2, random_slippage_max_pct: 0.001, simulated_latency_ms: 500, use_order_book: false,
max_slippage_pct: 0.002, };
let mut paper_engine = PaperTradingEngine::new(10000.0, slippage_config);
let data_stream = RealTimeDataStream::new().await?;
let data_stream = Arc::new(Mutex::new(data_stream));
paper_engine.set_real_time_data(data_stream.clone());
{
let mut stream = data_stream.lock().unwrap();
stream.connect().await?;
stream.subscribe_ticker("BTC").await?;
stream.subscribe_order_book("BTC").await?;
stream.subscribe_funding_rate("BTC").await?;
}
let strategy = SimpleSmaStrategy::new(10, 20);
let strategy_box: Box<dyn TradingStrategy> = Box::new(strategy);
let paper_engine_clone = Arc::new(Mutex::new(paper_engine));
let paper_engine_for_task = paper_engine_clone.clone();
let task_handle = tokio::spawn(async move {
let mut engine = paper_engine_for_task.lock().unwrap();
if let Err(e) = engine.start_simulation(strategy_box).await {
eprintln!("Error in paper trading simulation: {}", e);
}
});
println!("Paper trading simulation started. Running for 30 seconds...");
tokio::time::sleep(Duration::from_secs(30)).await;
{
let mut engine = paper_engine_clone.lock().unwrap();
engine.stop_simulation();
}
let _ = task_handle.await;
let report = {
let engine = paper_engine_clone.lock().unwrap();
engine.generate_report()
};
println!("\nPaper Trading Results:");
println!("{}", report);
let positions = {
let engine = paper_engine_clone.lock().unwrap();
engine.get_positions().clone()
};
println!("\nFinal Positions:");
for (symbol, position) in positions {
println!("{}: {} @ ${} (PnL: ${:.2})",
symbol,
position.size,
position.current_price,
position.total_pnl()
);
}
let trade_log = {
let engine = paper_engine_clone.lock().unwrap();
engine.get_trade_log().clone()
};
println!("\nTrade History:");
for (i, trade) in trade_log.iter().enumerate() {
println!("{}. {} {} {} @ ${} (Fees: ${:.2})",
i + 1,
trade.timestamp.format("%Y-%m-%d %H:%M:%S"),
trade.side,
trade.quantity,
trade.price,
trade.fees
);
}
println!("\nExample completed successfully!");
Ok(())
}