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.

692 lines
17 KiB

use crate::{impl_sqlx_type_display_from_str, olivia};
use anyhow::{Context, Result};
use bdk::bitcoin::{Address, Amount, Denomination};
use chrono::DateTime;
use reqwest::Url;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::de::Error as _;
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::convert::TryInto;
use std::num::NonZeroU8;
use std::ops::{Add, Div, Mul, Sub};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{fmt, str};
use time::{OffsetDateTime, PrimitiveDateTime, Time};
pub mod cfd;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Price of zero is not allowed.")]
ZeroPrice,
#[error("Negative Price is unimplemented.")]
NegativePrice,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Usd(Decimal);
impl Usd {
pub fn new(value: Decimal) -> Self {
Self(value)
}
pub fn try_into_u64(&self) -> Result<u64> {
self.0.to_u64().context("could not fit decimal into u64")
}
pub fn try_into_f64(&self) -> Result<f64> {
self.0.to_f64().context("Could not fit decimal into f64")
}
#[must_use]
pub fn into_decimal(self) -> Decimal {
self.0
}
}
impl fmt::Display for Usd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.round_dp(2).fmt(f)
}
}
impl str::FromStr for Usd {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let dec = Decimal::from_str(s)?;
Ok(Usd(dec))
}
}
impl_sqlx_type_display_from_str!(Usd);
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Price(Decimal);
impl_sqlx_type_display_from_str!(Price);
impl Price {
pub fn new(value: Decimal) -> Result<Self, Error> {
if value == Decimal::ZERO {
return Result::Err(Error::ZeroPrice);
}
if value < Decimal::ZERO {
return Result::Err(Error::NegativePrice);
}
Ok(Self(value))
}
pub fn try_into_u64(&self) -> Result<u64> {
self.0.to_u64().context("Could not fit decimal into u64")
}
pub fn try_into_f64(&self) -> Result<f64> {
self.0.to_f64().context("Could not fit decimal into f64")
}
#[must_use]
pub fn into_decimal(self) -> Decimal {
self.0
}
}
impl fmt::Display for Price {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl str::FromStr for Price {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let dec = Decimal::from_str(s)?;
Ok(Price(dec))
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct InversePrice(Decimal);
impl InversePrice {
pub fn new(value: Price) -> Result<Self, Error> {
if value.0 == Decimal::ZERO {
return Result::Err(Error::ZeroPrice);
}
if value.0 < Decimal::ZERO {
return Result::Err(Error::NegativePrice);
}
Ok(Self(Decimal::ONE / value.0))
}
pub fn try_into_u64(&self) -> Result<u64> {
self.0.to_u64().context("Could not fit decimal into u64")
}
pub fn try_into_f64(&self) -> Result<f64> {
self.0.to_f64().context("Could not fit decimal into f64")
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, sqlx::Type)]
#[sqlx(transparent)]
pub struct Leverage(u8);
impl Leverage {
pub fn new(value: u8) -> Result<Self> {
let val = NonZeroU8::new(value).context("Cannot use non-positive values")?;
Ok(Self(u8::from(val)))
}
pub fn get(&self) -> u8 {
self.0
}
}
// add impl's to do algebra with Usd, Leverage, and ExhangeRate as required
impl Mul<Leverage> for Usd {
type Output = Usd;
fn mul(self, rhs: Leverage) -> Self::Output {
let value = self.0 * Decimal::from(rhs.0);
Self(value)
}
}
impl Div<Leverage> for Usd {
type Output = Usd;
fn div(self, rhs: Leverage) -> Self::Output {
Self(self.0 / Decimal::from(rhs.0))
}
}
impl Mul<Usd> for Leverage {
type Output = Usd;
fn mul(self, rhs: Usd) -> Self::Output {
let value = Decimal::from(self.0) * rhs.0;
Usd(value)
}
}
impl Mul<u8> for Usd {
type Output = Usd;
fn mul(self, rhs: u8) -> Self::Output {
let value = self.0 * Decimal::from(rhs);
Self(value)
}
}
impl Div<u8> for Usd {
type Output = Usd;
fn div(self, rhs: u8) -> Self::Output {
let value = self.0 / Decimal::from(rhs);
Self(value)
}
}
impl Div<u8> for Price {
type Output = Price;
fn div(self, rhs: u8) -> Self::Output {
let value = self.0 / Decimal::from(rhs);
Self(value)
}
}
impl Add<Usd> for Usd {
type Output = Usd;
fn add(self, rhs: Usd) -> Self::Output {
let value = self.0 + rhs.0;
Self(value)
}
}
impl Sub<Usd> for Usd {
type Output = Usd;
fn sub(self, rhs: Usd) -> Self::Output {
let value = self.0 - rhs.0;
Self(value)
}
}
impl Div<Price> for Usd {
type Output = Amount;
fn div(self, rhs: Price) -> Self::Output {
let mut btc = self.0 / rhs.0;
btc.rescale(8);
Amount::from_str_in(&btc.to_string(), Denomination::Bitcoin)
.expect("Error computing BTC amount")
}
}
impl Mul<Leverage> for Price {
type Output = Price;
fn mul(self, rhs: Leverage) -> Self::Output {
let value = self.0 * Decimal::from(rhs.0);
Self(value)
}
}
impl Mul<Price> for Leverage {
type Output = Price;
fn mul(self, rhs: Price) -> Self::Output {
let value = Decimal::from(self.0) * rhs.0;
Price(value)
}
}
impl Div<Leverage> for Price {
type Output = Price;
fn div(self, rhs: Leverage) -> Self::Output {
let value = self.0 / Decimal::from(rhs.0);
Self(value)
}
}
impl Mul<InversePrice> for Usd {
type Output = Amount;
fn mul(self, rhs: InversePrice) -> Self::Output {
let mut btc = self.0 * rhs.0;
btc.rescale(8);
Amount::from_str_in(&btc.to_string(), Denomination::Bitcoin)
.expect("Error computing BTC amount")
}
}
impl Mul<Leverage> for InversePrice {
type Output = InversePrice;
fn mul(self, rhs: Leverage) -> Self::Output {
let value = self.0 * Decimal::from(rhs.0);
Self(value)
}
}
impl Mul<InversePrice> for Leverage {
type Output = InversePrice;
fn mul(self, rhs: InversePrice) -> Self::Output {
let value = Decimal::from(self.0) * rhs.0;
InversePrice(value)
}
}
impl Div<Leverage> for InversePrice {
type Output = InversePrice;
fn div(self, rhs: Leverage) -> Self::Output {
let value = self.0 / Decimal::from(rhs.0);
Self(value)
}
}
impl Add<Price> for Price {
type Output = Price;
fn add(self, rhs: Price) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub<Price> for Price {
type Output = Price;
fn sub(self, rhs: Price) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Add<InversePrice> for InversePrice {
type Output = InversePrice;
fn add(self, rhs: InversePrice) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub<InversePrice> for InversePrice {
type Output = InversePrice;
fn sub(self, rhs: InversePrice) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Add<u8> for Leverage {
type Output = Leverage;
fn add(self, rhs: u8) -> Self::Output {
Self(self.0 + rhs)
}
}
impl Add<Leverage> for u8 {
type Output = Leverage;
fn add(self, rhs: Leverage) -> Self::Output {
Leverage(self + rhs.0)
}
}
impl Div<Leverage> for Leverage {
type Output = Decimal;
fn div(self, rhs: Leverage) -> Self::Output {
Decimal::from(self.0) / Decimal::from(rhs.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct Percent(Decimal);
impl Percent {
#[must_use]
pub fn round_dp(self, digits: u32) -> Self {
Self(self.0.round_dp(digits))
}
}
impl fmt::Display for Percent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.round_dp(2).fmt(f)
}
}
impl From<Decimal> for Percent {
fn from(decimal: Decimal) -> Self {
Percent(decimal)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, sqlx::Type)]
pub enum TradingPair {
BtcUsd,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, sqlx::Type)]
pub enum Position {
Long,
Short,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Identity(x25519_dalek::PublicKey);
impl Identity {
pub fn new(key: x25519_dalek::PublicKey) -> Self {
Self(key)
}
}
impl Serialize for Identity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Identity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let hex = String::deserialize(deserializer)?;
let mut bytes = [0u8; 32];
hex::decode_to_slice(&hex, &mut bytes).map_err(D::Error::custom)?;
Ok(Self(x25519_dalek::PublicKey::from(bytes)))
}
}
impl fmt::Display for Identity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", hex::encode(self.0.as_bytes()))
}
}
impl str::FromStr for Identity {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut key = [0u8; 32];
hex::decode_to_slice(s, &mut key)?;
Ok(Self(key.into()))
}
}
impl_sqlx_type_display_from_str!(Identity);
#[derive(Debug, Clone)]
pub struct WalletInfo {
pub balance: Amount,
pub address: Address,
pub last_updated_at: Timestamp,
}
#[derive(
Debug, Clone, Copy, SerializeDisplay, DeserializeFromStr, PartialEq, Eq, Hash, PartialOrd, Ord,
)]
pub struct BitMexPriceEventId {
/// The timestamp this price event refers to.
timestamp: OffsetDateTime,
digits: usize,
}
impl BitMexPriceEventId {
pub fn new(timestamp: OffsetDateTime, digits: usize) -> Self {
let (hours, minutes, seconds) = timestamp.time().as_hms();
let time_without_nanos =
Time::from_hms(hours, minutes, seconds).expect("original timestamp was valid");
let timestamp_without_nanos = timestamp.replace_time(time_without_nanos);
Self {
timestamp: timestamp_without_nanos,
digits,
}
}
pub fn with_20_digits(timestamp: OffsetDateTime) -> Self {
Self::new(timestamp, 20)
}
/// Checks whether this event has likely already occurred.
///
/// We can't be sure about it because our local clock might be off from the oracle's clock.
pub fn has_likely_occured(&self) -> bool {
let now = OffsetDateTime::now_utc();
now > self.timestamp
}
pub fn to_olivia_url(self) -> Url {
"https://h00.ooo"
.parse::<Url>()
.expect("valid URL from constant")
.join(&self.to_string())
.expect("Event id can be joined")
}
pub fn timestamp(&self) -> OffsetDateTime {
self.timestamp
}
}
impl fmt::Display for BitMexPriceEventId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"/x/BitMEX/BXBT/{}.price?n={}",
self.timestamp
.format(&olivia::EVENT_TIME_FORMAT)
.expect("should always format and we can't return an error here"),
self.digits
)
}
}
impl str::FromStr for BitMexPriceEventId {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let remaining = s.trim_start_matches("/x/BitMEX/BXBT/");
let (timestamp, rest) = remaining.split_at(19);
let digits = rest.trim_start_matches(".price?n=");
Ok(Self {
timestamp: PrimitiveDateTime::parse(timestamp, &olivia::EVENT_TIME_FORMAT)
.with_context(|| format!("Failed to parse {} as timestamp", timestamp))?
.assume_utc(),
digits: digits.parse()?,
})
}
}
impl_sqlx_type_display_from_str!(BitMexPriceEventId);
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, sqlx::Type)]
pub struct Timestamp(i64);
impl Timestamp {
pub fn new(seconds: i64) -> Self {
Self(seconds)
}
pub fn now() -> Self {
let seconds: i64 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time not to go backwards")
.as_secs()
.try_into()
.expect("seconds of system time to fit into i64");
Self(seconds)
}
pub fn parse_from_rfc3339(datetime_str: &str) -> Result<Self> {
let datetime = DateTime::parse_from_rfc3339(datetime_str)
.context("Unable to parse datetime as RFC3339")?;
let seconds = datetime.timestamp();
Ok(Self(seconds))
}
pub fn seconds(&self) -> i64 {
self.0
}
pub fn seconds_u64(&self) -> Result<u64> {
let out = self.0.try_into().context("Unable to convert i64 to u64")?;
Ok(out)
}
}
#[cfg(test)]
mod tests {
use rust_decimal_macros::dec;
use time::macros::datetime;
use super::*;
#[test]
fn to_olivia_url() {
let url = BitMexPriceEventId::with_20_digits(datetime!(2021-09-23 10:00:00).assume_utc())
.to_olivia_url();
assert_eq!(
url,
"https://h00.ooo/x/BitMEX/BXBT/2021-09-23T10:00:00.price?n=20"
.parse()
.unwrap()
);
}
#[test]
fn parse_event_id() {
let parsed = "/x/BitMEX/BXBT/2021-09-23T10:00:00.price?n=20"
.parse::<BitMexPriceEventId>()
.unwrap();
let expected =
BitMexPriceEventId::with_20_digits(datetime!(2021-09-23 10:00:00).assume_utc());
assert_eq!(parsed, expected);
}
#[test]
fn new_event_has_no_nanos() {
let now = BitMexPriceEventId::with_20_digits(OffsetDateTime::now_utc());
assert_eq!(now.timestamp.nanosecond(), 0);
}
#[test]
fn has_occured_if_in_the_past() {
let past_event =
BitMexPriceEventId::with_20_digits(datetime!(2021-09-23 10:00:00).assume_utc());
assert!(past_event.has_likely_occured());
}
#[test]
fn algebra_with_usd() {
let usd_0 = Usd::new(dec!(1.234));
let usd_1 = Usd::new(dec!(9.876));
let usd_sum = usd_0 + usd_1;
let usd_diff = usd_0 - usd_1;
let half = usd_0 / 2;
let double = usd_1 * 2;
assert_eq!(usd_sum.0, dec!(11.110));
assert_eq!(usd_diff.0, dec!(-8.642));
assert_eq!(half.0, dec!(0.617));
assert_eq!(double.0, dec!(19.752));
}
#[test]
fn usd_for_1_btc_buys_1_btc() {
let usd = Usd::new(dec!(61234.5678));
let price = Price::new(dec!(61234.5678)).unwrap();
let inv_price = InversePrice::new(price).unwrap();
let res_0 = usd / price;
let res_1 = usd * inv_price;
assert_eq!(res_0, Amount::ONE_BTC);
assert_eq!(res_1, Amount::ONE_BTC);
}
#[test]
fn leverage_does_not_alter_type() {
let usd = Usd::new(dec!(61234.5678));
let leverage = Leverage::new(3).unwrap();
let res = usd * leverage / leverage;
assert_eq!(res.0, usd.0);
}
#[test]
fn test_algebra_with_types() {
let usd = Usd::new(dec!(61234.5678));
let leverage = Leverage::new(5).unwrap();
let price = Price::new(dec!(61234.5678)).unwrap();
let expected_buyin = Amount::from_str_in("0.2", Denomination::Bitcoin).unwrap();
let liquidation_price = price * leverage / (leverage + 1);
let inv_price = InversePrice::new(price).unwrap();
let inv_liquidation_price = InversePrice::new(liquidation_price).unwrap();
let long_buyin = usd / (price * leverage);
let long_payout =
(usd / leverage) * ((leverage + 1) * inv_price - leverage * inv_liquidation_price);
assert_eq!(long_buyin, expected_buyin);
assert_eq!(long_payout, Amount::ZERO);
}
#[test]
fn test_timestamp() {
let datetime_str_a = "1999-12-31T23:59:00.00Z";
let datetime_str_b = "1999-12-31T23:59:00.00+10:00";
let ts_a = Timestamp::parse_from_rfc3339(datetime_str_a).unwrap();
let ts_b = Timestamp::parse_from_rfc3339(datetime_str_b).unwrap();
assert_eq!(ts_b.seconds() - ts_a.seconds(), -36000);
}
#[test]
fn roundtrip_taker_id_serde() {
let id = Identity::new(x25519_dalek::PublicKey::from([42u8; 32]));
serde_test::assert_tokens(
&id,
&[serde_test::Token::String(
"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a",
)],
);
}
}