📈Funding Rates

This document outlines the key formulas and calculations that drive our protocol's operations. Understanding these mechanics is crucial for users, liquidity providers, and developers interacting with the system.

Funding Rates

Our system implements a dynamic funding rate system, designed to react to and optimize issues with heavily skewed markets in real-time.

The formula is designed to incentivize an equal balance between long / short positions for each market, to ensure markets are as delta neutral as possible, in order to protect liquidity providers.

Formula for Current Funding Rate

currentFundingRate = fundingRate + fundingRateVelocity * (timeElapsed / secondsInDay)

Parameters

  • fundingRate: The last recorded funding rate

  • fundingRateVelocity: The rate of change of the funding rate

  • timeElapsed: Time passed since the last funding rate update

  • secondsInDay: Constant, equal to 86400

Formula for Funding Rate Velocity

velocity = proportionalSkew * maxFundingVelocity

Where: proportionalSkew = skew / skewScale

Parameters

  • skew: The difference between long and short open interest

  • skewScale: A configured scale factor for the skew

  • maxFundingVelocity: The maximum allowed funding rate velocity (used to clamp velocity)

Unrecorded Funding Calculation

The contract also calculates unrecorded funding, which represents the funding that has accrued since the last update:

solidity

avgFundingRate = -(storedFundingRate + currentFundingRate) / 2

solidity

unrecordedFunding = avgFundingRate \* (timeElapsed / secondsInDay)

Parameters

  • storedFundingRate: The funding rate stored from the last update

  • currentFundingRate: The newly calculated funding rate

  • timeElapsed: Time passed since the last funding rate update

  • secondsInDay: Constant, equal to 86400

These formulas work together to continuously adjust the funding rate based on market conditions, helping to balance long and short positions.

Example [Provide a simple example calculation if applicable] Additional Notes [Any important considerations or edge cases]

Borrowing Rates

Borrowing rates determine the fees that traders pay for leveraged positions. These rates are dynamically adjusted based on market conditions.

Formula for Borrowing Rate

solidity

borrowRatePerDay = borrowScale * (openInterest / maxOpenInterest)

Parameters

  • borrowScale: The maximum possible borrowing fee per day

  • openInterest: The current open interest for the given side (long or short)

  • maxOpenInterest: The maximum allowed open interest in the market

Formula for Pending Fees

pendingFees = borrowRate * (timeElapsed / secondsPerDay)

Parameters

  • borrowRate: The current borrowing rate

  • timeElapsed: Time passed since the last update

  • secondsPerDay: Constant, equal to 86400

Formula for Average Cumulative Borrowing Fee

nextAverageCumulative = (lastCumulative * (1 - relativeSize)) + (currentCumulative * relativeSize)

Where:

relativeSize = sizeDelta / (openInterest + sizeDelta)

Parameters:

  • lastCumulative: The previous average cumulative borrowing fee

  • currentCumulative: The current cumulative borrowing fee

  • sizeDelta: The change in position size

  • openInterest: The current open interest

Formula for Total Fees Owed

totalFeesOwedUsd = (cumulativeBorrowFee - averageCumulativeBorrowFee) * openInterest

Parameters

  • cumulativeBorrowFee: The total accumulated borrowing fee

  • averageCumulativeBorrowFee: The average cumulative borrowing fee

  • openInterest: The current open interest

Additional Notes

  • If the open interest is greater than the maximum open interest, the borrowing rate defaults to the maximum rate per day (borrowScale).

  • The borrowing rates are calculated separately for long and short positions.

  • The average cumulative borrowing fee is used to track the average borrowing fee across all positions, weighted by their sizes.

These formulas work together to dynamically adjust borrowing rates based on market utilization, ensuring that rates increase as the market approaches maximum capacity and decrease when there's less demand for leverage.

Price Impact

Price impact represents how a trade affects the asset's price due to changes in market liquidity and skew.

Formula for Price Impact

The core formula for price impact is:

priceImpactUsd = sizeDeltaUsd * skewFactor * liquidityFactor

Where:

skewFactor = (initialSkew / initialTotalOi) - (updatedSkew / updatedTotalOi);
liquidityFactor = liquidityScalar * (sizeDeltaUsd / availableOi)

Parameters

  • sizeDeltaUsd: The size of the trade in USD

  • initialSkew: The difference between long and short open interest before the trade

  • updatedSkew: The difference between long and short open interest after the trade

  • initialTotalOi: Total open interest before the trade

  • updatedTotalOi: Total open interest after the trade

  • liquidityScalar: A configured scalar to adjust impact based on liquidity

  • availableOi: Available open interest for the trade direction

Formula for Impacted Price

percentageImpact = |priceImpactUsd| / sizeDeltaUsd
impactToPrice = indexPrice * percentageImpact

