bitpanda_csv/
trade.rs

1//! # Trade
2//!
3//! This module defines the trade data type, exposed by the Bitpanda CSV
4
5use chrono::{DateTime, FixedOffset};
6use rust_decimal::Decimal;
7
8mod asset;
9mod asset_class;
10mod crypto;
11mod currency;
12mod fiat;
13mod in_out;
14mod option;
15mod transaction_type;
16
17pub use asset::{Asset, Metal};
18pub use asset_class::AssetClass;
19pub use crypto::CryptoCurrency;
20pub use currency::Currency;
21pub use fiat::Fiat;
22pub use in_out::InOut;
23pub use option::CsvOption;
24pub use transaction_type::TransactionType;
25
26/// Defines a single `Trade` made on Bitpanda exchange
27#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash)]
28pub struct Trade {
29    /// Identity uniquely a transaction on bitpanda
30    #[serde(rename = "Transaction ID")]
31    transaction_id: String,
32    /// ISO8601 timestmap of the transaction issuing time
33    #[serde(rename = "Timestamp")]
34    timestamp: DateTime<FixedOffset>,
35    /// Defines the kind of transaction on bitpanda
36    #[serde(rename = "Transaction Type")]
37    transaction_type: TransactionType,
38    /// Defines whether the trade assets were given to us or given to bitpanda
39    #[serde(rename = "In/Out")]
40    in_out: InOut,
41    /// The amount in FIAT currency of the asset
42    #[serde(rename = "Amount Fiat")]
43    amount_fiat: Decimal,
44    /// The FIAT currency which describes the trade
45    #[serde(rename = "Fiat")]
46    fiat: Fiat,
47    /// The amount of assets in a Buy/Transfer/Sell
48    #[serde(rename = "Amount Asset")]
49    amount_asset: CsvOption<Decimal>,
50    /// The asset name
51    #[serde(rename = "Asset")]
52    asset: Asset,
53    /// The price of the asset in the market. Set only for Buy/Sell
54    #[serde(rename = "Asset market price")]
55    asset_market_price: CsvOption<Decimal>,
56    /// Describes the price currency of the asset market price
57    #[serde(rename = "Asset market price currency")]
58    asset_market_price_currency: CsvOption<Fiat>,
59    /// Describes the asset kind. Mind that some cryptos are somehow tagged as Fiat (e.g. MATIC, SHIB...)
60    #[serde(rename = "Asset class")]
61    asset_class: AssetClass,
62    /// Defines uniquely the asset in the bitpanda ecosystem
63    #[serde(rename = "Product ID")]
64    product_id: CsvOption<u64>,
65    /// An amount taken by Bitpanda on a Deposit/Withdrawal operation
66    #[serde(rename = "Fee")]
67    fee: CsvOption<Decimal>,
68    /// The currency which describes the fee amount
69    #[serde(rename = "Fee asset")]
70    fee_asset: CsvOption<Currency>,
71    /// Difference between "bid price" and "ask price"
72    #[serde(rename = "Spread")]
73    spread: CsvOption<Decimal>,
74    /// The currency which describes the spread amount
75    #[serde(rename = "Spread Currency")]
76    spread_currency: CsvOption<Fiat>,
77}
78
79impl Trade {
80    pub fn transaction_id(&self) -> &str {
81        &self.transaction_id
82    }
83
84    pub fn timestamp(&self) -> DateTime<FixedOffset> {
85        self.timestamp
86    }
87
88    pub fn transaction_type(&self) -> TransactionType {
89        self.transaction_type
90    }
91
92    pub fn in_out(&self) -> InOut {
93        self.in_out
94    }
95
96    pub fn amount_fiat(&self) -> Decimal {
97        self.amount_fiat
98    }
99
100    pub fn fiat(&self) -> Fiat {
101        self.fiat
102    }
103
104    pub fn amount_asset(&self) -> Option<Decimal> {
105        self.amount_asset.into()
106    }
107
108    pub fn asset(&self) -> Asset {
109        self.asset.clone()
110    }
111
112    pub fn asset_market_price(&self) -> Option<Decimal> {
113        self.asset_market_price.into()
114    }
115
116    pub fn asset_market_price_currency(&self) -> Option<Fiat> {
117        self.asset_market_price_currency.into()
118    }
119
120    pub fn asset_class(&self) -> AssetClass {
121        self.asset_class
122    }
123
124    pub fn product_id(&self) -> Option<u64> {
125        self.product_id.into()
126    }
127
128    pub fn fee(&self) -> Option<Decimal> {
129        self.fee.into()
130    }
131
132    pub fn fee_asset(&self) -> Option<Currency> {
133        self.fee_asset.into()
134    }
135
136    pub fn spread(&self) -> Option<Decimal> {
137        self.spread.into()
138    }
139
140    pub fn spread_currency(&self) -> Option<Fiat> {
141        self.spread_currency.into()
142    }
143}
144
145#[cfg(feature = "mock")]
146impl From<crate::mock::TradeBuilder> for Trade {
147    fn from(builder: crate::mock::TradeBuilder) -> Self {
148        Self {
149            transaction_id: builder.transaction_id,
150            timestamp: builder.timestamp,
151            transaction_type: builder.transaction_type,
152            in_out: builder.in_out,
153            amount_fiat: builder.amount_fiat,
154            fiat: builder.fiat,
155            amount_asset: builder.amount_asset,
156            asset: builder.asset,
157            asset_market_price: builder.asset_market_price,
158            asset_market_price_currency: builder.asset_market_price_currency,
159            asset_class: builder.asset_class,
160            product_id: builder.product_id,
161            fee: builder.fee,
162            fee_asset: builder.fee_asset,
163            spread: builder.spread,
164            spread_currency: builder.spread_currency,
165        }
166    }
167}
168
169#[cfg(test)]
170mod test {
171
172    use super::*;
173
174    use pretty_assertions::assert_eq;
175    use rust_decimal_macros::dec;
176    use std::io::Cursor;
177
178    #[test]
179    fn should_decode_trade() {
180        let csv = r#""Transaction ID",Timestamp,"Transaction Type",In/Out,"Amount Fiat",Fiat,"Amount Asset",Asset,"Asset market price","Asset market price currency","Asset class","Product ID",Fee,"Fee asset",Spread,"Spread Currency"
181F48a0adaa-824f-4753-8e2e-***********,2022-07-02T08:53:13+02:00,deposit,incoming,1000.00,EUR,-,EUR,-,-,Fiat,-,17.69000000,EUR,-,-
182T02f7a7ce-9c38-4b18-9306-***********,2022-07-02T09:09:41+02:00,buy,outgoing,150.00,EUR,1.42307692,AMZN,105.41,EUR,"Stock (derivative)",73,-,-,0.15000000,EUR
183T8123f94c-2580-4129-ae62-***********,2022-07-02T09:19:36+02:00,buy,outgoing,250.00,EUR,0.01329013,BTC,18810.95,EUR,Cryptocurrency,1,-,-,-,-
1842cbcc5dd-67c1-4ded-8020-6***********,2022-07-02T09:23:35+02:00,transfer,incoming,0.00,EUR,0.00869699,BEST,0.34,EUR,Cryptocurrency,33,-,-,-,-
185F9d880b45-e2bf-4a72-b39a-***********,2022-07-02T09:33:29+02:00,deposit,incoming,500.00,EUR,-,EUR,-,-,Fiat,-,8.85000000,EUR,-,-
186F04ce50ab-80e9-4bde-bc74-***********,2022-07-04T11:34:39+02:00,deposit,incoming,500.00,EUR,-,EUR,-,-,Fiat,-,8.85000000,EUR,-,-
187F9b623f2d-4432-445b-90a7-***********,2022-07-28T19:27:49+02:00,deposit,incoming,1527.00,EUR,-,EUR,-,-,Fiat,-,27.00000000,EUR,-,-
188C04e9125e-9688-4fbb-b23b-***********,2022-08-04T15:16:04+02:00,withdrawal,outgoing,0,EUR,0.34905088,ETH,0.00,-,Cryptocurrency,5,0.00100136,ETH,-,-
189Cd0386774-b60a-4f60-bc1e-***********,2022-08-04T15:17:46+02:00,withdrawal,outgoing,0,EUR,0.05039663,BTC,0.00,-,Cryptocurrency,1,0.00006000,BTC,-,-
190T2fdbfca0-fc44-4032-941e-***********,2022-08-05T14:35:07+02:00,sell,incoming,129.17,EUR,15.00000000,FTSE100,8.61,EUR,"ETF (derivative)",115,-,-,0.33000000,EUR
191F93590637-4ca5-4edf-af9f-***********,2022-08-13T10:28:48+02:00,withdrawal,outgoing,20.00,EUR,-,EUR,-,-,Fiat,-,0.00000000,EUR,-,-
192F542dc58a-c88e-45d5-9f00-***********,2022-08-24T01:32:08+02:00,withdrawal,outgoing,1197.70,EUR,-,EUR,-,-,Fiat,-,0.00000000,EUR,-,-
193"#;
194        let buffer = Cursor::new(csv);
195        let mut reader = csv::Reader::from_reader(buffer);
196        let mut trades: Vec<Trade> = Vec::new();
197        for result in reader.deserialize::<Trade>() {
198            trades.push(result.expect("failed to decode row"));
199        }
200        assert_eq!(trades.len(), 12);
201        let trade0 = &trades[0];
202        assert_eq!(
203            trade0.transaction_id(),
204            "F48a0adaa-824f-4753-8e2e-***********"
205        );
206        assert_eq!(
207            trade0.timestamp().to_rfc3339().as_str(),
208            "2022-07-02T08:53:13+02:00"
209        );
210        assert_eq!(trade0.transaction_type(), TransactionType::Deposit);
211        assert_eq!(trade0.in_out(), InOut::Incoming);
212        assert_eq!(trade0.amount_fiat(), dec!(1000.0));
213        assert_eq!(trade0.fiat(), Fiat::Eur);
214        assert_eq!(trade0.amount_asset(), None);
215        assert_eq!(trade0.asset(), Asset::Currency(Currency::Fiat(Fiat::Eur)));
216        assert_eq!(trade0.asset_market_price(), None);
217        assert_eq!(trade0.asset_market_price_currency(), None);
218        assert_eq!(trade0.asset_class(), AssetClass::Fiat);
219        assert_eq!(trade0.product_id(), None);
220        assert_eq!(trade0.fee(), Some(dec!(17.69000000)));
221        assert_eq!(trade0.fee_asset(), Some(Currency::Fiat(Fiat::Eur)));
222        assert_eq!(trade0.spread(), None);
223        assert_eq!(trade0.spread_currency(), None);
224    }
225}