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.

438 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 quote(&self) -> Result<Currency, markets::crypto::Error> {
  155. Currency::try_from(self.0[QUOTE_OFFSET])
  156. }
  157. #[inline]
  158. pub fn side(&self) -> Result<Option<Side>, markets::crypto::Error> {
  159. match self.0[SIDE_OFFSET] {
  160. 0 => Ok(None),
  161. other => Ok(Some(Side::try_from(other)?)),
  162. }
  163. }
  164. #[inline]
  165. pub fn ticker(&self) -> Ticker {
  166. Ticker {
  167. base: self.base().unwrap(),
  168. quote: self.quote().unwrap(),
  169. }
  170. }
  171. #[inline]
  172. pub fn meta_i32(&self) -> i32 {
  173. i32::from_le_bytes((&self.0[..4]).try_into().unwrap())
  174. }
  175. #[inline]
  176. pub fn time(&self) -> u64 {
  177. u64::from_le_bytes(
  178. (&self.0[TIME_OFFSET..(TIME_OFFSET + 8)]).try_into().unwrap()
  179. )
  180. }
  181. #[inline]
  182. pub fn price(&self) -> f64 {
  183. f64::from_le_bytes(
  184. (&self.0[PRICE_OFFSET..(PRICE_OFFSET + 8)]).try_into().unwrap()
  185. )
  186. }
  187. #[inline]
  188. pub fn amount(&self) -> f64 {
  189. f64::from_le_bytes(
  190. (&self.0[AMOUNT_OFFSET..(AMOUNT_OFFSET + 8)]).try_into().unwrap()
  191. )
  192. }
  193. #[inline]
  194. pub fn server_time(&self) -> Option<u64> {
  195. let delta = i32::from_le_bytes(
  196. (&self.0[SERVER_TIME_OFFSET..(SERVER_TIME_OFFSET + 4)]).try_into().unwrap()
  197. );
  198. delta_to_server_time(self.time(), delta)
  199. }
  200. }
  201. pub fn deserialize_csv_side<'de, D>(deserializer: D) -> Result<Option<Side>, D::Error>
  202. where D: Deserializer<'de>
  203. {
  204. let s: &str = Deserialize::deserialize(deserializer)?;
  205. match s {
  206. "bid" => Ok(Some(Side::Bid)),
  207. "ask" => Ok(Some(Side::Ask)),
  208. _ => Ok(None)
  209. }
  210. }
  211. pub fn deserialize_csv_server_time<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
  212. where D: Deserializer<'de>
  213. {
  214. let st: u64 = Deserialize::deserialize(deserializer)?;
  215. match st {
  216. 0 => Ok(None),
  217. other => Ok(Some(other))
  218. }
  219. }
  220. mod try_from_u8 {
  221. use std::convert::TryFrom;
  222. use std::fmt;
  223. use std::marker::PhantomData;
  224. use serde::{Serializer, Deserializer};
  225. use serde::de::Visitor;
  226. use serde::ser::Error as SerError;
  227. struct V<T>(PhantomData<T>);
  228. impl<'de, T> Visitor<'de> for V<T>
  229. where T: TryFrom<u8>
  230. {
  231. type Value = T;
  232. fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
  233. formatter.write_str("an integer code between 1-255")
  234. }
  235. fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
  236. where E: serde::de::Error,
  237. {
  238. match T::try_from(v) {
  239. Ok(v) => Ok(v),
  240. Err(_) => {
  241. Err(serde::de::Error::custom("Invalid code"))
  242. }
  243. }
  244. }
  245. fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
  246. where E: serde::de::Error,
  247. {
  248. if v > 255 {
  249. return Err(serde::de::Error::custom("Value greater than 255"))
  250. }
  251. match T::try_from(v as u8) {
  252. Ok(v) => Ok(v),
  253. Err(_) => {
  254. Err(serde::de::Error::custom("Invalid code"))
  255. }
  256. }
  257. }
  258. }
  259. pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
  260. where D: Deserializer<'de>,
  261. T: TryFrom<u8>
  262. {
  263. deserializer.deserialize_u8(V(PhantomData))
  264. }
  265. pub fn serialize<S, T>(item: &T, serializer: S) -> Result<S::Ok, S::Error>
  266. where S: Serializer,
  267. T: Copy,
  268. u8: From<T>
  269. {
  270. match u8::from(*item) {
  271. 0 => Err(S::Error::custom("not implemented: no code for variant or value")),
  272. x => serializer.serialize_u8(x)
  273. }
  274. }
  275. }
  276. #[allow(unused)]
  277. #[cfg(test)]
  278. mod tests {
  279. use super::*;
  280. use std::io::{self, prelude::*};
  281. use markets::{e, t, c};
  282. use approx::assert_relative_eq;
  283. const CSV: &str =
  284. "time,amount,exch,price,server_time,side,ticker\n\
  285. 1561939200002479372,1.4894,bnce,292.7,1561939199919000064,,eth_usd\n\
  286. 1561939200011035644,0.0833333283662796,btfx,10809.0,1561939199927000064,bid,btc_usd\n\
  287. 1561939200011055712,0.8333191871643066,btfx,10809.0,1561939199927000064,bid,btc_usd\n\
  288. 1561939200019037617,0.083096,bnce,10854.1,1561939199935000064,,btc_usd\n\
  289. 1561939200026450471,0.125,okex,123.21,1561939200026450432,ask,ltc_usd\n\
  290. 1561939200027716312,0.704054,okex,123.21,1561939200027716352,ask,ltc_usd\n\
  291. 1561939200028633907,0.11,okex,123.22,1561939200028633856,bid,ltc_usd\n\
  292. 1561939200029908535,1.438978,okex,123.22,1561939200029908480,ask,ltc_usd\n\
  293. 1561939200030393495,0.257589,okex,123.22,1561939200030393600,bid,ltc_usd"
  294. ;
  295. #[test]
  296. fn parse_csv_sample_with_csv_trade() {
  297. let csv: Vec<u8> = CSV.as_bytes().to_vec();
  298. let mut rdr = csv::Reader::from_reader(io::Cursor::new(csv));
  299. let mut rows = Vec::new();
  300. let headers = rdr.byte_headers().unwrap().clone();
  301. let mut row = csv::ByteRecord::new();
  302. while rdr.read_byte_record(&mut row).unwrap() {
  303. let trade: CsvTrade = row.deserialize(Some(&headers)).unwrap();
  304. rows.push(trade);
  305. }
  306. assert_eq!(rows[0].time, 1561939200002479372);
  307. assert_eq!(rows[1].exch, e!(btfx));
  308. let mut buf = vec![0u8; 32];
  309. for (i, trade) in rows.iter().enumerate() {
  310. assert!(trade.server_time.is_some());
  311. let st = trade.server_time.unwrap();
  312. let delta = server_time_to_delta(trade.time, st);
  313. dbg!(i, trade, trade.time, st,
  314. trade.time as i64 - st as i64, delta,
  315. (trade.time / SERVER_TIME_DOWNSCALE_FACTOR) as i64 - (st / SERVER_TIME_DOWNSCALE_FACTOR) as i64,
  316. );
  317. assert!(delta != 0);
  318. let rt: u64 = delta_to_server_time(trade.time, delta).unwrap();
  319. let abs_diff = (rt as i64 - st as i64).abs();
  320. let max_allowable_diff = SERVER_TIME_DOWNSCALE_FACTOR; // * 2;
  321. dbg!(rt, abs_diff, max_allowable_diff);
  322. assert!(abs_diff < max_allowable_diff as i64);
  323. serialize(&mut buf[..], &trade);
  324. {
  325. let packed = PackedTradeData(&buf[..]);
  326. assert_eq!(packed.time(), trade.time);
  327. assert_eq!(packed.exch().unwrap(), trade.exch);
  328. assert_eq!(packed.base().unwrap(), trade.ticker.base);
  329. assert_eq!(packed.quote().unwrap(), trade.ticker.quote);
  330. assert_eq!(packed.side().unwrap(), trade.side);
  331. assert_relative_eq!(packed.price(), trade.price);
  332. assert_relative_eq!(packed.amount(), trade.amount);
  333. }
  334. }
  335. }
  336. #[test]
  337. fn verify_packed_trade_is_32_bytes() {
  338. assert_eq!(size_of::<PackedTrade>(), 32);
  339. }
  340. #[test]
  341. fn check_bincode_serialized_size() {
  342. let trade = Serde32BytesTrade {
  343. time: 1586996977191449698,
  344. exch: e!(bmex),
  345. ticker: t!(btc-usd),
  346. price: 1.234,
  347. amount: 4.567,
  348. side: None,
  349. server_time: NonZeroI32::new(1_000_000),
  350. };
  351. assert_eq!(size_of::<Serde32BytesTrade>(), 32);
  352. assert_eq!(bincode::serialized_size(&trade).unwrap(), 32);
  353. }
  354. #[test]
  355. fn example_of_36_byte_trades_struct_without_the_offset_i32() {
  356. #[repr(packed)]
  357. pub struct Trade36 {
  358. pub exch: Exchange,
  359. pub ticker: Ticker,
  360. pub side: Option<Side>,
  361. pub time: u64,
  362. pub price: f64,
  363. pub amount: f64,
  364. pub server_time: Option<NonZeroU64>,
  365. }
  366. assert_eq!(size_of::<Trade36>(), 36);
  367. }
  368. }