@panoptic-eng/sdk 1.0.26 → 1.0.28
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/cow/index.d.ts +5 -4
- package/dist/cow/index.js +4 -3
- package/dist/cow/types.d.ts +2 -2
- package/dist/{cow-UR3yv8Wz.js → cow-Boc-39-Y.js} +2 -2
- package/dist/{cow-UR3yv8Wz.js.map → cow-Boc-39-Y.js.map} +1 -1
- package/dist/greeks-GysWXct-.js +1072 -0
- package/dist/greeks-GysWXct-.js.map +1 -0
- package/dist/index-B3JZRUNo.d.ts +9084 -0
- package/dist/index-B3JZRUNo.d.ts.map +1 -0
- package/dist/index-CDCAju5o.d.ts +261 -0
- package/dist/index-CDCAju5o.d.ts.map +1 -0
- package/dist/{index-CIlr4SNh.d.ts → index-DQS8Luef.d.ts} +4 -4
- package/dist/{index-CIlr4SNh.d.ts.map → index-DQS8Luef.d.ts.map} +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/{irm-Dw6fa-DV.js → irm-B0Bgj0z8.js} +4 -3
- package/dist/{irm-Dw6fa-DV.js.map → irm-B0Bgj0z8.js.map} +1 -1
- package/dist/{irm-CSJX9BsU.d.ts → irm-BNtE1Dmg.d.ts} +2 -2
- package/dist/{irm-CSJX9BsU.d.ts.map → irm-BNtE1Dmg.d.ts.map} +1 -1
- package/dist/{types-CspUPolV.d.ts → oracle-BnpPVG8V.d.ts} +4 -244
- package/dist/oracle-BnpPVG8V.d.ts.map +1 -0
- package/dist/panoptic/v2/greeks/index.d.ts +3 -0
- package/dist/panoptic/v2/greeks/index.js +3 -0
- package/dist/panoptic/v2/index.d.ts +8 -10180
- package/dist/panoptic/v2/index.js +6 -11281
- package/dist/panoptic/v2/react-public.d.ts +886 -0
- package/dist/panoptic/v2/react-public.d.ts.map +1 -0
- package/dist/panoptic/v2/react-public.js +3854 -0
- package/dist/panoptic/v2/react-public.js.map +1 -0
- package/dist/position-Bmd_3nzI.d.ts +199 -0
- package/dist/position-Bmd_3nzI.d.ts.map +1 -0
- package/dist/{position-GYsTTl71.js → position-R0IGLpQS.js} +3 -1056
- package/dist/position-R0IGLpQS.js.map +1 -0
- package/dist/{router-DGUQo-8h.js → router-NPhQUNh4.js} +2 -2
- package/dist/{router-DGUQo-8h.js.map → router-NPhQUNh4.js.map} +1 -1
- package/dist/{simulation-4SzTI-xf.d.ts → simulation-CUqERC5Y.d.ts} +21 -198
- package/dist/simulation-CUqERC5Y.d.ts.map +1 -0
- package/dist/test/index.d.ts +2 -2
- package/dist/test/index.d.ts.map +1 -1
- package/dist/{tx-Banz1S18.d.ts → tx-BXXqhBHn.d.ts} +1 -1
- package/dist/{tx-Banz1S18.d.ts.map → tx-BXXqhBHn.d.ts.map} +1 -1
- package/dist/{types-Bzw_7NJv.d.ts → types-DgWfno7q.d.ts} +2 -2
- package/dist/{types-Bzw_7NJv.d.ts.map → types-DgWfno7q.d.ts.map} +1 -1
- package/dist/types-VeWdNN0j.d.ts +247 -0
- package/dist/types-VeWdNN0j.d.ts.map +1 -0
- package/dist/uniswap/index.d.ts +5 -3
- package/dist/uniswap/index.d.ts.map +1 -1
- package/dist/uniswap/index.js +4 -3
- package/dist/uniswap/index.js.map +1 -1
- package/dist/v2-CkOo7vW2.js +7438 -0
- package/dist/v2-CkOo7vW2.js.map +1 -0
- package/dist/{writes-h-IX3XQq.js → writes-Dhzpw0dq.js} +4 -3
- package/dist/{writes-h-IX3XQq.js.map → writes-Dhzpw0dq.js.map} +1 -1
- package/package.json +9 -1
- package/dist/panoptic/v2/index.d.ts.map +0 -1
- package/dist/panoptic/v2/index.js.map +0 -1
- package/dist/position-GYsTTl71.js.map +0 -1
- package/dist/simulation-4SzTI-xf.d.ts.map +0 -1
- package/dist/types-CspUPolV.d.ts.map +0 -1
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
//#region src/panoptic/v2/utils/constants.ts
|
|
2
|
+
/**
|
|
3
|
+
* Constants for the Panoptic v2 SDK.
|
|
4
|
+
* @module v2/utils/constants
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* WAD constant (10^18) used for fixed-point arithmetic.
|
|
8
|
+
* Commonly used for spread calculations and other WAD-scaled values.
|
|
9
|
+
*/
|
|
10
|
+
const WAD = 10n ** 18n;
|
|
11
|
+
/**
|
|
12
|
+
* Zero collateral object for guest mode.
|
|
13
|
+
* Returns safe defaults when no wallet is connected.
|
|
14
|
+
*/
|
|
15
|
+
const ZERO_COLLATERAL = {
|
|
16
|
+
token0: {
|
|
17
|
+
assets: 0n,
|
|
18
|
+
shares: 0n,
|
|
19
|
+
availableAssets: 0n,
|
|
20
|
+
lockedAssets: 0n
|
|
21
|
+
},
|
|
22
|
+
token1: {
|
|
23
|
+
assets: 0n,
|
|
24
|
+
shares: 0n,
|
|
25
|
+
availableAssets: 0n,
|
|
26
|
+
lockedAssets: 0n
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Zero valuation object for guest mode.
|
|
31
|
+
* Returns safe defaults when no wallet is connected.
|
|
32
|
+
*/
|
|
33
|
+
const ZERO_VALUATION = {
|
|
34
|
+
netLiquidationValue0: 0n,
|
|
35
|
+
netLiquidationValue1: 0n,
|
|
36
|
+
maintenanceMargin0: 0n,
|
|
37
|
+
maintenanceMargin1: 0n,
|
|
38
|
+
marginExcess0: 0n,
|
|
39
|
+
marginExcess1: 0n
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Storage schema version for the SDK.
|
|
43
|
+
* Increment when storage format changes (triggers migration or clear).
|
|
44
|
+
*/
|
|
45
|
+
const SCHEMA_VERSION = 1;
|
|
46
|
+
/**
|
|
47
|
+
* Storage key prefix for all SDK data.
|
|
48
|
+
*/
|
|
49
|
+
const STORAGE_PREFIX = "panoptic-v2-sdk";
|
|
50
|
+
/**
|
|
51
|
+
* Maximum number of chunks that can be tracked per pool.
|
|
52
|
+
* Exceeding this limit throws ChunkLimitError.
|
|
53
|
+
*/
|
|
54
|
+
const MAX_TRACKED_CHUNKS = 1e3;
|
|
55
|
+
/**
|
|
56
|
+
* Default reorg depth for chain reorganization handling.
|
|
57
|
+
* On reorg detection, rollback this many blocks and re-sync.
|
|
58
|
+
*/
|
|
59
|
+
const REORG_DEPTH = 128n;
|
|
60
|
+
/**
|
|
61
|
+
* Oracle epoch duration in seconds (64 seconds per epoch).
|
|
62
|
+
*/
|
|
63
|
+
const ORACLE_EPOCH_SECONDS = 64n;
|
|
64
|
+
/**
|
|
65
|
+
* Minimum tick value for Uniswap v3/v4 pools.
|
|
66
|
+
*/
|
|
67
|
+
const MIN_TICK = -887272n;
|
|
68
|
+
/**
|
|
69
|
+
* Maximum tick value for Uniswap v3/v4 pools.
|
|
70
|
+
*/
|
|
71
|
+
const MAX_TICK = 887272n;
|
|
72
|
+
/**
|
|
73
|
+
* Basis points denominator (100% = 10000 bps).
|
|
74
|
+
*/
|
|
75
|
+
const BPS_DENOMINATOR = 10000n;
|
|
76
|
+
/**
|
|
77
|
+
* Utilization denominator (100% = 10000).
|
|
78
|
+
*/
|
|
79
|
+
const UTILIZATION_DENOMINATOR = 10000n;
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/panoptic/v2/formatters/tick.ts
|
|
83
|
+
const Q192$1 = 1n << 192n;
|
|
84
|
+
const RAW_PRICE_PRECISION = 40n;
|
|
85
|
+
function pow10(exponent) {
|
|
86
|
+
if (exponent < 0n) throw new RangeError("Exponent must be non-negative");
|
|
87
|
+
return 10n ** exponent;
|
|
88
|
+
}
|
|
89
|
+
function absBigint(value) {
|
|
90
|
+
return value < 0n ? -value : value;
|
|
91
|
+
}
|
|
92
|
+
function trimTrailingZeros(value) {
|
|
93
|
+
const dotIndex = value.indexOf(".");
|
|
94
|
+
if (dotIndex === -1) return value;
|
|
95
|
+
let end = value.length;
|
|
96
|
+
while (end > dotIndex && value[end - 1] === "0") end -= 1;
|
|
97
|
+
if (end === dotIndex + 1) end = dotIndex;
|
|
98
|
+
return value.slice(0, end);
|
|
99
|
+
}
|
|
100
|
+
function formatRatio(numerator, denominator, precision) {
|
|
101
|
+
if (precision < 0n) throw new RangeError("Precision must be non-negative");
|
|
102
|
+
const sign = numerator < 0n ? "-" : "";
|
|
103
|
+
const absNumerator = numerator < 0n ? -numerator : numerator;
|
|
104
|
+
const scale = pow10(precision);
|
|
105
|
+
const scaled = (absNumerator * scale + denominator / 2n) / denominator;
|
|
106
|
+
const integerPart = scaled / scale;
|
|
107
|
+
const fractionalPart = scaled % scale;
|
|
108
|
+
if (precision === 0n) return `${sign}${integerPart}`;
|
|
109
|
+
return `${sign}${integerPart}.${fractionalPart.toString().padStart(Number(precision), "0")}`;
|
|
110
|
+
}
|
|
111
|
+
function parseDecimalToFraction(value) {
|
|
112
|
+
const trimmed = value.trim();
|
|
113
|
+
if (trimmed.length === 0) throw new Error("Price must be a number");
|
|
114
|
+
const isNegative = trimmed.startsWith("-");
|
|
115
|
+
const unsigned = isNegative || trimmed.startsWith("+") ? trimmed.slice(1) : trimmed;
|
|
116
|
+
const [basePart, exponentPart] = unsigned.toLowerCase().split("e");
|
|
117
|
+
const [integerStr, fractionalStr = ""] = basePart.split(".");
|
|
118
|
+
if (integerStr === "" && fractionalStr === "") throw new Error("Price must be a number");
|
|
119
|
+
const integerDigits = integerStr === "" ? "0" : integerStr;
|
|
120
|
+
const digits = `${integerDigits}${fractionalStr}`;
|
|
121
|
+
let numerator = BigInt(digits === "" ? "0" : digits);
|
|
122
|
+
let denominator = pow10(BigInt(fractionalStr.length));
|
|
123
|
+
if (exponentPart !== void 0 && exponentPart !== "") {
|
|
124
|
+
const exponent = BigInt(exponentPart);
|
|
125
|
+
if (exponent > 0n) numerator *= pow10(exponent);
|
|
126
|
+
else if (exponent < 0n) denominator *= pow10(-exponent);
|
|
127
|
+
}
|
|
128
|
+
if (isNegative) numerator = -numerator;
|
|
129
|
+
return {
|
|
130
|
+
numerator,
|
|
131
|
+
denominator
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function compareRatios(leftNumerator, leftDenominator, rightNumerator, rightDenominator) {
|
|
135
|
+
const left = leftNumerator * rightDenominator;
|
|
136
|
+
const right = rightNumerator * leftDenominator;
|
|
137
|
+
if (left === right) return 0;
|
|
138
|
+
return left < right ? -1 : 1;
|
|
139
|
+
}
|
|
140
|
+
function tickToSqrtPriceX96(tick) {
|
|
141
|
+
if (tick < MIN_TICK || tick > MAX_TICK) throw new RangeError("Tick out of bounds");
|
|
142
|
+
const absTick = tick < 0n ? -tick : tick;
|
|
143
|
+
let ratio = (absTick & 0x1n) !== 0n ? 0xfffcb933bd6fad37aa2d162d1a594001n : 0x100000000000000000000000000000000n;
|
|
144
|
+
if ((absTick & 0x2n) !== 0n) ratio = ratio * 0xfff97272373d413259a46990580e213an >> 128n;
|
|
145
|
+
if ((absTick & 0x4n) !== 0n) ratio = ratio * 0xfff2e50f5f656932ef12357cf3c7fdccn >> 128n;
|
|
146
|
+
if ((absTick & 0x8n) !== 0n) ratio = ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0n >> 128n;
|
|
147
|
+
if ((absTick & 0x10n) !== 0n) ratio = ratio * 0xffcb9843d60f6159c9db58835c926644n >> 128n;
|
|
148
|
+
if ((absTick & 0x20n) !== 0n) ratio = ratio * 0xff973b41fa98c081472e6896dfb254c0n >> 128n;
|
|
149
|
+
if ((absTick & 0x40n) !== 0n) ratio = ratio * 0xff2ea16466c96a3843ec78b326b52861n >> 128n;
|
|
150
|
+
if ((absTick & 0x80n) !== 0n) ratio = ratio * 0xfe5dee046a99a2a811c461f1969c3053n >> 128n;
|
|
151
|
+
if ((absTick & 0x100n) !== 0n) ratio = ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4n >> 128n;
|
|
152
|
+
if ((absTick & 0x200n) !== 0n) ratio = ratio * 0xf987a7253ac413176f2b074cf7815e54n >> 128n;
|
|
153
|
+
if ((absTick & 0x400n) !== 0n) ratio = ratio * 0xf3392b0822b70005940c7a398e4b70f3n >> 128n;
|
|
154
|
+
if ((absTick & 0x800n) !== 0n) ratio = ratio * 0xe7159475a2c29b7443b29c7fa6e889d9n >> 128n;
|
|
155
|
+
if ((absTick & 0x1000n) !== 0n) ratio = ratio * 0xd097f3bdfd2022b8845ad8f792aa5825n >> 128n;
|
|
156
|
+
if ((absTick & 0x2000n) !== 0n) ratio = ratio * 0xa9f746462d870fdf8a65dc1f90e061e5n >> 128n;
|
|
157
|
+
if ((absTick & 0x4000n) !== 0n) ratio = ratio * 0x70d869a156d2a1b890bb3df62baf32f7n >> 128n;
|
|
158
|
+
if ((absTick & 0x8000n) !== 0n) ratio = ratio * 0x31be135f97d08fd981231505542fcfa6n >> 128n;
|
|
159
|
+
if ((absTick & 0x10000n) !== 0n) ratio = ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9n >> 128n;
|
|
160
|
+
if ((absTick & 0x20000n) !== 0n) ratio = ratio * 0x5d6af8dedb81196699c329225ee604n >> 128n;
|
|
161
|
+
if ((absTick & 0x40000n) !== 0n) ratio = ratio * 0x2216e584f5fa1ea926041bedfe98n >> 128n;
|
|
162
|
+
if ((absTick & 0x80000n) !== 0n) ratio = ratio * 0x48a170391f7dc42444e8fa2n >> 128n;
|
|
163
|
+
if (tick > 0n) ratio = ((1n << 256n) - 1n) / ratio;
|
|
164
|
+
const remainderMask = (1n << 32n) - 1n;
|
|
165
|
+
const sqrtPriceX96 = (ratio >> 32n) + ((ratio & remainderMask) === 0n ? 0n : 1n);
|
|
166
|
+
return sqrtPriceX96;
|
|
167
|
+
}
|
|
168
|
+
const MIN_SQRT_PRICE_X96 = tickToSqrtPriceX96(MIN_TICK);
|
|
169
|
+
const MAX_SQRT_PRICE_X96 = tickToSqrtPriceX96(MAX_TICK);
|
|
170
|
+
function getPriceRatioFromSqrtPriceX96(sqrtPriceX96) {
|
|
171
|
+
return {
|
|
172
|
+
numerator: sqrtPriceX96 * sqrtPriceX96,
|
|
173
|
+
denominator: Q192$1
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function getRawPriceRatio(tick) {
|
|
177
|
+
return getPriceRatioFromSqrtPriceX96(tickToSqrtPriceX96(tick));
|
|
178
|
+
}
|
|
179
|
+
function adjustRatioForDecimals(numerator, denominator, decimals0, decimals1) {
|
|
180
|
+
const diff = decimals0 - decimals1;
|
|
181
|
+
if (diff === 0n) return {
|
|
182
|
+
numerator,
|
|
183
|
+
denominator
|
|
184
|
+
};
|
|
185
|
+
if (diff > 0n) return {
|
|
186
|
+
numerator: numerator * pow10(diff),
|
|
187
|
+
denominator
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
numerator,
|
|
191
|
+
denominator: denominator * pow10(-diff)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Convert a tick to a raw price string (no decimal adjustment).
|
|
196
|
+
* Uses the formula: price = 1.0001^tick
|
|
197
|
+
*
|
|
198
|
+
* This returns the raw price ratio, not adjusted for token decimals.
|
|
199
|
+
* A fixed internal precision is used and trailing zeros are trimmed.
|
|
200
|
+
*
|
|
201
|
+
* @param tick - The tick value
|
|
202
|
+
* @returns Price string
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* tickToPrice(0n) // "1"
|
|
207
|
+
* tickToPrice(1000n) // "1.105..." (approximately)
|
|
208
|
+
* tickToPrice(-1000n) // "0.904..." (approximately)
|
|
209
|
+
* tickToPrice(200000n) // Very large number
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
function tickToPrice(tick) {
|
|
213
|
+
const { numerator, denominator } = getRawPriceRatio(tick);
|
|
214
|
+
const price = formatRatio(numerator, denominator, RAW_PRICE_PRECISION);
|
|
215
|
+
return trimTrailingZeros(price);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Convert a tick to a human-readable price with decimal scaling.
|
|
219
|
+
* Uses the formula: price = 1.0001^tick * 10^(decimals0-decimals1)
|
|
220
|
+
*
|
|
221
|
+
* This adjusts for the different decimals of the two tokens in the pair.
|
|
222
|
+
*
|
|
223
|
+
* @param tick - The tick value
|
|
224
|
+
* @param decimals0 - Decimals of token0
|
|
225
|
+
* @param decimals1 - Decimals of token1
|
|
226
|
+
* @param precision - Number of decimal places to display
|
|
227
|
+
* @returns Formatted price string
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* // WETH/USDC pool (18 decimals / 6 decimals)
|
|
232
|
+
* // At tick ~200000, price is roughly $2000 per ETH
|
|
233
|
+
* tickToPriceDecimalScaled(200000n, 18n, 6n, 2n) // "2000.00" (approximately)
|
|
234
|
+
*
|
|
235
|
+
* // For token1/token0 price, swap the decimals
|
|
236
|
+
* tickToPriceDecimalScaled(200000n, 6n, 18n, 6n) // "0.000500" (approximately)
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
function tickToPriceDecimalScaled(tick, decimals0, decimals1, precision) {
|
|
240
|
+
const rawRatio = getRawPriceRatio(tick);
|
|
241
|
+
const { numerator, denominator } = adjustRatioForDecimals(rawRatio.numerator, rawRatio.denominator, decimals0, decimals1);
|
|
242
|
+
return formatRatio(numerator, denominator, precision);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Convert a sqrtPriceX96 to a human-readable price with decimal scaling.
|
|
246
|
+
*
|
|
247
|
+
* Uses the formula: price = (sqrtPriceX96^2 / 2^192) * 10^(decimals0-decimals1)
|
|
248
|
+
*
|
|
249
|
+
* @param sqrtPriceX96 - The sqrt price in Q64.96 format
|
|
250
|
+
* @param decimals0 - Decimals of token0
|
|
251
|
+
* @param decimals1 - Decimals of token1
|
|
252
|
+
* @param precision - Number of decimal places to display
|
|
253
|
+
* @returns Formatted price string
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* sqrtPriceX96ToPriceDecimalScaled(2n ** 96n, 18n, 18n, 2n) // "1.00"
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
function sqrtPriceX96ToPriceDecimalScaled(sqrtPriceX96, decimals0, decimals1, precision) {
|
|
261
|
+
const rawRatio = getPriceRatioFromSqrtPriceX96(sqrtPriceX96);
|
|
262
|
+
const { numerator, denominator } = adjustRatioForDecimals(rawRatio.numerator, rawRatio.denominator, decimals0, decimals1);
|
|
263
|
+
return formatRatio(numerator, denominator, precision);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Convert a price to a tick value.
|
|
267
|
+
*
|
|
268
|
+
* @param price - The price string
|
|
269
|
+
* @param decimals0 - Decimals of token0
|
|
270
|
+
* @param decimals1 - Decimals of token1
|
|
271
|
+
* @returns The tick value (rounded to nearest integer)
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* // WETH/USDC: What tick for $2000 per ETH?
|
|
276
|
+
* priceToTick("2000", 18n, 6n) // ~200000n
|
|
277
|
+
*
|
|
278
|
+
* // Inverse: What tick for 0.0005 ETH per USDC?
|
|
279
|
+
* priceToTick("0.0005", 6n, 18n) // ~200000n
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
function priceToTick(price, decimals0, decimals1) {
|
|
283
|
+
const parsed = parseDecimalToFraction(price);
|
|
284
|
+
if (parsed.numerator <= 0n) throw new Error("Price must be positive");
|
|
285
|
+
let targetNumerator = parsed.numerator;
|
|
286
|
+
let targetDenominator = parsed.denominator;
|
|
287
|
+
const diff = decimals0 - decimals1;
|
|
288
|
+
if (diff > 0n) targetDenominator *= pow10(diff);
|
|
289
|
+
else if (diff < 0n) targetNumerator *= pow10(-diff);
|
|
290
|
+
let low = MIN_TICK;
|
|
291
|
+
let high = MAX_TICK;
|
|
292
|
+
while (low <= high) {
|
|
293
|
+
const mid = (low + high) / 2n;
|
|
294
|
+
const { numerator, denominator } = getRawPriceRatio(mid);
|
|
295
|
+
const cmp = compareRatios(numerator, denominator, targetNumerator, targetDenominator);
|
|
296
|
+
if (cmp === 0) return mid;
|
|
297
|
+
if (cmp < 0) low = mid + 1n;
|
|
298
|
+
else high = mid - 1n;
|
|
299
|
+
}
|
|
300
|
+
const floorTick = high;
|
|
301
|
+
const ceilTick = low;
|
|
302
|
+
if (floorTick < MIN_TICK) return MIN_TICK;
|
|
303
|
+
if (ceilTick > MAX_TICK) return MAX_TICK;
|
|
304
|
+
const floorRatio = getRawPriceRatio(floorTick);
|
|
305
|
+
const ceilRatio = getRawPriceRatio(ceilTick);
|
|
306
|
+
const floorDiffNumerator = absBigint(targetNumerator * floorRatio.denominator - floorRatio.numerator * targetDenominator);
|
|
307
|
+
const ceilDiffNumerator = absBigint(targetNumerator * ceilRatio.denominator - ceilRatio.numerator * targetDenominator);
|
|
308
|
+
const floorDiffDenominator = targetDenominator * floorRatio.denominator;
|
|
309
|
+
const ceilDiffDenominator = targetDenominator * ceilRatio.denominator;
|
|
310
|
+
return floorDiffNumerator * ceilDiffDenominator <= ceilDiffNumerator * floorDiffDenominator ? floorTick : ceilTick;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Convert a sqrtPriceX96 value to the nearest tick.
|
|
314
|
+
*
|
|
315
|
+
* @param sqrtPriceX96 - The sqrt price in Q64.96 format
|
|
316
|
+
* @returns The tick value (rounded to nearest integer)
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* const tick = sqrtPriceX96ToTick(2n ** 96n) // 0n
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
function sqrtPriceX96ToTick(sqrtPriceX96) {
|
|
324
|
+
if (sqrtPriceX96 <= 0n) throw new Error("Sqrt price must be positive");
|
|
325
|
+
if (sqrtPriceX96 < MIN_SQRT_PRICE_X96 || sqrtPriceX96 > MAX_SQRT_PRICE_X96) throw new RangeError("Sqrt price out of bounds");
|
|
326
|
+
let low = MIN_TICK;
|
|
327
|
+
let high = MAX_TICK;
|
|
328
|
+
while (low <= high) {
|
|
329
|
+
const mid = (low + high) / 2n;
|
|
330
|
+
const midSqrt = tickToSqrtPriceX96(mid);
|
|
331
|
+
if (midSqrt === sqrtPriceX96) return mid;
|
|
332
|
+
if (midSqrt < sqrtPriceX96) low = mid + 1n;
|
|
333
|
+
else high = mid - 1n;
|
|
334
|
+
}
|
|
335
|
+
const floorTick = high;
|
|
336
|
+
const ceilTick = low;
|
|
337
|
+
if (floorTick < MIN_TICK) return MIN_TICK;
|
|
338
|
+
if (ceilTick > MAX_TICK) return MAX_TICK;
|
|
339
|
+
const floorSqrt = tickToSqrtPriceX96(floorTick);
|
|
340
|
+
const ceilSqrt = tickToSqrtPriceX96(ceilTick);
|
|
341
|
+
const floorDiff = absBigint(sqrtPriceX96 - floorSqrt);
|
|
342
|
+
const ceilDiff = absBigint(ceilSqrt - sqrtPriceX96);
|
|
343
|
+
return floorDiff <= ceilDiff ? floorTick : ceilTick;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Format a tick value for display.
|
|
347
|
+
*
|
|
348
|
+
* @param tick - The tick value
|
|
349
|
+
* @returns Formatted tick string
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* formatTick(200000n) // "200000"
|
|
354
|
+
* formatTick(-50000n) // "-50000"
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
function formatTick(tick) {
|
|
358
|
+
return tick.toString();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get the price at a specific tick, returning both token0/token1 and token1/token0 prices.
|
|
362
|
+
*
|
|
363
|
+
* @param tick - The tick value
|
|
364
|
+
* @param decimals0 - Decimals of token0
|
|
365
|
+
* @param decimals1 - Decimals of token1
|
|
366
|
+
* @param precision - Number of decimal places to display
|
|
367
|
+
* @returns Object with both price directions
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* const prices = getPricesAtTick(200000n, 18n, 6n, 2n)
|
|
372
|
+
* // prices.token0PerToken1 = "0.00" (very small)
|
|
373
|
+
* // prices.token1PerToken0 = "2000.00" (USDC per ETH)
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
function getPricesAtTick(tick, decimals0, decimals1, precision) {
|
|
377
|
+
const rawRatio = getRawPriceRatio(tick);
|
|
378
|
+
const adjustedRatio = adjustRatioForDecimals(rawRatio.numerator, rawRatio.denominator, decimals0, decimals1);
|
|
379
|
+
return {
|
|
380
|
+
token0PerToken1: formatRatio(adjustedRatio.denominator, adjustedRatio.numerator, precision),
|
|
381
|
+
token1PerToken0: formatRatio(adjustedRatio.numerator, adjustedRatio.denominator, precision)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Format a tick range for display.
|
|
386
|
+
*
|
|
387
|
+
* @param tickLower - Lower tick
|
|
388
|
+
* @param tickUpper - Upper tick
|
|
389
|
+
* @returns Formatted tick range string
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* formatTickRange(-50000n, 200000n) // "-50000 - 200000"
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
function formatTickRange(tickLower, tickUpper) {
|
|
397
|
+
return `${tickLower} - ${tickUpper}`;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Format a price range for display.
|
|
401
|
+
*
|
|
402
|
+
* @param tickLower - Lower tick
|
|
403
|
+
* @param tickUpper - Upper tick
|
|
404
|
+
* @param decimals0 - Decimals of token0
|
|
405
|
+
* @param decimals1 - Decimals of token1
|
|
406
|
+
* @param precision - Number of decimal places to display
|
|
407
|
+
* @returns Formatted price range string
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```typescript
|
|
411
|
+
* formatPriceRange(0n, 0n, 18n, 18n, 2n) // "1.00 - 1.00"
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
function formatPriceRange(tickLower, tickUpper, decimals0, decimals1, precision) {
|
|
415
|
+
const lower = tickToPriceDecimalScaled(tickLower, decimals0, decimals1, precision);
|
|
416
|
+
const upper = tickToPriceDecimalScaled(tickUpper, decimals0, decimals1, precision);
|
|
417
|
+
return `${lower} - ${upper}`;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Calculate the tick spacing for a given fee tier.
|
|
421
|
+
*
|
|
422
|
+
* @param feeBps - Fee in basis points (e.g., 500n for 0.05%)
|
|
423
|
+
* @returns Tick spacing
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```typescript
|
|
427
|
+
* getTickSpacing(100n) // 1n (0.01% fee tier)
|
|
428
|
+
* getTickSpacing(500n) // 10n (0.05% fee tier)
|
|
429
|
+
* getTickSpacing(3000n) // 60n (0.30% fee tier)
|
|
430
|
+
* getTickSpacing(10000n) // 200n (1.00% fee tier)
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
function getTickSpacing(feeBps) {
|
|
434
|
+
switch (feeBps) {
|
|
435
|
+
case 100n: return 1n;
|
|
436
|
+
case 500n: return 10n;
|
|
437
|
+
case 3000n: return 60n;
|
|
438
|
+
case 10000n: return 200n;
|
|
439
|
+
default: {
|
|
440
|
+
const spacing = feeBps / 50n;
|
|
441
|
+
return spacing > 1n ? spacing : 1n;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Round a tick to the nearest valid tick for a given tick spacing.
|
|
447
|
+
*
|
|
448
|
+
* @param tick - The tick to round
|
|
449
|
+
* @param tickSpacing - The tick spacing
|
|
450
|
+
* @returns Rounded tick
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* roundToTickSpacing(12345n, 10n) // 12340n
|
|
455
|
+
* roundToTickSpacing(12345n, 60n) // 12360n
|
|
456
|
+
* roundToTickSpacing(-12345n, 10n) // -12350n
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
function roundToTickSpacing(tick, tickSpacing) {
|
|
460
|
+
const remainder = tick % tickSpacing;
|
|
461
|
+
if (remainder === 0n) return tick;
|
|
462
|
+
if (tick >= 0n) return remainder >= tickSpacing / 2n ? tick + (tickSpacing - remainder) : tick - remainder;
|
|
463
|
+
const absRemainder = -remainder;
|
|
464
|
+
return absRemainder >= tickSpacing / 2n ? tick - (tickSpacing - absRemainder) : tick + absRemainder;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Compute slippage-bounded tick limits around the current tick.
|
|
468
|
+
*
|
|
469
|
+
* 1 tick ≈ 1 basis point (0.01 %) of price change, so a `toleranceBps`
|
|
470
|
+
* of 500 allows roughly 5 % price movement. The result is clamped to
|
|
471
|
+
* the protocol's `[MIN_TICK, MAX_TICK]` range.
|
|
472
|
+
*
|
|
473
|
+
* Useful for setting `tickLimitLow` / `tickLimitHigh` on `openPosition`
|
|
474
|
+
* and `closePosition` to protect against MEV sandwiches and volatile
|
|
475
|
+
* tick moves.
|
|
476
|
+
*
|
|
477
|
+
* @param currentTick - The current pool tick (must be within [MIN_TICK, MAX_TICK]).
|
|
478
|
+
* @param toleranceBps - Slippage tolerance in basis points (≈ ticks). Must be non-negative.
|
|
479
|
+
* @returns Clamped `{ low, high }` tick limits.
|
|
480
|
+
* @throws {RangeError} If `toleranceBps` is negative or `currentTick` is out of bounds.
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* const { low, high } = tickLimits(200_000n, 500n)
|
|
485
|
+
* // low = 199_500n
|
|
486
|
+
* // high = 200_500n
|
|
487
|
+
*
|
|
488
|
+
* await openPosition({ ..., tickLimitLow: low, tickLimitHigh: high })
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
function tickLimits(currentTick, toleranceBps) {
|
|
492
|
+
if (toleranceBps < 0n) throw new RangeError(`toleranceBps must be non-negative, got ${toleranceBps}`);
|
|
493
|
+
if (currentTick < MIN_TICK || currentTick > MAX_TICK) throw new RangeError(`currentTick ${currentTick} is out of bounds [${MIN_TICK}, ${MAX_TICK}]`);
|
|
494
|
+
const rawLow = currentTick - toleranceBps;
|
|
495
|
+
const rawHigh = currentTick + toleranceBps;
|
|
496
|
+
const low = rawLow < MIN_TICK ? MIN_TICK : rawLow;
|
|
497
|
+
const high = rawHigh > MAX_TICK ? MAX_TICK : rawHigh;
|
|
498
|
+
if (low > high) throw new RangeError(`Computed tick limits are inverted: low ${low} > high ${high} (currentTick=${currentTick}, toleranceBps=${toleranceBps})`);
|
|
499
|
+
return {
|
|
500
|
+
low,
|
|
501
|
+
high
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/panoptic/v2/greeks/index.ts
|
|
507
|
+
/** Fixed-point scale constants for sqrtPriceX96 arithmetic */
|
|
508
|
+
const Q96 = 1n << 96n;
|
|
509
|
+
const Q192 = 1n << 192n;
|
|
510
|
+
/**
|
|
511
|
+
* Convert tick to quote-denominated tick based on asset direction.
|
|
512
|
+
*
|
|
513
|
+
* When isAssetToken0 = true (asset is token0, numeraire is token1):
|
|
514
|
+
* - Tick already encodes price as token1/token0 (numeraire per asset)
|
|
515
|
+
* - Return tick unchanged
|
|
516
|
+
*
|
|
517
|
+
* When isAssetToken0 = false (asset is token1, numeraire is token0):
|
|
518
|
+
* - Tick encodes price as token1/token0, but we need token0/token1 (numeraire per asset)
|
|
519
|
+
* - Invert by negating: 1/price = 1.0001^(-tick)
|
|
520
|
+
*/
|
|
521
|
+
function quoteTick(tick, isAssetToken0) {
|
|
522
|
+
return isAssetToken0 ? tick : -tick;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Divide with truncation toward zero (matches Solidity division behavior).
|
|
526
|
+
*
|
|
527
|
+
* JavaScript bigint division uses floor (toward negative infinity), but Solidity
|
|
528
|
+
* truncates toward zero. For on-chain fidelity, we must match Solidity.
|
|
529
|
+
*
|
|
530
|
+
* Example:
|
|
531
|
+
* - Solidity: -7 / 2 = -3 (truncate toward zero)
|
|
532
|
+
* - JS bigint: -7n / 2n = -4n (floor toward -∞)
|
|
533
|
+
* - This function: divTrunc(-7n, 2n) = -3n ✓
|
|
534
|
+
*/
|
|
535
|
+
function divTrunc(numerator, denominator) {
|
|
536
|
+
if (denominator === 0n) return 0n;
|
|
537
|
+
const quotient = numerator / denominator;
|
|
538
|
+
const remainder = numerator % denominator;
|
|
539
|
+
if (numerator < 0n !== denominator < 0n && remainder !== 0n) return quotient + 1n;
|
|
540
|
+
return quotient;
|
|
541
|
+
}
|
|
542
|
+
/** Resolve isAssetToken0: optional assetIndex overrides leg.asset */
|
|
543
|
+
function resolveAssetDirection(leg, assetIndex) {
|
|
544
|
+
return assetIndex !== void 0 ? assetIndex === 0n : leg.asset === 0n;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Compute the tokenType-denominated notional of a width=0 (loan/credit) leg.
|
|
548
|
+
*
|
|
549
|
+
* When leg.asset !== leg.tokenType, positionSize is in leg.asset units and
|
|
550
|
+
* the borrowed notional is encoded via leg.strike: notional_tokenType_raw =
|
|
551
|
+
* positionSize_raw × 1.0001^strike (a raw-to-raw ratio, unquoted by pool direction).
|
|
552
|
+
* When leg.asset === leg.tokenType, m is already the notional (old convention).
|
|
553
|
+
*
|
|
554
|
+
* `m > 0` for loans (isLong=false), `m < 0` for credits (isLong=true), so the
|
|
555
|
+
* returned notional is signed the same way.
|
|
556
|
+
*/
|
|
557
|
+
function computeWidth0Notional(leg, m) {
|
|
558
|
+
const scaleByStrike = leg.asset !== leg.tokenType;
|
|
559
|
+
if (!scaleByStrike) return m;
|
|
560
|
+
const signedStrike = leg.asset === 0n ? leg.strike : -leg.strike;
|
|
561
|
+
const sqrtKraw = tickToSqrtPriceX96(signedStrike);
|
|
562
|
+
const KrawX192 = sqrtKraw * sqrtKraw;
|
|
563
|
+
return divTrunc(m * KrawX192, Q192);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Calculate value for a width=0 (loan/credit) leg.
|
|
567
|
+
* Width=0 means the range is a single tick (the strike), so there's no meaningful
|
|
568
|
+
* "in range" — we use the below/above formulas which avoid division by (r-1)=0.
|
|
569
|
+
*
|
|
570
|
+
* This is the DEBT-ONLY value (the borrowed/lent obligation), used by delta/greeks
|
|
571
|
+
* aggregation where the held-collateral side lives in a separate wallet/CT term. For
|
|
572
|
+
* a standalone payoff chart that must reflect the net user-experienced payoff (which
|
|
573
|
+
* depends on Zap vs Cover-at-mint), use `getLegNetValueWidth0`.
|
|
574
|
+
*/
|
|
575
|
+
function getLegValueWidth0(leg, m, qCurrentTick, qStrikeTick, qMintTick, isAssetToken0, _definedRisk) {
|
|
576
|
+
const borrowsAsset = isCall(leg.tokenType, isAssetToken0);
|
|
577
|
+
const notional = computeWidth0Notional(leg, m);
|
|
578
|
+
if (borrowsAsset) {
|
|
579
|
+
const sqrtP = tickToSqrtPriceX96(qCurrentTick);
|
|
580
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
581
|
+
const PX192 = sqrtP * sqrtP;
|
|
582
|
+
const PmX192 = sqrtPm * sqrtPm;
|
|
583
|
+
return divTrunc(-notional * (PX192 - PmX192), Q192);
|
|
584
|
+
} else return 0n;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Calculate the NET (user-experienced) payoff value for a width=0 (loan/credit) leg.
|
|
588
|
+
*
|
|
589
|
+
* Unlike `getLegValueWidth0` (debt-only), this includes the collateral/holding side and
|
|
590
|
+
* therefore depends on how the position was opened:
|
|
591
|
+
*
|
|
592
|
+
* - **Cover at mint** (`swapAtMint = false`): the collateral is sourced in the SAME token as
|
|
593
|
+
* the debt/credit, so the holding side exactly offsets it → net PnL is FLAT (0 everywhere,
|
|
594
|
+
* mint-relative), regardless of which token the leg is denominated in.
|
|
595
|
+
* - **Zap** (`swapAtMint = true`): the collateral is sourced in the OTHER token, leaving a
|
|
596
|
+
* ±1-delta line in the asset (ETH), anchored to 0 at the mint price:
|
|
597
|
+
* - USDC loan → zap to ETH: +1 (long ETH)
|
|
598
|
+
* - ETH loan → zap to USDC: −1 (short ETH)
|
|
599
|
+
* - USDC credit ← zap from ETH: −1 (short ETH)
|
|
600
|
+
* - ETH credit ← zap from USDC: +1 (long ETH)
|
|
601
|
+
*
|
|
602
|
+
* @returns Net leg value in numeraire token smallest units (mint-relative PnL).
|
|
603
|
+
*/
|
|
604
|
+
function getLegNetValueWidth0(leg, m, qCurrentTick, qMintTick, isAssetToken0, swapAtMint, itmOffsetNotional = 0n) {
|
|
605
|
+
if (!swapAtMint) return 0n;
|
|
606
|
+
const borrowsAsset = isCall(leg.tokenType, isAssetToken0);
|
|
607
|
+
const notional = computeWidth0Notional(leg, m) + itmOffsetNotional;
|
|
608
|
+
const sqrtP = tickToSqrtPriceX96(qCurrentTick);
|
|
609
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
610
|
+
const PX192 = sqrtP * sqrtP;
|
|
611
|
+
const PmX192 = sqrtPm * sqrtPm;
|
|
612
|
+
if (borrowsAsset) return divTrunc(-notional * (PX192 - PmX192), Q192);
|
|
613
|
+
return divTrunc(notional * PX192, PmX192) - notional;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Compute the mint-time ITM (in-the-money) adjustment for an option leg.
|
|
617
|
+
*
|
|
618
|
+
* This is the `itm` baseline used by {@link getLegValue}: the amount by which the
|
|
619
|
+
* position was already ITM at mint, expressed in the leg's natural units:
|
|
620
|
+
* - Puts: numeraire units (added directly to the put's `debt*K + v` value).
|
|
621
|
+
* - Calls: asset-ratio units (the call value multiplies it by price: `itm*P`/`itm*Pm`).
|
|
622
|
+
*
|
|
623
|
+
* Extracted so callers (e.g. {@link calculatePositionValue}) can build a per-side ITM
|
|
624
|
+
* notional pool to net width=0 credit/loan legs against — an ITM-neutralizing credit
|
|
625
|
+
* was sized to offset exactly this amount, so it should not add a spurious swap line.
|
|
626
|
+
*/
|
|
627
|
+
function computeOptionItm(m, qStrikeTick, qMintTick, halfWidthTick, isPut) {
|
|
628
|
+
if (isPut) {
|
|
629
|
+
if (qMintTick < qStrikeTick - halfWidthTick) {
|
|
630
|
+
const sqrtK$1 = tickToSqrtPriceX96(qStrikeTick);
|
|
631
|
+
const sqrtPm$2 = tickToSqrtPriceX96(qMintTick);
|
|
632
|
+
const KX192 = sqrtK$1 * sqrtK$1;
|
|
633
|
+
const PmX192 = sqrtPm$2 * sqrtPm$2;
|
|
634
|
+
return divTrunc(m * (KX192 - PmX192), Q192);
|
|
635
|
+
} else if (qMintTick > qStrikeTick + halfWidthTick) return 0n;
|
|
636
|
+
const sqrtKR = tickToSqrtPriceX96(qStrikeTick + halfWidthTick);
|
|
637
|
+
const sqrtPm$1 = tickToSqrtPriceX96(qMintTick);
|
|
638
|
+
const sqrtR$1 = tickToSqrtPriceX96(halfWidthTick);
|
|
639
|
+
const rX192$1 = sqrtR$1 * sqrtR$1;
|
|
640
|
+
const diff$1 = sqrtKR - sqrtPm$1;
|
|
641
|
+
const diffSqX192$1 = diff$1 * diff$1;
|
|
642
|
+
return divTrunc(m * diffSqX192$1, rX192$1 - Q192);
|
|
643
|
+
}
|
|
644
|
+
if (qMintTick < qStrikeTick - halfWidthTick) return 0n;
|
|
645
|
+
else if (qMintTick > qStrikeTick + halfWidthTick) {
|
|
646
|
+
const sqrtK$1 = tickToSqrtPriceX96(qStrikeTick);
|
|
647
|
+
const sqrtPm$1 = tickToSqrtPriceX96(qMintTick);
|
|
648
|
+
const KX192 = sqrtK$1 * sqrtK$1;
|
|
649
|
+
const PmX192 = sqrtPm$1 * sqrtPm$1;
|
|
650
|
+
return divTrunc(m * (PmX192 - KX192), PmX192);
|
|
651
|
+
}
|
|
652
|
+
const sqrtR = tickToSqrtPriceX96(halfWidthTick);
|
|
653
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
654
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
655
|
+
const rX192 = sqrtR * sqrtR;
|
|
656
|
+
const sqrtKPmX96 = divTrunc(sqrtK * Q96, sqrtPm);
|
|
657
|
+
const diff = sqrtR - sqrtKPmX96;
|
|
658
|
+
const diffSqX192 = diff * diff;
|
|
659
|
+
return divTrunc(m * diffSqX192, rX192 - Q192);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Check if leg is a call option (vs put).
|
|
663
|
+
*
|
|
664
|
+
* A call is when the leg moves the asset token:
|
|
665
|
+
* - If asset is token0: call when tokenType=0
|
|
666
|
+
* - If asset is token1: call when tokenType=1
|
|
667
|
+
*/
|
|
668
|
+
function isCall(tokenType, isAssetToken0) {
|
|
669
|
+
return isAssetToken0 ? tokenType === 0n : tokenType === 1n;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Check if position has defined risk (is a spread).
|
|
673
|
+
*
|
|
674
|
+
* A position is defined risk if it has 2+ legs of the same tokenType
|
|
675
|
+
* with both long and short exposure.
|
|
676
|
+
*/
|
|
677
|
+
function isDefinedRisk(legs) {
|
|
678
|
+
if (legs.length < 2) return false;
|
|
679
|
+
for (const tt of [0n, 1n]) {
|
|
680
|
+
const group = legs.filter((l) => l.tokenType === tt);
|
|
681
|
+
if (group.length >= 2 && group.some((l) => l.isLong) && group.some((l) => !l.isLong)) return true;
|
|
682
|
+
}
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Calculate the value of a single leg.
|
|
687
|
+
*
|
|
688
|
+
* Value represents the current P&L of the position in numeraire token units.
|
|
689
|
+
* Combines base value (from Panoptic's piecewise formula), debt, and ITM adjustment.
|
|
690
|
+
*
|
|
691
|
+
* Uses sqrtPriceX96 for all calculations to maintain precision and on-chain fidelity.
|
|
692
|
+
*
|
|
693
|
+
* @param leg - The leg to calculate
|
|
694
|
+
* @param currentTick - Current pool tick
|
|
695
|
+
* @param mintTick - Tick at position mint
|
|
696
|
+
* @param positionSize - Position size in asset token smallest units
|
|
697
|
+
* @param poolTickSpacing - Pool tick spacing
|
|
698
|
+
* @param definedRisk - Whether position is defined risk
|
|
699
|
+
* @param assetIndex - Optional override for leg.asset (0n = token0 is asset, 1n = token1)
|
|
700
|
+
* @returns Leg value in numeraire token smallest units
|
|
701
|
+
*/
|
|
702
|
+
function getLegValue(leg, currentTick, mintTick, positionSize, poolTickSpacing, definedRisk, assetIndex, swapAtMint) {
|
|
703
|
+
const isAssetToken0 = resolveAssetDirection(leg, assetIndex);
|
|
704
|
+
const qCurrentTick = quoteTick(currentTick, isAssetToken0);
|
|
705
|
+
const qMintTick = quoteTick(mintTick, isAssetToken0);
|
|
706
|
+
const qStrikeTick = quoteTick(leg.strike, isAssetToken0);
|
|
707
|
+
const halfWidthTick = leg.width * poolTickSpacing / 2n;
|
|
708
|
+
const m = leg.isLong ? -(positionSize * leg.optionRatio) : positionSize * leg.optionRatio;
|
|
709
|
+
if (leg.width === 0n && swapAtMint !== void 0) return getLegNetValueWidth0(leg, m, qCurrentTick, qMintTick, isAssetToken0, swapAtMint);
|
|
710
|
+
if (halfWidthTick === 0n) return getLegValueWidth0(leg, m, qCurrentTick, qStrikeTick, qMintTick, isAssetToken0, definedRisk);
|
|
711
|
+
let v;
|
|
712
|
+
if (qCurrentTick < qStrikeTick - halfWidthTick) {
|
|
713
|
+
const sqrtP = tickToSqrtPriceX96(qCurrentTick);
|
|
714
|
+
const PX192 = sqrtP * sqrtP;
|
|
715
|
+
v = divTrunc(m * PX192, Q192);
|
|
716
|
+
} else if (qCurrentTick > qStrikeTick + halfWidthTick) {
|
|
717
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
718
|
+
const KX192 = sqrtK * sqrtK;
|
|
719
|
+
v = divTrunc(m * KX192, Q192);
|
|
720
|
+
} else {
|
|
721
|
+
const sqrtP = tickToSqrtPriceX96(qCurrentTick);
|
|
722
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
723
|
+
const sqrtPKR = tickToSqrtPriceX96(qCurrentTick + qStrikeTick + halfWidthTick);
|
|
724
|
+
const sqrtR = tickToSqrtPriceX96(halfWidthTick);
|
|
725
|
+
const PX192 = sqrtP * sqrtP;
|
|
726
|
+
const KX192 = sqrtK * sqrtK;
|
|
727
|
+
const rX192 = sqrtR * sqrtR;
|
|
728
|
+
const numerator = m * (2n * sqrtPKR * Q96 - PX192 - KX192);
|
|
729
|
+
const denominator = rX192 - Q192;
|
|
730
|
+
v = divTrunc(numerator, denominator);
|
|
731
|
+
}
|
|
732
|
+
const debt = -m;
|
|
733
|
+
const isPut = !isCall(leg.tokenType, isAssetToken0);
|
|
734
|
+
const itm = computeOptionItm(m, qStrikeTick, qMintTick, halfWidthTick, isPut);
|
|
735
|
+
if (isPut) {
|
|
736
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
737
|
+
const KX192 = sqrtK * sqrtK;
|
|
738
|
+
const debtK = divTrunc(debt * KX192, Q192);
|
|
739
|
+
return debtK + v + itm;
|
|
740
|
+
} else {
|
|
741
|
+
const sqrtP = tickToSqrtPriceX96(qCurrentTick);
|
|
742
|
+
const PX192 = sqrtP * sqrtP;
|
|
743
|
+
const debtP = divTrunc(debt * PX192, Q192);
|
|
744
|
+
if (definedRisk) {
|
|
745
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
746
|
+
const PmX192 = sqrtPm * sqrtPm;
|
|
747
|
+
const itmPm = divTrunc(itm * PmX192, Q192);
|
|
748
|
+
return debtP + v + itmPm;
|
|
749
|
+
} else {
|
|
750
|
+
const itmP = divTrunc(itm * PX192, Q192);
|
|
751
|
+
return debtP + v + itmP;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Calculate the delta of a single leg.
|
|
757
|
+
*
|
|
758
|
+
* Delta is the rate of change of position value with respect to price.
|
|
759
|
+
* For puts: delta = vDelta
|
|
760
|
+
* For calls: delta = debtDelta + vDelta + itmDelta (if not defined risk)
|
|
761
|
+
*
|
|
762
|
+
* Uses sqrtPriceX96 for all price calculations to maintain precision.
|
|
763
|
+
*
|
|
764
|
+
* @param leg - The leg to calculate
|
|
765
|
+
* @param currentTick - Current pool tick
|
|
766
|
+
* @param positionSize - Position size in asset token smallest units
|
|
767
|
+
* @param poolTickSpacing - Pool tick spacing
|
|
768
|
+
* @param mintTick - Tick at mint (optional, for ITM adjustment)
|
|
769
|
+
* @param definedRisk - Whether position is defined risk
|
|
770
|
+
* @param assetIndex - Optional override for leg.asset (0n = token0 is asset, 1n = token1)
|
|
771
|
+
* @returns Leg delta in asset token smallest units
|
|
772
|
+
*/
|
|
773
|
+
function getLegDelta(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk, assetIndex) {
|
|
774
|
+
const isAssetToken0 = resolveAssetDirection(leg, assetIndex);
|
|
775
|
+
const qCurrentTick = quoteTick(currentTick, isAssetToken0);
|
|
776
|
+
const qStrikeTick = quoteTick(leg.strike, isAssetToken0);
|
|
777
|
+
const halfWidthTick = leg.width * poolTickSpacing / 2n;
|
|
778
|
+
const m = leg.isLong ? -(positionSize * leg.optionRatio) : positionSize * leg.optionRatio;
|
|
779
|
+
if (leg.width === 0n) {
|
|
780
|
+
const borrowsAsset = isAssetToken0 ? leg.tokenType === 0n : leg.tokenType === 1n;
|
|
781
|
+
if (!borrowsAsset) return 0n;
|
|
782
|
+
if (leg.asset === leg.tokenType) return -m;
|
|
783
|
+
const signedStrike = leg.asset === 0n ? leg.strike : -leg.strike;
|
|
784
|
+
const sqrtKraw = tickToSqrtPriceX96(signedStrike);
|
|
785
|
+
const KrawX192 = sqrtKraw * sqrtKraw;
|
|
786
|
+
const notional = divTrunc(m * KrawX192, Q192);
|
|
787
|
+
return -notional;
|
|
788
|
+
}
|
|
789
|
+
if (halfWidthTick === 0n) {
|
|
790
|
+
const vDelta$1 = qCurrentTick <= qStrikeTick ? m : 0n;
|
|
791
|
+
const isPut$1 = !isCall(leg.tokenType, isAssetToken0);
|
|
792
|
+
if (isPut$1) return vDelta$1;
|
|
793
|
+
const debtDelta$1 = -m;
|
|
794
|
+
let itmDelta$1 = 0n;
|
|
795
|
+
if (mintTick !== void 0 && !definedRisk) {
|
|
796
|
+
const qMintTick = quoteTick(mintTick, isAssetToken0);
|
|
797
|
+
if (qMintTick > qStrikeTick) {
|
|
798
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
799
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
800
|
+
const PmX192 = sqrtPm * sqrtPm;
|
|
801
|
+
const KX192 = sqrtK * sqrtK;
|
|
802
|
+
itmDelta$1 = divTrunc((PmX192 - KX192) * m, PmX192);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return definedRisk ? debtDelta$1 + vDelta$1 : debtDelta$1 + vDelta$1 + itmDelta$1;
|
|
806
|
+
}
|
|
807
|
+
let vDelta;
|
|
808
|
+
if (qCurrentTick < qStrikeTick - halfWidthTick) vDelta = m;
|
|
809
|
+
else if (qCurrentTick > qStrikeTick + halfWidthTick) vDelta = 0n;
|
|
810
|
+
else {
|
|
811
|
+
const sqrtP = tickToSqrtPriceX96(qCurrentTick);
|
|
812
|
+
const sqrtKR = tickToSqrtPriceX96(qStrikeTick + halfWidthTick);
|
|
813
|
+
const sqrtR = tickToSqrtPriceX96(halfWidthTick);
|
|
814
|
+
const rX192 = sqrtR * sqrtR;
|
|
815
|
+
const numerator = m * (sqrtKR - sqrtP) * Q192;
|
|
816
|
+
const denominator = sqrtP * (rX192 - Q192);
|
|
817
|
+
vDelta = divTrunc(numerator, denominator);
|
|
818
|
+
}
|
|
819
|
+
const isPut = !isCall(leg.tokenType, isAssetToken0);
|
|
820
|
+
if (isPut) return vDelta;
|
|
821
|
+
const debtDelta = -m;
|
|
822
|
+
const itmDelta = mintTick === void 0 ? 0n : (() => {
|
|
823
|
+
const qMintTick = quoteTick(mintTick, isAssetToken0);
|
|
824
|
+
if (qMintTick < qStrikeTick - halfWidthTick) return 0n;
|
|
825
|
+
else if (qMintTick > qStrikeTick + halfWidthTick) {
|
|
826
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
827
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
828
|
+
const PmX192 = sqrtPm * sqrtPm;
|
|
829
|
+
const KX192 = sqrtK * sqrtK;
|
|
830
|
+
return divTrunc((PmX192 - KX192) * m, PmX192);
|
|
831
|
+
} else {
|
|
832
|
+
const sqrtR = tickToSqrtPriceX96(halfWidthTick);
|
|
833
|
+
const sqrtK = tickToSqrtPriceX96(qStrikeTick);
|
|
834
|
+
const sqrtPm = tickToSqrtPriceX96(qMintTick);
|
|
835
|
+
const rX192 = sqrtR * sqrtR;
|
|
836
|
+
const sqrtKPmX96 = sqrtK * Q96 / sqrtPm;
|
|
837
|
+
const diff = sqrtR - sqrtKPmX96;
|
|
838
|
+
const diffSqX192 = diff * diff;
|
|
839
|
+
return divTrunc(m * diffSqX192, rX192 - Q192);
|
|
840
|
+
}
|
|
841
|
+
})();
|
|
842
|
+
return definedRisk ? debtDelta + vDelta : debtDelta + vDelta + itmDelta;
|
|
843
|
+
}
|
|
844
|
+
/** Convert a delta between token frames at the current pool tick. */
|
|
845
|
+
function toVaultFrameAtTick(delta, fromAsset, vaultAssetIndex, currentTick, flipSignOnAssetInversion = false) {
|
|
846
|
+
if (fromAsset === vaultAssetIndex) return delta;
|
|
847
|
+
const sqrtPriceX96 = tickToSqrtPriceX96(currentTick);
|
|
848
|
+
const converted = vaultAssetIndex === 0n ? delta * Q192 / (sqrtPriceX96 * sqrtPriceX96) : delta * sqrtPriceX96 * sqrtPriceX96 / Q192;
|
|
849
|
+
return flipSignOnAssetInversion ? -converted : converted;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Calculate one leg's wallet-aware delta in the vault asset frame.
|
|
853
|
+
*
|
|
854
|
+
* Width-zero loans and credits must be evaluated directly in the vault frame.
|
|
855
|
+
* Option legs remain in their natural leg frame until converted at the mark tick.
|
|
856
|
+
*/
|
|
857
|
+
function getLegDeltaInVaultFrame(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk, vaultAssetIndex) {
|
|
858
|
+
if (leg.width === 0n) return getLegDelta(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk, vaultAssetIndex);
|
|
859
|
+
const legDelta = getLegDelta(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk);
|
|
860
|
+
return toVaultFrameAtTick(legDelta, leg.asset, vaultAssetIndex, currentTick, true);
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Calculate the gamma (dollar gamma) of a single leg.
|
|
864
|
+
*
|
|
865
|
+
* Formula: gamma = (m * sqrt(K * P * r)) / (2 * (r - 1))
|
|
866
|
+
* where:
|
|
867
|
+
* - m = positionSize * optionRatio (with sign based on long/short)
|
|
868
|
+
* - K = strike price (numeraire/asset)
|
|
869
|
+
* - P = current price (numeraire/asset)
|
|
870
|
+
* - r = 1.0001^(width*tickSpacing/2) ≈ 1 (dimensionless ratio)
|
|
871
|
+
*
|
|
872
|
+
* Uses sqrtPriceX96 arithmetic:
|
|
873
|
+
* - sqrt(K*P*r) = tickToSqrtPriceX96(strikeₜ + currentₜ + widthₜ/2)
|
|
874
|
+
* - Keeps X96/X192 precision until final division
|
|
875
|
+
*
|
|
876
|
+
* @param leg - The leg to calculate
|
|
877
|
+
* @param currentTick - Current pool tick
|
|
878
|
+
* @param positionSize - Position size in asset token smallest units
|
|
879
|
+
* @param poolTickSpacing - Pool tick spacing
|
|
880
|
+
* @param assetIndex - Optional override for leg.asset (0n = token0 is asset, 1n = token1)
|
|
881
|
+
* @returns Leg gamma in numeraire token smallest units
|
|
882
|
+
*/
|
|
883
|
+
function getLegGamma(leg, currentTick, positionSize, poolTickSpacing, assetIndex) {
|
|
884
|
+
const isAssetToken0 = resolveAssetDirection(leg, assetIndex);
|
|
885
|
+
const qCurrentTick = quoteTick(currentTick, isAssetToken0);
|
|
886
|
+
const qStrikeTick = quoteTick(leg.strike, isAssetToken0);
|
|
887
|
+
const halfWidthTick = leg.width * poolTickSpacing / 2n;
|
|
888
|
+
if (leg.width === 0n) return 0n;
|
|
889
|
+
if (halfWidthTick === 0n) return 0n;
|
|
890
|
+
if (qCurrentTick < qStrikeTick - halfWidthTick || qCurrentTick > qStrikeTick + halfWidthTick) return 0n;
|
|
891
|
+
const m = leg.isLong ? positionSize * leg.optionRatio : -(positionSize * leg.optionRatio);
|
|
892
|
+
const sqrtKPR = tickToSqrtPriceX96(qStrikeTick + qCurrentTick + halfWidthTick);
|
|
893
|
+
const sqrtR = tickToSqrtPriceX96(halfWidthTick);
|
|
894
|
+
const rX192 = sqrtR * sqrtR;
|
|
895
|
+
const numerator = m * sqrtKPR * Q96;
|
|
896
|
+
const denominator = 2n * (rX192 - Q192);
|
|
897
|
+
return divTrunc(numerator, denominator);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Calculate total value across all legs.
|
|
901
|
+
*/
|
|
902
|
+
function calculatePositionValue(input) {
|
|
903
|
+
const { legs, currentTick, mintTick, positionSize, poolTickSpacing, assetIndex, swapAtMint } = input;
|
|
904
|
+
const definedRisk = isDefinedRisk(legs);
|
|
905
|
+
if (swapAtMint === void 0) return legs.reduce((sum$1, leg) => sum$1 + getLegValue(leg, currentTick, mintTick, positionSize, poolTickSpacing, definedRisk, assetIndex, swapAtMint), 0n);
|
|
906
|
+
let numeraireItmPool = 0n;
|
|
907
|
+
let assetItmPool = 0n;
|
|
908
|
+
for (const leg of legs) {
|
|
909
|
+
const halfWidthTick = leg.width * poolTickSpacing / 2n;
|
|
910
|
+
if (leg.width === 0n || halfWidthTick === 0n) continue;
|
|
911
|
+
const isAssetToken0 = resolveAssetDirection(leg, assetIndex);
|
|
912
|
+
const qStrikeTick = quoteTick(leg.strike, isAssetToken0);
|
|
913
|
+
const qMintTick = quoteTick(mintTick, isAssetToken0);
|
|
914
|
+
const m = leg.isLong ? -(positionSize * leg.optionRatio) : positionSize * leg.optionRatio;
|
|
915
|
+
const isPut = !isCall(leg.tokenType, isAssetToken0);
|
|
916
|
+
const itm = computeOptionItm(m, qStrikeTick, qMintTick, halfWidthTick, isPut);
|
|
917
|
+
if (isPut) numeraireItmPool += itm;
|
|
918
|
+
else assetItmPool += itm;
|
|
919
|
+
}
|
|
920
|
+
let sum = 0n;
|
|
921
|
+
for (const leg of legs) if (leg.width === 0n) {
|
|
922
|
+
const isAssetToken0 = resolveAssetDirection(leg, assetIndex);
|
|
923
|
+
const borrowsAsset = isCall(leg.tokenType, isAssetToken0);
|
|
924
|
+
const offset = borrowsAsset ? assetItmPool : numeraireItmPool;
|
|
925
|
+
if (borrowsAsset) assetItmPool = 0n;
|
|
926
|
+
else numeraireItmPool = 0n;
|
|
927
|
+
const qCurrentTick = quoteTick(currentTick, isAssetToken0);
|
|
928
|
+
const qMintTick = quoteTick(mintTick, isAssetToken0);
|
|
929
|
+
const m = leg.isLong ? -(positionSize * leg.optionRatio) : positionSize * leg.optionRatio;
|
|
930
|
+
sum += getLegNetValueWidth0(leg, m, qCurrentTick, qMintTick, isAssetToken0, swapAtMint, offset);
|
|
931
|
+
} else sum += getLegValue(leg, currentTick, mintTick, positionSize, poolTickSpacing, definedRisk, assetIndex, swapAtMint);
|
|
932
|
+
return sum;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Calculate total delta across all legs.
|
|
936
|
+
*/
|
|
937
|
+
function calculatePositionDelta(input) {
|
|
938
|
+
const { legs, currentTick, mintTick, positionSize, poolTickSpacing, assetIndex } = input;
|
|
939
|
+
const definedRisk = isDefinedRisk(legs);
|
|
940
|
+
return legs.reduce((sum, leg) => sum + getLegDelta(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk, assetIndex), 0n);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Calculate debt-only position delta in a single target asset frame.
|
|
944
|
+
*
|
|
945
|
+
* Option legs are first valued in their natural `leg.asset` frame, then
|
|
946
|
+
* converted into `assetIndex` using the current pool price. Width-zero legs
|
|
947
|
+
* are evaluated directly in the target frame.
|
|
948
|
+
*
|
|
949
|
+
* Width=0 loan/credit legs include only their debt obligation. The held-token
|
|
950
|
+
* side must be added separately from account collateral balances, preventing a
|
|
951
|
+
* zap from being counted once in the position and again in collateral.
|
|
952
|
+
*/
|
|
953
|
+
function calculatePositionDeltaDebtOnly(input) {
|
|
954
|
+
const { legs, currentTick, mintTick, positionSize, poolTickSpacing, assetIndex } = input;
|
|
955
|
+
const definedRisk = isDefinedRisk(legs);
|
|
956
|
+
return legs.reduce((sum, leg) => sum + getLegDeltaInVaultFrame(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk, assetIndex), 0n);
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Calculate total gamma across all legs.
|
|
960
|
+
*/
|
|
961
|
+
function calculatePositionGamma(input) {
|
|
962
|
+
const { legs, currentTick, positionSize, poolTickSpacing, assetIndex } = input;
|
|
963
|
+
return legs.reduce((sum, leg) => sum + getLegGamma(leg, currentTick, positionSize, poolTickSpacing, assetIndex), 0n);
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Calculate all greeks for a position.
|
|
967
|
+
*/
|
|
968
|
+
function calculatePositionGreeks(input) {
|
|
969
|
+
return {
|
|
970
|
+
value: calculatePositionValue(input),
|
|
971
|
+
delta: calculatePositionDelta(input),
|
|
972
|
+
gamma: calculatePositionGamma(input)
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Aggregate value across multiple independent positions.
|
|
977
|
+
*
|
|
978
|
+
* Each entry is valued with its OWN `positionSize`, `mintTick`, and legs, then
|
|
979
|
+
* summed. Do NOT collapse multiple positions into one synthetic `PositionGreeksInput`
|
|
980
|
+
* with a shared `positionSize` — `m = positionSize * optionRatio` is per-position, so a
|
|
981
|
+
* shared size double-counts (and integer `optionRatio` cannot encode fractional shares).
|
|
982
|
+
*
|
|
983
|
+
* @param positions - One `PositionGreeksInput` per open position
|
|
984
|
+
* @returns Total value in numeraire token smallest units
|
|
985
|
+
*/
|
|
986
|
+
function calculatePortfolioValue(positions) {
|
|
987
|
+
return positions.reduce((sum, input) => sum + calculatePositionValue(input), 0n);
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Aggregate delta across multiple independent positions.
|
|
991
|
+
*
|
|
992
|
+
* See {@link calculatePortfolioValue} for why each position must keep its own
|
|
993
|
+
* `positionSize` rather than being merged into one synthetic position.
|
|
994
|
+
*
|
|
995
|
+
* @param positions - One `PositionGreeksInput` per open position
|
|
996
|
+
* @returns Total delta in asset token smallest units
|
|
997
|
+
*/
|
|
998
|
+
function calculatePortfolioDelta(positions) {
|
|
999
|
+
return positions.reduce((sum, input) => sum + calculatePositionDelta(input), 0n);
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Aggregate gamma across multiple independent positions.
|
|
1003
|
+
*
|
|
1004
|
+
* See {@link calculatePortfolioValue} for why each position must keep its own
|
|
1005
|
+
* `positionSize` rather than being merged into one synthetic position.
|
|
1006
|
+
*
|
|
1007
|
+
* @param positions - One `PositionGreeksInput` per open position
|
|
1008
|
+
* @returns Total gamma in numeraire token smallest units
|
|
1009
|
+
*/
|
|
1010
|
+
function calculatePortfolioGamma(positions) {
|
|
1011
|
+
return positions.reduce((sum, input) => sum + calculatePositionGamma(input), 0n);
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Calculate all greeks aggregated across multiple independent positions.
|
|
1015
|
+
*/
|
|
1016
|
+
function calculatePortfolioGreeks(positions) {
|
|
1017
|
+
return {
|
|
1018
|
+
value: calculatePortfolioValue(positions),
|
|
1019
|
+
delta: calculatePortfolioDelta(positions),
|
|
1020
|
+
gamma: calculatePortfolioGamma(positions)
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Calculate the effective delta of a loan leg accounting for swapAtMint.
|
|
1025
|
+
*
|
|
1026
|
+
* A loan borrows one token and (optionally) swaps it for the other at mint.
|
|
1027
|
+
* The net delta depends on whether the swap occurred:
|
|
1028
|
+
*
|
|
1029
|
+
* | Scenario | Result |
|
|
1030
|
+
* |-----------------------|-------------------------------------------------|
|
|
1031
|
+
* | No swap | 0n (hold what you owe, net zero) |
|
|
1032
|
+
* | Swap + borrows asset | -m (hold numeraire, owe asset → short exposure) |
|
|
1033
|
+
* | Swap + borrows numer. | +m (hold asset, owe numeraire → long exposure) |
|
|
1034
|
+
*
|
|
1035
|
+
* Only meaningful for legs with `width === 0n`. For options, use `getLegDelta`.
|
|
1036
|
+
*
|
|
1037
|
+
* @param leg - The loan leg
|
|
1038
|
+
* @param positionSize - Position size in asset token smallest units
|
|
1039
|
+
* @param swapAtMint - Whether the borrowed tokens were swapped at mint
|
|
1040
|
+
* @param assetIndex - Optional override for leg.asset (0n = token0 is asset)
|
|
1041
|
+
* @returns Effective delta in asset token smallest units
|
|
1042
|
+
*/
|
|
1043
|
+
function getLoanEffectiveDelta(leg, positionSize, swapAtMint, assetIndex) {
|
|
1044
|
+
if (!swapAtMint) return 0n;
|
|
1045
|
+
const isAssetToken0 = resolveAssetDirection(leg, assetIndex);
|
|
1046
|
+
const m = leg.isLong ? -(positionSize * leg.optionRatio) : positionSize * leg.optionRatio;
|
|
1047
|
+
const borrowsAsset = isAssetToken0 ? leg.tokenType === 0n : leg.tokenType === 1n;
|
|
1048
|
+
return borrowsAsset ? -m : m;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Calculate total delta for a position, using swap-aware delta for loan legs.
|
|
1052
|
+
*
|
|
1053
|
+
* For legs with `width === 0n` (loans/credits), uses `getLoanEffectiveDelta`
|
|
1054
|
+
* which accounts for the swapAtMint flag. For option legs (`width > 0n`),
|
|
1055
|
+
* uses the standard `getLegDelta`.
|
|
1056
|
+
*
|
|
1057
|
+
* @param input - Position greeks input plus swapAtMint flag
|
|
1058
|
+
* @returns Total delta in asset token smallest units
|
|
1059
|
+
*/
|
|
1060
|
+
function calculatePositionDeltaWithSwap(input) {
|
|
1061
|
+
const { legs, currentTick, mintTick, positionSize, poolTickSpacing, assetIndex, swapAtMint } = input;
|
|
1062
|
+
const optionLegs = legs.filter((l) => l.width !== 0n);
|
|
1063
|
+
const definedRisk = isDefinedRisk(optionLegs);
|
|
1064
|
+
return legs.reduce((sum, leg) => {
|
|
1065
|
+
if (leg.width === 0n) return sum + getLoanEffectiveDelta(leg, positionSize, swapAtMint, assetIndex);
|
|
1066
|
+
return sum + getLegDelta(leg, currentTick, positionSize, poolTickSpacing, mintTick, definedRisk, assetIndex);
|
|
1067
|
+
}, 0n);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
//#endregion
|
|
1071
|
+
export { BPS_DENOMINATOR, MAX_TICK, MAX_TRACKED_CHUNKS, MIN_TICK, ORACLE_EPOCH_SECONDS, REORG_DEPTH, SCHEMA_VERSION, STORAGE_PREFIX, UTILIZATION_DENOMINATOR, WAD, ZERO_COLLATERAL, ZERO_VALUATION, calculatePortfolioDelta, calculatePortfolioGamma, calculatePortfolioGreeks, calculatePortfolioValue, calculatePositionDelta, calculatePositionDeltaDebtOnly, calculatePositionDeltaWithSwap, calculatePositionGamma, calculatePositionGreeks, calculatePositionValue, formatPriceRange, formatTick, formatTickRange, getLegDelta, getLegDeltaInVaultFrame, getLegGamma, getLegNetValueWidth0, getLegValue, getLoanEffectiveDelta, getPricesAtTick, getTickSpacing, isCall, isDefinedRisk, priceToTick, roundToTickSpacing, sqrtPriceX96ToPriceDecimalScaled, sqrtPriceX96ToTick, tickLimits, tickToPrice, tickToPriceDecimalScaled, tickToSqrtPriceX96, toVaultFrameAtTick };
|
|
1072
|
+
//# sourceMappingURL=greeks-GysWXct-.js.map
|