@reserve-protocol/dtf-rebalance-lib 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/open-auction.d.ts +1 -1
- package/dist/open-auction.js +36 -72
- package/package.json +14 -4
- package/src/index.ts +0 -5
- package/src/numbers.ts +0 -17
- package/src/open-auction.ts +0 -557
- package/src/start-rebalance.ts +0 -170
- package/src/types.ts +0 -35
- package/src/utils.ts +0 -36
package/dist/open-auction.d.ts
CHANGED
package/dist/open-auction.js
CHANGED
@@ -27,17 +27,13 @@ var AuctionRound;
|
|
27
27
|
*/
|
28
28
|
const getTargetBasket = (_initialWeights, _prices, _decimals) => {
|
29
29
|
if (_initialWeights.length != _prices.length) {
|
30
|
-
throw new Error(
|
30
|
+
throw new Error("length mismatch");
|
31
31
|
}
|
32
32
|
const vals = _initialWeights.map((initialWeight, i) => {
|
33
33
|
const price = new decimal_js_light_1.default(_prices[i]);
|
34
34
|
const decimalScale = new decimal_js_light_1.default(`1e${_decimals[i]}`);
|
35
35
|
// {USD/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27 * {USD/wholeTok}
|
36
|
-
return new decimal_js_light_1.default(initialWeight.spot.toString())
|
37
|
-
.mul(numbers_1.D18d)
|
38
|
-
.div(decimalScale)
|
39
|
-
.div(numbers_1.D27d)
|
40
|
-
.mul(price);
|
36
|
+
return new decimal_js_light_1.default(initialWeight.spot.toString()).mul(numbers_1.D18d).div(decimalScale).div(numbers_1.D27d).mul(price);
|
41
37
|
});
|
42
38
|
const totalValue = vals.reduce((a, b) => a.add(b));
|
43
39
|
// D18{1} = {USD/wholeBU} / {USD/wholeBU} * D18
|
@@ -61,17 +57,17 @@ exports.getTargetBasket = getTargetBasket;
|
|
61
57
|
*/
|
62
58
|
const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket = [], _folio, _decimals, _prices, _priceError, _finalStageAt, logging) => {
|
63
59
|
if (logging) {
|
64
|
-
console.log(
|
60
|
+
console.log("getOpenAuction", rebalance, _supply, _initialFolio, _targetBasket, _folio, _decimals, _prices, _priceError, _finalStageAt);
|
65
61
|
}
|
66
62
|
if (rebalance.tokens.length != _targetBasket.length ||
|
67
63
|
_targetBasket.length != _folio.length ||
|
68
64
|
_folio.length != _decimals.length ||
|
69
65
|
_decimals.length != _prices.length ||
|
70
66
|
_prices.length != _priceError.length) {
|
71
|
-
throw new Error(
|
67
|
+
throw new Error("length mismatch");
|
72
68
|
}
|
73
69
|
if (_finalStageAt > 1) {
|
74
|
-
throw new Error(
|
70
|
+
throw new Error("finalStageAt must be less than 1");
|
75
71
|
}
|
76
72
|
// ================================================================
|
77
73
|
// {wholeShare} = {share} / {share/wholeShare}
|
@@ -96,34 +92,21 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
96
92
|
// {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
|
97
93
|
let weightRanges = rebalance.weights.map((range, i) => {
|
98
94
|
return {
|
99
|
-
low: new decimal_js_light_1.default(range.low.toString())
|
100
|
-
|
101
|
-
|
102
|
-
.div(numbers_1.D27d),
|
103
|
-
spot: new decimal_js_light_1.default(range.spot.toString())
|
104
|
-
.mul(numbers_1.D18d)
|
105
|
-
.div(decimalScale[i])
|
106
|
-
.div(numbers_1.D27d),
|
107
|
-
high: new decimal_js_light_1.default(range.high.toString())
|
108
|
-
.mul(numbers_1.D18d)
|
109
|
-
.div(decimalScale[i])
|
110
|
-
.div(numbers_1.D27d),
|
95
|
+
low: new decimal_js_light_1.default(range.low.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
|
96
|
+
spot: new decimal_js_light_1.default(range.spot.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
|
97
|
+
high: new decimal_js_light_1.default(range.high.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
|
111
98
|
};
|
112
99
|
});
|
113
100
|
const finalStageAt = new decimal_js_light_1.default(_finalStageAt.toString());
|
114
101
|
// ================================================================
|
115
102
|
// calculate ideal spot limit, the actual BU<->share ratio
|
116
103
|
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
117
|
-
const shareValue = folio
|
118
|
-
.map((f, i) => f.mul(prices[i]))
|
119
|
-
.reduce((a, b) => a.add(b));
|
104
|
+
const shareValue = folio.map((f, i) => f.mul(prices[i])).reduce((a, b) => a.add(b));
|
120
105
|
// {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
|
121
|
-
const buValue = weightRanges
|
122
|
-
.map((weightRange, i) => weightRange.spot.mul(prices[i]))
|
123
|
-
.reduce((a, b) => a.add(b));
|
106
|
+
const buValue = weightRanges.map((weightRange, i) => weightRange.spot.mul(prices[i])).reduce((a, b) => a.add(b));
|
124
107
|
if (logging) {
|
125
|
-
console.log(
|
126
|
-
console.log(
|
108
|
+
console.log("shareValue", shareValue.toString());
|
109
|
+
console.log("buValue", buValue.toString());
|
127
110
|
}
|
128
111
|
// ================================================================
|
129
112
|
// calculate rebalanceTarget
|
@@ -144,9 +127,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
144
127
|
// {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
|
145
128
|
const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i]);
|
146
129
|
// {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
|
147
|
-
const balanceInBU = balanceExpected.gt(actualBalance)
|
148
|
-
? actualBalance
|
149
|
-
: balanceExpected;
|
130
|
+
const balanceInBU = balanceExpected.gt(actualBalance) ? actualBalance : balanceExpected;
|
150
131
|
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
151
132
|
return balanceInBU.mul(prices[i]);
|
152
133
|
})
|
@@ -158,9 +139,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
158
139
|
// {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
|
159
140
|
const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i]);
|
160
141
|
// {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
|
161
|
-
const balanceInBU = balanceExpected.gt(initialBalance)
|
162
|
-
? initialBalance
|
163
|
-
: balanceExpected;
|
142
|
+
const balanceInBU = balanceExpected.gt(initialBalance) ? initialBalance : balanceExpected;
|
164
143
|
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
165
144
|
return balanceInBU.mul(prices[i]);
|
166
145
|
})
|
@@ -176,11 +155,11 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
176
155
|
let rebalanceTarget = numbers_1.ONE;
|
177
156
|
let round = AuctionRound.FINAL;
|
178
157
|
if (logging) {
|
179
|
-
console.log(
|
180
|
-
console.log(
|
181
|
-
console.log(
|
182
|
-
console.log(
|
183
|
-
console.log(
|
158
|
+
console.log("initialProgression", initialProgression.toString());
|
159
|
+
console.log("progression", progression.toString());
|
160
|
+
console.log("relativeProgression", relativeProgression.toString());
|
161
|
+
console.log("portionBeingEjected", portionBeingEjected.toString());
|
162
|
+
console.log("finalStageAt", finalStageAt.toString());
|
184
163
|
}
|
185
164
|
// make it an eject auction if there is 1 bps or more of value to eject
|
186
165
|
if (portionBeingEjected.gte(1e-4)) {
|
@@ -202,13 +181,13 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
202
181
|
}
|
203
182
|
}
|
204
183
|
if (rebalanceTarget.gt(numbers_1.ONE)) {
|
205
|
-
throw new Error(
|
184
|
+
throw new Error("something has gone very wrong");
|
206
185
|
}
|
207
|
-
if (rebalanceTarget.lt(progression)) {
|
186
|
+
if (rebalanceTarget.lt(progression) || numbers_1.ONE.sub(rebalanceTarget).lt(1e-4)) {
|
208
187
|
rebalanceTarget = numbers_1.ONE;
|
209
188
|
}
|
210
189
|
if (logging) {
|
211
|
-
console.log(
|
190
|
+
console.log("rebalanceTarget", rebalanceTarget.toString());
|
212
191
|
}
|
213
192
|
// {1}
|
214
193
|
const delta = numbers_1.ONE.sub(rebalanceTarget);
|
@@ -224,9 +203,9 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
224
203
|
};
|
225
204
|
if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
|
226
205
|
// aim 1% higher if executed permissonlessly
|
227
|
-
newLimits.spot += newLimits.spot * 1n / 100n;
|
206
|
+
newLimits.spot += (newLimits.spot * 1n) / 100n;
|
228
207
|
// leave 10% room to increase low in the future if ejection leaves dust behind
|
229
|
-
newLimits.high += newLimits.high * 10n / 100n;
|
208
|
+
newLimits.high += (newLimits.high * 10n) / 100n;
|
230
209
|
}
|
231
210
|
// low
|
232
211
|
if (newLimits.low < rebalance.limits.low) {
|
@@ -250,7 +229,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
250
229
|
newLimits.high = rebalance.limits.high;
|
251
230
|
}
|
252
231
|
if (logging) {
|
253
|
-
console.log(
|
232
|
+
console.log("newLimits", newLimits);
|
254
233
|
}
|
255
234
|
// ================================================================
|
256
235
|
// get new weights, constrained by extremes
|
@@ -263,10 +242,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
263
242
|
// D27{tok/BU}
|
264
243
|
const newWeights = rebalance.weights.map((weightRange, i) => {
|
265
244
|
// {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
|
266
|
-
const idealWeight = shareValue
|
267
|
-
.mul(targetBasket[i])
|
268
|
-
.div(actualLimits.spot)
|
269
|
-
.div(prices[i]);
|
245
|
+
const idealWeight = shareValue.mul(targetBasket[i]).div(actualLimits.spot).div(prices[i]);
|
270
246
|
// D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
|
271
247
|
const newWeightsD27 = {
|
272
248
|
low: (0, numbers_1.bn)(idealWeight
|
@@ -283,9 +259,9 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
283
259
|
};
|
284
260
|
if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
|
285
261
|
// aim 1% higher if executed permissonlessly
|
286
|
-
newWeightsD27.spot += newWeightsD27.spot * 1n / 100n;
|
262
|
+
newWeightsD27.spot += (newWeightsD27.spot * 1n) / 100n;
|
287
263
|
// leave 10% room to increase low in the future if ejection leaves dust behind
|
288
|
-
newWeightsD27.high += newWeightsD27.high * 10n / 100n;
|
264
|
+
newWeightsD27.high += (newWeightsD27.high * 10n) / 100n;
|
289
265
|
}
|
290
266
|
if (newWeightsD27.low < weightRange.low) {
|
291
267
|
newWeightsD27.low = weightRange.low;
|
@@ -308,7 +284,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
308
284
|
return newWeightsD27;
|
309
285
|
});
|
310
286
|
if (logging) {
|
311
|
-
console.log(
|
287
|
+
console.log("newWeights", newWeights);
|
312
288
|
}
|
313
289
|
// ================================================================
|
314
290
|
// get new prices, constrained by extremes
|
@@ -317,7 +293,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
317
293
|
// revert if price out of bounds
|
318
294
|
const spotPrice = (0, numbers_1.bn)(prices[i].mul(numbers_1.D27d).div(decimalScale[i]));
|
319
295
|
if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
|
320
|
-
throw new Error(
|
296
|
+
throw new Error("spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!");
|
321
297
|
}
|
322
298
|
if (rebalance.priceControl == types_1.PriceControl.NONE) {
|
323
299
|
return initialPrice;
|
@@ -342,38 +318,26 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
|
|
342
318
|
pricesD27.high = initialPrice.high;
|
343
319
|
}
|
344
320
|
if (pricesD27.low == pricesD27.high && priceError[i].gt(numbers_1.ZERO)) {
|
345
|
-
throw new Error(
|
321
|
+
throw new Error("no price range");
|
346
322
|
}
|
347
323
|
return pricesD27;
|
348
324
|
});
|
349
325
|
if (logging) {
|
350
|
-
console.log(
|
326
|
+
console.log("newPrices", newPrices);
|
351
327
|
}
|
352
328
|
// ================================================================
|
353
329
|
// calculate metrics
|
354
330
|
// {USD} = {1} * {USD/wholeShare} * {wholeShare}
|
355
|
-
const valueBeingTraded = rebalanceTarget
|
356
|
-
.sub(progression)
|
357
|
-
.mul(shareValue)
|
358
|
-
.mul(supply);
|
331
|
+
const valueBeingTraded = rebalanceTarget.sub(progression).mul(shareValue).mul(supply);
|
359
332
|
const surplusTokens = [];
|
360
333
|
const deficitTokens = [];
|
361
334
|
// update Decimal weightRanges
|
362
335
|
// {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
|
363
336
|
weightRanges = newWeights.map((range, i) => {
|
364
337
|
return {
|
365
|
-
low: new decimal_js_light_1.default(range.low.toString())
|
366
|
-
|
367
|
-
|
368
|
-
.div(numbers_1.D27d),
|
369
|
-
spot: new decimal_js_light_1.default(range.spot.toString())
|
370
|
-
.mul(numbers_1.D18d)
|
371
|
-
.div(decimalScale[i])
|
372
|
-
.div(numbers_1.D27d),
|
373
|
-
high: new decimal_js_light_1.default(range.high.toString())
|
374
|
-
.mul(numbers_1.D18d)
|
375
|
-
.div(decimalScale[i])
|
376
|
-
.div(numbers_1.D27d),
|
338
|
+
low: new decimal_js_light_1.default(range.low.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
|
339
|
+
spot: new decimal_js_light_1.default(range.spot.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
|
340
|
+
high: new decimal_js_light_1.default(range.high.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
|
377
341
|
};
|
378
342
|
});
|
379
343
|
rebalance.tokens.forEach((token, i) => {
|
package/package.json
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
{
|
2
2
|
"name": "@reserve-protocol/dtf-rebalance-lib",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.3",
|
4
4
|
"description": "Rebalancing library for DTFs in typescript",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"module": "dist/index.js",
|
7
7
|
"types": "dist/index.d.ts",
|
8
8
|
"files": [
|
9
9
|
"dist",
|
10
|
-
"src",
|
11
10
|
"LICENSE.md",
|
12
11
|
"README.md"
|
13
12
|
],
|
14
13
|
"scripts": {
|
15
14
|
"clean": "rm -rf dist",
|
16
|
-
"
|
15
|
+
"compile": "hardhat compile",
|
16
|
+
"build": "npm run clean && tsc --project tsconfig.build.json",
|
17
17
|
"prepublishOnly": "npm run build",
|
18
|
-
"test": "
|
18
|
+
"test": "npm run test:unit && npm run test:e2e",
|
19
|
+
"test:unit": "node --test --require ts-node/register test/unit/*.test.ts",
|
20
|
+
"test:e2e": "hardhat test test/e2e/*.test.ts --bail"
|
19
21
|
},
|
20
22
|
"publishConfig": {
|
21
23
|
"access": "public"
|
@@ -39,6 +41,14 @@
|
|
39
41
|
"decimal.js-light": "^2.5.1"
|
40
42
|
},
|
41
43
|
"devDependencies": {
|
44
|
+
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
45
|
+
"@openzeppelin/contracts": "^5.3.0",
|
46
|
+
"@reserve-protocol/reserve-index-dtf": "github:reserve-protocol/reserve-index-dtf#a240822d7a02520def5564668ac7699b0520bdae",
|
47
|
+
"@types/node": "^20.0.0",
|
48
|
+
"dotenv": "^16.5.0",
|
49
|
+
"hardhat": "^2.24.1",
|
50
|
+
"prettier": "^3.5.3",
|
51
|
+
"ts-node": "^10.9.2",
|
42
52
|
"typescript": "^5.8.3"
|
43
53
|
}
|
44
54
|
}
|
package/src/index.ts
DELETED
package/src/numbers.ts
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
import Decimal from 'decimal.js-light'
|
2
|
-
|
3
|
-
export const D27n: bigint = 10n ** 27n
|
4
|
-
export const D18n: bigint = 10n ** 18n
|
5
|
-
export const D9n: bigint = 10n ** 9n
|
6
|
-
|
7
|
-
export const D27d: Decimal = new Decimal('1e27')
|
8
|
-
export const D18d: Decimal = new Decimal('1e18')
|
9
|
-
export const D9d: Decimal = new Decimal('1e9')
|
10
|
-
|
11
|
-
export const ZERO = new Decimal('0')
|
12
|
-
export const ONE = new Decimal('1')
|
13
|
-
export const TWO = new Decimal('2')
|
14
|
-
|
15
|
-
export const bn = (str: string | Decimal): bigint => {
|
16
|
-
return BigInt(new Decimal(str).toFixed(0))
|
17
|
-
}
|
package/src/open-auction.ts
DELETED
@@ -1,557 +0,0 @@
|
|
1
|
-
import Decimal from 'decimal.js-light'
|
2
|
-
|
3
|
-
import { bn, D18d, D27d, ONE, ZERO } from './numbers'
|
4
|
-
|
5
|
-
import {
|
6
|
-
PriceControl,
|
7
|
-
PriceRange,
|
8
|
-
Rebalance,
|
9
|
-
RebalanceLimits,
|
10
|
-
WeightRange,
|
11
|
-
} from './types'
|
12
|
-
|
13
|
-
// Call `getOpenAuction()` to get the current auction round
|
14
|
-
export enum AuctionRound {
|
15
|
-
EJECT = 0,
|
16
|
-
PROGRESS = 1,
|
17
|
-
FINAL = 2,
|
18
|
-
}
|
19
|
-
|
20
|
-
/**
|
21
|
-
* Useful metrics to use to visualize things
|
22
|
-
*
|
23
|
-
* @param initialProgression {1} The progression the Folio had when the auction was first proposed
|
24
|
-
* @param absoluteProgression {1} The progression of the auction on an absolute scale
|
25
|
-
* @param relativeProgression {1} The relative progression of the auction
|
26
|
-
* @param target {1} The target of the auction on an absolute scale
|
27
|
-
* @param auctionSize {USD} The total value on sale in the auction
|
28
|
-
* @param surplusTokens The list of tokens in surplus
|
29
|
-
* @param deficitTokens The list of tokens in deficit
|
30
|
-
*/
|
31
|
-
export interface AuctionMetrics {
|
32
|
-
round: AuctionRound
|
33
|
-
initialProgression: number
|
34
|
-
absoluteProgression: number
|
35
|
-
relativeProgression: number
|
36
|
-
target: number
|
37
|
-
auctionSize: number
|
38
|
-
surplusTokens: string[]
|
39
|
-
deficitTokens: string[]
|
40
|
-
}
|
41
|
-
|
42
|
-
// All the args needed to call `folio.openAuction()`
|
43
|
-
export interface OpenAuctionArgs {
|
44
|
-
rebalanceNonce: bigint
|
45
|
-
tokens: string[]
|
46
|
-
newWeights: WeightRange[]
|
47
|
-
newPrices: PriceRange[]
|
48
|
-
newLimits: RebalanceLimits
|
49
|
-
}
|
50
|
-
|
51
|
-
/**
|
52
|
-
* Generator for the `targetBasket` parameter
|
53
|
-
*
|
54
|
-
* Depending on the usecase, pass either:
|
55
|
-
* - TRACKING: CURRENT prices
|
56
|
-
* - NATIVE: HISTORICAL prices
|
57
|
-
*
|
58
|
-
* @param _initialWeights D27{tok/BU} The initial historical weights emitted in the RebalanceStarted event
|
59
|
-
* @param _prices {USD/wholeTok} either CURRENT or HISTORICAL prices
|
60
|
-
* @returns D18{1} The target basket
|
61
|
-
*/
|
62
|
-
export const getTargetBasket = (
|
63
|
-
_initialWeights: WeightRange[],
|
64
|
-
_prices: number[],
|
65
|
-
_decimals: bigint[]
|
66
|
-
): bigint[] => {
|
67
|
-
if (_initialWeights.length != _prices.length) {
|
68
|
-
throw new Error('length mismatch')
|
69
|
-
}
|
70
|
-
|
71
|
-
const vals = _initialWeights.map((initialWeight: WeightRange, i: number) => {
|
72
|
-
const price = new Decimal(_prices[i])
|
73
|
-
const decimalScale = new Decimal(`1e${_decimals[i]}`)
|
74
|
-
|
75
|
-
// {USD/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27 * {USD/wholeTok}
|
76
|
-
return new Decimal(initialWeight.spot.toString())
|
77
|
-
.mul(D18d)
|
78
|
-
.div(decimalScale)
|
79
|
-
.div(D27d)
|
80
|
-
.mul(price)
|
81
|
-
})
|
82
|
-
|
83
|
-
const totalValue = vals.reduce((a, b) => a.add(b))
|
84
|
-
|
85
|
-
// D18{1} = {USD/wholeBU} / {USD/wholeBU} * D18
|
86
|
-
return vals.map((val) => bn(val.div(totalValue).mul(D18d)))
|
87
|
-
}
|
88
|
-
|
89
|
-
/**
|
90
|
-
* Get the values needed to call `folio.openAuction()` as the AUCTION_LAUNCHER
|
91
|
-
*
|
92
|
-
* Non-AUCTION_LAUNCHERs should use `folio.openAuctionUnrestricted()`
|
93
|
-
*
|
94
|
-
* @param rebalance The result of calling folio.getRebalance()
|
95
|
-
* @param _supply {share} The totalSupply() of the basket, today
|
96
|
-
* @param _initialFolio D18{tok/share} Initial balances per share, e.g result of folio.toAssets(1e18, 0) at time rebalance was first proposed
|
97
|
-
* @param _targetBasket D18{1} Result of calling `getTargetBasket()`
|
98
|
-
* @param _folio D18{tok/share} Current ratio of token per share, e.g result of folio.toAssets(1e18, 0)
|
99
|
-
* @param _decimals Decimals of each token
|
100
|
-
* @param _prices {USD/wholeTok} USD prices for each *whole* token
|
101
|
-
* @param _priceError {1} Price error to use for each token during auction pricing; should be smaller than price error during startRebalance
|
102
|
-
* @param _finalStageAt {1} The % rebalanced from the initial Folio to determine when is the final stage of the rebalance
|
103
|
-
*/
|
104
|
-
export const getOpenAuction = (
|
105
|
-
rebalance: Rebalance,
|
106
|
-
_supply: bigint,
|
107
|
-
_initialFolio: bigint[] = [],
|
108
|
-
_targetBasket: bigint[] = [],
|
109
|
-
_folio: bigint[],
|
110
|
-
_decimals: bigint[],
|
111
|
-
_prices: number[],
|
112
|
-
_priceError: number[],
|
113
|
-
_finalStageAt: number,
|
114
|
-
logging?: boolean
|
115
|
-
): [OpenAuctionArgs, AuctionMetrics] => {
|
116
|
-
if (logging) {
|
117
|
-
console.log('getOpenAuction', rebalance, _supply, _initialFolio, _targetBasket, _folio, _decimals, _prices, _priceError, _finalStageAt)
|
118
|
-
}
|
119
|
-
|
120
|
-
if (
|
121
|
-
rebalance.tokens.length != _targetBasket.length ||
|
122
|
-
_targetBasket.length != _folio.length ||
|
123
|
-
_folio.length != _decimals.length ||
|
124
|
-
_decimals.length != _prices.length ||
|
125
|
-
_prices.length != _priceError.length
|
126
|
-
) {
|
127
|
-
throw new Error('length mismatch')
|
128
|
-
}
|
129
|
-
|
130
|
-
if (_finalStageAt > 1) {
|
131
|
-
throw new Error('finalStageAt must be less than 1')
|
132
|
-
}
|
133
|
-
|
134
|
-
// ================================================================
|
135
|
-
|
136
|
-
// {wholeShare} = {share} / {share/wholeShare}
|
137
|
-
const supply = new Decimal(_supply.toString()).div(D18d)
|
138
|
-
|
139
|
-
// {1} = D18{1} / D18
|
140
|
-
const targetBasket = _targetBasket.map((a) =>
|
141
|
-
new Decimal(a.toString()).div(D18d)
|
142
|
-
)
|
143
|
-
|
144
|
-
// {USD/wholeTok}
|
145
|
-
const prices = _prices.map((a) => new Decimal(a))
|
146
|
-
for (let i = 0; i < prices.length; i++) {
|
147
|
-
if (prices[i].eq(ZERO)) {
|
148
|
-
throw new Error(`missing price for token ${rebalance.tokens[i]}`)
|
149
|
-
}
|
150
|
-
}
|
151
|
-
|
152
|
-
// {1}
|
153
|
-
const priceError = _priceError.map((a) => new Decimal(a.toString()))
|
154
|
-
|
155
|
-
// {tok/wholeTok}
|
156
|
-
const decimalScale = _decimals.map((a) => new Decimal(`1e${a}`))
|
157
|
-
|
158
|
-
// {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
|
159
|
-
const initialFolio = _initialFolio.map((c: bigint, i: number) =>
|
160
|
-
new Decimal(c.toString()).div(decimalScale[i])
|
161
|
-
)
|
162
|
-
|
163
|
-
// {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
|
164
|
-
const folio = _folio.map((c: bigint, i: number) =>
|
165
|
-
new Decimal(c.toString()).div(decimalScale[i])
|
166
|
-
)
|
167
|
-
|
168
|
-
// {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
|
169
|
-
let weightRanges = rebalance.weights.map((range: WeightRange, i: number) => {
|
170
|
-
return {
|
171
|
-
low: new Decimal(range.low.toString())
|
172
|
-
.mul(D18d)
|
173
|
-
.div(decimalScale[i])
|
174
|
-
.div(D27d),
|
175
|
-
spot: new Decimal(range.spot.toString())
|
176
|
-
.mul(D18d)
|
177
|
-
.div(decimalScale[i])
|
178
|
-
.div(D27d),
|
179
|
-
high: new Decimal(range.high.toString())
|
180
|
-
.mul(D18d)
|
181
|
-
.div(decimalScale[i])
|
182
|
-
.div(D27d),
|
183
|
-
}
|
184
|
-
})
|
185
|
-
|
186
|
-
const finalStageAt = new Decimal(_finalStageAt.toString())
|
187
|
-
|
188
|
-
|
189
|
-
// ================================================================
|
190
|
-
|
191
|
-
// calculate ideal spot limit, the actual BU<->share ratio
|
192
|
-
|
193
|
-
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
194
|
-
const shareValue = folio
|
195
|
-
.map((f: Decimal, i: number) => f.mul(prices[i]))
|
196
|
-
.reduce((a, b) => a.add(b))
|
197
|
-
|
198
|
-
// {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
|
199
|
-
const buValue = weightRanges
|
200
|
-
.map((weightRange, i) => weightRange.spot.mul(prices[i]))
|
201
|
-
.reduce((a, b) => a.add(b))
|
202
|
-
|
203
|
-
if (logging) {
|
204
|
-
console.log('shareValue', shareValue.toString())
|
205
|
-
console.log('buValue', buValue.toString())
|
206
|
-
}
|
207
|
-
|
208
|
-
// ================================================================
|
209
|
-
|
210
|
-
// calculate rebalanceTarget
|
211
|
-
|
212
|
-
const ejectionIndices: number[] = []
|
213
|
-
for (let i = 0; i < rebalance.weights.length; i++) {
|
214
|
-
if (rebalance.weights[i].spot == 0n) {
|
215
|
-
ejectionIndices.push(i)
|
216
|
-
}
|
217
|
-
}
|
218
|
-
|
219
|
-
// {1} = {wholeTok/wholeShare} * {USD/wholeTok} / {USD/wholeShare}
|
220
|
-
const portionBeingEjected = ejectionIndices
|
221
|
-
.map((i) => folio[i].mul(prices[i]))
|
222
|
-
.reduce((a, b) => a.add(b), ZERO)
|
223
|
-
.div(shareValue)
|
224
|
-
|
225
|
-
|
226
|
-
// {1} = {USD/wholeShare} / {USD/wholeShare}
|
227
|
-
let progression = folio
|
228
|
-
.map((actualBalance, i) => {
|
229
|
-
// {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
|
230
|
-
const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i])
|
231
|
-
|
232
|
-
// {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
|
233
|
-
const balanceInBU = balanceExpected.gt(actualBalance)
|
234
|
-
? actualBalance
|
235
|
-
: balanceExpected
|
236
|
-
|
237
|
-
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
238
|
-
return balanceInBU.mul(prices[i])
|
239
|
-
})
|
240
|
-
.reduce((a, b) => a.add(b))
|
241
|
-
.div(shareValue)
|
242
|
-
|
243
|
-
// {1} = {USD/wholeShare} / {USD/wholeShare}
|
244
|
-
const initialProgression = initialFolio
|
245
|
-
.map((initialBalance, i) => {
|
246
|
-
// {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
|
247
|
-
const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i])
|
248
|
-
|
249
|
-
// {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
|
250
|
-
const balanceInBU = balanceExpected.gt(initialBalance)
|
251
|
-
? initialBalance
|
252
|
-
: balanceExpected
|
253
|
-
|
254
|
-
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
255
|
-
return balanceInBU.mul(prices[i])
|
256
|
-
})
|
257
|
-
.reduce((a, b) => a.add(b))
|
258
|
-
.div(shareValue)
|
259
|
-
|
260
|
-
if (progression < initialProgression) {
|
261
|
-
progression = initialProgression // don't go backwards
|
262
|
-
}
|
263
|
-
|
264
|
-
|
265
|
-
// {1} = {1} / {1}
|
266
|
-
const relativeProgression = initialProgression.eq(ONE)
|
267
|
-
? ONE
|
268
|
-
: progression.sub(initialProgression).div(ONE.sub(initialProgression))
|
269
|
-
|
270
|
-
let rebalanceTarget = ONE
|
271
|
-
let round: AuctionRound = AuctionRound.FINAL
|
272
|
-
|
273
|
-
if (logging) {
|
274
|
-
console.log('initialProgression', initialProgression.toString())
|
275
|
-
console.log('progression', progression.toString())
|
276
|
-
console.log('relativeProgression', relativeProgression.toString())
|
277
|
-
console.log('portionBeingEjected', portionBeingEjected.toString())
|
278
|
-
console.log('finalStageAt', finalStageAt.toString())
|
279
|
-
}
|
280
|
-
|
281
|
-
// make it an eject auction if there is 1 bps or more of value to eject
|
282
|
-
if (portionBeingEjected.gte(1e-4)) {
|
283
|
-
round = AuctionRound.EJECT
|
284
|
-
|
285
|
-
if (relativeProgression.lt(finalStageAt.sub(0.02))) {
|
286
|
-
rebalanceTarget = progression.add(portionBeingEjected.mul(1.1)) // set rebalanceTarget to 10% more than needed to ensure ejection completes
|
287
|
-
|
288
|
-
// do not finish trading yet
|
289
|
-
if (rebalanceTarget.gte(ONE)) {
|
290
|
-
rebalanceTarget = initialProgression.add(ONE.sub(initialProgression).mul(finalStageAt))
|
291
|
-
}
|
292
|
-
}
|
293
|
-
} else if (relativeProgression.lt(finalStageAt.sub(0.02))) {
|
294
|
-
// wiggle room to prevent having to re-run an auction at the same stage after price movement
|
295
|
-
round = AuctionRound.PROGRESS
|
296
|
-
|
297
|
-
rebalanceTarget = initialProgression.add(ONE.sub(initialProgression).mul(finalStageAt))
|
298
|
-
if (rebalanceTarget.eq(ONE)) {
|
299
|
-
round = AuctionRound.FINAL
|
300
|
-
}
|
301
|
-
}
|
302
|
-
|
303
|
-
if (rebalanceTarget.gt(ONE)) {
|
304
|
-
throw new Error('something has gone very wrong')
|
305
|
-
}
|
306
|
-
|
307
|
-
if (rebalanceTarget.lt(progression)) {
|
308
|
-
rebalanceTarget = ONE
|
309
|
-
}
|
310
|
-
|
311
|
-
if (logging) {
|
312
|
-
console.log('rebalanceTarget', rebalanceTarget.toString())
|
313
|
-
}
|
314
|
-
|
315
|
-
// {1}
|
316
|
-
const delta = ONE.sub(rebalanceTarget)
|
317
|
-
|
318
|
-
// ================================================================
|
319
|
-
|
320
|
-
// get new limits, constrained by extremes
|
321
|
-
|
322
|
-
// {wholeBU/wholeShare} = {USD/wholeShare} / {USD/wholeBU}
|
323
|
-
const spotLimit = shareValue.div(buValue)
|
324
|
-
|
325
|
-
// D18{BU/share} = {wholeBU/wholeShare} * D18 * {1}
|
326
|
-
const newLimits = {
|
327
|
-
low: bn(spotLimit.sub(spotLimit.mul(delta)).mul(D18d)),
|
328
|
-
spot: bn(spotLimit.mul(D18d)),
|
329
|
-
high: bn(spotLimit.add(spotLimit.mul(delta)).mul(D18d)),
|
330
|
-
}
|
331
|
-
|
332
|
-
if (round == AuctionRound.EJECT && rebalanceTarget.eq(ONE)) {
|
333
|
-
// aim 1% higher if executed permissonlessly
|
334
|
-
newLimits.spot += newLimits.spot * 1n / 100n
|
335
|
-
|
336
|
-
// leave 10% room to increase low in the future if ejection leaves dust behind
|
337
|
-
newLimits.high += newLimits.high * 10n / 100n
|
338
|
-
}
|
339
|
-
|
340
|
-
// low
|
341
|
-
if (newLimits.low < rebalance.limits.low) {
|
342
|
-
newLimits.low = rebalance.limits.low
|
343
|
-
}
|
344
|
-
if (newLimits.low > rebalance.limits.high) {
|
345
|
-
newLimits.low = rebalance.limits.high
|
346
|
-
}
|
347
|
-
|
348
|
-
// spot
|
349
|
-
if (newLimits.spot < rebalance.limits.low) {
|
350
|
-
newLimits.spot = rebalance.limits.low
|
351
|
-
}
|
352
|
-
if (newLimits.spot > rebalance.limits.high) {
|
353
|
-
newLimits.spot = rebalance.limits.high
|
354
|
-
}
|
355
|
-
|
356
|
-
// high
|
357
|
-
if (newLimits.high < rebalance.limits.low) {
|
358
|
-
newLimits.high = rebalance.limits.low
|
359
|
-
}
|
360
|
-
if (newLimits.high > rebalance.limits.high) {
|
361
|
-
newLimits.high = rebalance.limits.high
|
362
|
-
}
|
363
|
-
|
364
|
-
if (logging) {
|
365
|
-
console.log('newLimits', newLimits)
|
366
|
-
}
|
367
|
-
|
368
|
-
// ================================================================
|
369
|
-
|
370
|
-
// get new weights, constrained by extremes
|
371
|
-
|
372
|
-
// {wholeBU/wholeShare} = D18{BU/share} / D18
|
373
|
-
const actualLimits = {
|
374
|
-
low: new Decimal(newLimits.low.toString()).div(D18d),
|
375
|
-
spot: new Decimal(newLimits.spot.toString()).div(D18d),
|
376
|
-
high: new Decimal(newLimits.high.toString()).div(D18d),
|
377
|
-
}
|
378
|
-
|
379
|
-
// D27{tok/BU}
|
380
|
-
const newWeights = rebalance.weights.map((weightRange, i) => {
|
381
|
-
// {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
|
382
|
-
const idealWeight = shareValue
|
383
|
-
.mul(targetBasket[i])
|
384
|
-
.div(actualLimits.spot)
|
385
|
-
.div(prices[i])
|
386
|
-
|
387
|
-
// D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
|
388
|
-
const newWeightsD27 = {
|
389
|
-
low: bn(
|
390
|
-
idealWeight
|
391
|
-
.mul(rebalanceTarget.div(actualLimits.low.div(actualLimits.spot))) // add remaining delta into weight
|
392
|
-
.mul(D27d)
|
393
|
-
.mul(decimalScale[i])
|
394
|
-
.div(D18d)
|
395
|
-
),
|
396
|
-
spot: bn(idealWeight.mul(D27d).mul(decimalScale[i]).div(D18d)),
|
397
|
-
high: bn(
|
398
|
-
idealWeight
|
399
|
-
.mul(ONE.add(delta).div(actualLimits.high.div(actualLimits.spot))) // add remaining delta into weight
|
400
|
-
.mul(D27d)
|
401
|
-
.mul(decimalScale[i])
|
402
|
-
.div(D18d)
|
403
|
-
),
|
404
|
-
}
|
405
|
-
|
406
|
-
if (round == AuctionRound.EJECT && rebalanceTarget.eq(ONE)) {
|
407
|
-
// aim 1% higher if executed permissonlessly
|
408
|
-
newWeightsD27.spot += newWeightsD27.spot * 1n / 100n
|
409
|
-
|
410
|
-
// leave 10% room to increase low in the future if ejection leaves dust behind
|
411
|
-
newWeightsD27.high += newWeightsD27.high * 10n / 100n
|
412
|
-
}
|
413
|
-
|
414
|
-
if (newWeightsD27.low < weightRange.low) {
|
415
|
-
newWeightsD27.low = weightRange.low
|
416
|
-
} else if (newWeightsD27.low > weightRange.high) {
|
417
|
-
newWeightsD27.low = weightRange.high
|
418
|
-
}
|
419
|
-
|
420
|
-
if (newWeightsD27.spot < weightRange.low) {
|
421
|
-
newWeightsD27.spot = weightRange.low
|
422
|
-
} else if (newWeightsD27.spot > weightRange.high) {
|
423
|
-
newWeightsD27.spot = weightRange.high
|
424
|
-
}
|
425
|
-
|
426
|
-
if (newWeightsD27.high < weightRange.low) {
|
427
|
-
newWeightsD27.high = weightRange.low
|
428
|
-
} else if (newWeightsD27.high > weightRange.high) {
|
429
|
-
newWeightsD27.high = weightRange.high
|
430
|
-
}
|
431
|
-
|
432
|
-
return newWeightsD27
|
433
|
-
})
|
434
|
-
|
435
|
-
if (logging) {
|
436
|
-
console.log('newWeights', newWeights)
|
437
|
-
}
|
438
|
-
|
439
|
-
// ================================================================
|
440
|
-
|
441
|
-
// get new prices, constrained by extremes
|
442
|
-
|
443
|
-
|
444
|
-
// D27{USD/tok}
|
445
|
-
const newPrices = rebalance.initialPrices.map((initialPrice, i) => {
|
446
|
-
// revert if price out of bounds
|
447
|
-
const spotPrice = bn(prices[i].mul(D27d).div(decimalScale[i]))
|
448
|
-
if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
|
449
|
-
throw new Error('spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!')
|
450
|
-
}
|
451
|
-
|
452
|
-
if (rebalance.priceControl == PriceControl.NONE) {
|
453
|
-
return initialPrice
|
454
|
-
}
|
455
|
-
|
456
|
-
// D27{USD/tok} = {USD/wholeTok} * D27 / {tok/wholeTok}
|
457
|
-
const pricesD27 = {
|
458
|
-
low: bn(
|
459
|
-
prices[i].mul(ONE.sub(priceError[i])).mul(D27d).div(decimalScale[i])
|
460
|
-
),
|
461
|
-
high: bn(
|
462
|
-
prices[i].div(ONE.sub(priceError[i])).mul(D27d).div(decimalScale[i])
|
463
|
-
),
|
464
|
-
}
|
465
|
-
|
466
|
-
|
467
|
-
// low
|
468
|
-
if (pricesD27.low < initialPrice.low) {
|
469
|
-
pricesD27.low = initialPrice.low
|
470
|
-
}
|
471
|
-
if (pricesD27.low > initialPrice.high) {
|
472
|
-
pricesD27.low = initialPrice.high
|
473
|
-
}
|
474
|
-
|
475
|
-
// high
|
476
|
-
if (pricesD27.high < initialPrice.low) {
|
477
|
-
pricesD27.high = initialPrice.low
|
478
|
-
}
|
479
|
-
if (pricesD27.high > initialPrice.high) {
|
480
|
-
pricesD27.high = initialPrice.high
|
481
|
-
}
|
482
|
-
|
483
|
-
if (pricesD27.low == pricesD27.high && priceError[i].gt(ZERO)) {
|
484
|
-
throw new Error('no price range')
|
485
|
-
}
|
486
|
-
|
487
|
-
return pricesD27
|
488
|
-
})
|
489
|
-
|
490
|
-
if (logging) {
|
491
|
-
console.log('newPrices', newPrices)
|
492
|
-
}
|
493
|
-
|
494
|
-
// ================================================================
|
495
|
-
|
496
|
-
// calculate metrics
|
497
|
-
|
498
|
-
// {USD} = {1} * {USD/wholeShare} * {wholeShare}
|
499
|
-
const valueBeingTraded = rebalanceTarget
|
500
|
-
.sub(progression)
|
501
|
-
.mul(shareValue)
|
502
|
-
.mul(supply)
|
503
|
-
|
504
|
-
const surplusTokens: string[] = []
|
505
|
-
const deficitTokens: string[] = []
|
506
|
-
|
507
|
-
// update Decimal weightRanges
|
508
|
-
// {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
|
509
|
-
weightRanges = newWeights.map((range, i) => {
|
510
|
-
return {
|
511
|
-
low: new Decimal(range.low.toString())
|
512
|
-
.mul(D18d)
|
513
|
-
.div(decimalScale[i])
|
514
|
-
.div(D27d),
|
515
|
-
spot: new Decimal(range.spot.toString())
|
516
|
-
.mul(D18d)
|
517
|
-
.div(decimalScale[i])
|
518
|
-
.div(D27d),
|
519
|
-
high: new Decimal(range.high.toString())
|
520
|
-
.mul(D18d)
|
521
|
-
.div(decimalScale[i])
|
522
|
-
.div(D27d),
|
523
|
-
}
|
524
|
-
})
|
525
|
-
|
526
|
-
rebalance.tokens.forEach((token, i) => {
|
527
|
-
// {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
|
528
|
-
const buyUpTo = weightRanges[i].low.mul(actualLimits.low)
|
529
|
-
const sellDownTo = weightRanges[i].high.mul(actualLimits.high)
|
530
|
-
|
531
|
-
if (folio[i].lt(buyUpTo)) {
|
532
|
-
deficitTokens.push(token)
|
533
|
-
} else if (folio[i].gt(sellDownTo)) {
|
534
|
-
surplusTokens.push(token)
|
535
|
-
}
|
536
|
-
})
|
537
|
-
|
538
|
-
return [
|
539
|
-
{
|
540
|
-
rebalanceNonce: rebalance.nonce,
|
541
|
-
tokens: rebalance.tokens, // full set of tokens, not pruned to the active buy/sells
|
542
|
-
newWeights: newWeights,
|
543
|
-
newPrices: newPrices,
|
544
|
-
newLimits: newLimits,
|
545
|
-
},
|
546
|
-
{
|
547
|
-
round: round,
|
548
|
-
initialProgression: initialProgression.toNumber(),
|
549
|
-
absoluteProgression: progression.toNumber(),
|
550
|
-
relativeProgression: relativeProgression.toNumber(),
|
551
|
-
target: rebalanceTarget.toNumber(),
|
552
|
-
auctionSize: valueBeingTraded.toNumber(),
|
553
|
-
surplusTokens: surplusTokens,
|
554
|
-
deficitTokens: deficitTokens,
|
555
|
-
},
|
556
|
-
]
|
557
|
-
}
|
package/src/start-rebalance.ts
DELETED
@@ -1,170 +0,0 @@
|
|
1
|
-
import Decimal from 'decimal.js-light'
|
2
|
-
|
3
|
-
import { bn, D18d, D27d, ONE, ZERO } from './numbers'
|
4
|
-
|
5
|
-
import { PriceRange, RebalanceLimits, WeightRange } from './types'
|
6
|
-
|
7
|
-
// Partial set of the args needed to call `startRebalance()`
|
8
|
-
export interface StartRebalanceArgsPartial {
|
9
|
-
// tokens: string[]
|
10
|
-
weights: WeightRange[]
|
11
|
-
prices: PriceRange[]
|
12
|
-
limits: RebalanceLimits
|
13
|
-
// auctionLauncherWindow: bigint
|
14
|
-
// ttl: bigint
|
15
|
-
}
|
16
|
-
|
17
|
-
/**
|
18
|
-
* Get the arguments needed to call startRebalance
|
19
|
-
*
|
20
|
-
* The `tokens` argument should be paired with the two return values and passed to `startRebalance()`
|
21
|
-
*
|
22
|
-
* @param _supply {share}
|
23
|
-
* @param tokens Addresses of tokens in the basket
|
24
|
-
* @param _folio D18{tok/share} Folio of the basket
|
25
|
-
* @param decimals Decimals of each token
|
26
|
-
* @param _targetBasket D18{1} Ideal basket
|
27
|
-
* @param _prices {USD/wholeTok} USD prices for each *whole* token
|
28
|
-
* @param _priceError {1} Price error per token to use in the rebalanc; should be larger than price error during openAuction
|
29
|
-
* @param _dtfPrice {USD/wholeShare} DTF price
|
30
|
-
* @param weightControl TRACKING=false, NATIVE=true
|
31
|
-
*/
|
32
|
-
export const getStartRebalance = (
|
33
|
-
_supply: bigint,
|
34
|
-
tokens: string[],
|
35
|
-
_folio: bigint[],
|
36
|
-
decimals: bigint[],
|
37
|
-
_targetBasket: bigint[],
|
38
|
-
_prices: number[],
|
39
|
-
_priceError: number[],
|
40
|
-
weightControl: boolean,
|
41
|
-
logging?: boolean
|
42
|
-
): StartRebalanceArgsPartial => {
|
43
|
-
if (logging) {
|
44
|
-
console.log('getStartRebalance', _supply, tokens, _folio, decimals, _targetBasket, _prices, _priceError, weightControl)
|
45
|
-
}
|
46
|
-
|
47
|
-
// {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
|
48
|
-
const folio = _folio.map((c: bigint, i: number) =>
|
49
|
-
new Decimal(c.toString()).div(new Decimal(`1e${decimals[i]}`))
|
50
|
-
)
|
51
|
-
|
52
|
-
// convert price number inputs to bigints
|
53
|
-
|
54
|
-
// {USD/wholeTok}
|
55
|
-
const prices = _prices.map((a) => new Decimal(a.toString()))
|
56
|
-
for (let i = 0; i < prices.length; i++) {
|
57
|
-
if (prices[i].eq(ZERO)) {
|
58
|
-
throw new Error(`missing price for token ${tokens[i]}`)
|
59
|
-
}
|
60
|
-
}
|
61
|
-
|
62
|
-
// {1} = D18{1} / D18
|
63
|
-
const targetBasket = _targetBasket.map((a) =>
|
64
|
-
new Decimal(a.toString()).div(D18d)
|
65
|
-
)
|
66
|
-
|
67
|
-
// {1}
|
68
|
-
const priceError = _priceError.map((a) => new Decimal(a.toString()))
|
69
|
-
|
70
|
-
// ================================================================
|
71
|
-
|
72
|
-
const newWeights: WeightRange[] = []
|
73
|
-
const newPrices: PriceRange[] = []
|
74
|
-
const newLimits: RebalanceLimits = {
|
75
|
-
low: bn('1e18'),
|
76
|
-
spot: bn('1e18'),
|
77
|
-
high: bn('1e18'),
|
78
|
-
}
|
79
|
-
|
80
|
-
// ================================================================
|
81
|
-
|
82
|
-
for (let i = 0; i < tokens.length; i++) {
|
83
|
-
if (priceError[i].gte(ONE)) {
|
84
|
-
throw new Error('cannot defer prices')
|
85
|
-
}
|
86
|
-
|
87
|
-
// === newWeights ===
|
88
|
-
|
89
|
-
// {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
|
90
|
-
const dtfPrice = folio.map((f: Decimal, i: number) => f.mul(prices[i])).reduce((a: Decimal, b: Decimal) => a.add(b))
|
91
|
-
|
92
|
-
// {wholeTok/wholeShare} = {1} * {USD/wholeShare} / {USD/wholeTok}
|
93
|
-
const spotWeight = targetBasket[i].mul(dtfPrice).div(prices[i])
|
94
|
-
|
95
|
-
|
96
|
-
// D27{tok/share}{wholeShare/wholeTok} = D27 * {tok/wholeTok} / {share/wholeShare}
|
97
|
-
const limitMultiplier = D27d.mul(new Decimal(`1e${decimals[i]}`)).div(D18d)
|
98
|
-
|
99
|
-
if (logging) {
|
100
|
-
console.log('limitMultiplier', limitMultiplier.toString())
|
101
|
-
}
|
102
|
-
|
103
|
-
if (!weightControl) {
|
104
|
-
// D27{tok/BU} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
|
105
|
-
newWeights.push({
|
106
|
-
low: bn(spotWeight.mul(limitMultiplier)),
|
107
|
-
spot: bn(spotWeight.mul(limitMultiplier)),
|
108
|
-
high: bn(spotWeight.mul(limitMultiplier)),
|
109
|
-
})
|
110
|
-
} else {
|
111
|
-
// NATIVE case
|
112
|
-
|
113
|
-
// {wholeTok/wholeShare} = {wholeTok/wholeShare} / {1}
|
114
|
-
const lowWeight = spotWeight.mul(ONE.div(ONE.add(priceError[i])))
|
115
|
-
const highWeight = spotWeight.mul(ONE.add(priceError[i]))
|
116
|
-
|
117
|
-
|
118
|
-
// D27{tok/share} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
|
119
|
-
newWeights.push({
|
120
|
-
low: bn(lowWeight.mul(limitMultiplier)),
|
121
|
-
spot: bn(spotWeight.mul(limitMultiplier)),
|
122
|
-
high: bn(highWeight.mul(limitMultiplier)),
|
123
|
-
})
|
124
|
-
}
|
125
|
-
|
126
|
-
// === newPrices ===
|
127
|
-
|
128
|
-
// D27{wholeTok/tok} = D27 / {tok/wholeTok}
|
129
|
-
const priceMultiplier = D27d.div(new Decimal(`1e${decimals[i]}`))
|
130
|
-
|
131
|
-
// {USD/wholeTok} = {USD/wholeTok} * {1}
|
132
|
-
const lowPrice = prices[i].mul(ONE.sub(priceError[i]))
|
133
|
-
const highPrice = prices[i].mul(ONE.add(priceError[i]))
|
134
|
-
|
135
|
-
|
136
|
-
// D27{USD/tok} = {USD/wholeTok} * D27{wholeTok/tok}
|
137
|
-
newPrices.push({
|
138
|
-
low: bn(lowPrice.mul(priceMultiplier)),
|
139
|
-
high: bn(highPrice.mul(priceMultiplier)),
|
140
|
-
})
|
141
|
-
}
|
142
|
-
|
143
|
-
// update low/high for tracking DTFs
|
144
|
-
if (!weightControl) {
|
145
|
-
// sum of dot product of targetBasket and priceError
|
146
|
-
const totalPortion = targetBasket
|
147
|
-
.map((portion: Decimal, i: number) => portion.mul(priceError[i]))
|
148
|
-
.reduce((a: Decimal, b: Decimal) => a.add(b))
|
149
|
-
|
150
|
-
if (totalPortion.gte(ONE)) {
|
151
|
-
throw new Error('totalPortion > 1')
|
152
|
-
}
|
153
|
-
|
154
|
-
// D18{BU/share} = {1} * D18 * {BU/share}
|
155
|
-
newLimits.low = bn(ONE.div(ONE.add(totalPortion)).mul(D18d))
|
156
|
-
newLimits.high = bn(ONE.add(totalPortion).mul(D18d))
|
157
|
-
}
|
158
|
-
|
159
|
-
if (logging) {
|
160
|
-
console.log("newWeights", newWeights)
|
161
|
-
console.log('newPrices', newPrices)
|
162
|
-
console.log('newLimits', newLimits)
|
163
|
-
}
|
164
|
-
|
165
|
-
return {
|
166
|
-
weights: newWeights,
|
167
|
-
prices: newPrices,
|
168
|
-
limits: newLimits,
|
169
|
-
}
|
170
|
-
}
|
package/src/types.ts
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
export enum PriceControl {
|
2
|
-
NONE = 0,
|
3
|
-
PARTIAL = 1,
|
4
|
-
ATOMIC_SWAP = 2,
|
5
|
-
}
|
6
|
-
|
7
|
-
export interface RebalanceLimits {
|
8
|
-
low: bigint // D18{BU/share}
|
9
|
-
spot: bigint // D18{BU/share}
|
10
|
-
high: bigint // D18{BU/share}
|
11
|
-
}
|
12
|
-
|
13
|
-
export interface WeightRange {
|
14
|
-
low: bigint // D27{tok/BU}
|
15
|
-
spot: bigint // D27{tok/BU}
|
16
|
-
high: bigint // D27{tok/BU}
|
17
|
-
}
|
18
|
-
|
19
|
-
export interface PriceRange {
|
20
|
-
low: bigint // D27{USD/tok}
|
21
|
-
high: bigint // D27{USD/tok}
|
22
|
-
}
|
23
|
-
|
24
|
-
export interface Rebalance {
|
25
|
-
nonce: bigint
|
26
|
-
tokens: string[]
|
27
|
-
weights: WeightRange[]
|
28
|
-
initialPrices: PriceRange[]
|
29
|
-
inRebalance: boolean[]
|
30
|
-
limits: RebalanceLimits
|
31
|
-
startedAt: bigint
|
32
|
-
restrictedUntil: bigint
|
33
|
-
availableUntil: bigint
|
34
|
-
priceControl: PriceControl
|
35
|
-
}
|
package/src/utils.ts
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
import Decimal from 'decimal.js-light'
|
2
|
-
|
3
|
-
import { bn, D18d } from './numbers'
|
4
|
-
|
5
|
-
/**
|
6
|
-
* This function can be used to get a basket distribution EITHER from a set of historical basket weights
|
7
|
-
* or from a set of current balances. Make sure to use prices from the right time.
|
8
|
-
*
|
9
|
-
* @param _bals {tok} Current balances; or previous historical weights
|
10
|
-
* @param _prices {USD/wholeTok} USD prices for each *whole* token; or previous historical prices
|
11
|
-
* @param decimals Decimals of each token
|
12
|
-
* @returns D18{1} Current basket, total will be around 1e18 but not exactly
|
13
|
-
*/
|
14
|
-
export const getBasketDistribution = (
|
15
|
-
_bals: bigint[],
|
16
|
-
_prices: number[],
|
17
|
-
decimals: bigint[]
|
18
|
-
): bigint[] => {
|
19
|
-
const decimalScale = decimals.map((d) => new Decimal(`1e${d}`))
|
20
|
-
|
21
|
-
// {wholeTok} = {tok} / {tok/wholeTok}
|
22
|
-
const bals = _bals.map((bal, i) =>
|
23
|
-
new Decimal(bal.toString()).div(decimalScale[i])
|
24
|
-
)
|
25
|
-
|
26
|
-
// {USD/wholeTok} = {USD/wholeTok}
|
27
|
-
const prices = _prices.map((a) => new Decimal(a.toString()))
|
28
|
-
|
29
|
-
// {USD} = {wholeTok} * {USD/wholeTok}
|
30
|
-
const totalValue = bals
|
31
|
-
.map((bal, i) => bal.mul(prices[i]))
|
32
|
-
.reduce((a, b) => a.add(b))
|
33
|
-
|
34
|
-
// D18{1} = {wholeTok} * {USD/wholeTok} / {USD}
|
35
|
-
return bals.map((bal, i) => bn(bal.mul(prices[i]).div(totalValue).mul(D18d)))
|
36
|
-
}
|