@pear-protocol/exchanges-sdk
Advanced tools
+0
-8
@@ -45,10 +45,2 @@ import { configureProxy } from '@pear-protocol/utils'; | ||
| } | ||
| /** | ||
| * Sizes a basket in one shot. Routed to the tracker's REST path on purpose: this entry point | ||
| * builds a throwaway tracker per call, so it must open no socket. | ||
| */ | ||
| async maxBasketSize(tradeAccountId, input) { | ||
| const connection = await this.fetchCredentials(tradeAccountId); | ||
| return this.buildTracker(connection).snapshotMaxBasketSize(input); | ||
| } | ||
| async snapshotAssets(tradeAccountId, coins) { | ||
@@ -55,0 +47,0 @@ const connection = await this.fetchCredentials(tradeAccountId); |
@@ -41,15 +41,2 @@ import { createExchangeWs } from '../../client'; | ||
| /** | ||
| * Binance sizes no basket in v1: the SDK reads no per-asset max leverage from it, and sizing off | ||
| * `availableMargin` alone would ship a number no venue field backs. The missing input is | ||
| * `/fapi/v2/positionRisk` → `maxNotionalValue` (the account's own per-symbol ceiling), with | ||
| * `/fapi/v1/leverageBracket` behind it for the tier a larger size would fall into. | ||
| */ | ||
| async maxBasketSize(_input) { | ||
| return unsizedBasket(); | ||
| } | ||
| /** The REST path answers the same way — there is no size either way, not a slower one. */ | ||
| async snapshotMaxBasketSize(_input) { | ||
| return unsizedBasket(); | ||
| } | ||
| /** | ||
| * Stateless one-shot per-asset settings read: `positionRisk` with no symbol returns leverage + | ||
@@ -258,11 +245,3 @@ * margin type for every symbol (positioned or not), so any number of assets costs one call. | ||
| __name(toTrackedAssetInfo, "toTrackedAssetInfo"); | ||
| function unsizedBasket() { | ||
| return { | ||
| maxNotional: null, | ||
| legs: [], | ||
| bindingConstraint: null | ||
| }; | ||
| } | ||
| __name(unsizedBasket, "unsizedBasket"); | ||
| export { BinanceAccountTracker }; |
@@ -40,15 +40,2 @@ import { createExchangeWs } from '../../client'; | ||
| /** | ||
| * Bybit sizes no basket in v1: the SDK reads no per-asset max leverage from it, and sizing off | ||
| * `availableMargin` alone would ship a number no venue field backs. The missing input is | ||
| * `/v5/position/list` → `leverage` (the account's own per-symbol setting), with | ||
| * `/v5/market/risk-limit` → `maxLeverage` behind it for the tier a larger size would fall into. | ||
| */ | ||
| async maxBasketSize(_input) { | ||
| return unsizedBasket(); | ||
| } | ||
| /** The REST path answers the same way — there is no size either way, not a slower one. */ | ||
| async snapshotMaxBasketSize(_input) { | ||
| return unsizedBasket(); | ||
| } | ||
| /** | ||
| * Stateless one-shot per-asset settings read. Bybit has no batch leverage endpoint, but | ||
@@ -430,10 +417,2 @@ * `position/list` by settleCoin returns every open position (with leverage) in one call, so | ||
| __name(sameWithdrawable, "sameWithdrawable"); | ||
| function unsizedBasket() { | ||
| return { | ||
| maxNotional: null, | ||
| legs: [], | ||
| bindingConstraint: null | ||
| }; | ||
| } | ||
| __name(unsizedBasket, "unsizedBasket"); | ||
| function toTrackedAssetInfo(coin, pos) { | ||
@@ -440,0 +419,0 @@ return { |
@@ -1,3 +0,3 @@ | ||
| import { minimumAvailableToTrade } from './basket-size'; | ||
| import { splitDexCoin } from './coin-name'; | ||
| import { toDecimal } from './decimal'; | ||
@@ -47,3 +47,16 @@ var __defProp = Object.defineProperty; | ||
| __name(collateralTokenByDex, "collateralTokenByDex"); | ||
| function minimumAvailableToTrade(assetData) { | ||
| let floor = null; | ||
| for (const data of assetData) { | ||
| for (const available of data.availableToTrade ?? []) { | ||
| const value = toDecimal(available); | ||
| if (floor === null || value.isLessThan(floor)) { | ||
| floor = value; | ||
| } | ||
| } | ||
| } | ||
| return floor === null ? null : floor.toFixed(); | ||
| } | ||
| __name(minimumAvailableToTrade, "minimumAvailableToTrade"); | ||
| export { buildAvailableMarginByToken, buildCollateralTokenNames }; | ||
| export { buildAvailableMarginByToken, buildCollateralTokenNames, minimumAvailableToTrade }; |
| import { createExchangeWs } from '../../client'; | ||
| import { fetchPerpDexs, fetchInfo, fetchAllMids, fetchUserAbstraction, fetchSpotClearinghouseState, fetchDelegatorSummary, fetchUserVaultEquities, fetchClearinghouseState, fetchActiveAssetData } from '../../client/hyperliquid/rest'; | ||
| import { BaseAccountTracker } from '../base'; | ||
| import { computeMaxBasketSize } from '../basket-size'; | ||
| import { resolveHyperliquidAccountType } from './account-mode'; | ||
| import { minimumAvailableToTrade, findLegMarket, buildPoolBudgets, buildCappedLegs } from './basket-size'; | ||
| import { buildAvailableMarginByToken, buildCollateralTokenNames } from './collateral'; | ||
| import { buildAvailableMarginByToken, buildCollateralTokenNames, minimumAvailableToTrade } from './collateral'; | ||
| import { LOCKED_TRACKED_COINS } from './const'; | ||
@@ -99,32 +97,3 @@ import { toDecimal, ZERO } from './decimal'; | ||
| } | ||
| /** Sizes a basket over REST alone, so the call stays usable without a running socket. */ | ||
| async snapshotMaxBasketSize(input) { | ||
| const [dexToMeta, tokenNames] = await Promise.all([ | ||
| this.loadPerpMetasOnce(), | ||
| this.loadCollateralTokenNamesOnce() | ||
| ]); | ||
| const assetData = await this.fetchActiveAssetEvents(input.legs.map((leg) => leg.asset)); | ||
| return sizeBasket(input, dexToMeta, tokenNames, assetData); | ||
| } | ||
| /** | ||
| * Sizes a basket against live state, fetching per-asset data for any leg the socket has not | ||
| * subscribed to — which is exactly why the call is asynchronous on both paths. | ||
| */ | ||
| async maxBasketSize(input) { | ||
| const [dexToMeta, tokenNames] = await Promise.all([ | ||
| this.loadPerpMetasOnce(), | ||
| this.loadCollateralTokenNamesOnce() | ||
| ]); | ||
| const missingCoins = input.legs.map((leg) => leg.asset).filter((asset) => !this.trackedAssetData.has(asset)); | ||
| const fetched = await this.fetchActiveAssetEvents(missingCoins); | ||
| const assetData = new Map(fetched); | ||
| for (const leg of input.legs) { | ||
| const cached = this.trackedAssetData.get(leg.asset); | ||
| if (cached) { | ||
| assetData.set(leg.asset, cached); | ||
| } | ||
| } | ||
| return sizeBasket(input, dexToMeta, tokenNames, assetData); | ||
| } | ||
| /** | ||
| * Stateless one-shot per-asset settings read via the `activeAssetData` info request (the REST | ||
@@ -367,27 +336,2 @@ * twin of the live WS subscription), one request per coin fired in parallel — the info endpoint | ||
| } | ||
| /** Loads the dex metadata a sizing call needs, reusing whatever `start()` already cached. */ | ||
| async loadPerpMetasOnce() { | ||
| if (this.dexToMeta.size > 0) { | ||
| return this.dexToMeta; | ||
| } | ||
| const [allMetas, perpDexs] = await Promise.all([ | ||
| fetchInfo({ | ||
| type: "allPerpMetas" | ||
| }, this.demo), | ||
| fetchPerpDexs(this.demo) | ||
| ]); | ||
| this.loadPerpMetas(allMetas, perpDexs); | ||
| return this.dexToMeta; | ||
| } | ||
| /** Loads the token names a pool id is spelled with, reusing whatever `start()` already cached. */ | ||
| async loadCollateralTokenNamesOnce() { | ||
| if (this.collateralTokenNames.size > 0) { | ||
| return this.collateralTokenNames; | ||
| } | ||
| const spotMeta = await fetchInfo({ | ||
| type: "spotMeta" | ||
| }, this.demo); | ||
| this.collateralTokenNames = buildCollateralTokenNames(spotMeta); | ||
| return this.collateralTokenNames; | ||
| } | ||
| /** Reads `activeAssetData` for each coin in parallel, dropping the coins the venue refuses. */ | ||
@@ -563,14 +507,2 @@ async fetchActiveAssetEvents(coins) { | ||
| } | ||
| function sizeBasket(input, dexToMeta, tokenNames, assetData) { | ||
| const markets = new Map(input.legs.map((leg) => [ | ||
| leg.asset, | ||
| findLegMarket(dexToMeta, tokenNames, leg.asset, assetData.get(leg.asset)?.leverage.value ?? null) | ||
| ])); | ||
| const budgets = buildPoolBudgets(input.legs, markets, assetData); | ||
| return computeMaxBasketSize({ | ||
| budgets, | ||
| legs: buildCappedLegs(input.legs, markets) | ||
| }); | ||
| } | ||
| __name(sizeBasket, "sizeBasket"); | ||
| function buildTrackedAssetInfo(data) { | ||
@@ -577,0 +509,0 @@ return { |
@@ -14,3 +14,2 @@ import { BinanceAccountTracker } from './binance/tracker'; | ||
| export { BaseAccountTracker } from './base'; | ||
| export { BasketLeg, MaxBasketSizeInput, MaxBasketSizeResult, computeMaxBasketSize } from './basket-size'; | ||
| export { BinanceMultiAssetBalance, BinanceSingleAssetBalance } from './binance/balance'; | ||
@@ -17,0 +16,0 @@ export { BybitIsolatedMarginBalance, BybitPortfolioMarginBalance, BybitRegularMarginBalance } from './bybit/balance'; |
@@ -6,4 +6,2 @@ import BigNumber from 'bignumber.js'; | ||
| import { BaseAccountTracker } from '../base'; | ||
| import { computeMaxBasketSize } from '../basket-size'; | ||
| import { toLighterBasketCalculation } from './basket-size'; | ||
| import { buildSpotPriceIndex, mapPositions, mapRestBalance, mapMarketDefaultAssetInfo, toLighterAccountType, mapLiveBalance, toLighterAccountTier } from './mapper'; | ||
@@ -145,32 +143,2 @@ | ||
| } | ||
| /** Sizes a basket against the live account state, falling back to REST before the socket is up. */ | ||
| async maxBasketSize(input) { | ||
| if (!this.stats || this.marketsBySymbol.size === 0) { | ||
| return this.snapshotMaxBasketSize(input); | ||
| } | ||
| return computeMaxBasketSize(toLighterBasketCalculation(input.legs, { | ||
| availableBalance: this.stats.total.available_balance, | ||
| perpBySymbol: this.marketsBySymbol, | ||
| settingsByMarketId: this.liveAccountSettings() | ||
| })); | ||
| } | ||
| /** Sizes a basket over REST alone, so the answer needs no running socket. */ | ||
| async snapshotMaxBasketSize(input) { | ||
| const [account, markets] = await Promise.all([ | ||
| this.fetchPrimaryAccount(), | ||
| this.fetchMarkets() | ||
| ]); | ||
| if (!account) { | ||
| return computeMaxBasketSize({ | ||
| budgets: {}, | ||
| legs: [] | ||
| }); | ||
| } | ||
| const context = { | ||
| availableBalance: account.available_balance, | ||
| perpBySymbol: toMarketsBySymbol(markets.perp), | ||
| settingsByMarketId: toAccountSettings(markets.perp, account.positions ?? []) | ||
| }; | ||
| return computeMaxBasketSize(toLighterBasketCalculation(input.legs, context)); | ||
| } | ||
| start() { | ||
@@ -419,13 +387,2 @@ if (this.startInFlight) { | ||
| } | ||
| /** The account's current per-market settings as the base tracker already holds them. */ | ||
| liveAccountSettings() { | ||
| const settings = /* @__PURE__ */ new Map(); | ||
| for (const marketId of this.marketsByMarketId.keys()) { | ||
| const info = this.getTrackedAsset(String(marketId)); | ||
| if (info) { | ||
| settings.set(String(marketId), info); | ||
| } | ||
| } | ||
| return settings; | ||
| } | ||
| /** Reads the account with credentials when present, or explicitly uses the read-only verifier path. */ | ||
@@ -610,9 +567,2 @@ async fetchPrimaryAccount() { | ||
| __name(toMarketContext, "toMarketContext"); | ||
| function toMarketsBySymbol(perpMarkets) { | ||
| return new Map(perpMarkets.map((market) => [ | ||
| market.symbol, | ||
| market | ||
| ])); | ||
| } | ||
| __name(toMarketsBySymbol, "toMarketsBySymbol"); | ||
| function toAccountSettings(perpMarkets, positions) { | ||
@@ -619,0 +569,0 @@ const settings = /* @__PURE__ */ new Map(); |
@@ -39,16 +39,2 @@ import { createExchangeWs } from '../../client'; | ||
| /** | ||
| * OKX sizes no basket in v1: the SDK reads no per-asset max leverage from it, and sizing off | ||
| * `availableMargin` alone would ship a number no venue field backs. The missing input is | ||
| * `GET /api/v5/public/position-tiers` → `maxLever`/`imr` per tier, which the client does not | ||
| * call — and it is NOT `GET /api/v5/account/position-tiers`, a different endpoint carrying no | ||
| * margin or leverage data at all. | ||
| */ | ||
| async maxBasketSize(_input) { | ||
| return unsizedBasket(); | ||
| } | ||
| /** The REST path answers the same way — there is no size either way, not a slower one. */ | ||
| async snapshotMaxBasketSize(_input) { | ||
| return unsizedBasket(); | ||
| } | ||
| /** | ||
| * Stateless one-shot per-asset leverage read via `leverage-info`, which accepts up to 20 instIds | ||
@@ -410,11 +396,3 @@ * per request (chunked in the client), so N assets cost ceil(N/20) calls. Cross margin only, | ||
| __name(sameWithdrawable, "sameWithdrawable"); | ||
| function unsizedBasket() { | ||
| return { | ||
| maxNotional: null, | ||
| legs: [], | ||
| bindingConstraint: null | ||
| }; | ||
| } | ||
| __name(unsizedBasket, "unsizedBasket"); | ||
| export { OkxAccountTracker }; |
+3
-3
| { | ||
| "name": "@pear-protocol/exchanges-sdk", | ||
| "version": "0.11.0", | ||
| "version": "0.12.0", | ||
| "description": "Pear Protocol Exchanges SDK", | ||
@@ -30,5 +30,5 @@ "private": false, | ||
| "dependencies": { | ||
| "@pear-protocol/core-sdk": "^1.19.0", | ||
| "@pear-protocol/core-sdk": "^1.19.1", | ||
| "@pear-protocol/types": "^1.30.1", | ||
| "@pear-protocol/utils": "^0.5.3", | ||
| "@pear-protocol/utils": "^0.6.0", | ||
| "bignumber.js": "^11.1.1", | ||
@@ -35,0 +35,0 @@ "partysocket": "^1.0.3", |
| import BigNumber from 'bignumber.js'; | ||
| import { z } from 'zod'; | ||
| import { Side, DecimalString, MarginMode } from './balance-core'; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| const BasketLeg = z.object({ | ||
| asset: z.string(), | ||
| marginMode: MarginMode, | ||
| leverage: DecimalString, | ||
| side: Side, | ||
| /** Unsigned percentage. The set sums to 100. */ | ||
| weight: z.number().positive() | ||
| }); | ||
| const MaxBasketSizeInput = z.object({ | ||
| legs: z.array(BasketLeg).min(1) | ||
| }); | ||
| const MaxBasketSizeResult = z.object({ | ||
| /** | ||
| * The whole basket's max GROSS notional in USD — Σ leg notional, not per-side and not per-asset. | ||
| * The basket is atomic, so this is one ceiling for the entire structure. | ||
| * Null when the venue cannot supply a per-asset max leverage (all three CEXs in v1), and null | ||
| * when any leg draws on a pool the caller could not value. | ||
| */ | ||
| maxNotional: DecimalString.nullable(), | ||
| /** Derived from maxNotional — each leg's notional at that basket size. NOT a per-leg maximum. */ | ||
| legs: z.array(z.object({ | ||
| asset: z.string(), | ||
| notional: DecimalString, | ||
| marginRequired: DecimalString | ||
| })), | ||
| /** Which pool or leg ran out first. */ | ||
| bindingConstraint: z.discriminatedUnion("kind", [ | ||
| z.object({ | ||
| kind: z.literal("cross-pool"), | ||
| pool: z.string() | ||
| }), | ||
| z.object({ | ||
| kind: z.literal("isolated-leg"), | ||
| asset: z.string() | ||
| }) | ||
| ]).nullable() | ||
| }); | ||
| function computeMaxBasketSize(input) { | ||
| if (!isSizeableInput(input)) { | ||
| return unsizedResult(); | ||
| } | ||
| const budgets = new Map(Object.entries(input.budgets)); | ||
| const weightSum = sumWeights(input.legs); | ||
| if (weightSum.isZero()) { | ||
| return unsizedResult(); | ||
| } | ||
| const shares = input.legs.map((leg) => toLegShare(leg, weightSum)); | ||
| const demandByPool = marginPerUnitNotionalByPool(shares); | ||
| const tightestPool = tightestPoolCeiling(demandByPool, budgets); | ||
| if (tightestPool === null) { | ||
| return unsizedResult(); | ||
| } | ||
| const tightestCap = tightestLegCap(shares); | ||
| if (tightestCap === null || tightestPool.size.isLessThanOrEqualTo(tightestCap.size)) { | ||
| return buildResult(tightestPool.size, shares, { | ||
| kind: "cross-pool", | ||
| pool: tightestPool.pool | ||
| }); | ||
| } | ||
| return buildResult(tightestCap.size, shares, { | ||
| kind: "isolated-leg", | ||
| asset: tightestCap.asset | ||
| }); | ||
| } | ||
| __name(computeMaxBasketSize, "computeMaxBasketSize"); | ||
| function isSizeableInput(input) { | ||
| for (const budget of Object.values(input.budgets)) { | ||
| if (!isUnsignedDecimal(budget)) { | ||
| return false; | ||
| } | ||
| } | ||
| for (const leg of input.legs) { | ||
| if (!isPositiveFinite(leg.weight)) { | ||
| return false; | ||
| } | ||
| if (!isUsableLeverage(leg.effectiveLeverage)) { | ||
| return false; | ||
| } | ||
| if (leg.maxNotional !== null && !isUnsignedDecimal(leg.maxNotional)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| __name(isSizeableInput, "isSizeableInput"); | ||
| function isUnsignedDecimal(value) { | ||
| return DecimalString.safeParse(value).success; | ||
| } | ||
| __name(isUnsignedDecimal, "isUnsignedDecimal"); | ||
| function isPositiveFinite(weight) { | ||
| return Number.isFinite(weight) && weight > 0; | ||
| } | ||
| __name(isPositiveFinite, "isPositiveFinite"); | ||
| function isUsableLeverage(effectiveLeverage) { | ||
| if (!isUnsignedDecimal(effectiveLeverage)) { | ||
| return false; | ||
| } | ||
| return !new BigNumber(effectiveLeverage).isZero(); | ||
| } | ||
| __name(isUsableLeverage, "isUsableLeverage"); | ||
| function sumWeights(legs) { | ||
| let total = new BigNumber(0); | ||
| for (const leg of legs) { | ||
| total = total.plus(String(leg.weight)); | ||
| } | ||
| return total; | ||
| } | ||
| __name(sumWeights, "sumWeights"); | ||
| function unsizedResult() { | ||
| return { | ||
| maxNotional: null, | ||
| legs: [], | ||
| bindingConstraint: null | ||
| }; | ||
| } | ||
| __name(unsizedResult, "unsizedResult"); | ||
| function toLegShare(leg, weightSum) { | ||
| return { | ||
| asset: leg.asset, | ||
| pool: leg.pool, | ||
| share: new BigNumber(String(leg.weight)).div(weightSum), | ||
| imr: new BigNumber(1).div(leg.effectiveLeverage), | ||
| maxNotional: leg.maxNotional === null ? null : new BigNumber(leg.maxNotional) | ||
| }; | ||
| } | ||
| __name(toLegShare, "toLegShare"); | ||
| function marginPerUnitNotionalByPool(shares) { | ||
| const demandByPool = /* @__PURE__ */ new Map(); | ||
| for (const leg of shares) { | ||
| const running = demandByPool.get(leg.pool) ?? new BigNumber(0); | ||
| demandByPool.set(leg.pool, running.plus(leg.share.times(leg.imr))); | ||
| } | ||
| return demandByPool; | ||
| } | ||
| __name(marginPerUnitNotionalByPool, "marginPerUnitNotionalByPool"); | ||
| function tightestPoolCeiling(demandByPool, budgets) { | ||
| let tightest = null; | ||
| for (const [pool, marginPerUnitNotional] of demandByPool) { | ||
| const budget = budgets.get(pool); | ||
| if (budget === void 0) { | ||
| return null; | ||
| } | ||
| const size = new BigNumber(budget).div(marginPerUnitNotional); | ||
| if (tightest === null || size.isLessThan(tightest.size)) { | ||
| tightest = { | ||
| pool, | ||
| size | ||
| }; | ||
| } | ||
| } | ||
| return tightest; | ||
| } | ||
| __name(tightestPoolCeiling, "tightestPoolCeiling"); | ||
| function tightestLegCap(shares) { | ||
| let tightest = null; | ||
| for (const leg of shares) { | ||
| if (leg.maxNotional === null) { | ||
| continue; | ||
| } | ||
| const size = leg.maxNotional.div(leg.share); | ||
| if (tightest === null || size.isLessThan(tightest.size)) { | ||
| tightest = { | ||
| asset: leg.asset, | ||
| size | ||
| }; | ||
| } | ||
| } | ||
| return tightest; | ||
| } | ||
| __name(tightestLegCap, "tightestLegCap"); | ||
| function buildResult(size, shares, bindingConstraint) { | ||
| const legs = shares.map((leg) => { | ||
| const notional = size.times(leg.share); | ||
| return { | ||
| asset: leg.asset, | ||
| notional: notional.toFixed(), | ||
| marginRequired: notional.times(leg.imr).toFixed() | ||
| }; | ||
| }); | ||
| return { | ||
| maxNotional: size.toFixed(), | ||
| legs, | ||
| bindingConstraint | ||
| }; | ||
| } | ||
| __name(buildResult, "buildResult"); | ||
| export { BasketLeg, MaxBasketSizeInput, MaxBasketSizeResult, computeMaxBasketSize }; |
| import BigNumber from 'bignumber.js'; | ||
| import { splitDexCoin } from './coin-name'; | ||
| import { toDecimal } from './decimal'; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| const UNRESOLVED_POOL = ""; | ||
| function buildCappedLegs(legs, markets) { | ||
| return legs.map((leg) => { | ||
| const market = markets.get(leg.asset) ?? null; | ||
| const leverage = effectiveLeverage(leg.leverage, market); | ||
| return { | ||
| asset: leg.asset, | ||
| pool: market?.pool ?? UNRESOLVED_POOL, | ||
| weight: leg.weight, | ||
| effectiveLeverage: leverage, | ||
| marginMode: leg.marginMode, | ||
| maxNotional: tierBoundaryNotional(market?.marginTiers ?? null, leverage) | ||
| }; | ||
| }); | ||
| } | ||
| __name(buildCappedLegs, "buildCappedLegs"); | ||
| function buildPoolBudgets(legs, markets, assetData) { | ||
| const dataByPool = /* @__PURE__ */ new Map(); | ||
| for (const leg of legs) { | ||
| const pool = markets.get(leg.asset)?.pool ?? null; | ||
| const data = assetData.get(leg.asset); | ||
| if (pool === null || data === void 0) { | ||
| continue; | ||
| } | ||
| const bucket = dataByPool.get(pool) ?? []; | ||
| bucket.push(data); | ||
| dataByPool.set(pool, bucket); | ||
| } | ||
| const budgets = {}; | ||
| for (const [pool, data] of dataByPool) { | ||
| const floor = minimumAvailableToTrade(data); | ||
| if (floor !== null) { | ||
| budgets[pool] = floor; | ||
| } | ||
| } | ||
| return budgets; | ||
| } | ||
| __name(buildPoolBudgets, "buildPoolBudgets"); | ||
| function effectiveLeverage(requestedLeverage, market) { | ||
| let leverage = toDecimal(requestedLeverage); | ||
| if (market?.venueMaxLeverage !== null && market?.venueMaxLeverage !== void 0) { | ||
| leverage = BigNumber.min(leverage, market.venueMaxLeverage); | ||
| } | ||
| if (market?.accountLeverage !== null && market?.accountLeverage !== void 0) { | ||
| leverage = BigNumber.min(leverage, market.accountLeverage); | ||
| } | ||
| return BigNumber.max(leverage, 1).toFixed(); | ||
| } | ||
| __name(effectiveLeverage, "effectiveLeverage"); | ||
| function tierBoundaryNotional(marginTiers, effectiveLeverage2) { | ||
| if (!marginTiers) { | ||
| return null; | ||
| } | ||
| const leverage = toDecimal(effectiveLeverage2); | ||
| const boundary = marginTiers.find((tier) => leverage.isGreaterThan(tier.maxLeverage)); | ||
| return boundary ? boundary.lowerBound : null; | ||
| } | ||
| __name(tierBoundaryNotional, "tierBoundaryNotional"); | ||
| function minimumAvailableToTrade(assetData) { | ||
| let floor = null; | ||
| for (const data of assetData) { | ||
| for (const available of data.availableToTrade ?? []) { | ||
| const value = toDecimal(available); | ||
| if (floor === null || value.isLessThan(floor)) { | ||
| floor = value; | ||
| } | ||
| } | ||
| } | ||
| return floor === null ? null : floor.toFixed(); | ||
| } | ||
| __name(minimumAvailableToTrade, "minimumAvailableToTrade"); | ||
| function findLegMarket(dexToMeta, tokenNames, coin, accountLeverage) { | ||
| const { dex, bareCoin } = splitDexCoin(coin); | ||
| const meta = dexToMeta.get(dex); | ||
| const asset = meta?.universe.find((candidate) => candidate.name === coin || candidate.name === bareCoin); | ||
| const marginTiers = meta?.marginTables.find(([id]) => id === asset?.marginTableId)?.[1].marginTiers; | ||
| const collateralToken = meta?.collateralToken; | ||
| return { | ||
| pool: collateralToken === void 0 ? null : tokenNames.get(collateralToken) ?? null, | ||
| venueMaxLeverage: asset?.maxLeverage ?? null, | ||
| accountLeverage, | ||
| marginTiers: marginTiers?.length ? marginTiers : null | ||
| }; | ||
| } | ||
| __name(findLegMarket, "findLegMarket"); | ||
| export { buildCappedLegs, buildPoolBudgets, effectiveLeverage, findLegMarket, minimumAvailableToTrade, tierBoundaryNotional }; |
| import BigNumber from 'bignumber.js'; | ||
| import { QUOTE_ASSET } from './const'; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| const MARGIN_FRACTION_SCALE = 1e4; | ||
| const CROSS_POOL = QUOTE_ASSET; | ||
| const DECIMAL_PATTERN = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/; | ||
| function toLighterBasketCalculation(legs, context) { | ||
| const capped = []; | ||
| for (const leg of legs) { | ||
| const market = context.perpBySymbol.get(leg.asset); | ||
| if (!market) { | ||
| return { | ||
| budgets: {}, | ||
| legs: [] | ||
| }; | ||
| } | ||
| capped.push(toCappedLeg(leg, market, context.settingsByMarketId.get(String(market.market_id)))); | ||
| } | ||
| return { | ||
| // Every leg draws on the same pool, so one entry carries the whole basket's budget. | ||
| budgets: { | ||
| [CROSS_POOL]: context.availableBalance | ||
| }, | ||
| legs: capped | ||
| }; | ||
| } | ||
| __name(toLighterBasketCalculation, "toLighterBasketCalculation"); | ||
| function toCappedLeg(leg, market, currentSetting) { | ||
| return { | ||
| asset: leg.asset, | ||
| pool: CROSS_POOL, | ||
| weight: leg.weight, | ||
| effectiveLeverage: effectiveLeverage(leg.leverage, market, currentSetting), | ||
| marginMode: leg.marginMode, | ||
| maxNotional: null | ||
| }; | ||
| } | ||
| __name(toCappedLeg, "toCappedLeg"); | ||
| function effectiveLeverage(requested, market, currentSetting) { | ||
| const accountLeverage = currentSetting === void 0 ? defaultAccountLeverage(market) : toDecimal(currentSetting.leverage); | ||
| const granted = BigNumber.min(toDecimal(requested), venueMaxLeverage(market), accountLeverage); | ||
| return granted.isFinite() && granted.isGreaterThan(0) ? granted.toFixed() : "1"; | ||
| } | ||
| __name(effectiveLeverage, "effectiveLeverage"); | ||
| function venueMaxLeverage(market) { | ||
| return leverageFromMarginFraction(market.min_initial_margin_fraction); | ||
| } | ||
| __name(venueMaxLeverage, "venueMaxLeverage"); | ||
| function defaultAccountLeverage(market) { | ||
| return leverageFromMarginFraction(market.default_initial_margin_fraction).integerValue(BigNumber.ROUND_FLOOR); | ||
| } | ||
| __name(defaultAccountLeverage, "defaultAccountLeverage"); | ||
| function leverageFromMarginFraction(marginFraction) { | ||
| const fraction = toDecimal(marginFraction); | ||
| if (!fraction.isGreaterThan(0)) { | ||
| return new BigNumber(1); | ||
| } | ||
| return new BigNumber(MARGIN_FRACTION_SCALE).div(fraction); | ||
| } | ||
| __name(leverageFromMarginFraction, "leverageFromMarginFraction"); | ||
| function toDecimal(value) { | ||
| if (typeof value === "number") { | ||
| return Number.isFinite(value) ? new BigNumber(value) : new BigNumber(0); | ||
| } | ||
| if (typeof value !== "string" || !DECIMAL_PATTERN.test(value)) { | ||
| return new BigNumber(0); | ||
| } | ||
| return new BigNumber(value); | ||
| } | ||
| __name(toDecimal, "toDecimal"); | ||
| export { toLighterBasketCalculation, venueMaxLeverage }; |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
444881
-5.68%107
-2.73%11211
-5.73%+ Added
- Removed
Updated