@reserve-protocol/dtf-rebalance-lib 0.1.3 → 0.1.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.
@@ -59,4 +59,4 @@ export declare const getTargetBasket: (_initialWeights: WeightRange[], _prices:
59
59
  * @param _priceError {1} Price error to use for each token during auction pricing; should be smaller than price error during startRebalance
60
60
  * @param _finalStageAt {1} The % rebalanced from the initial Folio to determine when is the final stage of the rebalance
61
61
  */
62
- export declare const getOpenAuction: (rebalance: Rebalance, _supply: bigint, _initialFolio: bigint[] | undefined, _targetBasket: bigint[] | undefined, _folio: bigint[], _decimals: bigint[], _prices: number[], _priceError: number[], _finalStageAt: number, logging?: boolean) => [OpenAuctionArgs, AuctionMetrics];
62
+ export declare const getOpenAuction: (rebalance: Rebalance, _supply: bigint, _initialFolio: bigint[] | undefined, _targetBasket: bigint[] | undefined, _folio: bigint[], _decimals: bigint[], _prices: number[], _priceError: number[], _finalStageAt: number, debug?: boolean) => [OpenAuctionArgs, AuctionMetrics];
@@ -55,8 +55,8 @@ exports.getTargetBasket = getTargetBasket;
55
55
  * @param _priceError {1} Price error to use for each token during auction pricing; should be smaller than price error during startRebalance
56
56
  * @param _finalStageAt {1} The % rebalanced from the initial Folio to determine when is the final stage of the rebalance
57
57
  */
58
- const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket = [], _folio, _decimals, _prices, _priceError, _finalStageAt, logging) => {
59
- if (logging) {
58
+ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket = [], _folio, _decimals, _prices, _priceError, _finalStageAt, debug) => {
59
+ if (debug) {
60
60
  console.log("getOpenAuction", rebalance, _supply, _initialFolio, _targetBasket, _folio, _decimals, _prices, _priceError, _finalStageAt);
61
61
  }
62
62
  if (rebalance.tokens.length != _targetBasket.length ||
@@ -101,13 +101,22 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
101
101
  // ================================================================
102
102
  // calculate ideal spot limit, the actual BU<->share ratio
103
103
  // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
104
- const shareValue = folio.map((f, i) => f.mul(prices[i])).reduce((a, b) => a.add(b));
104
+ const shareValue = folio
105
+ .map((f, i) => {
106
+ return rebalance.inRebalance[i] ? f.mul(prices[i]) : numbers_1.ZERO;
107
+ })
108
+ .reduce((a, b) => a.add(b));
105
109
  // {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
106
110
  const buValue = weightRanges.map((weightRange, i) => weightRange.spot.mul(prices[i])).reduce((a, b) => a.add(b));
107
- if (logging) {
111
+ const buPriceChange = buValue.sub(shareValue).abs().div(shareValue);
112
+ console.log(` 🧺 ${buPriceChange.mul(100).toFixed(2)}% price change`);
113
+ if (debug) {
108
114
  console.log("shareValue", shareValue.toString());
109
115
  console.log("buValue", buValue.toString());
110
116
  }
117
+ if (buValue.div(shareValue).gt(10) || shareValue.div(buValue).gt(10)) {
118
+ throw new Error("buValue and shareValue are too different");
119
+ }
111
120
  // ================================================================
112
121
  // calculate rebalanceTarget
113
122
  const ejectionIndices = [];
@@ -118,7 +127,9 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
118
127
  }
119
128
  // {1} = {wholeTok/wholeShare} * {USD/wholeTok} / {USD/wholeShare}
120
129
  const portionBeingEjected = ejectionIndices
121
- .map((i) => folio[i].mul(prices[i]))
130
+ .map((i) => {
131
+ return rebalance.inRebalance[i] ? folio[i].mul(prices[i]) : numbers_1.ZERO;
132
+ })
122
133
  .reduce((a, b) => a.add(b), numbers_1.ZERO)
123
134
  .div(shareValue);
124
135
  // {1} = {USD/wholeShare} / {USD/wholeShare}
@@ -154,39 +165,44 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
154
165
  : progression.sub(initialProgression).div(numbers_1.ONE.sub(initialProgression));
155
166
  let rebalanceTarget = numbers_1.ONE;
156
167
  let round = AuctionRound.FINAL;
157
- if (logging) {
168
+ if (debug) {
158
169
  console.log("initialProgression", initialProgression.toString());
159
170
  console.log("progression", progression.toString());
160
171
  console.log("relativeProgression", relativeProgression.toString());
161
172
  console.log("portionBeingEjected", portionBeingEjected.toString());
162
173
  console.log("finalStageAt", finalStageAt.toString());
163
174
  }
164
- // make it an eject auction if there is 1 bps or more of value to eject
165
- if (portionBeingEjected.gte(1e-4)) {
166
- round = AuctionRound.EJECT;
167
- if (relativeProgression.lt(finalStageAt.sub(0.02))) {
168
- rebalanceTarget = progression.add(portionBeingEjected.mul(1.1)); // set rebalanceTarget to 10% more than needed to ensure ejection completes
169
- // do not finish trading yet
170
- if (rebalanceTarget.gte(numbers_1.ONE)) {
171
- rebalanceTarget = initialProgression.add(numbers_1.ONE.sub(initialProgression).mul(finalStageAt));
172
- }
173
- }
174
- }
175
- else if (relativeProgression.lt(finalStageAt.sub(0.02))) {
176
- // wiggle room to prevent having to re-run an auction at the same stage after price movement
175
+ // approach finalStageAt first
176
+ if (progression.lt(0.99) && relativeProgression.lt(finalStageAt.sub(0.02))) {
177
177
  round = AuctionRound.PROGRESS;
178
178
  rebalanceTarget = initialProgression.add(numbers_1.ONE.sub(initialProgression).mul(finalStageAt));
179
+ if (rebalanceTarget.gte(0.99)) {
180
+ rebalanceTarget = numbers_1.ONE;
181
+ }
179
182
  if (rebalanceTarget.eq(numbers_1.ONE)) {
180
183
  round = AuctionRound.FINAL;
181
184
  }
182
185
  }
183
- if (rebalanceTarget.gt(numbers_1.ONE)) {
184
- throw new Error("something has gone very wrong");
186
+ // EJECT
187
+ if (portionBeingEjected.gt(0)) {
188
+ round = AuctionRound.EJECT;
189
+ // if the ejections are everything that's left, keep the finalStageAt targeting from above
190
+ if (progression.add(portionBeingEjected).lt(numbers_1.ONE)) {
191
+ // else: get rid of all the dust
192
+ let ejectionTarget = progression.add(portionBeingEjected.mul(1.02)); // buy 2% extra
193
+ if (ejectionTarget.gt(numbers_1.ONE)) {
194
+ ejectionTarget = numbers_1.ONE;
195
+ }
196
+ if (ejectionTarget.gt(rebalanceTarget)) {
197
+ rebalanceTarget = ejectionTarget;
198
+ }
199
+ }
185
200
  }
186
- if (rebalanceTarget.lt(progression) || numbers_1.ONE.sub(rebalanceTarget).lt(1e-4)) {
187
- rebalanceTarget = numbers_1.ONE;
201
+ if (rebalanceTarget.lte(numbers_1.ZERO) || rebalanceTarget.gt(numbers_1.ONE)) {
202
+ throw new Error("something has gone very wrong");
188
203
  }
189
- if (logging) {
204
+ if (debug) {
205
+ console.log("round", round);
190
206
  console.log("rebalanceTarget", rebalanceTarget.toString());
191
207
  }
192
208
  // {1}
@@ -201,11 +217,14 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
201
217
  spot: (0, numbers_1.bn)(spotLimit.mul(numbers_1.D18d)),
202
218
  high: (0, numbers_1.bn)(spotLimit.add(spotLimit.mul(delta)).mul(numbers_1.D18d)),
203
219
  };
204
- if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
205
- // aim 1% higher if executed permissonlessly
206
- newLimits.spot += (newLimits.spot * 1n) / 100n;
220
+ // hold some surpluses aside if ejecting
221
+ if (round == AuctionRound.EJECT) {
222
+ // buy 0.1% more
223
+ newLimits.low += newLimits.low / 1000n;
224
+ // aim 1% higher in the future
225
+ newLimits.spot += newLimits.spot / 100n;
207
226
  // leave 10% room to increase low in the future if ejection leaves dust behind
208
- newLimits.high += (newLimits.high * 10n) / 100n;
227
+ newLimits.high += newLimits.high / 10n;
209
228
  }
210
229
  // low
211
230
  if (newLimits.low < rebalance.limits.low) {
@@ -228,7 +247,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
228
247
  if (newLimits.high > rebalance.limits.high) {
229
248
  newLimits.high = rebalance.limits.high;
230
249
  }
231
- if (logging) {
250
+ if (debug) {
232
251
  console.log("newLimits", newLimits);
233
252
  }
234
253
  // ================================================================
@@ -257,11 +276,14 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
257
276
  .mul(decimalScale[i])
258
277
  .div(numbers_1.D18d)),
259
278
  };
260
- if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
261
- // aim 1% higher if executed permissonlessly
262
- newWeightsD27.spot += (newWeightsD27.spot * 1n) / 100n;
279
+ // hold some surpluses aside if ejecting
280
+ if (round == AuctionRound.EJECT) {
281
+ // buy 0.1% more
282
+ newWeightsD27.low += newWeightsD27.low / 1000n;
283
+ // aim 1% higher in the future
284
+ newWeightsD27.spot += newWeightsD27.spot / 100n;
263
285
  // leave 10% room to increase low in the future if ejection leaves dust behind
264
- newWeightsD27.high += (newWeightsD27.high * 10n) / 100n;
286
+ newWeightsD27.high += newWeightsD27.high / 10n;
265
287
  }
266
288
  if (newWeightsD27.low < weightRange.low) {
267
289
  newWeightsD27.low = weightRange.low;
@@ -283,7 +305,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
283
305
  }
284
306
  return newWeightsD27;
285
307
  });
286
- if (logging) {
308
+ if (debug) {
287
309
  console.log("newWeights", newWeights);
288
310
  }
289
311
  // ================================================================
@@ -293,7 +315,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
293
315
  // revert if price out of bounds
294
316
  const spotPrice = (0, numbers_1.bn)(prices[i].mul(numbers_1.D27d).div(decimalScale[i]));
295
317
  if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
296
- throw new Error("spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!");
318
+ throw new Error(`spot price ${spotPrice.toString()} out of bounds relative to initial range [${initialPrice.low.toString()}, ${initialPrice.high.toString()}]! auction launcher MUST closeRebalance to prevent loss!`);
297
319
  }
298
320
  if (rebalance.priceControl == types_1.PriceControl.NONE) {
299
321
  return initialPrice;
@@ -322,7 +344,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
322
344
  }
323
345
  return pricesD27;
324
346
  });
325
- if (logging) {
347
+ if (debug) {
326
348
  console.log("newPrices", newPrices);
327
349
  }
328
350
  // ================================================================
@@ -344,19 +366,31 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
344
366
  // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
345
367
  const buyUpTo = weightRanges[i].low.mul(actualLimits.low);
346
368
  const sellDownTo = weightRanges[i].high.mul(actualLimits.high);
347
- if (folio[i].lt(buyUpTo)) {
369
+ if (rebalance.inRebalance[i] && folio[i].lt(buyUpTo)) {
348
370
  deficitTokens.push(token);
349
371
  }
350
- else if (folio[i].gt(sellDownTo)) {
372
+ else if (rebalance.inRebalance[i] && folio[i].gt(sellDownTo)) {
351
373
  surplusTokens.push(token);
352
374
  }
353
375
  });
376
+ // ================================================================
377
+ // only return tokens that are in the rebalance
378
+ const returnTokens = [];
379
+ const returnWeights = [];
380
+ const returnPrices = [];
381
+ for (let i = 0; i < rebalance.tokens.length; i++) {
382
+ if (rebalance.inRebalance[i]) {
383
+ returnTokens.push(rebalance.tokens[i]);
384
+ returnWeights.push(newWeights[i]);
385
+ returnPrices.push(newPrices[i]);
386
+ }
387
+ }
354
388
  return [
355
389
  {
356
390
  rebalanceNonce: rebalance.nonce,
357
- tokens: rebalance.tokens, // full set of tokens, not pruned to the active buy/sells
358
- newWeights: newWeights,
359
- newPrices: newPrices,
391
+ tokens: returnTokens,
392
+ newWeights: returnWeights,
393
+ newPrices: returnPrices,
360
394
  newLimits: newLimits,
361
395
  },
362
396
  {
@@ -1,4 +1,4 @@
1
- import { PriceRange, RebalanceLimits, WeightRange } from './types';
1
+ import { PriceRange, RebalanceLimits, WeightRange } from "./types";
2
2
  export interface StartRebalanceArgsPartial {
3
3
  weights: WeightRange[];
4
4
  prices: PriceRange[];
@@ -19,4 +19,4 @@ export interface StartRebalanceArgsPartial {
19
19
  * @param _dtfPrice {USD/wholeShare} DTF price
20
20
  * @param weightControl TRACKING=false, NATIVE=true
21
21
  */
22
- export declare const getStartRebalance: (_supply: bigint, tokens: string[], _folio: bigint[], decimals: bigint[], _targetBasket: bigint[], _prices: number[], _priceError: number[], weightControl: boolean, logging?: boolean) => StartRebalanceArgsPartial;
22
+ export declare const getStartRebalance: (_supply: bigint, tokens: string[], _folio: bigint[], decimals: bigint[], _targetBasket: bigint[], _prices: number[], _priceError: number[], weightControl: boolean, debug?: boolean) => StartRebalanceArgsPartial;
@@ -21,9 +21,9 @@ const numbers_1 = require("./numbers");
21
21
  * @param _dtfPrice {USD/wholeShare} DTF price
22
22
  * @param weightControl TRACKING=false, NATIVE=true
23
23
  */
24
- const getStartRebalance = (_supply, tokens, _folio, decimals, _targetBasket, _prices, _priceError, weightControl, logging) => {
25
- if (logging) {
26
- console.log('getStartRebalance', _supply, tokens, _folio, decimals, _targetBasket, _prices, _priceError, weightControl);
24
+ const getStartRebalance = (_supply, tokens, _folio, decimals, _targetBasket, _prices, _priceError, weightControl, debug) => {
25
+ if (debug) {
26
+ console.log("getStartRebalance", _supply, tokens, _folio, decimals, _targetBasket, _prices, _priceError, weightControl);
27
27
  }
28
28
  // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
29
29
  const folio = _folio.map((c, i) => new decimal_js_light_1.default(c.toString()).div(new decimal_js_light_1.default(`1e${decimals[i]}`)));
@@ -43,24 +43,26 @@ const getStartRebalance = (_supply, tokens, _folio, decimals, _targetBasket, _pr
43
43
  const newWeights = [];
44
44
  const newPrices = [];
45
45
  const newLimits = {
46
- low: (0, numbers_1.bn)('1e18'),
47
- spot: (0, numbers_1.bn)('1e18'),
48
- high: (0, numbers_1.bn)('1e18'),
46
+ low: (0, numbers_1.bn)("1e18"),
47
+ spot: (0, numbers_1.bn)("1e18"),
48
+ high: (0, numbers_1.bn)("1e18"),
49
49
  };
50
50
  // ================================================================
51
51
  for (let i = 0; i < tokens.length; i++) {
52
52
  if (priceError[i].gte(numbers_1.ONE)) {
53
- throw new Error('cannot defer prices');
53
+ throw new Error("cannot defer prices");
54
54
  }
55
55
  // === newWeights ===
56
56
  // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
57
- const dtfPrice = folio.map((f, i) => f.mul(prices[i])).reduce((a, b) => a.add(b));
57
+ const dtfPrice = folio
58
+ .map((f, i) => f.mul(prices[i]))
59
+ .reduce((a, b) => a.add(b));
58
60
  // {wholeTok/wholeShare} = {1} * {USD/wholeShare} / {USD/wholeTok}
59
61
  const spotWeight = targetBasket[i].mul(dtfPrice).div(prices[i]);
60
62
  // D27{tok/share}{wholeShare/wholeTok} = D27 * {tok/wholeTok} / {share/wholeShare}
61
63
  const limitMultiplier = numbers_1.D27d.mul(new decimal_js_light_1.default(`1e${decimals[i]}`)).div(numbers_1.D18d);
62
- if (logging) {
63
- console.log('limitMultiplier', limitMultiplier.toString());
64
+ if (debug) {
65
+ console.log("limitMultiplier", limitMultiplier.toString());
64
66
  }
65
67
  if (!weightControl) {
66
68
  // D27{tok/BU} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
@@ -73,8 +75,8 @@ const getStartRebalance = (_supply, tokens, _folio, decimals, _targetBasket, _pr
73
75
  else {
74
76
  // NATIVE case
75
77
  // {wholeTok/wholeShare} = {wholeTok/wholeShare} / {1}
76
- const lowWeight = spotWeight.mul(numbers_1.ONE.div(numbers_1.ONE.add(priceError[i])));
77
- const highWeight = spotWeight.mul(numbers_1.ONE.add(priceError[i]));
78
+ const lowWeight = spotWeight.mul(numbers_1.ONE.sub(priceError[i]));
79
+ const highWeight = spotWeight.div(numbers_1.ONE.sub(priceError[i]));
78
80
  // D27{tok/share} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
79
81
  newWeights.push({
80
82
  low: (0, numbers_1.bn)(lowWeight.mul(limitMultiplier)),
@@ -101,16 +103,16 @@ const getStartRebalance = (_supply, tokens, _folio, decimals, _targetBasket, _pr
101
103
  .map((portion, i) => portion.mul(priceError[i]))
102
104
  .reduce((a, b) => a.add(b));
103
105
  if (totalPortion.gte(numbers_1.ONE)) {
104
- throw new Error('totalPortion > 1');
106
+ throw new Error("totalPortion > 1");
105
107
  }
106
108
  // D18{BU/share} = {1} * D18 * {BU/share}
107
- newLimits.low = (0, numbers_1.bn)(numbers_1.ONE.div(numbers_1.ONE.add(totalPortion)).mul(numbers_1.D18d));
108
- newLimits.high = (0, numbers_1.bn)(numbers_1.ONE.add(totalPortion).mul(numbers_1.D18d));
109
+ newLimits.low = (0, numbers_1.bn)(numbers_1.ONE.sub(totalPortion).mul(numbers_1.D18d));
110
+ newLimits.high = (0, numbers_1.bn)(numbers_1.ONE.div(numbers_1.ONE.sub(totalPortion)).mul(numbers_1.D18d));
109
111
  }
110
- if (logging) {
112
+ if (debug) {
111
113
  console.log("newWeights", newWeights);
112
- console.log('newPrices', newPrices);
113
- console.log('newLimits', newLimits);
114
+ console.log("newPrices", newPrices);
115
+ console.log("newLimits", newLimits);
114
116
  }
115
117
  return {
116
118
  weights: newWeights,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reserve-protocol/dtf-rebalance-lib",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Rebalancing library for DTFs in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",