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.

272 lines
7.8KB

  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 Trade {
  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. #[inline]
  161. pub fn server_time(&self) -> Result<Option<u64>, ParseError> {
  162. let st: i32 =
  163. atoi::atoi(&self.0[Self::SERVER_TIME_OFFSET..(Self::SERVER_TIME_OFFSET + 4)])
  164. .ok_or_else(|| {
  165. ParseError(Box::new(format!("failed to parse integer: '{}'",
  166. std::str::from_utf8(&self.0[Self::SERVER_TIME_OFFSET..(Self::SERVER_TIME_OFFSET + 4)]).unwrap_or("uft8 error")
  167. )))
  168. })?;
  169. match st {
  170. 0 => Ok(None),
  171. x @ std::i32::MIN .. 0 => Ok(Some(self.time()? - x.abs() as u64)),
  172. x @ 1 ..= std::i32::MAX => Ok(Some(self.time()? + x as u64)),
  173. }
  174. }
  175. }
  176. #[allow(unused)]
  177. #[cfg(test)]
  178. mod tests {
  179. use super::*;
  180. use markets::{e, t, c};
  181. #[test]
  182. fn verify_packed_trade_is_32_bytes() {
  183. assert_eq!(size_of::<PackedTrade>(), 32);
  184. }
  185. #[test]
  186. fn check_bincode_serialized_size() {
  187. let trade = Trade {
  188. time: 1586996977191449698,
  189. exch: e!(bmex),
  190. ticker: t!(btc-usd),
  191. price: 1.234,
  192. amount: 4.567,
  193. side: None,
  194. //server_time: NonZeroU64::new(1586996977191449698 + 1_000_000),
  195. server_time: NonZeroI32::new(1_000_000),
  196. //server_time: Some(1586996977191449698 + 1_000_000),
  197. };
  198. /*
  199. let packed = PackedTrade {
  200. time: trade.time,
  201. exch: u8::from(trade.exch),
  202. base: u8::from(trade.ticker.base),
  203. quote: u8::from(trade.ticker.quote),
  204. amount: trade.amount,
  205. price: trade.price,
  206. //side: trade.side.and_then(|s| NonZeroU8::new(u8::from(s))),
  207. //server_time: NonZeroI32::new(1_000_000),
  208. side: trade.side.map(|s| u8::from(s)).unwrap_or(0),
  209. server_time: 1_000_000,
  210. };
  211. */
  212. assert_eq!(size_of::<Trade>(), 32);
  213. //assert_eq!(serde_json::to_string(&trade).unwrap().len(), 32);
  214. assert_eq!(bincode::serialized_size(&trade).unwrap(), 32);
  215. }
  216. #[test]
  217. fn example_of_36_byte_trades_struct_without_the_offset_i32() {
  218. #[repr(packed)]
  219. pub struct Trade36 {
  220. pub exch: Exchange,
  221. pub ticker: Ticker,
  222. pub side: Option<Side>,
  223. pub time: u64,
  224. pub price: f64,
  225. pub amount: f64,
  226. pub server_time: Option<NonZeroU64>,
  227. }
  228. assert_eq!(size_of::<Trade36>(), 36);
  229. }
  230. }