For long positions:

impactedPrice = (priceImpactUsd < 0) ? indexPrice + impactToPrice : indexPrice - impactToPrice

For short positions:

impactedPrice = (priceImpactUsd < 0) ? indexPrice - impactToPrice : indexPrice + impactToPrice

Parameters:

  • priceImpactUsd: The calculated price impact in USD

  • sizeDeltaUsd: The size of the trade in USD

  • indexPrice: The current index price of the asset

Additional Notes

  • The price impact is calculated differently if the trade causes a skew flip (from positive to negative or vice versa).

  • Positive impact is capped by the available impact pool for the market.

  • For decreases in position size, there is no price impact calculated.

  • The contract includes a slippage check to ensure the impacted price doesn't exceed the user's specified maximum slippage.

  • The liquidityFactor is only applied for increases in position size, not for decreases.

  • The contract uses separate scalars for positive and negative liquidity impacts.

These formulas work together to adjust the trade price based on how it affects the market's balance between long and short positions, as well as the overall liquidity. This mechanism helps to maintain market stability and fairness, especially for larger trades that could significantly impact the market. It also helps to prevent price manipulation attacks, and keep markets relatively delta neutral, reducing risk to liquidity providers.

Dynamic Fees

Dynamic fees are applied to deposits and withdrawals to incentivize balanced liquidity between ETH / USDC in the market. These fees adjust based on how the action affects the market's skew between the long and short liquidity.

Formula for Deposit Fee

depositFee = baseFee + additionalFee

Where:

baseFee = tokenAmount * BASE_FEE
additionalFee = tokenAmount * feeFactor
feeFactor = FEE_SCALE * (negativeSkewAccrued / (longValue + shortValue))

Parameters

  • tokenAmount: The amount of tokens being deposited

  • BASE_FEE: A constant base fee percentage

  • FEE_SCALE: A constant to scale the fee factor

  • negativeSkewAccrued: The amount of negative skew added by the deposit

  • longValue: The total value of long tokens in the pool

  • shortValue: The total value of short tokens in the pool

Formula for Withdrawal Fee

withdrawalFee = baseFee + additionalFee

Where:

  • baseFee = tokenAmount * BASE_FEE

  • additionalFee = tokenAmount * feeFactor

  • feeFactor = FEE_SCALE * (negativeSkewAccrued / (longValue + shortValue + amountUsd))

Parameters

  • tokenAmount: The amount of tokens being withdrawn

  • BASE_FEE: A constant base fee percentage

  • FEE_SCALE: A constant to scale the fee factor

  • negativeSkewAccrued: The amount of negative skew added by the withdrawal

  • longValue: The total value of long tokens in the pool after withdrawal

  • shortValue: The total value of short tokens in the pool after withdrawal

  • amountUsd: The USD value of the tokens being withdrawn

Additional Notes

  • If the deposit or withdrawal improves the skew (reduces its absolute value) and doesn't cause a skew flip, only the base fee is charged.

  • A skew flip occurs when the action changes the skew from positive to negative or vice versa. In case of a skew flip, the full updated skew is used as the negativeSkewAccrued.

For deposits

  • If either token balance is zero, only the base fee is charged.

  • If the value of the token being deposited is zero in the pool, only the base fee is charged.

For withdrawals

  • If the withdrawal would empty the pool (longValue + shortValue = 0), the maximum possible fee is charged.

The formulas use the concept of "negative skew accrued" to determine the additional fee. This is either the full updated skew (in case of a flip) or the amount of the deposit/withdrawal (if no flip occurs).

These dynamic fee mechanisms help maintain balanced liquidity in the pool by discouraging actions that would significantly imbalance the pool and encouraging actions that improve the balance.

Auto-Deleveraging

Auto-Deleveraging (ADL) is a mechanism used to maintain the health and solvency of the protocol by reducing the impact of large profitable positions on the liquidity pool. This section outlines the key components and calculations involved in the ADL process.

PNL Factor

The PNL factor is used to determine when a pool can be auto-deleveraged. ADL is triggered when positive PNL makes up >= 45% of the pool.

Formula for PNL Factor

pnlFactor = pnl / poolUsd

Where:

  • pnl: The profit and loss for the market

  • poolUsd: The total USD value of the pool

Implementation

The getPnlFactor function calculates the PNL factor:

solidity

function getPnlFactor(
    MarketId _id,
    address _market,
    address _vault,
    uint256 _indexPrice,
    uint256 _collateralPrice,
    uint256 _collateralBaseUnit,
    bool _isLong
) public view returns (int256 pnlFactor) {
    uint256 poolUsd = getPoolBalanceUsd(IVault(_vault), _collateralPrice, _collateralBaseUnit, _isLong);

    if (poolUsd == 0) {
        return 0;
    }

    int256 pnl = getMarketPnl(_id, _market, _indexPrice, _isLong);

    uint256 factor = pnl.abs().divWadUp(poolUsd);

    return pnl > 0 ? factor.toInt256() : factor.toInt256() * -1;
}

