@d8x/perpetuals-sdk 0.0.21 → 0.0.22
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/abi/IPerpetualManager.json +51 -2
- package/abi/LimitOrderBook.json +75 -45
- package/config/defaultConfig.json +5 -4
- package/config/oldConfig.json +5 -4
- package/config/symbolList.json +9 -0
- package/dist/accountTrade.js +2 -2
- package/dist/d8XMath.d.ts +16 -0
- package/dist/d8XMath.js +48 -1
- package/dist/liquiditatorTool.d.ts +14 -0
- package/dist/liquiditatorTool.js +21 -0
- package/dist/marketData.d.ts +41 -5
- package/dist/marketData.js +139 -6
- package/dist/nodeSDKTypes.d.ts +14 -0
- package/dist/orderReferrerTool.js +4 -4
- package/dist/perpetualDataHandler.d.ts +21 -4
- package/dist/perpetualDataHandler.js +71 -24
- package/dist/perpetualEventHandler.d.ts +181 -0
- package/dist/perpetualEventHandler.js +390 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +37 -1
- package/package.json +1 -1
- package/src/accountTrade.ts +2 -2
- package/src/d8XMath.ts +85 -0
- package/src/liquidityProviderTool.ts +0 -8
- package/src/marketData.ts +226 -8
- package/src/nodeSDKTypes.ts +16 -0
- package/src/orderReferrerTool.ts +4 -4
- package/src/perpetualDataHandler.ts +93 -27
- package/src/perpetualEventHandler.ts +441 -0
- package/src/utils.ts +36 -0
|
@@ -29,8 +29,17 @@ import {
|
|
|
29
29
|
DEFAULT_CONFIG_TESTNET_NAME,
|
|
30
30
|
DEFAULT_CONFIG_TESTNET,
|
|
31
31
|
ONE_64x64,
|
|
32
|
+
PERP_STATE_STR,
|
|
33
|
+
PerpetualState,
|
|
32
34
|
} from "./nodeSDKTypes";
|
|
33
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
fromBytes4HexString,
|
|
37
|
+
to4Chars,
|
|
38
|
+
combineFlags,
|
|
39
|
+
containsFlag,
|
|
40
|
+
contractSymbolToSymbol,
|
|
41
|
+
symbol4BToLongSymbol,
|
|
42
|
+
} from "./utils";
|
|
34
43
|
import {
|
|
35
44
|
ABK64x64ToFloat,
|
|
36
45
|
floatToABK64x64,
|
|
@@ -50,6 +59,8 @@ export default class PerpetualDataHandler {
|
|
|
50
59
|
//this is initialized in the createProxyInstance function
|
|
51
60
|
protected symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>;
|
|
52
61
|
protected poolStaticInfos: Array<PoolStaticInfo>;
|
|
62
|
+
protected symbolList: Array<{ [key: string]: string }>;
|
|
63
|
+
|
|
53
64
|
//map margin token of the form MATIC or ETH or USDC into
|
|
54
65
|
//the address of the margin token
|
|
55
66
|
protected symbolToTokenAddrMap: Map<string, string>;
|
|
@@ -83,6 +94,7 @@ export default class PerpetualDataHandler {
|
|
|
83
94
|
this.proxyABI = require(config.proxyABILocation);
|
|
84
95
|
this.lobFactoryABI = require(config.limitOrderBookFactoryABILocation);
|
|
85
96
|
this.lobABI = require(config.limitOrderBookABILocation);
|
|
97
|
+
this.symbolList = require(config.symbolListLocation);
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
protected async initContractsAndData(signerOrProvider: ethers.Signer | ethers.providers.Provider) {
|
|
@@ -98,8 +110,7 @@ export default class PerpetualDataHandler {
|
|
|
98
110
|
* @returns order book contract for the perpetual
|
|
99
111
|
*/
|
|
100
112
|
public getOrderBookContract(symbol: string): ethers.Contract {
|
|
101
|
-
let
|
|
102
|
-
let orderBookAddr = this.symbolToPerpStaticInfo.get(cleanSymbol)?.limitOrderBookAddr;
|
|
113
|
+
let orderBookAddr = this.symbolToPerpStaticInfo.get(symbol)?.limitOrderBookAddr;
|
|
103
114
|
if (orderBookAddr == "" || orderBookAddr == undefined || this.signerOrProvider == null) {
|
|
104
115
|
throw Error(`no limit order book found for ${symbol} or no signer`);
|
|
105
116
|
}
|
|
@@ -132,10 +143,10 @@ export default class PerpetualDataHandler {
|
|
|
132
143
|
|
|
133
144
|
for (let k = 0; k < perpetualIDs.length; k++) {
|
|
134
145
|
let perp = await proxyContract.getPerpetual(perpetualIDs[k]);
|
|
135
|
-
let base =
|
|
136
|
-
let quote =
|
|
137
|
-
let base3 =
|
|
138
|
-
let quote3 =
|
|
146
|
+
let base = contractSymbolToSymbol(perp.S2BaseCCY, this.symbolList);
|
|
147
|
+
let quote = contractSymbolToSymbol(perp.S2QuoteCCY, this.symbolList);
|
|
148
|
+
let base3 = contractSymbolToSymbol(perp.S3BaseCCY, this.symbolList);
|
|
149
|
+
let quote3 = contractSymbolToSymbol(perp.S3QuoteCCY, this.symbolList);
|
|
139
150
|
currentSymbols.push(base + "-" + quote);
|
|
140
151
|
currentSymbolsS3.push(base3 + "-" + quote3);
|
|
141
152
|
initRate.push(ABK64x64ToFloat(perp.fInitialMarginRate));
|
|
@@ -155,19 +166,17 @@ export default class PerpetualDataHandler {
|
|
|
155
166
|
poolCCY = quote;
|
|
156
167
|
ccy.push(CollaterlCCY.QUOTE);
|
|
157
168
|
} else {
|
|
169
|
+
poolCCY = base3;
|
|
158
170
|
ccy.push(CollaterlCCY.QUANTO);
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
if (perpetualIDs.length == 0) {
|
|
162
174
|
continue;
|
|
163
175
|
}
|
|
164
|
-
if (poolCCY == undefined) {
|
|
165
|
-
throw Error("Pool only has quanto perps, unable to determine collateral currency");
|
|
166
|
-
}
|
|
167
176
|
let oracleFactoryAddr = await proxyContract.getOracleFactory();
|
|
168
177
|
let info: PoolStaticInfo = {
|
|
169
178
|
poolId: j + 1,
|
|
170
|
-
poolMarginSymbol: poolCCY
|
|
179
|
+
poolMarginSymbol: poolCCY!,
|
|
171
180
|
poolMarginTokenAddr: poolMarginTokenAddr,
|
|
172
181
|
shareTokenAddr: pool.shareTokenAddress,
|
|
173
182
|
oracleFactoryAddr: oracleFactoryAddr,
|
|
@@ -188,7 +197,7 @@ export default class PerpetualDataHandler {
|
|
|
188
197
|
});
|
|
189
198
|
}
|
|
190
199
|
// push margin token address into map
|
|
191
|
-
this.symbolToTokenAddrMap.set(poolCCY
|
|
200
|
+
this.symbolToTokenAddrMap.set(poolCCY!, poolMarginTokenAddr);
|
|
192
201
|
}
|
|
193
202
|
}
|
|
194
203
|
|
|
@@ -219,6 +228,21 @@ export default class PerpetualDataHandler {
|
|
|
219
228
|
return PerpetualDataHandler.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
|
|
220
229
|
}
|
|
221
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Get the symbol in long format of the perpetual id
|
|
233
|
+
* @param perpId perpetual id
|
|
234
|
+
*/
|
|
235
|
+
public getSymbolFromPerpId(perpId: number): string | undefined {
|
|
236
|
+
let symbol4Bytes = PerpetualDataHandler.perpetualIdToSymbol(perpId, this.symbolToPerpStaticInfo);
|
|
237
|
+
if (symbol4Bytes == undefined) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public symbol4BToLongSymbol(sym: string): string {
|
|
243
|
+
return symbol4BToLongSymbol(sym, this.symbolList);
|
|
244
|
+
}
|
|
245
|
+
|
|
222
246
|
protected static _getSymbolFromPoolId(poolId: number, staticInfos: PoolStaticInfo[]): string {
|
|
223
247
|
let idx = poolId - 1;
|
|
224
248
|
return staticInfos[idx].poolMarginSymbol;
|
|
@@ -230,10 +254,8 @@ export default class PerpetualDataHandler {
|
|
|
230
254
|
if (symbols.length == 3) {
|
|
231
255
|
symbol = symbols[2];
|
|
232
256
|
}
|
|
233
|
-
let cleanSymbol = to4Chars(symbol);
|
|
234
|
-
cleanSymbol = cleanSymbol.replace(/\0/g, "");
|
|
235
257
|
let j = 0;
|
|
236
|
-
while (j < staticInfos.length && staticInfos[j].poolMarginSymbol !=
|
|
258
|
+
while (j < staticInfos.length && staticInfos[j].poolMarginSymbol != symbol) {
|
|
237
259
|
j++;
|
|
238
260
|
}
|
|
239
261
|
if (j == staticInfos.length) {
|
|
@@ -262,8 +284,10 @@ export default class PerpetualDataHandler {
|
|
|
262
284
|
symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
|
|
263
285
|
_proxyContract: ethers.Contract
|
|
264
286
|
): Promise<MarginAccount> {
|
|
265
|
-
let
|
|
266
|
-
|
|
287
|
+
let perpId = Number(symbol);
|
|
288
|
+
if (isNaN(perpId)) {
|
|
289
|
+
perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
|
|
290
|
+
}
|
|
267
291
|
const idx_cash = 3;
|
|
268
292
|
const idx_notional = 4;
|
|
269
293
|
const idx_locked_in = 5;
|
|
@@ -283,7 +307,7 @@ export default class PerpetualDataHandler {
|
|
|
283
307
|
entryPrice = 0;
|
|
284
308
|
if (!isEmpty) {
|
|
285
309
|
[S2Liq, S3Liq, tau, pnl, unpaidFundingCC] = PerpetualDataHandler._calculateLiquidationPrice(
|
|
286
|
-
|
|
310
|
+
symbol,
|
|
287
311
|
traderState,
|
|
288
312
|
symbolToPerpStaticInfo
|
|
289
313
|
);
|
|
@@ -329,15 +353,40 @@ export default class PerpetualDataHandler {
|
|
|
329
353
|
return ABK64x64ToFloat(ammState[6].mul(ONE_64x64.add(ammState[8])).div(ONE_64x64));
|
|
330
354
|
}
|
|
331
355
|
|
|
356
|
+
protected static async _queryPerpetualState(
|
|
357
|
+
symbol: string,
|
|
358
|
+
symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
|
|
359
|
+
_proxyContract: ethers.Contract
|
|
360
|
+
): Promise<PerpetualState> {
|
|
361
|
+
let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
|
|
362
|
+
let ccy = symbol.split("-");
|
|
363
|
+
let ammState = await _proxyContract.getAMMState(perpId);
|
|
364
|
+
let markPrice = ABK64x64ToFloat(ammState[6].mul(ONE_64x64.add(ammState[8])).div(ONE_64x64));
|
|
365
|
+
let state = {
|
|
366
|
+
id: perpId,
|
|
367
|
+
state: PERP_STATE_STR[ammState[13]],
|
|
368
|
+
baseCurrency: ccy[0],
|
|
369
|
+
quoteCurrency: ccy[1],
|
|
370
|
+
indexPrice: ABK64x64ToFloat(ammState[6]),
|
|
371
|
+
collToQuoteIndexPrice: ABK64x64ToFloat(ammState[7]),
|
|
372
|
+
markPrice: markPrice,
|
|
373
|
+
midPrice: ABK64x64ToFloat(ammState[10]),
|
|
374
|
+
currentFundingRateBps: ABK64x64ToFloat(ammState[14]) * 1e4,
|
|
375
|
+
openInterestBC: ABK64x64ToFloat(ammState[11]),
|
|
376
|
+
maxPositionBC: ABK64x64ToFloat(ammState[12]),
|
|
377
|
+
};
|
|
378
|
+
return state;
|
|
379
|
+
}
|
|
380
|
+
|
|
332
381
|
/**
|
|
333
382
|
* Liquidation price
|
|
334
|
-
* @param
|
|
383
|
+
* @param symbol symbol of the form BTC-USD-MATIC
|
|
335
384
|
* @param traderState BigInt array according to smart contract
|
|
336
385
|
* @param symbolToPerpStaticInfo mapping symbol->PerpStaticInfo
|
|
337
386
|
* @returns liquidation mark-price, corresponding collateral/quote conversion
|
|
338
387
|
*/
|
|
339
388
|
protected static _calculateLiquidationPrice(
|
|
340
|
-
|
|
389
|
+
symbol: string,
|
|
341
390
|
traderState: BigNumber[],
|
|
342
391
|
symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
|
|
343
392
|
): [number, number, number, number, number] {
|
|
@@ -350,9 +399,9 @@ export default class PerpetualDataHandler {
|
|
|
350
399
|
const idx_s2 = 10;
|
|
351
400
|
let S2Liq: number;
|
|
352
401
|
let S3Liq: number = ABK64x64ToFloat(traderState[idx_s3]);
|
|
353
|
-
let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(
|
|
402
|
+
let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(symbol);
|
|
354
403
|
if (perpInfo == undefined) {
|
|
355
|
-
throw new Error(`no info for perpetual ${
|
|
404
|
+
throw new Error(`no info for perpetual ${symbol}`);
|
|
356
405
|
}
|
|
357
406
|
let tau = perpInfo.maintenanceMarginRate;
|
|
358
407
|
let lockedInValueQC = ABK64x64ToFloat(traderState[idx_locked_in]);
|
|
@@ -382,7 +431,7 @@ export default class PerpetualDataHandler {
|
|
|
382
431
|
* Finds the perpetual id for a symbol of the form
|
|
383
432
|
* <base>-<quote>-<collateral>. The function first converts the
|
|
384
433
|
* token names into bytes4 representation
|
|
385
|
-
* @param symbol symbol (e.g., BTC-USD-
|
|
434
|
+
* @param symbol symbol (e.g., BTC-USD-MATC)
|
|
386
435
|
* @param symbolToPerpStaticInfo map that contains the bytes4-symbol to PerpetualStaticInfo
|
|
387
436
|
* including id mapping
|
|
388
437
|
* @returns perpetual id or it fails
|
|
@@ -391,14 +440,32 @@ export default class PerpetualDataHandler {
|
|
|
391
440
|
symbol: string,
|
|
392
441
|
symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
|
|
393
442
|
): number {
|
|
394
|
-
let
|
|
395
|
-
let id = symbolToPerpStaticInfo.get(cleanSymbol)?.id;
|
|
443
|
+
let id = symbolToPerpStaticInfo.get(symbol)?.id;
|
|
396
444
|
if (id == undefined) {
|
|
397
445
|
throw Error(`No perpetual found for symbol ${symbol}`);
|
|
398
446
|
}
|
|
399
447
|
return id;
|
|
400
448
|
}
|
|
401
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Find the symbol ("ETH-USD-MATC") of the given perpetual id
|
|
452
|
+
* @param id perpetual id
|
|
453
|
+
* @param symbolToPerpStaticInfo map that contains the bytes4-symbol to PerpetualStaticInfo
|
|
454
|
+
* @returns symbol string or undefined
|
|
455
|
+
*/
|
|
456
|
+
protected static perpetualIdToSymbol(
|
|
457
|
+
id: number,
|
|
458
|
+
symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
|
|
459
|
+
): string | undefined {
|
|
460
|
+
let symbol;
|
|
461
|
+
for (symbol of symbolToPerpStaticInfo.keys()) {
|
|
462
|
+
if (symbolToPerpStaticInfo.get(symbol)?.id == id) {
|
|
463
|
+
return symbol;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
402
469
|
protected static symbolToBytes4Symbol(symbol: string): string {
|
|
403
470
|
//split by dashes BTC-USD-MATIC
|
|
404
471
|
let symbols: string[] = symbol.split("-");
|
|
@@ -588,8 +655,7 @@ export default class PerpetualDataHandler {
|
|
|
588
655
|
}
|
|
589
656
|
|
|
590
657
|
protected static _getLotSize(symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>): number {
|
|
591
|
-
let
|
|
592
|
-
let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(cleanSymbol);
|
|
658
|
+
let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(symbol);
|
|
593
659
|
if (perpInfo == undefined) {
|
|
594
660
|
throw new Error(`no info for perpetual ${symbol}`);
|
|
595
661
|
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { BigNumber, utils } from "ethers";
|
|
2
|
+
import { ABK64x64ToFloat, mul64x64, div64x64 } from "./d8XMath";
|
|
3
|
+
import {
|
|
4
|
+
PerpetualState,
|
|
5
|
+
ONE_64x64,
|
|
6
|
+
ExchangeInfo,
|
|
7
|
+
Order,
|
|
8
|
+
SmartContractOrder,
|
|
9
|
+
MarginAccount,
|
|
10
|
+
OrderStruct,
|
|
11
|
+
TradeEvent,
|
|
12
|
+
} from "./nodeSDKTypes";
|
|
13
|
+
import { emitWarning } from "process";
|
|
14
|
+
import MarketData from "./marketData";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This class handles events and stores relevant variables
|
|
18
|
+
* as member variables. The events change the state of the member variables:
|
|
19
|
+
* mktData : MarketData relevant market data with current state (e.g. index price)
|
|
20
|
+
* ordersInPerpetual: Map<number, OrderStruct> all open orders for the given trader
|
|
21
|
+
* positionInPerpetual: Map<number, MarginAccount> all open positions for the given trader
|
|
22
|
+
*
|
|
23
|
+
* TODO:
|
|
24
|
+
* - update functions for midprice & index & collateral prices without event
|
|
25
|
+
* - testing
|
|
26
|
+
*
|
|
27
|
+
* Get data:
|
|
28
|
+
* - getPerpetualData(perp id (string) or symbol) : PerpetualState. This is a reference!
|
|
29
|
+
* - getExchangeInfo() : ExchangeInfo. This is a reference!
|
|
30
|
+
* - getCurrentPositionRisk(perp id (string) or symbol) : MarginAccount. This is a reference!
|
|
31
|
+
* - getOrdersInPerpetualMap : Map<number, OrderStruct>. This is a reference!
|
|
32
|
+
* - getpositionInPerpetualMap : Map<number, MarginAccount>. This is a reference!
|
|
33
|
+
*
|
|
34
|
+
* Construct with a trader address and a marketData object
|
|
35
|
+
* Initialize to gather all the relevant data.
|
|
36
|
+
* Send event variables to event handler "on<EventName>" - this updates members
|
|
37
|
+
* - [x] onUpdateMarkPrice : emitted on proxy; updates markprice and index price data
|
|
38
|
+
* - [x] onUpdateUpdateFundingRate : emitted on proxy; sets funding rate
|
|
39
|
+
* - [x] onExecutionFailed : emitted on order book; removes an open order
|
|
40
|
+
* - [x] onPerpetualLimitOrderCancelled : emitted on order book; removes an open order
|
|
41
|
+
* - [x] onPerpetualLimitOrderCreated : emitted on order book; adds an open order to the data
|
|
42
|
+
* - [x] async onUpdateMarginAccount : emitted on proxy; updates position data and open interest
|
|
43
|
+
* - [x] onTrade : emitted on proxy; returns TradeEvent to be displayed
|
|
44
|
+
*/
|
|
45
|
+
export default class PerpetualEventHandler {
|
|
46
|
+
// market data class
|
|
47
|
+
private mktData: MarketData;
|
|
48
|
+
// trader for which the data is tracked
|
|
49
|
+
private traderAddr: string;
|
|
50
|
+
|
|
51
|
+
// perpetual id to trader data
|
|
52
|
+
private ordersInPerpetual: Map<number, OrderStruct>;
|
|
53
|
+
private positionInPerpetual: Map<number, MarginAccount>;
|
|
54
|
+
|
|
55
|
+
// keep current state of the system in exchangeInfo
|
|
56
|
+
// data is updated when calling "onEvent"-functions
|
|
57
|
+
private exchangeInfo: ExchangeInfo | undefined;
|
|
58
|
+
|
|
59
|
+
constructor(mktData: MarketData, traderAddr: string) {
|
|
60
|
+
this.mktData = mktData;
|
|
61
|
+
this.traderAddr = traderAddr;
|
|
62
|
+
this.ordersInPerpetual = new Map<number, OrderStruct>();
|
|
63
|
+
this.positionInPerpetual = new Map<number, MarginAccount>();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Call this async function to initialize the
|
|
68
|
+
* market data
|
|
69
|
+
*/
|
|
70
|
+
public async initialize() {
|
|
71
|
+
this.exchangeInfo = await this.mktData.exchangeInfo();
|
|
72
|
+
// loop through all pools and perpetuals and get
|
|
73
|
+
// open positions and open orders
|
|
74
|
+
for (let k = 0; k < this.exchangeInfo.pools.length; k++) {
|
|
75
|
+
let poolState = this.exchangeInfo.pools[k];
|
|
76
|
+
let poolSymbol = poolState.poolSymbol;
|
|
77
|
+
for (let j = 0; j < poolState.perpetuals.length; j++) {
|
|
78
|
+
let perpState: PerpetualState = poolState.perpetuals[j];
|
|
79
|
+
let perpSymbol = perpState.baseCurrency + "-" + perpState.quoteCurrency + "-" + poolSymbol;
|
|
80
|
+
let orders = await this.mktData.openOrders(this.traderAddr, perpSymbol);
|
|
81
|
+
let perpId = perpState.id;
|
|
82
|
+
this.ordersInPerpetual.set(perpId, orders);
|
|
83
|
+
let position = await this.mktData.positionRisk(this.traderAddr, perpSymbol);
|
|
84
|
+
this.positionInPerpetual.set(perpId, position);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the current exchange info
|
|
91
|
+
* @returns exchange info
|
|
92
|
+
*/
|
|
93
|
+
public getExchangeInfo(): ExchangeInfo | undefined {
|
|
94
|
+
return this.exchangeInfo;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* getOrdersInPerpetualMap
|
|
99
|
+
* @returns this.ordersInPerpetual
|
|
100
|
+
*/
|
|
101
|
+
public getOrdersInPerpetualMap(): Map<number, OrderStruct> {
|
|
102
|
+
return this.ordersInPerpetual;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* getpositionInPerpetualMap
|
|
107
|
+
* @returns this.positionInPerpetual
|
|
108
|
+
*/
|
|
109
|
+
public getpositionInPerpetualMap(): Map<number, MarginAccount> {
|
|
110
|
+
return this.positionInPerpetual;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the data for a perpetual with a given index
|
|
115
|
+
* @param perpetualIdOrSymbol perpetual idx as string or symbol for which we want the data
|
|
116
|
+
* @returns perpetual data for this idx
|
|
117
|
+
*/
|
|
118
|
+
public getPerpetualData(perpetualIdOrSymbol: string): PerpetualState | undefined {
|
|
119
|
+
let perpId = Number(perpetualIdOrSymbol);
|
|
120
|
+
if (isNaN(perpId)) {
|
|
121
|
+
perpId = this.mktData.getPerpIdFromSymbol(perpetualIdOrSymbol);
|
|
122
|
+
}
|
|
123
|
+
//uint24 perpetualId = uint24(_iPoolId) * 100_000 + iPerpetualIndex;
|
|
124
|
+
let poolIdx = Math.floor(perpId / 100_000);
|
|
125
|
+
let perpetuals = this.exchangeInfo?.pools[poolIdx].perpetuals;
|
|
126
|
+
if (perpetuals == undefined) {
|
|
127
|
+
emitWarning(`exchangeInfo not found, initialize perpetualEventHandler`);
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
// find perpetual
|
|
131
|
+
let k;
|
|
132
|
+
for (k = 0; k < perpetuals?.length && perpetuals[k].id != perpId; k++);
|
|
133
|
+
if (perpetuals[k].id != perpId) {
|
|
134
|
+
emitWarning(`getPerpetualData: perpetual id ${perpId} not found`);
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return perpetuals[k];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get the trader's current position risk (margin account data)
|
|
142
|
+
* @param perpetualIdOrSymbol perpetual id as string ('100003') or symbol ('BTC-USD-MATIC')
|
|
143
|
+
* @returns undefined if no position or margin account (='position risk')
|
|
144
|
+
*/
|
|
145
|
+
public getCurrentPositionRisk(perpetualIdOrSymbol: string): MarginAccount | undefined {
|
|
146
|
+
let perpId = Number(perpetualIdOrSymbol);
|
|
147
|
+
if (isNaN(perpId)) {
|
|
148
|
+
perpId = this.mktData.getPerpIdFromSymbol(perpetualIdOrSymbol);
|
|
149
|
+
}
|
|
150
|
+
return this.positionInPerpetual.get(perpId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Update the following prices:
|
|
155
|
+
* - index price
|
|
156
|
+
* - collateral price
|
|
157
|
+
* - mid-price
|
|
158
|
+
* @param perpetualIdOrSymbol perpetual id as string ('100003') or symbol ('BTC-USD-MATIC')
|
|
159
|
+
*/
|
|
160
|
+
public async updatePrices(perpetualIdOrSymbol: string) {
|
|
161
|
+
let perpId = Number(perpetualIdOrSymbol);
|
|
162
|
+
let symbol = perpetualIdOrSymbol;
|
|
163
|
+
if (!isNaN(perpId)) {
|
|
164
|
+
let sym = this.mktData.getSymbolFromPerpId(perpId);
|
|
165
|
+
if (sym == undefined) {
|
|
166
|
+
throw new Error(`Symbol not found for perpetual ${perpId}`);
|
|
167
|
+
}
|
|
168
|
+
symbol = sym;
|
|
169
|
+
}
|
|
170
|
+
let perpState: PerpetualState = await this.mktData.getPerpetualState(symbol);
|
|
171
|
+
let perp = this.getPerpetualData(symbol);
|
|
172
|
+
if (perp == undefined) {
|
|
173
|
+
throw new Error(`Perpetual not found: ${symbol}`);
|
|
174
|
+
}
|
|
175
|
+
perp.state = perpState.state;
|
|
176
|
+
perp.indexPrice = perpState.indexPrice;
|
|
177
|
+
perp.collToQuoteIndexPrice = perpState.collToQuoteIndexPrice;
|
|
178
|
+
perp.markPrice = perpState.markPrice;
|
|
179
|
+
perp.midPrice = perpState.midPrice;
|
|
180
|
+
perp.currentFundingRateBps = perpState.currentFundingRateBps;
|
|
181
|
+
perp.openInterestBC = perpState.openInterestBC;
|
|
182
|
+
perp.maxPositionBC = perpState.maxPositionBC;
|
|
183
|
+
perp.indexPrice = perpState.indexPrice;
|
|
184
|
+
perp.collToQuoteIndexPrice = perpState.collToQuoteIndexPrice;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Handle the event UpdateMarkPrice and update relevant
|
|
189
|
+
* data
|
|
190
|
+
* @param perpetualId perpetual Id
|
|
191
|
+
* @param fMarkPricePremium premium rate in ABDK format
|
|
192
|
+
* @param fSpotIndexPrice spot index price in ABDK format
|
|
193
|
+
* @returns void
|
|
194
|
+
*/
|
|
195
|
+
public onUpdateMarkPrice(perpetualId: number, fMarkPricePremium: BigNumber, fSpotIndexPrice: BigNumber): void {
|
|
196
|
+
let [newMarkPrice, newIndexPrice] = PerpetualEventHandler.ConvertUpdateMarkPrice(
|
|
197
|
+
fMarkPricePremium,
|
|
198
|
+
fSpotIndexPrice
|
|
199
|
+
);
|
|
200
|
+
let perpetual = this.getPerpetualData(perpetualId.toString());
|
|
201
|
+
if (perpetual == undefined) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
perpetual.markPrice = newMarkPrice;
|
|
205
|
+
perpetual.indexPrice = newIndexPrice;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Handle the event UpdateFundingRate and update relevant
|
|
210
|
+
* data
|
|
211
|
+
* UpdateFundingRate(uint24 indexed perpetualId, int128 fFundingRate)
|
|
212
|
+
* @param fFundingRate funding rate in ABDK format
|
|
213
|
+
*/
|
|
214
|
+
public onUpdateUpdateFundingRate(perpetualId: number, fFundingRate: BigNumber): void {
|
|
215
|
+
let newRate = ABK64x64ToFloat(fFundingRate);
|
|
216
|
+
let perpetual = this.getPerpetualData(perpetualId.toString());
|
|
217
|
+
if (perpetual == undefined) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
perpetual.currentFundingRateBps = newRate * 1e4;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* event ExecutionFailed(
|
|
225
|
+
uint24 indexed perpetualId,
|
|
226
|
+
address indexed trader,
|
|
227
|
+
bytes32 digest,
|
|
228
|
+
string reason
|
|
229
|
+
);
|
|
230
|
+
* @param perpetualId id of the perpetual
|
|
231
|
+
* @param trader address of the trader
|
|
232
|
+
* @param digest digest of the order/cancel order
|
|
233
|
+
* @param reason reason why the execution failed
|
|
234
|
+
*/
|
|
235
|
+
public onExecutionFailed(perpetualId: number, trader: string, digest: string, reason: string) {
|
|
236
|
+
if (trader != this.traderAddr) {
|
|
237
|
+
emitWarning(`onExecutionFailed: trader ${trader} not relevant`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// remove order from open orders
|
|
241
|
+
let orderStructs:
|
|
242
|
+
| {
|
|
243
|
+
orders: Order[];
|
|
244
|
+
orderIds: string[];
|
|
245
|
+
}
|
|
246
|
+
| undefined = this.ordersInPerpetual.get(perpetualId);
|
|
247
|
+
if (orderStructs == undefined) {
|
|
248
|
+
emitWarning(`onExecutionFailed: no order found for perpetual ${perpetualId}`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (reason == "cancel delay required") {
|
|
252
|
+
// canceling failed. We don't remove the order
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
PerpetualEventHandler.deleteOrder(orderStructs, digest);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Event emitted by perpetual proxy
|
|
260
|
+
* event PerpetualLimitOrderCancelled(bytes32 indexed orderHash);
|
|
261
|
+
* @param orderId string order id/digest
|
|
262
|
+
*/
|
|
263
|
+
public onPerpetualLimitOrderCancelled(orderId: string) {
|
|
264
|
+
// remove order from open orders
|
|
265
|
+
let perpId: number | undefined = PerpetualEventHandler.findOrderForId(orderId, this.ordersInPerpetual);
|
|
266
|
+
if (perpId == undefined) {
|
|
267
|
+
emitWarning(`onPerpetualLimitOrderCancelled: no order found with id ${orderId}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
let orderStruct: OrderStruct | undefined = this.ordersInPerpetual.get(perpId);
|
|
271
|
+
PerpetualEventHandler.deleteOrder(orderStruct!, orderId);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* event PerpetualLimitOrderCreated(
|
|
276
|
+
* uint24 indexed perpetualId,
|
|
277
|
+
* address indexed trader,
|
|
278
|
+
* address referrerAddr,
|
|
279
|
+
* address brokerAddr,
|
|
280
|
+
* Order order,
|
|
281
|
+
* bytes32 digest
|
|
282
|
+
*)
|
|
283
|
+
* @param perpetualId id of the perpetual
|
|
284
|
+
* @param trader address of the trader
|
|
285
|
+
* @param referrerAddr address of the referrer
|
|
286
|
+
* @param brokerAddr address of the broker
|
|
287
|
+
* @param Order order struct
|
|
288
|
+
* @param digest order id
|
|
289
|
+
*/
|
|
290
|
+
public onPerpetualLimitOrderCreated(
|
|
291
|
+
perpetualId: number,
|
|
292
|
+
trader: string,
|
|
293
|
+
referrerAddr: string,
|
|
294
|
+
brokerAddr: string,
|
|
295
|
+
Order: SmartContractOrder,
|
|
296
|
+
digest: string
|
|
297
|
+
): void {
|
|
298
|
+
if (trader != this.traderAddr) {
|
|
299
|
+
emitWarning(`onPerpetualLimitOrderCreated: trader ${trader} not relevant`);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
let order: Order = this.mktData.smartContractOrderToOrder(Order);
|
|
303
|
+
let orderStruct: OrderStruct | undefined = this.ordersInPerpetual.get(perpetualId);
|
|
304
|
+
if (orderStruct == undefined) {
|
|
305
|
+
// no order for this perpetual so far
|
|
306
|
+
this.ordersInPerpetual.set(perpetualId, { orders: [order], orderIds: [digest] });
|
|
307
|
+
} else {
|
|
308
|
+
orderStruct.orderIds.push(digest);
|
|
309
|
+
orderStruct.orders.push(order);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* This function is async -> queries the margin account
|
|
315
|
+
* @param perpetualId id of the perpetual
|
|
316
|
+
* @param trader trader address
|
|
317
|
+
* @param positionId position id
|
|
318
|
+
* @param fPositionBC position size in base currency
|
|
319
|
+
* @param fCashCC margin collateral in margin account
|
|
320
|
+
* @param fLockedInValueQC pos*average opening price
|
|
321
|
+
* @param fFundingPaymentCC funding payment made
|
|
322
|
+
* @param fOpenInterestBC open interest
|
|
323
|
+
*/
|
|
324
|
+
public async onUpdateMarginAccount(
|
|
325
|
+
perpetualId: number,
|
|
326
|
+
trader: string,
|
|
327
|
+
positionId: string,
|
|
328
|
+
fPositionBC: BigNumber,
|
|
329
|
+
fCashCC: BigNumber,
|
|
330
|
+
fLockedInValueQC: BigNumber,
|
|
331
|
+
fFundingPaymentCC: BigNumber,
|
|
332
|
+
fOpenInterestBC: BigNumber
|
|
333
|
+
): Promise<void> {
|
|
334
|
+
let perpetual = this.getPerpetualData(perpetualId.toString());
|
|
335
|
+
if (perpetual == undefined) {
|
|
336
|
+
emitWarning(`onUpdateMarginAccount: perpetual ${perpetualId} not found`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
perpetual.openInterestBC = ABK64x64ToFloat(fOpenInterestBC);
|
|
340
|
+
if (trader != this.traderAddr) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
let perpetualIdStr = perpetualId.toString();
|
|
344
|
+
let margin = await this.mktData.positionRisk(this.traderAddr, perpetualIdStr);
|
|
345
|
+
this.positionInPerpetual.set(perpetualId, margin);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
*
|
|
350
|
+
* @param perpetualId perpetual id
|
|
351
|
+
* @param trader trader address
|
|
352
|
+
* @param positionId position id
|
|
353
|
+
* @param order order struct
|
|
354
|
+
* @param orderDigest order id
|
|
355
|
+
* @param newPositionSizeBC new pos size in base currency ABDK
|
|
356
|
+
* @param price price in ABDK format
|
|
357
|
+
* @returns trade event data in regular number format
|
|
358
|
+
*/
|
|
359
|
+
public onTrade(
|
|
360
|
+
perpetualId: number,
|
|
361
|
+
trader: string,
|
|
362
|
+
positionId: string,
|
|
363
|
+
order: SmartContractOrder,
|
|
364
|
+
orderDigest: string,
|
|
365
|
+
newPositionSizeBC: BigNumber,
|
|
366
|
+
price: BigNumber
|
|
367
|
+
): TradeEvent {
|
|
368
|
+
// remove order digest from open orders
|
|
369
|
+
let orderStructs = this.ordersInPerpetual.get(perpetualId);
|
|
370
|
+
if (orderStructs == undefined) {
|
|
371
|
+
emitWarning(`onTrade: executed order not found ${orderDigest}`);
|
|
372
|
+
} else {
|
|
373
|
+
PerpetualEventHandler.deleteOrder(orderStructs, orderDigest);
|
|
374
|
+
}
|
|
375
|
+
// return transformed trade info
|
|
376
|
+
return {
|
|
377
|
+
perpetualId: perpetualId,
|
|
378
|
+
positionId: positionId,
|
|
379
|
+
orderId: orderDigest,
|
|
380
|
+
newPositionSizeBC: ABK64x64ToFloat(newPositionSizeBC),
|
|
381
|
+
executionPrice: ABK64x64ToFloat(newPositionSizeBC),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* static function to find the number of the OrderStruct with given orderId
|
|
387
|
+
* @param orderId id/digest of order
|
|
388
|
+
* @param orderMap mapping for perpetualId->OrderStruct
|
|
389
|
+
* @returns id of perpetual that contains order with id = orderId or undefined
|
|
390
|
+
*/
|
|
391
|
+
private static findOrderForId(orderId: string, orderMap: Map<number, OrderStruct>): number | undefined {
|
|
392
|
+
/*orderMapMap<number, {
|
|
393
|
+
orders: Order[];
|
|
394
|
+
orderIds: string[];*/
|
|
395
|
+
for (const perpId of orderMap.keys()) {
|
|
396
|
+
let orderStructs = orderMap.get(perpId);
|
|
397
|
+
if (orderStructs?.orderIds.includes(orderId)) {
|
|
398
|
+
return perpId;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Delete the order with given id from the class member data
|
|
406
|
+
* @param orderStructs array of order struct as stored for the trader and a given perpetual
|
|
407
|
+
* @param orderId digest/order id
|
|
408
|
+
* @returns void
|
|
409
|
+
*/
|
|
410
|
+
private static deleteOrder(orderStructs: OrderStruct, orderId: string): void {
|
|
411
|
+
// find order
|
|
412
|
+
let k;
|
|
413
|
+
for (k = 0; k < orderStructs.orderIds.length && orderStructs.orderIds[k] != orderId; k++);
|
|
414
|
+
if (orderStructs.orderIds[k] != orderId) {
|
|
415
|
+
emitWarning(`deleteOrder: no order found with digest ${orderId}`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
// delete element k on reference of orders
|
|
419
|
+
orderStructs.orders[k] = orderStructs.orders[orderStructs.orders.length - 1];
|
|
420
|
+
orderStructs.orders.pop();
|
|
421
|
+
orderStructs.orderIds[k] = orderStructs.orderIds[orderStructs.orderIds.length - 1];
|
|
422
|
+
orderStructs.orderIds.pop();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* UpdateMarkPrice(
|
|
427
|
+
* uint24 indexed perpetualId,
|
|
428
|
+
* int128 fMarkPricePremium,
|
|
429
|
+
* int128 fSpotIndexPrice
|
|
430
|
+
* )
|
|
431
|
+
* @param fMarkPricePremium premium rate in ABDK format
|
|
432
|
+
* @param fSpotIndexPrice spot index price in ABDK format
|
|
433
|
+
* @returns mark price and spot index in float
|
|
434
|
+
*/
|
|
435
|
+
private static ConvertUpdateMarkPrice(fMarkPricePremium: BigNumber, fSpotIndexPrice: BigNumber): [number, number] {
|
|
436
|
+
let fMarkPrice = mul64x64(fSpotIndexPrice, ONE_64x64.add(fMarkPricePremium));
|
|
437
|
+
let markPrice = ABK64x64ToFloat(fMarkPrice);
|
|
438
|
+
let indexPrice = ABK64x64ToFloat(fSpotIndexPrice);
|
|
439
|
+
return [markPrice, indexPrice];
|
|
440
|
+
}
|
|
441
|
+
}
|