hyperliquid_backtest/
unified_data_impl.rs

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