|
1 | 1 | use std::str::FromStr; |
| 2 | +use v_utils::macros::ScreamIt; |
2 | 3 |
|
3 | 4 | use color_eyre::eyre::Result; |
4 | 5 | use serde::{Deserialize, Serialize}; |
| 6 | +use serde_json::Value; |
| 7 | +use v_exchanges_adapters::bybit::{BybitHttpAuth, BybitOption}; |
| 8 | +use serde_with::{serde_as, DisplayFromStr}; |
5 | 9 |
|
6 | 10 | use crate::core::AssetBalance; |
| 11 | +use v_utils::trades::Asset; |
7 | 12 |
|
| 13 | +pub async fn asset_balance(client: &v_exchanges_adapters::Client, asset: Asset) -> Result<AssetBalance> { |
| 14 | + let balances = balances(client).await?; |
| 15 | + let balance = balances.into_iter().find(|b| b.asset == asset).unwrap(); |
| 16 | + Ok(balance) |
| 17 | +} |
| 18 | + |
| 19 | +/// Should be calling https://bybit-exchange.github.io/docs/v5/asset/balance/all-balance, but with how I'm registered on bybit, my key doesn't have permissions for that (they require it to be able to `transfer` for some reason) |
8 | 20 | pub async fn balances(client: &v_exchanges_adapters::Client) -> Result<Vec<AssetBalance>> { |
9 | | - println!("bybit::account::balances"); |
10 | | - todo!(); |
| 21 | + let value: serde_json::Value = client |
| 22 | + .get("/v5/account/wallet-balance", &[("accountType", "UNIFIED")], [BybitOption::HttpAuth(BybitHttpAuth::V3AndAbove)]) |
| 23 | + .await?; |
| 24 | + |
| 25 | + let account_response: AccountResponse = serde_json::from_value(value)?; |
| 26 | + assert_eq!(account_response.result.list.len(), 1); |
| 27 | + let account_info = account_response.result.list.first().unwrap(); |
| 28 | + |
| 29 | + let mut balances = Vec::new(); |
| 30 | + for r in &account_info.coin { |
| 31 | + balances.push( |
| 32 | + AssetBalance { |
| 33 | + asset: (&*r.coin).into(), |
| 34 | + balance: r.wallet_balance, |
| 35 | + timestamp: account_response.time, |
| 36 | + } |
| 37 | + ); |
| 38 | + } |
| 39 | + Ok(balances) |
11 | 40 | } |
12 | 41 |
|
13 | | -#[derive(Serialize, Deserialize)] |
14 | | -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] |
| 42 | + |
| 43 | +#[derive(Debug, Clone, ScreamIt, Copy)] |
15 | 44 | pub enum AccountType { |
16 | 45 | Spot, |
17 | 46 | Contract, |
18 | 47 | Unified, |
19 | 48 | Funding, |
20 | 49 | Option, |
21 | 50 | } |
22 | | -impl std::fmt::Display for AccountType { |
23 | | - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
24 | | - let s = serde_plain::to_string(self).map_err(|_| std::fmt::Error)?; |
25 | | - write!(f, "{s}") |
26 | | - } |
27 | | -} |
28 | | -impl FromStr for AccountType { |
29 | | - type Err = (); |
30 | 51 |
|
31 | | - fn from_str(s: &str) -> Result<Self, Self::Err> { |
32 | | - serde_plain::from_str(s).map_err(|_| ()) |
33 | | - } |
| 52 | +#[derive(Debug, Serialize, Deserialize)] |
| 53 | +#[serde(rename_all = "camelCase")] |
| 54 | +pub struct AccountResponse { |
| 55 | + pub result: AccountResult, |
| 56 | + pub ret_code: i64, |
| 57 | + pub ret_ext_info: RetExtInfo, |
| 58 | + pub ret_msg: String, |
| 59 | + pub time: i64, |
34 | 60 | } |
35 | 61 |
|
36 | | -mod tests { |
37 | | - use insta; |
| 62 | +#[derive(Debug, Serialize, Deserialize)] |
| 63 | +pub struct AccountResult { |
| 64 | + pub list: Vec<AccountInfo>, |
| 65 | +} |
38 | 66 |
|
39 | | - use super::*; |
| 67 | +#[derive(Debug, Serialize, Deserialize)] |
| 68 | +#[serde(rename_all = "camelCase")] |
| 69 | +pub struct AccountInfo { |
| 70 | + #[serde(rename = "accountIMRate")] |
| 71 | + pub account_im_rate: Option<Value>, |
| 72 | + #[serde(rename = "accountLTV")] |
| 73 | + pub account_ltv: Option<Value>, |
| 74 | + #[serde(rename = "accountMMRate")] |
| 75 | + pub account_mm_rate: Option<Value>, |
| 76 | + pub account_type: AccountType, |
| 77 | + pub coin: Vec<CoinInfo>, |
| 78 | + pub total_available_balance: String, |
| 79 | + pub total_equity: String, |
| 80 | + pub total_initial_margin: String, |
| 81 | + pub total_maintenance_margin: String, |
| 82 | + pub total_margin_balance: String, |
| 83 | + #[serde(rename = "totalPerpUPL")] |
| 84 | + pub total_perp_upl: String, |
| 85 | + pub total_wallet_balance: String, |
| 86 | +} |
40 | 87 |
|
41 | | - #[test] |
42 | | - fn test_account_type_serde() { |
43 | | - insta::assert_debug_snapshot!(format!("{}", AccountType::Unified), @r#""UNIFIED""#); |
44 | | - let s = "UNIFIED"; |
45 | | - let _: AccountType = s.parse().unwrap(); |
46 | | - } |
| 88 | +#[serde_as] |
| 89 | +#[derive(Debug, Serialize, Deserialize)] |
| 90 | +#[serde(rename_all = "camelCase")] |
| 91 | +pub struct CoinInfo { |
| 92 | + pub accrued_interest: String, |
| 93 | + /// deprecated |
| 94 | + pub available_to_borrow: Option<Value>, //? can I start it with __, will serde understand? |
| 95 | + pub available_to_withdraw: String, |
| 96 | + pub bonus: String, |
| 97 | + pub borrow_amount: String, |
| 98 | + pub coin: String, |
| 99 | + pub collateral_switch: bool, |
| 100 | + pub cum_realised_pnl: String, |
| 101 | + pub equity: String, |
| 102 | + pub locked: String, |
| 103 | + pub margin_collateral: bool, |
| 104 | + pub spot_hedging_qty: String, |
| 105 | + #[serde(rename = "totalOrderIM")] |
| 106 | + pub total_order_im: String, |
| 107 | + #[serde(rename = "totalPositionIM")] |
| 108 | + pub total_position_im: String, |
| 109 | + #[serde(rename = "totalPositionMM")] |
| 110 | + pub total_position_mm: String, |
| 111 | + #[serde_as(as = "DisplayFromStr")] |
| 112 | + pub unrealised_pnl: f64, |
| 113 | + #[serde_as(as = "DisplayFromStr")] |
| 114 | + pub usd_value: f64, |
| 115 | + #[serde_as(as = "DisplayFromStr")] |
| 116 | + pub wallet_balance: f64, |
47 | 117 | } |
| 118 | + |
| 119 | +#[derive(Debug, Serialize, Deserialize)] |
| 120 | +pub struct RetExtInfo {} |
0 commit comments