TWAP evidence: reconstruct the averaging rule behind the price
A time-weighted price average is meaningful only after the averaging rule, endpoints and units are fixed. In Uniswap v3, averaging cumulative ticks and then converting the result to price is different from averaging displayed prices directly. Integer rounding and the actual available history can create further differences.
The research task is to reconstruct the measurement behind a reported average. It does not establish an executable spot quote, a manipulation-proof oracle or a future price estimate.
Preserve the pool and requested interval
Record network, exact v3 pool, token0, token1, token decimals, observation block and requested secondsAgo values. A reversed pair changes the interpretation. A later call using latest state is not a repeat of a historical observation merely because it requests the same number of seconds.
The v3 Price Oracles page, Tick Accumulator, explains that the difference in cumulative ticks divided by elapsed time yields an average tick, corresponding to a geometric price average. Use that conceptual distinction while checking the concrete implementation for exact time handling and rounding.
For this worksheet, implementation details are pinned to OracleLibrary commit 24ccf01, consult and core Oracle commit 5ea68a7, observeSingle and transform. Their code, rather than a simplified prose description, determines the calculations described below.
First understand the ideal geometric average
Assume an ideal normalized price is 1 for thirty seconds and 4 for thirty seconds. This illustration treats prices continuously to explain the measurement target; it is not a claim that both prices are represented by exact integer ticks.
The time-weighted geometric average is exp((30 × ln 1 + 30 × ln 4)/60) = 2. The time-weighted arithmetic average is (30 × 1 + 30 × 4)/60 = 2.5. Neither computation is arithmetically wrong; they answer different averaging questions.
Now let price 1 persist for thirty seconds and price 4 for ninety seconds. The geometric result is 4^(90/120), about 2.828427. The arithmetic result is (30 + 360)/120 = 3.25. Counting two observations equally would lose the duration information and return the wrong measurement for this interval.
Keep the duration table with the result. “Average price” without the selected rule is insufficient to reproduce the claim.
Reconstruct an integer-tick example
For equal-decimal tokens and the stated quote orientation, consider hypothetical active ticks 0 and 13863, each lasting thirty seconds. The cumulative-tick increase is 0 × 30 + 13863 × 30 = 415890 tick-seconds. Dividing by sixty gives 6931.5.
| Calculation | Result |
|---|---|
| Elapsed interval | 60 seconds |
| Cumulative-tick difference | 415890 |
| Unrounded mean tick | 6931.5 |
| Integer mean returned by the pinned consult rule | 6931 |
| Price from that integer tick, 1.0001^6931 | About 1.99983634 |
The price at tick 13863 is approximately 3.99974532, not exactly 4. The unrounded geometric result for these two ticks is approximately 1.99993633. Distinguish that tick discretization from the additional rounding to an integer mean tick.
The displayed decimal calculations illustrate the target. A production quote path can involve additional integer arithmetic and amount rounding. Preserve the exact library call and amount if the study compares resulting quote units rather than merely the tick-derived ratio.
Negative means require the documented rounding direction
The pinned consult implementation divides the cumulative difference and adjusts a negative non-integral result downward. It rounds toward negative infinity, not simply toward zero.
Assume a sixty-second interval has cumulative difference −61, realizable in this hypothetical example through tick −1 for fifty-nine seconds and tick −2 for one second. The unrounded mean is −61/60, about −1.016667. The specified integer result is −2; truncating toward zero would instead give −1.
The corresponding ratios are approximately 0.99980003 and 0.99990001. That one-tick discrepancy is a calculation-policy difference, not evidence of an unexplained market move. Save the remainder and sign so a spreadsheet’s default rounding cannot silently change the result.
A cumulative tick value is signed. Do not assume it is universally increasing: negative active ticks can decrease the accumulator. The relevant quantity is the correctly ordered difference, with the implementation’s applicable integer and time conventions.
The observation endpoint can be constructed at the current time
Stored observations and the values returned for a requested endpoint are not always identical. In the pinned core implementation, observeSingle with secondsAgo zero can transform the latest stored observation to the current block timestamp using the active tick. It is not universally limited to returning the last stored timestamp unchanged.
Assume a query at timestamp 10000 requests [60, 0]. The intended start is 9940. Let its cumulative value be 100000, the stored value at 9980 be 104000 and the continuing active tick be 100. Transforming the last twenty seconds adds 100 × 20 = 2000, producing current cumulative value 106000.
The correct difference is 6000 over sixty seconds, giving mean tick 100. Substituting the stale stored value while retaining the sixty-second denominator gives 4000/60, about 66.6667 before rounding. Dividing that stored difference by forty answers another interval, not the requested sixty seconds.
Capacity does not establish retained history
Check the oldest initialized observation and the actual ability to serve the requested endpoints. The pinned core code distinguishes prepared capacity from initialized observations. Increasing a future cardinality setting does not instantly create a longer historical record.
If the requested start predates available history, retain the failure. Do not quietly shorten the window and continue labeling the answer a one-hour average. If a shorter window is separately useful, publish its actual duration and reason as a different observation.
Also separate price history from liquidity conditions. The same average tick can arise from different paths and in-range liquidity histories. A long available window does not alone establish adequate liquidity throughout it or immunity to manipulation.
Inspect initialized status as well as timestamps when reading raw slots. The pinned grow routine prepares slots with a placeholder timestamp while leaving them uninitialized. A very small timestamp in such a slot is not evidence that usable observations extend far into the past.
Endpoint prices cannot reconstruct the intervening path
In another idealized 120-second example, price is 1 for the first thirty seconds, 4 for sixty seconds and 1 for the final thirty seconds. Both endpoint prices are 1. Nevertheless, the geometric average is exp(60 × ln 4 / 120) = 2, and the arithmetic average is 2.5. Substituting the mean of two endpoint prices would incorrectly return 1.
An accumulator contains information about duration between observations that two price screenshots do not. Preserve its provenance rather than claiming to reconstruct the same average from sparse endpoint prices alone. An apparent discrepancy can reflect missing path information without establishing a broken oracle.
For the separate rounding step, if the unrounded mean tick lies between integer k and k+1, the ratio from that exact mean lies between 1.0001^k and 1.0001^(k+1). The multiplicative difference between adjacent tick ratios is 1.0001, or 0.01%. This bounds that isolated tick-rounding step; it does not bound market slippage, source manipulation or every other arithmetic approximation.
Quote units and release checks
The tick ratio relates raw token units. Human-unit interpretation requires token order and decimal conversion. For a token1-per-token0 quote, the human ratio equals the raw ratio multiplied by 10^(decimals0 − decimals1). Equal decimals in the examples deliberately remove that adjustment; applying them to an eighteen-versus-six-decimal pair without conversion would change scale by 10^12.
For an independent unit example, assume a raw ratio of 10^-12, decimals0 of eighteen and decimals1 of six. One whole token0 is 10^18 raw units, corresponding under that assumed ratio to 10^6 raw token1 units, or one whole token1. Omitting the 10^12 scale correction would misreport the human ratio as 10^-12 instead of 1. This conversion example does not assert an exactly representable tick or an executable quote.
| Check | Evidence to retain | Failure treatment |
|---|---|---|
| Pool identity | Network, deployment, token order and decimals | Resolve pair or unit ambiguity |
| Exact endpoints | Block timestamp and secondsAgo array | Do not mix latest calls with historical state |
| Accumulator arithmetic | Both returned cumulatives, difference and duration | Keep signed values and exact denominator |
| Rounding | Library version, quotient and remainder | Explain integer-tick discrepancies |
| Actual history | Oldest initialized record and request result | Mark unavailable windows, never relabel a shorter one |
| Interpretation | Quote orientation, units and liquidity context | Do not call the average executable depth |
Recheck after a corrected endpoint, implementation mismatch, decimal correction or recovered historical observation. Release the reconstructed window, unrounded calculation, implemented integer result and converted ratio together. That makes differences explainable without promoting a measured historical average into a trading recommendation.
FAQ
Is v3’s average tick equivalent to the arithmetic average of prices?
No. Conversion from an average logarithmic tick corresponds to a geometric price average, with implementation rounding layered on top.
Does expanding observation capacity immediately provide older history?
No. Inspect initialized observations and actual endpoint availability. Capacity alone does not create past measurements.