@reserve-protocol/dtf-rebalance-lib 0.1.3 → 0.1.4

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,20 @@ 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
+ if (debug) {
108
112
  console.log("shareValue", shareValue.toString());
109
113
  console.log("buValue", buValue.toString());
110
114
  }
115
+ if (buValue.div(shareValue).gt(10) || shareValue.div(buValue).gt(100)) {
116
+ throw new Error("buValue and shareValue are too different");
117
+ }
111
118
  // ================================================================
112
119
  // calculate rebalanceTarget
113
120
  const ejectionIndices = [];
@@ -118,7 +125,9 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
118
125
  }
119
126
  // {1} = {wholeTok/wholeShare} * {USD/wholeTok} / {USD/wholeShare}
120
127
  const portionBeingEjected = ejectionIndices
121
- .map((i) => folio[i].mul(prices[i]))
128
+ .map((i) => {
129
+ return rebalance.inRebalance[i] ? folio[i].mul(prices[i]) : numbers_1.ZERO;
130
+ })
122
131
  .reduce((a, b) => a.add(b), numbers_1.ZERO)
123
132
  .div(shareValue);
124
133
  // {1} = {USD/wholeShare} / {USD/wholeShare}
@@ -154,39 +163,44 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
154
163
  : progression.sub(initialProgression).div(numbers_1.ONE.sub(initialProgression));
155
164
  let rebalanceTarget = numbers_1.ONE;
156
165
  let round = AuctionRound.FINAL;
157
- if (logging) {
166
+ if (debug) {
158
167
  console.log("initialProgression", initialProgression.toString());
159
168
  console.log("progression", progression.toString());
160
169
  console.log("relativeProgression", relativeProgression.toString());
161
170
  console.log("portionBeingEjected", portionBeingEjected.toString());
162
171
  console.log("finalStageAt", finalStageAt.toString());
163
172
  }
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
173
+ // approach finalStageAt first
174
+ if (progression.lt(0.99) && relativeProgression.lt(finalStageAt.sub(0.02))) {
177
175
  round = AuctionRound.PROGRESS;
178
176
  rebalanceTarget = initialProgression.add(numbers_1.ONE.sub(initialProgression).mul(finalStageAt));
177
+ if (numbers_1.ONE.sub(rebalanceTarget).lt(0.99)) {
178
+ rebalanceTarget = numbers_1.ONE;
179
+ }
179
180
  if (rebalanceTarget.eq(numbers_1.ONE)) {
180
181
  round = AuctionRound.FINAL;
181
182
  }
182
183
  }
183
- if (rebalanceTarget.gt(numbers_1.ONE)) {
184
- throw new Error("something has gone very wrong");
184
+ // EJECT
185
+ if (portionBeingEjected.gt(0)) {
186
+ round = AuctionRound.EJECT;
187
+ // if the ejections are everything that's left, keep the finalStageAt targeting from above
188
+ if (progression.add(portionBeingEjected).lt(numbers_1.ONE)) {
189
+ // else: get rid of all the dust
190
+ let ejectionTarget = progression.add(portionBeingEjected.mul(1.02)); // buy 2% extra
191
+ if (ejectionTarget.gt(numbers_1.ONE)) {
192
+ ejectionTarget = numbers_1.ONE;
193
+ }
194
+ if (ejectionTarget.gt(rebalanceTarget)) {
195
+ rebalanceTarget = ejectionTarget;
196
+ }
197
+ }
185
198
  }
186
- if (rebalanceTarget.lt(progression) || numbers_1.ONE.sub(rebalanceTarget).lt(1e-4)) {
187
- rebalanceTarget = numbers_1.ONE;
199
+ if (rebalanceTarget.lte(numbers_1.ZERO) || rebalanceTarget.gt(numbers_1.ONE)) {
200
+ throw new Error("something has gone very wrong");
188
201
  }
189
- if (logging) {
202
+ if (debug) {
203
+ console.log("round", round);
190
204
  console.log("rebalanceTarget", rebalanceTarget.toString());
191
205
  }
192
206
  // {1}
@@ -201,11 +215,21 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
201
215
  spot: (0, numbers_1.bn)(spotLimit.mul(numbers_1.D18d)),
202
216
  high: (0, numbers_1.bn)(spotLimit.add(spotLimit.mul(delta)).mul(numbers_1.D18d)),
203
217
  };
204
- if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
205
- // aim 1% higher if executed permissonlessly
206
- newLimits.spot += (newLimits.spot * 1n) / 100n;
218
+ // hold some surpluses aside if ejecting
219
+ if (round == AuctionRound.EJECT) {
220
+ // buy 0.1% more
221
+ newLimits.low += newLimits.low / 1000n;
222
+ // aim 1% higher
223
+ newLimits.spot += newLimits.spot / 100n;
207
224
  // leave 10% room to increase low in the future if ejection leaves dust behind
208
- newLimits.high += (newLimits.high * 10n) / 100n;
225
+ newLimits.high += newLimits.high / 10n;
226
+ }
227
+ else if (round == AuctionRound.FINAL && progression.gt(0.999)) {
228
+ // if it's the final round and we're within 0.1%, buy 10% more than we need to
229
+ const delta = (0, numbers_1.bn)(numbers_1.ONE.sub(progression).mul(numbers_1.D18d).div(10));
230
+ newLimits.low += (newLimits.low * delta) / 10n ** 18n;
231
+ newLimits.spot += (newLimits.spot * delta) / 10n ** 18n;
232
+ newLimits.high += (newLimits.high * delta) / 10n ** 18n;
209
233
  }
210
234
  // low
211
235
  if (newLimits.low < rebalance.limits.low) {
@@ -228,7 +252,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
228
252
  if (newLimits.high > rebalance.limits.high) {
229
253
  newLimits.high = rebalance.limits.high;
230
254
  }
231
- if (logging) {
255
+ if (debug) {
232
256
  console.log("newLimits", newLimits);
233
257
  }
234
258
  // ================================================================
@@ -257,11 +281,21 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
257
281
  .mul(decimalScale[i])
258
282
  .div(numbers_1.D18d)),
259
283
  };
260
- if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
261
- // aim 1% higher if executed permissonlessly
262
- newWeightsD27.spot += (newWeightsD27.spot * 1n) / 100n;
284
+ // hold some surpluses aside if ejecting
285
+ if (round == AuctionRound.EJECT) {
286
+ // buy 0.1% more
287
+ newWeightsD27.low += newWeightsD27.low / 1000n;
288
+ // aim 1% higher
289
+ newWeightsD27.spot += newWeightsD27.spot / 100n;
263
290
  // leave 10% room to increase low in the future if ejection leaves dust behind
264
- newWeightsD27.high += (newWeightsD27.high * 10n) / 100n;
291
+ newWeightsD27.high += newWeightsD27.high / 10n;
292
+ }
293
+ else if (round == AuctionRound.FINAL && progression.gt(0.999)) {
294
+ // if it's the final round and we're within 0.1%, buy 10% more than we need to
295
+ const delta = (0, numbers_1.bn)(numbers_1.ONE.sub(progression).mul(numbers_1.D18d).div(10));
296
+ newWeightsD27.low += (newWeightsD27.low * delta) / 10n ** 18n;
297
+ newWeightsD27.spot += (newWeightsD27.spot * delta) / 10n ** 18n;
298
+ newWeightsD27.high += (newWeightsD27.high * delta) / 10n ** 18n;
265
299
  }
266
300
  if (newWeightsD27.low < weightRange.low) {
267
301
  newWeightsD27.low = weightRange.low;
@@ -283,7 +317,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
283
317
  }
284
318
  return newWeightsD27;
285
319
  });
286
- if (logging) {
320
+ if (debug) {
287
321
  console.log("newWeights", newWeights);
288
322
  }
289
323
  // ================================================================
@@ -322,7 +356,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
322
356
  }
323
357
  return pricesD27;
324
358
  });
