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.

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