@pear-protocol/exchanges-sdk 0.6.6 → 0.7.0
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 +105 -25
- package/dist/client/bybit/rest.js +8 -1
- package/dist/client/hyperliquid/rest.js +21 -1
- package/dist/client/lighter/rest.js +6 -12
- package/dist/client/okx/rest.js +5 -1
- package/dist/helper/lighter/helper.js +6 -2
- package/dist/index.d.ts +1112 -74
- package/dist/sdk.js +9 -2
- package/dist/tracker/balance-core.js +89 -0
- package/dist/tracker/balance.js +25 -0
- package/dist/tracker/basket-size.js +194 -0
- package/dist/tracker/binance/balance.js +11 -0
- package/dist/tracker/binance/mapper.js +87 -42
- package/dist/tracker/binance/tracker.js +29 -14
- package/dist/tracker/binance/types.js +0 -6
- package/dist/tracker/bybit/balance.js +14 -0
- package/dist/tracker/bybit/mapper.js +109 -52
- package/dist/tracker/bybit/tracker.js +242 -33
- package/dist/tracker/bybit/types.js +0 -7
- package/dist/tracker/hyperliquid/account-mode.js +14 -0
- package/dist/tracker/hyperliquid/balance.js +184 -0
- package/dist/tracker/hyperliquid/basket-size.js +93 -0
- package/dist/tracker/hyperliquid/coin-name.js +18 -0
- package/dist/tracker/hyperliquid/collateral.js +48 -0
- package/dist/tracker/hyperliquid/decimal.js +28 -0
- package/dist/tracker/hyperliquid/mapper.js +399 -176
- package/dist/tracker/hyperliquid/spot-pricing.js +106 -0
- package/dist/tracker/hyperliquid/tracker.js +237 -78
- package/dist/tracker/hyperliquid/types.js +1 -7
- package/dist/tracker/index.js +9 -7
- package/dist/tracker/lighter/balance.js +87 -0
- package/dist/tracker/lighter/basket-size.js +74 -0
- package/dist/tracker/lighter/mapper.js +324 -145
- package/dist/tracker/lighter/tracker.js +228 -117
- package/dist/tracker/lighter/types.js +1 -7
- package/dist/tracker/okx/balance.js +17 -0
- package/dist/tracker/okx/mapper.js +156 -58
- package/dist/tracker/okx/tracker.js +228 -29
- package/dist/tracker/okx/types.js +0 -8
- package/dist/tracker/types.js +0 -24
- package/package.json +7 -9
|
@@ -1,105 +1,109 @@
|
|
|
1
1
|
import BigNumber from 'bignumber.js';
|
|
2
2
|
import { positionKey } from '../utils';
|
|
3
|
-
import {
|
|
3
|
+
import { toDecimal, ZERO } from './decimal';
|
|
4
|
+
import { HyperliquidAccountType } from './types';
|
|
4
5
|
|
|
5
6
|
var __defProp = Object.defineProperty;
|
|
6
7
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
-
const LEGACY_COLLATERAL_TOKEN_MAP = new Map(Object.entries(COLLATERAL_TOKEN_MAP).map(([token, coin]) => [
|
|
8
|
-
Number(token),
|
|
9
|
-
coin
|
|
10
|
-
]));
|
|
11
8
|
const FULL_UTILIZATION_RATIO = "1.0000";
|
|
9
|
+
const RATIO_DECIMALS = 4;
|
|
10
|
+
const USDC_TOKEN_INDEX = 0;
|
|
12
11
|
const ZERO_COLLATERAL_SUMMARY = {
|
|
13
|
-
accountValue: "0",
|
|
14
|
-
totalMarginUsed: "0",
|
|
15
12
|
crossMaintenanceMarginUsed: "0",
|
|
16
|
-
isolatedMaintenanceMarginUsed: "0",
|
|
17
13
|
isolatedMarginUsed: "0",
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
accountValue: "0",
|
|
15
|
+
withdrawable: "0"
|
|
20
16
|
};
|
|
21
17
|
function aggregateClearinghouseStates(states) {
|
|
22
|
-
const summary =
|
|
23
|
-
accountValue: "0",
|
|
24
|
-
totalMarginUsed: "0",
|
|
25
|
-
totalRawUsd: "0",
|
|
26
|
-
crossMaintenanceMarginUsed: "0",
|
|
27
|
-
isolatedMaintenanceMarginUsed: "0",
|
|
28
|
-
maximumStandardMarginRatio: "0.0000",
|
|
29
|
-
withdrawable: "0",
|
|
30
|
-
totalUnrealizedPnl: "0",
|
|
31
|
-
byCollateralToken: /* @__PURE__ */ new Map()
|
|
32
|
-
};
|
|
18
|
+
const summary = emptyClearinghouseSummary();
|
|
33
19
|
const assetPositions = [];
|
|
34
20
|
for (const dexState of states) {
|
|
35
21
|
const state = dexState.state;
|
|
36
22
|
const positions = state.assetPositions ?? [];
|
|
37
|
-
const unrealizedPnl = sum(positions.map((position) => position.position.unrealizedPnl));
|
|
38
|
-
const
|
|
23
|
+
const unrealizedPnl = sum(positions.map((position) => position.position.unrealizedPnl)).toFixed();
|
|
24
|
+
const isolatedMarginUsed = sum(positions.flatMap((position) => {
|
|
39
25
|
if (position.position.leverage.type !== "isolated") {
|
|
40
26
|
return [];
|
|
41
27
|
}
|
|
42
28
|
return [
|
|
43
|
-
|
|
44
|
-
collateral: position.position.marginUsed,
|
|
45
|
-
maintenance: isolatedMaintenanceMargin(dexState, position)
|
|
46
|
-
}
|
|
29
|
+
position.position.marginUsed
|
|
47
30
|
];
|
|
48
|
-
});
|
|
49
|
-
const isolatedMaintenanceMarginUsed = sum(isolatedRisks.map((risk) => risk.maintenance));
|
|
50
|
-
const isolatedMarginUsed = sum(isolatedRisks.map((risk) => risk.collateral));
|
|
51
|
-
let dexMaximumMarginRatio = ratio(state.crossMaintenanceMarginUsed, state.crossMarginSummary.accountValue);
|
|
52
|
-
for (const isolatedRisk of isolatedRisks) {
|
|
53
|
-
dexMaximumMarginRatio = BigNumber.max(dexMaximumMarginRatio, ratio(isolatedRisk.maintenance, isolatedRisk.collateral)).toFixed(4);
|
|
54
|
-
}
|
|
31
|
+
})).toFixed();
|
|
55
32
|
summary.accountValue = add(summary.accountValue, state.marginSummary.accountValue);
|
|
56
|
-
summary.
|
|
57
|
-
summary.
|
|
33
|
+
summary.crossAccountValue = add(summary.crossAccountValue, state.crossMarginSummary.accountValue);
|
|
34
|
+
summary.crossNotional = add(summary.crossNotional, state.crossMarginSummary.totalNtlPos);
|
|
58
35
|
summary.crossMaintenanceMarginUsed = add(summary.crossMaintenanceMarginUsed, state.crossMaintenanceMarginUsed);
|
|
59
|
-
summary.isolatedMaintenanceMarginUsed = add(summary.isolatedMaintenanceMarginUsed, isolatedMaintenanceMarginUsed);
|
|
60
|
-
summary.maximumStandardMarginRatio = BigNumber.max(summary.maximumStandardMarginRatio, dexMaximumMarginRatio).toFixed(4);
|
|
61
36
|
summary.withdrawable = add(summary.withdrawable, state.withdrawable);
|
|
62
37
|
summary.totalUnrealizedPnl = add(summary.totalUnrealizedPnl, unrealizedPnl);
|
|
63
38
|
assetPositions.push(...positions);
|
|
39
|
+
summary.perDexState.push({
|
|
40
|
+
dex: dexState.dex,
|
|
41
|
+
collateralToken: dexState.collateralToken,
|
|
42
|
+
accountValue: state.marginSummary.accountValue,
|
|
43
|
+
crossNotional: state.crossMarginSummary.totalNtlPos,
|
|
44
|
+
crossMaintenanceMargin: state.crossMaintenanceMarginUsed,
|
|
45
|
+
withdrawable: state.withdrawable
|
|
46
|
+
});
|
|
64
47
|
if (dexState.collateralToken === null) {
|
|
65
48
|
continue;
|
|
66
49
|
}
|
|
67
50
|
const collateral = summary.byCollateralToken.get(dexState.collateralToken) ?? ZERO_COLLATERAL_SUMMARY;
|
|
68
51
|
summary.byCollateralToken.set(dexState.collateralToken, {
|
|
69
|
-
accountValue: add(collateral.accountValue, state.marginSummary.accountValue),
|
|
70
|
-
totalMarginUsed: add(collateral.totalMarginUsed, state.marginSummary.totalMarginUsed),
|
|
71
52
|
crossMaintenanceMarginUsed: add(collateral.crossMaintenanceMarginUsed, state.crossMaintenanceMarginUsed),
|
|
72
|
-
isolatedMaintenanceMarginUsed: add(collateral.isolatedMaintenanceMarginUsed, isolatedMaintenanceMarginUsed),
|
|
73
53
|
isolatedMarginUsed: add(collateral.isolatedMarginUsed, isolatedMarginUsed),
|
|
74
|
-
|
|
75
|
-
|
|
54
|
+
accountValue: add(collateral.accountValue, state.marginSummary.accountValue),
|
|
55
|
+
withdrawable: add(collateral.withdrawable, state.withdrawable)
|
|
76
56
|
});
|
|
77
57
|
}
|
|
58
|
+
summary.perDexState.sort(byDexName);
|
|
78
59
|
return {
|
|
79
60
|
summary,
|
|
80
61
|
assetPositions
|
|
81
62
|
};
|
|
82
63
|
}
|
|
83
64
|
__name(aggregateClearinghouseStates, "aggregateClearinghouseStates");
|
|
84
|
-
function
|
|
85
|
-
if (
|
|
65
|
+
function byDexName(left, right) {
|
|
66
|
+
if (left.dex === right.dex) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
return left.dex < right.dex ? -1 : 1;
|
|
70
|
+
}
|
|
71
|
+
__name(byDexName, "byDexName");
|
|
72
|
+
function emptyClearinghouseSummary() {
|
|
73
|
+
return {
|
|
74
|
+
accountValue: "0",
|
|
75
|
+
crossAccountValue: "0",
|
|
76
|
+
crossNotional: "0",
|
|
77
|
+
crossMaintenanceMarginUsed: "0",
|
|
78
|
+
withdrawable: "0",
|
|
79
|
+
totalUnrealizedPnl: "0",
|
|
80
|
+
byCollateralToken: /* @__PURE__ */ new Map(),
|
|
81
|
+
perDexState: []
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
__name(emptyClearinghouseSummary, "emptyClearinghouseSummary");
|
|
85
|
+
function mapBalance(context) {
|
|
86
|
+
if (!context.accountType) {
|
|
86
87
|
return null;
|
|
87
88
|
}
|
|
88
|
-
switch (accountType) {
|
|
89
|
+
switch (context.accountType) {
|
|
89
90
|
case HyperliquidAccountType.DEFAULT:
|
|
90
91
|
case HyperliquidAccountType.DISABLED:
|
|
91
|
-
return mapStandardBalance(
|
|
92
|
+
return mapStandardBalance(context);
|
|
92
93
|
case HyperliquidAccountType.UNIFIED_ACCOUNT:
|
|
94
|
+
return mapUnifiedBalance(context);
|
|
93
95
|
case HyperliquidAccountType.PORTFOLIO_MARGIN:
|
|
94
|
-
return
|
|
96
|
+
return mapPortfolioMarginBalance(context);
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
__name(mapBalance, "mapBalance");
|
|
98
100
|
function mapPositions(raw) {
|
|
99
101
|
const positions = /* @__PURE__ */ new Map();
|
|
100
|
-
for (const assetPosition of
|
|
102
|
+
for (const assetPosition of [
|
|
103
|
+
...raw
|
|
104
|
+
].sort(byPositionKey)) {
|
|
101
105
|
const position = assetPosition.position;
|
|
102
|
-
const size =
|
|
106
|
+
const size = toDecimal(position.szi);
|
|
103
107
|
if (size.isZero()) {
|
|
104
108
|
continue;
|
|
105
109
|
}
|
|
@@ -119,167 +123,386 @@ function mapPositions(raw) {
|
|
|
119
123
|
return positions;
|
|
120
124
|
}
|
|
121
125
|
__name(mapPositions, "mapPositions");
|
|
122
|
-
function
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (const [token, collateral] of summary.byCollateralToken) {
|
|
128
|
-
const coin = collateralTokenMap.get(token);
|
|
129
|
-
if (!coin) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
free[coin] = collateral.withdrawable;
|
|
133
|
-
used[coin] = collateral.totalMarginUsed;
|
|
134
|
-
total[coin] = collateral.accountValue;
|
|
135
|
-
assets.push({
|
|
136
|
-
asset: coin,
|
|
137
|
-
free: collateral.withdrawable,
|
|
138
|
-
used: collateral.totalMarginUsed,
|
|
139
|
-
total: collateral.accountValue,
|
|
140
|
-
walletBalance: subtract(collateral.accountValue, collateral.totalUnrealizedPnl),
|
|
141
|
-
usdValue: collateral.accountValue
|
|
142
|
-
});
|
|
126
|
+
function byPositionKey(left, right) {
|
|
127
|
+
const leftCoin = left.position.coin || "";
|
|
128
|
+
const rightCoin = right.position.coin || "";
|
|
129
|
+
if (leftCoin === rightCoin) {
|
|
130
|
+
return 0;
|
|
143
131
|
}
|
|
144
|
-
|
|
132
|
+
return leftCoin < rightCoin ? -1 : 1;
|
|
133
|
+
}
|
|
134
|
+
__name(byPositionKey, "byPositionKey");
|
|
135
|
+
function mapStandardBalance(context) {
|
|
136
|
+
const spotEquity = spotEquityOf(context);
|
|
137
|
+
const perpsEquity = subtract(context.summary.accountValue, context.summary.totalUnrealizedPnl);
|
|
138
|
+
const stakedEquity = stakedEquityOf(context);
|
|
145
139
|
return {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
140
|
+
accountMode: "hyperliquid:standard",
|
|
141
|
+
totalEquity: addAllKnown([
|
|
142
|
+
spotEquity,
|
|
143
|
+
perpsEquity,
|
|
144
|
+
context.vaultEquity,
|
|
145
|
+
stakedEquity
|
|
146
|
+
]),
|
|
147
|
+
// Standard withdrawable is the perps wallet's free margin, summed across dexes.
|
|
148
|
+
withdrawableBalance: context.summary.withdrawable,
|
|
149
|
+
availableMargin: context.availableMargin,
|
|
150
|
+
// Separate budgets, not holdings in one pool: a USDH dex cannot spend the USDC beside it.
|
|
151
|
+
poolsShared: false,
|
|
152
|
+
perCollateral: perpsCollateralRows(context),
|
|
153
|
+
spotEquity,
|
|
154
|
+
perpsEquity: signed(perpsEquity),
|
|
155
|
+
vaultEquity: context.vaultEquity,
|
|
156
|
+
stakedEquity,
|
|
157
|
+
unrealizedPnl: signed(context.summary.totalUnrealizedPnl),
|
|
158
|
+
// Cross-only: isolated legs carry their own margin and liquidate independently.
|
|
159
|
+
maintenanceMargin: context.summary.crossMaintenanceMarginUsed,
|
|
160
|
+
// Risk levers against ACCOUNT VALUE, not the cash above: one pool, one quotient, one division.
|
|
161
|
+
crossMarginRatio: ratio(context.summary.crossMaintenanceMarginUsed, context.summary.crossAccountValue),
|
|
162
|
+
crossAccountLeverage: accountLeverage(context.summary.crossNotional, context.summary.crossAccountValue),
|
|
163
|
+
perDexState: context.summary.perDexState,
|
|
164
|
+
timestamp: context.timestamp
|
|
159
165
|
};
|
|
160
166
|
}
|
|
161
167
|
__name(mapStandardBalance, "mapStandardBalance");
|
|
162
|
-
function mapUnifiedBalance(
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
function mapUnifiedBalance(context) {
|
|
169
|
+
const tradingEquity = spotEquityOf(context);
|
|
170
|
+
const stakedEquity = stakedEquityOf(context);
|
|
171
|
+
const portfolioValue = addAllKnown([
|
|
172
|
+
tradingEquity,
|
|
173
|
+
context.vaultEquity,
|
|
174
|
+
stakedEquity
|
|
175
|
+
]);
|
|
176
|
+
return {
|
|
177
|
+
accountMode: "hyperliquid:unified",
|
|
178
|
+
totalEquity: portfolioValue,
|
|
179
|
+
// What may leave is the spot balance net of the margin the perps side has locked.
|
|
180
|
+
withdrawableBalance: freeCollateral(context),
|
|
181
|
+
availableMargin: context.availableMargin,
|
|
182
|
+
// Separate budgets, not holdings in one pool: a USDH dex cannot spend the USDC beside it.
|
|
183
|
+
poolsShared: false,
|
|
184
|
+
perCollateral: spotCollateralRows(context),
|
|
185
|
+
tradingEquity,
|
|
186
|
+
vaultEquity: context.vaultEquity,
|
|
187
|
+
stakedEquity,
|
|
188
|
+
portfolioValue,
|
|
189
|
+
unrealizedPnl: signed(context.summary.totalUnrealizedPnl),
|
|
190
|
+
// Cross-only: adding isolated maintenance overstated the figure by 2x against the venue's UI.
|
|
191
|
+
perpsMaintenanceMargin: context.summary.crossMaintenanceMarginUsed,
|
|
192
|
+
unifiedAccountRatio: crossRatioAgainstFreeSpot(context),
|
|
193
|
+
unifiedAccountLeverage: unifiedAccountLeverage(context),
|
|
194
|
+
perDexState: context.summary.perDexState,
|
|
195
|
+
timestamp: context.timestamp
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
__name(mapUnifiedBalance, "mapUnifiedBalance");
|
|
199
|
+
function mapPortfolioMarginBalance(context) {
|
|
200
|
+
const tradingEquity = spotEquityOf(context);
|
|
201
|
+
const stakedEquity = stakedEquityOf(context);
|
|
202
|
+
return {
|
|
203
|
+
accountMode: "hyperliquid:portfolioMargin",
|
|
204
|
+
// Total Equity is the only figure that adds what sits outside trading back on.
|
|
205
|
+
totalEquity: addAllKnown([
|
|
206
|
+
tradingEquity,
|
|
207
|
+
context.vaultEquity,
|
|
208
|
+
stakedEquity
|
|
209
|
+
]),
|
|
210
|
+
withdrawableBalance: settlementAvailableBalance(context),
|
|
211
|
+
availableMargin: context.availableMargin,
|
|
212
|
+
// Holdings in one pool, not separate budgets: PM lends against every coin at its LTV weight.
|
|
213
|
+
poolsShared: true,
|
|
214
|
+
perCollateral: portfolioMarginCollateralRows(context),
|
|
215
|
+
tradingEquity,
|
|
216
|
+
vaultEquity: context.vaultEquity,
|
|
217
|
+
stakedEquity,
|
|
218
|
+
portfolioValue: tradingEquity,
|
|
219
|
+
unrealizedPnl: signed(context.summary.totalUnrealizedPnl),
|
|
220
|
+
perpsMaintenanceMargin: context.summary.crossMaintenanceMarginUsed,
|
|
221
|
+
portfolioMarginRatio: wireMarginRatio(context),
|
|
222
|
+
portfolioAccountLeverage: portfolioAccountLeverage(context),
|
|
223
|
+
borrowCapUsed: worstBorrowCapUsed(context),
|
|
224
|
+
perDexState: context.summary.perDexState,
|
|
225
|
+
timestamp: context.timestamp
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
__name(mapPortfolioMarginBalance, "mapPortfolioMarginBalance");
|
|
229
|
+
function settlementAvailableBalance(context) {
|
|
230
|
+
const settlement = spotBalanceForToken(context, USDC_TOKEN_INDEX);
|
|
231
|
+
if (!settlement) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const available = toDecimal(settlement.total).minus(toDecimal(settlement.hold));
|
|
235
|
+
return available.isNegative() ? null : available.toFixed();
|
|
236
|
+
}
|
|
237
|
+
__name(settlementAvailableBalance, "settlementAvailableBalance");
|
|
238
|
+
function portfolioMarginCollateralRows(context) {
|
|
239
|
+
const rows = [];
|
|
240
|
+
for (const balance of context.spotBalances.values()) {
|
|
241
|
+
const total = toDecimal(balance.total);
|
|
242
|
+
rows.push({
|
|
243
|
+
asset: balance.coin,
|
|
244
|
+
equity: signed(total.toFixed()),
|
|
245
|
+
availableMargin: context.availableMarginByToken.get(balance.token) ?? null,
|
|
246
|
+
withdrawable: signed(total.minus(toDecimal(balance.hold)).toFixed()),
|
|
247
|
+
ltv: balance.ltv === null ? null : toDecimal(balance.ltv).toFixed(),
|
|
248
|
+
borrowCapUsed: borrowCapUsedFor(context, balance.token)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
rows.sort(byRowAsset);
|
|
252
|
+
return rows;
|
|
253
|
+
}
|
|
254
|
+
__name(portfolioMarginCollateralRows, "portfolioMarginCollateralRows");
|
|
255
|
+
function byRowAsset(left, right) {
|
|
256
|
+
if (left.asset === right.asset) {
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
return left.asset < right.asset ? -1 : 1;
|
|
260
|
+
}
|
|
261
|
+
__name(byRowAsset, "byRowAsset");
|
|
262
|
+
function borrowCapUsedFor(context, token) {
|
|
263
|
+
const used = toDecimal(context.borrowRatioByToken.get(token));
|
|
264
|
+
return used.isZero() ? null : used.toFixed(RATIO_DECIMALS);
|
|
265
|
+
}
|
|
266
|
+
__name(borrowCapUsedFor, "borrowCapUsedFor");
|
|
267
|
+
function worstBorrowCapUsed(context) {
|
|
268
|
+
let worst = ZERO;
|
|
269
|
+
for (const ratio2 of context.borrowRatioByToken.values()) {
|
|
270
|
+
worst = BigNumber.max(worst, toDecimal(ratio2));
|
|
271
|
+
}
|
|
272
|
+
return worst.isZero() ? null : worst.toFixed(RATIO_DECIMALS);
|
|
273
|
+
}
|
|
274
|
+
__name(worstBorrowCapUsed, "worstBorrowCapUsed");
|
|
275
|
+
function wireMarginRatio(context) {
|
|
276
|
+
if (context.portfolioMarginRatio === null) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return toDecimal(context.portfolioMarginRatio).toFixed(RATIO_DECIMALS);
|
|
280
|
+
}
|
|
281
|
+
__name(wireMarginRatio, "wireMarginRatio");
|
|
282
|
+
function portfolioAccountLeverage(context) {
|
|
283
|
+
const collateral = haircutCollateral(context);
|
|
284
|
+
if (collateral === null) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
return accountLeverage(context.summary.crossNotional, collateral);
|
|
288
|
+
}
|
|
289
|
+
__name(portfolioAccountLeverage, "portfolioAccountLeverage");
|
|
290
|
+
function haircutCollateral(context) {
|
|
291
|
+
let collateral = ZERO;
|
|
292
|
+
for (const balance of context.spotBalances.values()) {
|
|
293
|
+
const total = toDecimal(balance.total);
|
|
294
|
+
if (total.isZero()) {
|
|
171
295
|
continue;
|
|
172
296
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
297
|
+
if (balance.ltv === null && total.isPositive()) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const mid = context.prices.midForToken(balance.token);
|
|
301
|
+
if (mid === null) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
collateral = collateral.plus(weightedCollateral(total.times(toDecimal(mid)), balance.ltv));
|
|
305
|
+
}
|
|
306
|
+
return collateral.toFixed();
|
|
307
|
+
}
|
|
308
|
+
__name(haircutCollateral, "haircutCollateral");
|
|
309
|
+
function weightedCollateral(usd, ltv) {
|
|
310
|
+
if (usd.isNegative()) {
|
|
311
|
+
return usd;
|
|
312
|
+
}
|
|
313
|
+
if (ltv === null) {
|
|
314
|
+
return ZERO;
|
|
315
|
+
}
|
|
316
|
+
const weight = toDecimal(ltv);
|
|
317
|
+
return weight.isZero() ? usd : usd.times(weight);
|
|
318
|
+
}
|
|
319
|
+
__name(weightedCollateral, "weightedCollateral");
|
|
320
|
+
function perpsCollateralRows(context) {
|
|
321
|
+
const rows = collateralPools(context).map((pool) => ({
|
|
322
|
+
asset: pool.asset,
|
|
323
|
+
equity: pool.collateral.accountValue,
|
|
324
|
+
availableMargin: context.availableMarginByToken.get(pool.token) ?? null,
|
|
325
|
+
withdrawable: pool.collateral.withdrawable
|
|
326
|
+
}));
|
|
327
|
+
return fundedRows(rows);
|
|
328
|
+
}
|
|
329
|
+
__name(perpsCollateralRows, "perpsCollateralRows");
|
|
330
|
+
function spotCollateralRows(context) {
|
|
331
|
+
const rows = collateralPools(context).map((pool) => {
|
|
332
|
+
const balance = spotBalanceForToken(context, pool.token);
|
|
333
|
+
const total = toDecimal(balance?.total);
|
|
334
|
+
const locked = BigNumber.max(toDecimal(balance?.hold), ZERO);
|
|
335
|
+
return {
|
|
336
|
+
asset: pool.asset,
|
|
337
|
+
equity: total.toFixed(),
|
|
338
|
+
availableMargin: context.availableMarginByToken.get(pool.token) ?? null,
|
|
339
|
+
withdrawable: total.minus(locked).toFixed()
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
return fundedRows(rows);
|
|
343
|
+
}
|
|
344
|
+
__name(spotCollateralRows, "spotCollateralRows");
|
|
345
|
+
function fundedRows(rows) {
|
|
346
|
+
return rows.filter(isFundedRow);
|
|
347
|
+
}
|
|
348
|
+
__name(fundedRows, "fundedRows");
|
|
349
|
+
function isFundedRow(row) {
|
|
350
|
+
const figures = [
|
|
351
|
+
row.equity,
|
|
352
|
+
row.withdrawable,
|
|
353
|
+
row.availableMargin ?? "0"
|
|
354
|
+
];
|
|
355
|
+
return figures.some((figure) => !toDecimal(figure).isZero());
|
|
356
|
+
}
|
|
357
|
+
__name(isFundedRow, "isFundedRow");
|
|
358
|
+
function collateralPools(context) {
|
|
359
|
+
const pools = [];
|
|
360
|
+
for (const [token, collateral] of context.summary.byCollateralToken) {
|
|
361
|
+
const asset = context.collateralTokenNames.get(token);
|
|
362
|
+
if (asset === void 0) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
pools.push({
|
|
366
|
+
token,
|
|
367
|
+
asset,
|
|
368
|
+
collateral
|
|
189
369
|
});
|
|
190
370
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const walletBalance = subtract(totalEquityValue, summary.totalUnrealizedPnl);
|
|
194
|
-
return {
|
|
195
|
-
accountType,
|
|
196
|
-
free,
|
|
197
|
-
used,
|
|
198
|
-
total,
|
|
199
|
-
availableToTrade: availableToTrade.toFixed(),
|
|
200
|
-
// Unified spot totals are the authoritative equity balances and already include perp PnL.
|
|
201
|
-
totalEquity: totalEquityValue,
|
|
202
|
-
walletBalance,
|
|
203
|
-
unrealizedPnl: summary.totalUnrealizedPnl,
|
|
204
|
-
initialMarginUsed: summary.totalMarginUsed,
|
|
205
|
-
maintenanceMargin,
|
|
206
|
-
marginRatio: unifiedMarginRatio(summary, spotCoinBalances),
|
|
207
|
-
assets,
|
|
208
|
-
timestamp: Date.now()
|
|
209
|
-
};
|
|
371
|
+
pools.sort(byAssetName);
|
|
372
|
+
return pools;
|
|
210
373
|
}
|
|
211
|
-
__name(
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const spotBalance = [
|
|
216
|
-
...spotCoinBalances.values()
|
|
217
|
-
].find((balance) => balance.token === token);
|
|
218
|
-
const available = new BigNumber(spotBalance?.total ?? "0").minus(collateral.isolatedMarginUsed);
|
|
219
|
-
maximumRatio = BigNumber.max(maximumRatio, ratio(collateral.crossMaintenanceMarginUsed, available.toFixed()));
|
|
374
|
+
__name(collateralPools, "collateralPools");
|
|
375
|
+
function byAssetName(left, right) {
|
|
376
|
+
if (left.asset === right.asset) {
|
|
377
|
+
return 0;
|
|
220
378
|
}
|
|
221
|
-
return
|
|
379
|
+
return left.asset < right.asset ? -1 : 1;
|
|
222
380
|
}
|
|
223
|
-
__name(
|
|
224
|
-
function
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
381
|
+
__name(byAssetName, "byAssetName");
|
|
382
|
+
function spotEquityOf(context) {
|
|
383
|
+
const valuation = context.prices.valueBalances(context.spotBalances.values());
|
|
384
|
+
if (valuation.unpricedCoins.length > 0) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
return valuation.equity;
|
|
388
|
+
}
|
|
389
|
+
__name(spotEquityOf, "spotEquityOf");
|
|
390
|
+
function stakedEquityOf(context) {
|
|
391
|
+
const staked = toDecimal(context.stakedHype);
|
|
392
|
+
if (staked.isZero()) {
|
|
227
393
|
return "0";
|
|
228
394
|
}
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (!marginTiers || !firstMarginTier || marginTiers.some((tier) => tier.maxLeverage <= 0)) {
|
|
233
|
-
return notional.div(position.maxLeverage).div(2).toFixed();
|
|
395
|
+
const mid = context.prices.midForCoin("HYPE");
|
|
396
|
+
if (mid === null) {
|
|
397
|
+
return null;
|
|
234
398
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
399
|
+
return staked.times(toDecimal(mid)).toFixed();
|
|
400
|
+
}
|
|
401
|
+
__name(stakedEquityOf, "stakedEquityOf");
|
|
402
|
+
function freeCollateral(context) {
|
|
403
|
+
let free = ZERO;
|
|
404
|
+
for (const token of context.summary.byCollateralToken.keys()) {
|
|
405
|
+
const balance = spotBalanceForToken(context, token);
|
|
406
|
+
if (!balance) {
|
|
407
|
+
continue;
|
|
240
408
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
maintenanceDeduction = maintenanceDeduction.plus(new BigNumber(nextTier.lowerBound).times(nextRate.minus(previousRate)));
|
|
244
|
-
activeTier = nextTier;
|
|
409
|
+
const locked = BigNumber.max(toDecimal(balance.hold), ZERO);
|
|
410
|
+
free = free.plus(toDecimal(balance.total).minus(locked));
|
|
245
411
|
}
|
|
246
|
-
return
|
|
412
|
+
return free.toFixed();
|
|
247
413
|
}
|
|
248
|
-
__name(
|
|
249
|
-
function
|
|
250
|
-
|
|
414
|
+
__name(freeCollateral, "freeCollateral");
|
|
415
|
+
function crossRatioAgainstFreeSpot(context) {
|
|
416
|
+
let maximumRatio = ZERO;
|
|
417
|
+
for (const [token, collateral] of context.summary.byCollateralToken) {
|
|
418
|
+
const spotTotal = spotBalanceForToken(context, token)?.total ?? "0";
|
|
419
|
+
const available = toDecimal(spotTotal).minus(toDecimal(collateral.isolatedMarginUsed));
|
|
420
|
+
maximumRatio = BigNumber.max(maximumRatio, ratio(collateral.crossMaintenanceMarginUsed, available.toFixed()));
|
|
421
|
+
}
|
|
422
|
+
return maximumRatio.toFixed(RATIO_DECIMALS);
|
|
423
|
+
}
|
|
424
|
+
__name(crossRatioAgainstFreeSpot, "crossRatioAgainstFreeSpot");
|
|
425
|
+
function unifiedAccountLeverage(context) {
|
|
426
|
+
const collateral = collateralValue(context);
|
|
427
|
+
if (collateral === null) {
|
|
251
428
|
return null;
|
|
252
429
|
}
|
|
253
|
-
|
|
254
|
-
const asset = dexState.meta.universe.find((candidate) => candidate.name === coin || candidate.name === unprefixedCoin);
|
|
255
|
-
const marginTable = dexState.meta.marginTables.find(([id]) => id === asset?.marginTableId)?.[1];
|
|
256
|
-
return marginTable?.marginTiers.length ? marginTable.marginTiers : null;
|
|
430
|
+
return accountLeverage(context.summary.crossNotional, collateral);
|
|
257
431
|
}
|
|
258
|
-
__name(
|
|
259
|
-
function
|
|
260
|
-
|
|
432
|
+
__name(unifiedAccountLeverage, "unifiedAccountLeverage");
|
|
433
|
+
function collateralValue(context) {
|
|
434
|
+
let collateral = ZERO;
|
|
435
|
+
for (const token of context.summary.byCollateralToken.keys()) {
|
|
436
|
+
const balance = spotBalanceForToken(context, token);
|
|
437
|
+
const total = toDecimal(balance?.total);
|
|
438
|
+
if (total.isZero()) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const mid = context.prices.midForToken(token);
|
|
442
|
+
if (mid === null) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
collateral = collateral.plus(total.times(toDecimal(mid)));
|
|
446
|
+
}
|
|
447
|
+
return collateral.toFixed();
|
|
448
|
+
}
|
|
449
|
+
__name(collateralValue, "collateralValue");
|
|
450
|
+
function spotBalanceForToken(context, token) {
|
|
451
|
+
for (const balance of context.spotBalances.values()) {
|
|
452
|
+
if (balance.token === token) {
|
|
453
|
+
return balance;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
__name(spotBalanceForToken, "spotBalanceForToken");
|
|
459
|
+
function accountLeverage(crossNotional, equity) {
|
|
460
|
+
const equityValue = toDecimal(equity);
|
|
461
|
+
if (!equityValue.isGreaterThan(0)) {
|
|
462
|
+
return "0.0000";
|
|
463
|
+
}
|
|
464
|
+
return toDecimal(crossNotional).div(equityValue).toFixed(RATIO_DECIMALS);
|
|
261
465
|
}
|
|
262
|
-
__name(
|
|
466
|
+
__name(accountLeverage, "accountLeverage");
|
|
263
467
|
function add(left, right) {
|
|
264
|
-
return
|
|
468
|
+
return toDecimal(left).plus(toDecimal(right)).toFixed();
|
|
265
469
|
}
|
|
266
470
|
__name(add, "add");
|
|
471
|
+
function addAllKnown(values) {
|
|
472
|
+
let total = ZERO;
|
|
473
|
+
for (const value of values) {
|
|
474
|
+
if (value === null) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
total = total.plus(toDecimal(value));
|
|
478
|
+
}
|
|
479
|
+
return total.toFixed();
|
|
480
|
+
}
|
|
481
|
+
__name(addAllKnown, "addAllKnown");
|
|
267
482
|
function subtract(left, right) {
|
|
268
|
-
return
|
|
483
|
+
return toDecimal(left).minus(toDecimal(right)).toFixed();
|
|
269
484
|
}
|
|
270
485
|
__name(subtract, "subtract");
|
|
271
486
|
function sum(values) {
|
|
272
|
-
return values.reduce((total, value) => total.plus(value
|
|
487
|
+
return values.reduce((total, value) => total.plus(toDecimal(value)), ZERO);
|
|
273
488
|
}
|
|
274
489
|
__name(sum, "sum");
|
|
490
|
+
function signed(value) {
|
|
491
|
+
const amount = toDecimal(value);
|
|
492
|
+
if (amount.isZero()) {
|
|
493
|
+
return "+0";
|
|
494
|
+
}
|
|
495
|
+
return amount.isNegative() ? amount.toFixed() : `+${amount.toFixed()}`;
|
|
496
|
+
}
|
|
497
|
+
__name(signed, "signed");
|
|
275
498
|
function ratio(numerator, denominator) {
|
|
276
|
-
const numeratorValue =
|
|
277
|
-
const denominatorValue =
|
|
499
|
+
const numeratorValue = toDecimal(numerator);
|
|
500
|
+
const denominatorValue = toDecimal(denominator);
|
|
278
501
|
if (denominatorValue.gt(0)) {
|
|
279
|
-
return numeratorValue.div(denominatorValue).toFixed(
|
|
502
|
+
return numeratorValue.div(denominatorValue).toFixed(RATIO_DECIMALS);
|
|
280
503
|
}
|
|
281
504
|
return numeratorValue.gt(0) ? FULL_UTILIZATION_RATIO : "0.0000";
|
|
282
505
|
}
|
|
283
506
|
__name(ratio, "ratio");
|
|
284
507
|
|
|
285
|
-
export { aggregateClearinghouseStates, mapBalance, mapPositions };
|
|
508
|
+
export { aggregateClearinghouseStates, emptyClearinghouseSummary, mapBalance, mapPositions };
|