ADL Target Selection

The protocol selects the next position to be auto-deleveraged based on an ADL Target Score.

Formula for ADL Target Score

solidity

ADL Target Score = (Position Size / Total Pool Size) * (Position PNL / Position Size)

Implementation

The getNextAdlTarget function selects the position with the highest ADL Target Score:

Solidity

function getNextAdlTarget(
    MarketId _id,
    ITradeStorage tradeStorage,
    string memory _ticker,
    uint256 _indexPrice,
    uint256 _indexBaseUnit,
    uint256 _totalPoolSizeUsd,
    bool _isLong
) external view returns (bytes32 positionKey) {
    // ... (implementation details)
}

ADL Percentage Calculation

The ADL percentage determines how much of a position should be deleveraged.

Formula for ADL Percentage

ADL Percentage = 1 - e^(-excessRatio^2 * (positionPnl/positionSize))

Where:

excessRatio = (currentPnlToPoolRatio / targetPnlToPoolRatio) - 1

Implementation

The calculateAdlPercentage function calculates the ADL percentage:

Solidity

function calculateAdlPercentage(uint256 _pnlToPoolRatio, int256 _positionProfit, uint256 _positionSize)
    public
    pure
    returns (uint256 adlPercentage)
{
    // ... (implementation details)
}

ADL Price Impact

To maintain market health, the execution price for ADL'd positions is adjusted within specific boundaries.

Formula for Impacted Price

impactedPrice = indexPrice ± priceDelta

Where:

priceDelta = (indexPrice - minProfitPrice) * poolImpact
poolImpact = min(pnlImpact / poolUsd, 100%)
pnlImpact = pnlBeingRealized * accelerationFactor
accelerationFactor = (pnlToPoolRatio - targetPnlRatio) / targetPnlRatio

Implementation

The _executeAdlImpact function calculates the impacted price for ADL:

Solidity

function _executeAdlImpact(
    uint256 _indexPrice,
    uint256 _averageEntryPrice,
    uint256 _pnlBeingRealized,
    uint256 _poolUsd,
    uint256 _pnlToPoolRatio,
    bool _isLong
) private pure returns (uint256 impactedPrice) {
    // ... (implementation details)
}

This Auto-Deleveraging mechanism helps maintain the protocol's solvency by systematically reducing the impact of large profitable positions on the liquidity pool, ensuring a fair and balanced market for all participants.

Liquidation Thresholds

The Liquidation Threshold determines when a position becomes eligible for liquidation. This section outlines the key components and calculations involved in determining whether a position is liquidatable.

Liquidation Condition

A position becomes liquidatable when the total losses plus fees exceed the available collateral. This is determined by comparing the remaining collateral to the maintenance collateral.

Formula for Liquidation Check

isLiquidatable = remainingCollateral < maintenanceCollateral

Where:

remainingCollateral = initialCollateral - totalLosses
totalLosses = -pnl + borrowingFeesUsd ± fundingFeesUsd

Note: Funding fees are added for long positions and subtracted for short positions.

Implementation

The checkIsLiquidatable function determines if a position is liquidatable:

Solidity

function checkIsLiquidatable(MarketId _id, IMarket market, Position.Data memory _position, Prices memory _prices)
    public
    view
    returns (bool isLiquidatable)
{
    int256 pnl = Position.getPositionPnl(
        _position.size, _position.weightedAvgEntryPrice, _prices.indexPrice, _prices.indexBaseUnit, _position.isLong
    );

    uint256 maintenanceCollateral = _getMaintenanceCollateral(_position);

    uint256 borrowingFeesUsd = Position.getTotalBorrowFeesUsd(_id, market, _position);

    int256 fundingFeesUsd = Position.getTotalFundingFees(_id, market, _position);

    // Calculate total losses (negative PnL plus fees)
    int256 totalLosses = -pnl + borrowingFeesUsd.toInt256();

    // Add or subtract funding fees based on position type
    if (_position.isLong) {
        totalLosses += fundingFeesUsd;
    } else {
        totalLosses -= fundingFeesUsd;
    }

    // Calculate remaining collateral after losses
    int256 remainingCollateral = _position.collateral.toInt256() - totalLosses;

    // Position is liquidatable if remaining collateral is less than maintenance collateral
    isLiquidatable = remainingCollateral < maintenanceCollateral.toInt256();
}

Maintenance Collateral

The maintenance collateral represents the minimum amount of collateral that must be maintained for a position to avoid liquidation. It is calculated based on the position's leverage.

