Skip to content

Commit

Permalink
add order_books
Browse files Browse the repository at this point in the history
  • Loading branch information
LimpidCrypto committed Jul 17, 2024
1 parent bb21c75 commit 1eed83b
Show file tree
Hide file tree
Showing 42 changed files with 3,561 additions and 23 deletions.
31 changes: 31 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
{
"name": "Rust",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye"

// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
// "mounts": [
// {
// "source": "devcontainer-cargo-cache-${devcontainerId}",
// "target": "/usr/local/cargo",
// "type": "volume"
// }
// ]

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "rustc --version",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
target
target_ci
target_ci_stable
Cargo.lock
third_party
/Cargo.toml
out/

.idea
.vscode
# .vscode
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Cargo test",
"cargo": {
"args": [
"test",
"--no-run",
"--lib"
]
},
"args": []
}
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"editor.formatOnSave": true,
"files.autoSave": "onFocusChange",
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
File renamed without changes.
30 changes: 30 additions & 0 deletions trading-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "trading-lib"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.86"
rust_decimal = "1.35.0"
thiserror = "1.0.62"

[dependencies.xrpl-rust]
git = "https://github.com/sephynox/xrpl-rust.git"
branch = "dev"
optional = true

[dev-dependencies]
rand = "0.8.5"

[features]
default = ["arbitrage", "market-maker", "xrpl"]
xrpl = ["xrpl-rust", "automated-market-maker"]
arbitrage = []
market-maker = []
automated-market-maker = []

[[test]]
name = "unit_tests"
path = "tests/unit/mod.rs"
File renamed without changes.
2 changes: 1 addition & 1 deletion xrplt/README.md → trading-lib/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
A library providing utilities for arbitrage trading and market making on the XRPL.
A library providing utilities for arbitrage trading and market making.
File renamed without changes.
1 change: 1 addition & 0 deletions xrplt/src/lib.rs → trading-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod models;
pub mod order_books;
pub mod trading_types;
pub mod utils;
49 changes: 49 additions & 0 deletions trading-lib/src/models/currency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::borrow::Cow;

extern crate xrpl;

use xrpl::models::amount::{Amount, IssuedCurrencyAmount, XRPAmount};
#[cfg(feature = "xrpl")]
use xrpl::models::currency::Currency as XRPLCurrency;

#[derive(Debug, Clone, PartialEq)]
pub struct Currency<'a> {
pub currency_code: Cow<'a, str>,
pub issuer: Cow<'a, str>,
pub transfer_fee: f32,
}

#[cfg(feature = "xrpl")]
impl<'a> Currency<'a> {
pub fn get_xrpl_amount(&self, amount: Cow<'a, str>) -> Amount<'a> {
match self.currency_code.as_ref() {
"XRP" => XRPAmount(amount).into(),
_ => IssuedCurrencyAmount::new(self.currency_code.clone(), self.issuer.clone(), amount)
.into(),
}
}
}

#[cfg(feature = "xrpl")]
impl<'a> Currency<'a> {
pub fn from_xrpl(currency: XRPLCurrency<'a>, transfer_fee: Option<f32>) -> Self {
match currency {
XRPLCurrency::XRP(_) => Self {
currency_code: "XRP".into(),
issuer: "".into(),
transfer_fee: 0.0,
},
XRPLCurrency::IssuedCurrency(issued_currency) => Self {
currency_code: issued_currency.currency,
issuer: issued_currency.issuer,
transfer_fee: transfer_fee.unwrap_or(0.0),
},
}
}
}

impl Currency<'_> {
pub fn is_same_currency(&self, other: &Self) -> bool {
self.currency_code == other.currency_code
}
}
1 change: 1 addition & 0 deletions trading-lib/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod currency;
7 changes: 7 additions & 0 deletions trading-lib/src/order_books/exceptions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use thiserror::Error;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)]
pub enum OrderBookException {
#[error("Invalid order")]
InvalidOrder,
}
38 changes: 38 additions & 0 deletions trading-lib/src/order_books/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
pub mod exceptions;
pub mod order;
pub mod order_book;

use order_book::OrderBook;

#[derive(Debug)]
pub struct OrderBooks<'a> {
pub order_books: Vec<OrderBook<'a>>,
pub liquidity_spread: f64,
}

impl<'a> OrderBooks<'a> {
pub fn sort(&mut self) {
self.order_books
.iter_mut()
.for_each(|order_book| order_book.sort());
}
}

pub trait IsLiquid {
/// Returns true if the order book is liquid determained based on the provided `liquidity_spread`.
fn is_liquid(&self, liquidity_spread: f64) -> bool;
}

