barter_data/exchange/coinbase/
trade.rs1use super::CoinbaseChannel;
2use crate::{
3 Identifier,
4 event::{MarketEvent, MarketIter},
5 exchange::ExchangeSub,
6 subscription::trade::PublicTrade,
7};
8use barter_instrument::{Side, exchange::ExchangeId};
9use barter_integration::subscription::SubscriptionId;
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
33pub struct CoinbaseTrade {
34 #[serde(alias = "product_id", deserialize_with = "de_trade_subscription_id")]
35 pub subscription_id: SubscriptionId,
36 #[serde(alias = "trade_id")]
37 pub id: u64,
38 pub time: DateTime<Utc>,
39 #[serde(
40 alias = "size",
41 deserialize_with = "barter_integration::serde::de::de_str"
42 )]
43 pub amount: f64,
44 #[serde(deserialize_with = "barter_integration::serde::de::de_str")]
45 pub price: f64,
46 pub side: Side,
47}
48
49impl Identifier<Option<SubscriptionId>> for CoinbaseTrade {
50 fn id(&self) -> Option<SubscriptionId> {
51 Some(self.subscription_id.clone())
52 }
53}
54
55impl<InstrumentKey> From<(ExchangeId, InstrumentKey, CoinbaseTrade)>
56 for MarketIter<InstrumentKey, PublicTrade>
57{
58 fn from((exchange_id, instrument, trade): (ExchangeId, InstrumentKey, CoinbaseTrade)) -> Self {
59 Self(vec![Ok(MarketEvent {
60 time_exchange: trade.time,
61 time_received: Utc::now(),
62 exchange: exchange_id,
63 instrument,
64 kind: PublicTrade {
65 id: trade.id.to_string(),
66 price: trade.price,
67 amount: trade.amount,
68 side: trade.side,
69 },
70 })])
71 }
72}
73
74pub fn de_trade_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
77where
78 D: serde::de::Deserializer<'de>,
79{
80 <&str as Deserialize>::deserialize(deserializer)
81 .map(|product_id| ExchangeSub::from((CoinbaseChannel::TRADES, product_id)).id())
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use barter_integration::error::SocketError;
88 use chrono::NaiveDateTime;
89 use serde::de::Error;
90 use std::str::FromStr;
91
92 #[test]
93 fn test_de_coinbase_trade() {
94 struct TestCase {
95 input: &'static str,
96 expected: Result<CoinbaseTrade, SocketError>,
97 }
98
99 let cases = vec![
100 TestCase {
101 input: r#"{"type": "unknown", "sequence": 50,"product_id": "BTC-USD"}"#,
103 expected: Err(SocketError::Deserialise {
104 error: serde_json::Error::custom(""),
105 payload: "".to_owned(),
106 }),
107 },
108 TestCase {
109 input: r#"
111 {
112 "type": "match","trade_id": 10,"sequence": 50,
113 "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
114 "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1",
115 "time": "2014-11-07T08:19:27.028459Z",
116 "product_id": "BTC-USD", "size": "5.23512", "price": "400.23", "side": "sell"
117 }"#,
118 expected: Ok(CoinbaseTrade {
119 subscription_id: SubscriptionId::from("matches|BTC-USD"),
120 id: 10,
121 price: 400.23,
122 amount: 5.23512,
123 side: Side::Sell,
124 time: NaiveDateTime::from_str("2014-11-07T08:19:27.028459")
125 .unwrap()
126 .and_utc(),
127 }),
128 },
129 ];
130
131 for (index, test) in cases.into_iter().enumerate() {
132 let actual = serde_json::from_str::<CoinbaseTrade>(test.input);
133 match (actual, test.expected) {
134 (Ok(actual), Ok(expected)) => {
135 assert_eq!(actual, expected, "TC{} failed", index)
136 }
137 (Err(_), Err(_)) => {
138 }
140 (actual, expected) => {
141 panic!(
143 "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
144 );
145 }
146 }
147 }
148 }
149}