hyperliquid_backtest/
unified_data.rs

1//! # Unified Data Structures
2//!
3//! This module provides unified data structures that work across all trading modes
4//! (backtest, paper trading, live trading) to ensure consistent strategy execution
5//! and seamless transitions between modes.
6//!
7//! ## Features
8//!
9//! - Position tracking across all trading modes
10//! - Order request and result structures for unified order management
11//! - Market data structure for real-time data handling
12//! - Trading configuration and risk management structures
13//! - Signal and strategy interfaces for consistent strategy execution
14
15use std::collections::HashMap;
16use chrono::{DateTime, FixedOffset};
17use serde::{Deserialize, Serialize};
18use crate::backtest::FundingPayment;
19
20/// Position information across all trading modes
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Position {
23    /// Symbol/ticker of the asset
24    pub symbol: String,
25    
26    /// Position size (positive for long, negative for short)
27    pub size: f64,
28    
29    /// Entry price
30    pub entry_price: f64,
31    
32    /// Current price
33    pub current_price: f64,
34    
35    /// Unrealized profit and loss
36    pub unrealized_pnl: f64,
37    
38    /// Realized profit and loss
39    pub realized_pnl: f64,
40    
41    /// Funding profit and loss (for perpetual futures)
42    pub funding_pnl: f64,
43    
44    /// Position timestamp
45    pub timestamp: DateTime<FixedOffset>,
46    
47    /// Leverage used for this position
48    pub leverage: f64,
49    
50    /// Liquidation price (if applicable)
51    pub liquidation_price: Option<f64>,
52    
53    /// Position margin (if applicable)
54    pub margin: Option<f64>,
55    
56    /// Additional position metadata
57    pub metadata: HashMap<String, String>,
58}
59
60impl Position {
61    /// Create a new position
62    pub fn new(
63        symbol: &str,
64        size: f64,
65        entry_price: f64,
66        current_price: f64,
67        timestamp: DateTime<FixedOffset>,
68    ) -> Self {
69        let unrealized_pnl = if size != 0.0 {
70            size * (current_price - entry_price)
71        } else {
72            0.0
73        };
74        
75        Self {
76            symbol: symbol.to_string(),
77            size,
78            entry_price,
79            current_price,
80            unrealized_pnl,
81            realized_pnl: 0.0,
82            funding_pnl: 0.0,
83            timestamp,
84            leverage: 1.0,
85            liquidation_price: None,
86            margin: None,
87            metadata: HashMap::new(),
88        }
89    }
90    
91    /// Update the position with a new price
92    pub fn update_price(&mut self, price: f64) {
93        self.current_price = price;
94        if self.size != 0.0 {
95            self.unrealized_pnl = self.size * (price - self.entry_price);
96        }
97    }
98    
99    /// Apply a funding payment to the position
100    pub fn apply_funding_payment(&mut self, payment: f64) {
101        self.funding_pnl += payment;
102    }
103    
104    /// Get the total PnL (realized + unrealized + funding)
105    pub fn total_pnl(&self) -> f64 {
106        self.realized_pnl + self.unrealized_pnl + self.funding_pnl
107    }
108    
109    /// Get the position notional value
110    pub fn notional_value(&self) -> f64 {
111        self.size.abs() * self.current_price
112    }
113    
114    /// Check if the position is long
115    pub fn is_long(&self) -> bool {
116        self.size > 0.0
117    }
118    
119    /// Check if the position is short
120    pub fn is_short(&self) -> bool {
121        self.size < 0.0
122    }
123    
124    /// Check if the position is flat (no position)
125    pub fn is_flat(&self) -> bool {
126        self.size == 0.0
127    }
128}
129
130/// Order side (buy/sell)
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
132pub enum OrderSide {
133    /// Buy order
134    Buy,
135    
136    /// Sell order
137    Sell,
138}
139
140impl std::fmt::Display for OrderSide {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        match self {
143            OrderSide::Buy => write!(f, "Buy"),
144            OrderSide::Sell => write!(f, "Sell"),
145        }
146    }
147}
148
149/// Order type
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
151pub enum OrderType {
152    /// Market order
153    Market,
154    
155    /// Limit order
156    Limit,
157    
158    /// Stop market order
159    StopMarket,
160    
161    /// Stop limit order
162    StopLimit,
163    
164    /// Take profit market order
165    TakeProfitMarket,
166    
167    /// Take profit limit order
168    TakeProfitLimit,
169}
170
171impl std::fmt::Display for OrderType {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        match self {
174            OrderType::Market => write!(f, "Market"),
175            OrderType::Limit => write!(f, "Limit"),
176            OrderType::StopMarket => write!(f, "StopMarket"),
177            OrderType::StopLimit => write!(f, "StopLimit"),
178            OrderType::TakeProfitMarket => write!(f, "TakeProfitMarket"),
179            OrderType::TakeProfitLimit => write!(f, "TakeProfitLimit"),
180        }
181    }
182}
183
184/// Time in force policy
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
186pub enum TimeInForce {
187    /// Good till cancelled
188    GoodTillCancel,
189    
190    /// Immediate or cancel
191    ImmediateOrCancel,
192    
193    /// Fill or kill
194    FillOrKill,
195    
196    /// Good till date
197    GoodTillDate,
198}
199
200impl std::fmt::Display for TimeInForce {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        match self {
203            TimeInForce::GoodTillCancel => write!(f, "GoodTillCancel"),
204            TimeInForce::ImmediateOrCancel => write!(f, "ImmediateOrCancel"),
205            TimeInForce::FillOrKill => write!(f, "FillOrKill"),
206            TimeInForce::GoodTillDate => write!(f, "GoodTillDate"),
207        }
208    }
209}
210
211/// Order request across all trading modes
212#[derive(Debug, Clone)]
213pub struct OrderRequest {
214    /// Symbol/ticker of the asset
215    pub symbol: String,
216    
217    /// Order side (buy/sell)
218    pub side: OrderSide,
219    
220    /// Order type (market/limit/etc)
221    pub order_type: OrderType,
222    
223    /// Order quantity
224    pub quantity: f64,
225    
226    /// Order price (for limit orders)
227    pub price: Option<f64>,
228    
229    /// Whether this order reduces position only
230    pub reduce_only: bool,
231    
232    /// Time in force policy
233    pub time_in_force: TimeInForce,
234    
235    /// Stop price (for stop orders)
236    pub stop_price: Option<f64>,
237    
238    /// Client order ID (if any)
239    pub client_order_id: Option<String>,
240    
241    /// Additional order parameters
242    pub parameters: HashMap<String, String>,
243}
244
245impl OrderRequest {
246    /// Create a new market order
247    pub fn market(symbol: &str, side: OrderSide, quantity: f64) -> Self {
248        Self {
249            symbol: symbol.to_string(),
250            side,
251            order_type: OrderType::Market,
252            quantity,
253            price: None,
254            reduce_only: false,
255            time_in_force: TimeInForce::GoodTillCancel,
256            stop_price: None,
257            client_order_id: None,
258            parameters: HashMap::new(),
259        }
260    }
261    
262    /// Create a new limit order
263    pub fn limit(symbol: &str, side: OrderSide, quantity: f64, price: f64) -> Self {
264        Self {
265            symbol: symbol.to_string(),
266            side,
267            order_type: OrderType::Limit,
268            quantity,
269            price: Some(price),
270            reduce_only: false,
271            time_in_force: TimeInForce::GoodTillCancel,
272            stop_price: None,
273            client_order_id: None,
274            parameters: HashMap::new(),
275        }
276    }
277    
278    /// Set the order as reduce-only
279    pub fn reduce_only(mut self) -> Self {
280        self.reduce_only = true;
281        self
282    }
283    
284    /// Set the time in force policy
285    pub fn with_time_in_force(mut self, time_in_force: TimeInForce) -> Self {
286        self.time_in_force = time_in_force;
287        self
288    }
289    
290    /// Set the client order ID
291    pub fn with_client_order_id(mut self, client_order_id: &str) -> Self {
292        self.client_order_id = Some(client_order_id.to_string());
293        self
294    }
295    
296    /// Add a parameter to the order
297    pub fn with_parameter(mut self, key: &str, value: &str) -> Self {
298        self.parameters.insert(key.to_string(), value.to_string());
299        self
300    }
301    
302    /// Validate the order request
303    pub fn validate(&self) -> Result<(), String> {
304        // Check for positive quantity
305        if self.quantity <= 0.0 {
306            return Err("Order quantity must be positive".to_string());
307        }
308        
309        // Check for price on limit orders
310        if matches!(self.order_type, OrderType::Limit | OrderType::StopLimit | OrderType::TakeProfitLimit) 
311            && self.price.is_none() {
312            return Err(format!("Price is required for {} orders", self.order_type));
313        }
314        
315        // Check for stop price on stop orders
316        if matches!(self.order_type, OrderType::StopMarket | OrderType::StopLimit) 
317            && self.stop_price.is_none() {
318            return Err(format!("Stop price is required for {} orders", self.order_type));
319        }
320        
321        // Check for take profit price on take profit orders
322        if matches!(self.order_type, OrderType::TakeProfitMarket | OrderType::TakeProfitLimit) 
323            && self.stop_price.is_none() {
324            return Err(format!("Take profit price is required for {} orders", self.order_type));
325        }
326        
327        Ok(())
328    }
329}
330
331/// Order status
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
333pub enum OrderStatus {
334    /// Order created but not yet submitted
335    Created,
336    
337    /// Order submitted to exchange
338    Submitted,
339    
340    /// Order partially filled
341    PartiallyFilled,
342    
343    /// Order fully filled
344    Filled,
345    
346    /// Order cancelled
347    Cancelled,
348    
349    /// Order rejected
350    Rejected,
351    
352    /// Order expired
353    Expired,
354}
355
356impl std::fmt::Display for OrderStatus {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        match self {
359            OrderStatus::Created => write!(f, "Created"),
360            OrderStatus::Submitted => write!(f, "Submitted"),
361            OrderStatus::PartiallyFilled => write!(f, "PartiallyFilled"),
362            OrderStatus::Filled => write!(f, "Filled"),
363            OrderStatus::Cancelled => write!(f, "Cancelled"),
364            OrderStatus::Rejected => write!(f, "Rejected"),
365            OrderStatus::Expired => write!(f, "Expired"),
366        }
367    }
368}
369
370/// Order result across all trading modes
371#[derive(Debug, Clone)]
372pub struct OrderResult {
373    /// Order ID
374    pub order_id: String,
375    
376    /// Symbol/ticker of the asset
377    pub symbol: String,
378    
379    /// Order side (buy/sell)
380    pub side: OrderSide,
381    
382    /// Order type
383    pub order_type: OrderType,
384    
385    /// Requested quantity
386    pub requested_quantity: f64,
387    
388    /// Filled quantity
389    pub filled_quantity: f64,
390    
391    /// Average fill price
392    pub average_price: Option<f64>,
393    
394    /// Order status
395    pub status: OrderStatus,
396    
397    /// Order timestamp
398    pub timestamp: DateTime<FixedOffset>,
399    
400    /// Fees paid
401    pub fees: Option<f64>,
402    
403    /// Error message (if any)
404    pub error: Option<String>,
405    
406    /// Client order ID (if any)
407    pub client_order_id: Option<String>,
408    
409    /// Additional order result data
410    pub metadata: HashMap<String, String>,
411}
412
413impl OrderResult {
414    /// Create a new order result
415    pub fn new(
416        order_id: &str,
417        symbol: &str,
418        side: OrderSide,
419        order_type: OrderType,
420        requested_quantity: f64,
421        timestamp: DateTime<FixedOffset>,
422    ) -> Self {
423        Self {
424            order_id: order_id.to_string(),
425            symbol: symbol.to_string(),
426            side,
427            order_type,
428            requested_quantity,
429            filled_quantity: 0.0,
430            average_price: None,
431            status: OrderStatus::Created,
432            timestamp,
433            fees: None,
434            error: None,
435            client_order_id: None,
436            metadata: HashMap::new(),
437        }
438    }
439    
440    /// Check if the order is active
441    pub fn is_active(&self) -> bool {
442        matches!(self.status, OrderStatus::Created | OrderStatus::Submitted | OrderStatus::PartiallyFilled)
443    }
444    
445    /// Check if the order is complete
446    pub fn is_complete(&self) -> bool {
447        matches!(self.status, OrderStatus::Filled | OrderStatus::Cancelled | OrderStatus::Rejected | OrderStatus::Expired)
448    }
449    
450    /// Check if the order is filled (partially or fully)
451    pub fn is_filled(&self) -> bool {
452        matches!(self.status, OrderStatus::PartiallyFilled | OrderStatus::Filled)
453    }
454    
455    /// Get the fill percentage
456    pub fn fill_percentage(&self) -> f64 {
457        if self.requested_quantity > 0.0 {
458            self.filled_quantity / self.requested_quantity * 100.0
459        } else {
460            0.0
461        }
462    }
463    
464    /// Get the notional value of the filled quantity
465    pub fn filled_notional(&self) -> Option<f64> {
466        self.average_price.map(|price| self.filled_quantity * price)
467    }
468}
469
470/// Market data structure for real-time data
471#[derive(Debug, Clone)]
472pub struct MarketData {
473    /// Symbol/ticker of the asset
474    pub symbol: String,
475    
476    /// Last price
477    pub price: f64,
478    
479    /// Best bid price
480    pub bid: f64,
481    
482    /// Best ask price
483    pub ask: f64,
484    
485    /// Trading volume
486    pub volume: f64,
487    
488    /// Timestamp
489    pub timestamp: DateTime<FixedOffset>,
490    
491    /// Current funding rate (if available)
492    pub funding_rate: Option<f64>,
493    
494    /// Next funding time (if available)
495    pub next_funding_time: Option<DateTime<FixedOffset>>,
496    
497    /// Open interest (if available)
498    pub open_interest: Option<f64>,
499    
500    /// Market depth (order book)
501    pub depth: Option<OrderBookSnapshot>,
502    
503    /// Recent trades
504    pub recent_trades: Option<Vec<Trade>>,
505    
506    /// 24-hour price change percentage
507    pub price_change_24h_pct: Option<f64>,
508    
509    /// 24-hour high price
510    pub high_24h: Option<f64>,
511    
512    /// 24-hour low price
513    pub low_24h: Option<f64>,
514    
515    /// Additional market data
516    pub metadata: HashMap<String, String>,
517}
518
519impl MarketData {
520    /// Create a new market data instance with basic price information
521    pub fn new(
522        symbol: &str,
523        price: f64,
524        bid: f64,
525        ask: f64,
526        volume: f64,
527        timestamp: DateTime<FixedOffset>,
528    ) -> Self {
529        Self {
530            symbol: symbol.to_string(),
531            price,
532            bid,
533            ask,
534            volume,
535            timestamp,
536            funding_rate: None,
537            next_funding_time: None,
538            open_interest: None,
539            depth: None,
540            recent_trades: None,
541            price_change_24h_pct: None,
542            high_24h: None,
543            low_24h: None,
544            metadata: HashMap::new(),
545        }
546    }
547    
548    /// Get the mid price (average of bid and ask)
549    pub fn mid_price(&self) -> f64 {
550        (self.bid + self.ask) / 2.0
551    }
552    
553    /// Get the spread (ask - bid)
554    pub fn spread(&self) -> f64 {
555        self.ask - self.bid
556    }
557    
558    /// Get the spread as a percentage of the mid price
559    pub fn spread_percentage(&self) -> f64 {
560        let mid = self.mid_price();
561        if mid > 0.0 {
562            self.spread() / mid * 100.0
563        } else {
564            0.0
565        }
566    }
567    
568    /// Add funding rate information
569    pub fn with_funding_rate(
570        mut self,
571        funding_rate: f64,
572        next_funding_time: DateTime<FixedOffset>,
573    ) -> Self {
574        self.funding_rate = Some(funding_rate);
575        self.next_funding_time = Some(next_funding_time);
576        self
577    }
578    
579    /// Add open interest information
580    pub fn with_open_interest(mut self, open_interest: f64) -> Self {
581        self.open_interest = Some(open_interest);
582        self
583    }
584    
585    /// Add 24-hour statistics
586    pub fn with_24h_stats(
587        mut self,
588        price_change_pct: f64,
589        high: f64,
590        low: f64,
591    ) -> Self {
592        self.price_change_24h_pct = Some(price_change_pct);
593        self.high_24h = Some(high);
594        self.low_24h = Some(low);
595        self
596    }
597    
598    /// Add a metadata field
599    pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
600        self.metadata.insert(key.to_string(), value.to_string());
601        self
602    }
603}
604
605/// Order book level (price and quantity)
606#[derive(Debug, Clone)]
607pub struct OrderBookLevel {
608    /// Price level
609    pub price: f64,
610    
611    /// Quantity at this price level
612    pub quantity: f64,
613}
614
615/// Order book snapshot
616#[derive(Debug, Clone)]
617pub struct OrderBookSnapshot {
618    /// Bid levels (sorted by price descending)
619    pub bids: Vec<OrderBookLevel>,
620    
621    /// Ask levels (sorted by price ascending)
622    pub asks: Vec<OrderBookLevel>,
623    
624    /// Timestamp of the snapshot
625    pub timestamp: DateTime<FixedOffset>,
626}
627
628/// Trade information
629#[derive(Debug, Clone)]
630pub struct Trade {
631    /// Trade ID
632    pub id: String,
633    
634    /// Trade price
635    pub price: f64,
636    
637    /// Trade quantity
638    pub quantity: f64,
639    
640    /// Trade timestamp
641    pub timestamp: DateTime<FixedOffset>,
642    
643    /// Trade side (buy/sell)
644    pub side: Option<OrderSide>,
645}
646
647/// Trading signal direction
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649pub enum SignalDirection {
650    /// Buy signal
651    Buy,
652    
653    /// Sell signal
654    Sell,
655    
656    /// Hold/neutral signal
657    Neutral,
658    
659    /// Close position signal
660    Close,
661}
662
663/// Trading signal
664#[derive(Debug, Clone)]
665pub struct Signal {
666    /// Symbol/ticker of the asset
667    pub symbol: String,
668    
669    /// Signal direction
670    pub direction: SignalDirection,
671    
672    /// Signal strength (0.0 to 1.0)
673    pub strength: f64,
674    
675    /// Signal timestamp
676    pub timestamp: DateTime<FixedOffset>,
677    
678    /// Signal metadata
679    pub metadata: HashMap<String, String>,
680}
681
682/// Trading strategy trait for unified strategy execution across all modes
683pub trait TradingStrategy: Send + Sync {
684    /// Get the strategy name
685    fn name(&self) -> &str;
686    
687    /// Process market data and generate signals
688    fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String>;
689    
690    /// Process order fill events
691    fn on_order_fill(&mut self, fill: &OrderResult) -> Result<(), String>;
692    
693    /// Process funding payment events
694    fn on_funding_payment(&mut self, payment: &FundingPayment) -> Result<(), String>;
695    
696    /// Get current strategy signals
697    fn get_current_signals(&self) -> HashMap<String, Signal>;
698}
699
700/// Trading configuration
701#[derive(Debug, Clone)]
702pub struct TradingConfig {
703    /// Initial balance for trading
704    pub initial_balance: f64,
705    
706    /// Risk management configuration
707    pub risk_config: Option<RiskConfig>,
708    
709    /// Slippage configuration for paper trading
710    pub slippage_config: Option<SlippageConfig>,
711    
712    /// API configuration for live trading
713    pub api_config: Option<ApiConfig>,
714    
715    /// Additional mode-specific configuration parameters
716    pub parameters: HashMap<String, String>,
717}
718
719/// Risk management configuration
720#[derive(Debug, Clone)]
721pub struct RiskConfig {
722    /// Maximum position size as a percentage of portfolio value
723    pub max_position_size_pct: f64,
724    
725    /// Maximum daily loss as a percentage of portfolio value
726    pub max_daily_loss_pct: f64,
727    
728    /// Stop loss percentage for positions
729    pub stop_loss_pct: f64,
730    
731    /// Take profit percentage for positions
732    pub take_profit_pct: f64,
733    
734    /// Maximum leverage allowed
735    pub max_leverage: f64,
736    
737    /// Maximum number of concurrent positions
738    pub max_positions: usize,
739    
740    /// Maximum drawdown percentage before stopping trading
741    pub max_drawdown_pct: f64,
742    
743    /// Whether to use trailing stop loss
744    pub use_trailing_stop: bool,
745    
746    /// Trailing stop distance percentage
747    pub trailing_stop_distance_pct: Option<f64>,
748}
749
750/// Slippage simulation configuration for paper trading
751#[derive(Debug, Clone)]
752pub struct SlippageConfig {
753    /// Base slippage as a percentage
754    pub base_slippage_pct: f64,
755    
756    /// Volume-based slippage factor
757    pub volume_impact_factor: f64,
758    
759    /// Volatility-based slippage factor
760    pub volatility_impact_factor: f64,
761    
762    /// Random slippage component maximum (percentage)
763    pub random_slippage_max_pct: f64,
764    
765    /// Simulated latency in milliseconds
766    pub simulated_latency_ms: u64,
767    
768    /// Whether to use order book for slippage calculation
769    pub use_order_book: bool,
770    
771    /// Maximum slippage percentage allowed
772    pub max_slippage_pct: f64,
773}
774
775/// API configuration for live trading
776#[derive(Debug, Clone)]
777pub struct ApiConfig {
778    /// API key for authentication
779    pub api_key: String,
780    
781    /// API secret for authentication
782    pub api_secret: String,
783    
784    /// API endpoint URL
785    pub endpoint: String,
786    
787    /// Whether to use testnet
788    pub use_testnet: bool,
789    
790    /// Timeout for API requests in milliseconds
791    pub timeout_ms: u64,
792    
793    /// Rate limit (requests per second)
794    pub rate_limit: Option<f64>,
795    
796    /// Retry attempts for failed requests
797    pub retry_attempts: u32,
798    
799    /// Retry delay in milliseconds
800    pub retry_delay_ms: u64,
801}