Borrowing Fee Calculations
To calculate borrowing fees for any given Position, we need it's borrow_rate, and essentially how much time has passed since the last time the Position was assessed. Rather than do this for each individual Position, we actually instead focus on keeping track of a global borrow counter for the Custody, and then when any given Position gets updated, assess fees against the global counter.
First, let's review how the global borrow rate at any given time is calculated by reviewing the JumpRateState for a Custody, and then see how when combined with passing time, this can be used to a global cumulative "owed time-rate" for all Positions.
Jump Rate State
This structure is used for calculating the borrow_rate at any point in time using the dual slope utilization model. It is located on the Custody.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct JumpRateState {
pub min_rate_bps: u64,
pub max_rate_bps: u64,
pub target_rate_bps: u64,
pub target_utilization_rate: u64,
}
How is this done? First, we grab the current utilization_rate of the Custody and compare it to the target_utilization_rate (bps) given in the JumpRateState.
If it is less than the target, we calculate the lower_slope of the dual slope utilization model to find the borrow_rate, which is a shallower slope designed to give borrowers an edge until the Custody reaches target_utilization. It's math is given by:
let lower_slope = (target_rate_bps - min_rate_bps) / target_utilization_rate
If it is more, we have the upper_slope, designed to disincentivize borrowing, given by:
let upper_slope = (max_rate_bps - target_rate_bps) / (10_000 - target_utilization_rate)
This "dual slope" is really two lines stapled together, representing different borrow_rates, each segment taking place over some portion of a 10_000 bps strip along the x axis (x being borrow_rate).
If using the lower_slope to derive borrow_rate, we multiply it by the current utilization_rate bps of the Custody, and then add it it to the min_rate_bps of the Custody to determine the borrow rate:
let borrow_rate = utilization * lower_slope + min_rate_bps
If using the upper_slope to derive borrow_rate, we take the difference between the utilization of the Custody and it's target, and multiply that by the upper_slope, and then add that to the target_rate_bps and that is the borrow_rate.
let borrow_rate = (utilization - target_utilization_rate) * upper_slope + target_rate_bps
Now, let's discuss how we can get the time passed.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct FundingRateState {
pub cumulative_interest_rate: u64,
pub last_updated: i64,
pub last_borrow_rate: u64,
}
The FundingRateState structure is present on the Custody object as well and is updated whenever a Position, any Position, is updated or liquidated. When that happens, the last_borrow_rate is updated to the current borrow_rate, as well as the last_updated timestamp set to the current time.
The cumulative_interest_rate is a cumulative value that is the sum of each new borrow_rate, multiplied by the change in hours to 5 digits of precision. It isn't so much an actual "interest rate" as a rate-time unit and represents the sort of "owed backlog of time-rate" all Positions owe if they are children of this Custody. So if the borrow_rate is now 5, and 5 hours have passed, and cumulative_interest_rate is 25, we add 25 additional to it making it cumulative_interest_rate = 50.
Correspondingly, all Positions have a cumulative_interest_snapshot that shows the difference between the last cumulative value they saw and the one on the Custody. When a Position is created, it's cumulative_interest_snapshot is set to the value presently on cumulative_interest_rate.
As you may have guessed, when a Position pays its borrowing fees during an update, closure, or liquidation, it sets it's cumulative_interest_snapshot to the value of cumulative_interest_rate, indicating it has paid it's dues for now. These are ever increasing values and never go down.
As an example, if a Position was created when cumulative_interest_rate was 20, then goes to close when cumulative_interest_rate = 50, it owes borrowing fees on 30 units of time-rate.
A Position calculates the borrowing fee it particularly owes by the following math pseudocode:
let borrow_rate = cumulative_interest_rate - cumulative_interest_snapshot;
let leveraged_size = size_usd - collateral_usd; // your loan
let borrow_with_leveraged_size = borrow_rate * leveraged_size;
let final_owed_amount = borrow_with_leveraged_size * short_long_mismatch_borrow_modifier_bps;
The final short_long_mismatch_borrow_modifier_bps is a Concept that is discussed elsewhere and is an advantage or detriment given to a Position depending on if it is making a trade that is in a contrarian mode or not in the Custody.
Last updated