pub trait Flip {
fn flip(&mut self);

fn get_flipped(&self) -> Self
where
Self: Sized + Clone,
{
let mut flipped = self.clone();
flipped.flip();

flipped
}
}
164 changes: 164 additions & 0 deletions trading-lib/src/order_books/order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use std::{cmp::Ordering, mem::swap};

use rust_decimal::{prelude::FromPrimitive, Decimal};
#[cfg(feature = "xrpl")]
use xrpl::models::{
amount::Amount,
currency::{Currency as XRPLCurrency, IssuedCurrency, XRP},
ledger::Offer,
transactions::{OfferCreate, OfferCreateFlag},
FlagCollection,
};

use crate::models::currency::Currency;

use super::Flip;

#[derive(Debug, Clone)]
pub struct Order<'a> {
pub base: Currency<'a>,
pub counter: Currency<'a>,
pub base_quantity: Decimal,
pub rate: Decimal,
}

impl Flip for Order<'_> {
fn flip(&mut self) {
self.rate = Decimal::from(1) / self.rate;
self.base_quantity = self.base_quantity * self.rate;
swap(&mut self.base, &mut self.counter);
}
}

impl PartialEq for Order<'_> {
fn eq(&self, other: &Self) -> bool {
self.rate == other.rate
}
}

impl Eq for Order<'_> {}

impl PartialOrd for Order<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.rate.partial_cmp(&other.rate)
}
}

impl Ord for Order<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.rate.cmp(&other.rate)
}
}

impl Order<'_> {
pub fn calculate_counter_quantity_after_fee(&self) -> Decimal {
(self.base_quantity * self.rate)
* Decimal::from_f32(1.0 - self.counter.transfer_fee).unwrap()
}
}

#[cfg(feature = "xrpl")]
impl<'a> From<Offer<'a>> for Order<'a> {
fn from(offer: Offer<'a>) -> Self {
let (taker_gets_currency, taker_gets_amount): (XRPLCurrency, String) =
match offer.taker_gets {
Amount::XRPAmount(xrp) => (XRP::new().into(), xrp.0.into()),
Amount::IssuedCurrencyAmount(issued_currency) => (
IssuedCurrency::new(issued_currency.currency, issued_currency.issuer).into(),
issued_currency.value.into(),
),
};
let (taker_pays_currency, taker_pays_amount): (XRPLCurrency, String) =
match offer.taker_pays {
Amount::XRPAmount(xrp) => (XRP::new().into(), xrp.0.into()),
Amount::IssuedCurrencyAmount(issued_currency) => (
IssuedCurrency::new(issued_currency.currency, issued_currency.issuer).into(),
issued_currency.value.into(),
),
};
let taker_gets_amount: Decimal = taker_gets_amount.parse().unwrap();
let taker_pays_amount: Decimal = taker_pays_amount.parse().unwrap();

Self {
base: Currency::from_xrpl(taker_pays_currency, None),
counter: Currency::from_xrpl(taker_gets_currency, None),
base_quantity: taker_pays_amount,
rate: taker_gets_amount / taker_pays_amount,
}
}
}

#[cfg(feature = "xrpl")]
impl<'a> Into<OfferCreate<'a>> for Order<'a> {
fn into(self) -> OfferCreate<'a> {
let base_qty = self.base_quantity.to_string();
let counter_qty = self.calculate_counter_quantity_after_fee().to_string();

OfferCreate::new(
"".into(),
None,
None,
Some(FlagCollection::new(vec![
OfferCreateFlag::TfImmediateOrCancel,
OfferCreateFlag::TfSell,
])),
None,
None,
None,
None,
None,
None,
self.counter.get_xrpl_amount(counter_qty.into()),
self.base.get_xrpl_amount(base_qty.into()),
None,
None,
)
}
}

#[cfg(test)]
mod order_tests {
use rust_decimal::{prelude::FromPrimitive, Decimal};
use xrpl::models::{
amount::{IssuedCurrencyAmount, XRPAmount},
FlagCollection,
};

use super::*;

#[test]
#[cfg(feature = "xrpl")]
fn test_from_offer() {
let offer = Offer::new(
FlagCollection::new(Vec::new()),
None,
None,
"r".into(),
"1".into(),
"10".into(),
"".into(),
"".into(),
0,
0,
IssuedCurrencyAmount::new("USD".into(), "issuer".into(), "10".into()).into(),
XRPAmount("20".into()).into(),
None,
);
let order = Order::from(offer);
assert_eq!(
order,
Order {
base: Currency::from_xrpl(XRPLCurrency::XRP(XRP::new()), None),
counter: Currency::from_xrpl(
XRPLCurrency::IssuedCurrency(IssuedCurrency::new(
"USD".into(),
"issuer".into()
)),
None
),
base_quantity: Decimal::from(20),
rate: Decimal::from_f32(0.5).unwrap(),
}
);
}
}
Loading

0 comments on commit 1eed83b

Please sign in to comment.