1use std::collections::HashMap;
16use chrono::{DateTime, FixedOffset};
17use serde::{Deserialize, Serialize};
18use crate::backtest::FundingPayment;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Position {
23 pub symbol: String,
25
26 pub size: f64,
28
29 pub entry_price: f64,
31
32 pub current_price: f64,
34
35 pub unrealized_pnl: f64,
37
38 pub realized_pnl: f64,
40
41 pub funding_pnl: f64,
43
44 pub timestamp: DateTime<FixedOffset>,
46
47 pub leverage: f64,
49
50 pub liquidation_price: Option<f64>,
52
53 pub margin: Option<f64>,
55
56 pub metadata: HashMap<String, String>,
58}
59
60impl Position {
61 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 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 pub fn apply_funding_payment(&mut self, payment: f64) {
101 self.funding_pnl += payment;
102 }
103
104 pub fn total_pnl(&self) -> f64 {
106 self.realized_pnl + self.unrealized_pnl + self.funding_pnl
107 }
108
109 pub fn notional_value(&self) -> f64 {
111 self.size.abs() * self.current_price
112 }
113
114 pub fn is_long(&self) -> bool {
116 self.size > 0.0
117 }
118
119 pub fn is_short(&self) -> bool {
121 self.size < 0.0
122 }
123
124 pub fn is_flat(&self) -> bool {
126 self.size == 0.0
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
132pub enum OrderSide {
133 Buy,
135
136 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
151pub enum OrderType {
152 Market,
154
155 Limit,
157
158 StopMarket,
160
161 StopLimit,
163
164 TakeProfitMarket,
166
167 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
186pub enum TimeInForce {
187 GoodTillCancel,
189
190 ImmediateOrCancel,
192
193 FillOrKill,
195
196 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#[derive(Debug, Clone)]
213pub struct OrderRequest {
214 pub symbol: String,
216
217 pub side: OrderSide,
219
220 pub order_type: OrderType,
222
223 pub quantity: f64,
225
226 pub price: Option<f64>,
228
229 pub reduce_only: bool,
231
232 pub time_in_force: TimeInForce,
234
235 pub stop_price: Option<f64>,
237
238 pub client_order_id: Option<String>,
240
241 pub parameters: HashMap<String, String>,
243}
244
245impl OrderRequest {
246 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 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 pub fn reduce_only(mut self) -> Self {
280 self.reduce_only = true;
281 self
282 }
283
284 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 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 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 pub fn validate(&self) -> Result<(), String> {
304 if self.quantity <= 0.0 {
306 return Err("Order quantity must be positive".to_string());
307 }
308
309 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
333pub enum OrderStatus {
334 Created,
336
337 Submitted,
339
340 PartiallyFilled,
342
343 Filled,
345
346 Cancelled,
348
349 Rejected,
351
352 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#[derive(Debug, Clone)]
372pub struct OrderResult {
373 pub order_id: String,
375
376 pub symbol: String,
378
379 pub side: OrderSide,
381
382 pub order_type: OrderType,
384
385 pub requested_quantity: f64,
387
388 pub filled_quantity: f64,
390
391 pub average_price: Option<f64>,
393
394 pub status: OrderStatus,
396
397 pub timestamp: DateTime<FixedOffset>,
399
400 pub fees: Option<f64>,
402
403 pub error: Option<String>,
405
406 pub client_order_id: Option<String>,
408
409 pub metadata: HashMap<String, String>,
411}
412
413impl OrderResult {
414 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 pub fn is_active(&self) -> bool {
442 matches!(self.status, OrderStatus::Created | OrderStatus::Submitted | OrderStatus::PartiallyFilled)
443 }
444
445 pub fn is_complete(&self) -> bool {
447 matches!(self.status, OrderStatus::Filled | OrderStatus::Cancelled | OrderStatus::Rejected | OrderStatus::Expired)
448 }
449
450 pub fn is_filled(&self) -> bool {
452 matches!(self.status, OrderStatus::PartiallyFilled | OrderStatus::Filled)
453 }
454
455 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 pub fn filled_notional(&self) -> Option<f64> {
466 self.average_price.map(|price| self.filled_quantity * price)
467 }
468}
469
470#[derive(Debug, Clone)]
472pub struct MarketData {
473 pub symbol: String,
475
476 pub price: f64,
478
479 pub bid: f64,
481
482 pub ask: f64,
484
485 pub volume: f64,
487
488 pub timestamp: DateTime<FixedOffset>,
490
491 pub funding_rate: Option<f64>,
493
494 pub next_funding_time: Option<DateTime<FixedOffset>>,
496
497 pub open_interest: Option<f64>,
499
500 pub depth: Option<OrderBookSnapshot>,
502
503 pub recent_trades: Option<Vec<Trade>>,
505
506 pub price_change_24h_pct: Option<f64>,
508
509 pub high_24h: Option<f64>,
511
512 pub low_24h: Option<f64>,
514
515 pub metadata: HashMap<String, String>,
517}
518
519impl MarketData {
520 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 pub fn mid_price(&self) -> f64 {
550 (self.bid + self.ask) / 2.0
551 }
552
553 pub fn spread(&self) -> f64 {
555 self.ask - self.bid
556 }
557
558 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 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 pub fn with_open_interest(mut self, open_interest: f64) -> Self {
581 self.open_interest = Some(open_interest);
582 self
583 }
584
585 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 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#[derive(Debug, Clone)]
607pub struct OrderBookLevel {
608 pub price: f64,
610
611 pub quantity: f64,
613}
614
615#[derive(Debug, Clone)]
617pub struct OrderBookSnapshot {
618 pub bids: Vec<OrderBookLevel>,
620
621 pub asks: Vec<OrderBookLevel>,
623
624 pub timestamp: DateTime<FixedOffset>,
626}
627
628#[derive(Debug, Clone)]
630pub struct Trade {
631 pub id: String,
633
634 pub price: f64,
636
637 pub quantity: f64,
639
640 pub timestamp: DateTime<FixedOffset>,
642
643 pub side: Option<OrderSide>,
645}
646
647#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649pub enum SignalDirection {
650 Buy,
652
653 Sell,
655
656 Neutral,
658
659 Close,
661}
662
663#[derive(Debug, Clone)]
665pub struct Signal {
666 pub symbol: String,
668
669 pub direction: SignalDirection,
671
672 pub strength: f64,
674
675 pub timestamp: DateTime<FixedOffset>,
677
678 pub metadata: HashMap<String, String>,
680}
681
682pub trait TradingStrategy: Send + Sync {
684 fn name(&self) -> &str;
686
687 fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String>;
689
690 fn on_order_fill(&mut self, fill: &OrderResult) -> Result<(), String>;
692
693 fn on_funding_payment(&mut self, payment: &FundingPayment) -> Result<(), String>;
695
696 fn get_current_signals(&self) -> HashMap<String, Signal>;
698}
699
700#[derive(Debug, Clone)]
702pub struct TradingConfig {
703 pub initial_balance: f64,
705
706 pub risk_config: Option<RiskConfig>,
708
709 pub slippage_config: Option<SlippageConfig>,
711
712 pub api_config: Option<ApiConfig>,
714
715 pub parameters: HashMap<String, String>,
717}
718
719#[derive(Debug, Clone)]
721pub struct RiskConfig {
722 pub max_position_size_pct: f64,
724
725 pub max_daily_loss_pct: f64,
727
728 pub stop_loss_pct: f64,
730
731 pub take_profit_pct: f64,
733
734 pub max_leverage: f64,
736
737 pub max_positions: usize,
739
740 pub max_drawdown_pct: f64,
742
743 pub use_trailing_stop: bool,
745
746 pub trailing_stop_distance_pct: Option<f64>,
748}
749
750#[derive(Debug, Clone)]
752pub struct SlippageConfig {
753 pub base_slippage_pct: f64,
755
756 pub volume_impact_factor: f64,
758
759 pub volatility_impact_factor: f64,
761
762 pub random_slippage_max_pct: f64,
764
765 pub simulated_latency_ms: u64,
767
768 pub use_order_book: bool,
770
771 pub max_slippage_pct: f64,
773}
774
775#[derive(Debug, Clone)]
777pub struct ApiConfig {
778 pub api_key: String,
780
781 pub api_secret: String,
783
784 pub endpoint: String,
786
787 pub use_testnet: bool,
789
790 pub timeout_ms: u64,
792
793 pub rate_limit: Option<f64>,
795
796 pub retry_attempts: u32,
798
799 pub retry_delay_ms: u64,
801}