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.

1093 lines
30KB

  1. //! Crypto currency and exchange tickers and other market-related primatives
  2. //!
  3. use std::fmt::{self, Display};
  4. use std::str::FromStr;
  5. use std::cmp::{PartialEq, Eq};
  6. use std::convert::TryFrom;
  7. use serde::{Serialize, Deserialize};
  8. use serde::de::{self, Deserializer};
  9. use serde::Serializer;
  10. //use decimal::d128;
  11. //use chrono::{DateTime, Utc, TimeZone};
  12. #[macro_export]
  13. macro_rules! c {
  14. ($x:tt) => {
  15. $crate::crypto::Currency::$x
  16. }
  17. }
  18. #[macro_export]
  19. macro_rules! e {
  20. ($x:tt) => {
  21. $crate::crypto::Exchange::$x
  22. }
  23. }
  24. #[macro_export]
  25. macro_rules! t {
  26. ($base:tt-$quote:tt) => {
  27. $crate::crypto::Ticker {
  28. base: c!($base),
  29. quote: c!($quote)
  30. }
  31. }
  32. }
  33. /// which side of the transaction (buying or selling)
  34. /// a price/trade is
  35. ///
  36. #[derive(Debug, PartialEq, Clone, Copy, Eq, Serialize)]
  37. pub enum Side {
  38. Bid,
  39. Ask
  40. }
  41. impl TryFrom<u8> for Side {
  42. type Error = Error;
  43. fn try_from(n: u8) -> Result<Self, Self::Error> {
  44. match n {
  45. 1 => Ok(Side::Bid),
  46. 2 => Ok(Side::Ask),
  47. other => Err(Error::IllegalFormat(Box::new(format!("Expected 1 or 2 (received {})", other))))
  48. }
  49. }
  50. }
  51. impl From<Side> for u8 {
  52. fn from(side: Side) -> Self {
  53. match side {
  54. Side::Bid => 1,
  55. Side::Ask => 2
  56. }
  57. }
  58. }
  59. impl Side {
  60. pub fn as_verb(&self) -> &'static str {
  61. match *self {
  62. Side::Bid => "buy",
  63. Side::Ask => "sell"
  64. }
  65. }
  66. pub fn as_past_tense(&self) -> &'static str {
  67. match *self {
  68. Side::Bid => "bought",
  69. Side::Ask => "sold"
  70. }
  71. }
  72. pub fn as_past_tense_title_case(&self) -> &'static str {
  73. match *self {
  74. Side::Bid => "Bought",
  75. Side::Ask => "Sold"
  76. }
  77. }
  78. pub fn to_str(&self) -> &'static str {
  79. match *self {
  80. Side::Bid => "bid",
  81. Side::Ask => "ask"
  82. }
  83. }
  84. pub fn is_bid(&self) -> bool {
  85. match self {
  86. &Side::Bid => true,
  87. _ => false
  88. }
  89. }
  90. pub fn is_ask(&self) -> bool { !self.is_bid() }
  91. }
  92. impl <'de> Deserialize<'de> for Side {
  93. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  94. where D: Deserializer<'de> {
  95. let side = String::deserialize(deserializer)?;
  96. match side.to_lowercase().as_ref() {
  97. "bid" | "buy" | "bids" => Ok(Side::Bid),
  98. "ask" | "sell" | "asks" => Ok(Side::Ask),
  99. other => Err(de::Error::custom(format!("could not parse Side from '{}'", other))),
  100. }
  101. }
  102. }
  103. impl Display for Side {
  104. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  105. write!(f, "{:?}", self)
  106. }
  107. }
  108. impl FromStr for Side {
  109. type Err = Error;
  110. fn from_str(s: &str) -> Result<Self, Error> {
  111. match s.to_lowercase().as_ref() {
  112. "bid" | "buy" | "bids" => Ok(Side::Bid),
  113. "ask" | "sell" | "asks" => Ok(Side::Ask),
  114. s => Err(Error::IllegalFormat(Box::new(s.to_string())))
  115. }
  116. }
  117. }
  118. macro_rules! make_currency {
  119. ($(|$ticker:ident, $name:expr, $code:expr, $upper:expr),*) => {
  120. #[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord, Serialize)]
  121. #[allow(non_camel_case_types)]
  122. #[repr(u8)]
  123. pub enum Currency {
  124. $($ticker = $code),*
  125. }
  126. impl Currency {
  127. pub fn name(&self) -> &'static str {
  128. match *self {
  129. $(
  130. c!($ticker) => { $name }
  131. ),*
  132. }
  133. }
  134. pub fn as_str(&self) -> &'static str {
  135. match *self {
  136. $(
  137. c!($ticker) => { stringify!($ticker) }
  138. ),*
  139. }
  140. }
  141. pub fn to_str_uppercase(&self) -> &'static str {
  142. match *self {
  143. $(
  144. c!($ticker) => { stringify!($upper) }
  145. ),*
  146. }
  147. }
  148. /// Optimized for expecting lowercase. Does not attempt to search beyond
  149. /// every currency symbol in lowercase.
  150. ///
  151. pub fn from_str_lowercase<'a>(s: &'a str) -> Result<Self, UnlistedCurrency<'a>> {
  152. match s {
  153. $(
  154. stringify!($ticker) => { Ok(c!($ticker)) }
  155. )*
  156. other => Err(UnlistedCurrency(other))
  157. }
  158. }
  159. /// Optimized for expecting uppercase. Does not attempt to search beyond
  160. /// every currency symbol in uppercase.
  161. ///
  162. pub fn from_str_uppercase<'a>(s: &'a str) -> Result<Self, UnlistedCurrency<'a>> {
  163. match s {
  164. $(
  165. stringify!($upper) => { Ok(c!($ticker)) }
  166. )*
  167. other => Err(UnlistedCurrency(other))
  168. }
  169. }
  170. pub fn all() -> Vec<Self> {
  171. vec![
  172. $( c!($ticker) ),*
  173. ]
  174. }
  175. }
  176. impl TryFrom<u8> for Currency {
  177. type Error = Error;
  178. fn try_from(n: u8) -> Result<Self, Self::Error> {
  179. match n {
  180. $(
  181. $code => { Ok(c!($ticker)) }
  182. ),*
  183. other => Err(Error::IllegalFormat(Box::new(format!("Illegal Currency value: {}", other))))
  184. }
  185. }
  186. }
  187. impl From<Currency> for u8 {
  188. fn from(c: Currency) -> Self {
  189. match c {
  190. $(
  191. c!($ticker) => { $code }
  192. ),*
  193. }
  194. }
  195. }
  196. impl FromStr for Currency {
  197. type Err = Error;
  198. fn from_str(s: &str) -> Result<Self, Self::Err> {
  199. let t = match s.to_lowercase().trim() {
  200. $(
  201. stringify!($ticker) => { c!($ticker) }
  202. ),*
  203. "xbt" => c!(btc),
  204. _ => {
  205. return Err(Error::IllegalCurrency);
  206. }
  207. };
  208. Ok(t)
  209. }
  210. }
  211. #[test]
  212. fn it_verifies_currency_lower_and_upper_consistency() {
  213. let currencies = vec![ $(c!($ticker)),* ];
  214. for c in &currencies {
  215. assert_eq!(c.as_str().to_uppercase(), c.to_str_uppercase().to_string());
  216. }
  217. }
  218. }
  219. }
  220. make_currency!(
  221. | btc, "Bitcoin", 1, BTC,
  222. | eth, "Ethereum", 2, ETH,
  223. | xmr, "Monero", 3, XMR,
  224. | usdt, "Tether", 4, USDT,
  225. | ltc, "Litecoin", 5, LTC,
  226. | dash, "Dash", 6, DASH,
  227. | nvc, "Novacoin", 7, NVC,
  228. | ppc, "Peercoin", 8, PPC,
  229. | zec, "Zcash", 9, ZEC,
  230. | xrp, "Ripple", 10, XRP,
  231. | gnt, "Golem", 11, GNT,
  232. | steem, "Steem", 12, STEEM,
  233. | rep, "Augur", 13, REP,
  234. | gno, "Gnosis", 14, GNO,
  235. | etc, "Ethereum Classic", 15, ETC,
  236. | icn, "Iconomi", 16, ICN,
  237. | xlm, "Stellar", 17, XLM,
  238. | mln, "Melon", 18, MLN,
  239. | bcn, "Bytecoin", 19, BCN,
  240. | bch, "Bitcoin Cash", 20, BCH,
  241. | doge, "Dogecoin", 21, DOGE,
  242. | eos, "Eos", 22, EOS,
  243. | nxt, "Nxt", 23, NXT,
  244. | sc, "Siacoin", 24, SC,
  245. | zrx, "0x", 25, ZRX,
  246. | bat, "Basic Attention Token", 26, BAT,
  247. | ada, "Cardano", 27, ADA,
  248. | usdc, "USD Coin", 28, USDC,
  249. | dai, "Dai", 29, DAI,
  250. | mkr, "Maker", 30, MKR,
  251. | loom, "Loom Network", 31, LOOM,
  252. | cvc, "Civic", 32, CVC,
  253. | mana, "Decentraland", 33, MANA,
  254. | dnt, "district0x", 34, DNT,
  255. | zil, "Zilliqa", 35, ZIL,
  256. | link, "Chainlink", 36, LINK,
  257. | algo, "Algorand", 37, ALGO,
  258. | xtz, "Tezos", 38, XTZ,
  259. | oxt, "Orchid", 39, OXT,
  260. | atom, "Cosmos", 40, ATOM,
  261. // fiat: u8 code starts at 100 (can be used to filter/sort)
  262. | usd, "U.S. Dollar", 100, USD,
  263. | eur, "Euro", 101, EUR,
  264. | rur, "Ruble", 102, RUR,
  265. | jpy, "Yen", 103, JPY,
  266. | gbp, "Pound", 104, GBP,
  267. | chf, "Franc", 105, CHF,
  268. | cad, "Canadian Dollar", 106, CAD,
  269. | aud, "Australian Dollar", 107, AUD,
  270. | zar, "Rand", 108, ZAR,
  271. | mxn, "Peso", 109, MXN
  272. );
  273. impl Currency {
  274. /// Convert fiat peg to tether equivalent.
  275. ///
  276. /// # Examples
  277. ///
  278. /// ```
  279. /// #[macro_use]
  280. /// extern crate markets;
  281. /// fn main() {
  282. /// assert_eq!(c!(usd).to_tether(), c!(usdt));
  283. /// assert_eq!(c!(btc).to_tether(), c!(btc));
  284. /// }
  285. /// ```
  286. ///
  287. pub fn to_tether(&self) -> Self {
  288. match *self {
  289. c!(usd) => c!(usdt),
  290. other => other
  291. }
  292. }
  293. /// Converts stablecoins into fiat peg.
  294. ///
  295. /// # Examples
  296. ///
  297. /// ```
  298. /// #[macro_use]
  299. /// extern crate markets;
  300. /// fn main() {
  301. /// assert_eq!(c!(usdt).from_tether(), c!(usd));
  302. /// assert_eq!(c!(btc).from_tether(), c!(btc));
  303. /// }
  304. /// ```
  305. ///
  306. pub fn from_tether(&self) -> Self {
  307. match *self {
  308. c!(usdt) => c!(usd),
  309. other => other
  310. }
  311. }
  312. }
  313. /// # Panics
  314. ///
  315. /// The `TryFrom` implementation in this macro will panic it is passed
  316. /// a value greater than the maximum value a `u8` can store.
  317. ///
  318. macro_rules! use_u8_impl_for_int {
  319. ($t:ty, $int:ty) => {
  320. impl From<$t> for $int {
  321. fn from(t: $t) -> Self {
  322. u8::from(t) as $int
  323. }
  324. }
  325. impl TryFrom<$int> for $t {
  326. type Error = Error;
  327. fn try_from(n: $int) -> Result<Self, Self::Error> {
  328. TryFrom::try_from(n as u8)
  329. }
  330. }
  331. }
  332. }
  333. use_u8_impl_for_int!(Currency, i16);
  334. use_u8_impl_for_int!(Exchange, i16);
  335. use_u8_impl_for_int!(Ticker, i16);
  336. use_u8_impl_for_int!(Side, i16);
  337. #[test]
  338. fn check_generated_currency_fns() {
  339. assert_eq!(c!(usd), Currency::usd);
  340. assert_eq!(c!(usd).name(), "U.S. Dollar");
  341. assert_eq!(Currency::try_from(1_u8).unwrap(), c!(btc));
  342. assert_eq!(u8::from(c!(usd)), 100_u8);
  343. assert_eq!(i16::from(c!(usd)), 100_i16);
  344. }
  345. impl <'de> Deserialize<'de> for Currency {
  346. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  347. where D: Deserializer<'de> {
  348. use serde::de::Error;
  349. let c = String::deserialize(deserializer)?;
  350. Currency::from_str(&c).map_err(|err| D::Error::custom(format!("{:?}", err)))
  351. }
  352. }
  353. impl Display for Currency {
  354. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  355. write!(f, "{:?}", self)
  356. }
  357. }
  358. macro_rules! make_exchange {
  359. ($(|$ticker:ident, $name:expr, $code:expr),*) => {
  360. #[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord,
  361. Serialize, Deserialize)]
  362. #[allow(non_camel_case_types)]
  363. #[repr(u8)]
  364. pub enum Exchange {
  365. $($ticker = $code),*
  366. }
  367. impl Exchange {
  368. pub fn name(&self) -> &'static str {
  369. match *self {
  370. $(
  371. e!($ticker) => { $name }
  372. ),*
  373. }
  374. }
  375. pub fn as_str(&self) -> &'static str {
  376. match *self {
  377. $(
  378. e!($ticker) => { stringify!($ticker) }
  379. ),*
  380. }
  381. }
  382. pub fn all() -> Vec<Self> {
  383. vec![
  384. $( e!($ticker) ),*
  385. ]
  386. }
  387. }
  388. impl TryFrom<u8> for Exchange {
  389. type Error = Error;
  390. fn try_from(n: u8) -> Result<Self, Self::Error> {
  391. match n {
  392. $(
  393. $code => { Ok(e!($ticker)) }
  394. ),*
  395. _ => Err(Error::IllegalExchangeCode(n))
  396. }
  397. }
  398. }
  399. impl From<Exchange> for u8 {
  400. fn from(c: Exchange) -> Self {
  401. match c {
  402. $(
  403. e!($ticker) => { $code }
  404. ),*
  405. }
  406. }
  407. }
  408. impl FromStr for Exchange {
  409. type Err = Error;
  410. fn from_str(s: &str) -> Result<Self, Self::Err> {
  411. let t = match s.to_lowercase().trim() {
  412. $(
  413. stringify!($ticker) => { e!($ticker) }
  414. ),*
  415. _ => {
  416. return Err(Error::IllegalExchange);
  417. }
  418. };
  419. Ok(t)
  420. }
  421. }
  422. /// Holds an object of the same type for each exchange.
  423. ///
  424. pub struct ByExchange<T> {
  425. $(
  426. pub $ticker: T
  427. ),*
  428. }
  429. impl<T> ByExchange<T> {
  430. pub fn new(
  431. $(
  432. $ticker: T
  433. ),*
  434. ) -> Self {
  435. Self {
  436. $(
  437. $ticker
  438. ),*
  439. }
  440. }
  441. pub fn of(&self, exchange: &Exchange) -> &T {
  442. match exchange {
  443. $(
  444. &e!($ticker) => &self.$ticker
  445. ),*
  446. }
  447. }
  448. pub fn of_mut(&mut self, exchange: &Exchange) -> &mut T {
  449. match exchange {
  450. $(
  451. &e!($ticker) => &mut self.$ticker
  452. ),*
  453. }
  454. }
  455. // to match map apis:
  456. pub fn get(&self, exchange: &Exchange) -> Option<&T> {
  457. Some(self.of(exchange))
  458. }
  459. pub fn get_mut(&mut self, exchange: &Exchange) -> Option<&mut T> {
  460. Some(self.of_mut(exchange))
  461. }
  462. }
  463. impl<T> Default for ByExchange<T>
  464. where T: Default
  465. {
  466. fn default() -> Self {
  467. ByExchange {
  468. $(
  469. $ticker: Default::default()
  470. ),*
  471. }
  472. }
  473. }
  474. impl<T> Clone for ByExchange<T>
  475. where T: Clone
  476. {
  477. fn clone(&self) -> Self {
  478. ByExchange {
  479. $(
  480. $ticker: self.$ticker.clone()
  481. ),*
  482. }
  483. }
  484. }
  485. }
  486. }
  487. make_exchange!(
  488. | plnx, "Poloniex", 1,
  489. | krkn, "Kraken", 2,
  490. | gdax, "GDAX", 3,
  491. | exmo, "Exmo", 4,
  492. | bits, "Bitstamp", 5,
  493. | bmex, "Bitmex", 6,
  494. | btfx, "Bitfinex", 7,
  495. | bnce, "Binance", 8,
  496. | okex, "OKEx", 9,
  497. | drbt, "Deribit", 10
  498. );
  499. #[test]
  500. fn check_generated_exchange_fns() {
  501. assert_eq!(e!(plnx), Exchange::plnx);
  502. assert_eq!(e!(plnx).name(), "Poloniex");
  503. assert_eq!(Exchange::try_from(2_u8).unwrap(), e!(krkn));
  504. assert_eq!(u8::from(e!(gdax)), 3_u8);
  505. }
  506. impl Display for Exchange {
  507. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  508. write!(f, "{}", self.as_str())
  509. }
  510. }
  511. #[derive(Debug, Clone, Hash, PartialEq, Eq, Copy, PartialOrd, Ord)]
  512. pub struct Ticker {
  513. pub base: Currency,
  514. pub quote: Currency,
  515. }
  516. impl Ticker {
  517. pub fn flip(&self) -> Self {
  518. Ticker { base: self.quote, quote: self.base }
  519. }
  520. pub fn to_s(&self) -> String {
  521. format!("{}", self)
  522. }
  523. }
  524. macro_rules! ticker_to_u8 {
  525. ($(|$base:tt-$quote:tt, $u_base:tt-$u_quote:tt, $n:expr),*) => {
  526. impl From<Ticker> for u8 {
  527. fn from(t: Ticker) -> Self {
  528. match t {
  529. $(
  530. t!($base-$quote) => { $n }
  531. ),*
  532. _ => 0
  533. }
  534. }
  535. }
  536. impl TryFrom<u8> for Ticker {
  537. type Error = Error;
  538. fn try_from(n: u8) -> Result<Self, Self::Error> {
  539. match n {
  540. $(
  541. $n => { Ok(t!($base-$quote)) }
  542. ),*
  543. other => Err(Error::IllegalValue(other))
  544. }
  545. }
  546. }
  547. impl Ticker {
  548. pub fn as_str(&self) -> &'static str {
  549. match *self {
  550. $(
  551. t!($base-$quote) => { concat!(stringify!($base), "_", stringify!($quote)) }
  552. ),*
  553. _ => "xxx_xxx"
  554. }
  555. }
  556. pub fn as_str_dash(&self) -> &'static str {
  557. match *self {
  558. $( t!($base-$quote) => { concat!(stringify!($base), "-", stringify!($quote)) } ),*
  559. _ => "xxx-xxx"
  560. }
  561. }
  562. pub fn to_str_uppercase(&self) -> &'static str {
  563. match *self {
  564. $(
  565. t!($base-$quote) => { concat!(stringify!($u_base), "_", stringify!($u_quote)) }
  566. ),*
  567. _ => "XXX_XXX"
  568. }
  569. }
  570. pub fn to_str_reversed(&self) -> &'static str {
  571. match *self {
  572. $(
  573. t!($base-$quote) => { concat!(stringify!($quote), "_", stringify!($base)) }
  574. ),*
  575. _ => "xxx_xxx"
  576. }
  577. }
  578. pub fn to_str_uppercase_reversed(&self) -> &'static str {
  579. match *self {
  580. $(
  581. t!($base-$quote) => { concat!(stringify!($u_quote), "_", stringify!($u_base)) }
  582. ),*
  583. _ => "XXX_XXX"
  584. }
  585. }
  586. /// t!(btc-usd) -> "BTC-USD"
  587. ///
  588. pub fn to_str_uppercase_dash_sep(&self) -> &'static str {
  589. match *self {
  590. $(
  591. t!($base-$quote) => { concat!(stringify!($u_base), "-", stringify!($u_quote)) }
  592. ),*
  593. _ => "XXX-XXX"
  594. }
  595. }
  596. pub fn as_gdax_str(&self) -> &'static str { self.to_str_uppercase_dash_sep() }
  597. /// Note - this performs the flip and fails for tickers not in the list.
  598. ///
  599. pub fn from_str_uppercase_reverse<'a>(s: &'a str) -> Result<Ticker, UnlistedTicker<'a>> {
  600. match s {
  601. $(
  602. concat!(stringify!($u_quote), "_", stringify!($u_base)) => { Ok(t!($base-$quote)) }
  603. )*
  604. other => Err(UnlistedTicker(other))
  605. }
  606. }
  607. pub fn from_str_uppercase_dash_sep(s: &str) -> Result<Ticker, Error> {
  608. match s {
  609. $(
  610. concat!(stringify!($u_base), "-", stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  611. )*
  612. other => ticker_parse_last_resort(other)
  613. }
  614. }
  615. pub fn from_str_bitfinex(s: &str) -> Result<Ticker, Error> {
  616. match s {
  617. $(
  618. concat!(stringify!($u_base), stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  619. )*
  620. other => ticker_parse_last_resort(other)
  621. }
  622. }
  623. pub fn from_str_bitmex(s: &str) -> Result<Ticker, Error> {
  624. match s {
  625. "XBTUSD" => Ok(t!(btc-usd)),
  626. "ETHUSD" => Ok(t!(eth-usd)),
  627. $(
  628. concat!(stringify!($u_base), stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  629. )*
  630. other => ticker_parse_last_resort(other)
  631. }
  632. }
  633. }
  634. fn ticker_parse_last_resort(other: &str) -> Result<Ticker, Error> {
  635. match other.find('_').or_else(|| other.find('-')).or_else(|| other.find('/')) {
  636. Some(sep) if sep < other.len() - 1 => {
  637. let base = Currency::from_str(&other[..sep])?;
  638. let quote = Currency::from_str(&other[sep + 1..])?;
  639. Ok(Ticker { base, quote })
  640. }
  641. _ => Err(Error::IllegalFormat(Box::new(format!("failed to parse Ticker '{}'", other))))
  642. }
  643. }
  644. impl FromStr for Ticker {
  645. type Err = Error;
  646. fn from_str(s: &str) -> Result<Self, Error> {
  647. match s {
  648. $(
  649. concat!(stringify!($base), "_", stringify!($quote))
  650. //| concat!(stringify!($base), "-", stringify!($quote))
  651. //| concat!(stringify!($base), "/", stringify!($quote))
  652. //| concat!(stringify!($u_base), "_", stringify!($u_quote))
  653. //| concat!(stringify!($u_base), "-", stringify!($u_quote))
  654. //| concat!(stringify!($u_base), "-", stringify!($u_quote))
  655. => { Ok(t!($base-$quote)) }
  656. )*
  657. other => ticker_parse_last_resort(other)
  658. }
  659. }
  660. }
  661. #[test]
  662. fn it_verifies_ticker_lower_and_upper_consistency() {
  663. let tickers = vec![ $(t!($base-$quote)),*];
  664. for t in &tickers {
  665. assert_eq!(t.as_str().to_uppercase(), t.to_str_uppercase().to_string());
  666. }
  667. }
  668. }
  669. }
  670. ticker_to_u8!(
  671. |btc-usd, BTC-USD, 1,
  672. |xmr-usd, XMR-USD, 2,
  673. |eth-usd, ETH-USD, 3,
  674. |ltc-usd, LTC-USD, 4,
  675. |etc-usd, ETC-USD, 5,
  676. |xrp-usd, XRP-USD, 6,
  677. |bch-usd, BCH-USD, 7,
  678. |zec-usd, ZEC-USD, 8,
  679. |dash-usd, DASH-USD, 9,
  680. |rep-usd, REP-USD, 10,
  681. |btc-usdt, BTC-USDT, 11,
  682. |xmr-usdt, XMR-USDT, 12,
  683. |eth-usdt, ETH-USDT, 13,
  684. |ltc-usdt, LTC-USDT, 14,
  685. |etc-usdt, ETC-USDT, 15,
  686. |xrp-usdt, XRP-USDT, 16,
  687. |bch-usdt, BCH-USDT, 17,
  688. |zec-usdt, ZEC-USDT, 18,
  689. |dash-usdt,DASH-USDT,19,
  690. |rep-usdt, REP-USDT, 20,
  691. |xmr-btc, XMR-BTC, 21,
  692. |eth-btc, ETH-BTC, 22,
  693. |ltc-btc, LTC-BTC, 23,
  694. |etc-btc, ETC-BTC, 24,
  695. |xrp-btc, XRP-BTC, 25,
  696. |bch-btc, BCH-BTC, 26,
  697. |zec-btc, ZEC-BTC, 27,
  698. |dash-btc, DASH-BTC, 28,
  699. |rep-btc, REP-BTC, 29,
  700. |zec-eth, ZEC-ETH, 30,
  701. |etc-eth, ETC-ETH, 31,
  702. |bch-eth, BCH-ETH, 32,
  703. |rep-eth, REP-ETH, 33,
  704. |btc-eur, BTC-EUR, 34,
  705. |btc-jpy, BTC-JPY, 35,
  706. |btc-gbp, BTC-GBP, 36,
  707. |btc-cad, BTC-CAD, 37,
  708. |eth-eur, ETH-EUR, 38,
  709. |eth-jpy, ETH-JPY, 39,
  710. |eth-gbp, ETH-GBP, 40,
  711. |eth-cad, ETH-CAD, 41,
  712. |ltc-eur, LTC-EUR, 42,
  713. |ltc-jpy, LTC-JPY, 43,
  714. |ltc-gbp, LTC-GBP, 44,
  715. |ltc-cad, LTC-CAD, 45,
  716. |xmr-eur, XMR-EUR, 46,
  717. |bch-eur, BCH-EUR, 47,
  718. |rep-eur, REP-EUR, 48,
  719. |etc-eur, ETC-EUR, 49,
  720. |usdt-usd, USDT-USD, 50
  721. // Note: a trailing comma will throw off the whole thing.
  722. );
  723. #[test]
  724. fn check_generated_ticker_fns() {
  725. assert_eq!(t!(btc-usd), Ticker { base: Currency::btc, quote: Currency::usd });
  726. assert_eq!(t!(btc-usd).as_str(), "btc_usd");
  727. assert_eq!(t!(eth-jpy).as_str(), "eth_jpy");
  728. assert_eq!(t!(usd-btc).as_str(), "xxx_xxx");
  729. assert_eq!(u8::from(t!(btc-usd)), 1_u8);
  730. assert_eq!(u8::from(t!(btc-cad)), 37_u8);
  731. assert_eq!(Ticker::try_from(1_u8).unwrap(), t!(btc-usd));
  732. assert_eq!(Ticker::try_from(21_u8).unwrap(), t!(xmr-btc));
  733. assert!(Ticker::try_from(121_u8).is_err());
  734. }
  735. impl Into<String> for Ticker {
  736. fn into(self) -> String {
  737. format!("{}_{}", self.base, self.quote)
  738. }
  739. }
  740. impl From<(Currency, Currency)> for Ticker {
  741. fn from(basequote: (Currency, Currency)) -> Self {
  742. let (base, quote) = basequote;
  743. Ticker { base, quote }
  744. }
  745. }
  746. impl Serialize for Ticker {
  747. fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
  748. where S: Serializer {
  749. let s: String = format!("{}_{}", self.base, self.quote);
  750. serializer.serialize_str(&s)
  751. }
  752. }
  753. impl <'de> Deserialize<'de> for Ticker {
  754. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  755. where D: Deserializer<'de> {
  756. let s = String::deserialize(deserializer)?;
  757. Ticker::from_str(s.as_str())
  758. .map_err(|_| de::Error::custom(format!("invalid Ticker '{}'", s)))
  759. }
  760. }
  761. impl Display for Ticker {
  762. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  763. write!(f, "{}_{}", self.base, self.quote)
  764. }
  765. }
  766. #[derive(Debug, Clone)]
  767. pub enum Error {
  768. IllegalCurrency,
  769. IllegalExchange,
  770. IllegalValue(u8),
  771. IllegalFormat(Box<String>),
  772. IllegalExchangeCode(u8),
  773. NaN,
  774. }
  775. impl Display for Error {
  776. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  777. write!(f, "{:?}", self)
  778. }
  779. }
  780. impl ::std::error::Error for Error {
  781. fn description(&self) -> &str { "MoneyError" }
  782. }
  783. #[derive(Debug)]
  784. pub struct UnlistedTicker<'a>(&'a str);
  785. impl<'a> Display for UnlistedTicker<'a> {
  786. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  787. write!(f, "UnlistedTicker({})", self.0)
  788. }
  789. }
  790. #[derive(Debug)]
  791. pub struct UnlistedCurrency<'a>(&'a str);
  792. impl<'a> Display for UnlistedCurrency<'a> {
  793. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  794. write!(f, "UnlistedCurrency({})", self.0)
  795. }
  796. }
  797. #[allow(unused)]
  798. #[cfg(test)]
  799. mod tests {
  800. use super::*;
  801. #[cfg(feature = "unstable")]
  802. use test::{black_box, Bencher};
  803. #[test]
  804. fn ticker_from_str_advanced() {
  805. assert_eq!(Ticker::from_str("btc_usd").unwrap(), t!(btc-usd));
  806. assert_eq!(Ticker::from_str("btc-usd").unwrap(), t!(btc-usd));
  807. assert_eq!(Ticker::from_str("btc/usd").unwrap(), t!(btc-usd));
  808. assert_eq!(Ticker::from_str("BTC_USD").unwrap(), t!(btc-usd));
  809. assert_eq!(Ticker::from_str("BTC-USD").unwrap(), t!(btc-usd));
  810. assert_eq!(Ticker::from_str("BTC/USD").unwrap(), t!(btc-usd));
  811. assert_eq!(Ticker::from_str("USD_BTC").unwrap(), t!(usd-btc));
  812. assert!(Ticker::from_str("not_a_ticker_format").is_err());
  813. }
  814. #[test]
  815. fn it_parses_bcn_ticker() {
  816. let ticker = Ticker::from_str("bcn_btc").unwrap();
  817. }
  818. #[test]
  819. fn it_checks_simple_macro_use() {
  820. let btc_cur = c!(btc);
  821. assert_eq!(btc_cur, Currency::btc);
  822. let plnx_exch = e!(plnx);
  823. assert_eq!(plnx_exch, Exchange::plnx);
  824. let eth_usd_ticker = t!(eth-usd);
  825. assert_eq!(eth_usd_ticker.base, c!(eth));
  826. assert_eq!(eth_usd_ticker.quote, c!(usd));
  827. }
  828. #[cfg(feature = "unstable")]
  829. #[bench]
  830. fn ticker_to_str_obfuscated(b: &mut Bencher) {
  831. let ticker = Ticker::from_str("btc_usd").unwrap();
  832. b.iter(|| {
  833. black_box(ticker.as_str())
  834. });
  835. }
  836. #[cfg(feature = "unstable")]
  837. #[bench]
  838. fn ticker_from_str_bench(b: &mut Bencher) {
  839. let t = black_box(String::from("btc_usd"));
  840. b.iter(||{
  841. Ticker::from_str(&t).unwrap()
  842. });
  843. }
  844. #[cfg(feature = "unstable")]
  845. #[bench]
  846. fn ticker_from_str_bench_last(b: &mut Bencher) {
  847. let t = black_box(String::from("ltc_cad"));
  848. b.iter(||{
  849. Ticker::from_str(&t).unwrap()
  850. });
  851. }
  852. #[cfg(feature = "unstable")]
  853. #[bench]
  854. fn ticker_from_str_bench_catchall(b: &mut Bencher) {
  855. let t = black_box(String::from("usd_zar"));
  856. b.iter(||{
  857. Ticker::from_str(&t).unwrap()
  858. });
  859. }
  860. #[cfg(feature = "unstable")]
  861. #[bench]
  862. fn it_converts_ticker_to_u8(b: &mut Bencher) {
  863. let t = black_box(t!(btc-usd));
  864. b.iter(|| {
  865. u8::from(black_box(t))
  866. });
  867. }
  868. #[cfg(feature = "unstable")]
  869. #[bench]
  870. fn it_converts_ticker_u8_plus_base_and_quote(b: &mut Bencher) {
  871. struct A {
  872. pub ticker: i16,
  873. pub base: i16,
  874. pub quote: i16
  875. }
  876. let t = black_box(t!(btc-usd));
  877. b.iter(|| {
  878. let ticker = u8::from(black_box(t)) as i16;
  879. let base = u8::from(black_box(t.base)) as i16;
  880. let quote = u8::from(black_box(t.quote)) as i16;
  881. A { ticker, base, quote }
  882. });
  883. }
  884. #[test]
  885. fn it_asserts_that_ticker_takes_up_two_bytes() {
  886. assert_eq!(::std::mem::size_of::<Ticker>(), 2);
  887. }
  888. #[test]
  889. fn it_checks_a_currency_to_str_return_value() {
  890. assert_eq!(c!(btc).as_str(), "btc");
  891. }
  892. #[cfg(feature = "unstable")]
  893. #[bench]
  894. fn ticker_to_string(b: &mut Bencher) {
  895. let ticker = t!(btc-usd);
  896. b.iter(|| {
  897. black_box(ticker.to_string())
  898. });
  899. }
  900. #[cfg(feature = "unstable")]
  901. #[bench]
  902. fn ticker_to_s(b: &mut Bencher) {
  903. let ticker = t!(btc-usd);
  904. b.iter(|| {
  905. black_box(ticker.to_s())
  906. });
  907. }
  908. #[cfg(feature = "unstable")]
  909. #[bench]
  910. fn ticker_to_str(b: &mut Bencher) {
  911. let ticker = t!(btc-usd);
  912. b.iter(|| {
  913. black_box(ticker.as_str())
  914. });
  915. }
  916. #[cfg(feature = "unstable")]
  917. #[bench]
  918. fn currency_to_str(b: &mut Bencher) {
  919. let usd_currency = c!(usd);
  920. b.iter(|| {
  921. usd_currency.as_str()
  922. });
  923. }
  924. #[cfg(feature = "unstable")]
  925. #[bench]
  926. fn currency_to_str_uppercase(b: &mut Bencher) {
  927. let usd_currency = c!(usd);
  928. b.iter(|| {
  929. usd_currency.to_str_uppercase()
  930. });
  931. }
  932. }