1use 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#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash)]
28pub struct Trade {
29 #[serde(rename = "Transaction ID")]
31 transaction_id: String,
32 #[serde(rename = "Timestamp")]
34 timestamp: DateTime<FixedOffset>,
35 #[serde(rename = "Transaction Type")]
37 transaction_type: TransactionType,
38 #[serde(rename = "In/Out")]
40 in_out: InOut,
41 #[serde(rename = "Amount Fiat")]
43 amount_fiat: Decimal,
44 #[serde(rename = "Fiat")]
46 fiat: Fiat,
47 #[serde(rename = "Amount Asset")]
49 amount_asset: CsvOption<Decimal>,
50 #[serde(rename = "Asset")]
52 asset: Asset,
53 #[serde(rename = "Asset market price")]
55 asset_market_price: CsvOption<Decimal>,
56 #[serde(rename = "Asset market price currency")]
58 asset_market_price_currency: CsvOption<Fiat>,
59 #[serde(rename = "Asset class")]
61 asset_class: AssetClass,
62 #[serde(rename = "Product ID")]
64 product_id: CsvOption<u64>,
65 #[serde(rename = "Fee")]
67 fee: CsvOption<Decimal>,
68 #[serde(rename = "Fee asset")]
70 fee_asset: CsvOption<Currency>,
71 #[serde(rename = "Spread")]
73 spread: CsvOption<Decimal>,
74 #[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}