Formula for Maintenance Collateral

maintenanceCollateral = initialCollateral * maintenanceMargin

Where:

maintenanceMargin = BASE_MAINTENANCE_MARGIN + bonusMaintenanceMargin
bonusMaintenanceMargin = MAINTENANCE_MARGIN_SCALE * min(leverage / MAX_LEVERAGE, 1)
leverage = positionSize / initialCollateral

Implementation

The _getMaintenanceCollateral function calculates the maintenance collateral:

Solidity

function _getMaintenanceCollateral(Position.Data memory _position)
    private
    pure
    returns (uint256 maintenanceCollateral)
{
    uint256 leverage = _position.size / _position.collateral;
    uint256 bonusMaintenanceMargin;
    if (leverage >= MAX_LEVERAGE) {
        bonusMaintenanceMargin = MAINTENANCE_MARGIN_SCALE;
    } else {
        bonusMaintenanceMargin = MAINTENANCE_MARGIN_SCALE * leverage / MAX_LEVERAGE;
    }
    uint256 maintenanceMargin = BASE_MAINTENANCE_MARGIN + bonusMaintenanceMargin;
    maintenanceCollateral = _position.collateral.percentage(maintenanceMargin);
}

Key Points

  1. The maintenance collateral percentage increases with higher leverage, providing a safety buffer for riskier positions.

  2. The BASE_MAINTENANCE_MARGIN ensures a minimum maintenance requirement for all positions.

  3. The MAINTENANCE_MARGIN_SCALE allows for fine-tuning of the maintenance margin based on leverage.

  4. Positions at or above MAX_LEVERAGE have the highest maintenance collateral requirement.

This Liquidation Threshold mechanism helps maintain the overall health of the protocol by ensuring that positions are liquidated before they can pose a significant risk to the system's solvency.

Gas Rebates

The Gas Rebates mechanism ensures that users are charged fairly for the actual gas used during transaction execution. This section outlines the process of estimating gas costs, tracking actual gas usage, and refunding excess fees.

Gas Estimation

Before execution, the Gas smart contract estimates the required gas for a given action.

Implementation

The validateExecutionFee function in the Gas library estimates the execution fee:

Solidity

function validateExecutionFee(
    IPriceFeed priceFeed,
    IPositionManager positionManager,
    uint256 _executionFee,
    uint256 _msgValue,
    Action _action,
    bool _hasPnlRequest,
    bool _isLimit
) internal view returns (uint256 priceUpdateFee) {
    // ... (implementation details)
}

This function:

  1. Checks if the sent value is sufficient for the execution fee.

  2. Estimates the gas cost for the action and price updates.

  3. Ensures the execution fee is sufficient to cover the estimated costs.

Gas Usage Tracking

During execution, the contract tracks the actual gas used.

  1. At the start of execution:

    Solidity

    uint256 initialGas = gasleft();
  2. At the end of execution:

    Solidity

    uint256 executionCost = (initialGas - gasleft()) * tx.gasprice;

This calculates the actual gas cost of the transaction execution.

Gas Rebate Calculation

After execution, the contract calculates the amount to refund based on the actual gas used.

Implementation

Solidity

if (executionCost > request.input.executionFee) {
    SafeTransferLib.safeTransferETH(msg.sender, executionCost);
} else {
    uint256 feeToRefund = request.input.executionFee - executionCost;

    SafeTransferLib.safeTransferETH(msg.sender, executionCost);

    if (feeToRefund > 0) {
        SafeTransferLib.sendEthNoRevert(WETH, request.user, feeToRefund, transferGasLimit, owner());
    }
}

This code:

  1. Checks if the actual execution cost exceeds the initial execution fee.

  2. If it does, transfers the full execution cost to the executor.

  3. If it doesn't, refunds the difference to the user.

Key Components

  1. Gas Estimation: The Gas contract provides functions to estimate gas costs for various actions, including deposits, withdrawals, and position management.

  2. Execution Fee Validation: The contract ensures that the provided execution fee is sufficient to cover the estimated costs.

  3. Actual Gas Usage: The contract tracks the actual gas used during execution using the gasleft() function.

  4. Refund Mechanism: After execution, any excess fee is refunded to the user, ensuring they only pay for the gas actually used.

  5. Executor Payment: The executor (typically a keeper or automated system) is paid for the actual gas used, even if it exceeds the initial estimate.

Benefits

  1. Fair Pricing: Users only pay for the actual gas used, rather than a fixed fee.

  2. Incentivized Execution: Executors are guaranteed payment for their services, even if gas prices spike.

  3. Flexible: The system adapts to varying gas prices and complex transactions.

This Gas Rebates mechanism ensures that the protocol remains efficient and fair in its gas fee management, benefiting both users and executors.

Last updated