1use std::collections::HashMap;
8use chrono::{DateTime, FixedOffset};
9
10#[derive(Debug, Clone)]
12pub struct Position {
13 pub symbol: String,
15
16 pub size: f64,
18
19 pub entry_price: f64,
21
22 pub current_price: f64,
24
25 pub unrealized_pnl: f64,
27
28 pub realized_pnl: f64,
30
31 pub funding_pnl: f64,
33
34 pub timestamp: DateTime<FixedOffset>,
36
37 pub leverage: f64,
39
40 pub liquidation_price: Option<f64>,
42
43 pub margin: Option<f64>,
45
46 pub metadata: HashMap<String, String>,
48}
49
50impl Position {
51 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 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 pub fn apply_funding_payment(&mut self, payment: f64) {
91 self.funding_pnl += payment;
92 }
93
94 pub fn total_pnl(&self) -> f64 {
96 self.realized_pnl + self.unrealized_pnl + self.funding_pnl
97 }
98
99 pub fn notional_value(&self) -> f64 {
101 self.size.abs() * self.current_price
102 }
103
104 pub fn is_long(&self) -> bool {
106 self.size > 0.0
107 }
108
109 pub fn is_short(&self) -> bool {
111 self.size < 0.0
112 }
113
114 pub fn is_flat(&self) -> bool {
116 self.size == 0.0
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum OrderSide {
123 Buy,
125
126 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum OrderType {
142 Market,
144
145 Limit,
147
148 StopMarket,
150
151 StopLimit,
153
154 TakeProfitMarket,
156
157 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum TimeInForce {
177 GoodTillCancel,
179
180 ImmediateOrCancel,
182
183 FillOrKill,
185
186 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#[derive(Debug, Clone)]
203pub struct OrderRequest {
204 pub symbol: String,
206
207 pub side: OrderSide,
209
210 pub order_type: OrderType,
212
213 pub quantity: f64,
215
216 pub price: Option<f64>,
218
219 pub reduce_only: bool,
221
222 pub time_in_force: TimeInForce,
224
225 pub stop_price: Option<f64>,
227
228 pub client_order_id: Option<String>,
230
231 pub parameters: HashMap<String, String>,
233}
234
235impl OrderRequest {
236 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 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 pub fn reduce_only(mut self) -> Self {
270 self.reduce_only = true;
271 self
272 }
273
274 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 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 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 pub fn validate(&self) -> Result<(), String> {
294 if self.quantity <= 0.0 {
296 return Err("Order quantity must be positive".to_string());
297 }
298
299 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub enum OrderStatus {
324 Created,
326
327 Submitted,
329
330 PartiallyFilled,
332
333 Filled,
335
336 Cancelled,
338
339 Rejected,
341
342 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#[derive(Debug, Clone)]
362pub struct OrderResult {
363 pub order_id: String,
365
366 pub symbol: String,
368
369 pub side: OrderSide,
371
372 pub order_type: OrderType,
374
375 pub requested_quantity: f64,
377
378 pub filled_quantity: f64,
380
381 pub average_price: Option<f64>,
383
384 pub status: OrderStatus,
386
387 pub timestamp: DateTime<FixedOffset>,
389
390 pub fees: Option<f64>,
392
393 pub error: Option<String>,
395
396 pub client_order_id: Option<String>,
398
399 pub metadata: HashMap<String, String>,
401}
402
403impl OrderResult {
404 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 pub fn is_active(&self) -> bool {
432 matches!(self.status, OrderStatus::Created | OrderStatus::Submitted | OrderStatus::PartiallyFilled)
433 }
434
435 pub fn is_complete(&self) -> bool {
437 matches!(self.status, OrderStatus::Filled | OrderStatus::Cancelled | OrderStatus::Rejected | OrderStatus::Expired)
438 }
439
440 pub fn is_filled(&self) -> bool {
442 matches!(self.status, OrderStatus::PartiallyFilled | OrderStatus::Filled)
443 }
444
445 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 pub fn filled_notional(&self) -> Option<f64> {
456 self.average_price.map(|price| self.filled_quantity * price)
457 }
458}
459
460#[derive(Debug, Clone)]
462pub struct MarketData {
463 pub symbol: String,
465
466 pub price: f64,
468
469 pub bid: f64,
471
472 pub ask: f64,
474
475 pub volume: f64,
477
478 pub timestamp: DateTime<FixedOffset>,
480
481 pub funding_rate: Option<f64>,
483
484 pub next_funding_time: Option<DateTime<FixedOffset>>,
486
487 pub open_interest: Option<f64>,
489
490 pub depth: Option<OrderBookSnapshot>,
492
493 pub recent_trades: Option<Vec<Trade>>,
495
496 pub price_change_24h_pct: Option<f64>,
498
499 pub high_24h: Option<f64>,
501
502 pub low_24h: Option<f64>,
504
505 pub metadata: HashMap<String, String>,
507}
508
509impl MarketData {
510 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 pub fn mid_price(&self) -> f64 {
540 (self.bid + self.ask) / 2.0
541 }
542
543 pub fn spread(&self) -> f64 {
545 self.ask - self.bid
546 }
547
548 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 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 pub fn with_open_interest(mut self, open_interest: f64) -> Self {
571 self.open_interest = Some(open_interest);
572 self
573 }
574
575 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 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#[derive(Debug, Clone)]
597pub struct OrderBookLevel {
598 pub price: f64,
600
601 pub quantity: f64,
603}
604
605#[derive(Debug, Clone)]
607pub struct OrderBookSnapshot {
608 pub bids: Vec<OrderBookLevel>,
610
611 pub asks: Vec<OrderBookLevel>,
613
614 pub timestamp: DateTime<FixedOffset>,
616}
617
618#[derive(Debug, Clone)]
620pub struct Trade {
621 pub id: String,
623
624 pub price: f64,
626
627 pub quantity: f64,
629
630 pub timestamp: DateTime<FixedOffset>,
632
633 pub side: Option<OrderSide>,
635}
636
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
639pub enum SignalDirection {
640 Buy,
642
643 Sell,
645
646 Neutral,
648
649 Close,
651}
652
653#[derive(Debug, Clone)]
655pub struct Signal {
656 pub symbol: String,
658
659 pub direction: SignalDirection,
661
662 pub strength: f64,
664
665 pub timestamp: DateTime<FixedOffset>,
667
668 pub metadata: HashMap<String, String>,
670}
671
672pub trait TradingStrategy: Send + Sync {
674 fn name(&self) -> &str;
676
677 fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String>;
679
680 fn on_order_fill(&mut self, fill: &OrderResult) -> Result<(), String>;
682
683 fn on_funding_payment(&mut self, payment: &FundingPayment) -> Result<(), String>;
685
686 fn get_current_signals(&self) -> HashMap<String, Signal>;
688}
689
690#[derive(Debug, Clone)]
692pub struct FundingPayment {
693 pub symbol: String,
695
696 pub rate: f64,
698
699 pub position_size: f64,
701
702 pub amount: f64,
704
705 pub timestamp: DateTime<FixedOffset>,
707}
708
709#[derive(Debug, Clone)]
711pub struct TradingConfig {
712 pub initial_balance: f64,
714
715 pub risk_config: Option<RiskConfig>,
717
718 pub slippage_config: Option<SlippageConfig>,
720
721 pub api_config: Option<ApiConfig>,
723
724 pub parameters: HashMap<String, String>,
726}
727
728#[derive(Debug, Clone)]
730pub struct RiskConfig {
731 pub max_position_size_pct: f64,
733
734 pub max_daily_loss_pct: f64,
736
737 pub stop_loss_pct: f64,
739
740 pub take_profit_pct: f64,
742
743 pub max_leverage: f64,
745
746 pub max_positions: usize,
748
749 pub max_drawdown_pct: f64,
751
752 pub use_trailing_stop: bool,
754
755 pub trailing_stop_distance_pct: Option<f64>,
757}
758
759#[derive(Debug, Clone)]
761pub struct SlippageConfig {
762 pub base_slippage_pct: f64,
764
765 pub volume_impact_factor: f64,
767
768 pub volatility_impact_factor: f64,
770
771 pub random_slippage_max_pct: f64,
773
774 pub simulated_latency_ms: u64,
776
777 pub use_order_book: bool,
779
780 pub max_slippage_pct: f64,
782}
783
784#[derive(Debug, Clone)]
786pub struct ApiConfig {
787 pub api_key: String,
789
790 pub api_secret: String,
792
793 pub endpoint: String,
795
796 pub use_testnet: bool,
798
799 pub timeout_ms: u64,
801
802 pub rate_limit: Option<f64>,
804
805 pub retry_attempts: u32,
807
808 pub retry_delay_ms: u64,
810}