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.

266 lines
8.0KB

  1. use std::num::{NonZeroU64, NonZeroU8, NonZeroI32};
  2. use std::mem::size_of;
  3. use std::convert::TryFrom;
  4. use serde::{Serialize, Deserialize};
  5. use markets::crypto::{Exchange, Currency, Ticker, Side};
  6. mod try_from_u8 {
  7. use std::convert::TryFrom;
  8. use std::fmt;
  9. use std::marker::PhantomData;
  10. use serde::{Serializer, Deserializer};
  11. use serde::de::Visitor;
  12. use serde::ser::Error as SerError;
  13. struct V<T>(PhantomData<T>);
  14. impl<'de, T> Visitor<'de> for V<T>
  15. where T: TryFrom<u8>
  16. {
  17. type Value = T;
  18. fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
  19. formatter.write_str("an integer code between 1-255")
  20. }
  21. fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
  22. where E: serde::de::Error,
  23. {
  24. match T::try_from(v) {
  25. Ok(v) => Ok(v),
  26. Err(_) => {
  27. Err(serde::de::Error::custom("Invalid code"))
  28. }
  29. }
  30. }
  31. fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
  32. where E: serde::de::Error,
  33. {
  34. if v > 255 {
  35. return Err(serde::de::Error::custom("Value greater than 255"))
  36. }
  37. match T::try_from(v as u8) {
  38. Ok(v) => Ok(v),
  39. Err(_) => {
  40. Err(serde::de::Error::custom("Invalid code"))
  41. }
  42. }
  43. }
  44. }
  45. pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
  46. where D: Deserializer<'de>,
  47. T: TryFrom<u8>
  48. {
  49. deserializer.deserialize_u8(V(PhantomData))
  50. }
  51. pub fn serialize<S, T>(item: &T, serializer: S) -> Result<S::Ok, S::Error>
  52. where S: Serializer,
  53. T: Copy,
  54. u8: From<T>
  55. {
  56. match u8::from(*item) {
  57. 0 => Err(S::Error::custom("not implemented: no code for variant or value")),
  58. x => serializer.serialize_u8(x)
  59. }
  60. }
  61. }
  62. #[derive(Deserialize, Serialize, Debug, Clone)]
  63. pub struct Serde32BytesTrade {
  64. pub time: u64,
  65. #[serde(with = "try_from_u8")]
  66. pub exch: Exchange,
  67. #[serde(with = "try_from_u8")]
  68. pub ticker: Ticker,
  69. pub price: f64,
  70. pub amount: f64,
  71. pub side: Option<Side>,
  72. pub server_time: Option<NonZeroI32>,
  73. }
  74. /// Represents the serialized form of a trades row
  75. ///
  76. /// ```console,ignore
  77. /// 1 2 3
  78. /// 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
  79. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  80. /// |e|b|q|s| srvtm | time: u64 | price: f64 | amount: f64 |
  81. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  82. /// | | | | |
  83. /// | | | | |
  84. /// | | | | |
  85. /// | | | | -> server_time: Option<i32> - 0=None, other=nano offset from `time`
  86. /// | | | |
  87. /// | | | -> side: Option<Side> - 0=None, 1=Bid, 2=Ask
  88. /// | | |
  89. /// | | -> quote: Currency - see markets::crypto for u8 <-> currency codes
  90. /// | |
  91. /// | -> base: Currency - see markets::crypto for u8 <-> currency codes
  92. /// |
  93. /// -> exch: Exchange - see markets::crypto for u8 <-> exchange codes
  94. ///
  95. /// ```
  96. ///
  97. #[derive(Debug, Clone)]
  98. pub struct PackedTrade {
  99. pub exch: u8,
  100. pub base: u8,
  101. pub quote: u8,
  102. /// 0=None
  103. pub side: u8,
  104. /// relative offset from `time`; 0=None
  105. pub server_time: i32,
  106. pub time: u64,
  107. pub price: f64,
  108. pub amount: f64,
  109. }
  110. #[derive(Debug, Clone)]
  111. pub struct ParseError(Box<String>);
  112. /// Pull out individual fields on demand from the serialized bytes of a PackedTrade
  113. #[repr(align(32))]
  114. pub struct PackedTradeData<'a>(&'a [u8]);
  115. impl<'a> PackedTradeData<'a> {
  116. const EXCH_OFFSET : usize = 0;
  117. const BASE_OFFSET : usize = 1;
  118. const QUOTE_OFFSET : usize = 2;
  119. const SIDE_OFFSET : usize = 3;
  120. const SERVER_TIME_OFFSET : usize = 4;
  121. const TIME_OFFSET : usize = 8;
  122. const PRICE_OFFSET : usize = 16;
  123. const AMOUNT_OFFSET : usize = 24;
  124. #[inline]
  125. pub fn exch(&self) -> Result<Exchange, markets::crypto::Error> {
  126. Exchange::try_from(self.0[Self::EXCH_OFFSET])
  127. }
  128. #[inline]
  129. pub fn base(&self) -> Result<Currency, markets::crypto::Error> {
  130. Currency::try_from(self.0[Self::BASE_OFFSET])
  131. }
  132. #[inline]
  133. pub fn quote(&self) -> Result<Currency, markets::crypto::Error> {
  134. Currency::try_from(self.0[Self::QUOTE_OFFSET])
  135. }
  136. #[inline]
  137. pub fn side(&self) -> Result<Option<Side>, markets::crypto::Error> {
  138. match self.0[Self::SIDE_OFFSET] {
  139. 0 => Ok(None),
  140. other => Ok(Some(Side::try_from(other)?)),
  141. }
  142. }
  143. #[inline]
  144. pub fn time(&self) -> Result<u64, ParseError> {
  145. atoi::atoi(&self.0[Self::TIME_OFFSET..(Self::TIME_OFFSET + 8)])
  146. .ok_or_else(|| {
  147. ParseError(Box::new(format!("failed to parse integer: '{}'",
  148. std::str::from_utf8(&self.0[Self::TIME_OFFSET..(Self::TIME_OFFSET + 8)]).unwrap_or("uft8 error")
  149. )))
  150. })
  151. }
  152. #[inline]
  153. pub fn price(&self) -> Result<f64, lexical::Error> {
  154. lexical::parse(&self.0[Self::PRICE_OFFSET..(Self::PRICE_OFFSET + 8)])
  155. }
  156. #[inline]
  157. pub fn amount(&self) -> Result<f64, lexical::Error> {
  158. lexical::parse(&self.0[Self::AMOUNT_OFFSET..(Self::AMOUNT_OFFSET + 8)])
  159. }
  160. /// `server_time` is stored in milliseconds, while `time` is nanoseconds.
  161. /// this is what you need to multiply the stored `server_time` data by to
  162. /// get it back to nanoseconds.
  163. const SERVER_TIME_DOWNSCALE_FACTOR: u64 = 1_000_000;
  164. #[inline]
  165. pub fn server_time(&self) -> Result<Option<u64>, ParseError> {
  166. let st: i32 =
  167. atoi::atoi(&self.0[Self::SERVER_TIME_OFFSET..(Self::SERVER_TIME_OFFSET + 4)])
  168. .ok_or_else(|| {
  169. ParseError(Box::new(format!("failed to parse integer: '{}'",
  170. std::str::from_utf8(&self.0[Self::SERVER_TIME_OFFSET..(Self::SERVER_TIME_OFFSET + 4)]).unwrap_or("uft8 error")
  171. )))
  172. })?;
  173. // while the `server_time` delta is stored as a signed integer, to be able to express a
  174. // delta in both directions relative to `time`, we can't just add a negative `i64` to a
  175. // `u64`, it doesn't work like that. this match either subtracts the absolute value of a
  176. // negative delta, or adds a positive delta, to get around this conundrum.
  177. //
  178. // `SERVER_TIME_DOWNSCALE_FACTOR` is used to rescale the delta to nanoseconds prior to its
  179. // being applied to `time`.
  180. match st {
  181. 0 => Ok(None),
  182. x @ std::i32::MIN .. 0 => Ok(Some(self.time()? - (x.abs() as u64 * Self::SERVER_TIME_DOWNSCALE_FACTOR))),
  183. x @ 1 ..= std::i32::MAX => Ok(Some(self.time()? + (x as u64 * Self::SERVER_TIME_DOWNSCALE_FACTOR))),
  184. }
  185. }
  186. }
  187. #[allow(unused)]
  188. #[cfg(test)]
  189. mod tests {
  190. use super::*;
  191. use markets::{e, t, c};
  192. #[test]
  193. fn verify_packed_trade_is_32_bytes() {
  194. assert_eq!(size_of::<PackedTrade>(), 32);
  195. }
  196. #[test]
  197. fn check_bincode_serialized_size() {
  198. let trade = Serde32BytesTrade {
  199. time: 1586996977191449698,
  200. exch: e!(bmex),
  201. ticker: t!(btc-usd),
  202. price: 1.234,
  203. amount: 4.567,
  204. side: None,
  205. server_time: NonZeroI32::new(1_000_000),
  206. };
  207. assert_eq!(size_of::<Serde32BytesTrade>(), 32);
  208. assert_eq!(bincode::serialized_size(&trade).unwrap(), 32);
  209. }
  210. #[test]
  211. fn example_of_36_byte_trades_struct_without_the_offset_i32() {
  212. #[repr(packed)]
  213. pub struct Trade36 {
  214. pub exch: Exchange,
  215. pub ticker: Ticker,
  216. pub side: Option<Side>,
  217. pub time: u64,
  218. pub price: f64,
  219. pub amount: f64,
  220. pub server_time: Option<NonZeroU64>,
  221. }
  222. assert_eq!(size_of::<Trade36>(), 36);
  223. }
  224. }