You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
13KB

  1. use std::num::{NonZeroU64, NonZeroU8, NonZeroI32};
  2. use std::mem::size_of;
  3. use std::convert::{TryFrom, TryInto};
  4. use serde::{Serialize, Deserialize, Deserializer};
  5. use markets::crypto::{Exchange, Currency, Ticker, Side};
  6. pub const EXCH_OFFSET : usize = 0;
  7. pub const BASE_OFFSET : usize = 1;
  8. pub const QUOTE_OFFSET : usize = 2;
  9. pub const SIDE_OFFSET : usize = 3;
  10. pub const SERVER_TIME_OFFSET : usize = 4;
  11. pub const TIME_OFFSET : usize = 8;
  12. pub const PRICE_OFFSET : usize = 16;
  13. pub const AMOUNT_OFFSET : usize = 24;
  14. pub const SERIALIZED_SIZE : usize = 32;
  15. /// `server_time` is stored in milliseconds, while `time` is nanoseconds.
  16. /// this is what you need to multiply the stored `server_time` data by to
  17. /// get it back to nanoseconds.
  18. pub const SERVER_TIME_DOWNSCALE_FACTOR: u64 = 1_000_000;
  19. /// Represents the serialized form of a trades row
  20. ///
  21. /// ```console,ignore
  22. /// 1 2 3
  23. /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  24. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  25. /// |e|b|q|s| srvtm | time: u64 | price: f64 | amount: f64 |
  26. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  27. /// | | | | |
  28. /// | | | | |
  29. /// | | | | |
  30. /// | | | | -> server_time: Option<i32> - 0=None, other=nano offset from `time`
  31. /// | | | |
  32. /// | | | -> side: Option<Side> - 0=None, 1=Bid, 2=Ask
  33. /// | | |
  34. /// | | -> quote: Currency - see markets::crypto for u8 <-> currency codes
  35. /// | |
  36. /// | -> base: Currency - see markets::crypto for u8 <-> currency codes
  37. /// |
  38. /// -> exch: Exchange - see markets::crypto for u8 <-> exchange codes
  39. ///
  40. /// ```
  41. ///
  42. #[derive(Debug, Clone)]
  43. pub struct PackedTrade {
  44. pub exch: u8,
  45. pub base: u8,
  46. pub quote: u8,
  47. /// 0=None
  48. pub side: u8,
  49. /// relative offset from `time`; 0=None
  50. pub server_time: i32,
  51. pub time: u64,
  52. pub price: f64,
  53. pub amount: f64,
  54. }
  55. #[derive(Deserialize, Serialize, Debug, Clone)]
  56. pub struct CsvTrade {
  57. pub time: u64,
  58. pub exch: Exchange,
  59. pub ticker: Ticker,
  60. pub price: f64,
  61. pub amount: f64,
  62. #[serde(deserialize_with = "deserialize_csv_side")]
  63. pub side: Option<Side>,
  64. #[serde(deserialize_with = "deserialize_csv_server_time")]
  65. pub server_time: Option<u64>,
  66. }
  67. #[derive(Debug, Clone)]
  68. pub struct ParseError(Box<String>);
  69. /// Pull out individual fields on demand from the serialized bytes of a PackedTrade
  70. #[repr(align(32))]
  71. pub struct PackedTradeData<'a>(&'a [u8]);
  72. #[derive(Deserialize, Serialize, Debug, Clone)]
  73. pub struct Serde32BytesTrade {
  74. pub time: u64,
  75. #[serde(with = "try_from_u8")]
  76. pub exch: Exchange,
  77. #[serde(with = "try_from_u8")]
  78. pub ticker: Ticker,
  79. pub price: f64,
  80. pub amount: f64,
  81. pub side: Option<Side>,
  82. pub server_time: Option<NonZeroI32>,
  83. }
  84. pub fn server_time_to_delta(time: u64, server_time: u64) -> i32 {
  85. let ms = (
  86. (server_time / SERVER_TIME_DOWNSCALE_FACTOR) as i64
  87. - (time / SERVER_TIME_DOWNSCALE_FACTOR) as i64
  88. ) as i32;
  89. match ms {
  90. // if the two values are either identical, or so close that the difference
  91. // is washed out when we downscale, return i32::MIN as a sentinel indicating
  92. // time == server_time
  93. //
  94. 0 => std::i32::MIN,
  95. other => other
  96. }
  97. }
  98. /// Convert a `server_time` delta back to its unix nanosecond timestamp form.
  99. ///
  100. /// Note: while the `server_time` delta is stored as a signed integer, to be able to express a
  101. /// delta in both directions relative to `time`, we can't just add a negative `i64` to a
  102. /// `u64`, it doesn't work like that. this match either subtracts the absolute value of a
  103. /// negative delta, or adds a positive delta, to get around this conundrum.
  104. pub fn delta_to_server_time(time: u64, delta: i32) -> Option<u64> {
  105. const MIN_VALID: i32 = std::i32::MIN + 1;
  106. match delta {
  107. 0 => None,
  108. // -1 is another sentinel indicating that time == server_time
  109. std::i32::MIN => Some(time),
  110. x @ MIN_VALID .. 0 => Some(time - (x.abs() as u64 * SERVER_TIME_DOWNSCALE_FACTOR)),
  111. x @ 1 ..= std::i32::MAX => Some(time + (x as u64 * SERVER_TIME_DOWNSCALE_FACTOR)),
  112. }
  113. }
  114. pub fn serialize<'a, 'b>(buf: &'a mut [u8], trade: &'b CsvTrade) {
  115. assert_eq!(buf.len(), SERIALIZED_SIZE);
  116. buf[EXCH_OFFSET] = u8::from(trade.exch);
  117. buf[BASE_OFFSET] = u8::from(trade.ticker.base);
  118. buf[QUOTE_OFFSET] = u8::from(trade.ticker.quote);
  119. match trade.side {
  120. Some(side) => {
  121. buf[SIDE_OFFSET] = u8::from(side);
  122. }
  123. None => {
  124. buf[SIDE_OFFSET] = 0;
  125. }
  126. }
  127. match trade.server_time {
  128. Some(st) => {
  129. let delta: i32 = server_time_to_delta(trade.time, st);
  130. (&mut buf[SERVER_TIME_OFFSET..(SERVER_TIME_OFFSET + 4)]).copy_from_slice(&delta.to_le_bytes()[..]);
  131. }
  132. None => {
  133. (&mut buf[SERVER_TIME_OFFSET..(SERVER_TIME_OFFSET + 4)]).copy_from_slice(&0i32.to_le_bytes()[..]);
  134. }
  135. }
  136. (&mut buf[TIME_OFFSET..(TIME_OFFSET + 8)]).copy_from_slice(&trade.time.to_le_bytes()[..]);
  137. (&mut buf[PRICE_OFFSET..(PRICE_OFFSET + 8)]).copy_from_slice(&trade.price.to_le_bytes()[..]);
  138. (&mut buf[AMOUNT_OFFSET..(AMOUNT_OFFSET + 8)]).copy_from_slice(&trade.amount.to_le_bytes()[..]);
  139. }
  140. impl<'a> PackedTradeData<'a> {
  141. pub fn new(buf: &'a [u8]) -> Self {
  142. assert_eq!(buf.len(), SERIALIZED_SIZE);
  143. Self(buf)
  144. }
  145. #[inline]
  146. pub fn exch(&self) -> Result<Exchange, markets::crypto::Error> {
  147. Exchange::try_from(self.0[EXCH_OFFSET])
  148. }
  149. #[inline]
  150. pub fn base(&self) -> Result<Currency, markets::crypto::Error> {
  151. Currency::try_from(self.0[BASE_OFFSET])
  152. }
  153. #[inline]
  154. pub fn ticker(&self) -> Ticker {
  155. Ticker {
  156. base: self.base().unwrap(),
  157. quote: self.quote().unwrap(),
  158. }
  159. }
  160. #[inline]
  161. pub fn quote(&self) -> Result<Currency, markets::crypto::Error> {
  162. Currency::try_from(self.0[QUOTE_OFFSET])
  163. }
  164. #[inline]
  165. pub fn side(&self) -> Result<Option<Side>, markets::crypto::Error> {
  166. match self.0[SIDE_OFFSET] {
  167. 0 => Ok(None),
  168. other => Ok(Some(Side::try_from(other)?)),
  169. }
  170. }
  171. #[inline]
  172. pub fn time(&self) -> u64 {
  173. u64::from_le_bytes(
  174. (&self.0[TIME_OFFSET..(TIME_OFFSET + 8)]).try_into().unwrap()
  175. )
  176. }
  177. #[inline]
  178. pub fn price(&self) -> f64 {
  179. f64::from_le_bytes(
  180. (&self.0[PRICE_OFFSET..(PRICE_OFFSET + 8)]).try_into().unwrap()
  181. )
  182. }
  183. #[inline]
  184. pub fn amount(&self) -> f64 {
  185. f64::from_le_bytes(
  186. (&self.0[AMOUNT_OFFSET..(AMOUNT_OFFSET + 8)]).try_into().unwrap()
  187. )
  188. }
  189. #[inline]
  190. pub fn server_time(&self) -> Option<u64> {
  191. let delta = i32::from_le_bytes(
  192. (&self.0[SERVER_TIME_OFFSET..(SERVER_TIME_OFFSET + 4)]).try_into().unwrap()
  193. );
  194. delta_to_server_time(self.time(), delta)
  195. }
  196. }
  197. pub fn deserialize_csv_side<'de, D>(deserializer: D) -> Result<Option<Side>, D::Error>
  198. where D: Deserializer<'de>
  199. {
  200. let s: &str = Deserialize::deserialize(deserializer)?;
  201. match s {
  202. "bid" => Ok(Some(Side::Bid)),
  203. "ask" => Ok(Some(Side::Ask)),
  204. _ => Ok(None)
  205. }
  206. }
  207. pub fn deserialize_csv_server_time<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
  208. where D: Deserializer<'de>
  209. {
  210. let st: u64 = Deserialize::deserialize(deserializer)?;
  211. match st {
  212. 0 => Ok(None),
  213. other => Ok(Some(other))
  214. }
  215. }
  216. mod try_from_u8 {
  217. use std::convert::TryFrom;
  218. use std::fmt;
  219. use std::marker::PhantomData;
  220. use serde::{Serializer, Deserializer};
  221. use serde::de::Visitor;
  222. use serde::ser::Error as SerError;
  223. struct V<T>(PhantomData<T>);
  224. impl<'de, T> Visitor<'de> for V<T>
  225. where T: TryFrom<u8>
  226. {
  227. type Value = T;
  228. fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
  229. formatter.write_str("an integer code between 1-255")
  230. }
  231. fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
  232. where E: serde::de::Error,
  233. {
  234. match T::try_from(v) {
  235. Ok(v) => Ok(v),
  236. Err(_) => {
  237. Err(serde::de::Error::custom("Invalid code"))
  238. }
  239. }
  240. }
  241. fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
  242. where E: serde::de::Error,
  243. {
  244. if v > 255 {
  245. return Err(serde::de::Error::custom("Value greater than 255"))
  246. }
  247. match T::try_from(v as u8) {
  248. Ok(v) => Ok(v),
  249. Err(_) => {
  250. Err(serde::de::Error::custom("Invalid code"))
  251. }
  252. }
  253. }
  254. }
  255. pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
  256. where D: Deserializer<'de>,
  257. T: TryFrom<u8>
  258. {
  259. deserializer.deserialize_u8(V(PhantomData))
  260. }
  261. pub fn serialize<S, T>(item: &T, serializer: S) -> Result<S::Ok, S::Error>
  262. where S: Serializer,
  263. T: Copy,
  264. u8: From<T>
  265. {
  266. match u8::from(*item) {
  267. 0 => Err(S::Error::custom("not implemented: no code for variant or value")),
  268. x => serializer.serialize_u8(x)
  269. }
  270. }
  271. }
  272. #[allow(unused)]
  273. #[cfg(test)]
  274. mod tests {
  275. use super::*;
  276. use std::io::{self, prelude::*};
  277. use markets::{e, t, c};
  278. use approx::assert_relative_eq;
  279. const CSV: &str =
  280. "time,amount,exch,price,server_time,side,ticker\n\
  281. 1561939200002479372,1.4894,bnce,292.7,1561939199919000064,,eth_usd\n\
  282. 1561939200011035644,0.0833333283662796,btfx,10809.0,1561939199927000064,bid,btc_usd\n\
  283. 1561939200011055712,0.8333191871643066,btfx,10809.0,1561939199927000064,bid,btc_usd\n\
  284. 1561939200019037617,0.083096,bnce,10854.1,1561939199935000064,,btc_usd\n\
  285. 1561939200026450471,0.125,okex,123.21,1561939200026450432,ask,ltc_usd\n\
  286. 1561939200027716312,0.704054,okex,123.21,1561939200027716352,ask,ltc_usd\n\
  287. 1561939200028633907,0.11,okex,123.22,1561939200028633856,bid,ltc_usd\n\
  288. 1561939200029908535,1.438978,okex,123.22,1561939200029908480,ask,ltc_usd\n\
  289. 1561939200030393495,0.257589,okex,123.22,1561939200030393600,bid,ltc_usd"
  290. ;
  291. #[test]
  292. fn parse_csv_sample_with_csv_trade() {
  293. let csv: Vec<u8> = CSV.as_bytes().to_vec();
  294. let mut rdr = csv::Reader::from_reader(io::Cursor::new(csv));
  295. let mut rows = Vec::new();
  296. let headers = rdr.byte_headers().unwrap().clone();
  297. let mut row = csv::ByteRecord::new();
  298. while rdr.read_byte_record(&mut row).unwrap() {
  299. let trade: CsvTrade = row.deserialize(Some(&headers)).unwrap();
  300. rows.push(trade);
  301. }
  302. assert_eq!(rows[0].time, 1561939200002479372);
  303. assert_eq!(rows[1].exch, e!(btfx));
  304. let mut buf = vec![0u8; 32];
  305. for (i, trade) in rows.iter().enumerate() {
  306. assert!(trade.server_time.is_some());
  307. let st = trade.server_time.unwrap();
  308. let delta = server_time_to_delta(trade.time, st);
  309. dbg!(i, trade, trade.time, st,
  310. trade.time as i64 - st as i64, delta,
  311. (trade.time / SERVER_TIME_DOWNSCALE_FACTOR) as i64 - (st / SERVER_TIME_DOWNSCALE_FACTOR) as i64,
  312. );
  313. assert!(delta != 0);
  314. let rt: u64 = delta_to_server_time(trade.time, delta).unwrap();
  315. let abs_diff = (rt as i64 - st as i64).abs();
  316. let max_allowable_diff = SERVER_TIME_DOWNSCALE_FACTOR; // * 2;
  317. dbg!(rt, abs_diff, max_allowable_diff);
  318. assert!(abs_diff < max_allowable_diff as i64);
  319. serialize(&mut buf[..], &trade);
  320. {
  321. let packed = PackedTradeData(&buf[..]);
  322. assert_eq!(packed.time(), trade.time);
  323. assert_eq!(packed.exch().unwrap(), trade.exch);
  324. assert_eq!(packed.base().unwrap(), trade.ticker.base);
  325. assert_eq!(packed.quote().unwrap(), trade.ticker.quote);
  326. assert_eq!(packed.side().unwrap(), trade.side);
  327. assert_relative_eq!(packed.price(), trade.price);
  328. assert_relative_eq!(packed.amount(), trade.amount);
  329. }
  330. }
  331. }
  332. #[test]
  333. fn verify_packed_trade_is_32_bytes() {
  334. assert_eq!(size_of::<PackedTrade>(), 32);
  335. }
  336. #[test]
  337. fn check_bincode_serialized_size() {
  338. let trade = Serde32BytesTrade {
  339. time: 1586996977191449698,
  340. exch: e!(bmex),
  341. ticker: t!(btc-usd),
  342. price: 1.234,
  343. amount: 4.567,
  344. side: None,
  345. server_time: NonZeroI32::new(1_000_000),
  346. };
  347. assert_eq!(size_of::<Serde32BytesTrade>(), 32);
  348. assert_eq!(bincode::serialized_size(&trade).unwrap(), 32);
  349. }
  350. #[test]
  351. fn example_of_36_byte_trades_struct_without_the_offset_i32() {
  352. #[repr(packed)]
  353. pub struct Trade36 {
  354. pub exch: Exchange,
  355. pub ticker: Ticker,
  356. pub side: Option<Side>,
  357. pub time: u64,
  358. pub price: f64,
  359. pub amount: f64,
  360. pub server_time: Option<NonZeroU64>,
  361. }
  362. assert_eq!(size_of::<Trade36>(), 36);
  363. }
  364. }