@d8x/perpetuals-sdk 0.6.4 → 0.6.5
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/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -1
- package/src/abi/ERC20.json +288 -0
- package/src/abi/IPerpetualManager.json +5888 -0
- package/src/abi/LimitOrderBook.json +1062 -0
- package/src/abi/LimitOrderBookFactory.json +161 -0
- package/src/abi/MockTokenSwap.json +186 -0
- package/src/abi/ShareToken.json +428 -0
- package/src/accountTrade.ts +428 -0
- package/src/brokerTool.ts +555 -0
- package/src/config/defaultConfig.json +62 -0
- package/src/config/mockSwap.json +6 -0
- package/src/config/priceFeedConfig.json +104 -0
- package/src/config/symbolList.json +13 -0
- package/src/contracts/ERC20.ts +444 -0
- package/src/contracts/IPerpetualManager.ts +7227 -0
- package/src/contracts/LimitOrderBook.ts +1251 -0
- package/src/contracts/LimitOrderBookFactory.ts +348 -0
- package/src/contracts/MockTokenSwap.ts +373 -0
- package/src/contracts/ShareToken.ts +695 -0
- package/src/contracts/common.ts +44 -0
- package/src/contracts/factories/ERC20__factory.ts +306 -0
- package/src/contracts/factories/IPerpetualManager__factory.ts +5912 -0
- package/src/contracts/factories/LimitOrderBookFactory__factory.ts +189 -0
- package/src/contracts/factories/LimitOrderBook__factory.ts +1086 -0
- package/src/contracts/factories/MockTokenSwap__factory.ts +207 -0
- package/src/contracts/factories/ShareToken__factory.ts +449 -0
- package/src/contracts/factories/index.ts +9 -0
- package/src/contracts/index.ts +16 -0
- package/src/d8XMath.ts +376 -0
- package/src/index.ts +29 -0
- package/src/liquidatorTool.ts +270 -0
- package/src/liquidityProviderTool.ts +148 -0
- package/src/marketData.ts +1310 -0
- package/src/nodeSDKTypes.ts +332 -0
- package/src/orderReferrerTool.ts +516 -0
- package/src/perpetualDataHandler.ts +1161 -0
- package/src/perpetualEventHandler.ts +455 -0
- package/src/priceFeeds.ts +382 -0
- package/src/traderDigests.ts +86 -0
- package/src/traderInterface.ts +172 -0
- package/src/triangulator.ts +105 -0
- package/src/utils.ts +134 -0
- package/src/version.ts +1 -0
- package/src/writeAccessHandler.ts +139 -0
|
@@ -0,0 +1,1310 @@
|
|
|
1
|
+
import { BigNumber } from "@ethersproject/bignumber";
|
|
2
|
+
import { CallOverrides, Contract } from "@ethersproject/contracts";
|
|
3
|
+
import { Provider, StaticJsonRpcProvider } from "@ethersproject/providers";
|
|
4
|
+
import { formatUnits } from "@ethersproject/units";
|
|
5
|
+
import { ERC20__factory, LimitOrderBook } from "./contracts";
|
|
6
|
+
import { IClientOrder } from "./contracts/LimitOrderBook";
|
|
7
|
+
import {
|
|
8
|
+
ABK64x64ToFloat,
|
|
9
|
+
calculateLiquidationPriceCollateralBase,
|
|
10
|
+
calculateLiquidationPriceCollateralQuanto,
|
|
11
|
+
calculateLiquidationPriceCollateralQuote,
|
|
12
|
+
floatToABK64x64,
|
|
13
|
+
getDepositAmountForLvgTrade,
|
|
14
|
+
dec18ToFloat,
|
|
15
|
+
} from "./d8XMath";
|
|
16
|
+
import {
|
|
17
|
+
BUY_SIDE,
|
|
18
|
+
ClientOrder,
|
|
19
|
+
CLOSED_SIDE,
|
|
20
|
+
COLLATERAL_CURRENCY_BASE,
|
|
21
|
+
COLLATERAL_CURRENCY_QUANTO,
|
|
22
|
+
CollaterlCCY,
|
|
23
|
+
ERC20_ABI,
|
|
24
|
+
ExchangeInfo,
|
|
25
|
+
MarginAccount,
|
|
26
|
+
NodeSDKConfig,
|
|
27
|
+
Order,
|
|
28
|
+
PerpetualState,
|
|
29
|
+
PerpetualStaticInfo,
|
|
30
|
+
PERP_STATE_STR,
|
|
31
|
+
PoolState,
|
|
32
|
+
PoolStaticInfo,
|
|
33
|
+
SELL_SIDE,
|
|
34
|
+
SmartContractOrder,
|
|
35
|
+
ZERO_ADDRESS,
|
|
36
|
+
} from "./nodeSDKTypes";
|
|
37
|
+
import PerpetualDataHandler from "./perpetualDataHandler";
|
|
38
|
+
import PriceFeeds from "./priceFeeds";
|
|
39
|
+
import { contractSymbolToSymbol, toBytes4 } from "./utils";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Functions to access market data (e.g., information on open orders, information on products that can be traded).
|
|
43
|
+
* This class requires no private key and is blockchain read-only.
|
|
44
|
+
* No gas required for the queries here.
|
|
45
|
+
* @extends PerpetualDataHandler
|
|
46
|
+
*/
|
|
47
|
+
export default class MarketData extends PerpetualDataHandler {
|
|
48
|
+
/**
|
|
49
|
+
* Constructor
|
|
50
|
+
* @param {NodeSDKConfig} config Configuration object, see
|
|
51
|
+
* PerpetualDataHandler.readSDKConfig.
|
|
52
|
+
* @example
|
|
53
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
54
|
+
* async function main() {
|
|
55
|
+
* console.log(MarketData);
|
|
56
|
+
* // load configuration for testnet
|
|
57
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
58
|
+
* // MarketData (read only, no authentication needed)
|
|
59
|
+
* let mktData = new MarketData(config);
|
|
60
|
+
* // Create a proxy instance to access the blockchain
|
|
61
|
+
* await mktData.createProxyInstance();
|
|
62
|
+
* }
|
|
63
|
+
* main();
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
public constructor(config: NodeSDKConfig) {
|
|
67
|
+
super(config);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Initialize the marketData-Class with this function
|
|
72
|
+
* to create instance of D8X perpetual contract and gather information
|
|
73
|
+
* about perpetual currencies
|
|
74
|
+
* @param provider optional provider
|
|
75
|
+
*/
|
|
76
|
+
public async createProxyInstance(provider?: Provider, overrides?: CallOverrides): Promise<void> {
|
|
77
|
+
if (provider == undefined) {
|
|
78
|
+
this.provider = new StaticJsonRpcProvider(this.nodeURL);
|
|
79
|
+
} else {
|
|
80
|
+
this.provider = provider;
|
|
81
|
+
}
|
|
82
|
+
await this.initContractsAndData(this.provider, overrides);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the proxy address
|
|
87
|
+
* @returns Address of the perpetual proxy contract
|
|
88
|
+
*/
|
|
89
|
+
public getProxyAddress(): string {
|
|
90
|
+
if (this.proxyContract == null) {
|
|
91
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
92
|
+
}
|
|
93
|
+
return this.proxyContract.address;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert the smart contract output of an order into a convenient format of type "Order"
|
|
98
|
+
* @param smOrder SmartContractOrder, as obtained e.g., by PerpetualLimitOrderCreated event
|
|
99
|
+
* @returns more convenient format of order, type "Order"
|
|
100
|
+
*/
|
|
101
|
+
public smartContractOrderToOrder(smOrder: SmartContractOrder): Order {
|
|
102
|
+
return PerpetualDataHandler.fromSmartContractOrder(smOrder, this.symbolToPerpStaticInfo);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get contract instance. Useful for event listening.
|
|
107
|
+
* @example
|
|
108
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
109
|
+
* async function main() {
|
|
110
|
+
* console.log(MarketData);
|
|
111
|
+
* // setup
|
|
112
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
113
|
+
* let mktData = new MarketData(config);
|
|
114
|
+
* await mktData.createProxyInstance();
|
|
115
|
+
* // Get contract instance
|
|
116
|
+
* let proxy = await mktData.getReadOnlyProxyInstance();
|
|
117
|
+
* console.log(proxy);
|
|
118
|
+
* }
|
|
119
|
+
* main();
|
|
120
|
+
*
|
|
121
|
+
* @returns read-only proxy instance
|
|
122
|
+
*/
|
|
123
|
+
public getReadOnlyProxyInstance(): Contract {
|
|
124
|
+
if (this.proxyContract == null) {
|
|
125
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
126
|
+
}
|
|
127
|
+
return this.proxyContract;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Information about the products traded in the exchange.
|
|
132
|
+
* @example
|
|
133
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
134
|
+
* async function main() {
|
|
135
|
+
* console.log(MarketData);
|
|
136
|
+
* // setup
|
|
137
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
138
|
+
* let mktData = new MarketData(config);
|
|
139
|
+
* await mktData.createProxyInstance();
|
|
140
|
+
* // Get exchange info
|
|
141
|
+
* let info = await mktData.exchangeInfo();
|
|
142
|
+
* console.log(info);
|
|
143
|
+
* }
|
|
144
|
+
* main();
|
|
145
|
+
*
|
|
146
|
+
* @returns {ExchangeInfo} Array of static data for all the pools and perpetuals in the system.
|
|
147
|
+
*/
|
|
148
|
+
public async exchangeInfo(overrides?: CallOverrides): Promise<ExchangeInfo> {
|
|
149
|
+
if (this.proxyContract == null) {
|
|
150
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
151
|
+
}
|
|
152
|
+
return await MarketData._exchangeInfo(
|
|
153
|
+
this.proxyContract,
|
|
154
|
+
this.poolStaticInfos,
|
|
155
|
+
this.symbolToPerpStaticInfo,
|
|
156
|
+
this.perpetualIdToSymbol,
|
|
157
|
+
this.nestedPerpetualIDs,
|
|
158
|
+
this.symbolList,
|
|
159
|
+
this.priceFeedGetter,
|
|
160
|
+
overrides
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* All open orders for a trader-address and a symbol.
|
|
166
|
+
* @param {string} traderAddr Address of the trader for which we get the open orders.
|
|
167
|
+
* @param {string} symbol Symbol of the form ETH-USD-MATIC or a pool symbol.
|
|
168
|
+
* @example
|
|
169
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
170
|
+
* async function main() {
|
|
171
|
+
* console.log(MarketData);
|
|
172
|
+
* // setup
|
|
173
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
174
|
+
* let mktData = new MarketData(config);
|
|
175
|
+
* await mktData.createProxyInstance();
|
|
176
|
+
* // Get all open orders for a trader/symbol
|
|
177
|
+
* let opOrder = await mktData.openOrders("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
|
|
178
|
+
* "ETH-USD-MATIC");
|
|
179
|
+
* console.log(opOrder);
|
|
180
|
+
* }
|
|
181
|
+
* main();
|
|
182
|
+
*
|
|
183
|
+
* @returns For each perpetual an array of open orders and corresponding order-ids.
|
|
184
|
+
*/
|
|
185
|
+
public async openOrders(
|
|
186
|
+
traderAddr: string,
|
|
187
|
+
symbol: string,
|
|
188
|
+
overrides?: CallOverrides
|
|
189
|
+
): Promise<{ orders: Order[]; orderIds: string[] }[]> {
|
|
190
|
+
// open orders requested only for given symbol
|
|
191
|
+
let resArray: Array<{ orders: Order[]; orderIds: string[] }> = [];
|
|
192
|
+
if (symbol.split("-").length == 1) {
|
|
193
|
+
// pool symbol
|
|
194
|
+
const symbols = this.getPerpetualSymbolsInPool(symbol);
|
|
195
|
+
let prom: Array<Promise<{ orders: Order[]; orderIds: string[] }>> = [];
|
|
196
|
+
for (let k = 0; k < symbols.length; k++) {
|
|
197
|
+
let p = this._openOrdersOfPerpetual(traderAddr, symbols[k], overrides);
|
|
198
|
+
prom.push(p);
|
|
199
|
+
}
|
|
200
|
+
resArray = await Promise.all(prom);
|
|
201
|
+
} else {
|
|
202
|
+
let res = await this._openOrdersOfPerpetual(traderAddr, symbol, overrides);
|
|
203
|
+
resArray.push(res!);
|
|
204
|
+
}
|
|
205
|
+
return resArray;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* All open orders for a trader-address and a given perpetual symbol.
|
|
210
|
+
* @param {string} traderAddr Address of the trader for which we get the open orders.
|
|
211
|
+
* @param {string} symbol perpetual-symbol of the form ETH-USD-MATIC
|
|
212
|
+
* @returns open orders and order ids
|
|
213
|
+
*/
|
|
214
|
+
private async _openOrdersOfPerpetual(
|
|
215
|
+
traderAddr: string,
|
|
216
|
+
symbol: string,
|
|
217
|
+
overrides?: CallOverrides
|
|
218
|
+
): Promise<{ orders: Order[]; orderIds: string[] }> {
|
|
219
|
+
// open orders requested only for given symbol
|
|
220
|
+
let orderBookContract = this.getOrderBookContract(symbol);
|
|
221
|
+
let [orders, digests] = await Promise.all([
|
|
222
|
+
this.openOrdersOnOrderBook(traderAddr, orderBookContract, overrides),
|
|
223
|
+
MarketData.orderIdsOfTrader(traderAddr, orderBookContract, overrides),
|
|
224
|
+
]);
|
|
225
|
+
return { orders: orders, orderIds: digests };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Information about the position open by a given trader in a given perpetual contract, or
|
|
230
|
+
* for all perpetuals in a pool
|
|
231
|
+
* @param {string} traderAddr Address of the trader for which we get the position risk.
|
|
232
|
+
* @param {string} symbol Symbol of the form ETH-USD-MATIC or pool symbol ("MATIC")
|
|
233
|
+
* @example
|
|
234
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
235
|
+
* async function main() {
|
|
236
|
+
* console.log(MarketData);
|
|
237
|
+
* // setup
|
|
238
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
239
|
+
* let mktData = new MarketData(config);
|
|
240
|
+
* await mktData.createProxyInstance();
|
|
241
|
+
* // Get position risk info
|
|
242
|
+
* let posRisk = await mktData.positionRisk("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
|
|
243
|
+
* "ETH-USD-MATIC");
|
|
244
|
+
* console.log(posRisk);
|
|
245
|
+
* }
|
|
246
|
+
* main();
|
|
247
|
+
*
|
|
248
|
+
* @returns {MarginAccount[]} Array of position risks of trader.
|
|
249
|
+
*/
|
|
250
|
+
public async positionRisk(traderAddr: string, symbol: string, overrides?: CallOverrides): Promise<MarginAccount[]> {
|
|
251
|
+
if (this.proxyContract == null) {
|
|
252
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
253
|
+
}
|
|
254
|
+
let resArray: Array<MarginAccount> = [];
|
|
255
|
+
if (symbol.split("-").length == 1) {
|
|
256
|
+
// pool symbol
|
|
257
|
+
const symbols = this.getPerpetualSymbolsInPool(symbol);
|
|
258
|
+
let prom: Array<Promise<MarginAccount>> = [];
|
|
259
|
+
for (let k = 0; k < symbols.length; k++) {
|
|
260
|
+
let p = this._positionRiskForTraderInPerpetual(traderAddr, symbols[k], overrides);
|
|
261
|
+
prom.push(p);
|
|
262
|
+
}
|
|
263
|
+
resArray = await Promise.all(prom);
|
|
264
|
+
} else {
|
|
265
|
+
let res = await this._positionRiskForTraderInPerpetual(traderAddr, symbol, overrides);
|
|
266
|
+
resArray.push(res!);
|
|
267
|
+
}
|
|
268
|
+
return resArray;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Information about the position open by a given trader in a given perpetual contract.
|
|
273
|
+
* @param {string} traderAddr Address of the trader for which we get the position risk.
|
|
274
|
+
* @param {string} symbol perpetual symbol of the form ETH-USD-MATIC
|
|
275
|
+
* @returns MarginAccount struct for the trader
|
|
276
|
+
*/
|
|
277
|
+
private async _positionRiskForTraderInPerpetual(
|
|
278
|
+
traderAddr: string,
|
|
279
|
+
symbol: string,
|
|
280
|
+
overrides?: CallOverrides
|
|
281
|
+
): Promise<MarginAccount> {
|
|
282
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
|
|
283
|
+
let mgnAcct = await PerpetualDataHandler.getMarginAccount(
|
|
284
|
+
traderAddr,
|
|
285
|
+
symbol,
|
|
286
|
+
this.symbolToPerpStaticInfo,
|
|
287
|
+
this.proxyContract!,
|
|
288
|
+
[obj.idxPrices[0], obj.idxPrices[1]],
|
|
289
|
+
overrides
|
|
290
|
+
);
|
|
291
|
+
return mgnAcct;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Estimates what the position risk will be if a given order is executed.
|
|
296
|
+
* @param traderAddr Address of trader
|
|
297
|
+
* @param order Order to be submitted
|
|
298
|
+
* @param account Position risk before trade
|
|
299
|
+
* @param indexPriceInfo Index prices and market status (open/closed)
|
|
300
|
+
* @returns Position risk after trade
|
|
301
|
+
*/
|
|
302
|
+
public async positionRiskOnTrade(
|
|
303
|
+
traderAddr: string,
|
|
304
|
+
order: Order,
|
|
305
|
+
account?: MarginAccount,
|
|
306
|
+
indexPriceInfo?: [number, number, boolean, boolean],
|
|
307
|
+
overrides?: CallOverrides
|
|
308
|
+
): Promise<{ newPositionRisk: MarginAccount; orderCost: number }> {
|
|
309
|
+
if (this.proxyContract == null) {
|
|
310
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
311
|
+
}
|
|
312
|
+
// fetch undefined data
|
|
313
|
+
if (account == undefined) {
|
|
314
|
+
account = (await this.positionRisk(traderAddr, order.symbol, overrides))[0];
|
|
315
|
+
}
|
|
316
|
+
if (indexPriceInfo == undefined) {
|
|
317
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
|
|
318
|
+
indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let lotSizeBC = MarketData._getLotSize(account.symbol, this.symbolToPerpStaticInfo);
|
|
322
|
+
// Too small, no change to account
|
|
323
|
+
if (Math.abs(order.quantity) < lotSizeBC) {
|
|
324
|
+
return { newPositionRisk: account, orderCost: 0 };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Current state:
|
|
328
|
+
// perp (for FXs and such)
|
|
329
|
+
let perpetualState = await this.getPerpetualState(order.symbol, indexPriceInfo, overrides);
|
|
330
|
+
let [S2, S3, Sm] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice, perpetualState.markPrice];
|
|
331
|
+
// cash in margin account: upon trading, unpaid funding will be realized
|
|
332
|
+
let currentMarginCashCC = account.collateralCC;
|
|
333
|
+
// signed position, still correct if side is closed (==0)
|
|
334
|
+
let currentPositionBC = (account.side == BUY_SIDE ? 1 : -1) * account.positionNotionalBaseCCY;
|
|
335
|
+
// signed locked-in value
|
|
336
|
+
let currentLockedInQC = account.entryPrice * currentPositionBC;
|
|
337
|
+
|
|
338
|
+
// New trader state:
|
|
339
|
+
// signed trade amount
|
|
340
|
+
let tradeAmountBC = Math.abs(order.quantity) * (order.side == BUY_SIDE ? 1 : -1);
|
|
341
|
+
// signed position
|
|
342
|
+
let newPositionBC = currentPositionBC + tradeAmountBC;
|
|
343
|
+
if (Math.abs(newPositionBC) < 10 * lotSizeBC) {
|
|
344
|
+
// fully closed
|
|
345
|
+
tradeAmountBC = -currentPositionBC;
|
|
346
|
+
newPositionBC = 0;
|
|
347
|
+
}
|
|
348
|
+
let newSide = newPositionBC > 0 ? BUY_SIDE : newPositionBC < 0 ? SELL_SIDE : CLOSED_SIDE;
|
|
349
|
+
|
|
350
|
+
// price for this order = limit price (conservative) if given, else the current perp price
|
|
351
|
+
let tradePrice =
|
|
352
|
+
order.limitPrice ??
|
|
353
|
+
(await this.getPerpetualPrice(order.symbol, tradeAmountBC, [indexPriceInfo[0], indexPriceInfo[1]], overrides));
|
|
354
|
+
|
|
355
|
+
// fees
|
|
356
|
+
let poolId = PerpetualDataHandler._getPoolIdFromSymbol(order.symbol, this.poolStaticInfos);
|
|
357
|
+
let exchangeFeeTbps = await this.proxyContract.queryExchangeFee(
|
|
358
|
+
poolId,
|
|
359
|
+
traderAddr,
|
|
360
|
+
order.brokerAddr ?? ZERO_ADDRESS,
|
|
361
|
+
overrides || {}
|
|
362
|
+
);
|
|
363
|
+
let exchangeFeeCC = (Math.abs(tradeAmountBC) * exchangeFeeTbps * 1e-5 * S2) / S3;
|
|
364
|
+
let brokerFeeCC = (Math.abs(tradeAmountBC) * (order.brokerFeeTbps ?? 0) * 1e-5 * S2) / S3;
|
|
365
|
+
let referralFeeCC = this.symbolToPerpStaticInfo.get(account.symbol)!.referralRebate;
|
|
366
|
+
// Trade type:
|
|
367
|
+
let isClose = newPositionBC == 0 || newPositionBC * tradeAmountBC < 0;
|
|
368
|
+
let isOpen = newPositionBC != 0 && (currentPositionBC == 0 || tradeAmountBC * currentPositionBC > 0); // regular open, no flip
|
|
369
|
+
let isFlip = Math.abs(newPositionBC) > Math.abs(currentPositionBC) && !isOpen; // flip position sign, not fully closed
|
|
370
|
+
let keepPositionLvgOnClose = (order.keepPositionLvg ?? false) && !isOpen;
|
|
371
|
+
|
|
372
|
+
// Contract: _doMarginCollateralActions
|
|
373
|
+
// No collateral actions if
|
|
374
|
+
// 1) leverage is not set or
|
|
375
|
+
// 2) fully closed after trade or
|
|
376
|
+
// 3) is a partial closing, it doesn't flip, and keep lvg flag is not set
|
|
377
|
+
let traderDepositCC: number;
|
|
378
|
+
let targetLvg: number;
|
|
379
|
+
if (order.leverage == undefined || newPositionBC == 0 || (!isOpen && !isFlip && !keepPositionLvgOnClose)) {
|
|
380
|
+
traderDepositCC = 0;
|
|
381
|
+
targetLvg = 0;
|
|
382
|
+
} else {
|
|
383
|
+
// 1) opening and flipping trades need to specify a leverage: default to max if not given
|
|
384
|
+
// 2) for others it's ignored, set target to 0
|
|
385
|
+
let initialMarginRate = this.symbolToPerpStaticInfo.get(account.symbol)!.initialMarginRate;
|
|
386
|
+
targetLvg = isFlip || isOpen ? order.leverage ?? 1 / initialMarginRate : 0;
|
|
387
|
+
let [b0, pos0] = isOpen ? [0, 0] : [account.collateralCC, currentPositionBC];
|
|
388
|
+
traderDepositCC = getDepositAmountForLvgTrade(b0, pos0, tradeAmountBC, targetLvg, tradePrice, S3, Sm);
|
|
389
|
+
// fees are paid from wallet in this case
|
|
390
|
+
traderDepositCC += exchangeFeeCC + brokerFeeCC + referralFeeCC;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Contract: _executeTrade
|
|
394
|
+
let deltaCashCC = (-tradeAmountBC * (tradePrice - S2)) / S3;
|
|
395
|
+
let deltaLockedQC = tradeAmountBC * S2;
|
|
396
|
+
if (isClose) {
|
|
397
|
+
let pnl = account.entryPrice * tradeAmountBC - deltaLockedQC;
|
|
398
|
+
deltaLockedQC += pnl;
|
|
399
|
+
deltaCashCC += pnl / S3;
|
|
400
|
+
}
|
|
401
|
+
// funding and fees
|
|
402
|
+
deltaCashCC = deltaCashCC + account.unrealizedFundingCollateralCCY - exchangeFeeCC - brokerFeeCC - referralFeeCC;
|
|
403
|
+
|
|
404
|
+
// New cash, locked-in, entry price & leverage after trade
|
|
405
|
+
let newLockedInValueQC = currentLockedInQC + deltaLockedQC;
|
|
406
|
+
let newMarginCashCC = currentMarginCashCC + deltaCashCC + traderDepositCC;
|
|
407
|
+
let newEntryPrice = newPositionBC == 0 ? 0 : Math.abs(newLockedInValueQC / newPositionBC);
|
|
408
|
+
let newMarginBalanceCC = newMarginCashCC + (newPositionBC * Sm - newLockedInValueQC) / S3;
|
|
409
|
+
let newLeverage =
|
|
410
|
+
newPositionBC == 0
|
|
411
|
+
? 0
|
|
412
|
+
: newMarginBalanceCC <= 0
|
|
413
|
+
? Infinity
|
|
414
|
+
: (Math.abs(newPositionBC) * Sm) / S3 / newMarginBalanceCC;
|
|
415
|
+
|
|
416
|
+
// Liquidation params
|
|
417
|
+
let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(
|
|
418
|
+
account.symbol,
|
|
419
|
+
newLockedInValueQC,
|
|
420
|
+
newPositionBC,
|
|
421
|
+
newMarginCashCC,
|
|
422
|
+
Sm,
|
|
423
|
+
S3,
|
|
424
|
+
this.symbolToPerpStaticInfo
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
// New position risk
|
|
428
|
+
let newPositionRisk: MarginAccount = {
|
|
429
|
+
symbol: account.symbol,
|
|
430
|
+
positionNotionalBaseCCY: Math.abs(newPositionBC),
|
|
431
|
+
side: newSide,
|
|
432
|
+
entryPrice: newEntryPrice,
|
|
433
|
+
leverage: newLeverage,
|
|
434
|
+
markPrice: Sm,
|
|
435
|
+
unrealizedPnlQuoteCCY: newPositionBC * Sm - newLockedInValueQC,
|
|
436
|
+
unrealizedFundingCollateralCCY: 0,
|
|
437
|
+
collateralCC: newMarginCashCC,
|
|
438
|
+
collToQuoteConversion: S3,
|
|
439
|
+
liquidationPrice: [S2Liq, S3Liq],
|
|
440
|
+
liquidationLvg: 1 / tau,
|
|
441
|
+
};
|
|
442
|
+
return { newPositionRisk: newPositionRisk, orderCost: traderDepositCC };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Estimates what the position risk will be if given amount of collateral is added/removed from the account.
|
|
447
|
+
* @param traderAddr Address of trader
|
|
448
|
+
* @param deltaCollateral Amount of collateral to add or remove (signed)
|
|
449
|
+
* @param currentPositionRisk Position risk before
|
|
450
|
+
* @returns {MarginAccount} Position risk after
|
|
451
|
+
*/
|
|
452
|
+
public async positionRiskOnCollateralAction(
|
|
453
|
+
deltaCollateral: number,
|
|
454
|
+
account: MarginAccount,
|
|
455
|
+
indexPriceInfo?: [number, number, boolean, boolean],
|
|
456
|
+
overrides?: CallOverrides
|
|
457
|
+
): Promise<MarginAccount> {
|
|
458
|
+
if (this.proxyContract == null) {
|
|
459
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
460
|
+
}
|
|
461
|
+
if (deltaCollateral + account.collateralCC + account.unrealizedFundingCollateralCCY < 0) {
|
|
462
|
+
throw Error("not enough margin to remove");
|
|
463
|
+
}
|
|
464
|
+
if (indexPriceInfo == undefined) {
|
|
465
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
|
|
466
|
+
indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
|
|
467
|
+
}
|
|
468
|
+
let perpetualState = await this.getPerpetualState(account.symbol, indexPriceInfo, overrides);
|
|
469
|
+
let [S2, S3, Sm] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice, perpetualState.markPrice];
|
|
470
|
+
|
|
471
|
+
// no position: just increase collateral and kill liquidation vars
|
|
472
|
+
if (account.positionNotionalBaseCCY == 0) {
|
|
473
|
+
return {
|
|
474
|
+
symbol: account.symbol,
|
|
475
|
+
positionNotionalBaseCCY: account.positionNotionalBaseCCY,
|
|
476
|
+
side: account.side,
|
|
477
|
+
entryPrice: account.entryPrice,
|
|
478
|
+
leverage: account.leverage,
|
|
479
|
+
markPrice: Sm,
|
|
480
|
+
unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
|
|
481
|
+
unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
|
|
482
|
+
collateralCC: account.collateralCC + deltaCollateral,
|
|
483
|
+
collToQuoteConversion: S3,
|
|
484
|
+
liquidationPrice: [0, undefined],
|
|
485
|
+
liquidationLvg: Infinity,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let positionBC = account.positionNotionalBaseCCY * (account.side == BUY_SIDE ? 1 : -1);
|
|
490
|
+
let lockedInQC = account.entryPrice * positionBC;
|
|
491
|
+
let newMarginCashCC = account.collateralCC + deltaCollateral;
|
|
492
|
+
let newMarginBalanceCC =
|
|
493
|
+
newMarginCashCC + account.unrealizedFundingCollateralCCY + (positionBC * Sm - lockedInQC) / S3;
|
|
494
|
+
if (newMarginBalanceCC <= 0) {
|
|
495
|
+
return {
|
|
496
|
+
symbol: account.symbol,
|
|
497
|
+
positionNotionalBaseCCY: account.positionNotionalBaseCCY,
|
|
498
|
+
side: account.side,
|
|
499
|
+
entryPrice: account.entryPrice,
|
|
500
|
+
leverage: Infinity,
|
|
501
|
+
markPrice: Sm,
|
|
502
|
+
unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
|
|
503
|
+
unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
|
|
504
|
+
collateralCC: newMarginCashCC,
|
|
505
|
+
collToQuoteConversion: S3,
|
|
506
|
+
liquidationPrice: [S2, S3],
|
|
507
|
+
liquidationLvg: 0,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
let newLeverage = (Math.abs(positionBC) * Sm) / S3 / newMarginBalanceCC;
|
|
511
|
+
|
|
512
|
+
// Liquidation params
|
|
513
|
+
let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(
|
|
514
|
+
account.symbol,
|
|
515
|
+
lockedInQC,
|
|
516
|
+
positionBC,
|
|
517
|
+
newMarginCashCC,
|
|
518
|
+
Sm,
|
|
519
|
+
S3,
|
|
520
|
+
this.symbolToPerpStaticInfo
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
// New position risk
|
|
524
|
+
let newPositionRisk: MarginAccount = {
|
|
525
|
+
symbol: account.symbol,
|
|
526
|
+
positionNotionalBaseCCY: account.positionNotionalBaseCCY,
|
|
527
|
+
side: account.side,
|
|
528
|
+
entryPrice: account.entryPrice,
|
|
529
|
+
leverage: newLeverage,
|
|
530
|
+
markPrice: Sm,
|
|
531
|
+
unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
|
|
532
|
+
unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
|
|
533
|
+
collateralCC: newMarginCashCC,
|
|
534
|
+
collToQuoteConversion: S3,
|
|
535
|
+
liquidationPrice: [S2Liq, S3Liq],
|
|
536
|
+
liquidationLvg: 1 / tau,
|
|
537
|
+
};
|
|
538
|
+
return newPositionRisk;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
protected static _getLiquidationParams(
|
|
542
|
+
symbol: string,
|
|
543
|
+
lockedInQC: number,
|
|
544
|
+
signedPositionBC: number,
|
|
545
|
+
marginCashCC: number,
|
|
546
|
+
markPrice: number,
|
|
547
|
+
collToQuoteConversion: number,
|
|
548
|
+
symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
|
|
549
|
+
): [number, number | undefined, number] {
|
|
550
|
+
let S2Liq: number, S3Liq: number | undefined;
|
|
551
|
+
let tau = symbolToPerpStaticInfo.get(symbol)!.maintenanceMarginRate;
|
|
552
|
+
let ccyType = symbolToPerpStaticInfo.get(symbol)!.collateralCurrencyType;
|
|
553
|
+
if (ccyType == CollaterlCCY.BASE) {
|
|
554
|
+
S2Liq = calculateLiquidationPriceCollateralBase(lockedInQC, signedPositionBC, marginCashCC, tau);
|
|
555
|
+
S3Liq = S2Liq;
|
|
556
|
+
} else if (ccyType == CollaterlCCY.QUANTO) {
|
|
557
|
+
S3Liq = collToQuoteConversion;
|
|
558
|
+
S2Liq = calculateLiquidationPriceCollateralQuanto(
|
|
559
|
+
lockedInQC,
|
|
560
|
+
signedPositionBC,
|
|
561
|
+
marginCashCC,
|
|
562
|
+
tau,
|
|
563
|
+
collToQuoteConversion,
|
|
564
|
+
markPrice
|
|
565
|
+
);
|
|
566
|
+
} else {
|
|
567
|
+
S2Liq = calculateLiquidationPriceCollateralQuote(lockedInQC, signedPositionBC, marginCashCC, tau);
|
|
568
|
+
}
|
|
569
|
+
// floor at 0
|
|
570
|
+
S2Liq = S2Liq < 0 ? 0 : S2Liq;
|
|
571
|
+
S3Liq = S3Liq && S3Liq < 0 ? 0 : S3Liq;
|
|
572
|
+
return [S2Liq, S3Liq, tau];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Gets the wallet balance in the collateral currency corresponding to a given perpetual symbol.
|
|
577
|
+
* @param address Address to check
|
|
578
|
+
* @param symbol Symbol of the form ETH-USD-MATIC.
|
|
579
|
+
* @returns Balance
|
|
580
|
+
*/
|
|
581
|
+
public async getWalletBalance(address: string, symbol: string, overrides?: CallOverrides): Promise<number> {
|
|
582
|
+
let poolIdx = this.getPoolStaticInfoIndexFromSymbol(symbol);
|
|
583
|
+
let marginTokenAddr = this.poolStaticInfos[poolIdx].poolMarginTokenAddr;
|
|
584
|
+
let token = ERC20__factory.connect(marginTokenAddr, this.provider!);
|
|
585
|
+
let walletBalance = await token.balanceOf(address, overrides || {});
|
|
586
|
+
let decimals = await token.decimals(overrides || {});
|
|
587
|
+
return Number(formatUnits(walletBalance, decimals));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Get the address' balance of the pool share token
|
|
592
|
+
* @param address address of the liquidity provider
|
|
593
|
+
* @param symbolOrId Symbol of the form ETH-USD-MATIC, or MATIC (collateral only), or Pool-Id
|
|
594
|
+
*/
|
|
595
|
+
public async getPoolShareTokenBalance(
|
|
596
|
+
address: string,
|
|
597
|
+
symbolOrId: string | number,
|
|
598
|
+
overrides?: CallOverrides
|
|
599
|
+
): Promise<number> {
|
|
600
|
+
let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
|
|
601
|
+
return this._getPoolShareTokenBalanceFromId(address, poolId, overrides);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Query the pool share token holdings of address
|
|
606
|
+
* @param address address of token holder
|
|
607
|
+
* @param poolId pool id
|
|
608
|
+
* @returns pool share token balance of address
|
|
609
|
+
*/
|
|
610
|
+
private async _getPoolShareTokenBalanceFromId(
|
|
611
|
+
address: string,
|
|
612
|
+
poolId: number,
|
|
613
|
+
overrides?: CallOverrides
|
|
614
|
+
): Promise<number> {
|
|
615
|
+
let shareTokenAddr = this.poolStaticInfos[poolId - 1].shareTokenAddr;
|
|
616
|
+
let shareToken = ERC20__factory.connect(shareTokenAddr, this.provider!);
|
|
617
|
+
let d18ShareTokenBalanceOfAddr = await shareToken.balanceOf(address, overrides || {});
|
|
618
|
+
return dec18ToFloat(d18ShareTokenBalanceOfAddr);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Value of pool token in collateral currency
|
|
623
|
+
* @param symbolOrId symbol of the form ETH-USD-MATIC, MATIC (collateral), or poolId
|
|
624
|
+
* @returns current pool share token price in collateral currency
|
|
625
|
+
*/
|
|
626
|
+
public async getShareTokenPrice(symbolOrId: string | number, overrides?: CallOverrides): Promise<number> {
|
|
627
|
+
let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
|
|
628
|
+
const priceDec18 = await this.proxyContract!.getShareTokenPriceD18(poolId, overrides || {});
|
|
629
|
+
const price = dec18ToFloat(priceDec18);
|
|
630
|
+
return price;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Value of the pool share tokens for this liquidity provider
|
|
635
|
+
* in poolSymbol-currency (e.g. MATIC, USDC).
|
|
636
|
+
* @param address address of liquidity provider
|
|
637
|
+
* @param symbolOrId symbol of the form ETH-USD-MATIC, MATIC (collateral), or poolId
|
|
638
|
+
* @example
|
|
639
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
640
|
+
* async function main() {
|
|
641
|
+
* console.log(MarketData);
|
|
642
|
+
* // setup (authentication required, PK is an environment variable with a private key)
|
|
643
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
644
|
+
* let md = new MarketData(config);
|
|
645
|
+
* await md.createProxyInstance();
|
|
646
|
+
* // get value of pool share token
|
|
647
|
+
* let shareToken = await md.getParticipationValue(myaddress, "MATIC");
|
|
648
|
+
* console.log(shareToken);
|
|
649
|
+
* }
|
|
650
|
+
* main();
|
|
651
|
+
* @returns the value (in collateral tokens) of the pool share, #share tokens, shareTokenAddress
|
|
652
|
+
*/
|
|
653
|
+
public async getParticipationValue(
|
|
654
|
+
address: string,
|
|
655
|
+
symbolOrId: string | number,
|
|
656
|
+
overrides?: CallOverrides
|
|
657
|
+
): Promise<{ value: number; shareTokenBalance: number; poolShareToken: string }> {
|
|
658
|
+
let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
|
|
659
|
+
const shareTokens = await this._getPoolShareTokenBalanceFromId(address, poolId, overrides);
|
|
660
|
+
const priceDec18 = await this.proxyContract!.getShareTokenPriceD18(poolId, overrides || {});
|
|
661
|
+
const price = dec18ToFloat(priceDec18);
|
|
662
|
+
const value = price * shareTokens;
|
|
663
|
+
const shareTokenAddr = this.poolStaticInfos[poolId - 1].shareTokenAddr;
|
|
664
|
+
return {
|
|
665
|
+
value: value,
|
|
666
|
+
shareTokenBalance: shareTokens,
|
|
667
|
+
poolShareToken: shareTokenAddr,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private _poolSymbolOrIdToPoolId(poolSymbolOrId: string | number): number {
|
|
672
|
+
if (this.proxyContract == null || this.poolStaticInfos.length == 0) {
|
|
673
|
+
throw Error("no proxy contract or wallet or data initialized. Use createProxyInstance().");
|
|
674
|
+
}
|
|
675
|
+
let poolId: number;
|
|
676
|
+
if (isNaN(Number(poolSymbolOrId))) {
|
|
677
|
+
poolId = PerpetualDataHandler._getPoolIdFromSymbol(poolSymbolOrId as string, this.poolStaticInfos);
|
|
678
|
+
} else {
|
|
679
|
+
poolId = Number(poolSymbolOrId);
|
|
680
|
+
}
|
|
681
|
+
return poolId;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Gets the maximal order size to open positions (increase size),
|
|
686
|
+
* considering the existing position, state of the perpetual
|
|
687
|
+
* Ignores users wallet balance.
|
|
688
|
+
* @param side BUY or SELL
|
|
689
|
+
* @param positionRisk Current position risk (as seen in positionRisk)
|
|
690
|
+
* @returns Maximal trade size, not signed
|
|
691
|
+
*/
|
|
692
|
+
public async maxOrderSizeForTrader(
|
|
693
|
+
side: string,
|
|
694
|
+
positionRisk: MarginAccount,
|
|
695
|
+
overrides?: CallOverrides
|
|
696
|
+
): Promise<number> {
|
|
697
|
+
let curPosition = side == BUY_SIDE ? positionRisk.positionNotionalBaseCCY : -positionRisk.positionNotionalBaseCCY;
|
|
698
|
+
let perpId = this.getPerpIdFromSymbol(positionRisk.symbol);
|
|
699
|
+
let perpMaxPositionABK = await this.proxyContract!.getMaxSignedOpenTradeSizeForPos(
|
|
700
|
+
perpId,
|
|
701
|
+
floatToABK64x64(curPosition),
|
|
702
|
+
side == BUY_SIDE,
|
|
703
|
+
overrides || {}
|
|
704
|
+
);
|
|
705
|
+
return ABK64x64ToFloat(perpMaxPositionABK.abs());
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
*
|
|
710
|
+
* @param side BUY_SIDE or SELL_SIDE
|
|
711
|
+
* @param symbol of the form ETH-USD-MATIC.
|
|
712
|
+
* @returns signed maximal position size in base currency
|
|
713
|
+
*/
|
|
714
|
+
public async maxSignedPosition(side: string, symbol: string, overrides?: CallOverrides): Promise<number> {
|
|
715
|
+
let perpId = this.getPerpIdFromSymbol(symbol);
|
|
716
|
+
let isBuy = side == BUY_SIDE;
|
|
717
|
+
let maxSignedPos = await this.proxyContract!.getMaxSignedOpenTradeSizeForPos(
|
|
718
|
+
perpId,
|
|
719
|
+
BigNumber.from(0),
|
|
720
|
+
isBuy,
|
|
721
|
+
overrides || {}
|
|
722
|
+
);
|
|
723
|
+
return ABK64x64ToFloat(maxSignedPos);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Uses the Oracle(s) in the exchange to get the latest price of a given index in a given currency, if a route exists.
|
|
728
|
+
* @param {string} base Index name, e.g. ETH.
|
|
729
|
+
* @param {string} quote Quote currency, e.g. USD.
|
|
730
|
+
* @example
|
|
731
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
732
|
+
* async function main() {
|
|
733
|
+
* console.log(MarketData);
|
|
734
|
+
* // setup
|
|
735
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
736
|
+
* let mktData = new MarketData(config);
|
|
737
|
+
* await mktData.createProxyInstance();
|
|
738
|
+
* // get oracle price
|
|
739
|
+
* let price = await mktData.getOraclePrice("ETH", "USD");
|
|
740
|
+
* console.log(price);
|
|
741
|
+
* }
|
|
742
|
+
* main();
|
|
743
|
+
*
|
|
744
|
+
* @returns {number} Price of index in given currency.
|
|
745
|
+
*/
|
|
746
|
+
public async getOraclePrice(base: string, quote: string, overrides?: CallOverrides): Promise<number | undefined> {
|
|
747
|
+
if (!this.proxyContract) {
|
|
748
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
749
|
+
}
|
|
750
|
+
let px = await this.proxyContract.getOraclePrice([toBytes4(base), toBytes4(quote)], overrides || {});
|
|
751
|
+
return px == undefined ? undefined : ABK64x64ToFloat(px);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
*
|
|
756
|
+
* @param symbol Symbol of the form ETH-USD-MATIC
|
|
757
|
+
* @param orderId Order Id
|
|
758
|
+
* @param overrides
|
|
759
|
+
* @returns Order status ()
|
|
760
|
+
*/
|
|
761
|
+
public async getOrderStatus(symbol: string, orderId: string, overrides?: CallOverrides): Promise<number> {
|
|
762
|
+
if (!this.proxyContract) {
|
|
763
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
764
|
+
}
|
|
765
|
+
const orderBookContract = this.getOrderBookContract(symbol);
|
|
766
|
+
let status = await orderBookContract.getOrderStatus(orderId, overrides || {});
|
|
767
|
+
return status;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Get the current mark price
|
|
772
|
+
* @param symbol symbol of the form ETH-USD-MATIC
|
|
773
|
+
* @example
|
|
774
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
775
|
+
* async function main() {
|
|
776
|
+
* console.log(MarketData);
|
|
777
|
+
* // setup
|
|
778
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
779
|
+
* let mktData = new MarketData(config);
|
|
780
|
+
* await mktData.createProxyInstance();
|
|
781
|
+
* // get mark price
|
|
782
|
+
* let price = await mktData.getMarkPrice("ETH-USD-MATIC");
|
|
783
|
+
* console.log(price);
|
|
784
|
+
* }
|
|
785
|
+
* main();
|
|
786
|
+
*
|
|
787
|
+
* @returns mark price
|
|
788
|
+
*/
|
|
789
|
+
public async getMarkPrice(symbol: string, indexPrices?: [number, number]): Promise<number> {
|
|
790
|
+
if (this.proxyContract == null) {
|
|
791
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
792
|
+
}
|
|
793
|
+
if (indexPrices == undefined) {
|
|
794
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
|
|
795
|
+
indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
|
|
796
|
+
}
|
|
797
|
+
return await PerpetualDataHandler._queryPerpetualMarkPrice(
|
|
798
|
+
symbol,
|
|
799
|
+
this.symbolToPerpStaticInfo,
|
|
800
|
+
this.proxyContract,
|
|
801
|
+
indexPrices
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* get the current price for a given quantity
|
|
807
|
+
* @param symbol symbol of the form ETH-USD-MATIC
|
|
808
|
+
* @param quantity quantity to be traded, negative if short
|
|
809
|
+
* @example
|
|
810
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
811
|
+
* async function main() {
|
|
812
|
+
* console.log(MarketData);
|
|
813
|
+
* // setup
|
|
814
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
815
|
+
* let mktData = new MarketData(config);
|
|
816
|
+
* await mktData.createProxyInstance();
|
|
817
|
+
* // get perpetual price
|
|
818
|
+
* let price = await mktData.getPerpetualPrice("ETH-USD-MATIC", 1);
|
|
819
|
+
* console.log(price);
|
|
820
|
+
* }
|
|
821
|
+
* main();
|
|
822
|
+
*
|
|
823
|
+
* @returns price (number)
|
|
824
|
+
*/
|
|
825
|
+
public async getPerpetualPrice(
|
|
826
|
+
symbol: string,
|
|
827
|
+
quantity: number,
|
|
828
|
+
indexPrices?: [number, number],
|
|
829
|
+
overrides?: CallOverrides
|
|
830
|
+
): Promise<number> {
|
|
831
|
+
if (this.proxyContract == null) {
|
|
832
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
833
|
+
}
|
|
834
|
+
if (indexPrices == undefined) {
|
|
835
|
+
// fetch from API
|
|
836
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
|
|
837
|
+
indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
|
|
838
|
+
}
|
|
839
|
+
return await PerpetualDataHandler._queryPerpetualPrice(
|
|
840
|
+
symbol,
|
|
841
|
+
quantity,
|
|
842
|
+
this.symbolToPerpStaticInfo,
|
|
843
|
+
this.proxyContract,
|
|
844
|
+
indexPrices,
|
|
845
|
+
overrides
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Query recent perpetual state from blockchain
|
|
851
|
+
* @param symbol symbol of the form ETH-USD-MATIC
|
|
852
|
+
* @param indexPrices S2 and S3 prices/isMarketOpen if not provided fetch via REST API
|
|
853
|
+
* @returns PerpetualState reference
|
|
854
|
+
*/
|
|
855
|
+
public async getPerpetualState(
|
|
856
|
+
symbol: string,
|
|
857
|
+
indexPriceInfo?: [number, number, boolean, boolean],
|
|
858
|
+
overrides?: CallOverrides
|
|
859
|
+
): Promise<PerpetualState> {
|
|
860
|
+
if (this.proxyContract == null) {
|
|
861
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
862
|
+
}
|
|
863
|
+
if (indexPriceInfo == undefined) {
|
|
864
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
|
|
865
|
+
indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
|
|
866
|
+
}
|
|
867
|
+
let state: PerpetualState = await PerpetualDataHandler._queryPerpetualState(
|
|
868
|
+
symbol,
|
|
869
|
+
this.symbolToPerpStaticInfo,
|
|
870
|
+
this.proxyContract,
|
|
871
|
+
indexPriceInfo,
|
|
872
|
+
overrides
|
|
873
|
+
);
|
|
874
|
+
return state;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Query perpetual static info.
|
|
879
|
+
* This information is queried once at createProxyInstance-time and remains static after that.
|
|
880
|
+
* @param symbol symbol of the form ETH-USD-MATIC
|
|
881
|
+
* @returns PerpetualStaticInfo copy.
|
|
882
|
+
*/
|
|
883
|
+
public getPerpetualStaticInfo(symbol: string): PerpetualStaticInfo {
|
|
884
|
+
let perpInfo = this.symbolToPerpStaticInfo.get(symbol);
|
|
885
|
+
if (perpInfo == undefined) {
|
|
886
|
+
throw Error(`Perpetual with symbol ${symbol} not found. Check symbol or use createProxyInstance().`);
|
|
887
|
+
}
|
|
888
|
+
// return new copy, not a reference
|
|
889
|
+
let res: PerpetualStaticInfo = {
|
|
890
|
+
id: perpInfo.id,
|
|
891
|
+
poolId: perpInfo.poolId,
|
|
892
|
+
limitOrderBookAddr: perpInfo.limitOrderBookAddr,
|
|
893
|
+
initialMarginRate: perpInfo.initialMarginRate,
|
|
894
|
+
maintenanceMarginRate: perpInfo.maintenanceMarginRate,
|
|
895
|
+
collateralCurrencyType: perpInfo.collateralCurrencyType,
|
|
896
|
+
S2Symbol: perpInfo.S2Symbol,
|
|
897
|
+
S3Symbol: perpInfo.S3Symbol,
|
|
898
|
+
lotSizeBC: perpInfo.lotSizeBC,
|
|
899
|
+
referralRebate: perpInfo.referralRebate,
|
|
900
|
+
priceIds: perpInfo.priceIds,
|
|
901
|
+
};
|
|
902
|
+
return res;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* get the current mid-price for a perpetual
|
|
907
|
+
* @param symbol symbol of the form ETH-USD-MATIC
|
|
908
|
+
* @example
|
|
909
|
+
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
|
|
910
|
+
* async function main() {
|
|
911
|
+
* console.log(MarketData);
|
|
912
|
+
* // setup
|
|
913
|
+
* const config = PerpetualDataHandler.readSDKConfig("testnet");
|
|
914
|
+
* let mktData = new MarketData(config);
|
|
915
|
+
* await mktData.createProxyInstance();
|
|
916
|
+
* // get perpetual mid price
|
|
917
|
+
* let midPrice = await mktData.getPerpetualMidPrice("ETH-USD-MATIC");
|
|
918
|
+
* console.log(midPrice);
|
|
919
|
+
* }
|
|
920
|
+
* main();
|
|
921
|
+
*
|
|
922
|
+
* @returns {number} price
|
|
923
|
+
*/
|
|
924
|
+
public async getPerpetualMidPrice(symbol: string): Promise<number> {
|
|
925
|
+
if (this.proxyContract == null) {
|
|
926
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
927
|
+
}
|
|
928
|
+
return await this.getPerpetualPrice(symbol, 0);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Query smart contract to get user orders and convert to user friendly order format.
|
|
933
|
+
* @param {string} traderAddr Address of trader.
|
|
934
|
+
* @param {ethers.Contract} orderBookContract Instance of order book.
|
|
935
|
+
* @returns {Order[]} Array of user friendly order struct.
|
|
936
|
+
* @ignore
|
|
937
|
+
*/
|
|
938
|
+
protected async openOrdersOnOrderBook(
|
|
939
|
+
traderAddr: string,
|
|
940
|
+
orderBookContract: LimitOrderBook,
|
|
941
|
+
overrides?: CallOverrides
|
|
942
|
+
): Promise<Order[]> {
|
|
943
|
+
//eliminate empty orders and map to user friendly orders
|
|
944
|
+
let userFriendlyOrders: Order[] = new Array<Order>();
|
|
945
|
+
let haveMoreOrders = true;
|
|
946
|
+
let from = 0;
|
|
947
|
+
const bulkSize = 15;
|
|
948
|
+
while (haveMoreOrders) {
|
|
949
|
+
let orders: IClientOrder.ClientOrderStructOutput[] = await orderBookContract.getOrders(
|
|
950
|
+
traderAddr,
|
|
951
|
+
from,
|
|
952
|
+
bulkSize,
|
|
953
|
+
overrides || {}
|
|
954
|
+
);
|
|
955
|
+
let k = 0;
|
|
956
|
+
while (k < orders.length && orders[k].traderAddr !== ZERO_ADDRESS) {
|
|
957
|
+
userFriendlyOrders.push(PerpetualDataHandler.fromClientOrder(orders[k], this.symbolToPerpStaticInfo));
|
|
958
|
+
k++;
|
|
959
|
+
}
|
|
960
|
+
haveMoreOrders = orders[orders.length - 1].traderAddr !== ZERO_ADDRESS;
|
|
961
|
+
from = from + bulkSize;
|
|
962
|
+
}
|
|
963
|
+
return userFriendlyOrders;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
*
|
|
968
|
+
* @param traderAddr Address of the trader
|
|
969
|
+
* @param orderBookContract Instance of order book contract
|
|
970
|
+
* @returns Array of order-id's
|
|
971
|
+
* @ignore
|
|
972
|
+
*/
|
|
973
|
+
public static async orderIdsOfTrader(
|
|
974
|
+
traderAddr: string,
|
|
975
|
+
orderBookContract: LimitOrderBook,
|
|
976
|
+
overrides?: CallOverrides
|
|
977
|
+
): Promise<string[]> {
|
|
978
|
+
let digestsRaw: string[] = await orderBookContract.limitDigestsOfTrader(traderAddr, 0, 15, overrides || {});
|
|
979
|
+
let k: number = 0;
|
|
980
|
+
let digests: string[] = [];
|
|
981
|
+
while (k < digestsRaw.length && BigNumber.from(digestsRaw[k]).gt(0)) {
|
|
982
|
+
digests.push(digestsRaw[k]);
|
|
983
|
+
k++;
|
|
984
|
+
}
|
|
985
|
+
return digests;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Query the available margin conditional on the given (or current) index prices
|
|
990
|
+
* Result is in collateral currency
|
|
991
|
+
* @param traderAddr address of the trader
|
|
992
|
+
* @param symbol perpetual symbol of the form BTC-USD-MATIC
|
|
993
|
+
* @param indexPrices optional index prices, will otherwise fetch from REST API
|
|
994
|
+
* @returns available margin in collateral currency
|
|
995
|
+
*/
|
|
996
|
+
public async getAvailableMargin(
|
|
997
|
+
traderAddr: string,
|
|
998
|
+
symbol: string,
|
|
999
|
+
indexPrices?: [number, number],
|
|
1000
|
+
overrides?: CallOverrides
|
|
1001
|
+
): Promise<number> {
|
|
1002
|
+
if (!this.proxyContract) {
|
|
1003
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (indexPrices == undefined) {
|
|
1007
|
+
// fetch from API
|
|
1008
|
+
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
|
|
1009
|
+
indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
|
|
1010
|
+
}
|
|
1011
|
+
let perpID = PerpetualDataHandler.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
|
|
1012
|
+
let traderState = await this.proxyContract.getTraderState(
|
|
1013
|
+
perpID,
|
|
1014
|
+
traderAddr,
|
|
1015
|
+
indexPrices.map((x) => floatToABK64x64(x)) as [BigNumber, BigNumber],
|
|
1016
|
+
overrides || {}
|
|
1017
|
+
);
|
|
1018
|
+
const idx_availableMargin = 1;
|
|
1019
|
+
let mgn = ABK64x64ToFloat(traderState[idx_availableMargin]);
|
|
1020
|
+
return mgn;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Calculate a type of exchange loyality score based on trader volume
|
|
1025
|
+
* @param traderAddr address of the trader
|
|
1026
|
+
* @param brokerAddr address of the trader's broker or undefined
|
|
1027
|
+
* @returns a loyality score (4 worst, 1 best)
|
|
1028
|
+
*/
|
|
1029
|
+
public async getTraderLoyalityScore(
|
|
1030
|
+
traderAddr: string,
|
|
1031
|
+
brokerAddr?: string,
|
|
1032
|
+
overrides?: CallOverrides
|
|
1033
|
+
): Promise<number> {
|
|
1034
|
+
if (this.proxyContract == null) {
|
|
1035
|
+
throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
|
|
1036
|
+
}
|
|
1037
|
+
// loop over all pools and query volumes
|
|
1038
|
+
let brokerProm: Array<Promise<BigNumber>> = [];
|
|
1039
|
+
let traderProm: Array<Promise<BigNumber>> = [];
|
|
1040
|
+
for (let k = 0; k < this.poolStaticInfos.length; k++) {
|
|
1041
|
+
if (brokerAddr != "" && brokerAddr != undefined) {
|
|
1042
|
+
let brkrVol = this.proxyContract.getCurrentBrokerVolume(
|
|
1043
|
+
this.poolStaticInfos[k].poolId,
|
|
1044
|
+
brokerAddr,
|
|
1045
|
+
overrides || {}
|
|
1046
|
+
);
|
|
1047
|
+
brokerProm.push(brkrVol);
|
|
1048
|
+
}
|
|
1049
|
+
let trdrVol = this.proxyContract.getCurrentTraderVolume(
|
|
1050
|
+
this.poolStaticInfos[k].poolId,
|
|
1051
|
+
traderAddr,
|
|
1052
|
+
overrides || {}
|
|
1053
|
+
);
|
|
1054
|
+
traderProm.push(trdrVol);
|
|
1055
|
+
}
|
|
1056
|
+
// sum
|
|
1057
|
+
let totalBrokerVolume = 0;
|
|
1058
|
+
let totalTraderVolume = 0;
|
|
1059
|
+
let brkrVol = await Promise.all(brokerProm);
|
|
1060
|
+
let trdrVol = await Promise.all(traderProm);
|
|
1061
|
+
for (let k = 0; k < this.poolStaticInfos.length; k++) {
|
|
1062
|
+
if (brokerAddr != "" && brokerAddr != undefined) {
|
|
1063
|
+
totalBrokerVolume += ABK64x64ToFloat(brkrVol[k]);
|
|
1064
|
+
}
|
|
1065
|
+
totalTraderVolume += ABK64x64ToFloat(trdrVol[k]);
|
|
1066
|
+
}
|
|
1067
|
+
const volumeCap = 500_000;
|
|
1068
|
+
let score = totalBrokerVolume == 0 ? totalTraderVolume / volumeCap : totalBrokerVolume;
|
|
1069
|
+
// 5 different equally spaced categories: (4 is best, 1 worst)
|
|
1070
|
+
let rank4 = 1 + Math.floor(Math.min(score, 1 - 1e-15) * 4);
|
|
1071
|
+
// desired ranking starts at 4 (worst) and ends at 1 (best)
|
|
1072
|
+
return 5 - rank4;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Get all off-chain prices
|
|
1077
|
+
* @param _symbolToPerpStaticInfo mapping: PerpetualStaticInfo for each perpetual
|
|
1078
|
+
* @param _priceFeedGetter priceFeed class from which we can get offchain price data
|
|
1079
|
+
* @returns mapping of symbol-pair (e.g. BTC-USD) to price/isMarketClosed
|
|
1080
|
+
*/
|
|
1081
|
+
private static async _getAllIndexPrices(
|
|
1082
|
+
_symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
|
|
1083
|
+
_priceFeedGetter: PriceFeeds
|
|
1084
|
+
): Promise<Map<string, [number, boolean]>> {
|
|
1085
|
+
// get all prices from off-chain price-sources
|
|
1086
|
+
let allSym = new Set<string>();
|
|
1087
|
+
for (let perpSymbol of _symbolToPerpStaticInfo.keys()) {
|
|
1088
|
+
let sInfo: PerpetualStaticInfo | undefined = _symbolToPerpStaticInfo.get(perpSymbol);
|
|
1089
|
+
allSym.add(sInfo!.S2Symbol);
|
|
1090
|
+
if (sInfo!.S3Symbol != "") {
|
|
1091
|
+
allSym.add(sInfo!.S3Symbol);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
let allSymArr = Array.from(allSym.values());
|
|
1095
|
+
let idxPriceMap: Map<string, [number, boolean]> = await _priceFeedGetter.fetchPrices(allSymArr);
|
|
1096
|
+
return idxPriceMap;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Get market open/closed status
|
|
1101
|
+
* @param symbol Perpetual symbol of the form ETH-USD-MATIC
|
|
1102
|
+
* @returns True if the market is closed
|
|
1103
|
+
*/
|
|
1104
|
+
public async isMarketClosed(symbol: string): Promise<boolean> {
|
|
1105
|
+
if (this.proxyContract == null) {
|
|
1106
|
+
throw Error("no proxy contract initialized. Use createProxyInstance().");
|
|
1107
|
+
}
|
|
1108
|
+
return await MarketData._isMarketClosed(symbol, this.symbolToPerpStaticInfo, this.priceFeedGetter);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
private static async _isMarketClosed(
|
|
1112
|
+
symbol: string,
|
|
1113
|
+
_symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
|
|
1114
|
+
_priceFeedGetter: PriceFeeds
|
|
1115
|
+
): Promise<boolean> {
|
|
1116
|
+
const sInfo: PerpetualStaticInfo | undefined = _symbolToPerpStaticInfo.get(symbol);
|
|
1117
|
+
let priceSymbols: string[] = [];
|
|
1118
|
+
if (sInfo?.S2Symbol != undefined && sInfo.S2Symbol != "") {
|
|
1119
|
+
priceSymbols.push(sInfo.S2Symbol);
|
|
1120
|
+
}
|
|
1121
|
+
if (sInfo?.S3Symbol != undefined && sInfo.S3Symbol != "") {
|
|
1122
|
+
priceSymbols.push(sInfo.S3Symbol);
|
|
1123
|
+
}
|
|
1124
|
+
if (priceSymbols.length == 0) {
|
|
1125
|
+
throw new Error("symbol not found");
|
|
1126
|
+
}
|
|
1127
|
+
const priceInfos = await _priceFeedGetter.fetchPrices(priceSymbols);
|
|
1128
|
+
return [...priceInfos.values()].some((p) => p[1]);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Collect all mid-prices
|
|
1133
|
+
* @param _proxyContract contract instance
|
|
1134
|
+
* @param _nestedPerpetualIDs contains all perpetual ids for each pool
|
|
1135
|
+
* @param _symbolToPerpStaticInfo maps symbol to static info
|
|
1136
|
+
* @param _perpetualIdToSymbol maps perpetual id to symbol of the form BTC-USD-MATIC
|
|
1137
|
+
* @param _idxPriceMap symbol to price/market closed
|
|
1138
|
+
* @returns perpetual symbol to mid-prices mapping
|
|
1139
|
+
*/
|
|
1140
|
+
private static async _queryMidPrices(
|
|
1141
|
+
_proxyContract: Contract,
|
|
1142
|
+
_nestedPerpetualIDs: Array<Array<number>>,
|
|
1143
|
+
_symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
|
|
1144
|
+
_perpetualIdToSymbol: Map<number, string>,
|
|
1145
|
+
_idxPriceMap: Map<string, [number, boolean]>,
|
|
1146
|
+
overrides?: CallOverrides
|
|
1147
|
+
): Promise<Map<string, number>> {
|
|
1148
|
+
// what is the maximal number of queries at once?
|
|
1149
|
+
const chunkSize = 10;
|
|
1150
|
+
let perpetualIDChunks: Array<Array<number>> = PerpetualDataHandler.nestedIDsToChunks(
|
|
1151
|
+
chunkSize,
|
|
1152
|
+
_nestedPerpetualIDs
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
let midPriceMap = new Map<string, number>();
|
|
1156
|
+
for (let k = 0; k < perpetualIDChunks.length; k++) {
|
|
1157
|
+
let indexPrices: BigNumber[] = [];
|
|
1158
|
+
// collect/order all index prices
|
|
1159
|
+
for (let j = 0; j < perpetualIDChunks[k].length; j++) {
|
|
1160
|
+
let id = perpetualIDChunks[k][j];
|
|
1161
|
+
let symbol3s = _perpetualIdToSymbol.get(id);
|
|
1162
|
+
let info = _symbolToPerpStaticInfo.get(symbol3s!);
|
|
1163
|
+
let S2 = floatToABK64x64(_idxPriceMap.get(info!.S2Symbol)![0]);
|
|
1164
|
+
let S3 = BigNumber.from(0);
|
|
1165
|
+
if (info!.S3Symbol != "") {
|
|
1166
|
+
S3 = floatToABK64x64(_idxPriceMap.get(info!.S3Symbol)![0]);
|
|
1167
|
+
}
|
|
1168
|
+
indexPrices.push(S2);
|
|
1169
|
+
indexPrices.push(S3);
|
|
1170
|
+
}
|
|
1171
|
+
let fMidPrice = await _proxyContract.queryMidPrices(perpetualIDChunks[k], indexPrices, overrides || {});
|
|
1172
|
+
for (let j = 0; j < fMidPrice.length; j++) {
|
|
1173
|
+
let id = perpetualIDChunks[k][j];
|
|
1174
|
+
let symbol3s = _perpetualIdToSymbol.get(id);
|
|
1175
|
+
midPriceMap.set(symbol3s!, ABK64x64ToFloat(fMidPrice[j]));
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return midPriceMap;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
private static async _queryPoolStates(
|
|
1182
|
+
_proxyContract: Contract,
|
|
1183
|
+
_poolStaticInfos: PoolStaticInfo[],
|
|
1184
|
+
_numPools: number,
|
|
1185
|
+
overrides?: CallOverrides
|
|
1186
|
+
): Promise<Array<PoolState>> {
|
|
1187
|
+
const chunkSize = 5;
|
|
1188
|
+
let iFrom = 1;
|
|
1189
|
+
let poolStates: Array<PoolState> = [];
|
|
1190
|
+
while (iFrom <= _numPools) {
|
|
1191
|
+
let pools = await _proxyContract.getLiquidityPools(iFrom, iFrom + chunkSize, overrides || {});
|
|
1192
|
+
for (let k = 0; k < pools.length; k++) {
|
|
1193
|
+
let poolSymbol = _poolStaticInfos[iFrom + k - 1].poolMarginSymbol;
|
|
1194
|
+
let poolState: PoolState = {
|
|
1195
|
+
isRunning: pools[k].isRunning,
|
|
1196
|
+
poolSymbol: poolSymbol,
|
|
1197
|
+
marginTokenAddr: pools[k].marginTokenAddress,
|
|
1198
|
+
poolShareTokenAddr: pools[k].shareTokenAddress,
|
|
1199
|
+
defaultFundCashCC: ABK64x64ToFloat(pools[k].fDefaultFundCashCC),
|
|
1200
|
+
pnlParticipantCashCC: ABK64x64ToFloat(pools[k].fPnLparticipantsCashCC),
|
|
1201
|
+
totalAMMFundCashCC: ABK64x64ToFloat(pools[k].fAMMFundCashCC),
|
|
1202
|
+
totalTargetAMMFundSizeCC: ABK64x64ToFloat(pools[k].fTargetAMMFundSize),
|
|
1203
|
+
brokerCollateralLotSize: ABK64x64ToFloat(pools[k].fBrokerCollateralLotSize),
|
|
1204
|
+
perpetuals: [],
|
|
1205
|
+
};
|
|
1206
|
+
poolStates.push(poolState);
|
|
1207
|
+
}
|
|
1208
|
+
iFrom = iFrom + chunkSize + 1;
|
|
1209
|
+
}
|
|
1210
|
+
return poolStates;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
private static async _queryPerpetualStates(
|
|
1214
|
+
_proxyContract: Contract,
|
|
1215
|
+
_nestedPerpetualIDs: Array<Array<number>>,
|
|
1216
|
+
_symbolList: Map<string, string>,
|
|
1217
|
+
overrides?: CallOverrides
|
|
1218
|
+
) {
|
|
1219
|
+
// what is the maximal number of queries at once?
|
|
1220
|
+
const chunkSize = 10;
|
|
1221
|
+
let perpetualIDChunks: Array<Array<number>> = PerpetualDataHandler.nestedIDsToChunks(
|
|
1222
|
+
chunkSize,
|
|
1223
|
+
_nestedPerpetualIDs
|
|
1224
|
+
);
|
|
1225
|
+
let perpStateInfos = new Array<PerpetualState>();
|
|
1226
|
+
for (let k = 0; k < perpetualIDChunks.length; k++) {
|
|
1227
|
+
let perps = await _proxyContract.getPerpetuals(perpetualIDChunks[k], overrides || {});
|
|
1228
|
+
for (let j = 0; j < perps.length; j++) {
|
|
1229
|
+
let PerpetualState: PerpetualState = {
|
|
1230
|
+
id: perps[j].id,
|
|
1231
|
+
state: PERP_STATE_STR[perps[j].state],
|
|
1232
|
+
baseCurrency: contractSymbolToSymbol(perps[j].S2BaseCCY, _symbolList)!,
|
|
1233
|
+
quoteCurrency: contractSymbolToSymbol(perps[j].S2QuoteCCY, _symbolList)!,
|
|
1234
|
+
indexPrice: 0, //fill later
|
|
1235
|
+
collToQuoteIndexPrice: 0, //fill later
|
|
1236
|
+
markPrice: ABK64x64ToFloat(perps[j].currentMarkPremiumRate.fPrice), // fill later: indexS2 * (1 + markPremiumRate),
|
|
1237
|
+
midPrice: 0, // fill later
|
|
1238
|
+
currentFundingRateBps: 1e4 * ABK64x64ToFloat(perps[j].fCurrentFundingRate),
|
|
1239
|
+
openInterestBC: ABK64x64ToFloat(perps[j].fOpenInterest),
|
|
1240
|
+
isMarketClosed: false, //fill later
|
|
1241
|
+
};
|
|
1242
|
+
perpStateInfos.push(PerpetualState);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return perpStateInfos;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
public static async _exchangeInfo(
|
|
1249
|
+
_proxyContract: Contract,
|
|
1250
|
+
_poolStaticInfos: Array<PoolStaticInfo>,
|
|
1251
|
+
_symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
|
|
1252
|
+
_perpetualIdToSymbol: Map<number, string>,
|
|
1253
|
+
_nestedPerpetualIDs: Array<Array<number>>,
|
|
1254
|
+
_symbolList: Map<string, string>,
|
|
1255
|
+
_priceFeedGetter: PriceFeeds,
|
|
1256
|
+
overrides?: CallOverrides
|
|
1257
|
+
): Promise<ExchangeInfo> {
|
|
1258
|
+
// get the factory address (shared among all pools)
|
|
1259
|
+
let factory = _poolStaticInfos[0].oracleFactoryAddr;
|
|
1260
|
+
let info: ExchangeInfo = { pools: [], oracleFactoryAddr: factory, proxyAddr: _proxyContract.address };
|
|
1261
|
+
const numPools = _nestedPerpetualIDs.length;
|
|
1262
|
+
|
|
1263
|
+
// get all prices from off-chain price-sources
|
|
1264
|
+
let idxPriceMap = await MarketData._getAllIndexPrices(_symbolToPerpStaticInfo, _priceFeedGetter);
|
|
1265
|
+
// query mid-prices from on-chain conditional on the off-chain prices
|
|
1266
|
+
let midPriceMap: Map<string, number> = await MarketData._queryMidPrices(
|
|
1267
|
+
_proxyContract,
|
|
1268
|
+
_nestedPerpetualIDs,
|
|
1269
|
+
_symbolToPerpStaticInfo,
|
|
1270
|
+
_perpetualIdToSymbol,
|
|
1271
|
+
idxPriceMap,
|
|
1272
|
+
overrides
|
|
1273
|
+
);
|
|
1274
|
+
let poolStateInfos = await MarketData._queryPoolStates(_proxyContract, _poolStaticInfos, numPools, overrides);
|
|
1275
|
+
let perpStateInfos = await MarketData._queryPerpetualStates(
|
|
1276
|
+
_proxyContract,
|
|
1277
|
+
_nestedPerpetualIDs,
|
|
1278
|
+
_symbolList,
|
|
1279
|
+
overrides
|
|
1280
|
+
);
|
|
1281
|
+
// put together all info
|
|
1282
|
+
for (let k = 0; k < perpStateInfos.length; k++) {
|
|
1283
|
+
const perp = perpStateInfos[k];
|
|
1284
|
+
let symbol3s = _perpetualIdToSymbol.get(perp.id);
|
|
1285
|
+
let info = _symbolToPerpStaticInfo.get(symbol3s!);
|
|
1286
|
+
const idxPriceS2Pair = idxPriceMap.get(info!.S2Symbol);
|
|
1287
|
+
let idxPriceS3Pair: [number, boolean] = [0, false];
|
|
1288
|
+
perp.isMarketClosed = idxPriceS2Pair![1];
|
|
1289
|
+
if (info!.S3Symbol != "") {
|
|
1290
|
+
idxPriceS3Pair = idxPriceMap.get(info!.S3Symbol)!;
|
|
1291
|
+
perp.isMarketClosed = perp.isMarketClosed || idxPriceS3Pair![1];
|
|
1292
|
+
}
|
|
1293
|
+
perp.indexPrice = idxPriceS2Pair![0];
|
|
1294
|
+
perp.markPrice = idxPriceS2Pair![0] * (1 + perp.markPrice); // currently filled with mark premium rate
|
|
1295
|
+
let indexS3 = 1;
|
|
1296
|
+
if (info!.collateralCurrencyType == COLLATERAL_CURRENCY_BASE) {
|
|
1297
|
+
indexS3 = idxPriceS2Pair![0];
|
|
1298
|
+
} else if (info!.collateralCurrencyType == COLLATERAL_CURRENCY_QUANTO) {
|
|
1299
|
+
indexS3 = idxPriceS3Pair[0];
|
|
1300
|
+
}
|
|
1301
|
+
perp.collToQuoteIndexPrice = indexS3;
|
|
1302
|
+
perp.midPrice = midPriceMap.get(symbol3s!)!;
|
|
1303
|
+
// which pool?
|
|
1304
|
+
const poolId = info!.poolId;
|
|
1305
|
+
poolStateInfos[poolId - 1].perpetuals.push(perp);
|
|
1306
|
+
}
|
|
1307
|
+
info.pools = poolStateInfos;
|
|
1308
|
+
return info;
|
|
1309
|
+
}
|
|
1310
|
+
}
|