1use chrono::{DateTime, Utc};
17use serde::{Deserialize, Deserializer, Serialize};
18use ustr::Ustr;
19use uuid::Uuid;
20
21use crate::common::enums::{
22 CoinbaseIntxAlgoStrategy, CoinbaseIntxAssetStatus, CoinbaseIntxFeeTierType,
23 CoinbaseIntxInstrumentType, CoinbaseIntxOrderEventType, CoinbaseIntxOrderStatus,
24 CoinbaseIntxOrderType, CoinbaseIntxSTPMode, CoinbaseIntxSide, CoinbaseIntxTimeInForce,
25 CoinbaseIntxTradingState,
26};
27
28fn empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
29where
30 D: Deserializer<'de>,
31{
32 let s = String::deserialize(deserializer)?;
33 if s.is_empty() { Ok(None) } else { Ok(Some(s)) }
34}
35
36fn deserialize_optional_datetime<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
37where
38 D: Deserializer<'de>,
39{
40 let s: Option<String> = Option::deserialize(deserializer)?;
41 match s {
42 Some(ref s) if s.is_empty() => Ok(None),
43 Some(s) => DateTime::parse_from_rfc3339(&s)
44 .map(|dt| Some(dt.with_timezone(&Utc)))
45 .map_err(serde::de::Error::custom),
46 None => Ok(None),
47 }
48}
49
50#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct CoinbaseIntxAsset {
53 pub asset_id: String,
55 pub asset_uuid: String,
57 pub asset_name: String,
59 pub status: CoinbaseIntxAssetStatus,
61 pub collateral_weight: f64,
63 pub supported_networks_enabled: bool,
65 pub min_borrow_qty: Option<String>,
67 pub max_borrow_qty: Option<String>,
69 pub loan_collateral_requirement_multiplier: f64,
71 pub account_collateral_limit: Option<String>,
73 pub ecosystem_collateral_limit_breached: bool,
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
79pub struct CoinbaseIntxInstrument {
80 pub instrument_id: String,
82 pub instrument_uuid: String,
84 pub symbol: Ustr,
86 #[serde(rename = "type")]
88 pub instrument_type: CoinbaseIntxInstrumentType,
89 pub mode: String,
91 pub base_asset_id: String,
93 pub base_asset_uuid: String,
95 pub base_asset_name: String,
97 pub quote_asset_id: String,
99 pub quote_asset_uuid: String,
101 pub quote_asset_name: String,
103 pub base_increment: String,
105 pub quote_increment: String,
107 pub price_band_percent: f64,
109 pub market_order_percent: f64,
111 pub qty_24hr: String,
113 pub notional_24hr: String,
115 pub avg_daily_qty: String,
117 pub avg_daily_notional: String,
119 pub avg_30day_notional: String,
121 pub avg_30day_qty: String,
123 pub previous_day_qty: String,
125 pub open_interest: String,
127 pub position_limit_qty: String,
129 pub position_limit_adq_pct: f64,
131 pub position_notional_limit: Option<String>,
133 pub open_interest_notional_limit: Option<String>,
135 pub replacement_cost: String,
137 pub base_imf: f64,
139 pub min_notional_value: String,
141 pub funding_interval: String,
143 pub trading_state: CoinbaseIntxTradingState,
145 pub quote: CoinbaseIntxInstrumentQuote,
147 pub default_imf: Option<f64>,
149 pub base_asset_multiplier: String,
151 pub underlying_type: CoinbaseIntxInstrumentType,
153}
154
155#[derive(Clone, Debug, Serialize, Deserialize)]
157pub struct CoinbaseIntxInstrumentQuote {
158 #[serde(default)]
160 pub best_bid_price: Option<String>,
161 #[serde(default)]
163 pub best_bid_size: Option<String>,
164 #[serde(default)]
166 pub best_ask_price: Option<String>,
167 #[serde(default)]
169 pub best_ask_size: Option<String>,
170 #[serde(default)]
172 pub trade_price: Option<String>,
173 #[serde(default)]
175 pub trade_qty: Option<String>,
176 pub index_price: Option<String>,
178 pub mark_price: String,
180 pub settlement_price: String,
182 pub limit_up: Option<String>,
184 pub limit_down: Option<String>,
186 #[serde(default)]
188 pub predicted_funding: Option<String>,
189 pub timestamp: DateTime<Utc>,
191}
192
193#[derive(Clone, Debug, Serialize, Deserialize)]
195pub struct CoinbaseIntxFeeTier {
196 pub fee_tier_type: CoinbaseIntxFeeTierType,
198 pub instrument_type: String, pub fee_tier_id: String,
202 pub fee_tier_name: String,
204 pub maker_fee_rate: String,
206 pub taker_fee_rate: String,
208 pub min_balance: String,
210 pub min_volume: String,
212 pub require_balance_and_volume: bool,
214}
215
216#[derive(Clone, Debug, Serialize, Deserialize)]
218pub struct CoinbaseIntxPortfolioFeeRates {
219 pub instrument_type: String, pub fee_tier_id: String,
223 pub fee_tier_name: String,
225 pub maker_fee_rate: String,
227 pub taker_fee_rate: String,
229 pub is_vip_tier: bool,
231 pub is_override: bool,
233 #[serde(default)]
235 pub trailing_30day_volume: Option<String>,
236 pub trailing_24hr_usdc_balance: String,
238}
239
240#[derive(Clone, Debug, Serialize, Deserialize)]
242pub struct CoinbaseIntxPortfolio {
243 pub portfolio_id: String,
245 pub portfolio_uuid: Uuid,
247 pub name: String,
249 pub user_uuid: Uuid,
251 pub maker_fee_rate: String,
253 pub taker_fee_rate: String,
255 pub trading_lock: bool,
257 pub borrow_disabled: bool,
259 pub is_lsp: bool,
261 pub is_default: bool,
263 pub cross_collateral_enabled: bool,
265 pub pre_launch_trading_enabled: bool,
267}
268
269#[derive(Clone, Debug, Serialize, Deserialize)]
270pub struct CoinbaseIntxPortfolioDetails {
271 pub summary: CoinbaseIntxPortfolioSummary,
272 pub balances: Vec<CoinbaseIntxBalance>,
273 pub positions: Vec<CoinbaseIntxPosition>,
274}
275
276#[derive(Clone, Debug, Serialize, Deserialize)]
277pub struct CoinbaseIntxPortfolioSummary {
278 pub collateral: String,
279 pub unrealized_pnl: String,
280 pub unrealized_pnl_percent: String,
281 pub position_notional: String,
282 pub balance: String,
283 pub buying_power: String,
284 pub portfolio_initial_margin: f64,
285 pub portfolio_maintenance_margin: f64,
286 pub in_liquidation: bool,
287}
288
289#[derive(Clone, Debug, Serialize, Deserialize)]
290pub struct CoinbaseIntxBalance {
291 pub asset_id: String,
292 pub asset_name: String,
293 pub quantity: String,
294 pub hold: String,
295 pub collateral_value: String,
296 pub max_withdraw_amount: String,
297}
298
299#[derive(Clone, Debug, Serialize, Deserialize)]
301pub struct CoinbaseIntxOrderList {
302 pub pagination: OrderListPagination,
304 pub results: Vec<CoinbaseIntxOrder>,
306}
307
308#[derive(Clone, Debug, Serialize, Deserialize)]
310pub struct OrderListPagination {
311 pub ref_datetime: Option<DateTime<Utc>>,
313 pub result_limit: u32,
315 pub result_offset: u32,
317}
318
319#[derive(Clone, Debug, Serialize, Deserialize)]
320pub struct CoinbaseIntxOrder {
321 pub order_id: Ustr,
323 pub client_order_id: Ustr,
325 pub side: CoinbaseIntxSide,
327 pub instrument_id: Ustr,
329 pub instrument_uuid: Uuid,
331 pub symbol: Ustr,
333 pub portfolio_id: Ustr,
335 pub portfolio_uuid: Uuid,
337 #[serde(rename = "type")]
339 pub order_type: CoinbaseIntxOrderType,
340 pub price: Option<String>,
342 pub stop_price: Option<String>,
344 pub stop_limit_price: Option<String>,
346 pub size: String,
348 pub tif: CoinbaseIntxTimeInForce,
350 pub expire_time: Option<DateTime<Utc>>,
352 pub stp_mode: CoinbaseIntxSTPMode,
354 pub event_type: CoinbaseIntxOrderEventType,
356 pub event_time: Option<DateTime<Utc>>,
358 pub submit_time: Option<DateTime<Utc>>,
360 pub order_status: CoinbaseIntxOrderStatus,
362 pub leaves_qty: String,
364 pub exec_qty: String,
366 pub avg_price: Option<String>,
368 pub fee: Option<String>,
370 pub post_only: bool,
372 pub close_only: bool,
374 pub algo_strategy: Option<CoinbaseIntxAlgoStrategy>,
376 pub text: Option<String>,
378}
379
380#[derive(Clone, Debug, Serialize, Deserialize)]
382pub struct CoinbaseIntxFillList {
383 pub pagination: OrderListPagination,
385 pub results: Vec<CoinbaseIntxFill>,
387}
388
389#[derive(Clone, Debug, Serialize, Deserialize)]
391pub struct CoinbaseIntxFill {
392 pub portfolio_id: Ustr,
394 pub portfolio_uuid: Uuid,
396 pub portfolio_name: String,
398 pub fill_id: String,
400 pub fill_uuid: Uuid,
402 pub exec_id: String,
404 pub order_id: Ustr,
406 pub order_uuid: Uuid,
408 pub instrument_id: Ustr,
410 pub instrument_uuid: Uuid,
412 pub symbol: Ustr,
414 pub match_id: String,
416 pub match_uuid: Uuid,
418 pub fill_price: String,
420 pub fill_qty: String,
422 pub client_id: String,
424 pub client_order_id: Ustr,
426 pub order_qty: String,
428 pub limit_price: String,
430 pub total_filled: String,
432 pub filled_vwap: String,
434 #[serde(deserialize_with = "deserialize_optional_datetime")]
436 pub expire_time: Option<DateTime<Utc>>,
437 #[serde(default)]
439 #[serde(deserialize_with = "empty_string_as_none")]
440 pub stop_price: Option<String>,
441 pub side: CoinbaseIntxSide,
443 pub tif: CoinbaseIntxTimeInForce,
445 pub stp_mode: CoinbaseIntxSTPMode,
447 pub flags: String,
449 pub fee: String,
451 pub fee_asset: String,
453 pub order_status: CoinbaseIntxOrderStatus,
455 pub event_time: DateTime<Utc>,
457 pub source: String,
459}
460
461#[derive(Clone, Debug, Serialize, Deserialize)]
463pub struct CoinbaseIntxPosition {
464 pub id: String,
466 pub uuid: Uuid,
468 pub symbol: Ustr,
470 pub instrument_id: Ustr,
472 pub instrument_uuid: Uuid,
474 pub vwap: String,
476 pub net_size: String,
478 pub buy_order_size: String,
480 pub sell_order_size: String,
482 pub im_contribution: String,
484 pub unrealized_pnl: String,
486 pub mark_price: String,
488 pub entry_vwap: String,
490}
491
492#[cfg(test)]
493mod tests {
494 use rstest::rstest;
495
496 use super::*;
497 use crate::common::{enums::CoinbaseIntxTradingState, testing::load_test_json};
498
499 #[rstest]
500 fn test_parse_asset_model() {
501 let json_data = load_test_json("http_get_assets_BTC.json");
502 let parsed: CoinbaseIntxAsset = serde_json::from_str(&json_data).unwrap();
503
504 assert_eq!(parsed.asset_id, "118059611751202816");
505 assert_eq!(parsed.asset_uuid, "5b71fc48-3dd3-540c-809b-f8c94d0e68b5");
506 assert_eq!(parsed.asset_name, "BTC");
507 assert_eq!(parsed.status, CoinbaseIntxAssetStatus::Active);
508 assert_eq!(parsed.collateral_weight, 0.9);
509 assert!(parsed.supported_networks_enabled);
510 assert_eq!(parsed.min_borrow_qty, Some("0".to_string()));
511 assert_eq!(parsed.max_borrow_qty, Some("0".to_string()));
512 assert_eq!(parsed.loan_collateral_requirement_multiplier, 0.0);
513 assert_eq!(parsed.account_collateral_limit, Some("0".to_string()));
514 assert!(!parsed.ecosystem_collateral_limit_breached);
515 }
516
517 #[rstest]
518 fn test_parse_spot_model() {
519 let json_data = load_test_json("http_get_instruments_BTC-USDC.json");
520 let parsed: CoinbaseIntxInstrument = serde_json::from_str(&json_data).unwrap();
521
522 assert_eq!(parsed.instrument_id, "252572044003115008");
523 assert_eq!(
524 parsed.instrument_uuid,
525 "cf8dee38-6d4e-4658-a5ff-70c19201c485"
526 );
527 assert_eq!(parsed.symbol, "BTC-USDC");
528 assert_eq!(parsed.instrument_type, CoinbaseIntxInstrumentType::Spot);
529 assert_eq!(parsed.mode, "STANDARD");
530 assert_eq!(parsed.base_asset_id, "118059611751202816");
531 assert_eq!(
532 parsed.base_asset_uuid,
533 "5b71fc48-3dd3-540c-809b-f8c94d0e68b5"
534 );
535 assert_eq!(parsed.base_asset_name, "BTC");
536 assert_eq!(parsed.quote_asset_id, "1");
537 assert_eq!(
538 parsed.quote_asset_uuid,
539 "2b92315d-eab7-5bef-84fa-089a131333f5"
540 );
541 assert_eq!(parsed.quote_asset_name, "USDC");
542 assert_eq!(parsed.base_increment, "0.00001");
543 assert_eq!(parsed.quote_increment, "0.01");
544 assert_eq!(parsed.price_band_percent, 0.02);
545 assert_eq!(parsed.market_order_percent, 0.0075);
546 assert_eq!(parsed.qty_24hr, "0");
547 assert_eq!(parsed.notional_24hr, "0");
548 assert_eq!(parsed.avg_daily_qty, "1241.5042833333332");
549 assert_eq!(parsed.avg_daily_notional, "125201028.9956107");
550 assert_eq!(parsed.avg_30day_notional, "3756030869.868321");
551 assert_eq!(parsed.avg_30day_qty, "37245.1285");
552 assert_eq!(parsed.previous_day_qty, "0");
553 assert_eq!(parsed.open_interest, "0");
554 assert_eq!(parsed.position_limit_qty, "0");
555 assert_eq!(parsed.position_limit_adq_pct, 0.0);
556 assert_eq!(parsed.position_notional_limit.as_ref().unwrap(), "5000000");
557 assert_eq!(
558 parsed.open_interest_notional_limit.as_ref().unwrap(),
559 "26000000"
560 );
561 assert_eq!(parsed.replacement_cost, "0");
562 assert_eq!(parsed.base_imf, 1.0);
563 assert_eq!(parsed.min_notional_value, "10");
564 assert_eq!(parsed.funding_interval, "0");
565 assert_eq!(parsed.trading_state, CoinbaseIntxTradingState::Trading);
566 assert_eq!(parsed.default_imf.unwrap(), 1.0);
567 assert_eq!(parsed.base_asset_multiplier, "1.0");
568 assert_eq!(parsed.underlying_type, CoinbaseIntxInstrumentType::Spot);
569
570 assert_eq!(parsed.quote.best_bid_size.as_ref().unwrap(), "0");
572 assert_eq!(parsed.quote.best_ask_size.as_ref().unwrap(), "0");
573 assert_eq!(parsed.quote.trade_price, Some("101761.64".to_string()));
574 assert_eq!(parsed.quote.trade_qty, Some("3".to_string()));
575 assert_eq!(parsed.quote.index_price.as_ref().unwrap(), "97728.02");
576 assert_eq!(parsed.quote.mark_price, "101761.64");
577 assert_eq!(parsed.quote.settlement_price, "101761.64");
578 assert_eq!(parsed.quote.limit_up.as_ref().unwrap(), "102614.41");
579 assert_eq!(parsed.quote.limit_down.as_ref().unwrap(), "92841.61");
580 assert_eq!(
581 parsed.quote.timestamp.to_rfc3339(),
582 "2025-02-05T06:40:23.040+00:00"
583 );
584 }
585
586 #[rstest]
587 fn test_parse_perp_model() {
588 let json_data = load_test_json("http_get_instruments_BTC-PERP.json");
589 let parsed: CoinbaseIntxInstrument = serde_json::from_str(&json_data).unwrap();
590
591 assert_eq!(parsed.instrument_id, "149264167780483072");
592 assert_eq!(
593 parsed.instrument_uuid,
594 "b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0"
595 );
596 assert_eq!(parsed.symbol, "BTC-PERP");
597 assert_eq!(parsed.instrument_type, CoinbaseIntxInstrumentType::Perp);
598 assert_eq!(parsed.mode, "STANDARD");
599 assert_eq!(parsed.base_asset_id, "118059611751202816");
600 assert_eq!(
601 parsed.base_asset_uuid,
602 "5b71fc48-3dd3-540c-809b-f8c94d0e68b5"
603 );
604 assert_eq!(parsed.base_asset_name, "BTC");
605 assert_eq!(parsed.quote_asset_id, "1");
606 assert_eq!(
607 parsed.quote_asset_uuid,
608 "2b92315d-eab7-5bef-84fa-089a131333f5"
609 );
610 assert_eq!(parsed.quote_asset_name, "USDC");
611 assert_eq!(parsed.base_increment, "0.0001");
612 assert_eq!(parsed.quote_increment, "0.1");
613 assert_eq!(parsed.price_band_percent, 0.05);
614 assert_eq!(parsed.market_order_percent, 0.01);
615 assert_eq!(parsed.qty_24hr, "0.0051");
616 assert_eq!(parsed.notional_24hr, "499.3577");
617 assert_eq!(parsed.avg_daily_qty, "2362.797683333333");
618 assert_eq!(parsed.avg_daily_notional, "237951057.95349997");
619 assert_eq!(parsed.avg_30day_notional, "7138531738.605");
620 assert_eq!(parsed.avg_30day_qty, "70883.9305");
621 assert_eq!(parsed.previous_day_qty, "0.0116");
622 assert_eq!(parsed.open_interest, "899.6503");
623 assert_eq!(parsed.position_limit_qty, "2362.7977");
624 assert_eq!(parsed.position_limit_adq_pct, 1.0);
625 assert_eq!(
626 parsed.position_notional_limit.as_ref().unwrap(),
627 "120000000"
628 );
629 assert_eq!(
630 parsed.open_interest_notional_limit.as_ref().unwrap(),
631 "300000000"
632 );
633 assert_eq!(parsed.replacement_cost, "0.19");
634 assert_eq!(parsed.base_imf, 0.1);
635 assert_eq!(parsed.min_notional_value, "10");
636 assert_eq!(parsed.funding_interval, "3600000000000");
637 assert_eq!(parsed.trading_state, CoinbaseIntxTradingState::Trading);
638 assert_eq!(parsed.default_imf.unwrap(), 0.2);
639 assert_eq!(parsed.base_asset_multiplier, "1.0");
640 assert_eq!(parsed.underlying_type, CoinbaseIntxInstrumentType::Spot);
641
642 assert_eq!(parsed.quote.best_bid_price.as_ref().unwrap(), "96785.5");
643 assert_eq!(parsed.quote.best_bid_size.as_ref().unwrap(), "0.0005");
644 assert_eq!(parsed.quote.best_ask_size.as_ref().unwrap(), "0");
645 assert_eq!(parsed.quote.trade_price, Some("97908.8".to_string()));
646 assert_eq!(parsed.quote.trade_qty, Some("0.0005".to_string()));
647 assert_eq!(parsed.quote.index_price.as_ref().unwrap(), "97743.1");
648 assert_eq!(parsed.quote.mark_price, "97908.8");
649 assert_eq!(parsed.quote.settlement_price, "97908.8");
650 assert_eq!(parsed.quote.limit_up.as_ref().unwrap(), "107517.3");
651 assert_eq!(parsed.quote.limit_down.as_ref().unwrap(), "87968.7");
652 assert_eq!(
653 parsed.quote.predicted_funding.as_ref().unwrap(),
654 "-0.000044"
655 );
656 assert_eq!(
657 parsed.quote.timestamp.to_rfc3339(),
658 "2025-02-05T06:40:42.399+00:00"
659 );
660 }
661
662 #[rstest]
663 fn test_parse_fee_rate_tiers() {
664 let json_data = load_test_json("http_get_fee-rate-tiers.json");
665 let parsed: Vec<CoinbaseIntxFeeTier> = serde_json::from_str(&json_data).unwrap();
666
667 assert_eq!(parsed.len(), 2);
668
669 let first = &parsed[0];
670 assert_eq!(first.fee_tier_type, CoinbaseIntxFeeTierType::Regular);
671 assert_eq!(first.instrument_type, "PERPETUAL_FUTURE");
672 assert_eq!(first.fee_tier_id, "1");
673 assert_eq!(first.fee_tier_name, "Public Tier 6");
674 assert_eq!(
675 first.maker_fee_rate,
676 "0.00020000000000000000958434720477185919662588275969028472900390625"
677 );
678 assert_eq!(
679 first.taker_fee_rate,
680 "0.0004000000000000000191686944095437183932517655193805694580078125"
681 );
682 assert_eq!(first.min_balance, "0");
683 assert_eq!(first.min_volume, "0");
684 assert!(!first.require_balance_and_volume);
685
686 let second = &parsed[1];
687 assert_eq!(second.fee_tier_type, CoinbaseIntxFeeTierType::Regular);
688 assert_eq!(second.instrument_type, "PERPETUAL_FUTURE");
689 assert_eq!(second.fee_tier_id, "2");
690 assert_eq!(second.fee_tier_name, "Public Tier 5");
691 assert_eq!(
692 second.maker_fee_rate,
693 "0.00016000000000000001308848862624500952733797021210193634033203125"
694 );
695 assert_eq!(
696 second.taker_fee_rate,
697 "0.0004000000000000000191686944095437183932517655193805694580078125"
698 );
699 assert_eq!(second.min_balance, "50000");
700 assert_eq!(second.min_volume, "1000000");
701 assert!(second.require_balance_and_volume);
702 }
703
704 #[rstest]
705 fn test_parse_order() {
706 let json_data = load_test_json("http_post_orders.json");
707 let parsed: CoinbaseIntxOrder = serde_json::from_str(&json_data).unwrap();
708
709 assert_eq!(parsed.order_id, "2v2ckc1g-1-0");
710 assert_eq!(
711 parsed.client_order_id,
712 "f346ca69-11b4-4e1b-ae47-85971290c771"
713 );
714 assert_eq!(parsed.side, CoinbaseIntxSide::Sell);
715 assert_eq!(parsed.instrument_id, "114jqqhr-0-0");
716 assert_eq!(
717 parsed.instrument_uuid,
718 Uuid::parse_str("e9360798-6a10-45d6-af05-67c30eb91e2d").unwrap()
719 );
720 assert_eq!(parsed.symbol, "ETH-PERP");
721 assert_eq!(parsed.portfolio_id, "3mnk39ap-1-21");
722 assert_eq!(
723 parsed.portfolio_uuid,
724 Uuid::parse_str("cc0958ad-0c7d-4445-a812-1370fe46d0d4").unwrap()
725 );
726 assert_eq!(parsed.order_type, CoinbaseIntxOrderType::Limit);
727 assert_eq!(parsed.price, Some("3000".to_string()));
728 assert_eq!(parsed.stop_price, None);
729 assert_eq!(parsed.stop_limit_price, None);
730 assert_eq!(parsed.size, "0.01");
731 assert_eq!(parsed.tif, CoinbaseIntxTimeInForce::Gtc);
732 assert_eq!(parsed.expire_time, None);
733 assert_eq!(parsed.stp_mode, CoinbaseIntxSTPMode::Both);
734 assert_eq!(parsed.event_type, CoinbaseIntxOrderEventType::New);
735 assert_eq!(parsed.event_time, None);
736 assert_eq!(parsed.submit_time, None);
737 assert_eq!(parsed.order_status, CoinbaseIntxOrderStatus::Working);
738 assert_eq!(parsed.leaves_qty, "0.01");
739 assert_eq!(parsed.exec_qty, "0");
740 assert_eq!(parsed.avg_price, Some("0".to_string()));
741 assert_eq!(parsed.fee, Some("0".to_string()));
742 assert!(!parsed.post_only);
743 assert!(!parsed.close_only);
744 assert_eq!(parsed.algo_strategy, None);
745 assert_eq!(parsed.text, None);
746 }
747
748 #[rstest]
749 fn test_parse_position() {
750 let json_data = load_test_json("http_get_portfolios_positions_ETH-PERP.json");
751 let parsed: CoinbaseIntxPosition = serde_json::from_str(&json_data).unwrap();
752
753 assert_eq!(parsed.id, "2vev82mx-1-57");
754 assert_eq!(
755 parsed.uuid,
756 Uuid::parse_str("cb1df22f-05c7-8000-8000-7102a7804039").unwrap()
757 );
758 assert_eq!(parsed.symbol, "ETH-PERP");
759 assert_eq!(parsed.instrument_id, "114jqqhr-0-0");
760 assert_eq!(
761 parsed.instrument_uuid,
762 Uuid::parse_str("e9360798-6a10-45d6-af05-67c30eb91e2d").unwrap()
763 );
764 assert_eq!(parsed.vwap, "2747.71");
765 assert_eq!(parsed.net_size, "0.01");
766 assert_eq!(parsed.buy_order_size, "0");
767 assert_eq!(parsed.sell_order_size, "0");
768 assert_eq!(parsed.im_contribution, "0.2");
769 assert_eq!(parsed.unrealized_pnl, "0.0341");
770 assert_eq!(parsed.mark_price, "2751.12");
771 assert_eq!(parsed.entry_vwap, "2749.61");
772 }
773}