@reyaxyz/api-sdk 0.18.0 → 0.18.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.
Files changed (38) hide show
  1. package/dist/clients/api-client.js +1 -1
  2. package/dist/clients/api-client.js.map +1 -1
  3. package/dist/clients/helpers/exposure.calculator.js +436 -0
  4. package/dist/clients/helpers/exposure.calculator.js.map +1 -0
  5. package/dist/clients/helpers/number.js +13 -0
  6. package/dist/clients/helpers/number.js.map +1 -0
  7. package/dist/clients/helpers/trade.simulation.types.js +3 -0
  8. package/dist/clients/helpers/trade.simulation.types.js.map +1 -0
  9. package/dist/clients/modules/account.js +11 -0
  10. package/dist/clients/modules/account.js.map +1 -1
  11. package/dist/clients/modules/trade.simulation.js +40 -38
  12. package/dist/clients/modules/trade.simulation.js.map +1 -1
  13. package/dist/clients/types.js.map +1 -1
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/types/clients/helpers/exposure.calculator.d.ts +58 -0
  17. package/dist/types/clients/helpers/exposure.calculator.d.ts.map +1 -0
  18. package/dist/types/clients/helpers/number.d.ts +3 -0
  19. package/dist/types/clients/helpers/number.d.ts.map +1 -0
  20. package/dist/types/clients/helpers/trade.simulation.types.d.ts +104 -0
  21. package/dist/types/clients/helpers/trade.simulation.types.d.ts.map +1 -0
  22. package/dist/types/clients/modules/account.d.ts +3 -1
  23. package/dist/types/clients/modules/account.d.ts.map +1 -1
  24. package/dist/types/clients/modules/trade.simulation.d.ts +3 -0
  25. package/dist/types/clients/modules/trade.simulation.d.ts.map +1 -1
  26. package/dist/types/clients/types.d.ts +5 -1
  27. package/dist/types/clients/types.d.ts.map +1 -1
  28. package/dist/types/index.d.ts +2 -0
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/package.json +4 -3
  31. package/src/clients/api-client.ts +1 -1
  32. package/src/clients/helpers/exposure.calculator.ts +792 -0
  33. package/src/clients/helpers/number.ts +8 -0
  34. package/src/clients/helpers/trade.simulation.types.ts +115 -0
  35. package/src/clients/modules/account.ts +11 -0
  36. package/src/clients/modules/trade.simulation.ts +103 -44
  37. package/src/clients/types.ts +8 -1
  38. package/src/index.ts +2 -0