325
- if (logging) {
359
+ if (debug) {
326
360
  console.log("newPrices", newPrices);
327
361
  }
328
362
  // ================================================================
@@ -344,19 +378,31 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
344
378
  // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
345
379
  const buyUpTo = weightRanges[i].low.mul(actualLimits.low);
346
380
  const sellDownTo = weightRanges[i].high.mul(actualLimits.high);
347
- if (folio[i].lt(buyUpTo)) {
381
+ if (rebalance.inRebalance[i] && folio[i].lt(buyUpTo)) {
348
382
  deficitTokens.push(token);
349
383
  }
350
- else if (folio[i].gt(sellDownTo)) {
384
+ else if (rebalance.inRebalance[i] && folio[i].gt(sellDownTo)) {
351
385
  surplusTokens.push(token);
352
386
  }
353
387
  });
388
+ // ================================================================
389
+ // only return tokens that are in the rebalance
390
+ const returnTokens = [];
391
+ const returnWeights = [];
392
+ const returnPrices = [];
393
+ for (let i = 0; i < rebalance.tokens.length; i++) {
394
+ if (rebalance.inRebalance[i]) {
395
+ returnTokens.push(rebalance.tokens[i]);
396
+ returnWeights.push(newWeights[i]);
397
+ returnPrices.push(newPrices[i]);
398
+ }
399
+ }
354
400
  return [
355
401
  {
356
402
  rebalanceNonce: rebalance.nonce,
357
- tokens: rebalance.tokens, // full set of tokens, not pruned to the active buy/sells
358
- newWeights: newWeights,
359
- newPrices: newPrices,
403
+ tokens: returnTokens,
404
+ newWeights: returnWeights,
405
+ newPrices: returnPrices,
360
406
  newLimits: newLimits,
361
407
  },
362
408
  {
@@ -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}
@@ -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
109
  newLimits.low = (0, numbers_1.bn)(numbers_1.ONE.div(numbers_1.ONE.add(totalPortion)).mul(numbers_1.D18d));
108
110
  newLimits.high = (0, numbers_1.bn)(numbers_1.ONE.add(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.4",
4
4
  "description": "Rebalancing library for DTFs in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",