From 710bf5c65c05e27078dd660b3d69921cd09c763d Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:48:42 -0500 Subject: [PATCH 1/4] bigz/sdk-contract-tier-funding-rate-clamp --- sdk/src/math/funding.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/sdk/src/math/funding.ts b/sdk/src/math/funding.ts index 46e1e3db5..7d166d815 100644 --- a/sdk/src/math/funding.ts +++ b/sdk/src/math/funding.ts @@ -7,10 +7,11 @@ import { ONE, FUNDING_RATE_OFFSET_DENOMINATOR, } from '../constants/numericConstants'; -import { PerpMarketAccount, isVariant } from '../types'; +import { ContractTier, PerpMarketAccount, isVariant } from '../types'; import { OraclePriceData } from '../oracles/types'; import { calculateBidAskPrice } from './amm'; import { calculateLiveOracleTwap } from './oracles'; +import { clampBN } from './utils'; function calculateLiveMarkTwap( market: PerpMarketAccount, @@ -120,6 +121,26 @@ export async function calculateAllEstimatedFundingRate( markPrice?: BN, now?: BN ): Promise<[BN, BN, BN, BN, BN]> { + function getMaxPriceDivergenceForFundingRate( + market: PerpMarketAccount, + oracleTwap: BN + ) { + switch (market.contractTier) { + case ContractTier.A: + return oracleTwap.divn(33); + case ContractTier.B: + return oracleTwap.divn(33); + case ContractTier.C: + return oracleTwap.divn(20); + case ContractTier.SPECULATIVE: + return oracleTwap.divn(10); + case ContractTier.ISOLATED: + return oracleTwap.divn(10); + default: + return oracleTwap.divn(10); + } + } + if (isVariant(market.status, 'uninitialized')) { return [ZERO, ZERO, ZERO, ZERO, ZERO]; } @@ -160,8 +181,15 @@ export async function calculateAllEstimatedFundingRate( const twapSpreadWithOffset = twapSpread.add( oracleTwap.abs().div(FUNDING_RATE_OFFSET_DENOMINATOR) ); + const maxSpread = getMaxPriceDivergenceForFundingRate(market, oracleTwap); + + const clampedSpreadWithOffset = clampBN( + twapSpreadWithOffset, + maxSpread.mul(new BN(-1)), + maxSpread + ); - const twapSpreadPct = twapSpreadWithOffset + const twapSpreadPct = clampedSpreadWithOffset .mul(PRICE_PRECISION) .mul(new BN(100)) .div(oracleTwap); From d1d43a6698157b529f40fe2093daafad5c41a42b Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:23:10 -0500 Subject: [PATCH 2/4] add sdk test --- sdk/tests/amm/test.ts | 149 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index 484d075bc..00ad98827 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -28,6 +28,7 @@ import { squareRootBN, calculateReferencePriceOffset, calculateInventoryLiquidityRatio, + ContractTier, } from '../../src'; import { mockPerpMarkets } from '../dlob/helpers'; @@ -1065,6 +1066,154 @@ describe('AMM Tests', () => { assert(est2.eq(new BN('-719'))); }); + it('predicted funding rate mock clamp', async () => { + const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); + const mockMarket1 = myMockPerpMarkets[0]; + + // make it like OP + const now = new BN(1688881915); + + mockMarket1.amm.fundingPeriod = new BN(3600); + mockMarket1.amm.lastFundingRateTs = new BN(1688864415); + + const currentMarkPrice = new BN(1.2242 * PRICE_PRECISION.toNumber()); // trading at a premium + const oraclePriceData: OraclePriceData = { + price: new BN(1.924 * PRICE_PRECISION.toNumber()), + slot: new BN(0), + confidence: new BN(1), + hasSufficientNumberOfDataPoints: true, + }; + mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN( + 1.9535 * PRICE_PRECISION.toNumber() + ); + + // mockMarket1.amm.pegMultiplier = new BN(1.897573 * 1e3); + + mockMarket1.amm.lastMarkPriceTwap = new BN( + 1.218363 * PRICE_PRECISION.toNumber() + ); + mockMarket1.amm.lastBidPriceTwap = new BN( + 1.218363 * PRICE_PRECISION.toNumber() + ); + mockMarket1.amm.lastAskPriceTwap = new BN( + 1.218364 * PRICE_PRECISION.toNumber() + ); + mockMarket1.amm.lastMarkPriceTwapTs = new BN(1688878815); + + mockMarket1.amm.historicalOracleData.lastOraclePriceTwap = new BN( + 1.820964 * PRICE_PRECISION.toNumber() + ); + mockMarket1.amm.historicalOracleData.lastOraclePriceTwapTs = new BN( + 1688879991 + ); + mockMarket1.contractTier = ContractTier.A; + + const [ + _markTwapLive, + _oracleTwapLive, + _lowerboundEst, + _cappedAltEst, + _interpEst, + ] = await calculateAllEstimatedFundingRate( + mockMarket1, + oraclePriceData, + currentMarkPrice, + now + ); + + // console.log(_markTwapLive.toString()); + // console.log(_oracleTwapLive.toString()); + // console.log(_lowerboundEst.toString()); + // console.log(_cappedAltEst.toString()); + // console.log(_interpEst.toString()); + // console.log('-----'); + + let [markTwapLive, oracleTwapLive, est1, est2] = + await calculateLongShortFundingRateAndLiveTwaps( + mockMarket1, + oraclePriceData, + currentMarkPrice, + now + ); + + console.log( + 'markTwapLive:', + mockMarket1.amm.lastMarkPriceTwap.toString(), + '->', + markTwapLive.toString() + ); + console.log( + 'oracTwapLive:', + mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(), + '->', + oracleTwapLive.toString() + ); + console.log('pred funding:', est1.toString(), est2.toString()); + + assert(markTwapLive.eq(new BN('1680634'))); + assert(oracleTwapLive.eq(new BN('1876031'))); + assert(est1.eq(est2)); + assert(est2.eq(new BN('-126261'))); + + mockMarket1.contractTier = ContractTier.C; + + [markTwapLive, oracleTwapLive, est1, est2] = + await calculateLongShortFundingRateAndLiveTwaps( + mockMarket1, + oraclePriceData, + currentMarkPrice, + now + ); + + console.log( + 'markTwapLive:', + mockMarket1.amm.lastMarkPriceTwap.toString(), + '->', + markTwapLive.toString() + ); + console.log( + 'oracTwapLive:', + mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(), + '->', + oracleTwapLive.toString() + ); + console.log('pred funding:', est1.toString(), est2.toString()); + + assert(markTwapLive.eq(new BN('1680634'))); + assert(oracleTwapLive.eq(new BN('1876031'))); + assert(est1.eq(est2)); + assert(est2.eq(new BN('-208332'))); + + mockMarket1.contractTier = ContractTier.SPECULATIVE; + + [markTwapLive, oracleTwapLive, est1, est2] = + await calculateLongShortFundingRateAndLiveTwaps( + mockMarket1, + oraclePriceData, + currentMarkPrice, + now + ); + + console.log( + 'markTwapLive:', + mockMarket1.amm.lastMarkPriceTwap.toString(), + '->', + markTwapLive.toString() + ); + console.log( + 'oracTwapLive:', + mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(), + '->', + oracleTwapLive.toString() + ); + console.log('pred funding:', est1.toString(), est2.toString()); + + assert(markTwapLive.eq(new BN('1680634'))); + assert(oracleTwapLive.eq(new BN('1876031'))); + assert(est1.eq(est2)); + assert(est2.eq(new BN('-416666'))); + }); + it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); From 3a6023c298d8866958c220d3c2dfff8f36fa48ea Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 5 Jan 2024 15:51:27 -0500 Subject: [PATCH 3/4] tweak --- sdk/src/math/funding.ts | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/sdk/src/math/funding.ts b/sdk/src/math/funding.ts index 7d166d815..e33693785 100644 --- a/sdk/src/math/funding.ts +++ b/sdk/src/math/funding.ts @@ -121,26 +121,6 @@ export async function calculateAllEstimatedFundingRate( markPrice?: BN, now?: BN ): Promise<[BN, BN, BN, BN, BN]> { - function getMaxPriceDivergenceForFundingRate( - market: PerpMarketAccount, - oracleTwap: BN - ) { - switch (market.contractTier) { - case ContractTier.A: - return oracleTwap.divn(33); - case ContractTier.B: - return oracleTwap.divn(33); - case ContractTier.C: - return oracleTwap.divn(20); - case ContractTier.SPECULATIVE: - return oracleTwap.divn(10); - case ContractTier.ISOLATED: - return oracleTwap.divn(10); - default: - return oracleTwap.divn(10); - } - } - if (isVariant(market.status, 'uninitialized')) { return [ZERO, ZERO, ZERO, ZERO, ZERO]; } @@ -262,6 +242,25 @@ export async function calculateAllEstimatedFundingRate( return [markTwap, oracleTwap, lowerboundEst, cappedAltEst, interpEst]; } +function getMaxPriceDivergenceForFundingRate( + market: PerpMarketAccount, + oracleTwap: BN +) { + if (isVariant(market.contractTier, 'a')) { + return oracleTwap.divn(33); + } else if (isVariant(market.contractTier, 'b')) { + return oracleTwap.divn(33); + } else if (isVariant(market.contractTier, 'c')) { + return oracleTwap.divn(20); + } else if (isVariant(market.contractTier, 'speculative')) { + return oracleTwap.divn(10); + } else if (isVariant(market.contractTier, 'isolated')) { + return oracleTwap.divn(10); + } else { + return oracleTwap.divn(10); + } +} + /** * * @param market From 726e5b9ab7435977b35b68b676f78970b1514f97 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 5 Jan 2024 15:52:58 -0500 Subject: [PATCH 4/4] CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72879d4fd..6bcc140cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes -- ts-sd: fix oracle is valid ([#806](https://github.com/drift-labs/protocol-v2/pull/806)) +- ts-sdk: contract tier funding rate clamp ([#785](https://github.com/drift-labs/protocol-v2/pull/785)) +- ts-sdk: fix oracle is valid ([#806](https://github.com/drift-labs/protocol-v2/pull/806)) ### Breaking