use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use chrono::{DateTime, FixedOffset, Utc};
use ethers::signers::LocalWallet;
use hyperliquid_backtest::prelude::*;
use hyperliquid_backtest::live_trading::{
LiveTradingEngine, LiveTradingError, AlertLevel, AlertMessage,
RetryPolicy, SafetyCircuitBreakerConfig
};
use hyperliquid_backtest::trading_mode::{ApiConfig, RiskConfig};
use hyperliquid_backtest::unified_data::{
Position, OrderRequest, OrderResult, MarketData,
OrderSide, OrderType, TimeInForce, OrderStatus,
TradingStrategy, Signal
};
use hyperliquid_backtest::logging::init_logger;
struct SmaCrossStrategy {
symbol: String,
short_period: usize,
long_period: usize,
short_ma: Vec<f64>,
long_ma: Vec<f64>,
prices: Vec<f64>,
position_size: f64,
current_position: f64,
}
impl SmaCrossStrategy {
fn new(symbol: &str, short_period: usize, long_period: usize, position_size: f64) -> Self {
Self {
symbol: symbol.to_string(),
short_period,
long_period,
short_ma: Vec::new(),
long_ma: Vec::new(),
prices: Vec::new(),
position_size,
current_position: 0.0,
}
}
fn calculate_sma(&self, period: usize) -> Option<f64> {
if self.prices.len() < period {
return None;
}
let sum: f64 = self.prices.iter().rev().take(period).sum();
Some(sum / period as f64)
}
fn update_indicators(&mut self, price: f64) {
self.prices.push(price);
if let Some(short_ma) = self.calculate_sma(self.short_period) {
self.short_ma.push(short_ma);
}
if let Some(long_ma) = self.calculate_sma(self.long_period) {
self.long_ma.push(long_ma);
}
if self.prices.len() > self.long_period * 2 {
self.prices.remove(0);
}
if self.short_ma.len() > 10 {
self.short_ma.remove(0);
}
if self.long_ma.len() > 10 {
self.long_ma.remove(0);
}
}
fn get_signal(&self) -> Option<OrderSide> {
if self.short_ma.len() < 2 || self.long_ma.len() < 2 {
return None;
}
let short_ma_current = self.short_ma.last().unwrap();
let short_ma_prev = self.short_ma.get(self.short_ma.len() - 2).unwrap();
let long_ma_current = self.long_ma.last().unwrap();
let long_ma_prev = self.long_ma.get(self.long_ma.len() - 2).unwrap();
if short_ma_prev <= long_ma_prev && short_ma_current > long_ma_current {
return Some(OrderSide::Buy);
}
if short_ma_prev >= long_ma_prev && short_ma_current < long_ma_current {
return Some(OrderSide::Sell);
}
None
}
}
impl TradingStrategy for SmaCrossStrategy {
fn name(&self) -> &str {
"SMA Crossover Strategy"
}
fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String> {
if data.symbol != self.symbol {
return Ok(Vec::new());
}
self.update_indicators(data.price);
let signal = self.get_signal();
let mut orders = Vec::new();
if let Some(side) = signal {
match side {
OrderSide::Buy => {
if self.current_position <= 0.0 {
if self.current_position < 0.0 {
orders.push(OrderRequest {
symbol: self.symbol.clone(),
side: OrderSide::Buy,
order_type: OrderType::Market,
quantity: self.current_position.abs(),
price: None,
reduce_only: true,
time_in_force: TimeInForce::ImmediateOrCancel,
});
}
orders.push(OrderRequest {
symbol: self.symbol.clone(),
side: OrderSide::Buy,
order_type: OrderType::Market,
quantity: self.position_size,
price: None,
reduce_only: false,
time_in_force: TimeInForce::ImmediateOrCancel,
});
self.current_position = self.position_size;
}
},
OrderSide::Sell => {
if self.current_position >= 0.0 {
if self.current_position > 0.0 {
orders.push(OrderRequest {
symbol: self.symbol.clone(),
side: OrderSide::Sell,
order_type: OrderType::Market,
quantity: self.current_position,
price: None,
reduce_only: true,
time_in_force: TimeInForce::ImmediateOrCancel,
});
}
orders.push(OrderRequest {
symbol: self.symbol.clone(),
side: OrderSide::Sell,
order_type: OrderType::Market,
quantity: self.position_size,
price: None,
reduce_only: false,
time_in_force: TimeInForce::ImmediateOrCancel,
});
self.current_position = -self.position_size;
}
},
}
}
Ok(orders)
}
fn on_order_fill(&mut self, fill: &crate::unified_data::OrderFill) -> Result<(), String> {
if fill.symbol == self.symbol {
match fill.side {
OrderSide::Buy => {
self.current_position += fill.quantity;
},
OrderSide::Sell => {
self.current_position -= fill.quantity;
},
}
}
Ok(())
}
fn on_funding_payment(&mut self, _payment: &crate::unified_data::FundingPayment) -> Result<(), String> {
Ok(())
}
fn get_current_signals(&self) -> HashMap<String, Signal> {
let mut signals = HashMap::new();
if let Some(signal_side) = self.get_signal() {
let direction = match signal_side {
OrderSide::Buy => SignalDirection::Long,
OrderSide::Sell => SignalDirection::Short,
};
signals.insert(
self.symbol.clone(),
Signal {
symbol: self.symbol.clone(),
direction,
strength: 1.0,
timestamp: Utc::now().with_timezone(&FixedOffset::east(0)),
}
);
}
signals
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logger();
println!("🛡️ Live Trading Safety Example");
println!("==============================");
let private_key = "0000000000000000000000000000000000000000000000000000000000000001";
let wallet = LocalWallet::from_str(private_key).unwrap();
println!("📝 Wallet address: {}", wallet.address());
let api_config = ApiConfig {
api_key: "your_api_key".to_string(),
api_secret: "your_api_secret".to_string(),
endpoint: "https://api.hyperliquid-testnet.xyz".to_string(),
use_testnet: true, timeout_ms: 5000,
};
let risk_config = RiskConfig {
max_position_size_pct: 0.1, max_daily_loss_pct: 0.02, stop_loss_pct: 0.05, take_profit_pct: 0.1, max_leverage: 3.0, max_concentration_pct: 0.25, max_position_correlation: 0.7, volatility_sizing_factor: 0.5, max_portfolio_volatility_pct: 0.05, max_drawdown_pct: 0.1, };
let retry_policy = RetryPolicy {
max_attempts: 3,
initial_delay_ms: 500,
backoff_factor: 2.0,
max_delay_ms: 5000,
};
let safety_config = SafetyCircuitBreakerConfig {
max_consecutive_failed_orders: 3,
max_order_failure_rate: 0.5,
order_failure_rate_window: 10,
max_position_drawdown_pct: 0.15,
max_account_drawdown_pct: 0.10,
max_price_deviation_pct: 0.05,
price_deviation_window_sec: 60,
max_critical_alerts: 3,
critical_alerts_window: 10,
};
println!("⚙️ Creating live trading engine...");
let mut engine = LiveTradingEngine::new(wallet, risk_config, api_config).await?;
engine.set_retry_policy(retry_policy);
engine.set_safety_circuit_breaker_config(safety_config);
engine.set_detailed_logging(true);
println!("🛡️ Initializing safety mechanisms...");
engine.init_safety_mechanisms().await?;
println!("🔌 Connecting to exchange...");
engine.connect().await?;
println!("✅ Connected to exchange");
let strategy = Box::new(SmaCrossStrategy::new("BTC", 10, 20, 0.01));
println!("🚀 Starting trading with strategy: {}", strategy.name());
match engine.start_trading(strategy).await {
Ok(_) => {
println!("✅ Trading completed successfully");
},
Err(e) => {
println!("❌ Trading error: {}", e);
if engine.is_emergency_stop_active() {
println!("⚠️ Emergency stop was triggered");
println!("📊 Positions at emergency stop:");
for (symbol, position) in engine.get_positions() {
println!(" {}: {} @ {:.2} (PnL: {:.2})",
symbol, position.size, position.current_price, position.unrealized_pnl);
}
}
}
}
println!("🔌 Disconnecting from exchange...");
engine.disconnect().await?;
println!("✅ Disconnected from exchange");
Ok(())
}