@reserve-protocol/dtf-rebalance-lib 0.0.1
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/.github/workflows/publish.yml +15 -0
- package/LICENSE.md +55 -0
- package/README.md +3 -0
- package/package.json +37 -0
- package/src/index.ts +5 -0
- package/src/numbers.ts +17 -0
- package/src/open-auction.ts +495 -0
- package/src/start-rebalance.ts +150 -0
- package/src/types.ts +35 -0
- package/src/utils.ts +36 -0
- package/test/rebalancing.test.ts +1448 -0
- package/tsconfig.json +14 -0
- package/tsconfig.test.json +9 -0
@@ -0,0 +1,150 @@
|
|
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 decimals Decimals of each token
|
25
|
+
* @param _targetBasket D18{1} Ideal basket
|
26
|
+
* @param _prices {USD/wholeTok} USD prices for each *whole* token
|
27
|
+
* @param _priceError {1} Price error per token to use in the rebalanc; should be larger than price error during openAuction
|
28
|
+
* @param _dtfPrice {USD/wholeShare} DTF price
|
29
|
+
* @param weightControl TRACKING=false, NATIVE=true
|
30
|
+
*/
|
31
|
+
export const getStartRebalance = (
|
32
|
+
_supply: bigint,
|
33
|
+
tokens: string[],
|
34
|
+
decimals: bigint[],
|
35
|
+
_targetBasket: bigint[],
|
36
|
+
_prices: number[],
|
37
|
+
_priceError: number[],
|
38
|
+
_dtfPrice: number,
|
39
|
+
weightControl: boolean
|
40
|
+
): StartRebalanceArgsPartial => {
|
41
|
+
// convert price number inputs to bigints
|
42
|
+
|
43
|
+
// {USD/wholeTok}
|
44
|
+
const prices = _prices.map((a) => new Decimal(a.toString()))
|
45
|
+
for (let i = 0; i < prices.length; i++) {
|
46
|
+
if (prices[i].eq(ZERO)) {
|
47
|
+
throw new Error(`missing price for token ${tokens[i]}`)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
// {USD/wholeShare}
|
52
|
+
const dtfPrice = new Decimal(_dtfPrice)
|
53
|
+
|
54
|
+
// {1} = D18{1} / D18
|
55
|
+
const targetBasket = _targetBasket.map((a) =>
|
56
|
+
new Decimal(a.toString()).div(D18d)
|
57
|
+
)
|
58
|
+
|
59
|
+
// {1}
|
60
|
+
const priceError = _priceError.map((a) => new Decimal(a.toString()))
|
61
|
+
|
62
|
+
// ================================================================
|
63
|
+
|
64
|
+
const newWeights: WeightRange[] = []
|
65
|
+
const newPrices: PriceRange[] = []
|
66
|
+
const newLimits: RebalanceLimits = {
|
67
|
+
low: bn('1e18'),
|
68
|
+
spot: bn('1e18'),
|
69
|
+
high: bn('1e18'),
|
70
|
+
}
|
71
|
+
|
72
|
+
// ================================================================
|
73
|
+
|
74
|
+
for (let i = 0; i < tokens.length; i++) {
|
75
|
+
if (priceError[i].gte(ONE)) {
|
76
|
+
throw new Error('cannot defer prices')
|
77
|
+
}
|
78
|
+
|
79
|
+
// === newWeights ===
|
80
|
+
|
81
|
+
|
82
|
+
// {wholeTok/wholeShare} = {1} * {USD/wholeShare} / {USD/wholeTok}
|
83
|
+
const spotWeight = targetBasket[i].mul(dtfPrice).div(prices[i])
|
84
|
+
|
85
|
+
|
86
|
+
// D27{tok/share}{wholeShare/wholeTok} = D27 * {tok/wholeTok} / {share/wholeShare}
|
87
|
+
const limitMultiplier = D27d.mul(new Decimal(`1e${decimals[i]}`)).div(D18d)
|
88
|
+
|
89
|
+
if (!weightControl) {
|
90
|
+
// D27{tok/BU} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
|
91
|
+
newWeights.push({
|
92
|
+
low: bn(spotWeight.mul(limitMultiplier)),
|
93
|
+
spot: bn(spotWeight.mul(limitMultiplier)),
|
94
|
+
high: bn(spotWeight.mul(limitMultiplier)),
|
95
|
+
})
|
96
|
+
} else {
|
97
|
+
// NATIVE case
|
98
|
+
|
99
|
+
// {wholeTok/wholeShare} = {wholeTok/wholeShare} / {1}
|
100
|
+
const lowWeight = spotWeight.mul(ONE.div(ONE.add(priceError[i])))
|
101
|
+
const highWeight = spotWeight.mul(ONE.add(priceError[i]))
|
102
|
+
|
103
|
+
|
104
|
+
// D27{tok/share} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
|
105
|
+
newWeights.push({
|
106
|
+
low: bn(lowWeight.mul(limitMultiplier)),
|
107
|
+
spot: bn(spotWeight.mul(limitMultiplier)),
|
108
|
+
high: bn(highWeight.mul(limitMultiplier)),
|
109
|
+
})
|
110
|
+
}
|
111
|
+
|
112
|
+
// === newPrices ===
|
113
|
+
|
114
|
+
// D27{wholeTok/tok} = D27 / {tok/wholeTok}
|
115
|
+
const priceMultiplier = D27d.div(new Decimal(`1e${decimals[i]}`))
|
116
|
+
|
117
|
+
// {USD/wholeTok} = {USD/wholeTok} * {1}
|
118
|
+
const lowPrice = prices[i].mul(ONE.sub(priceError[i]))
|
119
|
+
const highPrice = prices[i].mul(ONE.add(priceError[i]))
|
120
|
+
|
121
|
+
|
122
|
+
// D27{USD/tok} = {USD/wholeTok} * D27{wholeTok/tok}
|
123
|
+
newPrices.push({
|
124
|
+
low: bn(lowPrice.mul(priceMultiplier)),
|
125
|
+
high: bn(highPrice.mul(priceMultiplier)),
|
126
|
+
})
|
127
|
+
}
|
128
|
+
|
129
|
+
// update low/high for tracking DTFs
|
130
|
+
if (!weightControl) {
|
131
|
+
// sum of dot product of targetBasket and priceError
|
132
|
+
const totalPortion = targetBasket
|
133
|
+
.map((portion: Decimal, i: number) => portion.mul(priceError[i]))
|
134
|
+
.reduce((a: Decimal, b: Decimal) => a.add(b))
|
135
|
+
|
136
|
+
if (totalPortion.gte(ONE)) {
|
137
|
+
throw new Error('totalPortion > 1')
|
138
|
+
}
|
139
|
+
|
140
|
+
// D18{BU/share} = {1} * D18 * {BU/share}
|
141
|
+
newLimits.low = bn(ONE.div(ONE.add(totalPortion)).mul(D18d))
|
142
|
+
newLimits.high = bn(ONE.add(totalPortion).mul(D18d))
|
143
|
+
}
|
144
|
+
|
145
|
+
return {
|
146
|
+
weights: newWeights,
|
147
|
+
prices: newPrices,
|
148
|
+
limits: newLimits,
|
149
|
+
}
|
150
|
+
}
|
package/src/types.ts
ADDED
@@ -0,0 +1,35 @@
|
|
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
ADDED
@@ -0,0 +1,36 @@
|
|
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
|
+
}
|