Leverage & Balance Sheet helpers (#637)

This commit is contained in:
Maximilian Schneider 2023-08-09 13:54:09 +02:00 committed by GitHub
parent 6dcd5c925c
commit e4162a8281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 171 additions and 5 deletions

View File

@ -686,6 +686,66 @@ impl HealthCache {
(total_assets, total_liabs)
}
/// Computes the account assets and liabilities marked to market.
///
/// Contrary to health_assets_and_liabs, there's no health weighing or adjustment
/// for stable prices. It uses oracle prices directly.
///
/// Returns (assets, liabilities)
pub fn assets_and_liabs(&self) -> (I80F48, I80F48) {
let mut assets = I80F48::ZERO;
let mut liabs = I80F48::ZERO;
for token_info in self.token_infos.iter() {
if token_info.balance_spot.is_negative() {
liabs -= token_info.balance_spot * token_info.prices.oracle;
} else {
assets += token_info.balance_spot * token_info.prices.oracle;
}
}
for serum_info in self.serum3_infos.iter() {
let quote = &self.token_infos[serum_info.quote_info_index];
let base = &self.token_infos[serum_info.base_info_index];
assets += serum_info.reserved_base * base.prices.oracle;
assets += serum_info.reserved_quote * quote.prices.oracle;
}
for perp_info in self.perp_infos.iter() {
let quote_price = self.token_infos[perp_info.settle_token_index as usize]
.prices
.oracle;
let quote_position_value = perp_info.quote * quote_price;
if perp_info.quote.is_negative() {
liabs -= quote_position_value;
} else {
assets += quote_position_value;
}
let base_position_value = I80F48::from(perp_info.base_lots * perp_info.base_lot_size)
* perp_info.base_prices.oracle
* quote_price;
if base_position_value.is_negative() {
liabs -= base_position_value;
} else {
assets += base_position_value;
}
}
return (assets, liabs);
}
/// Computes the account leverage as ratio of liabs / (assets - liabs)
///
/// The goal of this function is to provide a quick overview over the accounts balance sheet.
/// It's not actually used to make any margin decisions internally and doesn't account for
/// open orders or stable / oracle price differences. Use health_ratio to make risk decisions.
pub fn leverage(&self) -> I80F48 {
let (assets, liabs) = self.assets_and_liabs();
let equity = assets - liabs;
liabs / equity.max(I80F48::from_num(0.001))
}
pub fn token_info(&self, token_index: TokenIndex) -> Result<&TokenInfo> {
Ok(&self.token_infos[self.token_info_index(token_index)?])
}

View File

@ -610,6 +610,16 @@ mod tests {
}
}
fn leverage_eq(h: &HealthCache, b: f64) -> bool {
let a = h.leverage();
if (a - I80F48::from_num(b)).abs() < 0.001 {
true
} else {
println!("leverage is {}, but expected {}", a, b);
false
}
}
fn default_token_info(x: f64, price: f64) -> TokenInfo {
TokenInfo {
token_index: 0,
@ -624,7 +634,7 @@ mod tests {
}
}
fn default_perp_info(x: f64) -> PerpInfo {
fn default_perp_info(x: f64, price: f64) -> PerpInfo {
PerpInfo {
perp_market_index: 0,
settle_token_index: 0,
@ -639,7 +649,7 @@ mod tests {
bids_base_lots: 0,
asks_base_lots: 0,
quote: I80F48::ZERO,
base_prices: Prices::new_single_price(I80F48::from_num(2.0)),
base_prices: Prices::new_single_price(I80F48::from_num(price)),
has_open_orders: false,
has_open_fills: false,
}
@ -1008,7 +1018,7 @@ mod tests {
health_cache.perp_infos.push(PerpInfo {
perp_market_index: 0,
settle_token_index: 1,
..default_perp_info(0.3)
..default_perp_info(0.3, 2.0)
});
adjust_by_usdc(&mut health_cache, 0, 60.0);
@ -1048,7 +1058,7 @@ mod tests {
perp_market_index: 0,
settle_token_index: 1,
base_lot_size,
..default_perp_info(0.3)
..default_perp_info(0.3, 2.0)
}],
being_liquidated: false,
};
@ -1425,7 +1435,7 @@ mod tests {
perp_infos: vec![PerpInfo {
perp_market_index: 0,
settle_token_index: 0,
..default_perp_info(0.3)
..default_perp_info(0.3, 2.0)
}],
being_liquidated: false,
};
@ -1458,4 +1468,100 @@ mod tests {
assert!((assets.to_num::<f64>() - 2.0 * (10.0 * 1.2 + 2.0 * 0.8)) < 0.01);
}
}
#[test]
fn test_leverage() {
// only deposits
let health_cache = HealthCache {
token_infos: vec![
TokenInfo {
token_index: 0,
balance_spot: I80F48::ONE,
..default_token_info(0.0, 1.0)
},
TokenInfo {
token_index: 1,
..default_token_info(0.2, 2.0)
},
],
serum3_infos: vec![],
perp_infos: vec![],
being_liquidated: false,
};
assert!(leverage_eq(&health_cache, 0.0));
// deposits and borrows: assets = 10, equity = 1
let health_cache = HealthCache {
token_infos: vec![
TokenInfo {
token_index: 0,
balance_spot: I80F48::from_num(-9),
..default_token_info(0.0, 1.0)
},
TokenInfo {
token_index: 1,
balance_spot: I80F48::from_num(5),
..default_token_info(0.2, 2.0)
},
],
serum3_infos: vec![],
perp_infos: vec![],
being_liquidated: false,
};
assert!(leverage_eq(&health_cache, 9.0));
// perp trade: assets = 1 + 9.9, equity = 1
let health_cache = HealthCache {
token_infos: vec![
TokenInfo {
token_index: 0,
balance_spot: I80F48::ONE,
..default_token_info(0.0, 1.0)
},
TokenInfo {
token_index: 1,
..default_token_info(0.2, 2.0)
},
],
serum3_infos: vec![],
perp_infos: vec![PerpInfo {
perp_market_index: 0,
base_lot_size: 3,
base_lots: -3,
quote: I80F48::from_num(9.9),
..default_perp_info(0.1, 1.1)
}],
being_liquidated: false,
};
assert!(leverage_eq(&health_cache, 9.9));
// open orders: assets = 3, equity = 1
let health_cache = HealthCache {
token_infos: vec![
TokenInfo {
token_index: 0,
balance_spot: I80F48::ONE,
..default_token_info(0.0, 1.0)
},
TokenInfo {
token_index: 1,
balance_spot: I80F48::from_num(-1),
..default_token_info(0.2, 2.0)
},
],
serum3_infos: vec![Serum3Info {
reserved_base: I80F48::ONE,
reserved_quote: I80F48::ZERO,
base_info_index: 1,
quote_info_index: 0,
market_index: 0,
has_zero_funds: true,
}],
perp_infos: vec![],
being_liquidated: false,
};
assert!(leverage_eq(&health_cache, 2.0));
}
}