@reserve-protocol/dtf-rebalance-lib 0.0.4 → 0.0.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.
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './numbers';
3
+ export * from './utils';
4
+ export * from './open-auction';
5
+ export * from './start-rebalance';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./numbers"), exports);
19
+ __exportStar(require("./utils"), exports);
20
+ __exportStar(require("./open-auction"), exports);
21
+ __exportStar(require("./start-rebalance"), exports);
@@ -0,0 +1,11 @@
1
+ import Decimal from 'decimal.js-light';
2
+ export declare const D27n: bigint;
3
+ export declare const D18n: bigint;
4
+ export declare const D9n: bigint;
5
+ export declare const D27d: Decimal;
6
+ export declare const D18d: Decimal;
7
+ export declare const D9d: Decimal;
8
+ export declare const ZERO: Decimal;
9
+ export declare const ONE: Decimal;
10
+ export declare const TWO: Decimal;
11
+ export declare const bn: (str: string | Decimal) => bigint;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.bn = exports.TWO = exports.ONE = exports.ZERO = exports.D9d = exports.D18d = exports.D27d = exports.D9n = exports.D18n = exports.D27n = void 0;
7
+ const decimal_js_light_1 = __importDefault(require("decimal.js-light"));
8
+ exports.D27n = 10n ** 27n;
9
+ exports.D18n = 10n ** 18n;
10
+ exports.D9n = 10n ** 9n;
11
+ exports.D27d = new decimal_js_light_1.default('1e27');
12
+ exports.D18d = new decimal_js_light_1.default('1e18');
13
+ exports.D9d = new decimal_js_light_1.default('1e9');
14
+ exports.ZERO = new decimal_js_light_1.default('0');
15
+ exports.ONE = new decimal_js_light_1.default('1');
16
+ exports.TWO = new decimal_js_light_1.default('2');
17
+ const bn = (str) => {
18
+ return BigInt(new decimal_js_light_1.default(str).toFixed(0));
19
+ };
20
+ exports.bn = bn;
@@ -0,0 +1,50 @@
1
+ import { PriceRange, Rebalance, RebalanceLimits, WeightRange } from './types';
2
+ export declare enum AuctionRound {
3
+ EJECT = 0,
4
+ PROGRESS = 1,
5
+ FINAL = 2
6
+ }
7
+ /**
8
+ * Useful metrics to use to visualize things
9
+ *
10
+ * @param initialProgression {1} The progression the Folio had when the auction was first proposed
11
+ * @param absoluteProgression {1} The progression of the auction on an absolute scale
12
+ * @param relativeProgression {1} The relative progression of the auction
13
+ * @param target {1} The target of the auction on an absolute scale
14
+ * @param auctionSize {USD} The total value on sale in the auction
15
+ * @param surplusTokens The list of tokens in surplus
16
+ * @param deficitTokens The list of tokens in deficit
17
+ */
18
+ export interface AuctionMetrics {
19
+ round: AuctionRound;
20
+ initialProgression: number;
21
+ absoluteProgression: number;
22
+ relativeProgression: number;
23
+ target: number;
24
+ auctionSize: number;
25
+ surplusTokens: string[];
26
+ deficitTokens: string[];
27
+ }
28
+ export interface OpenAuctionArgs {
29
+ rebalanceNonce: bigint;
30
+ tokens: string[];
31
+ newWeights: WeightRange[];
32
+ newPrices: PriceRange[];
33
+ newLimits: RebalanceLimits;
34
+ }
35
+ /**
36
+ * Get the values needed to call `folio.openAuction()` as the AUCTION_LAUNCHER
37
+ *
38
+ * Non-AUCTION_LAUNCHERs should use `folio.openAuctionUnrestricted()`
39
+ *
40
+ * @param rebalance The result of calling folio.getRebalance()
41
+ * @param _supply {share} The totalSupply() of the basket, today
42
+ * @param _initialFolio D18{tok/share} Initial balances per share, e.g result of folio.toAssets(1e18, 0) at time rebalance was first proposed
43
+ * @param _targetBasket D18{1} Result of calling `getTargetBasket()`
44
+ * @param _folio D18{tok/share} Current ratio of token per share, e.g result of folio.toAssets(1e18, 0)
45
+ * @param _decimals Decimals of each token
46
+ * @param _prices {USD/wholeTok} USD prices for each *whole* token
47
+ * @param _priceError {1} Price error to use for each token during auction pricing; should be smaller than price error during startRebalance
48
+ * @param _finalStageAt {1} The % rebalanced from the initial Folio to determine when is the final stage of the rebalance
49
+ */
50
+ export declare const getOpenAuction: (rebalance: Rebalance, _supply: bigint, _initialFolio: bigint[] | undefined, _targetBasket: bigint[] | undefined, _folio: bigint[], _decimals: bigint[], _prices: number[], _priceError: number[], _finalStageAt?: number) => [OpenAuctionArgs, AuctionMetrics];
@@ -0,0 +1,359 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getOpenAuction = exports.AuctionRound = void 0;
7
+ const decimal_js_light_1 = __importDefault(require("decimal.js-light"));
8
+ const numbers_1 = require("./numbers");
9
+ const types_1 = require("./types");
10
+ // Call `getOpenAuction()` to get the current auction round
11
+ var AuctionRound;
12
+ (function (AuctionRound) {
13
+ AuctionRound[AuctionRound["EJECT"] = 0] = "EJECT";
14
+ AuctionRound[AuctionRound["PROGRESS"] = 1] = "PROGRESS";
15
+ AuctionRound[AuctionRound["FINAL"] = 2] = "FINAL";
16
+ })(AuctionRound || (exports.AuctionRound = AuctionRound = {}));
17
+ /**
18
+ * Generator for the `targetBasket` parameter
19
+ *
20
+ * Depending on the usecase, pass either:
21
+ * - TRACKING: CURRENT prices
22
+ * - NATIVE: HISTORICAL prices
23
+ *
24
+ * @param _initialWeights D27{tok/BU} The initial historical weights emitted in the RebalanceStarted event
25
+ * @param _prices {USD/wholeTok} either CURRENT or HISTORICAL prices
26
+ * @returns D18{1} The target basket
27
+ */
28
+ const getTargetBasket = (_initialWeights, _prices, _decimals) => {
29
+ if (_initialWeights.length != _prices.length) {
30
+ throw new Error('length mismatch');
31
+ }
32
+ const vals = _initialWeights.map((initialWeight, i) => {
33
+ const price = new decimal_js_light_1.default(_prices[i]);
34
+ const decimalScale = new decimal_js_light_1.default(`1e${_decimals[i]}`);
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);
41
+ });
42
+ const totalValue = vals.reduce((a, b) => a.add(b));
43
+ // D18{1} = {USD/wholeBU} / {USD/wholeBU} * D18
44
+ return vals.map((val) => (0, numbers_1.bn)(val.div(totalValue).mul(numbers_1.D18d)));
45
+ };
46
+ /**
47
+ * Get the values needed to call `folio.openAuction()` as the AUCTION_LAUNCHER
48
+ *
49
+ * Non-AUCTION_LAUNCHERs should use `folio.openAuctionUnrestricted()`
50
+ *
51
+ * @param rebalance The result of calling folio.getRebalance()
52
+ * @param _supply {share} The totalSupply() of the basket, today
53
+ * @param _initialFolio D18{tok/share} Initial balances per share, e.g result of folio.toAssets(1e18, 0) at time rebalance was first proposed
54
+ * @param _targetBasket D18{1} Result of calling `getTargetBasket()`
55
+ * @param _folio D18{tok/share} Current ratio of token per share, e.g result of folio.toAssets(1e18, 0)
56
+ * @param _decimals Decimals of each token
57
+ * @param _prices {USD/wholeTok} USD prices for each *whole* token
58
+ * @param _priceError {1} Price error to use for each token during auction pricing; should be smaller than price error during startRebalance
59
+ * @param _finalStageAt {1} The % rebalanced from the initial Folio to determine when is the final stage of the rebalance
60
+ */
61
+ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket = [], _folio, _decimals, _prices, _priceError, _finalStageAt = 0.9) => {
62
+ if (rebalance.tokens.length != _targetBasket.length ||
63
+ _targetBasket.length != _folio.length ||
64
+ _folio.length != _decimals.length ||
65
+ _decimals.length != _prices.length ||
66
+ _prices.length != _priceError.length) {
67
+ throw new Error('length mismatch');
68
+ }
69
+ if (_finalStageAt >= 1) {
70
+ throw new Error('finalStageAt must be less than 1');
71
+ }
72
+ // ================================================================
73
+ // {wholeShare} = {share} / {share/wholeShare}
74
+ const supply = new decimal_js_light_1.default(_supply.toString()).div(numbers_1.D18d);
75
+ // {1} = D18{1} / D18
76
+ const targetBasket = _targetBasket.map((a) => new decimal_js_light_1.default(a.toString()).div(numbers_1.D18d));
77
+ // {USD/wholeTok}
78
+ const prices = _prices.map((a) => new decimal_js_light_1.default(a));
79
+ for (let i = 0; i < prices.length; i++) {
80
+ if (prices[i].eq(numbers_1.ZERO)) {
81
+ throw new Error(`missing price for token ${rebalance.tokens[i]}`);
82
+ }
83
+ }
84
+ // {1}
85
+ const priceError = _priceError.map((a) => new decimal_js_light_1.default(a.toString()));
86
+ // {tok/wholeTok}
87
+ const decimalScale = _decimals.map((a) => new decimal_js_light_1.default(`1e${a}`));
88
+ // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
89
+ const initialFolio = _initialFolio.map((c, i) => new decimal_js_light_1.default(c.toString()).div(decimalScale[i]));
90
+ // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
91
+ const folio = _folio.map((c, i) => new decimal_js_light_1.default(c.toString()).div(decimalScale[i]));
92
+ // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
93
+ let weightRanges = rebalance.weights.map((range, i) => {
94
+ return {
95
+ low: new decimal_js_light_1.default(range.low.toString())
96
+ .mul(numbers_1.D18d)
97
+ .div(decimalScale[i])
98
+ .div(numbers_1.D27d),
99
+ spot: new decimal_js_light_1.default(range.spot.toString())
100
+ .mul(numbers_1.D18d)
101
+ .div(decimalScale[i])
102
+ .div(numbers_1.D27d),
103
+ high: new decimal_js_light_1.default(range.high.toString())
104
+ .mul(numbers_1.D18d)
105
+ .div(decimalScale[i])
106
+ .div(numbers_1.D27d),
107
+ };
108
+ });
109
+ const finalStageAt = new decimal_js_light_1.default(_finalStageAt.toString());
110
+ // ================================================================
111
+ // calculate ideal spot limit, the actual BU<->share ratio
112
+ // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
113
+ const shareValue = folio
114
+ .map((f, i) => f.mul(prices[i]))
115
+ .reduce((a, b) => a.add(b));
116
+ // {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
117
+ const buValue = weightRanges
118
+ .map((weightRange, i) => weightRange.spot.mul(prices[i]))
119
+ .reduce((a, b) => a.add(b));
120
+ // ================================================================
121
+ // calculate rebalanceTarget
122
+ const ejectionIndices = [];
123
+ for (let i = 0; i < rebalance.weights.length; i++) {
124
+ if (rebalance.weights[i].spot == 0n) {
125
+ ejectionIndices.push(i);
126
+ }
127
+ }
128
+ // {1} = {wholeTok/wholeShare} * {USD/wholeTok} / {USD/wholeShare}
129
+ const portionBeingEjected = ejectionIndices
130
+ .map((i) => folio[i].mul(prices[i]))
131
+ .reduce((a, b) => a.add(b), numbers_1.ZERO)
132
+ .div(shareValue);
133
+ // {1} = {USD/wholeShare} / {USD/wholeShare}
134
+ let progression = folio
135
+ .map((actualBalance, i) => {
136
+ // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
137
+ const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i]);
138
+ // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
139
+ const balanceInBU = balanceExpected.gt(actualBalance)
140
+ ? actualBalance
141
+ : balanceExpected;
142
+ // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
143
+ return balanceInBU.mul(prices[i]);
144
+ })
145
+ .reduce((a, b) => a.add(b))
146
+ .div(shareValue);
147
+ // {1} = {USD/wholeShare} / {USD/wholeShare}
148
+ const initialProgression = initialFolio
149
+ .map((initialBalance, i) => {
150
+ // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
151
+ const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i]);
152
+ // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
153
+ const balanceInBU = balanceExpected.gt(initialBalance)
154
+ ? initialBalance
155
+ : balanceExpected;
156
+ // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
157
+ return balanceInBU.mul(prices[i]);
158
+ })
159
+ .reduce((a, b) => a.add(b))
160
+ .div(shareValue);
161
+ if (progression < initialProgression) {
162
+ progression = initialProgression; // don't go backwards
163
+ }
164
+ // {1} = {1} / {1}
165
+ const relativeProgression = initialProgression.eq(numbers_1.ONE)
166
+ ? numbers_1.ONE
167
+ : progression.sub(initialProgression).div(numbers_1.ONE.sub(initialProgression));
168
+ let rebalanceTarget = numbers_1.ONE;
169
+ let round = AuctionRound.FINAL;
170
+ // make it an eject auction if there is 1 bps or more of value to eject
171
+ if (portionBeingEjected.gte(1e-4)) {
172
+ round = AuctionRound.EJECT;
173
+ rebalanceTarget = progression.add(portionBeingEjected.mul(1.5)); // set rebalanceTarget to 50% more than needed, to ensure ejection completes
174
+ if (rebalanceTarget.gt(numbers_1.ONE)) {
175
+ rebalanceTarget = numbers_1.ONE;
176
+ }
177
+ }
178
+ else if (relativeProgression.lt(finalStageAt.sub(0.02))) {
179
+ // wiggle room to prevent having to re-run an auction at the same stage after price movement
180
+ round = AuctionRound.PROGRESS;
181
+ rebalanceTarget = finalStageAt;
182
+ }
183
+ // {1}
184
+ const delta = numbers_1.ONE.sub(rebalanceTarget);
185
+ // ================================================================
186
+ // get new limits, constrained by extremes
187
+ // {wholeBU/wholeShare} = {USD/wholeShare} / {USD/wholeBU}
188
+ const spotLimit = shareValue.div(buValue);
189
+ // D18{BU/share} = {wholeBU/wholeShare} * D18 * {1}
190
+ const newLimits = {
191
+ low: (0, numbers_1.bn)(spotLimit.sub(spotLimit.mul(delta)).mul(numbers_1.D18d)),
192
+ spot: (0, numbers_1.bn)(spotLimit.mul(numbers_1.D18d)),
193
+ high: (0, numbers_1.bn)(spotLimit.add(spotLimit.mul(delta)).mul(numbers_1.D18d)),
194
+ };
195
+ // low
196
+ if (newLimits.low < rebalance.limits.low) {
197
+ newLimits.low = rebalance.limits.low;
198
+ }
199
+ if (newLimits.low > rebalance.limits.high) {
200
+ newLimits.low = rebalance.limits.high;
201
+ }
202
+ // spot
203
+ if (newLimits.spot < rebalance.limits.low) {
204
+ newLimits.spot = rebalance.limits.low;
205
+ }
206
+ if (newLimits.spot > rebalance.limits.high) {
207
+ newLimits.spot = rebalance.limits.high;
208
+ }
209
+ // high
210
+ if (newLimits.high < rebalance.limits.low) {
211
+ newLimits.high = rebalance.limits.low;
212
+ }
213
+ if (newLimits.high > rebalance.limits.high) {
214
+ newLimits.high = rebalance.limits.high;
215
+ }
216
+ // ================================================================
217
+ // get new weights, constrained by extremes
218
+ // {wholeBU/wholeShare} = D18{BU/share} / D18
219
+ const actualLimits = {
220
+ low: new decimal_js_light_1.default(newLimits.low.toString()).div(numbers_1.D18d),
221
+ spot: new decimal_js_light_1.default(newLimits.spot.toString()).div(numbers_1.D18d),
222
+ high: new decimal_js_light_1.default(newLimits.high.toString()).div(numbers_1.D18d),
223
+ };
224
+ // D27{tok/BU}
225
+ const newWeights = rebalance.weights.map((weightRange, i) => {
226
+ // {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
227
+ const idealWeight = shareValue
228
+ .mul(targetBasket[i])
229
+ .div(actualLimits.spot)
230
+ .div(prices[i]);
231
+ // D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
232
+ const newWeightsD27 = {
233
+ low: (0, numbers_1.bn)(idealWeight
234
+ .mul(rebalanceTarget.div(actualLimits.low.div(actualLimits.spot))) // add remaining delta into weight
235
+ .mul(numbers_1.D27d)
236
+ .mul(decimalScale[i])
237
+ .div(numbers_1.D18d)),
238
+ spot: (0, numbers_1.bn)(idealWeight.mul(numbers_1.D27d).mul(decimalScale[i]).div(numbers_1.D18d)),
239
+ high: (0, numbers_1.bn)(idealWeight
240
+ .mul(numbers_1.ONE.add(delta).div(actualLimits.high.div(actualLimits.spot))) // add remaining delta into weight
241
+ .mul(numbers_1.D27d)
242
+ .mul(decimalScale[i])
243
+ .div(numbers_1.D18d)),
244
+ };
245
+ if (newWeightsD27.low < weightRange.low) {
246
+ newWeightsD27.low = weightRange.low;
247
+ }
248
+ else if (newWeightsD27.low > weightRange.high) {
249
+ newWeightsD27.low = weightRange.high;
250
+ }
251
+ if (newWeightsD27.spot < weightRange.low) {
252
+ newWeightsD27.spot = weightRange.low;
253
+ }
254
+ else if (newWeightsD27.spot > weightRange.high) {
255
+ newWeightsD27.spot = weightRange.high;
256
+ }
257
+ if (newWeightsD27.high < weightRange.low) {
258
+ newWeightsD27.high = weightRange.low;
259
+ }
260
+ else if (newWeightsD27.high > weightRange.high) {
261
+ newWeightsD27.high = weightRange.high;
262
+ }
263
+ return newWeightsD27;
264
+ });
265
+ // ================================================================
266
+ // get new prices, constrained by extremes
267
+ // D27{USD/tok}
268
+ const newPrices = rebalance.initialPrices.map((initialPrice, i) => {
269
+ // revert if price out of bounds
270
+ const spotPrice = (0, numbers_1.bn)(prices[i].mul(numbers_1.D27d).div(decimalScale[i]));
271
+ if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
272
+ throw new Error('spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!');
273
+ }
274
+ if (rebalance.priceControl == types_1.PriceControl.NONE) {
275
+ return initialPrice;
276
+ }
277
+ // D27{USD/tok} = {USD/wholeTok} * D27 / {tok/wholeTok}
278
+ const pricesD27 = {
279
+ low: (0, numbers_1.bn)(prices[i].mul(numbers_1.ONE.sub(priceError[i])).mul(numbers_1.D27d).div(decimalScale[i])),
280
+ high: (0, numbers_1.bn)(prices[i].div(numbers_1.ONE.sub(priceError[i])).mul(numbers_1.D27d).div(decimalScale[i])),
281
+ };
282
+ // low
283
+ if (pricesD27.low < initialPrice.low) {
284
+ pricesD27.low = initialPrice.low;
285
+ }
286
+ if (pricesD27.low > initialPrice.high) {
287
+ pricesD27.low = initialPrice.high;
288
+ }
289
+ // high
290
+ if (pricesD27.high < initialPrice.low) {
291
+ pricesD27.high = initialPrice.low;
292
+ }
293
+ if (pricesD27.high > initialPrice.high) {
294
+ pricesD27.high = initialPrice.high;
295
+ }
296
+ if (pricesD27.low == pricesD27.high) {
297
+ throw new Error('no price range');
298
+ }
299
+ return pricesD27;
300
+ });
301
+ // ================================================================
302
+ // calculate metrics
303
+ // {USD} = {1} * {USD/wholeShare} * {wholeShare}
304
+ const valueBeingTraded = rebalanceTarget
305
+ .sub(progression)
306
+ .mul(shareValue)
307
+ .mul(supply);
308
+ const surplusTokens = [];
309
+ const deficitTokens = [];
310
+ // update Decimal weightRanges
311
+ // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
312
+ weightRanges = newWeights.map((range, i) => {
313
+ return {
314
+ low: new decimal_js_light_1.default(range.low.toString())
315
+ .mul(numbers_1.D18d)
316
+ .div(decimalScale[i])
317
+ .div(numbers_1.D27d),
318
+ spot: new decimal_js_light_1.default(range.spot.toString())
319
+ .mul(numbers_1.D18d)
320
+ .div(decimalScale[i])
321
+ .div(numbers_1.D27d),
322
+ high: new decimal_js_light_1.default(range.high.toString())
323
+ .mul(numbers_1.D18d)
324
+ .div(decimalScale[i])
325
+ .div(numbers_1.D27d),
326
+ };
327
+ });
328
+ rebalance.tokens.forEach((token, i) => {
329
+ // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
330
+ const buyUpTo = weightRanges[i].low.mul(actualLimits.low);
331
+ const sellDownTo = weightRanges[i].high.mul(actualLimits.high);
332
+ if (folio[i].lt(buyUpTo)) {
333
+ deficitTokens.push(token);
334
+ }
335
+ else if (folio[i].gt(sellDownTo)) {
336
+ surplusTokens.push(token);
337
+ }
338
+ });
339
+ return [
340
+ {
341
+ rebalanceNonce: rebalance.nonce,
342
+ tokens: rebalance.tokens, // full set of tokens, not pruned to the active buy/sells
343
+ newWeights: newWeights,
344
+ newPrices: newPrices,
345
+ newLimits: newLimits,
346
+ },
347
+ {
348
+ round: round,
349
+ initialProgression: initialProgression.toNumber(),
350
+ absoluteProgression: progression.toNumber(),
351
+ relativeProgression: relativeProgression.toNumber(),
352
+ target: rebalanceTarget.toNumber(),
353
+ auctionSize: valueBeingTraded.toNumber(),
354
+ surplusTokens: surplusTokens,
355
+ deficitTokens: deficitTokens,
356
+ },
357
+ ];
358
+ };
359
+ exports.getOpenAuction = getOpenAuction;
@@ -0,0 +1,21 @@
1
+ import { PriceRange, RebalanceLimits, WeightRange } from './types';
2
+ export interface StartRebalanceArgsPartial {
3
+ weights: WeightRange[];
4
+ prices: PriceRange[];
5
+ limits: RebalanceLimits;
6
+ }
7
+ /**
8
+ * Get the arguments needed to call startRebalance
9
+ *
10
+ * The `tokens` argument should be paired with the two return values and passed to `startRebalance()`
11
+ *
12
+ * @param _supply {share}
13
+ * @param tokens Addresses of tokens in the basket
14
+ * @param decimals Decimals of each token
15
+ * @param _targetBasket D18{1} Ideal basket
16
+ * @param _prices {USD/wholeTok} USD prices for each *whole* token
17
+ * @param _priceError {1} Price error per token to use in the rebalanc; should be larger than price error during openAuction
18
+ * @param _dtfPrice {USD/wholeShare} DTF price
19
+ * @param weightControl TRACKING=false, NATIVE=true
20
+ */
21
+ export declare const getStartRebalance: (_supply: bigint, tokens: string[], decimals: bigint[], _targetBasket: bigint[], _prices: number[], _priceError: number[], _dtfPrice: number, weightControl: boolean) => StartRebalanceArgsPartial;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getStartRebalance = void 0;
7
+ const decimal_js_light_1 = __importDefault(require("decimal.js-light"));
8
+ const numbers_1 = require("./numbers");
9
+ /**
10
+ * Get the arguments needed to call startRebalance
11
+ *
12
+ * The `tokens` argument should be paired with the two return values and passed to `startRebalance()`
13
+ *
14
+ * @param _supply {share}
15
+ * @param tokens Addresses of tokens in the basket
16
+ * @param decimals Decimals of each token
17
+ * @param _targetBasket D18{1} Ideal basket
18
+ * @param _prices {USD/wholeTok} USD prices for each *whole* token
19
+ * @param _priceError {1} Price error per token to use in the rebalanc; should be larger than price error during openAuction
20
+ * @param _dtfPrice {USD/wholeShare} DTF price
21
+ * @param weightControl TRACKING=false, NATIVE=true
22
+ */
23
+ const getStartRebalance = (_supply, tokens, decimals, _targetBasket, _prices, _priceError, _dtfPrice, weightControl) => {
24
+ // convert price number inputs to bigints
25
+ // {USD/wholeTok}
26
+ const prices = _prices.map((a) => new decimal_js_light_1.default(a.toString()));
27
+ for (let i = 0; i < prices.length; i++) {
28
+ if (prices[i].eq(numbers_1.ZERO)) {
29
+ throw new Error(`missing price for token ${tokens[i]}`);
30
+ }
31
+ }
32
+ // {USD/wholeShare}
33
+ const dtfPrice = new decimal_js_light_1.default(_dtfPrice);
34
+ // {1} = D18{1} / D18
35
+ const targetBasket = _targetBasket.map((a) => new decimal_js_light_1.default(a.toString()).div(numbers_1.D18d));
36
+ // {1}
37
+ const priceError = _priceError.map((a) => new decimal_js_light_1.default(a.toString()));
38
+ // ================================================================
39
+ const newWeights = [];
40
+ const newPrices = [];
41
+ const newLimits = {
42
+ low: (0, numbers_1.bn)('1e18'),
43
+ spot: (0, numbers_1.bn)('1e18'),
44
+ high: (0, numbers_1.bn)('1e18'),
45
+ };
46
+ // ================================================================
47
+ for (let i = 0; i < tokens.length; i++) {
48
+ if (priceError[i].gte(numbers_1.ONE)) {
49
+ throw new Error('cannot defer prices');
50
+ }
51
+ // === newWeights ===
52
+ // {wholeTok/wholeShare} = {1} * {USD/wholeShare} / {USD/wholeTok}
53
+ const spotWeight = targetBasket[i].mul(dtfPrice).div(prices[i]);
54
+ // D27{tok/share}{wholeShare/wholeTok} = D27 * {tok/wholeTok} / {share/wholeShare}
55
+ const limitMultiplier = numbers_1.D27d.mul(new decimal_js_light_1.default(`1e${decimals[i]}`)).div(numbers_1.D18d);
56
+ if (!weightControl) {
57
+ // D27{tok/BU} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
58
+ newWeights.push({
59
+ low: (0, numbers_1.bn)(spotWeight.mul(limitMultiplier)),
60
+ spot: (0, numbers_1.bn)(spotWeight.mul(limitMultiplier)),
61
+ high: (0, numbers_1.bn)(spotWeight.mul(limitMultiplier)),
62
+ });
63
+ }
64
+ else {
65
+ // NATIVE case
66
+ // {wholeTok/wholeShare} = {wholeTok/wholeShare} / {1}
67
+ const lowWeight = spotWeight.mul(numbers_1.ONE.div(numbers_1.ONE.add(priceError[i])));
68
+ const highWeight = spotWeight.mul(numbers_1.ONE.add(priceError[i]));
69
+ // D27{tok/share} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
70
+ newWeights.push({
71
+ low: (0, numbers_1.bn)(lowWeight.mul(limitMultiplier)),
72
+ spot: (0, numbers_1.bn)(spotWeight.mul(limitMultiplier)),
73
+ high: (0, numbers_1.bn)(highWeight.mul(limitMultiplier)),
74
+ });
75
+ }
76
+ // === newPrices ===
77
+ // D27{wholeTok/tok} = D27 / {tok/wholeTok}
78
+ const priceMultiplier = numbers_1.D27d.div(new decimal_js_light_1.default(`1e${decimals[i]}`));
79
+ // {USD/wholeTok} = {USD/wholeTok} * {1}
80
+ const lowPrice = prices[i].mul(numbers_1.ONE.sub(priceError[i]));
81
+ const highPrice = prices[i].mul(numbers_1.ONE.add(priceError[i]));
82
+ // D27{USD/tok} = {USD/wholeTok} * D27{wholeTok/tok}
83
+ newPrices.push({
84
+ low: (0, numbers_1.bn)(lowPrice.mul(priceMultiplier)),
85
+ high: (0, numbers_1.bn)(highPrice.mul(priceMultiplier)),
86
+ });
87
+ }
88
+ // update low/high for tracking DTFs
89
+ if (!weightControl) {
90
+ // sum of dot product of targetBasket and priceError
91
+ const totalPortion = targetBasket
92
+ .map((portion, i) => portion.mul(priceError[i]))
93
+ .reduce((a, b) => a.add(b));
94
+ if (totalPortion.gte(numbers_1.ONE)) {
95
+ throw new Error('totalPortion > 1');
96
+ }
97
+ // D18{BU/share} = {1} * D18 * {BU/share}
98
+ newLimits.low = (0, numbers_1.bn)(numbers_1.ONE.div(numbers_1.ONE.add(totalPortion)).mul(numbers_1.D18d));
99
+ newLimits.high = (0, numbers_1.bn)(numbers_1.ONE.add(totalPortion).mul(numbers_1.D18d));
100
+ }
101
+ return {
102
+ weights: newWeights,
103
+ prices: newPrices,
104
+ limits: newLimits,
105
+ };
106
+ };
107
+ exports.getStartRebalance = getStartRebalance;
@@ -0,0 +1,31 @@
1
+ export declare enum PriceControl {
2
+ NONE = 0,
3
+ PARTIAL = 1,
4
+ ATOMIC_SWAP = 2
5
+ }
6
+ export interface RebalanceLimits {
7
+ low: bigint;
8
+ spot: bigint;
9
+ high: bigint;
10
+ }
11
+ export interface WeightRange {
12
+ low: bigint;
13
+ spot: bigint;
14
+ high: bigint;
15
+ }
16
+ export interface PriceRange {
17
+ low: bigint;
18
+ high: bigint;
19
+ }
20
+ export interface Rebalance {
21
+ nonce: bigint;
22
+ tokens: string[];
23
+ weights: WeightRange[];
24
+ initialPrices: PriceRange[];
25
+ inRebalance: boolean[];
26
+ limits: RebalanceLimits;
27
+ startedAt: bigint;
28
+ restrictedUntil: bigint;
29
+ availableUntil: bigint;
30
+ priceControl: PriceControl;
31
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PriceControl = void 0;
4
+ var PriceControl;
5
+ (function (PriceControl) {
6
+ PriceControl[PriceControl["NONE"] = 0] = "NONE";
7
+ PriceControl[PriceControl["PARTIAL"] = 1] = "PARTIAL";
8
+ PriceControl[PriceControl["ATOMIC_SWAP"] = 2] = "ATOMIC_SWAP";
9
+ })(PriceControl || (exports.PriceControl = PriceControl = {}));
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This function can be used to get a basket distribution EITHER from a set of historical basket weights
3
+ * or from a set of current balances. Make sure to use prices from the right time.
4
+ *
5
+ * @param _bals {tok} Current balances; or previous historical weights
6
+ * @param _prices {USD/wholeTok} USD prices for each *whole* token; or previous historical prices
7
+ * @param decimals Decimals of each token
8
+ * @returns D18{1} Current basket, total will be around 1e18 but not exactly
9
+ */
10
+ export declare const getBasketDistribution: (_bals: bigint[], _prices: number[], decimals: bigint[]) => bigint[];
package/dist/utils.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getBasketDistribution = void 0;
7
+ const decimal_js_light_1 = __importDefault(require("decimal.js-light"));
8
+ const numbers_1 = require("./numbers");
9
+ /**
10
+ * This function can be used to get a basket distribution EITHER from a set of historical basket weights
11
+ * or from a set of current balances. Make sure to use prices from the right time.
12
+ *
13
+ * @param _bals {tok} Current balances; or previous historical weights
14
+ * @param _prices {USD/wholeTok} USD prices for each *whole* token; or previous historical prices
15
+ * @param decimals Decimals of each token
16
+ * @returns D18{1} Current basket, total will be around 1e18 but not exactly
17
+ */
18
+ const getBasketDistribution = (_bals, _prices, decimals) => {
19
+ const decimalScale = decimals.map((d) => new decimal_js_light_1.default(`1e${d}`));
20
+ // {wholeTok} = {tok} / {tok/wholeTok}
21
+ const bals = _bals.map((bal, i) => new decimal_js_light_1.default(bal.toString()).div(decimalScale[i]));
22
+ // {USD/wholeTok} = {USD/wholeTok}
23
+ const prices = _prices.map((a) => new decimal_js_light_1.default(a.toString()));
24
+ // {USD} = {wholeTok} * {USD/wholeTok}
25
+ const totalValue = bals
26
+ .map((bal, i) => bal.mul(prices[i]))
27
+ .reduce((a, b) => a.add(b));
28
+ // D18{1} = {wholeTok} * {USD/wholeTok} / {USD}
29
+ return bals.map((bal, i) => (0, numbers_1.bn)(bal.mul(prices[i]).div(totalValue).mul(numbers_1.D18d)));
30
+ };
31
+ exports.getBasketDistribution = getBasketDistribution;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reserve-protocol/dtf-rebalance-lib",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Rebalancing library for DTFs in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",