@reyaxyz/common 0.364.1 → 0.364.3
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 +1 -1
- package/dist/commands/exposure/command.js +2 -340
- package/dist/commands/exposure/command.js.map +1 -1
- package/dist/commands/exposure/types.js.map +1 -1
- package/dist/trading-api-types.js.map +1 -1
- package/dist/types/commands/exposure/command.d.ts +1 -44
- package/dist/types/commands/exposure/command.d.ts.map +1 -1
- package/dist/types/commands/exposure/types.d.ts +0 -10
- package/dist/types/commands/exposure/types.d.ts.map +1 -1
- package/dist/types/trading-api-types.d.ts +2 -51
- package/dist/types/trading-api-types.d.ts.map +1 -1
- package/dist/types/types.d.ts +0 -8
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/calculate.d.ts +0 -125
- package/dist/types/utils/calculate.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/calculate.js +1 -324
- package/dist/utils/calculate.js.map +1 -1
- package/package.json +3 -5
- package/src/commands/exposure/command.ts +0 -550
- package/src/commands/exposure/types.ts +0 -12
- package/src/trading-api-types.ts +1 -66
- package/src/types.ts +0 -10
- package/src/utils/calculate.ts +0 -497
package/src/trading-api-types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParamsDictionary, Query } from 'express-serve-static-core';
|
|
2
|
-
import { Address, AssetPair
|
|
2
|
+
import { Address, AssetPair } from './types';
|
|
3
3
|
import {
|
|
4
4
|
AddressParam,
|
|
5
5
|
ApiEndpoint,
|
|
@@ -353,7 +353,6 @@ export type TradingApiSource = {
|
|
|
353
353
|
market_id: Decimal;
|
|
354
354
|
risk_matrix_index: Decimal | null;
|
|
355
355
|
max_open_base: Decimal | null;
|
|
356
|
-
velocity_multiplier: Decimal | null;
|
|
357
356
|
minimum_order_base: Decimal | null;
|
|
358
357
|
base_spacing: Decimal | null;
|
|
359
358
|
price_spacing: Decimal | null;
|
|
@@ -363,11 +362,6 @@ export type TradingApiSource = {
|
|
|
363
362
|
dutch_config_min_base: Decimal | null;
|
|
364
363
|
slippage_params_phi: Decimal | null;
|
|
365
364
|
slippage_params_beta: Decimal | null;
|
|
366
|
-
depth_factor: Decimal | null;
|
|
367
|
-
max_exposure_factor: Decimal | null;
|
|
368
|
-
max_p_slippage: Decimal | null;
|
|
369
|
-
price_spread: Decimal | null;
|
|
370
|
-
volatility_index_multiplier: Decimal | null;
|
|
371
365
|
block_timestamp: Decimal | null;
|
|
372
366
|
block_number: Decimal;
|
|
373
367
|
unique_id: bigint | null;
|
|
@@ -413,27 +407,6 @@ export type TradingApiSource = {
|
|
|
413
407
|
// trading.controller.ts. V2 callers should use
|
|
414
408
|
// `/v2/wallet/:address/openOrders` which serves both spot and perp
|
|
415
409
|
// orders from the unified ME stream via AccountOrdersMap.
|
|
416
|
-
assets: TradingApiListEndpoint<{
|
|
417
|
-
address: string;
|
|
418
|
-
name: string;
|
|
419
|
-
short: string;
|
|
420
|
-
createdAt: Date;
|
|
421
|
-
updatedAt: Date;
|
|
422
|
-
asset_price_contract_id: string | null;
|
|
423
|
-
asset_price_usdc_contract_id: string | null;
|
|
424
|
-
decimals: number;
|
|
425
|
-
}>;
|
|
426
|
-
prices: TradingApiEndpoint<
|
|
427
|
-
Partial<Record<AssetPair, MarketPrice | CollateralPrice>>,
|
|
428
|
-
never
|
|
429
|
-
>;
|
|
430
|
-
'prices/:assetPairId': TradingApiEndpoint<
|
|
431
|
-
MarketPrice | CollateralPrice | undefined,
|
|
432
|
-
{
|
|
433
|
-
assetPairId: string;
|
|
434
|
-
},
|
|
435
|
-
'price'
|
|
436
|
-
>;
|
|
437
410
|
// Bridge deposits and withdrawals for a wallet. Rows are registered by the
|
|
438
411
|
// Reya app when it broadcasts the source-chain transaction, and reconciled
|
|
439
412
|
// by the pending-transaction cron -- nothing else writes them, which is why
|
|
@@ -517,17 +490,6 @@ export type TradingApiSource = {
|
|
|
517
490
|
},
|
|
518
491
|
never
|
|
519
492
|
>;
|
|
520
|
-
dynamicDepthConfig: TradingApiEndpoint<
|
|
521
|
-
{
|
|
522
|
-
markets: {
|
|
523
|
-
marketId: number;
|
|
524
|
-
baseDepth: number;
|
|
525
|
-
thresholds: number[];
|
|
526
|
-
ratios: number[];
|
|
527
|
-
}[];
|
|
528
|
-
},
|
|
529
|
-
never
|
|
530
|
-
>;
|
|
531
493
|
'wallet/:address/configuration': TradingApiEndpoint<
|
|
532
494
|
{
|
|
533
495
|
tier_id: Decimal;
|
|
@@ -783,28 +745,6 @@ export type BalanceStreamMessage = GenericReplicationMessage<
|
|
|
783
745
|
'transfers'
|
|
784
746
|
>;
|
|
785
747
|
|
|
786
|
-
type positions_migration = {
|
|
787
|
-
market_id: Decimal;
|
|
788
|
-
account_id: Decimal;
|
|
789
|
-
base: Decimal | null;
|
|
790
|
-
realized_pnl: Decimal | null;
|
|
791
|
-
last_price: Decimal | null;
|
|
792
|
-
last_price_timestamp: Decimal | null;
|
|
793
|
-
funding_value: Decimal | null;
|
|
794
|
-
base_multiplier: Decimal | null;
|
|
795
|
-
adl_unwind_price: Decimal | null;
|
|
796
|
-
average_entry_funding_value: Decimal | null;
|
|
797
|
-
session: Decimal | null;
|
|
798
|
-
exchange_id: Decimal | null;
|
|
799
|
-
transaction_hash: string;
|
|
800
|
-
block_timestamp: Decimal | null;
|
|
801
|
-
block_number: Decimal;
|
|
802
|
-
unique_id: bigint | null;
|
|
803
|
-
event_sequence_number: bigint | null;
|
|
804
|
-
referrer_account_id: Decimal | null;
|
|
805
|
-
referrer_fee_credit: Decimal | null;
|
|
806
|
-
};
|
|
807
|
-
|
|
808
748
|
type execution_bust = {
|
|
809
749
|
event_sequence_number: bigint;
|
|
810
750
|
market_id: Decimal;
|
|
@@ -863,11 +803,6 @@ type ReplicationMessage =
|
|
|
863
803
|
// PRO-126 Bug 7: `ConditionalOrdersSensitive` replication channel
|
|
864
804
|
// retired with the rest of the `ConditionalOrders` DB pipeline.
|
|
865
805
|
// Order data flows through the ME's `{orders}:changes` stream.
|
|
866
|
-
| GenericReplicationMessage<
|
|
867
|
-
// Todo: remove after migration
|
|
868
|
-
positions_migration,
|
|
869
|
-
'positions_migration'
|
|
870
|
-
>
|
|
871
806
|
| GenericReplicationMessage<spot_execution, 'spot_executions'>
|
|
872
807
|
| GenericReplicationMessage<Order, 'orders'>
|
|
873
808
|
| GenericReplicationMessage<execution_bust, 'execution_busts'>
|
package/src/types.ts
CHANGED
|
@@ -453,16 +453,6 @@ export type GetBalancesForBridgeArgs = {
|
|
|
453
453
|
walletAddress: string;
|
|
454
454
|
};
|
|
455
455
|
|
|
456
|
-
export type DepthSimulationState = {
|
|
457
|
-
spotPrice: number;
|
|
458
|
-
depthFactor: number;
|
|
459
|
-
maxOrderSizeLong: number;
|
|
460
|
-
maxOrderSizeShort: number;
|
|
461
|
-
netExposure: number;
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
export type GetLpPoolDepthChartSimulationDataResult = DepthSimulationState;
|
|
465
|
-
|
|
466
456
|
export type SignatureData = {
|
|
467
457
|
signature: string;
|
|
468
458
|
timestamp: number;
|
package/src/utils/calculate.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { mergeMap } from './struct';
|
|
2
2
|
import BigNumber from 'bignumber.js';
|
|
3
3
|
|
|
4
|
-
export const POOL_IMR = 9.1;
|
|
5
|
-
|
|
6
4
|
type MarketId = string;
|
|
7
5
|
type Address = string;
|
|
8
6
|
|
|
@@ -53,148 +51,6 @@ export const calculateMaxWithdraw = ({
|
|
|
53
51
|
return Math.max(0, Math.min(realBalance, maxFromMargin));
|
|
54
52
|
};
|
|
55
53
|
|
|
56
|
-
export const calculateMaxExposure = ({
|
|
57
|
-
poolBalance,
|
|
58
|
-
riskFactor,
|
|
59
|
-
poolIMR = POOL_IMR,
|
|
60
|
-
}: {
|
|
61
|
-
poolBalance: number;
|
|
62
|
-
poolIMR?: number;
|
|
63
|
-
riskFactor: number;
|
|
64
|
-
}) => poolBalance / (poolIMR * Math.sqrt(riskFactor));
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Splits a trade exposure into rebalancing and unbalancing portions relative to pool net exposure.
|
|
68
|
-
* Rebalancing reduces pool's absolute exposure; unbalancing increases it.
|
|
69
|
-
*/
|
|
70
|
-
export const splitTradeExposure = (
|
|
71
|
-
tradeExposure: number,
|
|
72
|
-
poolNetExposure: number,
|
|
73
|
-
): { rebalancing: number; unbalancing: number } => {
|
|
74
|
-
if (tradeExposure === 0) {
|
|
75
|
-
return { rebalancing: 0, unbalancing: 0 };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// If pool has no exposure, entire trade is unbalancing
|
|
79
|
-
if (poolNetExposure === 0) {
|
|
80
|
-
return { rebalancing: 0, unbalancing: tradeExposure };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Opposite sign means trade increases pool exposure → pure unbalancing
|
|
84
|
-
const sameSign =
|
|
85
|
-
(poolNetExposure > 0 && tradeExposure > 0) ||
|
|
86
|
-
(poolNetExposure < 0 && tradeExposure < 0);
|
|
87
|
-
if (!sameSign) {
|
|
88
|
-
return { rebalancing: 0, unbalancing: tradeExposure };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Same sign: trade reduces pool exposure (pool takes opposite side)
|
|
92
|
-
const absPool = Math.abs(poolNetExposure);
|
|
93
|
-
const absTrade = Math.abs(tradeExposure);
|
|
94
|
-
|
|
95
|
-
if (absTrade <= absPool) {
|
|
96
|
-
// Pure rebalancing — trade doesn't cross zero
|
|
97
|
-
return { rebalancing: tradeExposure, unbalancing: 0 };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Mixed: rebalance up to pool exposure, then unbalance the rest
|
|
101
|
-
const rebalancing = poolNetExposure;
|
|
102
|
-
const unbalancing = tradeExposure - rebalancing;
|
|
103
|
-
return { rebalancing, unbalancing };
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Computes the new log price multiplier (logF) after a trade, handling both
|
|
108
|
-
* rebalancing and unbalancing portions.
|
|
109
|
-
*
|
|
110
|
-
* Mirrors on-chain logic in PriceMultiplier.sol:computeNewLogPriceMultiplier.
|
|
111
|
-
*/
|
|
112
|
-
export const computeNewLogF = ({
|
|
113
|
-
currentLogF,
|
|
114
|
-
tradeExposure,
|
|
115
|
-
poolNetExposure,
|
|
116
|
-
depthFactor,
|
|
117
|
-
maxExposure,
|
|
118
|
-
}: {
|
|
119
|
-
currentLogF: number;
|
|
120
|
-
tradeExposure: number;
|
|
121
|
-
poolNetExposure: number;
|
|
122
|
-
depthFactor: number;
|
|
123
|
-
maxExposure: number;
|
|
124
|
-
}): number => {
|
|
125
|
-
if (tradeExposure === 0) return currentLogF;
|
|
126
|
-
|
|
127
|
-
const { rebalancing, unbalancing } = splitTradeExposure(
|
|
128
|
-
tradeExposure,
|
|
129
|
-
poolNetExposure,
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
// Case 1: Has unbalancing portion (includes mixed trades)
|
|
133
|
-
if (unbalancing !== 0) {
|
|
134
|
-
// If also rebalancing, it brings logF to 0 by definition (trade crosses zero)
|
|
135
|
-
const startLogF = rebalancing === 0 ? currentLogF : 0;
|
|
136
|
-
return startLogF + unbalancing / (depthFactor * maxExposure);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Case 2: Only rebalancing
|
|
140
|
-
// Both rebalancing and poolNetExposure have the same sign, so ratio is in [0, 1]
|
|
141
|
-
if (rebalancing !== 0 && poolNetExposure !== 0) {
|
|
142
|
-
const ratio = rebalancing / poolNetExposure;
|
|
143
|
-
return currentLogF * (1 - ratio);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return currentLogF;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Estimates the execution price for a trade using the exponential logPriceMultiplier formula.
|
|
151
|
-
*
|
|
152
|
-
* Mirrors on-chain logic:
|
|
153
|
-
* 1. computeNewLogPriceMultiplier (PriceMultiplier.sol)
|
|
154
|
-
* 2. computeAmmPrice: ammPrice = oraclePrice * exp(logF)
|
|
155
|
-
* 3. computeSignedSpread: executionPrice = ammPrice * (1 + signedSpread)
|
|
156
|
-
*
|
|
157
|
-
* @audit spreadDiscount not applied — known limitation, to be addressed later
|
|
158
|
-
* @audit priceSpacing rounding not applied
|
|
159
|
-
*/
|
|
160
|
-
export const calculateEstimatedExecutionPrice = ({
|
|
161
|
-
oraclePrice,
|
|
162
|
-
currentLogF,
|
|
163
|
-
tradeExposure,
|
|
164
|
-
poolNetExposure,
|
|
165
|
-
depthFactor,
|
|
166
|
-
maxExposure,
|
|
167
|
-
priceSpread,
|
|
168
|
-
}: {
|
|
169
|
-
oraclePrice: number;
|
|
170
|
-
currentLogF: number;
|
|
171
|
-
tradeExposure: number;
|
|
172
|
-
poolNetExposure: number;
|
|
173
|
-
depthFactor: number;
|
|
174
|
-
maxExposure: number;
|
|
175
|
-
priceSpread: number;
|
|
176
|
-
}): number => {
|
|
177
|
-
if (tradeExposure === 0) {
|
|
178
|
-
return oraclePrice * Math.exp(currentLogF);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// 1. Compute new logF after trade impact
|
|
182
|
-
const newLogF = computeNewLogF({
|
|
183
|
-
currentLogF,
|
|
184
|
-
tradeExposure,
|
|
185
|
-
poolNetExposure,
|
|
186
|
-
depthFactor,
|
|
187
|
-
maxExposure,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// 2. Compute AMM price: oraclePrice * exp(newLogF)
|
|
191
|
-
const ammPrice = oraclePrice * Math.exp(newLogF);
|
|
192
|
-
|
|
193
|
-
// 3. Apply signed spread
|
|
194
|
-
const signedSpread = tradeExposure > 0 ? priceSpread : -priceSpread;
|
|
195
|
-
return Math.max(0, ammPrice * (1 + signedSpread));
|
|
196
|
-
};
|
|
197
|
-
|
|
198
54
|
export const calculateTradeFee = ({
|
|
199
55
|
feeRate,
|
|
200
56
|
tradeNotional,
|
|
@@ -203,42 +59,6 @@ export const calculateTradeFee = ({
|
|
|
203
59
|
tradeNotional: number;
|
|
204
60
|
}) => feeRate * Math.abs(tradeNotional);
|
|
205
61
|
|
|
206
|
-
export const calculateSpread = ({
|
|
207
|
-
depthFactor,
|
|
208
|
-
maxExposure,
|
|
209
|
-
oraclePrice,
|
|
210
|
-
poolPrice,
|
|
211
|
-
tradeNotional,
|
|
212
|
-
}: {
|
|
213
|
-
depthFactor: number;
|
|
214
|
-
maxExposure: number;
|
|
215
|
-
oraclePrice: number;
|
|
216
|
-
poolPrice: number;
|
|
217
|
-
tradeNotional: number;
|
|
218
|
-
}) =>
|
|
219
|
-
(poolPrice / oraclePrice) *
|
|
220
|
-
(1 + tradeNotional / (depthFactor * maxExposure) - 1) *
|
|
221
|
-
tradeNotional;
|
|
222
|
-
|
|
223
|
-
export const calculateEstimatedLiquidationPrice = ({
|
|
224
|
-
LMR,
|
|
225
|
-
accountBalance,
|
|
226
|
-
isShort,
|
|
227
|
-
notional,
|
|
228
|
-
oraclePrice,
|
|
229
|
-
}: {
|
|
230
|
-
LMR: number;
|
|
231
|
-
accountBalance: number;
|
|
232
|
-
isShort: boolean;
|
|
233
|
-
notional: number;
|
|
234
|
-
oraclePrice: number;
|
|
235
|
-
}) =>
|
|
236
|
-
Math.max(
|
|
237
|
-
0,
|
|
238
|
-
oraclePrice *
|
|
239
|
-
(1 - ((accountBalance - LMR) / notional) * (isShort ? -1 : 1)),
|
|
240
|
-
);
|
|
241
|
-
|
|
242
62
|
export const calculateLeverage = ({
|
|
243
63
|
imrMultiplier,
|
|
244
64
|
riskFactor,
|
|
@@ -262,212 +82,6 @@ export const calculateRequiredMargin = ({
|
|
|
262
82
|
(Math.abs(currentNotional + tradeNotional) - Math.abs(currentNotional)) /
|
|
263
83
|
leverage;
|
|
264
84
|
|
|
265
|
-
/**
|
|
266
|
-
* Calculates the maximum notional trade size (unsigned, always ≥ 0).
|
|
267
|
-
*
|
|
268
|
-
* Implements the UI quant spec v2 ("Maximum trade") with two cases:
|
|
269
|
-
* Case 1 — Extending: trade same direction as existing position.
|
|
270
|
-
* Case 2 — Reverting: decompose into (a) close position exactly, then
|
|
271
|
-
* (b) extend from zero in the opposite direction.
|
|
272
|
-
*
|
|
273
|
-
* All quadratics use the unsigned convention (t = |tradeNotional|) so the
|
|
274
|
-
* positive root always corresponds to the correct direction.
|
|
275
|
-
*/
|
|
276
|
-
export const calculateMaxOrderSize = ({
|
|
277
|
-
currIMR,
|
|
278
|
-
currNodeBalance,
|
|
279
|
-
currNotional,
|
|
280
|
-
depthFactor,
|
|
281
|
-
feeRate,
|
|
282
|
-
isShortTrade,
|
|
283
|
-
leverage,
|
|
284
|
-
maxExposure,
|
|
285
|
-
oraclePrice,
|
|
286
|
-
poolPrice,
|
|
287
|
-
currentLogF,
|
|
288
|
-
netExposure,
|
|
289
|
-
}: {
|
|
290
|
-
currIMR: number;
|
|
291
|
-
currNodeBalance: number;
|
|
292
|
-
currNotional: number;
|
|
293
|
-
depthFactor: number;
|
|
294
|
-
feeRate: number;
|
|
295
|
-
isShortTrade: boolean;
|
|
296
|
-
leverage: number;
|
|
297
|
-
maxExposure: number;
|
|
298
|
-
oraclePrice: number;
|
|
299
|
-
poolPrice: number;
|
|
300
|
-
currentLogF: number;
|
|
301
|
-
netExposure: number;
|
|
302
|
-
}): number => {
|
|
303
|
-
const D = depthFactor * maxExposure;
|
|
304
|
-
const rho = poolPrice / oraclePrice;
|
|
305
|
-
const tradeSign = isShortTrade ? -1 : 1;
|
|
306
|
-
const s0 = currNotional > 0 ? 1 : currNotional < 0 ? -1 : tradeSign;
|
|
307
|
-
const absN0 = Math.abs(currNotional);
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Solve the extending quadratic (spec §Case 1) for max trade size.
|
|
311
|
-
* a·t² + b·t + c = 0 where a = ρ·γ, b = s₀·(1/lev + fee) + (ρ−1), c = −surplus.
|
|
312
|
-
* Root selection: pick the root whose sign matches s₀ (dir).
|
|
313
|
-
* For rebalancing (a < 0), caps at |NE| (beyond which γ switches to unbalancing).
|
|
314
|
-
*/
|
|
315
|
-
const solveExtending = (
|
|
316
|
-
dir: number,
|
|
317
|
-
surplus: number, // aka, IMRDelta
|
|
318
|
-
rho_: number,
|
|
319
|
-
gamma_: number,
|
|
320
|
-
isRebal: boolean,
|
|
321
|
-
absNE: number,
|
|
322
|
-
): number => {
|
|
323
|
-
if (surplus <= 0) return 0;
|
|
324
|
-
const a = rho_ * gamma_;
|
|
325
|
-
const b = dir * (1 / leverage + feeRate) + (rho_ - 1);
|
|
326
|
-
const c = -surplus;
|
|
327
|
-
// Degenerate: γ = 0 → linear equation b·t + c = 0 (rebalancing with f = 0)
|
|
328
|
-
if (a === 0) {
|
|
329
|
-
if (b === 0) return isRebal ? absNE : 0;
|
|
330
|
-
const t = -c / b;
|
|
331
|
-
if (t * dir <= 0) return isRebal ? absNE : 0;
|
|
332
|
-
return isRebal ? Math.min(Math.abs(t), absNE) : Math.abs(t);
|
|
333
|
-
}
|
|
334
|
-
const disc = b * b - 4 * a * c;
|
|
335
|
-
if (isRebal) {
|
|
336
|
-
if (disc < 0) return absNE;
|
|
337
|
-
const t = (-b + dir * Math.sqrt(disc)) / (2 * a);
|
|
338
|
-
return t * dir <= 0 ? absNE : Math.min(Math.abs(t), absNE);
|
|
339
|
-
}
|
|
340
|
-
if (disc < 0)
|
|
341
|
-
throw new Error(`calculateMaxOrderSize: negative discriminant (${disc})`);
|
|
342
|
-
const t = (-b + dir * Math.sqrt(disc)) / (2 * a);
|
|
343
|
-
if (t * dir <= 0)
|
|
344
|
-
throw new Error(`calculateMaxOrderSize: no valid root (${t})`);
|
|
345
|
-
return Math.abs(t);
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
// Determine γ for a trade of given direction against the current pool state
|
|
349
|
-
const getGamma = (dir: number, logF: number, ne: number) => {
|
|
350
|
-
const isRebal = ne !== 0 && dir === Math.sign(ne);
|
|
351
|
-
const gamma = isRebal ? -logF / ne : 1 / D;
|
|
352
|
-
return { gamma, isRebal };
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Like solveExtending, but when the rebalancing leg is capped at |NE|
|
|
357
|
-
* (pool-flip), continues with the unbalancing leg from NE = 0.
|
|
358
|
-
*
|
|
359
|
-
* For the exact rebalancing trade to NE = 0:
|
|
360
|
-
* execRatio = ρ·exp(γ·dir·|NE|) = exp(f)·exp(−f) = 1
|
|
361
|
-
* so only fee and IMR consume margin. Then extend with γ = 1/D and ρ' = 1.
|
|
362
|
-
*/
|
|
363
|
-
const extendWithFlip = (
|
|
364
|
-
dir: number,
|
|
365
|
-
surplus: number,
|
|
366
|
-
rho_: number,
|
|
367
|
-
gamma_: number,
|
|
368
|
-
isRebal: boolean,
|
|
369
|
-
absNE: number,
|
|
370
|
-
): number => {
|
|
371
|
-
const result = solveExtending(dir, surplus, rho_, gamma_, isRebal, absNE);
|
|
372
|
-
if (!isRebal || result < absNE || absNE === 0) return result;
|
|
373
|
-
|
|
374
|
-
// Pool-flip: rebalancing consumed absNE, continue unbalancing from NE = 0
|
|
375
|
-
const surplusAfter = surplus - absNE * (1 / leverage + feeRate);
|
|
376
|
-
if (surplusAfter <= 0) return absNE;
|
|
377
|
-
return absNE + solveExtending(dir, surplusAfter, 1, 1 / D, false, 0);
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// ── Case 1: Extending ──────────────────────────────────────────────────
|
|
381
|
-
if (tradeSign === s0) {
|
|
382
|
-
const { gamma, isRebal } = getGamma(tradeSign, currentLogF, netExposure);
|
|
383
|
-
return extendWithFlip(
|
|
384
|
-
s0,
|
|
385
|
-
currNodeBalance - currIMR,
|
|
386
|
-
rho,
|
|
387
|
-
gamma,
|
|
388
|
-
isRebal,
|
|
389
|
-
Math.abs(netExposure),
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// ── Case 2: Reverting — close N₀, then re-extend ──────────────────────
|
|
394
|
-
|
|
395
|
-
// Step 1: Close N₀ entirely (spec §Case 2, Step 1)
|
|
396
|
-
const closeTrade = -currNotional;
|
|
397
|
-
const closeDir = -s0;
|
|
398
|
-
const { gamma: gammaC, isRebal: closeIsRebal } = getGamma(
|
|
399
|
-
closeDir,
|
|
400
|
-
currentLogF,
|
|
401
|
-
netExposure,
|
|
402
|
-
);
|
|
403
|
-
const closeFlipsPool = closeIsRebal && absN0 > Math.abs(netExposure);
|
|
404
|
-
|
|
405
|
-
// Exec price ratio: ρ·exp(γ_c·closeTrade) for non-flip, dedicated formula for flip
|
|
406
|
-
const closeExecRatio = closeFlipsPool
|
|
407
|
-
? Math.exp((closeTrade - netExposure) / D)
|
|
408
|
-
: rho * Math.exp(gammaC * closeTrade);
|
|
409
|
-
|
|
410
|
-
const balClose =
|
|
411
|
-
currNodeBalance - feeRate * absN0 + closeTrade * (1 - closeExecRatio);
|
|
412
|
-
const imrClose = currIMR - absN0 / leverage;
|
|
413
|
-
const surplusClose = balClose - imrClose;
|
|
414
|
-
|
|
415
|
-
// Insolvency: can't fully close without going under margin.
|
|
416
|
-
// Solve partial-close quadratic (spec appendix): b has flipped feeRate sign.
|
|
417
|
-
// Falls back to full close (|N₀|) when no valid partial-close root exists.
|
|
418
|
-
if (surplusClose <= 0) {
|
|
419
|
-
const IMRDelta = currNodeBalance - currIMR;
|
|
420
|
-
const { gamma: gammaP } = getGamma(closeDir, currentLogF, netExposure);
|
|
421
|
-
const a = rho * gammaP;
|
|
422
|
-
const b = s0 * (1 / leverage - feeRate) + (rho - 1);
|
|
423
|
-
const c = -IMRDelta;
|
|
424
|
-
|
|
425
|
-
if (a === 0) {
|
|
426
|
-
if (b === 0) return absN0;
|
|
427
|
-
const t = -c / b;
|
|
428
|
-
if (t * closeDir <= 0) return absN0;
|
|
429
|
-
return Math.min(Math.abs(t), absN0);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const disc = b * b - 4 * a * c;
|
|
433
|
-
if (disc < 0) return absN0;
|
|
434
|
-
const t = (-b + closeDir * Math.sqrt(disc)) / (2 * a);
|
|
435
|
-
if (t * closeDir <= 0) return absN0;
|
|
436
|
-
return Math.min(Math.abs(t), absN0);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Step 2: Extend from zero in -s₀ direction with post-close pool state
|
|
440
|
-
const neAfter = netExposure + currNotional;
|
|
441
|
-
let fPrime: number;
|
|
442
|
-
if (closeFlipsPool) {
|
|
443
|
-
fPrime = (closeTrade - netExposure) / D;
|
|
444
|
-
} else if (closeIsRebal) {
|
|
445
|
-
fPrime = currentLogF * (1 + currNotional / netExposure);
|
|
446
|
-
} else {
|
|
447
|
-
fPrime = currentLogF + closeTrade / D;
|
|
448
|
-
}
|
|
449
|
-
const rhoPrime = Math.exp(fPrime);
|
|
450
|
-
|
|
451
|
-
const extDir = -s0;
|
|
452
|
-
const { gamma: gammaPrime, isRebal: isExtRebal } = getGamma(
|
|
453
|
-
extDir,
|
|
454
|
-
fPrime,
|
|
455
|
-
neAfter,
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
return (
|
|
459
|
-
absN0 +
|
|
460
|
-
extendWithFlip(
|
|
461
|
-
extDir,
|
|
462
|
-
surplusClose,
|
|
463
|
-
rhoPrime,
|
|
464
|
-
gammaPrime,
|
|
465
|
-
isExtRebal,
|
|
466
|
-
Math.abs(neAfter),
|
|
467
|
-
)
|
|
468
|
-
);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
85
|
export const calculateIMR = ({
|
|
472
86
|
marketLeverages,
|
|
473
87
|
marketPrices,
|
|
@@ -520,29 +134,6 @@ export const calculateLMR = ({
|
|
|
520
134
|
: sum;
|
|
521
135
|
}, 0);
|
|
522
136
|
|
|
523
|
-
// if account balance is below LMR position is liquidable
|
|
524
|
-
|
|
525
|
-
export const calculateMarginRatio = ({
|
|
526
|
-
LMR,
|
|
527
|
-
totalBalance,
|
|
528
|
-
tradeCost,
|
|
529
|
-
}: {
|
|
530
|
-
LMR: number;
|
|
531
|
-
totalBalance: number;
|
|
532
|
-
tradeCost: number;
|
|
533
|
-
}) => {
|
|
534
|
-
if (LMR === 0) {
|
|
535
|
-
return 0;
|
|
536
|
-
}
|
|
537
|
-
if (tradeCost > totalBalance) {
|
|
538
|
-
return 1;
|
|
539
|
-
}
|
|
540
|
-
if (totalBalance === tradeCost) {
|
|
541
|
-
return Infinity;
|
|
542
|
-
}
|
|
543
|
-
return Math.min(1, LMR / (totalBalance - tradeCost));
|
|
544
|
-
};
|
|
545
|
-
|
|
546
137
|
export const calculateTotalMargin = ({
|
|
547
138
|
marketsTrackers,
|
|
548
139
|
accountBalances,
|
|
@@ -703,94 +294,6 @@ export const calculatePositionAdjustedQty = (
|
|
|
703
294
|
return origQty.times(marketBaseMultiplier).div(positionBaseMultiplier);
|
|
704
295
|
};
|
|
705
296
|
|
|
706
|
-
/**
|
|
707
|
-
* Linearly interpolate a depth ratio from a (thresholds, ratios) curve.
|
|
708
|
-
* The curve implicitly starts at (exposure=0, ratio=1.0).
|
|
709
|
-
* - Between thresholds: linear interpolation
|
|
710
|
-
* - Above last threshold: last ratio (flat extrapolation)
|
|
711
|
-
*
|
|
712
|
-
* @param thresholds - Sorted ascending exposure thresholds in notional USD (must be non-empty)
|
|
713
|
-
* @param ratios - Depth ratio at each threshold (same length as thresholds, must be non-empty)
|
|
714
|
-
* @param exposure - Absolute exposure notional (must be >= 0)
|
|
715
|
-
* @throws if thresholds or ratios are empty, or if they have different lengths
|
|
716
|
-
*/
|
|
717
|
-
export const interpolateDepthRatio = (
|
|
718
|
-
thresholds: number[],
|
|
719
|
-
ratios: number[],
|
|
720
|
-
exposure: number,
|
|
721
|
-
): number => {
|
|
722
|
-
if (exposure < 0) {
|
|
723
|
-
throw new Error('interpolateDepthRatio: exposure must be >= 0');
|
|
724
|
-
}
|
|
725
|
-
if (thresholds.length === 0 || ratios.length === 0) {
|
|
726
|
-
throw new Error(
|
|
727
|
-
'interpolateDepthRatio: thresholds and ratios must be non-empty',
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
if (thresholds.length !== ratios.length) {
|
|
731
|
-
throw new Error(
|
|
732
|
-
'interpolateDepthRatio: thresholds and ratios must have equal length',
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
const allThresholds = [0, ...thresholds];
|
|
737
|
-
const allRatios = [1.0, ...ratios];
|
|
738
|
-
|
|
739
|
-
if (exposure === 0) return 1.0;
|
|
740
|
-
if (exposure >= allThresholds[allThresholds.length - 1]) {
|
|
741
|
-
return allRatios[allRatios.length - 1];
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
for (let i = 0; i < allThresholds.length - 1; i++) {
|
|
745
|
-
if (exposure <= allThresholds[i + 1]) {
|
|
746
|
-
const t =
|
|
747
|
-
(exposure - allThresholds[i]) /
|
|
748
|
-
(allThresholds[i + 1] - allThresholds[i]);
|
|
749
|
-
return allRatios[i] + t * (allRatios[i + 1] - allRatios[i]);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
throw new Error('interpolateDepthRatio: unreachable');
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Calculate the dynamic depth factor for a trade given pool state and config.
|
|
758
|
-
*
|
|
759
|
-
* The pool takes the opposite side of the trade, so:
|
|
760
|
-
* poolNotionalAfterTrade = currentPoolNotional - signedTradeNotional
|
|
761
|
-
*
|
|
762
|
-
* The depth ratio is interpolated from the exposure curve, then multiplied
|
|
763
|
-
* by baseDepth to produce the final depth factor.
|
|
764
|
-
*
|
|
765
|
-
* @param baseDepth - Base depth multiplier for this market
|
|
766
|
-
* @param thresholds - Sorted ascending exposure thresholds (notional USD)
|
|
767
|
-
* @param ratios - Depth ratio at each threshold
|
|
768
|
-
* @param currentPoolNotional - Pool's current notional in this market (signed)
|
|
769
|
-
* @param signedTradeNotional - Trade notional (positive=buy, negative=sell)
|
|
770
|
-
*/
|
|
771
|
-
export const calculateDynamicDepthFactor = ({
|
|
772
|
-
baseDepth,
|
|
773
|
-
thresholds,
|
|
774
|
-
ratios,
|
|
775
|
-
currentPoolNotional,
|
|
776
|
-
signedTradeNotional,
|
|
777
|
-
}: {
|
|
778
|
-
baseDepth: number;
|
|
779
|
-
thresholds: number[];
|
|
780
|
-
ratios: number[];
|
|
781
|
-
currentPoolNotional: number;
|
|
782
|
-
signedTradeNotional: number;
|
|
783
|
-
}): number => {
|
|
784
|
-
const poolNotionalAfterTrade = currentPoolNotional - signedTradeNotional;
|
|
785
|
-
const absExposureNotional = Math.abs(poolNotionalAfterTrade);
|
|
786
|
-
const depthRatio = interpolateDepthRatio(
|
|
787
|
-
thresholds,
|
|
788
|
-
ratios,
|
|
789
|
-
absExposureNotional,
|
|
790
|
-
);
|
|
791
|
-
return depthRatio * baseDepth;
|
|
792
|
-
};
|
|
793
|
-
|
|
794
297
|
export const calculatePositionAdlPnL = ({
|
|
795
298
|
marketTrackerAdlUnwindPrice,
|
|
796
299
|
marketTrackerBaseMultiplier,
|