Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions examples/binance/market_futures.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
use std::env;

use v_exchanges::{binance::Binance, core::Exchange};
use v_exchanges::{
binance::{self},
core::{Exchange, MarketTrait as _},
};

#[tokio::main]
async fn main() {
color_eyre::install().unwrap();
v_utils::utils::init_subscriber(v_utils::utils::LogDestination::xdg("v_exchanges"));

let mut bn = Binance::default();
let m = binance::Market::Futures;
let mut bn = m.client();

let klines = bn.futures_klines(("BTC", "USDT").into(), "1m".into(), 2.into()).await.unwrap();
let price = bn.futures_price(("BTC", "USDT").into()).await.unwrap();
let klines = bn.klines(("BTC", "USDT").into(), "1m".into(), 2.into(), m).await.unwrap();
let price = bn.price(("BTC", "USDT").into(), m).await.unwrap();
dbg!(&klines, price);

if let (Ok(key), Ok(secret)) = (env::var("BINANCE_TIGER_READ_KEY"), env::var("BINANCE_TIGER_READ_SECRET")) {
bn.auth(key, secret);
let balance = bn.futures_asset_balance("USDT".into()).await.unwrap();
let balance = bn.asset_balance("USDT".into(), m).await.unwrap();
dbg!(&balance);
} else {
eprintln!("BINANCE_TIGER_READ_KEY or BINANCE_TIGER_READ_SECRET is missing, skipping private API methods.");
Expand Down
13 changes: 9 additions & 4 deletions examples/binance/market_spot.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use v_exchanges::{binance::Binance, core::Exchange};
use v_exchanges::{
binance::{self},
core::{Exchange, MarketTrait as _},
};

#[tokio::main]
async fn main() {
color_eyre::install().unwrap();
v_utils::utils::init_subscriber(v_utils::utils::LogDestination::xdg("v_exchanges"));
let bn = Binance::default();

let spot_klines = bn.spot_klines(("BTC", "USDT").into(), "1m".into(), 2.into()).await.unwrap();
let m = binance::Market::Spot;
let bn = m.client();

let spot_klines = bn.klines(("BTC", "USDT").into(), "1m".into(), 2.into(), m).await.unwrap();
dbg!(&spot_klines);

let spot_prices = bn.spot_prices(None).await.unwrap();
let spot_prices = bn.prices(None, m).await.unwrap();
dbg!(&spot_prices[..5]);
}
14 changes: 8 additions & 6 deletions examples/bybit/market.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::env;

use v_exchanges::{bybit::Bybit, core::Exchange};
use v_exchanges::{bybit::{self, Bybit}, core::{Exchange, MarketTrait as _}};

#[tokio::main]
async fn main() {
color_eyre::install().unwrap();
v_utils::utils::init_subscriber(v_utils::utils::LogDestination::xdg("v_exchanges"));

let mut bb = Bybit::default();
//let m: Market = "Bybit/Linear".into(); // would be nice to be able to do it like this
let m = bybit::Market::Linear;
let mut bb = m.client();

//let ticker: serde_json::Value =
//bb.get("/v5/market/tickers", &[("category", "spot"), ("symbol", "BTCUSDT")], [BybitOption::Default])
Expand All @@ -17,22 +19,22 @@ async fn main() {

//let klines = bb.futures_klines(("BTC", "USDT").into(), "1m".into(), 2.into()).await.unwrap();
//dbg!(&klines);
let price = bb.futures_price(("BTC", "USDT").into()).await.unwrap();
let price = bb.price(("BTC", "USDT").into(), m).await.unwrap();
dbg!(&price);

if let (Ok(key), Ok(secret)) = (env::var("BYBIT_TIGER_READ_KEY"), env::var("BYBIT_TIGER_READ_SECRET")) {
bb.auth(key, secret);
private(&mut bb).await;
private(&mut bb, m).await;
} else {
eprintln!("BYBIT_TIGER_READ_KEY or BYBIT_TIGER_READ_SECRET is missing, skipping private API methods.");
}
}

async fn private(bb: &mut Bybit) {
async fn private(bb: &mut Bybit, m: bybit::Market) {
//let key_permissions: serde_json::Value = bb.get_no_query("/v5/user/query-api", [BybitOption::HttpAuth(BybitHttpAuth::V3AndAbove)])
// .await
// .unwrap();

let balances = bb.futures_balances().await.unwrap();
let balances = bb.balances(m).await.unwrap();
dbg!(&balances);
}
49 changes: 31 additions & 18 deletions v_exchanges/src/binance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,44 @@ pub struct Binance(pub Client);

//? currently client ends up importing this from crate::binance, but could it be possible to lift the [Client] reexport up, and still have the ability to call all exchange methods right on it?
impl Exchange for Binance {
type M = Market;

fn auth<S: Into<String>>(&mut self, key: S, secret: S) {
self.update_default_option(BinanceOption::Key(key.into()));
self.update_default_option(BinanceOption::Secret(secret.into()));
}

async fn spot_klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange) -> Result<Klines> {
market::klines(&self.0, pair, tf, range, Market::Spot).await
}

async fn spot_prices(&self, pairs: Option<Vec<Pair>>) -> Result<Vec<(Pair, f64)>> {
spot::market::prices(&self.0, pairs).await
async fn klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange, m: Self::M) -> Result<Klines> {
market::klines(&self.0, pair, tf, range, m).await
}

async fn spot_price(&self, pair: Pair) -> Result<f64> {
spot::market::price(&self.0, pair).await
async fn prices(&self, pairs: Option<Vec<Pair>>, m: Self::M) -> Result<Vec<(Pair, f64)>> {
match m {
Market::Spot => spot::market::prices(&self.0, pairs).await,
_ => unimplemented!(),
}
}

async fn futures_klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange) -> Result<Klines> {
market::klines(&self.0, pair, tf, range, Market::Futures).await
async fn price(&self, pair: Pair, m: Self::M) -> Result<f64> {
match m {
Market::Spot => spot::market::price(&self.0, pair).await,
Market::Futures => futures::market::price(&self.0, pair).await,
_ => unimplemented!(),
}
}

async fn futures_price(&self, pair: Pair) -> Result<f64> {
futures::market::price(&self.0, pair).await
async fn asset_balance(&self, asset: Asset, m: Self::M) -> Result<AssetBalance> {
match m {
Market::Futures => futures::account::asset_balance(self, asset).await,
_ => unimplemented!(),
}
}

async fn futures_asset_balance(&self, asset: Asset) -> Result<AssetBalance> {
futures::account::asset_balance(&self.0, asset).await
}

async fn futures_balances(&self) -> Result<Vec<AssetBalance>> {
futures::account::balances(&self.0).await
async fn balances(&self, m: Self::M) -> Result<Vec<AssetBalance>> {
match m {
Market::Futures => futures::account::balances(&self.0).await,
_ => unimplemented!(),
}
}
}

Expand All @@ -56,3 +63,9 @@ pub enum Market {
Spot,
Margin,
}
impl crate::core::MarketTrait for Market {
type Client = Binance;
fn client(&self) -> Binance {
Binance::default()
}
}
48 changes: 29 additions & 19 deletions v_exchanges/src/bybit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,44 @@ pub struct Bybit(pub Client);

//? currently client ends up importing this from crate::binance, but could it be possible to lift the [Client] reexport up, and still have the ability to call all exchange methods right on it?
impl Exchange for Bybit {
type M = Market;

fn auth<S: Into<String>>(&mut self, key: S, secret: S) {
self.update_default_option(BybitOption::Key(key.into()));
self.update_default_option(BybitOption::Secret(secret.into()));
}

async fn spot_klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange) -> Result<Klines> {
todo!();
async fn klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange, m: Self::M) -> Result<Klines> {
match m {
Market::Linear => market::klines(&self.0, pair, tf, range).await,
_ => unimplemented!(),
}
}

async fn spot_prices(&self, pairs: Option<Vec<Pair>>) -> Result<Vec<(Pair, f64)>> {
todo!();
async fn price(&self, pair: Pair, m: Self::M) -> Result<f64> {
match m {
Market::Linear => market::price(&self.0, pair).await,
_ => unimplemented!(),
}
}

async fn spot_price(&self, symbol: Pair) -> Result<f64> {
async fn prices(&self, pairs: Option<Vec<Pair>>, m: Self::M) -> Result<Vec<(Pair, f64)>> {
todo!();
}

async fn futures_klines(&self, symbol: Pair, tf: Timeframe, range: KlinesRequestRange) -> Result<Klines> {
market::klines(&self.0, symbol, tf, range).await
}

async fn futures_price(&self, symbol: Pair) -> Result<f64> {
market::price(&self.0, symbol).await
async fn asset_balance(&self, asset: Asset, m: Self::M) -> Result<AssetBalance> {
match m {
Market::Linear => account::asset_balance(&self.0, asset).await,
_ => unimplemented!(),
}
}

async fn futures_asset_balance(&self, asset: Asset) -> Result<AssetBalance> {
account::asset_balance(&self.0, asset).await
async fn balances(&self, m: Self::M) -> Result<Vec<AssetBalance>> {
match m{
Market::Linear => account::balances(&self.0).await,
_ => unimplemented!(),
}
}

async fn futures_balances(&self) -> Result<Vec<AssetBalance>> {
account::balances(&self.0).await
}

//DO: async fn balance(&self,
}


Expand All @@ -59,3 +63,9 @@ pub enum Market {
Spot,
Inverse,
}
impl crate::core::MarketTrait for Market {
type Client = Bybit;
fn client(&self) -> Bybit {
Bybit::default()
}
}
85 changes: 76 additions & 9 deletions v_exchanges/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@ use v_utils::trades::{Asset, Kline, Pair, Timeframe};
//TODO!!!!!!!!!!!!!: klines switch to defining the range via an Enum over either limit either start and end times

pub trait Exchange {
fn auth<S: Into<String>>(&mut self, key: S, secret: S);
type M: MarketTrait;

fn spot_klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange) -> impl std::future::Future<Output = Result<Klines>> + Send;
fn spot_price(&self, pair: Pair) -> impl std::future::Future<Output = Result<f64>> + Send;
/// If no symbols are specified, returns all spot prices.
fn spot_prices(&self, pairs: Option<Vec<Pair>>) -> impl std::future::Future<Output = Result<Vec<(Pair, f64)>>> + Send;
fn auth<S: Into<String>>(&mut self, key: S, secret: S);

//? should I have Self::Pair too? Like to catch the non-existent ones immediately? Although this would increase the error surface on new listings.
fn futures_klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange) -> impl std::future::Future<Output = Result<Klines>> + Send;
fn futures_price(&self, pair: Pair) -> impl std::future::Future<Output = Result<f64>> + Send;
fn klines(&self, pair: Pair, tf: Timeframe, range: KlinesRequestRange, m: Self::M) -> impl std::future::Future<Output = Result<Klines>> + Send;

/// If no pairs are specified, returns for all;
fn prices(&self, pairs: Option<Vec<Pair>>, m: Self::M) -> impl std::future::Future<Output = Result<Vec<(Pair, f64)>>> + Send;
fn price(&self, pair: Pair, m: Self::M) -> impl std::future::Future<Output = Result<f64>> + Send;

// Defined in terms of actors
//TODO!!!: fn spawn_klines_listener(&self, symbol: Pair, tf: Timeframe) -> mpsc::Receiver<Kline>;

/// balance of a specific asset
fn futures_asset_balance(&self, asset: Asset) -> impl std::future::Future<Output = Result<AssetBalance>> + Send;
fn asset_balance(&self, asset: Asset, m: Self::M) -> impl std::future::Future<Output = Result<AssetBalance>> + Send;
/// vec of balances of specific assets
fn futures_balances(&self) -> impl std::future::Future<Output = Result<Vec<AssetBalance>>> + Send;
fn balances(&self, m: Self::M) -> impl std::future::Future<Output = Result<Vec<AssetBalance>>> + Send;
//? potentially `total_balance`? Would return precompiled USDT-denominated balance of a (bybit::wallet/binance::account)
// balances are defined for each margin type: [futures_balance, spot_balance, margin_balance], but note that on some exchanges, (like bybit), some of these may point to the same exact call
// to negate confusion could add a `total_balance` endpoint
Expand Down Expand Up @@ -147,3 +147,70 @@ pub struct AssetBalance {
//margin_available: bool,
pub timestamp: i64,
}

pub trait MarketTrait {
type Client: Exchange;
fn client(&self) -> Self::Client;
}

//#[derive(Debug, Clone, Copy)]
//pub enum Market {
// Binance(binance::Market),
// Bybit(bybit::Market),
// //TODO
//}
//impl Default for Market {
// fn default() -> Self {
// Self::Binance(binance::Market::default())
// }
//}
//
//impl std::fmt::Display for Market {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// match self {
// Market::Binance(m) => write!(f, "Binance/{}", m),
// Market::Bybit(m) => write!(f, "Bybit/{}", m),
// }
// }
//}
//
//impl std::str::FromStr for Market {
// type Err = eyre::Error;
//
// fn from_str(s: &str) -> Result<Self> {
// let parts: Vec<&str> = s.split('/').collect();
// if parts.len() != 2 {
// return Err(eyre::eyre!("Invalid market string: {}", s));
// }
// let exchange = parts[0];
// let sub_market = parts[1];
// match exchange.to_lowercase().as_str() {
// "binance" => Ok(Self::Binance(sub_market.parse()?)),
// "bybit" => Ok(Self::Bybit({
// match sub_market.parse() {
// Ok(m) => m,
// Err(e) => match sub_market.to_lowercase() == "futures" {
// true => bybit::Market::Linear,
// false => bail!(e),
// }
// }
// })),
// _ => bail!("Invalid market string: {}", s),
// }
// }
//}
//impl From<Market> for String {
// fn from(value: Market) -> Self {
// value.to_string()
// }
//}
//impl From<String> for Market {
// fn from(value: String) -> Self {
// value.parse().unwrap()
// }
//}
//impl From<&str> for Market {
// fn from(value: &str) -> Self {
// value.parse().unwrap()
// }
//}
Loading
Loading