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)]
497mod tests {
498 use rstest::rstest;
499
500 use super::*;
501 use crate::common::{enums::CoinbaseIntxTradingState, testing::load_test_json};
502
503 #[rstest]
504 fn test_parse_asset_model() {
505 let json_data = load_test_json("http_get_assets_BTC.json");
506 let parsed: CoinbaseIntxAsset = serde_json::from_str(&json_data).unwrap();
507
508 assert_eq!(parsed.asset_id, "118059611751202816");
509 assert_eq!(parsed.asset_uuid, "5b71fc48-3dd3-540c-809b-f8c94d0e68b5");
510 assert_eq!(parsed.asset_name, "BTC");
511 assert_eq!(parsed.status, CoinbaseIntxAssetStatus::Active);
512 assert_eq!(parsed.collateral_weight, 0.9);
513 assert_eq!(parsed.supported_networks_enabled, true);
514 assert_eq!(parsed.min_borrow_qty, Some("0".to_string()));
515 assert_eq!(parsed.max_borrow_qty, Some("0".to_string()));
516 assert_eq!(parsed.loan_collateral_requirement_multiplier, 0.0);
517 assert_eq!(parsed.account_collateral_limit, Some("0".to_string()));
518 assert_eq!(parsed.ecosystem_collateral_limit_breached, false);
519 }
520
521 #[rstest]
522 fn test_parse_spot_model() {
523 let json_data = load_test_json("http_get_instruments_BTC-USDC.json");
524 let parsed: CoinbaseIntxInstrument = serde_json::from_str(&json_data).unwrap();
525
526 assert_eq!(parsed.instrument_id, "252572044003115008");
527 assert_eq!(
528 parsed.instrument_uuid,
529 "cf8dee38-6d4e-4658-a5ff-70c19201c485"
530 );
531 assert_eq!(parsed.symbol, "BTC-USDC");
532 assert_eq!(parsed.instrument_type, CoinbaseIntxInstrumentType::Spot);
533 assert_eq!(parsed.mode, "STANDARD");
534 assert_eq!(parsed.base_asset_id, "118059611751202816");
535 assert_eq!(
536 parsed.base_asset_uuid,
537 "5b71fc48-3dd3-540c-809b-f8c94d0e68b5"
538 );
539 assert_eq!(parsed.base_asset_name, "BTC");
540 assert_eq!(parsed.quote_asset_id, "1");
541 assert_eq!(
542 parsed.quote_asset_uuid,
543 "2b92315d-eab7-5bef-84fa-089a131333f5"
544 );
545 assert_eq!(parsed.quote_asset_name, "USDC");
546 assert_eq!(parsed.base_increment, "0.00001");
547 assert_eq!(parsed.quote_increment, "0.01");
548 assert_eq!(parsed.price_band_percent, 0.02);
549 assert_eq!(parsed.market_order_percent, 0.0075);
550 assert_eq!(parsed.qty_24hr, "0");
551 assert_eq!(parsed.notional_24hr, "0");
552 assert_eq!(parsed.avg_daily_qty, "1241.5042833333332");
553 assert_eq!(parsed.avg_daily_notional, "125201028.9956107");
554 assert_eq!(parsed.avg_30day_notional, "3756030869.868321");
555 assert_eq!(parsed.avg_30day_qty, "37245.1285");
556 assert_eq!(parsed.previous_day_qty, "0");
557 assert_eq!(parsed.open_interest, "0");
558 assert_eq!(parsed.position_limit_qty, "0");
559 assert_eq!(parsed.position_limit_adq_pct, 0.0);
560 assert_eq!(parsed.position_notional_limit.as_ref().unwrap(), "5000000");
561 assert_eq!(
562 parsed.open_interest_notional_limit.as_ref().unwrap(),
563 "26000000"
564 );
565 assert_eq!(parsed.replacement_cost, "0");
566 assert_eq!(parsed.base_imf, 1.0);
567 assert_eq!(parsed.min_notional_value, "10");
568 assert_eq!(parsed.funding_interval, "0");
569 assert_eq!(parsed.trading_state, CoinbaseIntxTradingState::Trading);
570 assert_eq!(parsed.default_imf.unwrap(), 1.0);
571 assert_eq!(parsed.base_asset_multiplier, "1.0");
572 assert_eq!(parsed.underlying_type, CoinbaseIntxInstrumentType::Spot);
573
574 assert_eq!(parsed.quote.best_bid_size.as_ref().unwrap(), "0");
576 assert_eq!(parsed.quote.best_ask_size.as_ref().unwrap(), "0");
577 assert_eq!(parsed.quote.trade_price, Some("101761.64".to_string()));
578 assert_eq!(parsed.quote.trade_qty, Some("3".to_string()));
579 assert_eq!(parsed.quote.index_price.as_ref().unwrap(), "97728.02");
580 assert_eq!(parsed.quote.mark_price, "101761.64");
581 assert_eq!(parsed.quote.settlement_price, "101761.64");
582 assert_eq!(parsed.quote.limit_up.as_ref().unwrap(), "102614.41");
583 assert_eq!(parsed.quote.limit_down.as_ref().unwrap(), "92841.61");
584 assert_eq!(
585 parsed.quote.timestamp.to_rfc3339(),
586 "2025-02-05T06:40:23.040+00:00"
587 );
588 }
589
590 #[rstest]
591 fn test_parse_perp_model() {
592 let json_data = load_test_json("http_get_instruments_BTC-PERP.json");
593 let parsed: CoinbaseIntxInstrument = serde_json::from_str(&json_data).unwrap();
594
595 assert_eq!(parsed.instrument_id, "149264167780483072");
596 assert_eq!(
597 parsed.instrument_uuid,
598 "b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0"
599 );
600 assert_eq!(parsed.symbol, "BTC-PERP");
601 assert_eq!(parsed.instrument_type, CoinbaseIntxInstrumentType::Perp);
602 assert_eq!(parsed.mode, "STANDARD");
603 assert_eq!(parsed.base_asset_id, "118059611751202816");
604 assert_eq!(
605 parsed.base_asset_uuid,
606 "5b71fc48-3dd3-540c-809b-f8c94d0e68b5"
607 );
608 assert_eq!(parsed.base_asset_name, "BTC");
609 assert_eq!(parsed.quote_asset_id, "1");
610 assert_eq!(
611 parsed.quote_asset_uuid,
612 "2b92315d-eab7-5bef-84fa-089a131333f5"
613 );
614 assert_eq!(parsed.quote_asset_name, "USDC");
615 assert_eq!(parsed.base_increment, "0.0001");
616 assert_eq!(parsed.quote_increment, "0.1");
617 assert_eq!(parsed.price_band_percent, 0.05);
618 assert_eq!(parsed.market_order_percent, 0.01);
619 assert_eq!(parsed.qty_24hr, "0.0051");
620 assert_eq!(parsed.notional_24hr, "499.3577");
621 assert_eq!(parsed.avg_daily_qty, "2362.797683333333");
622 assert_eq!(parsed.avg_daily_notional, "237951057.95349997");
623 assert_eq!(parsed.avg_30day_notional, "7138531738.605");
624 assert_eq!(parsed.avg_30day_qty, "70883.9305");
625 assert_eq!(parsed.previous_day_qty, "0.0116");
626 assert_eq!(parsed.open_interest, "899.6503");
627 assert_eq!(parsed.position_limit_qty, "2362.7977");
628 assert_eq!(parsed.position_limit_adq_pct, 1.0);
629 assert_eq!(
630 parsed.position_notional_limit.as_ref().unwrap(),
631 "120000000"
632 );
633 assert_eq!(
634 parsed.open_interest_notional_limit.as_ref().unwrap(),
635 "300000000"
636 );
637 assert_eq!(parsed.replacement_cost, "0.19");
638 assert_eq!(parsed.base_imf, 0.1);
639 assert_eq!(parsed.min_notional_value, "10");
640 assert_eq!(parsed.funding_interval, "3600000000000");
641 assert_eq!(parsed.trading_state, CoinbaseIntxTradingState::Trading);
642 assert_eq!(parsed.default_imf.unwrap(), 0.2);
643 assert_eq!(parsed.base_asset_multiplier, "1.0");
644 assert_eq!(parsed.underlying_type, CoinbaseIntxInstrumentType::Spot);
645
646 assert_eq!(parsed.quote.best_bid_price.as_ref().unwrap(), "96785.5");
647 assert_eq!(parsed.quote.best_bid_size.as_ref().unwrap(), "0.0005");
648 assert_eq!(parsed.quote.best_ask_size.as_ref().unwrap(), "0");
649 assert_eq!(parsed.quote.trade_price, Some("97908.8".to_string()));
650 assert_eq!(parsed.quote.trade_qty, Some("0.0005".to_string()));
651 assert_eq!(parsed.quote.index_price.as_ref().unwrap(), "97743.1");
652 assert_eq!(parsed.quote.mark_price, "97908.8");
653 assert_eq!(parsed.quote.settlement_price, "97908.8");
654 assert_eq!(parsed.quote.limit_up.as_ref().unwrap(), "107517.3");
655 assert_eq!(parsed.quote.limit_down.as_ref().unwrap(), "87968.7");
656 assert_eq!(
657 parsed.quote.predicted_funding.as_ref().unwrap(),
658 "-0.000044"
659 );
660 assert_eq!(
661 parsed.quote.timestamp.to_rfc3339(),
662 "2025-02-05T06:40:42.399+00:00"
663 );
664 }
665
666 #[rstest]
667 fn test_parse_fee_rate_tiers() {
668 let json_data = load_test_json("http_get_fee-rate-tiers.json");
669 let parsed: Vec<CoinbaseIntxFeeTier> = serde_json::from_str(&json_data).unwrap();
670
671 assert_eq!(parsed.len(), 2);
672
673 let first = &parsed[0];
674 assert_eq!(first.fee_tier_type, CoinbaseIntxFeeTierType::Regular);
675 assert_eq!(first.instrument_type, "PERPETUAL_FUTURE");
676 assert_eq!(first.fee_tier_id, "1");
677 assert_eq!(first.fee_tier_name, "Public Tier 6");
678 assert_eq!(
679 first.maker_fee_rate,
680 "0.00020000000000000000958434720477185919662588275969028472900390625"
681 );
682 assert_eq!(
683 first.taker_fee_rate,
684 "0.0004000000000000000191686944095437183932517655193805694580078125"
685 );
686 assert_eq!(first.min_balance, "0");
687 assert_eq!(first.min_volume, "0");
688 assert!(!first.require_balance_and_volume);
689
690 let second = &parsed[1];
691 assert_eq!(second.fee_tier_type, CoinbaseIntxFeeTierType::Regular);
692 assert_eq!(second.instrument_type, "PERPETUAL_FUTURE");
693 assert_eq!(second.fee_tier_id, "2");
694 assert_eq!(second.fee_tier_name, "Public Tier 5");
695 assert_eq!(
696 second.maker_fee_rate,
697 "0.00016000000000000001308848862624500952733797021210193634033203125"
698 );
699 assert_eq!(
700 second.taker_fee_rate,
701 "0.0004000000000000000191686944095437183932517655193805694580078125"
702 );
703 assert_eq!(second.min_balance, "50000");
704 assert_eq!(second.min_volume, "1000000");
705 assert!(second.require_balance_and_volume);
706 }
707
708 #[rstest]
709 fn test_parse_order() {
710 let json_data = load_test_json("http_post_orders.json");
711 let parsed: CoinbaseIntxOrder = serde_json::from_str(&json_data).unwrap();
712
713 assert_eq!(parsed.order_id, "2v2ckc1g-1-0");
714 assert_eq!(
715 parsed.client_order_id,
716 "f346ca69-11b4-4e1b-ae47-85971290c771"
717 );
718 assert_eq!(parsed.side, CoinbaseIntxSide::Sell);
719 assert_eq!(parsed.instrument_id, "114jqqhr-0-0");
720 assert_eq!(
721 parsed.instrument_uuid,
722 Uuid::parse_str("e9360798-6a10-45d6-af05-67c30eb91e2d").unwrap()
723 );
724 assert_eq!(parsed.symbol, "ETH-PERP");
725 assert_eq!(parsed.portfolio_id, "3mnk39ap-1-21");
726 assert_eq!(
727 parsed.portfolio_uuid,
728 Uuid::parse_str("cc0958ad-0c7d-4445-a812-1370fe46d0d4").unwrap()
729 );
730 assert_eq!(parsed.order_type, CoinbaseIntxOrderType::Limit);
731 assert_eq!(parsed.price, Some("3000".to_string()));
732 assert_eq!(parsed.stop_price, None);
733 assert_eq!(parsed.stop_limit_price, None);
734 assert_eq!(parsed.size, "0.01");
735 assert_eq!(parsed.tif, CoinbaseIntxTimeInForce::Gtc);
736 assert_eq!(parsed.expire_time, None);
737 assert_eq!(parsed.stp_mode, CoinbaseIntxSTPMode::Both);
738 assert_eq!(parsed.event_type, CoinbaseIntxOrderEventType::New);
739 assert_eq!(parsed.event_time, None);
740 assert_eq!(parsed.submit_time, None);
741 assert_eq!(parsed.order_status, CoinbaseIntxOrderStatus::Working);
742 assert_eq!(parsed.leaves_qty, "0.01");
743 assert_eq!(parsed.exec_qty, "0");
744 assert_eq!(parsed.avg_price, Some("0".to_string()));
745 assert_eq!(parsed.fee, Some("0".to_string()));
746 assert_eq!(parsed.post_only, false);
747 assert_eq!(parsed.close_only, false);
748 assert_eq!(parsed.algo_strategy, None);
749 assert_eq!(parsed.text, None);
750 }
751
752 #[rstest]
753 fn test_parse_position() {
754 let json_data = load_test_json("http_get_portfolios_positions_ETH-PERP.json");
755 let parsed: CoinbaseIntxPosition = serde_json::from_str(&json_data).unwrap();
756
757 assert_eq!(parsed.id, "2vev82mx-1-57");
758 assert_eq!(
759 parsed.uuid,
760 Uuid::parse_str("cb1df22f-05c7-8000-8000-7102a7804039").unwrap()
761 );
762 assert_eq!(parsed.symbol, "ETH-PERP");
763 assert_eq!(parsed.instrument_id, "114jqqhr-0-0");
764 assert_eq!(
765 parsed.instrument_uuid,
766 Uuid::parse_str("e9360798-6a10-45d6-af05-67c30eb91e2d").unwrap()
767 );
768 assert_eq!(parsed.vwap, "2747.71");
769 assert_eq!(parsed.net_size, "0.01");
770 assert_eq!(parsed.buy_order_size, "0");
771 assert_eq!(parsed.sell_order_size, "0");
772 assert_eq!(parsed.im_contribution, "0.2");
773 assert_eq!(parsed.unrealized_pnl, "0.0341");
774 assert_eq!(parsed.mark_price, "2751.12");
775 assert_eq!(parsed.entry_vwap, "2749.61");
776 }
777}