@@ -0,0 +1,792 @@
1
+ import BigNumber from 'bignumber.js';
2
+ import {
3
+ AccountAssetBalance,
4
+ CollateralInfo,
5
+ ExchangeInfo,
6
+ ExposureCommandState,
7
+ MarginInfo,
8
+ MarketConfiguration,
9
+ MarketStorage,
10
+ PositionInfo,
11
+ PositionInfoMarketConfiguration,
12
+ RiskMatrix,
13
+ RiskMultipliersConfiguration,
14
+ } from './trade.simulation.types';
15
+ import { amountNormalizer } from './number';
16
+
17
+ export class ExposureCommand {
18
+ rootCollateralPoolId: number;
19
+ oraclePrice: number;
20
+ rate: number;
21
+ accountBalancePerAsset: AccountAssetBalance[];
22
+ groupedByCollateral: Record<string, AccountAssetBalance>;
23
+ riskMultipliers: RiskMultipliersConfiguration;
24
+ riskMatrices: RiskMatrix[];
25
+ exchangeInfoPerAsset: ExchangeInfo[];
26
+ positionInfoMarketConfiguration: PositionInfoMarketConfiguration[];
27
+ uniqueTokenAddresses: string[];
28
+ uniqueQuoteCollaterals: string[];
29
+ tokenMarginInfoPerAsset: MarginInfo[];
30
+ realizedPnLSum: BigNumber;
31
+ unrealizedPnLSum: BigNumber;
32
+ constructor(
33
+ rootCollateralPoolId: number,
34
+ oraclePrice: number,
35
+ rate: number,
36
+ accountBalancePerAsset: AccountAssetBalance[],
37
+ groupedByCollateral: Record<string, AccountAssetBalance>,
38
+ riskMultipliers: RiskMultipliersConfiguration,
39
+ riskMatrices: RiskMatrix[],
40
+ exchangeInfoPerAsset: ExchangeInfo[],
41
+ positionInfoMarketConfiguration: PositionInfoMarketConfiguration[],
42
+ uniqueTokenAddresses: string[],
43
+ uniqueQuoteCollaterals: string[],
44
+ tokenMarginInfoPerAsset: MarginInfo[],
45
+ realizedPnLSum: BigNumber,
46
+ unrealizedPnLSum: BigNumber,
47
+ ) {
48
+ this.rootCollateralPoolId = rootCollateralPoolId;
49
+ this.oraclePrice = oraclePrice;
50
+ this.rate = rate;
51
+ this.accountBalancePerAsset = accountBalancePerAsset;
52
+ this.groupedByCollateral = groupedByCollateral;
53
+ this.riskMultipliers = riskMultipliers;
54
+ this.riskMatrices = riskMatrices;
55
+ this.exchangeInfoPerAsset = exchangeInfoPerAsset;
56
+ this.positionInfoMarketConfiguration = positionInfoMarketConfiguration;
57
+ this.uniqueTokenAddresses = uniqueTokenAddresses;
58
+ this.uniqueQuoteCollaterals = uniqueQuoteCollaterals;
59
+ this.tokenMarginInfoPerAsset = tokenMarginInfoPerAsset;
60
+ this.realizedPnLSum = realizedPnLSum;
61
+ this.unrealizedPnLSum = unrealizedPnLSum;
62
+ }
63
+
64
+ getState(): ExposureCommandState {
65
+ return {
66
+ rootCollateralPoolId: this.rootCollateralPoolId,
67
+ oraclePrice: this.oraclePrice,
68
+ rate: this.rate,
69
+ accountBalancePerAsset: this.accountBalancePerAsset,
70
+ groupedByCollateral: this.groupedByCollateral,
71
+ riskMultipliers: this.riskMultipliers,
72
+ riskMatrices: this.riskMatrices,
73
+ exchangeInfoPerAsset: this.exchangeInfoPerAsset,
74
+ positionInfoMarketConfiguration: this.positionInfoMarketConfiguration,
75
+ uniqueTokenAddresses: this.uniqueTokenAddresses,
76
+ uniqueQuoteCollaterals: this.uniqueQuoteCollaterals,
77
+ tokenMarginInfoPerAsset: this.tokenMarginInfoPerAsset,
78
+ realizedPnLSum: this.realizedPnLSum,
79
+ unrealizedPnLSum: this.unrealizedPnLSum,
80
+ };
81
+ }
82
+
83
+ get getUsdNodeMarginInfo() {
84
+ return ExposureCommand.getUsdNodeMarginInfo(
85
+ this.rootCollateralPoolId,
86
+ this.uniqueTokenAddresses,
87
+ this.exchangeInfoPerAsset,
88
+ this.tokenMarginInfoPerAsset,
89
+ );
90
+ }
91
+
92
+ get balancePerAsset() {
93
+ return this.tokenMarginInfoPerAsset;
94
+ }
95
+
96
+ getUsdNodeMarginInfoPostTrade(
97
+ positionAmount: number,
98
+ collateralAddress: string,
99
+ marketConfiguration: MarketConfiguration,
100
+ ) {
101
+ const positionInfoMarketConfiguration = [
102
+ ...this.positionInfoMarketConfiguration,
103
+ ];
104
+
105
+ // Check if the market_id already exists in the array
106
+ const existingConfigIndex = positionInfoMarketConfiguration.findIndex(
107
+ (config) =>
108
+ config.market_id ===
109
+ BigNumber(String(marketConfiguration.market_id)).toNumber(),
110
+ );
111
+
112
+ if (existingConfigIndex !== -1) {
113
+ // If it exists, update the amount
114
+ positionInfoMarketConfiguration[existingConfigIndex].base = BigNumber(
115
+ positionInfoMarketConfiguration[existingConfigIndex].base,
116
+ ).plus(positionAmount);
117
+ } else {
118
+ // If it doesn't exist, add a new element
119
+ positionInfoMarketConfiguration.push({
120
+ base: BigNumber(positionAmount),
121
+ realized_pnl: BigNumber(0),
122
+ last_price: BigNumber(0),
123
+ last_timestamp: BigNumber(0),
124
+ funding_value: BigNumber(0),
125
+ base_multiplier: BigNumber(0),
126
+ adl_unwind_price: BigNumber(0),
127
+ market_id: BigNumber(String(marketConfiguration.market_id)).toNumber(),
128
+ market_configuration: marketConfiguration,
129
+ });
130
+ }
131
+
132
+ const uniqueQuoteCollaterals = new Set(this.uniqueQuoteCollaterals);
133
+ uniqueQuoteCollaterals.add(collateralAddress);
134
+
135
+ const tokenMarginInfoPerAsset =
136
+ ExposureCommand.calculateTokenMarginInfoPerAsset(
137
+ this.groupedByCollateral,
138
+ this.rootCollateralPoolId,
139
+ this.riskMatrices,
140
+ this.riskMultipliers,
141
+ uniqueQuoteCollaterals,
142
+ this.realizedPnLSum,
143
+ this.unrealizedPnLSum,
144
+ positionInfoMarketConfiguration,
145
+ this.oraclePrice,
146
+ );
147
+
148
+ const uniqueTokenAddresses = [...this.uniqueTokenAddresses];
149
+ if (!this.uniqueTokenAddresses.includes(collateralAddress)) {
150
+ uniqueTokenAddresses.push(collateralAddress);
151
+ }
152
+
153
+ return ExposureCommand.getUsdNodeMarginInfo(
154
+ this.rootCollateralPoolId,
155
+ uniqueTokenAddresses,
156
+ this.exchangeInfoPerAsset,
157
+ tokenMarginInfoPerAsset,
158
+ );
159
+ }
160
+
161
+ static calculateTokenMarginInfoPerAsset(
162
+ groupedByCollateral: Record<string, AccountAssetBalance>,
163
+ rootCollateralPoolId: number,
164
+ riskMatrices: RiskMatrix[],
165
+ riskMultipliers: RiskMultipliersConfiguration,
166
+ uniqueQuoteCollaterals: Set<string>,
167
+ realizedPnLSum: BigNumber,
168
+ unrealizedPnLSum: BigNumber,
169
+ positionInfoMarketConfiguration: PositionInfoMarketConfiguration[],
170
+ oraclePrice: number,
171
+ ) {
172
+ const tokenMarginInfoPerAsset: MarginInfo[] = [];
173
+
174
+ const uniqueQuoteTokens: string[] = Array.from(uniqueQuoteCollaterals);
175
+
176
+ const tokenUnion = new Set([
177
+ ...Object.keys(groupedByCollateral),
178
+ ...uniqueQuoteTokens,
179
+ ]); // get unique union of those arrays
180
+ const uniqueTokenAddresses: string[] = Array.from(tokenUnion);
181
+
182
+ for (const token of uniqueTokenAddresses) {
183
+ tokenMarginInfoPerAsset.push(
184
+ ExposureCommand.getTokenMarginInfo(
185
+ rootCollateralPoolId,
186
+ riskMatrices,
187
+ riskMultipliers,
188
+ ExposureCommand.getCollateralInfo(
189
+ token,
190
+ uniqueQuoteCollaterals.has(token) ? realizedPnLSum : BigNumber(0),
191
+ uniqueQuoteCollaterals.has(token) ? unrealizedPnLSum : BigNumber(0),
192
+ groupedByCollateral[token]?.amount || 0,
193
+ ),
194
+ token,
195
+ positionInfoMarketConfiguration,
196
+ oraclePrice,
197
+ ),
198
+ );
199
+ }
200
+
201
+ return tokenMarginInfoPerAsset;
202
+ }
203
+ static calculateLiquidation(
204
+ globalMarginInfo: MarginInfo,
205
+ oraclePrice: number,
206
+ positionBase: number,
207
+ ): BigNumber {
208
+ const liquidationPrice = BigNumber(oraclePrice).minus(
209
+ BigNumber(globalMarginInfo.marginBalance)
210
+ .minus(globalMarginInfo.liquidationMarginRequirement)
211
+ .div(positionBase),
212
+ );
213
+
214
+ return BigNumber.max(0, liquidationPrice);
215
+ }
216
+
217
+ static calculateImpliedLeverage(
218
+ notionalExposure: number,
219
+ oldIMR: number,
220
+ newIMR: number,
221
+ ): number {
222
+ const changeInImr = BigNumber(newIMR).minus(oldIMR);
223
+
224
+ if (changeInImr.eq(0)) {
225
+ return 0;
226
+ }
227
+ return BigNumber(notionalExposure).div(changeInImr).toNumber();
228
+ }
229
+
230
+ static combineMarginInfo(
231
+ parentMarginInfo: MarginInfo,
232
+ sonMarginInfo: MarginInfo,
233
+ sonParentExchangeInfo: ExchangeInfo,
234
+ ): MarginInfo {
235
+ return {
236
+ assetAddress: parentMarginInfo.assetAddress,
237
+ marginBalance: BigNumber(parentMarginInfo.marginBalance)
238
+ .plus(
239
+ ExposureCommand.exchangeWithPriceHaircut(
240
+ sonMarginInfo.marginBalance,
241
+ sonParentExchangeInfo.price,
242
+ sonParentExchangeInfo.priceHaircut,
243
+ ),
244
+ )
245
+ .toNumber(),
246
+ realBalance: BigNumber(parentMarginInfo.realBalance)
247
+ .plus(
248
+ ExposureCommand.exchangeWithPriceHaircut(
249
+ sonMarginInfo.realBalance,
250
+ sonParentExchangeInfo.price,
251
+ sonParentExchangeInfo.priceHaircut,
252
+ ),
253
+ )
254
+ .toNumber(),
255
+ initialDelta: BigNumber(parentMarginInfo.initialDelta)
256
+ .plus(
257
+ ExposureCommand.exchangeWithPriceHaircut(
258
+ BigNumber.min(
259
+ sonMarginInfo.realBalance,
260
+ sonMarginInfo.initialDelta,
261
+ ).toNumber(),
262
+ sonParentExchangeInfo.price,
263
+ sonParentExchangeInfo.priceHaircut,
264
+ ),
265
+ )
266
+ .toNumber(),
267
+ maintenanceDelta: BigNumber(parentMarginInfo.maintenanceDelta)
268
+ .plus(
269
+ ExposureCommand.exchangeWithPriceHaircut(
270
+ BigNumber.min(
271
+ sonMarginInfo.maintenanceDelta,
272
+ sonMarginInfo.realBalance,
273
+ ).toNumber(),
274
+ sonParentExchangeInfo.price,
275
+ sonParentExchangeInfo.priceHaircut,
276
+ ),
277
+ )
278
+ .toNumber(),
279
+ liquidationDelta: BigNumber(parentMarginInfo.liquidationDelta)
280
+ .plus(
281
+ ExposureCommand.exchangeWithPriceHaircut(
282
+ BigNumber.min(
283
+ sonMarginInfo.liquidationDelta,
284
+ sonMarginInfo.realBalance,
285
+ ).toNumber(),
286
+ sonParentExchangeInfo.price,
287
+ sonParentExchangeInfo.priceHaircut,
288
+ ),
289
+ )
290
+ .toNumber(),
291
+ dutchDelta: BigNumber(parentMarginInfo.dutchDelta)
292
+ .plus(
293
+ ExposureCommand.exchangeWithPriceHaircut(
294
+ BigNumber.min(
295
+ sonMarginInfo.dutchDelta,
296
+ sonMarginInfo.realBalance,
297
+ ).toNumber(),
298
+ sonParentExchangeInfo.price,
299
+ sonParentExchangeInfo.priceHaircut,
300
+ ),
301
+ )
302
+ .toNumber(),
303
+ adlDelta: BigNumber(parentMarginInfo.adlDelta)
304
+ .plus(
305
+ ExposureCommand.exchangeWithPriceHaircut(
306
+ BigNumber.min(
307
+ sonMarginInfo.adlDelta,
308
+ sonMarginInfo.realBalance,
309
+ ).toNumber(),
310
+ sonParentExchangeInfo.price,
311
+ sonParentExchangeInfo.priceHaircut,
312
+ ),
313
+ )
314
+ .toNumber(),
315
+
316
+ initialBufferDelta: BigNumber(parentMarginInfo.initialBufferDelta)
317
+ .plus(
318
+ ExposureCommand.exchangeWithPriceHaircut(
319
+ BigNumber.min(
320
+ sonMarginInfo.initialBufferDelta,
321
+ sonMarginInfo.realBalance,
322
+ ).toNumber(),
323
+ sonParentExchangeInfo.price,
324
+ sonParentExchangeInfo.priceHaircut,
325
+ ),
326
+ )
327
+ .toNumber(),
328
+ liquidationMarginRequirement: BigNumber(
329
+ parentMarginInfo.liquidationMarginRequirement,
330
+ )
331
+ .plus(
332
+ ExposureCommand.exchangeWithPriceHaircut(
333
+ sonMarginInfo.liquidationMarginRequirement,
334
+ sonParentExchangeInfo.price,
335
+ sonParentExchangeInfo.priceHaircut,
336
+ ),
337
+ )
338
+ .toNumber(),
339
+ };
340
+ }
341
+
342
+ static getUsdNodeMarginInfo(
343
+ accountCollateralPoolId: number,
344
+ quoteTokens: string[],
345
+ exchangeInfoPerAsset: ExchangeInfo[],
346
+ marginInfoPerToken: MarginInfo[],
347
+ ) {
348
+ let usdNodeMarginInfo: MarginInfo = {
349
+ assetAddress: '',
350
+ marginBalance: 0,
351
+ realBalance: 0,
352
+ initialDelta: 0,
353
+ maintenanceDelta: 0,
354
+ liquidationDelta: 0,
355
+ dutchDelta: 0,
356
+ adlDelta: 0,
357
+ initialBufferDelta: 0,
358
+ liquidationMarginRequirement: 0,
359
+ };
360
+ for (const quoteToken of quoteTokens) {
361
+ const exchangeInfo = exchangeInfoPerAsset.find((exchangeInfo) => {
362
+ return quoteToken === exchangeInfo.tokenAddress;
363
+ });
364
+
365
+ const marginInfo = marginInfoPerToken.find((marginInfo) => {
366
+ return quoteToken === marginInfo.assetAddress;
367
+ });
368
+
369
+ if (!exchangeInfo || !marginInfo) {
370
+ throw Error('Missing exchangeInfo/marginInfo');
371
+ }
372
+
373
+ usdNodeMarginInfo = ExposureCommand.combineMarginInfo(
374
+ usdNodeMarginInfo,
375
+ marginInfo,
376
+ exchangeInfo,
377
+ );
378
+ }
379
+
380
+ return usdNodeMarginInfo;
381
+ }
382
+ static getCollateralInfo(
383
+ collateralAddress: string,
384
+ realisedPnl: BigNumber,
385
+ unrealizedPnL: BigNumber,
386
+ netDeposits: number,
387
+ ): CollateralInfo {
388
+ return {
389
+ netDeposits: netDeposits,
390
+ marginBalance: BigNumber(netDeposits)
391
+ .plus(realisedPnl)
392
+ .plus(unrealizedPnL)
393
+ .toNumber(),
394
+ realBalance: BigNumber(netDeposits).plus(realisedPnl).toNumber(),
395
+ };
396
+ }
397
+
398
+ static getTokenMarginInfo(
399
+ rootCollateralPoolId: number,
400
+ riskMatrices: RiskMatrix[],
401
+ riskMultipliers: RiskMultipliersConfiguration,
402
+ collateralInfo: CollateralInfo,
403
+ collateralAddress: string,
404
+ positions: PositionInfoMarketConfiguration[],
405
+ oraclePrice: number,
406
+ ): MarginInfo {
407
+ const marginRequirements = {
408
+ liquidationMarginRequirement: 0,
409
+ initialMarginRequirement: 0,
410
+ maintenanceMarginRequirement: 0,
411
+ dutchMarginRequirement: 0,
412
+ adlMarginRequirement: 0,
413
+ initialBufferMarginRequirement: 0,
414
+ };
415
+
416
+ for (const riskMatrix of riskMatrices) {
417
+ const filledExposures = ExposureCommand.getBlockExposures(
418
+ positions,
419
+ oraclePrice,
420
+ );
421
+
422
+ marginRequirements.liquidationMarginRequirement = BigNumber(
423
+ marginRequirements.liquidationMarginRequirement,
424
+ )
425
+ .plus(
426
+ ExposureCommand.computeLiquidationMarginRequirement(
427
+ riskMatrix.matrix,
428
+ filledExposures,
429
+ ),
430
+ )
431
+ .toNumber();
432
+ }
433
+
434
+ // Get the initial margin requirement
435
+ marginRequirements.initialMarginRequirement = amountNormalizer(
436
+ String(riskMultipliers.im_multiplier),
437
+ )
438
+ .multipliedBy(marginRequirements.liquidationMarginRequirement)
439
+ .toNumber();
440
+ // Get the maintenance margin requirement
441
+ marginRequirements.maintenanceMarginRequirement = amountNormalizer(
442
+ String(riskMultipliers.mmr_multiplier),
443
+ )
444
+ .multipliedBy(marginRequirements.liquidationMarginRequirement)
445
+ .toNumber();
446
+ // Get the dutch margin requirement
447
+ marginRequirements.dutchMarginRequirement = amountNormalizer(
448
+ String(riskMultipliers.dutch_multiplier),
449
+ )
450
+ .multipliedBy(marginRequirements.liquidationMarginRequirement)
451
+ .toNumber();
452
+
453
+ // Get the adl margin requirement
454
+ marginRequirements.adlMarginRequirement = amountNormalizer(
455
+ String(riskMultipliers.adl_multiplier),
456
+ )
457
+ .multipliedBy(marginRequirements.liquidationMarginRequirement)
458
+ .toNumber();
459
+
460
+ // Get the initial buffer margin requirement
461
+ marginRequirements.initialBufferMarginRequirement = amountNormalizer(
462
+ String(riskMultipliers.im_buffer_multiplier),
463
+ )
464
+ .multipliedBy(marginRequirements.liquidationMarginRequirement)
465
+ .toNumber();
466
+
467
+ return {
468
+ assetAddress: collateralAddress,
469
+ marginBalance: collateralInfo.marginBalance,
470
+ realBalance: collateralInfo.realBalance,
471
+ initialDelta: BigNumber(collateralInfo.marginBalance)
472
+ .minus(marginRequirements.initialMarginRequirement)
473
+ .toNumber(),
474
+ maintenanceDelta: BigNumber(collateralInfo.marginBalance)
475
+ .minus(marginRequirements.maintenanceMarginRequirement)
476
+ .toNumber(),
477
+ liquidationDelta: BigNumber(collateralInfo.marginBalance)
478
+ .minus(marginRequirements.liquidationMarginRequirement)
479
+ .toNumber(),
480
+ dutchDelta: BigNumber(collateralInfo.marginBalance)
481
+ .minus(marginRequirements.dutchMarginRequirement)
482
+ .toNumber(),
483
+ adlDelta: BigNumber(collateralInfo.marginBalance)
484
+ .minus(marginRequirements.adlMarginRequirement)
485
+ .toNumber(),
486
+ initialBufferDelta: BigNumber(collateralInfo.marginBalance)
487
+ .minus(marginRequirements.initialBufferMarginRequirement)
488
+ .toNumber(),
489
+ liquidationMarginRequirement:
490
+ marginRequirements.liquidationMarginRequirement,
491
+ };
492
+ }
493
+
494
+ static computeLiquidationMarginRequirement(
495
+ matrix: BigNumber[][],
496
+ filledExposures: BigNumber[],
497
+ ): number {
498
+ let lmrFilledSquared = 0;
499
+
500
+ for (let i = 0; i < filledExposures.length; i++) {
501
+ if (BigNumber(filledExposures[i]).eq(0)) {
502
+ continue;
503
+ }
504
+ for (let j = 0; j < filledExposures.length; j++) {
505
+ const riskParam = matrix[i][j];
506
+
507
+ if (BigNumber(filledExposures[j]).eq(0) || BigNumber(riskParam).eq(0)) {
508
+ continue;
509
+ }
510
+
511
+ lmrFilledSquared = BigNumber(lmrFilledSquared)
512
+ .plus(
513
+ BigNumber(filledExposures[i])
514
+ .multipliedBy(filledExposures[j])
515
+ .multipliedBy(riskParam),
516
+ )
517
+ .toNumber();
518
+ }
519
+ }
520
+ return BigNumber(lmrFilledSquared).sqrt().toNumber();
521
+ }
522
+
523
+ static getBlockExposures(
524
+ positions: PositionInfoMarketConfiguration[],
525
+ oraclePrice: number,
526
+ ): BigNumber[] {
527
+ const filledExposures: number[] = [];
528
+
529
+ for (const position of positions) {
530
+ const marketFilledExposure = ExposureCommand.getAccountFilledExposures(
531
+ position,
532
+ position.market_configuration,
533
+ oraclePrice,
534
+ );
535
+ filledExposures[marketFilledExposure.riskMatrixIndex] = BigNumber(
536
+ filledExposures[marketFilledExposure.riskMatrixIndex] || 0,
537
+ )
538
+ .plus(marketFilledExposure.exposure)
539
+ .toNumber();
540
+ }
541
+
542
+ return filledExposures.map((num) => BigNumber(num));
543
+ }
544
+
545
+ static getAccountFilledExposures(
546
+ position: PositionInfo,
547
+ marketConfiguration: MarketConfiguration,
548
+ oraclePrice: number,
549
+ ) {
550
+ const base = position.base;
551
+
552
+ return {
553
+ exposure: BigNumber(oraclePrice).multipliedBy(base),
554
+ riskMatrixIndex: BigNumber(
555
+ String(marketConfiguration.risk_matrix_index),
556
+ ).toNumber(),
557
+ };
558
+ }
559
+
560
+ static computePricePnL(
561
+ openBase: BigNumber,
562
+ openPrice: BigNumber,
563
+ exitPrice: BigNumber,
564
+ ) {
565
+ return BigNumber(
566
+ BigNumber(exitPrice).minus(openPrice).multipliedBy(openBase),
567
+ );
568
+ }
569
+
570
+ static getMarginRatio(marginInfo: MarginInfo) {
571
+ if (marginInfo.liquidationMarginRequirement === 0) {
572
+ return 0;
573
+ }
574
+
575
+ if (marginInfo.marginBalance <= 0) {
576
+ return 1;
577
+ }
578
+
579
+ const health = BigNumber(marginInfo.liquidationMarginRequirement).div(
580
+ marginInfo.marginBalance,
581
+ );
582
+
583
+ if (health.gt(1)) {
584
+ return 1;
585
+ }
586
+ return health.toNumber();
587
+ }
588
+
589
+ static exchangeWithPriceHaircut(
590
+ quantity: number,
591
+ price: number,
592
+ haircut: number,
593
+ ) {
594
+ // For positive quantities, the haircut is `quantity * (1 - haircut)`
595
+ // For negative values, the haircut is `quantity / (1 - haircut)` because a negative value means the haircut should be applied from B to A.
596
+ const calHelper = BigNumber(quantity).gt(0)
597
+ ? BigNumber(1).minus(haircut)
598
+ : BigNumber(1).div(BigNumber(1).minus(haircut));
599
+ const haircutPrice = BigNumber(price).multipliedBy(calHelper);
600
+
601
+ return haircutPrice.multipliedBy(quantity).toNumber();
602
+ }
603
+
604
+ getSlippage(
605
+ deltaBase: number,
606
+ marketConfiguration: MarketConfiguration,
607
+ marketStorage: MarketStorage,
608
+ ): number {
609
+ const deltaExposure = BigNumber(this.oraclePrice)
610
+ .times(deltaBase)
611
+ .toNumber();
612
+
613
+ const riskMatrixIndex = BigNumber(
614
+ String(marketConfiguration.risk_matrix_index),
615
+ ).toNumber();
616
+
617
+ const { maxExposureShort, maxExposureLong, exposures } =
618
+ this.getMaxExposure(marketConfiguration, marketStorage);
619
+
620
+ const netExposure = exposures[riskMatrixIndex].plus(deltaExposure);
621
+ const maxExposure = netExposure.lt(0) ? maxExposureShort : maxExposureLong;
622
+
623
+ return BigNumber(netExposure)
624
+ .negated()
625
+ .div(BigNumber(maxExposure).plus(netExposure))
626
+ .toNumber();
627
+ }
628
+
629
+ getMaxExposure(
630
+ marketConfiguration: MarketConfiguration,
631
+ marketStorage: MarketStorage,
632
+ ) {
633
+ const riskMatrix = this.riskMatrices.find((riskMatrix) => {
634
+ return (
635
+ riskMatrix.risk_block_id ===
636
+ BigNumber(String(marketStorage.risk_block_id)).toNumber()
637
+ );
638
+ });
639
+
640
+ if (!riskMatrix) {
641
+ throw new Error("RiskMatrix Doesn't exist");
642
+ }
643
+
644
+ const riskMatrixIndex = BigNumber(
645
+ String(marketConfiguration.risk_matrix_index),
646
+ ).toNumber();
647
+
648
+ const imrMultiplier = amountNormalizer(
649
+ String(this.riskMultipliers.im_multiplier),
650
+ ).toNumber();
651
+
652
+ const marginInfo = this.tokenMarginInfoPerAsset.find((marginInfo) => {
653
+ return marginInfo.assetAddress === marketStorage.quote_collateral;
654
+ });
655
+
656
+ if (!marginInfo) {
657
+ throw new Error("marginInfo doesn't exist");
658
+ }
659
+
660
+ const exposures = ExposureCommand.getBlockExposures(
661
+ this.positionInfoMarketConfiguration,
662
+ this.oraclePrice,
663
+ );
664
+
665
+ const { maxExposureShort, maxExposureLong } =
666
+ ExposureCommand.computeMaxExposures(
667
+ riskMatrix.matrix,
668
+ exposures,
669
+ marginInfo.liquidationMarginRequirement,
670
+ marginInfo.marginBalance < 0 ? 0 : marginInfo.marginBalance,
671
+ imrMultiplier,
672
+ riskMatrixIndex,
673
+ );
674
+
675
+ return {
676
+ maxExposureShort,
677
+ maxExposureLong,
678
+ exposures,
679
+ };
680
+ }
681
+
682
+ static computeMaxExposures(
683
+ riskMatrix: BigNumber[][],
684
+ exposures: BigNumber[],
685
+ lmr: number,
686
+ balance: number,
687
+ imrMultiplier: number,
688
+ exposureIndex: number,
689
+ ) {
690
+ let b = BigNumber(0);
691
+
692
+ for (let i = 0; i < exposures.length; i++) {
693
+ b = BigNumber(b).plus(
694
+ BigNumber(exposures[i]).multipliedBy(
695
+ BigNumber(riskMatrix[exposureIndex][i]).plus(
696
+ riskMatrix[i][exposureIndex],
697
+ ),
698
+ ),
699
+ );
700
+ }
701
+ const { x1, x2 } = this.solveQuadraticEquation(
702
+ BigNumber(riskMatrix[exposureIndex][exposureIndex]).toNumber(), // changes here
703
+ b.toNumber(),
704
+ this.computeC(lmr, balance, imrMultiplier),
705
+ );
706
+
707
+ const maxShortExposure = BigNumber(x1).plus(exposures[exposureIndex]);
708
+ const maxLongExposure = BigNumber(x2).plus(exposures[exposureIndex]);
709
+
710
+ const availableShortExposure = maxShortExposure.lt(0)
711
+ ? maxShortExposure.negated().toNumber()
712
+ : 0;
713
+
714
+ const availableLongExposure = maxLongExposure.gt(0)
715
+ ? maxLongExposure.toNumber()
716
+ : 0;
717
+
718
+ return {
719
+ maxExposureShort: availableShortExposure,
720
+ maxExposureLong: availableLongExposure,
721
+ };
722
+ }
723
+
724
+ static solveQuadraticEquation(a: number, b: number, c: number) {
725
+ if (BigNumber(a).eq(0)) {
726
+ throw new Error('ZeroQuadraticCoefficient');
727
+ }
728
+
729
+ const delta = BigNumber(b)
730
+ .multipliedBy(b)
731
+ .minus(BigNumber(4).multipliedBy(a).multipliedBy(c));
732
+
733
+ if (delta.lt(0)) {
734
+ throw new Error('ComplexQuadraticRoots(a, b, c)');
735
+ }
736
+
737
+ const rootDelta = delta.sqrt();
738
+
739
+ const x1 = BigNumber(b)
740
+ .multipliedBy(-1)
741
+ .minus(rootDelta)
742
+ .div(BigNumber(2).multipliedBy(a));
743
+
744
+ const x2 = BigNumber(b)
745
+ .multipliedBy(-1)
746
+ .plus(rootDelta)
747
+ .div(BigNumber(2).multipliedBy(a));
748
+
749
+ return {
750
+ x1,
751
+ x2,
752
+ };
753
+ }
754
+
755
+ static computeC(lmr: number, balance: number, imrMultiplier: number): number {
756
+ const lmrSD = BigNumber(lmr);
757
+ const lmrSquared = lmrSD.multipliedBy(lmrSD);
758
+
759
+ const balanceSD = BigNumber(balance);
760
+ const balanceSquared = balanceSD.multipliedBy(balanceSD);
761
+
762
+ const imrMultiplierSD = BigNumber(imrMultiplier);
763
+ const imrMultiplierSquared = imrMultiplierSD.multipliedBy(imrMultiplierSD);
764
+
765
+ return lmrSquared
766
+ .minus(balanceSquared.div(imrMultiplierSquared))
767
+ .toNumber();
768
+ }
769
+
770
+ static calculateFee(
771
+ price: number,
772
+ amount: number,
773
+ feeParameter: BigNumber,
774
+ ): number {
775
+ return BigNumber(price).times(amount).times(feeParameter).abs().toNumber(); // @todo abs value
776
+ }
777
+
778
+ static calculateEstimatedPrice(price: number, slippage: number): number {
779
+ return BigNumber(price).times(BigNumber(1).plus(slippage)).toNumber();
780
+ }
781
+
782
+ static evaluateHealthStatus(number: number) {
783
+ // todo update logic
784
+ if (number >= 67) {
785
+ return 'danger';
786
+ } else if (number >= 34) {
787
+ return 'warning';
788
+ } else {
789
+ return 'healthy';
790
+ }
791
+ }
792
+ }