Leverage & Balance Sheet helpers (#637)
This commit is contained in:
parent
6dcd5c925c
commit
e4162a8281
|
@ -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)?])
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue