@velocity-exchange/sdk 0.4.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/browser/adminClient.d.ts +52 -0
- package/lib/browser/adminClient.js +97 -0
- package/lib/browser/config.js +6 -7
- package/lib/browser/constants/numericConstants.d.ts +1 -1
- package/lib/browser/constants/numericConstants.js +1 -1
- package/lib/browser/decode/user.js +5 -1
- package/lib/browser/idl/velocity.d.ts +191 -15
- package/lib/browser/idl/velocity.json +192 -16
- package/lib/browser/orderSubscriber/OrderSubscriber.d.ts +1 -0
- package/lib/browser/orderSubscriber/OrderSubscriber.js +10 -4
- package/lib/browser/pyth/pythLazerSubscriber.js +37 -27
- package/lib/browser/types.d.ts +6 -0
- package/lib/browser/user.d.ts +20 -3
- package/lib/browser/user.js +52 -4
- package/lib/browser/velocityClient.d.ts +46 -10
- package/lib/browser/velocityClient.js +107 -35
- package/lib/node/adminClient.d.ts +52 -0
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +97 -0
- package/lib/node/config.d.ts.map +1 -1
- package/lib/node/config.js +6 -7
- package/lib/node/constants/numericConstants.d.ts +1 -1
- package/lib/node/constants/numericConstants.d.ts.map +1 -1
- package/lib/node/constants/numericConstants.js +1 -1
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +5 -1
- package/lib/node/idl/velocity.d.ts +191 -15
- package/lib/node/idl/velocity.d.ts.map +1 -1
- package/lib/node/idl/velocity.json +192 -16
- package/lib/node/orderSubscriber/OrderSubscriber.d.ts +1 -0
- package/lib/node/orderSubscriber/OrderSubscriber.d.ts.map +1 -1
- package/lib/node/orderSubscriber/OrderSubscriber.js +10 -4
- package/lib/node/pyth/pythLazerSubscriber.d.ts.map +1 -1
- package/lib/node/pyth/pythLazerSubscriber.js +37 -27
- package/lib/node/types.d.ts +6 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/user.d.ts +20 -3
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +52 -4
- package/lib/node/velocityClient.d.ts +46 -10
- package/lib/node/velocityClient.d.ts.map +1 -1
- package/lib/node/velocityClient.js +107 -35
- package/package.json +7 -3
- package/.prettierignore +0 -1
- package/CHANGELOG.md +0 -132
- package/build-browser.js +0 -58
- package/bunfig.toml +0 -3
- package/esbuild-shims.js +0 -12
- package/scripts/deposit-isolated-positions.ts +0 -110
- package/scripts/find-flagged-users.ts +0 -213
- package/scripts/grpc-client-test-comparison.ts +0 -372
- package/scripts/grpc-multiuser-client-test-comparison.ts +0 -158
- package/scripts/postbuild.js +0 -95
- package/scripts/single-grpc-client-test.ts +0 -284
- package/scripts/withdraw-isolated-positions.ts +0 -174
- package/src/accounts/README_WebSocketAccountSubscriberV2.md +0 -95
- package/src/accounts/README_WebSocketProgramAccountSubscriberV2.md +0 -135
- package/src/accounts/basicUserAccountSubscriber.ts +0 -79
- package/src/accounts/basicUserStatsAccountSubscriber.ts +0 -87
- package/src/accounts/bulkAccountLoader.ts +0 -358
- package/src/accounts/bulkUserStatsSubscription.ts +0 -40
- package/src/accounts/bulkUserSubscription.ts +0 -39
- package/src/accounts/customizedCadenceBulkAccountLoader.ts +0 -250
- package/src/accounts/fetch.ts +0 -200
- package/src/accounts/grpcAccountSubscriber.ts +0 -225
- package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +0 -73
- package/src/accounts/grpcMultiAccountSubscriber.ts +0 -566
- package/src/accounts/grpcMultiUserAccountSubscriber.ts +0 -328
- package/src/accounts/grpcProgramAccountSubscriber.ts +0 -264
- package/src/accounts/grpcUserAccountSubscriber.ts +0 -63
- package/src/accounts/grpcUserStatsAccountSubscriber.ts +0 -65
- package/src/accounts/grpcVelocityClientAccountSubscriber.ts +0 -257
- package/src/accounts/grpcVelocityClientAccountSubscriberV2.ts +0 -941
- package/src/accounts/laserProgramAccountSubscriber.ts +0 -266
- package/src/accounts/oneShotUserAccountSubscriber.ts +0 -88
- package/src/accounts/oneShotUserStatsAccountSubscriber.ts +0 -94
- package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -223
- package/src/accounts/pollingOracleAccountSubscriber.ts +0 -151
- package/src/accounts/pollingTokenAccountSubscriber.ts +0 -147
- package/src/accounts/pollingUserAccountSubscriber.ts +0 -198
- package/src/accounts/pollingUserStatsAccountSubscriber.ts +0 -180
- package/src/accounts/pollingVelocityClientAccountSubscriber.ts +0 -774
- package/src/accounts/testBulkAccountLoader.ts +0 -59
- package/src/accounts/types.ts +0 -428
- package/src/accounts/utils.ts +0 -90
- package/src/accounts/webSocketAccountSubscriber.ts +0 -304
- package/src/accounts/webSocketAccountSubscriberV2.ts +0 -536
- package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +0 -171
- package/src/accounts/webSocketProgramAccountSubscriber.ts +0 -244
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -1048
- package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +0 -1042
- package/src/accounts/webSocketUserAccountSubscriber.ts +0 -139
- package/src/accounts/webSocketUserStatsAccountSubsriber.ts +0 -131
- package/src/accounts/webSocketVelocityClientAccountSubscriber.ts +0 -865
- package/src/accounts/webSocketVelocityClientAccountSubscriberV2.ts +0 -897
- package/src/accounts/websocketProgramUserAccountSubscriber.ts +0 -127
- package/src/addresses/marketAddresses.ts +0 -28
- package/src/addresses/pda.ts +0 -620
- package/src/adminClient.ts +0 -8136
- package/src/assert/assert.ts +0 -5
- package/src/auctionSubscriber/auctionSubscriber.ts +0 -86
- package/src/auctionSubscriber/auctionSubscriberGrpc.ts +0 -99
- package/src/auctionSubscriber/index.ts +0 -9
- package/src/auctionSubscriber/types.ts +0 -30
- package/src/bankrun/bankrunConnection.ts +0 -630
- package/src/blockhashSubscriber/BlockhashSubscriber.ts +0 -160
- package/src/blockhashSubscriber/index.ts +0 -6
- package/src/blockhashSubscriber/types.ts +0 -13
- package/src/clock/clockSubscriber.ts +0 -146
- package/src/config.ts +0 -312
- package/src/constants/index.ts +0 -9
- package/src/constants/numericConstants.ts +0 -194
- package/src/constants/perpMarkets.ts +0 -127
- package/src/constants/spotMarkets.ts +0 -104
- package/src/constants/txConstants.ts +0 -2
- package/src/constituentMap/constituentMap.ts +0 -305
- package/src/constituentMap/pollingConstituentAccountSubscriber.ts +0 -99
- package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +0 -121
- package/src/core/VelocityCore.ts +0 -606
- package/src/core/index.ts +0 -12
- package/src/core/instructions/deposit.ts +0 -64
- package/src/core/instructions/fill.ts +0 -51
- package/src/core/instructions/funding.ts +0 -33
- package/src/core/instructions/liquidation.ts +0 -55
- package/src/core/instructions/orders.ts +0 -84
- package/src/core/instructions/perpOrders.ts +0 -312
- package/src/core/instructions/settlement.ts +0 -40
- package/src/core/instructions/trigger.ts +0 -41
- package/src/core/instructions/withdraw.ts +0 -65
- package/src/core/remainingAccounts.ts +0 -320
- package/src/core/signedMsg.ts +0 -109
- package/src/decode/customCoder.ts +0 -148
- package/src/decode/user.ts +0 -401
- package/src/dlob/DLOB.ts +0 -2793
- package/src/dlob/DLOBNode.ts +0 -305
- package/src/dlob/DLOBSubscriber.ts +0 -269
- package/src/dlob/NodeList.ts +0 -244
- package/src/dlob/orderBookLevels.ts +0 -695
- package/src/dlob/types.ts +0 -37
- package/src/events/eventList.ts +0 -114
- package/src/events/eventSubscriber.ts +0 -521
- package/src/events/eventsServerLogProvider.ts +0 -189
- package/src/events/fetchLogs.ts +0 -219
- package/src/events/parse.ts +0 -271
- package/src/events/pollingLogProvider.ts +0 -113
- package/src/events/sort.ts +0 -51
- package/src/events/txEventCache.ts +0 -92
- package/src/events/types.ts +0 -337
- package/src/events/webSocketLogProvider.ts +0 -152
- package/src/factory/bigNum.ts +0 -709
- package/src/factory/oracleClient.ts +0 -73
- package/src/idl/pyth.json +0 -142
- package/src/idl/pyth.ts +0 -97
- package/src/idl/pyth_solana_receiver.json +0 -628
- package/src/idl/token_faucet.json +0 -229
- package/src/idl/token_faucet.ts +0 -197
- package/src/idl/velocity.json +0 -23974
- package/src/idl/velocity.ts +0 -23980
- package/src/index.ts +0 -150
- package/src/indicative-quotes/indicativeQuotesSender.ts +0 -233
- package/src/isomorphic/README.md +0 -19
- package/src/isomorphic/anchor.browser.ts +0 -44
- package/src/isomorphic/anchor.node.ts +0 -1
- package/src/isomorphic/anchor.ts +0 -1
- package/src/isomorphic/anchor29.browser.ts +0 -24
- package/src/isomorphic/anchor29.node.ts +0 -1
- package/src/isomorphic/anchor29.ts +0 -1
- package/src/isomorphic/grpc.browser.ts +0 -4
- package/src/isomorphic/grpc.node.ts +0 -130
- package/src/isomorphic/grpc.ts +0 -1
- package/src/jupiter/jupiterClient.ts +0 -502
- package/src/keypair.ts +0 -32
- package/src/margin/README.md +0 -138
- package/src/marginCalculation.ts +0 -407
- package/src/marinade/idl/idl.json +0 -1962
- package/src/marinade/index.ts +0 -73
- package/src/marinade/types.ts +0 -3925
- package/src/math/amm.ts +0 -1758
- package/src/math/auction.ts +0 -480
- package/src/math/bankruptcy.ts +0 -138
- package/src/math/builder.ts +0 -130
- package/src/math/conversion.ts +0 -45
- package/src/math/exchangeStatus.ts +0 -261
- package/src/math/funding.ts +0 -498
- package/src/math/insurance.ts +0 -171
- package/src/math/liquidation.ts +0 -433
- package/src/math/margin.ts +0 -327
- package/src/math/market.ts +0 -487
- package/src/math/oracles.ts +0 -477
- package/src/math/orders.ts +0 -500
- package/src/math/position.ts +0 -381
- package/src/math/repeg.ts +0 -223
- package/src/math/spotBalance.ts +0 -996
- package/src/math/spotMarket.ts +0 -115
- package/src/math/spotPosition.ts +0 -263
- package/src/math/state.ts +0 -71
- package/src/math/superStake.ts +0 -602
- package/src/math/tiers.ts +0 -73
- package/src/math/trade.ts +0 -879
- package/src/math/utils.ts +0 -183
- package/src/memcmp.ts +0 -279
- package/src/oracles/oracleClientCache.ts +0 -41
- package/src/oracles/oracleId.ts +0 -99
- package/src/oracles/prelaunchOracleClient.ts +0 -63
- package/src/oracles/pythClient.ts +0 -125
- package/src/oracles/pythLazerClient.ts +0 -141
- package/src/oracles/quoteAssetOracleClient.ts +0 -43
- package/src/oracles/strictOraclePrice.ts +0 -40
- package/src/oracles/types.ts +0 -82
- package/src/oracles/utils.ts +0 -45
- package/src/orderParams.ts +0 -117
- package/src/orderSubscriber/OrderSubscriber.ts +0 -378
- package/src/orderSubscriber/PollingSubscription.ts +0 -50
- package/src/orderSubscriber/WebsocketSubscription.ts +0 -137
- package/src/orderSubscriber/grpcSubscription.ts +0 -155
- package/src/orderSubscriber/index.ts +0 -7
- package/src/orderSubscriber/types.ts +0 -64
- package/src/priorityFee/averageOverSlotsStrategy.ts +0 -17
- package/src/priorityFee/averageStrategy.ts +0 -13
- package/src/priorityFee/ewmaStrategy.ts +0 -51
- package/src/priorityFee/heliusPriorityFeeMethod.ts +0 -68
- package/src/priorityFee/index.ts +0 -16
- package/src/priorityFee/maxOverSlotsStrategy.ts +0 -18
- package/src/priorityFee/maxStrategy.ts +0 -8
- package/src/priorityFee/priorityFeeSubscriber.ts +0 -328
- package/src/priorityFee/priorityFeeSubscriberMap.ts +0 -134
- package/src/priorityFee/solanaPriorityFeeMethod.ts +0 -46
- package/src/priorityFee/types.ts +0 -82
- package/src/priorityFee/velocityPriorityFeeMethod.ts +0 -53
- package/src/pyth/constants.ts +0 -9
- package/src/pyth/index.ts +0 -15
- package/src/pyth/pythLazerSubscriber.ts +0 -365
- package/src/pyth/types.ts +0 -4451
- package/src/pyth/utils.ts +0 -13
- package/src/slot/SlotSubscriber.ts +0 -126
- package/src/slot/SlothashSubscriber.ts +0 -163
- package/src/swap/UnifiedSwapClient.ts +0 -315
- package/src/swift/grpcSignedMsgUserAccountSubscriber.ts +0 -81
- package/src/swift/index.ts +0 -10
- package/src/swift/signedMsgUserAccountSubscriber.ts +0 -251
- package/src/swift/swiftOrderSubscriber.ts +0 -351
- package/src/testClient.ts +0 -42
- package/src/titan/titanClient.ts +0 -438
- package/src/token/index.ts +0 -13
- package/src/tokenFaucet.ts +0 -296
- package/src/tx/baseTxSender.ts +0 -651
- package/src/tx/blockhashFetcher/baseBlockhashFetcher.ts +0 -31
- package/src/tx/blockhashFetcher/cachedBlockhashFetcher.ts +0 -105
- package/src/tx/blockhashFetcher/types.ts +0 -9
- package/src/tx/fastSingleTxSender.ts +0 -200
- package/src/tx/forwardOnlyTxSender.ts +0 -193
- package/src/tx/priorityFeeCalculator.ts +0 -117
- package/src/tx/reportTransactionError.ts +0 -176
- package/src/tx/retryTxSender.ts +0 -177
- package/src/tx/txHandler.ts +0 -1009
- package/src/tx/txParamProcessor.ts +0 -225
- package/src/tx/types.ts +0 -117
- package/src/tx/utils.ts +0 -133
- package/src/tx/whileValidTxSender.ts +0 -377
- package/src/types.ts +0 -2575
- package/src/user.ts +0 -5316
- package/src/userConfig.ts +0 -38
- package/src/userMap/PollingSubscription.ts +0 -61
- package/src/userMap/WebsocketSubscription.ts +0 -93
- package/src/userMap/grpcSubscription.ts +0 -93
- package/src/userMap/referrerMap.ts +0 -360
- package/src/userMap/revenueShareEscrowMap.ts +0 -342
- package/src/userMap/userMap.ts +0 -784
- package/src/userMap/userMapConfig.ts +0 -82
- package/src/userMap/userStatsMap.ts +0 -393
- package/src/userName.ts +0 -37
- package/src/userStats.ts +0 -159
- package/src/userStatsConfig.ts +0 -35
- package/src/util/TransactionConfirmationManager.ts +0 -333
- package/src/util/chainClock.ts +0 -65
- package/src/util/computeUnits.ts +0 -101
- package/src/util/digest.ts +0 -33
- package/src/util/ed25519Utils.ts +0 -95
- package/src/util/promiseTimeout.ts +0 -23
- package/src/velocityClient.ts +0 -14802
- package/src/velocityClientConfig.ts +0 -119
- package/src/wallet.ts +0 -91
- package/tests/VelocityCore/builder_escrow.test.ts +0 -121
- package/tests/VelocityCore/decode.test.ts +0 -16
- package/tests/VelocityCore/fill_trigger.test.ts +0 -73
- package/tests/VelocityCore/instructions.test.ts +0 -46
- package/tests/VelocityCore/pdas.test.ts +0 -30
- package/tests/VelocityCore/perp_orders.test.ts +0 -205
- package/tests/VelocityCore/remainingAccounts.test.ts +0 -72
- package/tests/VelocityCore/settlement_liquidation.test.ts +0 -69
- package/tests/accounts/customizedCadenceBulkAccountLoader.test.ts +0 -211
- package/tests/amm/test.ts +0 -2402
- package/tests/auctions/test.ts +0 -81
- package/tests/bn/test.ts +0 -355
- package/tests/builder/builderFee.test.ts +0 -42
- package/tests/ci/verifyConstants.ts +0 -393
- package/tests/decode/test.ts +0 -262
- package/tests/decode/userAccountBufferStrings.ts +0 -102
- package/tests/dlob/helpers.ts +0 -748
- package/tests/dlob/test.ts +0 -6969
- package/tests/dlob/tickSizeStandardization.ts +0 -545
- package/tests/events/parseLogsForCuUsage.ts +0 -139
- package/tests/exchangeStatus/test.ts +0 -45
- package/tests/insurance/test.ts +0 -40
- package/tests/liquidation/test.ts +0 -125
- package/tests/oracles/mmOracleGate.test.ts +0 -379
- package/tests/oracles/pythPegSnap.test.ts +0 -76
- package/tests/sdkParity/enumParity.test.ts +0 -84
- package/tests/sdkParity/marginCategoryFill.test.ts +0 -143
- package/tests/sdkParity/memcmpOffsets.test.ts +0 -139
- package/tests/spot/test.ts +0 -281
- package/tests/tx/TransactionConfirmationManager.test.ts +0 -312
- package/tests/tx/cachedBlockhashFetcher.test.ts +0 -96
- package/tests/tx/priorityFeeCalculator.ts +0 -77
- package/tests/tx/priorityFeeStrategy.ts +0 -95
- package/tests/user/bankruptcy.ts +0 -165
- package/tests/user/feeAndWithdrawLimits.ts +0 -284
- package/tests/user/getMarginCalculation.ts +0 -586
- package/tests/user/helpers.ts +0 -184
- package/tests/user/liquidations.ts +0 -129
- package/tests/user/marginCalculations.test.ts +0 -321
- package/tests/user/test.ts +0 -833
- package/tsconfig.browser.json +0 -18
- package/tsconfig.json +0 -36
package/src/math/amm.ts
DELETED
|
@@ -1,1758 +0,0 @@
|
|
|
1
|
-
import { BN } from '../isomorphic/anchor';
|
|
2
|
-
import {
|
|
3
|
-
AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO,
|
|
4
|
-
PRICE_PRECISION,
|
|
5
|
-
PEG_PRECISION,
|
|
6
|
-
ZERO,
|
|
7
|
-
BID_ASK_SPREAD_PRECISION,
|
|
8
|
-
ONE,
|
|
9
|
-
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
10
|
-
QUOTE_PRECISION,
|
|
11
|
-
PRICE_DIV_PEG,
|
|
12
|
-
PERCENTAGE_PRECISION,
|
|
13
|
-
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
|
|
14
|
-
FUNDING_RATE_BUFFER_PRECISION,
|
|
15
|
-
FUNDING_RATE_OFFSET_PERCENTAGE,
|
|
16
|
-
FUNDING_RATE_OFFSET_DENOMINATOR,
|
|
17
|
-
TWO,
|
|
18
|
-
} from '../constants/numericConstants';
|
|
19
|
-
import {
|
|
20
|
-
AMM,
|
|
21
|
-
MarketStats,
|
|
22
|
-
PositionDirection,
|
|
23
|
-
SwapDirection,
|
|
24
|
-
PerpMarketAccount,
|
|
25
|
-
isVariant,
|
|
26
|
-
} from '../types';
|
|
27
|
-
import { assert } from '../assert/assert';
|
|
28
|
-
import { squareRootBN, sigNum, clampBN } from './utils';
|
|
29
|
-
import { standardizeBaseAssetAmount } from './orders';
|
|
30
|
-
|
|
31
|
-
import { MMOraclePriceData, OraclePriceData } from '../oracles/types';
|
|
32
|
-
import {
|
|
33
|
-
calculateRepegCost,
|
|
34
|
-
calculateAdjustKCost,
|
|
35
|
-
calculateBudgetedPeg,
|
|
36
|
-
} from './repeg';
|
|
37
|
-
|
|
38
|
-
import { calculateLiveOracleStd, getNewOracleConfPct } from './oracles';
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Solves for the `pegMultiplier` that would make the AMM's constant-product price equal
|
|
42
|
-
* `targetPrice` at the current reserves, mirroring `calculate_peg_from_target_price` in
|
|
43
|
-
* `vlp/amm/math/repeg.rs`. Rounds to the nearest peg unit and floors at 1.
|
|
44
|
-
* @param targetPrice Desired price, PRICE_PRECISION (1e6).
|
|
45
|
-
* @param baseAssetReserve AMM base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
46
|
-
* @param quoteAssetReserve AMM quote asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
47
|
-
* @returns Peg multiplier that yields `targetPrice`, PEG_PRECISION (1e6), never below 1.
|
|
48
|
-
*/
|
|
49
|
-
export function calculatePegFromTargetPrice(
|
|
50
|
-
targetPrice: BN,
|
|
51
|
-
baseAssetReserve: BN,
|
|
52
|
-
quoteAssetReserve: BN
|
|
53
|
-
): BN {
|
|
54
|
-
return BN.max(
|
|
55
|
-
targetPrice
|
|
56
|
-
.mul(baseAssetReserve)
|
|
57
|
-
.div(quoteAssetReserve)
|
|
58
|
-
.add(PRICE_DIV_PEG.div(new BN(2)))
|
|
59
|
-
.div(PRICE_DIV_PEG),
|
|
60
|
-
ONE
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Computes the oracle-implied target price/peg for a repeg and the quote budget available to
|
|
66
|
-
* fund it, mirroring `calculate_optimal_peg_and_budget` in `vlp/amm/math/repeg.rs`. The
|
|
67
|
-
* budget is the AMM's own retained equity (`max(0, totalFeeMinusDistributions)`) — there is
|
|
68
|
-
* no separate protocol floor post-isolation. If that budget can't cover a direct repeg to
|
|
69
|
-
* the oracle price, the target is pulled back to the edge of half the market's max spread
|
|
70
|
-
* instead (a partial repeg that costs exactly the recomputed budget), and
|
|
71
|
-
* `checkLowerBound` is set to `false` since that partial move is unconditionally affordable
|
|
72
|
-
* by construction. `checkLowerBound` is also `false` when the budget is exactly zero (no
|
|
73
|
-
* equity to spend at all — `calculateUpdatedAMM` uses this to know whether it must reject
|
|
74
|
-
* the whole update or can proceed with the recomputed, always-affordable target).
|
|
75
|
-
* @param amm AMM state (reserves, `pegMultiplier`, `totalFeeMinusDistributions`, `maxSpread`).
|
|
76
|
-
* @param mmOraclePriceData Current MM oracle price data; `price` is the desired target.
|
|
77
|
-
* @returns `[targetPrice, newPeg, budget, checkLowerBound]`: `targetPrice`/`newPeg` are
|
|
78
|
-
* PRICE_PRECISION (1e6) / PEG_PRECISION (1e6) respectively; `budget` is the quote amount
|
|
79
|
-
* available to spend, QUOTE_PRECISION (1e6); `checkLowerBound` tells the caller whether it
|
|
80
|
-
* must still verify the repeg doesn't push `totalFeeMinusDistributions` negative.
|
|
81
|
-
*/
|
|
82
|
-
export function calculateOptimalPegAndBudget(
|
|
83
|
-
amm: AMM,
|
|
84
|
-
mmOraclePriceData: MMOraclePriceData
|
|
85
|
-
): [BN, BN, BN, boolean] {
|
|
86
|
-
const reservePriceBefore = calculatePrice(
|
|
87
|
-
amm.baseAssetReserve,
|
|
88
|
-
amm.quoteAssetReserve,
|
|
89
|
-
amm.pegMultiplier
|
|
90
|
-
);
|
|
91
|
-
const targetPrice = mmOraclePriceData.price;
|
|
92
|
-
const newPeg = calculatePegFromTargetPrice(
|
|
93
|
-
targetPrice,
|
|
94
|
-
amm.baseAssetReserve,
|
|
95
|
-
amm.quoteAssetReserve
|
|
96
|
-
);
|
|
97
|
-
const prePegCost = calculateRepegCost(amm, newPeg);
|
|
98
|
-
|
|
99
|
-
// no protocol floor post-isolation: tfmd contains only the AMM's own
|
|
100
|
-
// equity and is fully spendable on the repeg
|
|
101
|
-
const budget = BN.max(ZERO, amm.totalFeeMinusDistributions);
|
|
102
|
-
|
|
103
|
-
let checkLowerBound = true;
|
|
104
|
-
if (budget.lt(prePegCost)) {
|
|
105
|
-
const halfMaxPriceSpread = new BN(amm.maxSpread)
|
|
106
|
-
.div(new BN(2))
|
|
107
|
-
.mul(targetPrice)
|
|
108
|
-
.div(BID_ASK_SPREAD_PRECISION);
|
|
109
|
-
|
|
110
|
-
let newTargetPrice: BN;
|
|
111
|
-
let newOptimalPeg: BN;
|
|
112
|
-
let newBudget: BN;
|
|
113
|
-
const targetPriceGap = reservePriceBefore.sub(targetPrice);
|
|
114
|
-
|
|
115
|
-
if (targetPriceGap.abs().gt(halfMaxPriceSpread)) {
|
|
116
|
-
const markAdj = targetPriceGap.abs().sub(halfMaxPriceSpread);
|
|
117
|
-
|
|
118
|
-
if (targetPriceGap.lt(new BN(0))) {
|
|
119
|
-
newTargetPrice = reservePriceBefore.add(markAdj);
|
|
120
|
-
} else {
|
|
121
|
-
newTargetPrice = reservePriceBefore.sub(markAdj);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
newOptimalPeg = calculatePegFromTargetPrice(
|
|
125
|
-
newTargetPrice,
|
|
126
|
-
amm.baseAssetReserve,
|
|
127
|
-
amm.quoteAssetReserve
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
newBudget = calculateRepegCost(amm, newOptimalPeg);
|
|
131
|
-
checkLowerBound = false;
|
|
132
|
-
|
|
133
|
-
return [newTargetPrice, newOptimalPeg, newBudget, false];
|
|
134
|
-
} else if (budget.eq(ZERO)) {
|
|
135
|
-
// mirrors the program: budget = max(0, tfmd), so a zero budget
|
|
136
|
-
// means the AMM has no equity to spend (no floor post-isolation)
|
|
137
|
-
checkLowerBound = false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return [targetPrice, newPeg, budget, checkLowerBound];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Determines the full curve update (repeg cost, K scale factor, new peg) for `amm` against
|
|
146
|
-
* the current oracle price, mirroring the "use full budget peg" fallback path of `adjust_amm`
|
|
147
|
-
* in `vlp/amm/math/repeg.rs`. Starts from `calculateOptimalPegAndBudget`'s target/budget; if
|
|
148
|
-
* the direct repeg cost meets or exceeds that budget, shrinks `sqrtK` by 0.1% (999/1000) via
|
|
149
|
-
* `calculateAdjustKCost` first to free up additional budget, then re-solves for the peg with
|
|
150
|
-
* `calculateBudgetedPeg` using the combined budget.
|
|
151
|
-
* @param amm AMM state to evaluate a curve update for.
|
|
152
|
-
* @param mmOraclePriceData Current MM oracle price data.
|
|
153
|
-
* @returns `[prePegCost, pKNumer, pKDenom, newPeg, checkLowerBound]`: `prePegCost` is the
|
|
154
|
-
* quote cost of the full update, QUOTE_PRECISION (1e6); `pKNumer`/`pKDenom` are the sqrtK
|
|
155
|
-
* scale factor (999/1000 if K was shrunk, else 1/1); `newPeg` is PEG_PRECISION (1e6);
|
|
156
|
-
* `checkLowerBound` is forwarded from `calculateOptimalPegAndBudget` and tells
|
|
157
|
-
* `calculateUpdatedAMM` whether it must still verify affordability against
|
|
158
|
-
* `totalFeeMinusDistributions`.
|
|
159
|
-
*/
|
|
160
|
-
export function calculateNewAmm(
|
|
161
|
-
amm: AMM,
|
|
162
|
-
mmOraclePriceData: MMOraclePriceData
|
|
163
|
-
): [BN, BN, BN, BN, boolean] {
|
|
164
|
-
let pKNumer = new BN(1);
|
|
165
|
-
let pKDenom = new BN(1);
|
|
166
|
-
|
|
167
|
-
const [targetPrice, _newPeg, budget, checkLowerBound] =
|
|
168
|
-
calculateOptimalPegAndBudget(amm, mmOraclePriceData);
|
|
169
|
-
let prePegCost = calculateRepegCost(amm, _newPeg);
|
|
170
|
-
let newPeg = _newPeg;
|
|
171
|
-
|
|
172
|
-
if (prePegCost.gte(budget) && prePegCost.gt(ZERO)) {
|
|
173
|
-
[pKNumer, pKDenom] = [new BN(999), new BN(1000)];
|
|
174
|
-
const deficitMadeup = calculateAdjustKCost(amm, pKNumer, pKDenom);
|
|
175
|
-
assert(deficitMadeup.lte(new BN(0)));
|
|
176
|
-
prePegCost = budget.add(deficitMadeup.abs());
|
|
177
|
-
const newAmm = Object.assign({}, amm);
|
|
178
|
-
newAmm.baseAssetReserve = newAmm.baseAssetReserve.mul(pKNumer).div(pKDenom);
|
|
179
|
-
newAmm.sqrtK = newAmm.sqrtK.mul(pKNumer).div(pKDenom);
|
|
180
|
-
const invariant = newAmm.sqrtK.mul(newAmm.sqrtK);
|
|
181
|
-
newAmm.quoteAssetReserve = invariant.div(newAmm.baseAssetReserve);
|
|
182
|
-
const directionToClose = amm.baseAssetAmountWithAmm.gt(ZERO)
|
|
183
|
-
? PositionDirection.SHORT
|
|
184
|
-
: PositionDirection.LONG;
|
|
185
|
-
|
|
186
|
-
const [newQuoteAssetReserve, _newBaseAssetReserve] =
|
|
187
|
-
calculateAmmReservesAfterSwap(
|
|
188
|
-
newAmm,
|
|
189
|
-
'base',
|
|
190
|
-
amm.baseAssetAmountWithAmm.abs(),
|
|
191
|
-
getSwapDirection('base', directionToClose)
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
newAmm.terminalQuoteAssetReserve = newQuoteAssetReserve;
|
|
195
|
-
newPeg = calculateBudgetedPeg(newAmm, prePegCost, targetPrice);
|
|
196
|
-
prePegCost = calculateRepegCost(newAmm, newPeg);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return [prePegCost, pKNumer, pKDenom, newPeg, checkLowerBound];
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Returns a copy of `amm` with its curve (peg, reserves, sqrtK) repegged/updated to the
|
|
204
|
-
* current oracle price, or `amm` unchanged if no update applies. Mirrors the program's
|
|
205
|
-
* curve-update gating: a no-op if `curveUpdateIntensity == 0`, if `mmOraclePriceData` is
|
|
206
|
-
* omitted, or if the oracle price is non-positive (mirrors
|
|
207
|
-
* `is_oracle_valid_for_action(..., UpdateAMMCurve)` — only a non-positive price invalidates
|
|
208
|
-
* the update here). **Affordability gate:** when `calculateNewAmm`'s `checkLowerBound` is
|
|
209
|
-
* true and debiting the computed cost from `totalFeeMinusDistributions` would push it
|
|
210
|
-
* negative, the update is rejected wholesale and `amm` is returned unchanged — the AMM will
|
|
211
|
-
* never spend equity it doesn't have on a repeg. When the update proceeds, both
|
|
212
|
-
* `totalFeeMinusDistributions` and `netRevenueSinceLastFunding` are debited by the repeg
|
|
213
|
-
* cost.
|
|
214
|
-
* @param amm AMM state to update.
|
|
215
|
-
* @param mmOraclePriceData Current MM oracle price data; omit to skip the update entirely.
|
|
216
|
-
* @returns Updated `AMM` (new object), or the original `amm` reference if no update applies or the affordability gate rejects it.
|
|
217
|
-
*/
|
|
218
|
-
export function calculateUpdatedAMM(
|
|
219
|
-
amm: AMM,
|
|
220
|
-
mmOraclePriceData?: MMOraclePriceData
|
|
221
|
-
): AMM {
|
|
222
|
-
if (amm.curveUpdateIntensity == 0 || mmOraclePriceData === undefined) {
|
|
223
|
-
return amm;
|
|
224
|
-
}
|
|
225
|
-
// mirrors is_oracle_valid_for_action(..., UpdateAMMCurve): only a
|
|
226
|
-
// non-positive oracle price invalidates the curve update
|
|
227
|
-
if (mmOraclePriceData.price.lte(ZERO)) {
|
|
228
|
-
return amm;
|
|
229
|
-
}
|
|
230
|
-
const newAmm = Object.assign({}, amm);
|
|
231
|
-
const [prepegCost, pKNumer, pKDenom, newPeg, checkLowerBound] =
|
|
232
|
-
calculateNewAmm(amm, mmOraclePriceData);
|
|
233
|
-
|
|
234
|
-
if (prepegCost.gt(ZERO)) {
|
|
235
|
-
const newTotalFeeMinusDistributions =
|
|
236
|
-
amm.totalFeeMinusDistributions.sub(prepegCost);
|
|
237
|
-
if (checkLowerBound && newTotalFeeMinusDistributions.lt(ZERO)) {
|
|
238
|
-
// affordability floor rejected the debit: passthrough, unchanged
|
|
239
|
-
return amm;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
newAmm.baseAssetReserve = newAmm.baseAssetReserve.mul(pKNumer).div(pKDenom);
|
|
244
|
-
newAmm.sqrtK = newAmm.sqrtK.mul(pKNumer).div(pKDenom);
|
|
245
|
-
const invariant = newAmm.sqrtK.mul(newAmm.sqrtK);
|
|
246
|
-
newAmm.quoteAssetReserve = invariant.div(newAmm.baseAssetReserve);
|
|
247
|
-
newAmm.pegMultiplier = newPeg;
|
|
248
|
-
|
|
249
|
-
const directionToClose = amm.baseAssetAmountWithAmm.gt(ZERO)
|
|
250
|
-
? PositionDirection.SHORT
|
|
251
|
-
: PositionDirection.LONG;
|
|
252
|
-
|
|
253
|
-
const [newQuoteAssetReserve, _newBaseAssetReserve] =
|
|
254
|
-
calculateAmmReservesAfterSwap(
|
|
255
|
-
newAmm,
|
|
256
|
-
'base',
|
|
257
|
-
amm.baseAssetAmountWithAmm.abs(),
|
|
258
|
-
getSwapDirection('base', directionToClose)
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
newAmm.terminalQuoteAssetReserve = newQuoteAssetReserve;
|
|
262
|
-
|
|
263
|
-
newAmm.totalFeeMinusDistributions =
|
|
264
|
-
newAmm.totalFeeMinusDistributions.sub(prepegCost);
|
|
265
|
-
newAmm.netRevenueSinceLastFunding =
|
|
266
|
-
newAmm.netRevenueSinceLastFunding.sub(prepegCost);
|
|
267
|
-
return newAmm;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Repegs `amm` to the current oracle price (`calculateUpdatedAMM`) and returns the
|
|
272
|
-
* one-sided spread reserves (bid reserves for `short`, ask reserves for `long`) plus the
|
|
273
|
-
* post-update `sqrtK`/peg — the reserves a trade closing/opening in `direction` would
|
|
274
|
-
* actually execute against.
|
|
275
|
-
* @param amm AMM state to update and derive spread reserves from.
|
|
276
|
-
* @param marketStats Market stats needed for spread and reference-price-offset calculation.
|
|
277
|
-
* @param direction Which side's spread reserves to return.
|
|
278
|
-
* @param mmOraclePriceData Current MM oracle price data, forwarded to `calculateUpdatedAMM`.
|
|
279
|
-
* @param latestSlot Current slot, forwarded for reference-price-offset smoothing.
|
|
280
|
-
* @returns `baseAssetReserve`/`quoteAssetReserve` for the requested side (AMM_RESERVE_PRECISION, 1e9), and the post-update `sqrtK`/`newPeg` (AMM_RESERVE_PRECISION 1e9 / PEG_PRECISION 1e6).
|
|
281
|
-
*/
|
|
282
|
-
export function calculateUpdatedAMMSpreadReserves(
|
|
283
|
-
amm: AMM,
|
|
284
|
-
marketStats: MarketStats,
|
|
285
|
-
direction: PositionDirection,
|
|
286
|
-
mmOraclePriceData?: MMOraclePriceData,
|
|
287
|
-
latestSlot?: BN
|
|
288
|
-
): { baseAssetReserve: BN; quoteAssetReserve: BN; sqrtK: BN; newPeg: BN } {
|
|
289
|
-
const newAmm = calculateUpdatedAMM(amm, mmOraclePriceData);
|
|
290
|
-
const [shortReserves, longReserves] = calculateSpreadReserves(
|
|
291
|
-
newAmm,
|
|
292
|
-
marketStats,
|
|
293
|
-
mmOraclePriceData,
|
|
294
|
-
undefined,
|
|
295
|
-
latestSlot
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
const dirReserves = isVariant(direction, 'long')
|
|
299
|
-
? longReserves
|
|
300
|
-
: shortReserves;
|
|
301
|
-
|
|
302
|
-
const result = {
|
|
303
|
-
baseAssetReserve: dirReserves.baseAssetReserve,
|
|
304
|
-
quoteAssetReserve: dirReserves.quoteAssetReserve,
|
|
305
|
-
sqrtK: newAmm.sqrtK,
|
|
306
|
-
newPeg: newAmm.pegMultiplier,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
return result;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Returns the AMM's current bid and ask prices, computed from its spread reserves
|
|
314
|
-
* (`calculateSpreadReserves`) after optionally repegging to the oracle price first.
|
|
315
|
-
* @param amm AMM state to price.
|
|
316
|
-
* @param marketStats Market stats needed for spread calculation.
|
|
317
|
-
* @param mmOraclePriceData Current MM oracle price data; used both to repeg (if `withUpdate`) and to compute the spread.
|
|
318
|
-
* @param withUpdate If true (default), repegs `amm` to the oracle price (`calculateUpdatedAMM`) before pricing; if false, prices the AMM's stored reserves as-is.
|
|
319
|
-
* @param latestSlot Current slot, forwarded for reference-price-offset smoothing.
|
|
320
|
-
* @returns `[bidPrice, askPrice]`, both PRICE_PRECISION (1e6).
|
|
321
|
-
*/
|
|
322
|
-
export function calculateBidAskPrice(
|
|
323
|
-
amm: AMM,
|
|
324
|
-
marketStats: MarketStats,
|
|
325
|
-
mmOraclePriceData?: MMOraclePriceData,
|
|
326
|
-
withUpdate = true,
|
|
327
|
-
latestSlot?: BN
|
|
328
|
-
): [BN, BN] {
|
|
329
|
-
let newAmm: AMM;
|
|
330
|
-
if (withUpdate) {
|
|
331
|
-
newAmm = calculateUpdatedAMM(amm, mmOraclePriceData);
|
|
332
|
-
} else {
|
|
333
|
-
newAmm = amm;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const [bidReserves, askReserves] = calculateSpreadReserves(
|
|
337
|
-
newAmm,
|
|
338
|
-
marketStats,
|
|
339
|
-
mmOraclePriceData,
|
|
340
|
-
undefined,
|
|
341
|
-
latestSlot
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
const askPrice = calculatePrice(
|
|
345
|
-
askReserves.baseAssetReserve,
|
|
346
|
-
askReserves.quoteAssetReserve,
|
|
347
|
-
newAmm.pegMultiplier
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
const bidPrice = calculatePrice(
|
|
351
|
-
bidReserves.baseAssetReserve,
|
|
352
|
-
bidReserves.quoteAssetReserve,
|
|
353
|
-
newAmm.pegMultiplier
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
return [bidPrice, askPrice];
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Computes the constant-product price implied by a pair of AMM reserves and a peg multiplier:
|
|
361
|
-
* `quoteAssetReserves * pegMultiplier / baseAssetReserves`, converted to `PRICE_PRECISION`.
|
|
362
|
-
* `baseAssetReserves` and `quoteAssetReserves` must be the same precision (typically both
|
|
363
|
-
* `AMM_RESERVE_PRECISION`, 1e9) — this is a pure ratio, not tied to any specific reserve field.
|
|
364
|
-
* @param baseAssetReserves Base reserve amount, same precision as `quoteAssetReserves`.
|
|
365
|
-
* @param quoteAssetReserves Quote reserve amount, same precision as `baseAssetReserves`.
|
|
366
|
-
* @param pegMultiplier Peg multiplier to scale the ratio by, PEG_PRECISION (1e6).
|
|
367
|
-
* @returns Price, PRICE_PRECISION (1e6); zero if `baseAssetReserves` is zero.
|
|
368
|
-
*/
|
|
369
|
-
export function calculatePrice(
|
|
370
|
-
baseAssetReserves: BN,
|
|
371
|
-
quoteAssetReserves: BN,
|
|
372
|
-
pegMultiplier: BN
|
|
373
|
-
): BN {
|
|
374
|
-
if (baseAssetReserves.abs().lte(ZERO)) {
|
|
375
|
-
return new BN(0);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return quoteAssetReserves
|
|
379
|
-
.mul(PRICE_PRECISION)
|
|
380
|
-
.mul(pegMultiplier)
|
|
381
|
-
.div(PEG_PRECISION)
|
|
382
|
-
.div(baseAssetReserves);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/** Which side of an AMM swap an amount is denominated in. */
|
|
386
|
-
export type AssetType = 'quote' | 'base';
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Calculates what the AMM's reserves would be after swapping a quote or base asset amount
|
|
390
|
-
* against its constant-product curve (`sqrtK^2 = baseAssetReserve * quoteAssetReserve`). When
|
|
391
|
-
* `inputAssetType` is `'quote'`, `swapAmount` is first converted from `QUOTE_PRECISION`-scale
|
|
392
|
-
* quote units into the AMM's own quote-reserve precision via the peg multiplier before being
|
|
393
|
-
* applied.
|
|
394
|
-
* @param amm AMM state (`pegMultiplier`, `quoteAssetReserve`, `sqrtK`, `baseAssetReserve`).
|
|
395
|
-
* @param inputAssetType Which side `swapAmount` is denominated in.
|
|
396
|
-
* @param swapAmount Amount being swapped, QUOTE_PRECISION (1e6) if `inputAssetType` is `'quote'`, else AMM_RESERVE_PRECISION (1e9); must be non-negative.
|
|
397
|
-
* @param swapDirection Whether `swapAmount` is added to or removed from the AMM's reserve on the input side.
|
|
398
|
-
* @returns `[quoteAssetReserve, baseAssetReserve]` after the swap, both AMM_RESERVE_PRECISION (1e9).
|
|
399
|
-
*/
|
|
400
|
-
export function calculateAmmReservesAfterSwap(
|
|
401
|
-
amm: Pick<
|
|
402
|
-
AMM,
|
|
403
|
-
'pegMultiplier' | 'quoteAssetReserve' | 'sqrtK' | 'baseAssetReserve'
|
|
404
|
-
>,
|
|
405
|
-
inputAssetType: AssetType,
|
|
406
|
-
swapAmount: BN,
|
|
407
|
-
swapDirection: SwapDirection
|
|
408
|
-
): [BN, BN] {
|
|
409
|
-
assert(swapAmount.gte(ZERO), 'swapAmount must be greater than 0');
|
|
410
|
-
|
|
411
|
-
let newQuoteAssetReserve;
|
|
412
|
-
let newBaseAssetReserve;
|
|
413
|
-
|
|
414
|
-
if (inputAssetType === 'quote') {
|
|
415
|
-
swapAmount = swapAmount
|
|
416
|
-
.mul(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
|
|
417
|
-
.div(amm.pegMultiplier);
|
|
418
|
-
|
|
419
|
-
[newQuoteAssetReserve, newBaseAssetReserve] = calculateSwapOutput(
|
|
420
|
-
amm.quoteAssetReserve,
|
|
421
|
-
swapAmount,
|
|
422
|
-
swapDirection,
|
|
423
|
-
amm.sqrtK.mul(amm.sqrtK)
|
|
424
|
-
);
|
|
425
|
-
} else {
|
|
426
|
-
[newBaseAssetReserve, newQuoteAssetReserve] = calculateSwapOutput(
|
|
427
|
-
amm.baseAssetReserve,
|
|
428
|
-
swapAmount,
|
|
429
|
-
swapDirection,
|
|
430
|
-
amm.sqrtK.mul(amm.sqrtK)
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return [newQuoteAssetReserve, newBaseAssetReserve];
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Calculates how much base asset the AMM could still absorb on each side before hitting its
|
|
439
|
-
* configured reserve bounds — the AMM's own "open interest" available to bids/asks. Zeroes
|
|
440
|
-
* out a side if its available room is less than half a step size (dust, not fillable), when
|
|
441
|
-
* `stepSize` is provided.
|
|
442
|
-
* @param baseAssetReserve AMM's current base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
443
|
-
* @param minBaseAssetReserve AMM's minimum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
444
|
-
* @param maxBaseAssetReserve AMM's maximum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
445
|
-
* @param stepSize Optional order step size, AMM_RESERVE_PRECISION (1e9), used to zero out dust amounts.
|
|
446
|
-
* @returns `[openBids, openAsks]`: `openBids` non-negative (room to absorb more longs), `openAsks` non-positive (room to absorb more shorts), both AMM_RESERVE_PRECISION (1e9).
|
|
447
|
-
*/
|
|
448
|
-
export function calculateMarketOpenBidAsk(
|
|
449
|
-
baseAssetReserve: BN,
|
|
450
|
-
minBaseAssetReserve: BN,
|
|
451
|
-
maxBaseAssetReserve: BN,
|
|
452
|
-
stepSize?: BN
|
|
453
|
-
): [BN, BN] {
|
|
454
|
-
// open orders
|
|
455
|
-
let openAsks;
|
|
456
|
-
if (minBaseAssetReserve.lt(baseAssetReserve)) {
|
|
457
|
-
openAsks = baseAssetReserve.sub(minBaseAssetReserve).mul(new BN(-1));
|
|
458
|
-
|
|
459
|
-
if (stepSize && openAsks.abs().div(TWO).lt(stepSize)) {
|
|
460
|
-
openAsks = ZERO;
|
|
461
|
-
}
|
|
462
|
-
} else {
|
|
463
|
-
openAsks = ZERO;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
let openBids;
|
|
467
|
-
if (maxBaseAssetReserve.gt(baseAssetReserve)) {
|
|
468
|
-
openBids = maxBaseAssetReserve.sub(baseAssetReserve);
|
|
469
|
-
|
|
470
|
-
if (stepSize && openBids.div(TWO).lt(stepSize)) {
|
|
471
|
-
openBids = ZERO;
|
|
472
|
-
}
|
|
473
|
-
} else {
|
|
474
|
-
openBids = ZERO;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return [openBids, openAsks];
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Measures how skewed the AMM's net inventory is relative to the thinner of its two
|
|
482
|
-
* remaining liquidity sides, as a fraction: `|baseAssetAmountWithAmm| / minSideLiquidity`,
|
|
483
|
-
* capped at 100%. Feeds `calculateInventoryScale`'s spread widening — a fuller inventory
|
|
484
|
-
* relative to available liquidity widens the paying side's spread more.
|
|
485
|
-
* @param baseAssetAmountWithAmm AMM's net inventory, AMM_RESERVE_PRECISION (1e9, signed).
|
|
486
|
-
* @param baseAssetReserve AMM's current base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
487
|
-
* @param minBaseAssetReserve AMM's minimum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
488
|
-
* @param maxBaseAssetReserve AMM's maximum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
489
|
-
* @returns Inventory-to-min-side-liquidity ratio, PERCENTAGE_PRECISION (1e6), capped at 100%.
|
|
490
|
-
*/
|
|
491
|
-
export function calculateInventoryLiquidityRatio(
|
|
492
|
-
baseAssetAmountWithAmm: BN,
|
|
493
|
-
baseAssetReserve: BN,
|
|
494
|
-
minBaseAssetReserve: BN,
|
|
495
|
-
maxBaseAssetReserve: BN
|
|
496
|
-
): BN {
|
|
497
|
-
// inventory skew
|
|
498
|
-
const [openBids, openAsks] = calculateMarketOpenBidAsk(
|
|
499
|
-
baseAssetReserve,
|
|
500
|
-
minBaseAssetReserve,
|
|
501
|
-
maxBaseAssetReserve
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
const minSideLiquidity = BN.min(openBids.abs(), openAsks.abs());
|
|
505
|
-
|
|
506
|
-
const inventoryScaleBN = BN.min(
|
|
507
|
-
baseAssetAmountWithAmm
|
|
508
|
-
.mul(PERCENTAGE_PRECISION)
|
|
509
|
-
.div(BN.max(minSideLiquidity, ONE))
|
|
510
|
-
.abs(),
|
|
511
|
-
PERCENTAGE_PRECISION
|
|
512
|
-
);
|
|
513
|
-
return inventoryScaleBN;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Same shape as `calculateInventoryLiquidityRatio` but normalizes by the *average* of the
|
|
518
|
-
* two liquidity sides rather than the thinner side, used specifically as the liquidity
|
|
519
|
-
* fraction input to `calculateReferencePriceOffset` (whose offset should react to overall
|
|
520
|
-
* inventory pressure, not just the constraining side).
|
|
521
|
-
* @param baseAssetAmountWithAmm AMM's net inventory, AMM_RESERVE_PRECISION (1e9, signed).
|
|
522
|
-
* @param baseAssetReserve AMM's current base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
523
|
-
* @param minBaseAssetReserve AMM's minimum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
524
|
-
* @param maxBaseAssetReserve AMM's maximum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
525
|
-
* @returns Inventory-to-average-side-liquidity ratio, PERCENTAGE_PRECISION (1e6), capped at 100%.
|
|
526
|
-
*/
|
|
527
|
-
export function calculateInventoryLiquidityRatioForReferencePriceOffset(
|
|
528
|
-
baseAssetAmountWithAmm: BN,
|
|
529
|
-
baseAssetReserve: BN,
|
|
530
|
-
minBaseAssetReserve: BN,
|
|
531
|
-
maxBaseAssetReserve: BN
|
|
532
|
-
): BN {
|
|
533
|
-
// inventory skew
|
|
534
|
-
const [openBids, openAsks] = calculateMarketOpenBidAsk(
|
|
535
|
-
baseAssetReserve,
|
|
536
|
-
minBaseAssetReserve,
|
|
537
|
-
maxBaseAssetReserve
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
const avgSideLiquidity = openBids.abs().add(openAsks.abs()).div(TWO);
|
|
541
|
-
|
|
542
|
-
const inventoryScaleBN = BN.min(
|
|
543
|
-
baseAssetAmountWithAmm
|
|
544
|
-
.mul(PERCENTAGE_PRECISION)
|
|
545
|
-
.div(BN.max(avgSideLiquidity, ONE))
|
|
546
|
-
.abs(),
|
|
547
|
-
PERCENTAGE_PRECISION
|
|
548
|
-
);
|
|
549
|
-
return inventoryScaleBN;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Multiplier applied to the paying side's spread based on inventory skew
|
|
554
|
-
* (`calculateInventoryLiquidityRatio`), scaled so the multiplier never exceeds the greater of
|
|
555
|
-
* a fixed 10x cap or the ratio between the market's max spread and the current directional
|
|
556
|
-
* spread. Returns `1` (no scaling) for a flat AMM.
|
|
557
|
-
* @param baseAssetAmountWithAmm AMM's net inventory, AMM_RESERVE_PRECISION (1e9, signed).
|
|
558
|
-
* @param baseAssetReserve AMM's current base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
559
|
-
* @param minBaseAssetReserve AMM's minimum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
560
|
-
* @param maxBaseAssetReserve AMM's maximum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
561
|
-
* @param directionalSpread The spread (in `BID_ASK_SPREAD_PRECISION` bps-like units) on the inventory's own side, before this scale is applied.
|
|
562
|
-
* @param maxSpread Market's configured max spread, `BID_ASK_SPREAD_PRECISION`-scaled units.
|
|
563
|
-
* @returns Plain multiplier (not BN) to apply to the directional spread, `>= 1`.
|
|
564
|
-
*/
|
|
565
|
-
export function calculateInventoryScale(
|
|
566
|
-
baseAssetAmountWithAmm: BN,
|
|
567
|
-
baseAssetReserve: BN,
|
|
568
|
-
minBaseAssetReserve: BN,
|
|
569
|
-
maxBaseAssetReserve: BN,
|
|
570
|
-
directionalSpread: number,
|
|
571
|
-
maxSpread: number
|
|
572
|
-
): number {
|
|
573
|
-
if (baseAssetAmountWithAmm.eq(ZERO)) {
|
|
574
|
-
return 1;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
|
|
578
|
-
new BN(10)
|
|
579
|
-
);
|
|
580
|
-
|
|
581
|
-
const inventoryScaleBN = calculateInventoryLiquidityRatio(
|
|
582
|
-
baseAssetAmountWithAmm,
|
|
583
|
-
baseAssetReserve,
|
|
584
|
-
minBaseAssetReserve,
|
|
585
|
-
maxBaseAssetReserve
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
const inventoryScaleMaxBN = BN.max(
|
|
589
|
-
MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
|
|
590
|
-
new BN(maxSpread)
|
|
591
|
-
.mul(BID_ASK_SPREAD_PRECISION)
|
|
592
|
-
.div(new BN(Math.max(directionalSpread, 1)))
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
const inventoryScaleCapped =
|
|
596
|
-
BN.min(
|
|
597
|
-
inventoryScaleMaxBN,
|
|
598
|
-
BID_ASK_SPREAD_PRECISION.add(
|
|
599
|
-
inventoryScaleMaxBN.mul(inventoryScaleBN).div(PERCENTAGE_PRECISION)
|
|
600
|
-
)
|
|
601
|
-
).toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
|
|
602
|
-
|
|
603
|
-
return inventoryScaleCapped;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* Calculates the AMM's reference-price offset — a persistent skew applied to both bid and
|
|
608
|
-
* ask reserves (on top of the volatility/inventory spread) that lets the AMM's quoted price
|
|
609
|
-
* drift slightly off the raw oracle price when inventory and recent funding both point the
|
|
610
|
-
* same direction. Averages three clamped mark/oracle premium estimates (1-minute, 1-hour,
|
|
611
|
-
* and a 24h-funding-implied premium net of the `FUNDING_RATE_OFFSET_DENOMINATOR` baseline —
|
|
612
|
-
* this baseline subtraction is what keeps the offset from double-counting the funding rate's
|
|
613
|
-
* own built-in offset), converts to a price-relative percentage, then scales by half the
|
|
614
|
-
* (signed) inventory `liquidityFraction`. Zeroed out entirely when inventory skew and the
|
|
615
|
-
* premium disagree in sign (`!sigNum(liquidityFraction).eq(sigNum(markPremiumAvgPct))`) —
|
|
616
|
-
* the offset only applies when it would reduce net exposure, never to compound it. Returns
|
|
617
|
-
* zero immediately if there's no funding history or no inventory skew.
|
|
618
|
-
* @param reservePrice Current AMM reserve price, PRICE_PRECISION (1e6).
|
|
619
|
-
* @param last24hAvgFundingRate Market's 24h average funding rate, FUNDING_RATE_PRECISION-buffer-scaled (divided internally by `FUNDING_RATE_BUFFER_PRECISION`).
|
|
620
|
-
* @param liquidityFraction Signed inventory liquidity fraction (see `calculateInventoryLiquidityRatioForReferencePriceOffset`, sign-adjusted for inventory direction), PERCENTAGE_PRECISION (1e6).
|
|
621
|
-
* @param oracleTwapFast 5-minute oracle TWAP, PRICE_PRECISION (1e6).
|
|
622
|
-
* @param markTwapFast 5-minute mark TWAP, PRICE_PRECISION (1e6).
|
|
623
|
-
* @param oracleTwapSlow 1-hour oracle TWAP, PRICE_PRECISION (1e6).
|
|
624
|
-
* @param markTwapSlow 1-hour mark TWAP, PRICE_PRECISION (1e6).
|
|
625
|
-
* @param maxOffsetPct Maximum allowed offset, PERCENTAGE_PRECISION (1e6) fraction-of-price units — both the intermediate premium clamps and the final result are bounded by this.
|
|
626
|
-
* @returns Reference price offset, PERCENTAGE_PRECISION (1e6, signed), clamped to `±maxOffsetPct`.
|
|
627
|
-
*/
|
|
628
|
-
export function calculateReferencePriceOffset(
|
|
629
|
-
reservePrice: BN,
|
|
630
|
-
last24hAvgFundingRate: BN,
|
|
631
|
-
liquidityFraction: BN,
|
|
632
|
-
oracleTwapFast: BN,
|
|
633
|
-
markTwapFast: BN,
|
|
634
|
-
oracleTwapSlow: BN,
|
|
635
|
-
markTwapSlow: BN,
|
|
636
|
-
maxOffsetPct: number
|
|
637
|
-
): BN {
|
|
638
|
-
if (last24hAvgFundingRate.eq(ZERO) || liquidityFraction.eq(ZERO)) {
|
|
639
|
-
return ZERO;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const maxOffsetInPrice = new BN(maxOffsetPct)
|
|
643
|
-
.mul(reservePrice)
|
|
644
|
-
.div(PERCENTAGE_PRECISION);
|
|
645
|
-
|
|
646
|
-
// Calculate quote denominated market premium
|
|
647
|
-
const markPremiumMinute = clampBN(
|
|
648
|
-
markTwapFast.sub(oracleTwapFast),
|
|
649
|
-
maxOffsetInPrice.mul(new BN(-1)),
|
|
650
|
-
maxOffsetInPrice
|
|
651
|
-
);
|
|
652
|
-
|
|
653
|
-
const markPremiumHour = clampBN(
|
|
654
|
-
markTwapSlow.sub(oracleTwapSlow),
|
|
655
|
-
maxOffsetInPrice.mul(new BN(-1)),
|
|
656
|
-
maxOffsetInPrice
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
// Convert last24hAvgFundingRate to quote denominated premium
|
|
660
|
-
const markPremiumDay = clampBN(
|
|
661
|
-
last24hAvgFundingRate
|
|
662
|
-
.div(FUNDING_RATE_BUFFER_PRECISION)
|
|
663
|
-
.mul(new BN(24))
|
|
664
|
-
.sub(oracleTwapSlow.abs().div(FUNDING_RATE_OFFSET_DENOMINATOR)),
|
|
665
|
-
maxOffsetInPrice.mul(new BN(-1)),
|
|
666
|
-
maxOffsetInPrice
|
|
667
|
-
);
|
|
668
|
-
|
|
669
|
-
// Take average clamped premium as the price-based offset
|
|
670
|
-
const markPremiumAvg = markPremiumMinute
|
|
671
|
-
.add(markPremiumHour)
|
|
672
|
-
.add(markPremiumDay)
|
|
673
|
-
.div(new BN(3));
|
|
674
|
-
|
|
675
|
-
const markPremiumAvgPct = markPremiumAvg
|
|
676
|
-
.mul(PRICE_PRECISION)
|
|
677
|
-
.div(reservePrice);
|
|
678
|
-
|
|
679
|
-
// Only apply when inventory is consistent with recent and 24h market premium
|
|
680
|
-
let offsetPct = markPremiumAvgPct.mul(liquidityFraction.abs()).divn(2);
|
|
681
|
-
|
|
682
|
-
if (!sigNum(liquidityFraction).eq(sigNum(markPremiumAvgPct))) {
|
|
683
|
-
offsetPct = ZERO;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
const clampedOffsetPct = clampBN(
|
|
687
|
-
offsetPct,
|
|
688
|
-
new BN(-maxOffsetPct),
|
|
689
|
-
new BN(maxOffsetPct)
|
|
690
|
-
);
|
|
691
|
-
|
|
692
|
-
return clampedOffsetPct;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Estimates how "levered" the AMM's own net position is relative to its retained equity —
|
|
697
|
-
* the gap between the AMM's local (reserve-price-valued) exposure and its actual net
|
|
698
|
-
* inventory value, divided by `totalFeeMinusDistributions`. Used to scale up the spread on
|
|
699
|
-
* the inventory side when the AMM is thinly capitalized relative to its exposure (see
|
|
700
|
-
* `calculateSpreadBN`'s `effectiveLeverageCapped` term).
|
|
701
|
-
* @param baseSpread Market's configured base spread (unused directly here beyond being part of the caller's contract; kept for parity with the on-chain signature).
|
|
702
|
-
* @param quoteAssetReserve AMM quote asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
703
|
-
* @param terminalQuoteAssetReserve AMM terminal (post-close) quote asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
704
|
-
* @param pegMultiplier AMM peg multiplier, PEG_PRECISION (1e6).
|
|
705
|
-
* @param netBaseAssetAmount AMM's net inventory, AMM_RESERVE_PRECISION (1e9, signed).
|
|
706
|
-
* @param reservePrice Current AMM reserve price, PRICE_PRECISION (1e6).
|
|
707
|
-
* @param totalFeeMinusDistributions AMM's retained equity, QUOTE_PRECISION (1e6).
|
|
708
|
-
* @returns Plain (unitless) effective leverage ratio, floored at 0.
|
|
709
|
-
*/
|
|
710
|
-
export function calculateEffectiveLeverage(
|
|
711
|
-
baseSpread: number,
|
|
712
|
-
quoteAssetReserve: BN,
|
|
713
|
-
terminalQuoteAssetReserve: BN,
|
|
714
|
-
pegMultiplier: BN,
|
|
715
|
-
netBaseAssetAmount: BN,
|
|
716
|
-
reservePrice: BN,
|
|
717
|
-
totalFeeMinusDistributions: BN
|
|
718
|
-
): number {
|
|
719
|
-
// vAMM skew
|
|
720
|
-
const netBaseAssetValue = quoteAssetReserve
|
|
721
|
-
.sub(terminalQuoteAssetReserve)
|
|
722
|
-
.mul(pegMultiplier)
|
|
723
|
-
.div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
|
|
724
|
-
|
|
725
|
-
const localBaseAssetValue = netBaseAssetAmount
|
|
726
|
-
.mul(reservePrice)
|
|
727
|
-
.div(AMM_TO_QUOTE_PRECISION_RATIO.mul(PRICE_PRECISION));
|
|
728
|
-
|
|
729
|
-
const effectiveGap = Math.max(
|
|
730
|
-
0,
|
|
731
|
-
localBaseAssetValue.sub(netBaseAssetValue).toNumber()
|
|
732
|
-
);
|
|
733
|
-
|
|
734
|
-
const effectiveLeverage =
|
|
735
|
-
effectiveGap / (Math.max(0, totalFeeMinusDistributions.toNumber()) + 1) +
|
|
736
|
-
1 / QUOTE_PRECISION.toNumber();
|
|
737
|
-
|
|
738
|
-
return effectiveLeverage;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Computes the volatility-driven component of the AMM's bid/ask spread, before inventory,
|
|
743
|
-
* leverage, revenue-retreat, or funding-bias adjustments are layered on in `calculateSpreadBN`.
|
|
744
|
-
* Blends the recent mark/oracle standard deviation (`markStd`, `oracleStd`) with oracle
|
|
745
|
-
* confidence, then scales each side independently by that side's recent fill intensity
|
|
746
|
-
* relative to 24h volume (a side that's been trading heavily gets a wider spread on that side).
|
|
747
|
-
* The oracle confidence interval is dampened to 5% of its value below 25bps so tiny confidence
|
|
748
|
-
* noise doesn't dominate a quiet market.
|
|
749
|
-
* @param lastOracleConfPct Oracle confidence interval as a fraction of price, PERCENTAGE_PRECISION (1e6).
|
|
750
|
-
* @param reservePrice Current AMM reserve price, PRICE_PRECISION (1e6).
|
|
751
|
-
* @param markStd Recent mark price standard deviation, PRICE_PRECISION (1e6).
|
|
752
|
-
* @param oracleStd Recent oracle price standard deviation, PRICE_PRECISION (1e6).
|
|
753
|
-
* @param longIntensity Recent long-side fill volume intensity, BASE_PRECISION (1e9) or QUOTE_PRECISION depending on caller; only used relative to `volume24H`.
|
|
754
|
-
* @param shortIntensity Recent short-side fill volume intensity, same units as `longIntensity`.
|
|
755
|
-
* @param volume24H Trailing 24h volume, same units as `longIntensity`/`shortIntensity`.
|
|
756
|
-
* @returns `[longVolSpread, shortVolSpread]`, both PERCENTAGE_PRECISION (1e6) fraction-of-price units.
|
|
757
|
-
*/
|
|
758
|
-
export function calculateVolSpreadBN(
|
|
759
|
-
lastOracleConfPct: BN,
|
|
760
|
-
reservePrice: BN,
|
|
761
|
-
markStd: BN,
|
|
762
|
-
oracleStd: BN,
|
|
763
|
-
longIntensity: BN,
|
|
764
|
-
shortIntensity: BN,
|
|
765
|
-
volume24H: BN
|
|
766
|
-
): [BN, BN] {
|
|
767
|
-
const marketAvgStdPct = markStd
|
|
768
|
-
.add(oracleStd)
|
|
769
|
-
.mul(PERCENTAGE_PRECISION)
|
|
770
|
-
.div(reservePrice)
|
|
771
|
-
.div(new BN(4));
|
|
772
|
-
const volSpread = BN.max(lastOracleConfPct, marketAvgStdPct.div(new BN(2)));
|
|
773
|
-
|
|
774
|
-
const clampMin = PERCENTAGE_PRECISION.div(new BN(100));
|
|
775
|
-
const clampMax = PERCENTAGE_PRECISION;
|
|
776
|
-
|
|
777
|
-
const longVolSpreadFactor = clampBN(
|
|
778
|
-
longIntensity.mul(PERCENTAGE_PRECISION).div(BN.max(ONE, volume24H)),
|
|
779
|
-
clampMin,
|
|
780
|
-
clampMax
|
|
781
|
-
);
|
|
782
|
-
const shortVolSpreadFactor = clampBN(
|
|
783
|
-
shortIntensity.mul(PERCENTAGE_PRECISION).div(BN.max(ONE, volume24H)),
|
|
784
|
-
clampMin,
|
|
785
|
-
clampMax
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
// only consider confidence interval at full value when above 25 bps
|
|
789
|
-
let confComponent = lastOracleConfPct;
|
|
790
|
-
|
|
791
|
-
if (lastOracleConfPct.lte(PRICE_PRECISION.div(new BN(400)))) {
|
|
792
|
-
confComponent = lastOracleConfPct.div(new BN(20));
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
const longVolSpread = BN.max(
|
|
796
|
-
confComponent,
|
|
797
|
-
volSpread.mul(longVolSpreadFactor).div(PERCENTAGE_PRECISION)
|
|
798
|
-
);
|
|
799
|
-
const shortVolSpread = BN.max(
|
|
800
|
-
confComponent,
|
|
801
|
-
volSpread.mul(shortVolSpreadFactor).div(PERCENTAGE_PRECISION)
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
return [longVolSpread, shortVolSpread];
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Funding bias β(f) (BID_ASK_SPREAD_PRECISION): bounded multiplier for the
|
|
809
|
-
* paying-side spread while the vAMM is paying funding. Mirrors the program's
|
|
810
|
-
* `calculate_spread_funding_bias_scale`.
|
|
811
|
-
*
|
|
812
|
-
* ρ(f) = clamp(|f| / f_ref, 0, 1), f_ref = FUNDING_RATE_OFFSET_PERCENTAGE
|
|
813
|
-
* β(f) = 1 + s * ρ(f), s = fundingBiasSensitivity / 100
|
|
814
|
-
*
|
|
815
|
-
* f = 24h avg funding rate normalized to a daily fraction of the oracle twap
|
|
816
|
-
* captured at the last funding update. The vAMM pays when f * q < 0
|
|
817
|
-
* (q = baseAssetAmountWithAmm). Returns 1x when the vAMM receives funding or
|
|
818
|
-
* s = 0.
|
|
819
|
-
*/
|
|
820
|
-
export function calculateSpreadFundingBiasScale(
|
|
821
|
-
baseAssetAmountWithAmm: BN,
|
|
822
|
-
last24HAvgFundingRate: BN,
|
|
823
|
-
lastFundingOracleTwap: BN,
|
|
824
|
-
fundingBiasSensitivity: number
|
|
825
|
-
): number {
|
|
826
|
-
const one = BID_ASK_SPREAD_PRECISION.toNumber();
|
|
827
|
-
if (fundingBiasSensitivity === 0 || lastFundingOracleTwap.lte(ZERO)) {
|
|
828
|
-
return one;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// f: daily funding rate as a fraction of price, FUNDING_RATE_PRECISION
|
|
832
|
-
const fNorm = last24HAvgFundingRate
|
|
833
|
-
.mul(PRICE_PRECISION)
|
|
834
|
-
.div(lastFundingOracleTwap)
|
|
835
|
-
.muln(24);
|
|
836
|
-
|
|
837
|
-
// f * q >= 0: vAMM receives (or rate/inventory is zero), β = 1
|
|
838
|
-
if (fNorm.isZero() || baseAssetAmountWithAmm.isZero()) {
|
|
839
|
-
return one;
|
|
840
|
-
}
|
|
841
|
-
if (fNorm.isNeg() === baseAssetAmountWithAmm.isNeg()) {
|
|
842
|
-
return one;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// ρ = clamp(|f| / f_ref, 0, 1), PERCENTAGE_PRECISION
|
|
846
|
-
const ramp = BN.min(
|
|
847
|
-
fNorm.abs().mul(PERCENTAGE_PRECISION).div(FUNDING_RATE_OFFSET_PERCENTAGE),
|
|
848
|
-
PERCENTAGE_PRECISION
|
|
849
|
-
).toNumber();
|
|
850
|
-
|
|
851
|
-
// β = 1 + s * ρ
|
|
852
|
-
return one + Math.floor((fundingBiasSensitivity * ramp) / 100);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
* Full intermediate breakdown of `calculateSpreadBN`'s pipeline, returned instead of the plain
|
|
857
|
-
* `[longSpread, shortSpread]` tuple when `returnTerms` is `true` — useful for debugging/UI
|
|
858
|
-
* display of how each stage (volatility, peg-adjustment floor, inventory scale, effective
|
|
859
|
-
* leverage, revenue retreat, funding bias, max-spread clamp) contributed to the final spread.
|
|
860
|
-
* All numeric fields are plain numbers in `BID_ASK_SPREAD_PRECISION`/`PERCENTAGE_PRECISION`
|
|
861
|
-
* (1e6) fraction-of-price units except `effectiveLeverage`/`effectiveLeverageCapped`
|
|
862
|
-
* (unitless ratios) and `inventorySpreadScale`/`fundingBiasScale` (unitless multipliers,
|
|
863
|
-
* `fundingBiasScale` additionally pre-scaled by `BID_ASK_SPREAD_PRECISION`).
|
|
864
|
-
*/
|
|
865
|
-
export interface SpreadTerms {
|
|
866
|
-
longVolSpread: number;
|
|
867
|
-
shortVolSpread: number;
|
|
868
|
-
longSpreadwPS: number;
|
|
869
|
-
shortSpreadwPS: number;
|
|
870
|
-
maxTargetSpread: number;
|
|
871
|
-
inventorySpreadScale: number;
|
|
872
|
-
longSpreadwInvScale: number;
|
|
873
|
-
shortSpreadwInvScale: number;
|
|
874
|
-
effectiveLeverage: number;
|
|
875
|
-
effectiveLeverageCapped: number;
|
|
876
|
-
longSpreadwEL: number;
|
|
877
|
-
shortSpreadwEL: number;
|
|
878
|
-
revenueRetreatAmount: number;
|
|
879
|
-
halfRevenueRetreatAmount: number;
|
|
880
|
-
longSpreadwRevRetreat: number;
|
|
881
|
-
shortSpreadwRevRetreat: number;
|
|
882
|
-
fundingBiasScale: number;
|
|
883
|
-
longSpreadwFundingBias: number;
|
|
884
|
-
shortSpreadwFundingBias: number;
|
|
885
|
-
longSpreadwOffsetShrink: number;
|
|
886
|
-
shortSpreadwOffsetShrink: number;
|
|
887
|
-
totalSpread: number;
|
|
888
|
-
longSpread: number;
|
|
889
|
-
shortSpread: number;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Computes the AMM's directional (long/short) bid-ask spread, mirroring `calculate_spread` in
|
|
894
|
-
* `vlp/amm/math/amm_spread.rs`. Pipeline: start from `calculateVolSpreadBN`'s volatility
|
|
895
|
-
* spread, widen whichever side is on the far side of the oracle-vs-reserve price gap
|
|
896
|
-
* (`lastOracleReservePriceSpreadPct`), apply inventory skew scaling
|
|
897
|
-
* (`calculateInventoryScale`), scale by effective leverage when the AMM has positive retained
|
|
898
|
-
* equity (or a flat 10x when it doesn't), add a revenue-retreat widening when
|
|
899
|
-
* `netRevenueSinceLastFunding` is below the default retreat threshold, apply the funding-bias
|
|
900
|
-
* multiplier (`calculateSpreadFundingBiasScale`) to the paying side, apply the market's manual
|
|
901
|
-
* `ammInventorySpreadAdjustment` (%, shrink if negative/grow if positive), then clamp
|
|
902
|
-
* `longSpread + shortSpread` to `maxTargetSpread` (proportionally rebalancing whichever side is
|
|
903
|
-
* larger). Pass `returnTerms: true` to get the full `SpreadTerms` breakdown instead of just the
|
|
904
|
-
* final tuple.
|
|
905
|
-
* @param baseSpread Market's configured base spread floor (each side gets at least half), BID_ASK_SPREAD_PRECISION (1e6).
|
|
906
|
-
* @param lastOracleReservePriceSpreadPct Signed reserve-price-vs-oracle gap, BID_ASK_SPREAD_PRECISION (1e6); widens the side the reserve price is away from the oracle.
|
|
907
|
-
* @param lastOracleConfPct Oracle confidence interval as a fraction of price, PERCENTAGE_PRECISION (1e6).
|
|
908
|
-
* @param maxSpread Market's configured max total spread, BID_ASK_SPREAD_PRECISION (1e6).
|
|
909
|
-
* @param quoteAssetReserve AMM quote asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
910
|
-
* @param terminalQuoteAssetReserve AMM terminal (post-close) quote asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
911
|
-
* @param pegMultiplier AMM peg multiplier, PEG_PRECISION (1e6).
|
|
912
|
-
* @param baseAssetAmountWithAmm AMM's net inventory, AMM_RESERVE_PRECISION (1e9, signed).
|
|
913
|
-
* @param reservePrice Current AMM reserve price, PRICE_PRECISION (1e6).
|
|
914
|
-
* @param totalFeeMinusDistributions AMM's retained equity, QUOTE_PRECISION (1e6).
|
|
915
|
-
* @param netRevenueSinceLastFunding Net revenue accrued since the last funding update, QUOTE_PRECISION (1e6, signed); below `DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT` triggers a spread widening.
|
|
916
|
-
* @param baseAssetReserve AMM's current base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
917
|
-
* @param minBaseAssetReserve AMM's minimum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
918
|
-
* @param maxBaseAssetReserve AMM's maximum allowed base asset reserve, AMM_RESERVE_PRECISION (1e9).
|
|
919
|
-
* @param markStd Recent mark price standard deviation, PRICE_PRECISION (1e6).
|
|
920
|
-
* @param oracleStd Recent oracle price standard deviation, PRICE_PRECISION (1e6).
|
|
921
|
-
* @param longIntensity Recent long-side fill volume intensity (see `calculateVolSpreadBN`).
|
|
922
|
-
* @param shortIntensity Recent short-side fill volume intensity (see `calculateVolSpreadBN`).
|
|
923
|
-
* @param volume24H Trailing 24h volume, same units as `longIntensity`/`shortIntensity`.
|
|
924
|
-
* @param ammInventorySpreadAdjustment Market's manual spread adjustment, percent (-100..100); negative shrinks, positive grows.
|
|
925
|
-
* @param last24HAvgFundingRate Market's 24h average funding rate, forwarded to `calculateSpreadFundingBiasScale`; defaults to zero (no funding bias).
|
|
926
|
-
* @param lastFundingOracleTwap Oracle TWAP captured at the last funding update, forwarded to `calculateSpreadFundingBiasScale`; defaults to zero.
|
|
927
|
-
* @param fundingBiasSensitivity Market's funding-bias sensitivity setting (0-100); defaults to 0 (disabled).
|
|
928
|
-
* @param returnTerms When omitted/`false`, returns the `[longSpread, shortSpread]` tuple; when `true`, returns the full `SpreadTerms` breakdown instead.
|
|
929
|
-
* @returns `[longSpread, shortSpread]`, both BID_ASK_SPREAD_PRECISION (1e6) fraction-of-price units.
|
|
930
|
-
*/
|
|
931
|
-
export function calculateSpreadBN(
|
|
932
|
-
baseSpread: number,
|
|
933
|
-
lastOracleReservePriceSpreadPct: BN,
|
|
934
|
-
lastOracleConfPct: BN,
|
|
935
|
-
maxSpread: number,
|
|
936
|
-
quoteAssetReserve: BN,
|
|
937
|
-
terminalQuoteAssetReserve: BN,
|
|
938
|
-
pegMultiplier: BN,
|
|
939
|
-
baseAssetAmountWithAmm: BN,
|
|
940
|
-
reservePrice: BN,
|
|
941
|
-
totalFeeMinusDistributions: BN,
|
|
942
|
-
netRevenueSinceLastFunding: BN,
|
|
943
|
-
baseAssetReserve: BN,
|
|
944
|
-
minBaseAssetReserve: BN,
|
|
945
|
-
maxBaseAssetReserve: BN,
|
|
946
|
-
markStd: BN,
|
|
947
|
-
oracleStd: BN,
|
|
948
|
-
longIntensity: BN,
|
|
949
|
-
shortIntensity: BN,
|
|
950
|
-
volume24H: BN,
|
|
951
|
-
ammInventorySpreadAdjustment: number,
|
|
952
|
-
last24HAvgFundingRate?: BN,
|
|
953
|
-
lastFundingOracleTwap?: BN,
|
|
954
|
-
fundingBiasSensitivity?: number,
|
|
955
|
-
returnTerms?: false
|
|
956
|
-
): [number, number];
|
|
957
|
-
/**
|
|
958
|
-
* Same computation as the tuple-returning `calculateSpreadBN` overload, but with `returnTerms`
|
|
959
|
-
* forced to `true` so it returns the full `SpreadTerms` breakdown of every pipeline stage
|
|
960
|
-
* instead of just the final `[longSpread, shortSpread]`.
|
|
961
|
-
* @returns The full `SpreadTerms` breakdown, BID_ASK_SPREAD_PRECISION/PERCENTAGE_PRECISION (1e6) units per field (see `SpreadTerms`).
|
|
962
|
-
*/
|
|
963
|
-
export function calculateSpreadBN(
|
|
964
|
-
baseSpread: number,
|
|
965
|
-
lastOracleReservePriceSpreadPct: BN,
|
|
966
|
-
lastOracleConfPct: BN,
|
|
967
|
-
maxSpread: number,
|
|
968
|
-
quoteAssetReserve: BN,
|
|
969
|
-
terminalQuoteAssetReserve: BN,
|
|
970
|
-
pegMultiplier: BN,
|
|
971
|
-
baseAssetAmountWithAmm: BN,
|
|
972
|
-
reservePrice: BN,
|
|
973
|
-
totalFeeMinusDistributions: BN,
|
|
974
|
-
netRevenueSinceLastFunding: BN,
|
|
975
|
-
baseAssetReserve: BN,
|
|
976
|
-
minBaseAssetReserve: BN,
|
|
977
|
-
maxBaseAssetReserve: BN,
|
|
978
|
-
markStd: BN,
|
|
979
|
-
oracleStd: BN,
|
|
980
|
-
longIntensity: BN,
|
|
981
|
-
shortIntensity: BN,
|
|
982
|
-
volume24H: BN,
|
|
983
|
-
ammInventorySpreadAdjustment: number,
|
|
984
|
-
last24HAvgFundingRate: BN,
|
|
985
|
-
lastFundingOracleTwap: BN,
|
|
986
|
-
fundingBiasSensitivity: number,
|
|
987
|
-
returnTerms: true
|
|
988
|
-
): SpreadTerms;
|
|
989
|
-
export function calculateSpreadBN(
|
|
990
|
-
baseSpread: number,
|
|
991
|
-
lastOracleReservePriceSpreadPct: BN,
|
|
992
|
-
lastOracleConfPct: BN,
|
|
993
|
-
maxSpread: number,
|
|
994
|
-
quoteAssetReserve: BN,
|
|
995
|
-
terminalQuoteAssetReserve: BN,
|
|
996
|
-
pegMultiplier: BN,
|
|
997
|
-
baseAssetAmountWithAmm: BN,
|
|
998
|
-
reservePrice: BN,
|
|
999
|
-
totalFeeMinusDistributions: BN,
|
|
1000
|
-
netRevenueSinceLastFunding: BN,
|
|
1001
|
-
baseAssetReserve: BN,
|
|
1002
|
-
minBaseAssetReserve: BN,
|
|
1003
|
-
maxBaseAssetReserve: BN,
|
|
1004
|
-
markStd: BN,
|
|
1005
|
-
oracleStd: BN,
|
|
1006
|
-
longIntensity: BN,
|
|
1007
|
-
shortIntensity: BN,
|
|
1008
|
-
volume24H: BN,
|
|
1009
|
-
ammInventorySpreadAdjustment: number,
|
|
1010
|
-
last24HAvgFundingRate: BN = ZERO,
|
|
1011
|
-
lastFundingOracleTwap: BN = ZERO,
|
|
1012
|
-
fundingBiasSensitivity = 0,
|
|
1013
|
-
returnTerms = false
|
|
1014
|
-
): [number, number] | SpreadTerms {
|
|
1015
|
-
assert(Number.isInteger(baseSpread));
|
|
1016
|
-
assert(Number.isInteger(maxSpread));
|
|
1017
|
-
|
|
1018
|
-
const spreadTerms = {
|
|
1019
|
-
longVolSpread: 0,
|
|
1020
|
-
shortVolSpread: 0,
|
|
1021
|
-
longSpreadwPS: 0,
|
|
1022
|
-
shortSpreadwPS: 0,
|
|
1023
|
-
maxTargetSpread: 0,
|
|
1024
|
-
inventorySpreadScale: 0,
|
|
1025
|
-
longSpreadwInvScale: 0,
|
|
1026
|
-
shortSpreadwInvScale: 0,
|
|
1027
|
-
effectiveLeverage: 0,
|
|
1028
|
-
effectiveLeverageCapped: 0,
|
|
1029
|
-
longSpreadwEL: 0,
|
|
1030
|
-
shortSpreadwEL: 0,
|
|
1031
|
-
revenueRetreatAmount: 0,
|
|
1032
|
-
halfRevenueRetreatAmount: 0,
|
|
1033
|
-
longSpreadwRevRetreat: 0,
|
|
1034
|
-
shortSpreadwRevRetreat: 0,
|
|
1035
|
-
fundingBiasScale: 0,
|
|
1036
|
-
longSpreadwFundingBias: 0,
|
|
1037
|
-
shortSpreadwFundingBias: 0,
|
|
1038
|
-
longSpreadwOffsetShrink: 0,
|
|
1039
|
-
shortSpreadwOffsetShrink: 0,
|
|
1040
|
-
totalSpread: 0,
|
|
1041
|
-
longSpread: 0,
|
|
1042
|
-
shortSpread: 0,
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
const [longVolSpread, shortVolSpread] = calculateVolSpreadBN(
|
|
1046
|
-
lastOracleConfPct,
|
|
1047
|
-
reservePrice,
|
|
1048
|
-
markStd,
|
|
1049
|
-
oracleStd,
|
|
1050
|
-
longIntensity,
|
|
1051
|
-
shortIntensity,
|
|
1052
|
-
volume24H
|
|
1053
|
-
);
|
|
1054
|
-
|
|
1055
|
-
spreadTerms.longVolSpread = longVolSpread.toNumber();
|
|
1056
|
-
spreadTerms.shortVolSpread = shortVolSpread.toNumber();
|
|
1057
|
-
|
|
1058
|
-
let longSpread = Math.max(baseSpread / 2, longVolSpread.toNumber());
|
|
1059
|
-
let shortSpread = Math.max(baseSpread / 2, shortVolSpread.toNumber());
|
|
1060
|
-
|
|
1061
|
-
if (lastOracleReservePriceSpreadPct.gt(ZERO)) {
|
|
1062
|
-
shortSpread = Math.max(
|
|
1063
|
-
shortSpread,
|
|
1064
|
-
lastOracleReservePriceSpreadPct.abs().toNumber() +
|
|
1065
|
-
shortVolSpread.toNumber()
|
|
1066
|
-
);
|
|
1067
|
-
} else if (lastOracleReservePriceSpreadPct.lt(ZERO)) {
|
|
1068
|
-
longSpread = Math.max(
|
|
1069
|
-
longSpread,
|
|
1070
|
-
lastOracleReservePriceSpreadPct.abs().toNumber() +
|
|
1071
|
-
longVolSpread.toNumber()
|
|
1072
|
-
);
|
|
1073
|
-
}
|
|
1074
|
-
spreadTerms.longSpreadwPS = longSpread;
|
|
1075
|
-
spreadTerms.shortSpreadwPS = shortSpread;
|
|
1076
|
-
|
|
1077
|
-
const maxSpreadBaseline = Math.min(
|
|
1078
|
-
Math.max(
|
|
1079
|
-
lastOracleReservePriceSpreadPct.abs().toNumber(),
|
|
1080
|
-
lastOracleConfPct.muln(2).toNumber(),
|
|
1081
|
-
BN.max(markStd, oracleStd)
|
|
1082
|
-
.mul(PERCENTAGE_PRECISION)
|
|
1083
|
-
.div(reservePrice)
|
|
1084
|
-
.toNumber()
|
|
1085
|
-
),
|
|
1086
|
-
BID_ASK_SPREAD_PRECISION.toNumber()
|
|
1087
|
-
);
|
|
1088
|
-
|
|
1089
|
-
const maxTargetSpread: number = Math.floor(
|
|
1090
|
-
Math.max(maxSpread, maxSpreadBaseline)
|
|
1091
|
-
);
|
|
1092
|
-
|
|
1093
|
-
const inventorySpreadScale = calculateInventoryScale(
|
|
1094
|
-
baseAssetAmountWithAmm,
|
|
1095
|
-
baseAssetReserve,
|
|
1096
|
-
minBaseAssetReserve,
|
|
1097
|
-
maxBaseAssetReserve,
|
|
1098
|
-
baseAssetAmountWithAmm.gt(ZERO) ? longSpread : shortSpread,
|
|
1099
|
-
maxTargetSpread
|
|
1100
|
-
);
|
|
1101
|
-
|
|
1102
|
-
if (baseAssetAmountWithAmm.gt(ZERO)) {
|
|
1103
|
-
longSpread *= inventorySpreadScale;
|
|
1104
|
-
} else if (baseAssetAmountWithAmm.lt(ZERO)) {
|
|
1105
|
-
shortSpread *= inventorySpreadScale;
|
|
1106
|
-
}
|
|
1107
|
-
spreadTerms.maxTargetSpread = maxTargetSpread;
|
|
1108
|
-
spreadTerms.inventorySpreadScale = inventorySpreadScale;
|
|
1109
|
-
spreadTerms.longSpreadwInvScale = longSpread;
|
|
1110
|
-
spreadTerms.shortSpreadwInvScale = shortSpread;
|
|
1111
|
-
|
|
1112
|
-
const MAX_SPREAD_SCALE = 10;
|
|
1113
|
-
if (totalFeeMinusDistributions.gt(ZERO)) {
|
|
1114
|
-
const effectiveLeverage = calculateEffectiveLeverage(
|
|
1115
|
-
baseSpread,
|
|
1116
|
-
quoteAssetReserve,
|
|
1117
|
-
terminalQuoteAssetReserve,
|
|
1118
|
-
pegMultiplier,
|
|
1119
|
-
baseAssetAmountWithAmm,
|
|
1120
|
-
reservePrice,
|
|
1121
|
-
totalFeeMinusDistributions
|
|
1122
|
-
);
|
|
1123
|
-
spreadTerms.effectiveLeverage = effectiveLeverage;
|
|
1124
|
-
|
|
1125
|
-
const spreadScale = Math.min(MAX_SPREAD_SCALE, 1 + effectiveLeverage);
|
|
1126
|
-
spreadTerms.effectiveLeverageCapped = spreadScale;
|
|
1127
|
-
|
|
1128
|
-
if (baseAssetAmountWithAmm.gt(ZERO)) {
|
|
1129
|
-
longSpread *= spreadScale;
|
|
1130
|
-
longSpread = Math.floor(longSpread);
|
|
1131
|
-
} else {
|
|
1132
|
-
shortSpread *= spreadScale;
|
|
1133
|
-
shortSpread = Math.floor(shortSpread);
|
|
1134
|
-
}
|
|
1135
|
-
} else {
|
|
1136
|
-
longSpread *= MAX_SPREAD_SCALE;
|
|
1137
|
-
shortSpread *= MAX_SPREAD_SCALE;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
spreadTerms.longSpreadwEL = longSpread;
|
|
1141
|
-
spreadTerms.shortSpreadwEL = shortSpread;
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1144
|
-
netRevenueSinceLastFunding.lt(
|
|
1145
|
-
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT
|
|
1146
|
-
)
|
|
1147
|
-
) {
|
|
1148
|
-
const maxRetreat = maxTargetSpread / 10;
|
|
1149
|
-
let revenueRetreatAmount = maxRetreat;
|
|
1150
|
-
if (
|
|
1151
|
-
netRevenueSinceLastFunding.gte(
|
|
1152
|
-
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.mul(new BN(1000))
|
|
1153
|
-
)
|
|
1154
|
-
) {
|
|
1155
|
-
revenueRetreatAmount = Math.min(
|
|
1156
|
-
maxRetreat,
|
|
1157
|
-
Math.floor(
|
|
1158
|
-
(baseSpread * netRevenueSinceLastFunding.abs().toNumber()) /
|
|
1159
|
-
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.abs().toNumber()
|
|
1160
|
-
)
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
const halfRevenueRetreatAmount = Math.floor(revenueRetreatAmount / 2);
|
|
1165
|
-
|
|
1166
|
-
spreadTerms.revenueRetreatAmount = revenueRetreatAmount;
|
|
1167
|
-
spreadTerms.halfRevenueRetreatAmount = halfRevenueRetreatAmount;
|
|
1168
|
-
|
|
1169
|
-
if (baseAssetAmountWithAmm.gt(ZERO)) {
|
|
1170
|
-
longSpread += revenueRetreatAmount;
|
|
1171
|
-
shortSpread += halfRevenueRetreatAmount;
|
|
1172
|
-
} else if (baseAssetAmountWithAmm.lt(ZERO)) {
|
|
1173
|
-
longSpread += halfRevenueRetreatAmount;
|
|
1174
|
-
shortSpread += revenueRetreatAmount;
|
|
1175
|
-
} else {
|
|
1176
|
-
longSpread += halfRevenueRetreatAmount;
|
|
1177
|
-
shortSpread += halfRevenueRetreatAmount;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
spreadTerms.longSpreadwRevRetreat = longSpread;
|
|
1182
|
-
spreadTerms.shortSpreadwRevRetreat = shortSpread;
|
|
1183
|
-
|
|
1184
|
-
// funding bias: w_pay = min(w_max, (w_0 * σ(q) * λ(q) + r(q)) * β(f)).
|
|
1185
|
-
// β multiplies the fully built paying side only, selected by sign(q)
|
|
1186
|
-
// (the same side σ widens); the max-spread cap below still bounds it.
|
|
1187
|
-
// β = 1 when the vAMM receives.
|
|
1188
|
-
const fundingBiasScale = calculateSpreadFundingBiasScale(
|
|
1189
|
-
baseAssetAmountWithAmm,
|
|
1190
|
-
last24HAvgFundingRate,
|
|
1191
|
-
lastFundingOracleTwap,
|
|
1192
|
-
fundingBiasSensitivity
|
|
1193
|
-
);
|
|
1194
|
-
const spreadPrecision = BID_ASK_SPREAD_PRECISION.toNumber();
|
|
1195
|
-
if (fundingBiasScale > spreadPrecision) {
|
|
1196
|
-
if (baseAssetAmountWithAmm.gt(ZERO)) {
|
|
1197
|
-
longSpread = Math.floor(
|
|
1198
|
-
(longSpread * fundingBiasScale) / spreadPrecision
|
|
1199
|
-
);
|
|
1200
|
-
} else if (baseAssetAmountWithAmm.lt(ZERO)) {
|
|
1201
|
-
shortSpread = Math.floor(
|
|
1202
|
-
(shortSpread * fundingBiasScale) / spreadPrecision
|
|
1203
|
-
);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
spreadTerms.fundingBiasScale = fundingBiasScale;
|
|
1207
|
-
spreadTerms.longSpreadwFundingBias = longSpread;
|
|
1208
|
-
spreadTerms.shortSpreadwFundingBias = shortSpread;
|
|
1209
|
-
|
|
1210
|
-
if (ammInventorySpreadAdjustment < 0) {
|
|
1211
|
-
const adjustment = Math.abs(ammInventorySpreadAdjustment);
|
|
1212
|
-
|
|
1213
|
-
const shrunkLong = Math.max(
|
|
1214
|
-
1,
|
|
1215
|
-
longSpread - Math.floor((longSpread * adjustment) / 100)
|
|
1216
|
-
);
|
|
1217
|
-
const shrunkShort = Math.max(
|
|
1218
|
-
1,
|
|
1219
|
-
shortSpread - Math.floor((shortSpread * adjustment) / 100)
|
|
1220
|
-
);
|
|
1221
|
-
|
|
1222
|
-
longSpread = Math.max(longVolSpread.toNumber(), shrunkLong);
|
|
1223
|
-
shortSpread = Math.max(shortVolSpread.toNumber(), shrunkShort);
|
|
1224
|
-
} else if (ammInventorySpreadAdjustment > 0) {
|
|
1225
|
-
const adjustment = ammInventorySpreadAdjustment;
|
|
1226
|
-
|
|
1227
|
-
const grownLong = Math.max(
|
|
1228
|
-
1,
|
|
1229
|
-
longSpread + Math.ceil((longSpread * adjustment) / 100)
|
|
1230
|
-
);
|
|
1231
|
-
const grownShort = Math.max(
|
|
1232
|
-
1,
|
|
1233
|
-
shortSpread + Math.ceil((shortSpread * adjustment) / 100)
|
|
1234
|
-
);
|
|
1235
|
-
|
|
1236
|
-
longSpread = Math.max(longVolSpread.toNumber(), grownLong);
|
|
1237
|
-
shortSpread = Math.max(shortVolSpread.toNumber(), grownShort);
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
const totalSpread = longSpread + shortSpread;
|
|
1241
|
-
if (totalSpread > maxTargetSpread) {
|
|
1242
|
-
if (longSpread > shortSpread) {
|
|
1243
|
-
longSpread = Math.ceil((longSpread * maxTargetSpread) / totalSpread);
|
|
1244
|
-
shortSpread = Math.floor(maxTargetSpread - longSpread);
|
|
1245
|
-
} else {
|
|
1246
|
-
shortSpread = Math.ceil((shortSpread * maxTargetSpread) / totalSpread);
|
|
1247
|
-
longSpread = Math.floor(maxTargetSpread - shortSpread);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
spreadTerms.totalSpread = totalSpread;
|
|
1252
|
-
spreadTerms.longSpread = longSpread;
|
|
1253
|
-
spreadTerms.shortSpread = shortSpread;
|
|
1254
|
-
if (returnTerms) {
|
|
1255
|
-
return spreadTerms;
|
|
1256
|
-
}
|
|
1257
|
-
return [longSpread, shortSpread];
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
/**
|
|
1261
|
-
* Convenience wrapper around `calculateSpreadBN` that derives its lower-level inputs
|
|
1262
|
-
* (reserve price, oracle-vs-reserve spread, live oracle std, and confidence interval) from
|
|
1263
|
-
* `amm`/`marketStats`/`oraclePriceData` directly, then applies the market's manual
|
|
1264
|
-
* `ammSpreadAdjustment` (%, shrink if negative/grow if positive, floored at 1) on top. Returns
|
|
1265
|
-
* `[baseSpread/2, baseSpread/2]` unchanged (no dynamic widening) if `baseSpread` or
|
|
1266
|
-
* `curveUpdateIntensity` is zero.
|
|
1267
|
-
* @param amm AMM state to price the spread for.
|
|
1268
|
-
* @param marketStats Market stats needed for volatility/funding-bias inputs.
|
|
1269
|
-
* @param oraclePriceData Current oracle price data; required unless `baseSpread`/`curveUpdateIntensity` are both zero.
|
|
1270
|
-
* @param now Current unix timestamp (seconds); defaults to wall-clock time if omitted.
|
|
1271
|
-
* @param reservePrice Current AMM reserve price, PRICE_PRECISION (1e6); computed from `amm`'s reserves if omitted.
|
|
1272
|
-
* @throws if `oraclePriceData` is omitted while `baseSpread` and `curveUpdateIntensity` are both nonzero.
|
|
1273
|
-
* @returns `[longSpread, shortSpread]`, both BID_ASK_SPREAD_PRECISION (1e6) fraction-of-price units.
|
|
1274
|
-
*/
|
|
1275
|
-
export function calculateSpread(
|
|
1276
|
-
amm: AMM,
|
|
1277
|
-
marketStats: MarketStats,
|
|
1278
|
-
oraclePriceData?: OraclePriceData,
|
|
1279
|
-
now?: BN,
|
|
1280
|
-
reservePrice?: BN
|
|
1281
|
-
): [number, number] {
|
|
1282
|
-
if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
|
|
1283
|
-
return [amm.baseSpread / 2, amm.baseSpread / 2];
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
if (!oraclePriceData) {
|
|
1287
|
-
throw new Error(
|
|
1288
|
-
'calculateSpread: oraclePriceData is required when baseSpread and curveUpdateIntensity are nonzero'
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
if (!reservePrice) {
|
|
1293
|
-
reservePrice = calculatePrice(
|
|
1294
|
-
amm.baseAssetReserve,
|
|
1295
|
-
amm.quoteAssetReserve,
|
|
1296
|
-
amm.pegMultiplier
|
|
1297
|
-
);
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const targetPrice = oraclePriceData?.price || reservePrice;
|
|
1301
|
-
const targetMarkSpreadPct = reservePrice
|
|
1302
|
-
.sub(targetPrice)
|
|
1303
|
-
.mul(BID_ASK_SPREAD_PRECISION)
|
|
1304
|
-
.div(reservePrice);
|
|
1305
|
-
|
|
1306
|
-
now = now || new BN(new Date().getTime() / 1000); //todo
|
|
1307
|
-
const liveOracleStd = calculateLiveOracleStd(
|
|
1308
|
-
marketStats,
|
|
1309
|
-
oraclePriceData,
|
|
1310
|
-
now
|
|
1311
|
-
);
|
|
1312
|
-
const confIntervalPct = getNewOracleConfPct(
|
|
1313
|
-
marketStats,
|
|
1314
|
-
oraclePriceData,
|
|
1315
|
-
reservePrice,
|
|
1316
|
-
now
|
|
1317
|
-
);
|
|
1318
|
-
|
|
1319
|
-
const spreads = calculateSpreadBN(
|
|
1320
|
-
amm.baseSpread,
|
|
1321
|
-
targetMarkSpreadPct,
|
|
1322
|
-
confIntervalPct,
|
|
1323
|
-
amm.maxSpread,
|
|
1324
|
-
amm.quoteAssetReserve,
|
|
1325
|
-
amm.terminalQuoteAssetReserve,
|
|
1326
|
-
amm.pegMultiplier,
|
|
1327
|
-
amm.baseAssetAmountWithAmm,
|
|
1328
|
-
reservePrice,
|
|
1329
|
-
amm.totalFeeMinusDistributions,
|
|
1330
|
-
amm.netRevenueSinceLastFunding,
|
|
1331
|
-
amm.baseAssetReserve,
|
|
1332
|
-
amm.minBaseAssetReserve,
|
|
1333
|
-
amm.maxBaseAssetReserve,
|
|
1334
|
-
marketStats.markStd,
|
|
1335
|
-
liveOracleStd,
|
|
1336
|
-
marketStats.longIntensityVolume,
|
|
1337
|
-
marketStats.shortIntensityVolume,
|
|
1338
|
-
marketStats.volume24H,
|
|
1339
|
-
amm.ammInventorySpreadAdjustment,
|
|
1340
|
-
marketStats.last24HAvgFundingRate,
|
|
1341
|
-
marketStats.lastFundingOracleTwap,
|
|
1342
|
-
amm.fundingBiasSensitivity
|
|
1343
|
-
);
|
|
1344
|
-
let longSpread = spreads[0];
|
|
1345
|
-
let shortSpread = spreads[1];
|
|
1346
|
-
|
|
1347
|
-
if (amm.ammSpreadAdjustment > 0) {
|
|
1348
|
-
longSpread = Math.max(
|
|
1349
|
-
longSpread + (longSpread * amm.ammSpreadAdjustment) / 100,
|
|
1350
|
-
1
|
|
1351
|
-
);
|
|
1352
|
-
shortSpread = Math.max(
|
|
1353
|
-
shortSpread + (shortSpread * amm.ammSpreadAdjustment) / 100,
|
|
1354
|
-
1
|
|
1355
|
-
);
|
|
1356
|
-
} else if (amm.ammSpreadAdjustment < 0) {
|
|
1357
|
-
longSpread = Math.max(
|
|
1358
|
-
longSpread - (longSpread * -amm.ammSpreadAdjustment) / 100,
|
|
1359
|
-
1
|
|
1360
|
-
);
|
|
1361
|
-
shortSpread = Math.max(
|
|
1362
|
-
shortSpread - (shortSpread * -amm.ammSpreadAdjustment) / 100,
|
|
1363
|
-
1
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
return [longSpread, shortSpread];
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
/**
|
|
1371
|
-
* Computes the AMM's one-sided bid and ask reserves — the reserves a long (ask side) or short
|
|
1372
|
-
* (bid side) trade would actually execute against — by combining `calculateSpread`'s
|
|
1373
|
-
* volatility/inventory spread with the reference-price-offset skew, mirroring
|
|
1374
|
-
* `calculate_spread_reserves` in `vlp/amm/math/amm_spread.rs`. The reference price offset
|
|
1375
|
-
* (enabled only when `curveUpdateIntensity > 100`) lets quotes drift up to `maxOffset` off the
|
|
1376
|
-
* raw reserve price when inventory skew and recent/24h funding premium agree in direction; a
|
|
1377
|
-
* configurable deadband (`referencePriceOffsetDeadbandPct`) suppresses small offsets, and when
|
|
1378
|
-
* the offset's sign flips versus the market's last stored offset, the change is smoothed in
|
|
1379
|
-
* gradually over elapsed slots (`latestSlot - amm.lastUpdateSlot`) rather than snapping
|
|
1380
|
-
* instantly, to avoid quote whiplash.
|
|
1381
|
-
* @param amm AMM state to derive spread reserves for.
|
|
1382
|
-
* @param marketStats Market stats needed for spread and reference-price-offset calculation (including `lastReferencePriceOffset` for smoothing).
|
|
1383
|
-
* @param mmOraclePriceData Current MM oracle price data, forwarded to `calculateSpread`.
|
|
1384
|
-
* @param now Current unix timestamp (seconds), forwarded to `calculateSpread`.
|
|
1385
|
-
* @param latestSlot Current slot; required for reference-price-offset smoothing to take effect (treated as 0 slots elapsed if omitted).
|
|
1386
|
-
* @returns `[bidReserves, askReserves]`, each `{ baseAssetReserve, quoteAssetReserve }` in AMM_RESERVE_PRECISION (1e9).
|
|
1387
|
-
*/
|
|
1388
|
-
export function calculateSpreadReserves(
|
|
1389
|
-
amm: AMM,
|
|
1390
|
-
marketStats: MarketStats,
|
|
1391
|
-
mmOraclePriceData?: MMOraclePriceData,
|
|
1392
|
-
now?: BN,
|
|
1393
|
-
latestSlot?: BN
|
|
1394
|
-
) {
|
|
1395
|
-
function calculateSpreadReserve(
|
|
1396
|
-
spread: number,
|
|
1397
|
-
direction: PositionDirection,
|
|
1398
|
-
amm: AMM
|
|
1399
|
-
): {
|
|
1400
|
-
baseAssetReserve: BN;
|
|
1401
|
-
quoteAssetReserve: BN;
|
|
1402
|
-
} {
|
|
1403
|
-
if (spread === 0) {
|
|
1404
|
-
return {
|
|
1405
|
-
baseAssetReserve: amm.baseAssetReserve,
|
|
1406
|
-
quoteAssetReserve: amm.quoteAssetReserve,
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1409
|
-
let spreadFraction = new BN(spread).div(new BN(2));
|
|
1410
|
-
|
|
1411
|
-
// make non-zero
|
|
1412
|
-
if (spreadFraction.eq(ZERO)) {
|
|
1413
|
-
spreadFraction = spread >= 0 ? new BN(1) : new BN(-1);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const quoteAssetReserveDelta = amm.quoteAssetReserve.div(
|
|
1417
|
-
BID_ASK_SPREAD_PRECISION.div(spreadFraction)
|
|
1418
|
-
);
|
|
1419
|
-
|
|
1420
|
-
let quoteAssetReserve;
|
|
1421
|
-
if (quoteAssetReserveDelta.gte(ZERO)) {
|
|
1422
|
-
quoteAssetReserve = amm.quoteAssetReserve.add(
|
|
1423
|
-
quoteAssetReserveDelta.abs()
|
|
1424
|
-
);
|
|
1425
|
-
} else {
|
|
1426
|
-
quoteAssetReserve = amm.quoteAssetReserve.sub(
|
|
1427
|
-
quoteAssetReserveDelta.abs()
|
|
1428
|
-
);
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve);
|
|
1432
|
-
return {
|
|
1433
|
-
baseAssetReserve,
|
|
1434
|
-
quoteAssetReserve,
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
const reservePrice = calculatePrice(
|
|
1439
|
-
amm.baseAssetReserve,
|
|
1440
|
-
amm.quoteAssetReserve,
|
|
1441
|
-
amm.pegMultiplier
|
|
1442
|
-
);
|
|
1443
|
-
|
|
1444
|
-
// always allow 10 bps of price offset, up to a half of the market's max_spread
|
|
1445
|
-
let maxOffset = 0;
|
|
1446
|
-
let referencePriceOffset = 0;
|
|
1447
|
-
if (amm.curveUpdateIntensity > 100) {
|
|
1448
|
-
if (amm.curveUpdateIntensity == 200) {
|
|
1449
|
-
maxOffset = Math.max(amm.maxSpread / 2, 10_000);
|
|
1450
|
-
} else {
|
|
1451
|
-
maxOffset = Math.min(
|
|
1452
|
-
amm.maxSpread / 2,
|
|
1453
|
-
(PERCENTAGE_PRECISION.toNumber() / 10000) *
|
|
1454
|
-
(amm.curveUpdateIntensity - 100)
|
|
1455
|
-
);
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
const liquidityFraction =
|
|
1459
|
-
calculateInventoryLiquidityRatioForReferencePriceOffset(
|
|
1460
|
-
amm.baseAssetAmountWithAmm,
|
|
1461
|
-
amm.baseAssetReserve,
|
|
1462
|
-
amm.minBaseAssetReserve,
|
|
1463
|
-
amm.maxBaseAssetReserve
|
|
1464
|
-
);
|
|
1465
|
-
const liquidityFractionSigned = liquidityFraction.mul(
|
|
1466
|
-
sigNum(amm.baseAssetAmountWithAmm)
|
|
1467
|
-
);
|
|
1468
|
-
|
|
1469
|
-
let liquidityFractionAfterDeadband = liquidityFractionSigned;
|
|
1470
|
-
const deadbandPct = amm.referencePriceOffsetDeadbandPct
|
|
1471
|
-
? PERCENTAGE_PRECISION.mul(
|
|
1472
|
-
new BN(amm.referencePriceOffsetDeadbandPct as number)
|
|
1473
|
-
).divn(100)
|
|
1474
|
-
: ZERO;
|
|
1475
|
-
if (!liquidityFractionAfterDeadband.eq(ZERO) && deadbandPct.gt(ZERO)) {
|
|
1476
|
-
const abs = liquidityFractionAfterDeadband.abs();
|
|
1477
|
-
if (abs.lte(deadbandPct)) {
|
|
1478
|
-
liquidityFractionAfterDeadband = ZERO;
|
|
1479
|
-
} else {
|
|
1480
|
-
liquidityFractionAfterDeadband = liquidityFractionAfterDeadband.sub(
|
|
1481
|
-
deadbandPct.mul(sigNum(liquidityFractionAfterDeadband))
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
referencePriceOffset = calculateReferencePriceOffset(
|
|
1487
|
-
reservePrice,
|
|
1488
|
-
marketStats.last24HAvgFundingRate,
|
|
1489
|
-
liquidityFractionAfterDeadband,
|
|
1490
|
-
marketStats.historicalOracleData.lastOraclePriceTwap5Min,
|
|
1491
|
-
marketStats.lastMarkPriceTwap5Min,
|
|
1492
|
-
marketStats.historicalOracleData.lastOraclePriceTwap,
|
|
1493
|
-
marketStats.lastMarkPriceTwap,
|
|
1494
|
-
maxOffset
|
|
1495
|
-
).toNumber();
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
let [longSpread, shortSpread] = calculateSpread(
|
|
1499
|
-
amm,
|
|
1500
|
-
marketStats,
|
|
1501
|
-
mmOraclePriceData,
|
|
1502
|
-
now,
|
|
1503
|
-
reservePrice
|
|
1504
|
-
);
|
|
1505
|
-
|
|
1506
|
-
const lastReferencePriceOffset = marketStats.lastReferencePriceOffset;
|
|
1507
|
-
const doReferencePricOffsetSmooth =
|
|
1508
|
-
Math.sign(referencePriceOffset) !== Math.sign(lastReferencePriceOffset) &&
|
|
1509
|
-
amm.curveUpdateIntensity > 100;
|
|
1510
|
-
|
|
1511
|
-
if (doReferencePricOffsetSmooth) {
|
|
1512
|
-
const slotsPassed =
|
|
1513
|
-
latestSlot != null
|
|
1514
|
-
? BN.max(latestSlot.sub(amm.lastUpdateSlot), ZERO).toNumber()
|
|
1515
|
-
: 0;
|
|
1516
|
-
const fullOffsetDelta = referencePriceOffset - lastReferencePriceOffset;
|
|
1517
|
-
const raw = Math.trunc(
|
|
1518
|
-
Math.min(Math.abs(fullOffsetDelta), slotsPassed * 1000) / 10
|
|
1519
|
-
);
|
|
1520
|
-
const maxAllowed =
|
|
1521
|
-
Math.abs(lastReferencePriceOffset) || Math.abs(referencePriceOffset);
|
|
1522
|
-
|
|
1523
|
-
const magnitude = Math.min(Math.max(raw, 10), maxAllowed);
|
|
1524
|
-
const referencePriceDelta = Math.sign(fullOffsetDelta) * magnitude;
|
|
1525
|
-
|
|
1526
|
-
referencePriceOffset = lastReferencePriceOffset + referencePriceDelta;
|
|
1527
|
-
|
|
1528
|
-
if (referencePriceDelta < 0) {
|
|
1529
|
-
longSpread += Math.abs(referencePriceDelta);
|
|
1530
|
-
shortSpread += Math.abs(referencePriceOffset);
|
|
1531
|
-
} else {
|
|
1532
|
-
shortSpread += Math.abs(referencePriceDelta);
|
|
1533
|
-
longSpread += Math.abs(referencePriceOffset);
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
const askReserves = calculateSpreadReserve(
|
|
1538
|
-
longSpread + referencePriceOffset,
|
|
1539
|
-
PositionDirection.LONG,
|
|
1540
|
-
amm
|
|
1541
|
-
);
|
|
1542
|
-
const bidReserves = calculateSpreadReserve(
|
|
1543
|
-
-shortSpread + referencePriceOffset,
|
|
1544
|
-
PositionDirection.SHORT,
|
|
1545
|
-
amm
|
|
1546
|
-
);
|
|
1547
|
-
|
|
1548
|
-
return [bidReserves, askReserves];
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
/**
|
|
1552
|
-
* Applies the constant-product invariant (`invariant = k^2 = inputReserve * outputReserve`) to
|
|
1553
|
-
* a single reserve swap; agnostic to whether the input side is quote or base. Both reserve
|
|
1554
|
-
* arguments and the result must share the same precision as `invariant`'s square root
|
|
1555
|
-
* (typically `AMM_RESERVE_PRECISION`, 1e9).
|
|
1556
|
-
* @param inputAssetReserve Current reserve on the input side, same precision as `invariant`'s square root.
|
|
1557
|
-
* @param swapAmount Amount being added to or removed from `inputAssetReserve`.
|
|
1558
|
-
* @param swapDirection Whether `swapAmount` is added to or removed from the input reserve.
|
|
1559
|
-
* @param invariant Constant-product invariant (`sqrtK^2`), same precision as `inputAssetReserve` squared.
|
|
1560
|
-
* @returns `[newInputAssetReserve, newOutputAssetReserve]`, both same precision as `inputAssetReserve`.
|
|
1561
|
-
*/
|
|
1562
|
-
export function calculateSwapOutput(
|
|
1563
|
-
inputAssetReserve: BN,
|
|
1564
|
-
swapAmount: BN,
|
|
1565
|
-
swapDirection: SwapDirection,
|
|
1566
|
-
invariant: BN
|
|
1567
|
-
): [BN, BN] {
|
|
1568
|
-
let newInputAssetReserve;
|
|
1569
|
-
if (swapDirection === SwapDirection.ADD) {
|
|
1570
|
-
newInputAssetReserve = inputAssetReserve.add(swapAmount);
|
|
1571
|
-
} else {
|
|
1572
|
-
newInputAssetReserve = inputAssetReserve.sub(swapAmount);
|
|
1573
|
-
}
|
|
1574
|
-
const newOutputAssetReserve = invariant.div(newInputAssetReserve);
|
|
1575
|
-
return [newInputAssetReserve, newOutputAssetReserve];
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
/**
|
|
1579
|
-
* Maps a desired position direction and the asset side being specified into the AMM
|
|
1580
|
-
* reserve-swap direction (`ADD`/`REMOVE`) needed to execute it: opening a long by specifying
|
|
1581
|
-
* base, or a short by specifying quote, removes that reserve from the AMM; every other
|
|
1582
|
-
* combination adds to it.
|
|
1583
|
-
* @param inputAssetType Which side (`'quote'` or `'base'`) the trade amount is denominated in.
|
|
1584
|
-
* @param positionDirection Direction of the position being opened/closed.
|
|
1585
|
-
* @returns `SwapDirection.ADD` or `SwapDirection.REMOVE` for `calculateAmmReservesAfterSwap`/`calculateSwapOutput`.
|
|
1586
|
-
*/
|
|
1587
|
-
export function getSwapDirection(
|
|
1588
|
-
inputAssetType: AssetType,
|
|
1589
|
-
positionDirection: PositionDirection
|
|
1590
|
-
): SwapDirection {
|
|
1591
|
-
if (isVariant(positionDirection, 'long') && inputAssetType === 'base') {
|
|
1592
|
-
return SwapDirection.REMOVE;
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
if (isVariant(positionDirection, 'short') && inputAssetType === 'quote') {
|
|
1596
|
-
return SwapDirection.REMOVE;
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
return SwapDirection.ADD;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
/**
|
|
1603
|
-
* Computes the AMM's "terminal price" — the constant-product price that would result if the
|
|
1604
|
-
* AMM's entire net inventory (`baseAssetAmountWithAmm`) were closed out against itself in one
|
|
1605
|
-
* swap. Used as a floor/ceiling reference distinct from the current spot reserve price, since
|
|
1606
|
-
* it reflects where the curve would settle once open interest unwinds.
|
|
1607
|
-
* @param market Perp market whose AMM to compute the terminal price for.
|
|
1608
|
-
* @returns Terminal price, PRICE_PRECISION (1e6).
|
|
1609
|
-
*/
|
|
1610
|
-
export function calculateTerminalPrice(market: PerpMarketAccount) {
|
|
1611
|
-
const directionToClose = market.amm.baseAssetAmountWithAmm.gt(ZERO)
|
|
1612
|
-
? PositionDirection.SHORT
|
|
1613
|
-
: PositionDirection.LONG;
|
|
1614
|
-
|
|
1615
|
-
const [newQuoteAssetReserve, newBaseAssetReserve] =
|
|
1616
|
-
calculateAmmReservesAfterSwap(
|
|
1617
|
-
market.amm,
|
|
1618
|
-
'base',
|
|
1619
|
-
market.amm.baseAssetAmountWithAmm.abs(),
|
|
1620
|
-
getSwapDirection('base', directionToClose)
|
|
1621
|
-
);
|
|
1622
|
-
|
|
1623
|
-
const terminalPrice = newQuoteAssetReserve
|
|
1624
|
-
.mul(PRICE_PRECISION)
|
|
1625
|
-
.mul(market.amm.pegMultiplier)
|
|
1626
|
-
.div(PEG_PRECISION)
|
|
1627
|
-
.div(newBaseAssetReserve);
|
|
1628
|
-
|
|
1629
|
-
return terminalPrice;
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
/**
|
|
1633
|
-
* Solves for how much base asset the AMM could absorb, trading against its `direction`-side
|
|
1634
|
-
* spread reserves (`calculateSpreadReserves`), before its constant-product price would cross
|
|
1635
|
-
* `limit_price` — i.e. the AMM-side fill size available up to a resting limit order's price.
|
|
1636
|
-
* The returned direction is the side the AMM would be trading (opposite what a taker matching
|
|
1637
|
-
* against it would take): `SHORT` if the AMM's reserves must shrink to reach `limit_price`
|
|
1638
|
-
* (limit price above current), `LONG` if they must grow (limit price below current).
|
|
1639
|
-
* @param amm AMM state to solve against.
|
|
1640
|
-
* @param marketStats Market stats needed to derive spread reserves.
|
|
1641
|
-
* @param limit_price Limit price the AMM may trade up to, PRICE_PRECISION (1e6).
|
|
1642
|
-
* @param direction Which side's spread reserves to start from (see `calculateSpreadReserves`).
|
|
1643
|
-
* @param mmOraclePriceData Current MM oracle price data, forwarded to `calculateSpreadReserves`.
|
|
1644
|
-
* @param now Current unix timestamp (seconds), forwarded to `calculateSpreadReserves`.
|
|
1645
|
-
* @returns `[baseAssetAmount, direction]`: `baseAssetAmount` is AMM_RESERVE_PRECISION (1e9), zero if the trade size would round to nothing.
|
|
1646
|
-
*/
|
|
1647
|
-
export function calculateMaxBaseAssetAmountToTrade(
|
|
1648
|
-
amm: AMM,
|
|
1649
|
-
marketStats: MarketStats,
|
|
1650
|
-
limit_price: BN,
|
|
1651
|
-
direction: PositionDirection,
|
|
1652
|
-
mmOraclePriceData: MMOraclePriceData,
|
|
1653
|
-
now?: BN
|
|
1654
|
-
): [BN, PositionDirection] {
|
|
1655
|
-
const invariant = amm.sqrtK.mul(amm.sqrtK);
|
|
1656
|
-
|
|
1657
|
-
const newBaseAssetReserveSquared = invariant
|
|
1658
|
-
.mul(PRICE_PRECISION)
|
|
1659
|
-
.mul(amm.pegMultiplier)
|
|
1660
|
-
.div(limit_price)
|
|
1661
|
-
.div(PEG_PRECISION);
|
|
1662
|
-
|
|
1663
|
-
const newBaseAssetReserve = squareRootBN(newBaseAssetReserveSquared);
|
|
1664
|
-
const [shortSpreadReserves, longSpreadReserves] = calculateSpreadReserves(
|
|
1665
|
-
amm,
|
|
1666
|
-
marketStats,
|
|
1667
|
-
mmOraclePriceData,
|
|
1668
|
-
now
|
|
1669
|
-
);
|
|
1670
|
-
|
|
1671
|
-
const baseAssetReserveBefore: BN = isVariant(direction, 'long')
|
|
1672
|
-
? longSpreadReserves.baseAssetReserve
|
|
1673
|
-
: shortSpreadReserves.baseAssetReserve;
|
|
1674
|
-
|
|
1675
|
-
if (newBaseAssetReserve.gt(baseAssetReserveBefore)) {
|
|
1676
|
-
return [
|
|
1677
|
-
newBaseAssetReserve.sub(baseAssetReserveBefore),
|
|
1678
|
-
PositionDirection.SHORT,
|
|
1679
|
-
];
|
|
1680
|
-
} else if (newBaseAssetReserve.lt(baseAssetReserveBefore)) {
|
|
1681
|
-
return [
|
|
1682
|
-
baseAssetReserveBefore.sub(newBaseAssetReserve),
|
|
1683
|
-
PositionDirection.LONG,
|
|
1684
|
-
];
|
|
1685
|
-
} else {
|
|
1686
|
-
console.log('tradeSize Too Small');
|
|
1687
|
-
return [new BN(0), PositionDirection.LONG];
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
/**
|
|
1692
|
-
* Converts a quote-asset AMM reserve amount into the actual quote asset amount swapped
|
|
1693
|
-
* (applying the peg multiplier), rounding by 1 in the direction that favors the AMM when
|
|
1694
|
-
* `swapDirection` is `remove` (quote leaving the AMM), mirroring the on-chain rounding used to
|
|
1695
|
-
* avoid ever giving out a fraction of a unit more than intended.
|
|
1696
|
-
* @param quoteAssetReserves Quote reserve delta from a swap, AMM_RESERVE_PRECISION (1e9).
|
|
1697
|
-
* @param pegMultiplier AMM peg multiplier, PEG_PRECISION (1e6).
|
|
1698
|
-
* @param swapDirection Whether quote is being added to or removed from the AMM.
|
|
1699
|
-
* @returns Quote asset amount actually swapped, QUOTE_PRECISION (1e6).
|
|
1700
|
-
*/
|
|
1701
|
-
export function calculateQuoteAssetAmountSwapped(
|
|
1702
|
-
quoteAssetReserves: BN,
|
|
1703
|
-
pegMultiplier: BN,
|
|
1704
|
-
swapDirection: SwapDirection
|
|
1705
|
-
): BN {
|
|
1706
|
-
if (isVariant(swapDirection, 'remove')) {
|
|
1707
|
-
quoteAssetReserves = quoteAssetReserves.add(ONE);
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
let quoteAssetAmount = quoteAssetReserves
|
|
1711
|
-
.mul(pegMultiplier)
|
|
1712
|
-
.div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
|
|
1713
|
-
|
|
1714
|
-
if (isVariant(swapDirection, 'remove')) {
|
|
1715
|
-
quoteAssetAmount = quoteAssetAmount.add(ONE);
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
return quoteAssetAmount;
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
/**
|
|
1722
|
-
* Caps how much base asset the AMM is willing to fill in one instruction: the smaller of
|
|
1723
|
-
* `amm.maxFillReserveFraction`'s share of the current base reserve and the room remaining to
|
|
1724
|
-
* the AMM's min/max reserve bound on the taker's side, then rounded down to `orderStepSize`.
|
|
1725
|
-
* This is a per-fill risk limit distinct from `calculateMaxBaseAssetAmountToTrade` (which sizes
|
|
1726
|
-
* against a limit price) — it bounds how much of the AMM's own liquidity can move at once
|
|
1727
|
-
* regardless of price.
|
|
1728
|
-
* @param amm AMM state (`baseAssetReserve`, `minBaseAssetReserve`, `maxBaseAssetReserve`, `maxFillReserveFraction`).
|
|
1729
|
-
* @param orderStepSize Order step size to standardize the result to, BASE_PRECISION (1e9).
|
|
1730
|
-
* @param orderDirection Direction of the order being filled against the AMM.
|
|
1731
|
-
* @returns Max fillable base asset amount, BASE_PRECISION (1e9), standardized to `orderStepSize`.
|
|
1732
|
-
*/
|
|
1733
|
-
export function calculateMaxBaseAssetAmountFillable(
|
|
1734
|
-
amm: AMM,
|
|
1735
|
-
orderStepSize: BN,
|
|
1736
|
-
orderDirection: PositionDirection
|
|
1737
|
-
): BN {
|
|
1738
|
-
const maxFillSize = amm.baseAssetReserve.div(
|
|
1739
|
-
new BN(amm.maxFillReserveFraction)
|
|
1740
|
-
);
|
|
1741
|
-
let maxBaseAssetAmountOnSide: BN;
|
|
1742
|
-
if (isVariant(orderDirection, 'long')) {
|
|
1743
|
-
maxBaseAssetAmountOnSide = BN.max(
|
|
1744
|
-
ZERO,
|
|
1745
|
-
amm.baseAssetReserve.sub(amm.minBaseAssetReserve)
|
|
1746
|
-
);
|
|
1747
|
-
} else {
|
|
1748
|
-
maxBaseAssetAmountOnSide = BN.max(
|
|
1749
|
-
ZERO,
|
|
1750
|
-
amm.maxBaseAssetReserve.sub(amm.baseAssetReserve)
|
|
1751
|
-
);
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
return standardizeBaseAssetAmount(
|
|
1755
|
-
BN.min(maxFillSize, maxBaseAssetAmountOnSide),
|
|
1756
|
-
orderStepSize
|
|
1757
|
-
);
|
|
1758
|
-
}
|