saving in place

This commit is contained in:
Krypto Kajun
2025-10-04 09:31:02 -05:00
parent 76c1b5cee1
commit f358f49aa9
295 changed files with 72071 additions and 17209 deletions

View File

@@ -0,0 +1,126 @@
// Package uniswap provides mathematical functions for Uniswap V3 calculations.
package uniswap
import (
"math"
"math/big"
)
// SqrtPriceX96ToPriceAdvanced converts sqrtPriceX96 to a price using advanced caching.
func SqrtPriceX96ToPriceAdvanced(sqrtPriceX96 *big.Int) *big.Float {
// Initialize global cached constants
initConstants()
// price = (sqrtPriceX96 / 2^96)^2
// price = sqrtPriceX96^2 / 2^192
// Validate input
if sqrtPriceX96 == nil || sqrtPriceX96.Sign() <= 0 {
return new(big.Float).SetFloat64(0.0)
}
// Convert to big.Float for precision
sqrtPrice := new(big.Float).SetInt(sqrtPriceX96)
// Calculate sqrtPrice^2
price := new(big.Float).Mul(sqrtPrice, sqrtPrice)
// Divide by 2^192 using cached constant
price.Quo(price, GetQ192Float())
// Validate result is reasonable (not extremely high or low)
priceFloat, _ := price.Float64()
if math.IsNaN(priceFloat) || math.IsInf(priceFloat, 0) ||
priceFloat > 1e20 || priceFloat < 1e-20 { // Extremely high/low prices are likely errors
return new(big.Float).SetFloat64(1.0) // Default to 1.0 as safe fallback
}
return price
}
// PriceToSqrtPriceX96Advanced converts a price to sqrtPriceX96 using advanced caching.
func PriceToSqrtPriceX96Advanced(price *big.Float) *big.Int {
// Initialize global cached constants
initConstants()
// sqrtPriceX96 = sqrt(price) * 2^96
// Calculate sqrt(price)
sqrtPrice := new(big.Float).Sqrt(price)
// Multiply by 2^96 using cached constant
sqrtPrice.Mul(sqrtPrice, GetQ96Float())
// Validate result
resultFloat, _ := sqrtPrice.Float64()
if math.IsNaN(resultFloat) || math.IsInf(resultFloat, 0) {
return new(big.Int).SetUint64(0) // Return 0 for invalid results
}
// Convert to big.Int
sqrtPriceX96 := new(big.Int)
sqrtPrice.Int(sqrtPriceX96)
return sqrtPriceX96
}
// TickToSqrtPriceX96Advanced converts a tick to sqrtPriceX96 using advanced caching.
func TickToSqrtPriceX96Advanced(tick int) *big.Int {
// Initialize global cached constants
initConstants()
// sqrtPriceX96 = 1.0001^(tick/2) * 2^96
// Using logarithms: 1.0001^(tick/2) = e^(ln(1.0001) * tick/2)
logResult := GetLnBase() * float64(tick) / 2.0
result := math.Exp(logResult)
// Convert to big.Float
price := new(big.Float).SetFloat64(result)
// Multiply by 2^96 using cached constant
price.Mul(price, GetQ96Float())
// Validate result
resultFloat, _ := price.Float64()
if math.IsNaN(resultFloat) || math.IsInf(resultFloat, 0) {
return new(big.Int).SetUint64(0) // Return 0 for invalid results
}
// Convert to big.Int
sqrtPriceX96 := new(big.Int)
price.Int(sqrtPriceX96)
return sqrtPriceX96
}
// SqrtPriceX96ToTickAdvanced converts sqrtPriceX96 to a tick using advanced caching.
func SqrtPriceX96ToTickAdvanced(sqrtPriceX96 *big.Int) int {
// Initialize global cached constants
initConstants()
// tick = log_1.0001(sqrtPriceX96 / 2^96)^2
// tick = 2 * ln(sqrtPriceX96 / 2^96) / ln(1.0001)
if sqrtPriceX96.Cmp(big.NewInt(0)) <= 0 {
return 0 // Invalid input
}
// Convert to big.Float
sqrtPrice := new(big.Float).SetInt(sqrtPriceX96)
q96Float := GetQ96Float()
// Calculate ln(sqrtPriceX96 / 2^96) to avoid potential overflow
ratio := new(big.Float).Quo(sqrtPrice, q96Float)
lnRatio, _ := ratio.Float64()
if lnRatio <= 0 {
return 0 // Invalid ratio
}
lnValue := math.Log(lnRatio)
// Calculate tick: tick = 2 * lnValue / ln(1.0001)
tick := int(2.0 * lnValue * GetInvLnBase())
return tick
}

View File

@@ -0,0 +1,47 @@
package uniswap
import (
"math/big"
"testing"
)
func BenchmarkSqrtPriceX96ToPriceAdvanced(b *testing.B) {
// Create a test sqrtPriceX96 value
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = SqrtPriceX96ToPriceAdvanced(sqrtPriceX96)
}
}
func BenchmarkPriceToSqrtPriceX96Advanced(b *testing.B) {
// Create a test price value
price := new(big.Float).SetFloat64(1.0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = PriceToSqrtPriceX96Advanced(price)
}
}
func BenchmarkTickToSqrtPriceX96Advanced(b *testing.B) {
tick := 100000
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = TickToSqrtPriceX96Advanced(tick)
}
}
func BenchmarkSqrtPriceX96ToTickAdvanced(b *testing.B) {
// Create a test sqrtPriceX96 value
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = SqrtPriceX96ToTickAdvanced(sqrtPriceX96)
}
}

View File

@@ -0,0 +1,82 @@
package uniswap
import (
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSqrtPriceX96ToPriceAdvanced(t *testing.T) {
// Test with sqrtPriceX96 = 2^96 (which should give price = 1.0)
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
expected := 1.0
result := SqrtPriceX96ToPriceAdvanced(sqrtPriceX96)
resultFloat, _ := result.Float64()
assert.InDelta(t, expected, resultFloat, 0.0001, "SqrtPriceX96ToPriceAdvanced should convert correctly for 2^96")
}
func TestPriceToSqrtPriceX96Advanced(t *testing.T) {
// Test with price = 1.0 (which should give sqrtPriceX96 = 2^96)
price := new(big.Float).SetFloat64(1.0)
expectedSqrtPriceX96 := new(big.Int)
expectedSqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
result := PriceToSqrtPriceX96Advanced(price)
// Allow for small differences due to floating point precision
diff := new(big.Int).Sub(expectedSqrtPriceX96, result)
diff.Abs(diff) // Get absolute value of difference
tolerance := big.NewInt(1000000000000) // Allow difference up to 1e12
assert.True(t, diff.Cmp(tolerance) < 0, "PriceToSqrtPriceX96Advanced should convert correctly for price=1.0")
}
func TestTickToSqrtPriceX96Advanced(t *testing.T) {
// Test with tick = 0 (which should give sqrtPriceX96 = 2^96)
expectedSqrtPriceX96 := new(big.Int)
expectedSqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
result := TickToSqrtPriceX96Advanced(0)
// Allow for small differences due to floating point precision
diff := new(big.Int).Sub(expectedSqrtPriceX96, result)
diff.Abs(diff) // Get absolute value of difference
tolerance := big.NewInt(1000000000000) // Allow difference up to 1e12
assert.True(t, diff.Cmp(tolerance) < 0, "TickToSqrtPriceX96Advanced should convert tick 0 correctly")
}
func TestSqrtPriceX96ToTickAdvanced(t *testing.T) {
// Test with sqrtPriceX96 = 2^96 (which should give tick = 0)
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
expected := 0
result := SqrtPriceX96ToTickAdvanced(sqrtPriceX96)
assert.Equal(t, expected, result, "SqrtPriceX96ToTickAdvanced should convert sqrtPriceX96 for price 1.0 correctly")
}
func TestAdvancedRoundTripConversions(t *testing.T) {
// Test sqrtPriceX96 -> price -> sqrtPriceX96 round trip using advanced functions
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96 (price = 1.0)
price := SqrtPriceX96ToPriceAdvanced(sqrtPriceX96)
resultSqrtPriceX96 := PriceToSqrtPriceX96Advanced(price)
// Allow for small differences due to floating point precision
diff := new(big.Int).Sub(sqrtPriceX96, resultSqrtPriceX96)
diff.Abs(diff)
assert.True(t, diff.Cmp(big.NewInt(1000000000000)) < 0, "Advanced round trip conversion should be accurate")
// Test tick -> sqrtPriceX96 -> tick round trip
tick := 100000
sqrtPrice := TickToSqrtPriceX96Advanced(tick)
resultTick := SqrtPriceX96ToTickAdvanced(sqrtPrice)
// Allow for small differences due to floating point precision
assert.InDelta(t, tick, resultTick, 1, "Advanced round trip tick conversion should be accurate")
}

View File

@@ -1,26 +1,9 @@
// Package uniswap provides mathematical functions for Uniswap V3 calculations.
package uniswap
import (
"math/big"
"sync"
)
import "math/big"
var (
// Cached constants to avoid recomputing them
q96 *big.Int
q192 *big.Int
once sync.Once
)
// initConstants initializes the cached constants
func initConstants() {
once.Do(func() {
q96 = new(big.Int).Exp(big.NewInt(2), big.NewInt(96), nil)
q192 = new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil)
})
}
// SqrtPriceX96ToPriceCached converts sqrtPriceX96 to a price using cached constants
// SqrtPriceX96ToPriceCached converts sqrtPriceX96 to a price using cached constants.
func SqrtPriceX96ToPriceCached(sqrtPriceX96 *big.Int) *big.Float {
// Initialize cached constants
initConstants()
@@ -35,13 +18,12 @@ func SqrtPriceX96ToPriceCached(sqrtPriceX96 *big.Int) *big.Float {
price := new(big.Float).Mul(sqrtPrice, sqrtPrice)
// Divide by 2^192 using cached constant
q192Float := new(big.Float).SetInt(q192)
price.Quo(price, q192Float)
price.Quo(price, GetQ192Float())
return price
}
// PriceToSqrtPriceX96Cached converts a price to sqrtPriceX96 using cached constants
// PriceToSqrtPriceX96Cached converts a price to sqrtPriceX96 using cached constants.
func PriceToSqrtPriceX96Cached(price *big.Float) *big.Int {
// Initialize cached constants
initConstants()
@@ -52,8 +34,7 @@ func PriceToSqrtPriceX96Cached(price *big.Float) *big.Int {
sqrtPrice := new(big.Float).Sqrt(price)
// Multiply by 2^96 using cached constant
q96Float := new(big.Float).SetInt(q96)
sqrtPrice.Mul(sqrtPrice, q96Float)
sqrtPrice.Mul(sqrtPrice, GetQ96Float())
// Convert to big.Int
sqrtPriceX96 := new(big.Int)

72
pkg/uniswap/constants.go Normal file
View File

@@ -0,0 +1,72 @@
// Package uniswap provides mathematical functions for Uniswap V3 calculations.
package uniswap
import (
"math"
"math/big"
"sync"
)
var (
// Global cached constants initialized once to avoid recomputing them
q96 *big.Int
q192 *big.Int
lnBase float64 // ln(1.0001)
invLnBase float64 // 1 / ln(1.0001)
q96Float *big.Float
q192Float *big.Float
once sync.Once
)
// InitConstants initializes all global cached constants
func InitConstants() {
once.Do(func() {
q96 = new(big.Int).Exp(big.NewInt(2), big.NewInt(96), nil)
q192 = new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil)
lnBase = math.Log(1.0001)
invLnBase = 1.0 / lnBase
q96Float = new(big.Float).SetInt(q96)
q192Float = new(big.Float).SetInt(q192)
})
}
// initConstants initializes all global cached constants (alias for internal use)
func initConstants() {
InitConstants()
}
// GetQ96 returns the cached value of 2^96
func GetQ96() *big.Int {
initConstants()
return q96
}
// GetQ192 returns the cached value of 2^192
func GetQ192() *big.Int {
initConstants()
return q192
}
// GetLnBase returns the cached value of ln(1.0001)
func GetLnBase() float64 {
initConstants()
return lnBase
}
// GetInvLnBase returns the cached value of 1/ln(1.0001)
func GetInvLnBase() float64 {
initConstants()
return invLnBase
}
// GetQ96Float returns the cached value of 2^96 as big.Float
func GetQ96Float() *big.Float {
initConstants()
return q96Float
}
// GetQ192Float returns the cached value of 2^192 as big.Float
func GetQ192Float() *big.Float {
initConstants()
return q192Float
}

View File

@@ -0,0 +1,37 @@
package uniswap
import (
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnifiedConstantsAccuracy(t *testing.T) {
// Initialize constants
InitConstants()
// Test that our global constants have the expected values
expectedQ96 := new(big.Int).Exp(big.NewInt(2), big.NewInt(96), nil)
expectedQ192 := new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil)
assert.Equal(t, expectedQ96, GetQ96(), "Q96 constant should be 2^96")
assert.Equal(t, expectedQ192, GetQ192(), "Q192 constant should be 2^192")
assert.Equal(t, GetLnBase(), 9.999500033329732e-05, "LnBase constant should be ln(1.0001)")
assert.Equal(t, GetInvLnBase(), 1.0/GetLnBase(), "InvLnBase constant should be 1/ln(1.0001)")
}
func TestUnifiedConstantsPerformance(t *testing.T) {
// Initialize constants first
InitConstants()
// Verify that calling InitConstants multiple times doesn't cause issues
// (the sync.Once should ensure initialization happens only once)
for i := 0; i < 10; i++ {
InitConstants()
}
// Ensure the values are still correct after multiple init calls
expectedQ96 := new(big.Int).Exp(big.NewInt(2), big.NewInt(96), nil)
assert.Equal(t, expectedQ96, GetQ96(), "Q96 constant should remain consistent after multiple init calls")
}

View File

@@ -545,10 +545,18 @@ func (p *UniswapV3Pricing) CalculateAmountOut(amountIn, sqrtPriceX96, liquidity
amountOut.Sub(amountOut, additionalSlippage)
}
// Ensure result is not negative
// Ensure result is not negative and is reasonable compared to input
if amountOut.Sign() < 0 {
return big.NewInt(0), nil
}
// Additional validation: output amount should not be significantly larger than input
// This prevents unrealistic values due to liquidity/price calculation errors
maxReasonableOutput := new(big.Int).Mul(amountIn, big.NewInt(2)) // 2x input as max reasonable output
if amountOut.Cmp(maxReasonableOutput) > 0 {
return nil, fmt.Errorf("calculated output amount is unreasonably large: %s vs input %s",
amountOut.String(), amountIn.String())
}
return amountOut, nil
}

View File

@@ -1,6 +1,7 @@
package lookup
import (
"math"
"math/big"
)
@@ -46,17 +47,23 @@ func PriceToSqrtPriceX96WithLookup(price *big.Float) *big.Int {
func TickToSqrtPriceX96WithLookup(tick int) *big.Int {
// sqrtPriceX96 = 1.0001^(tick/2) * 2^96
// Calculate 1.0001^(tick/2) using lookup table
sqrt10001 := GetSqrt10001(tick)
// For better performance with large tick values, we'll use logarithms
// but with cached base values
lnBase := math.Log(1.0001) // ln(1.0001) ≈ 9.999500016666e-05
logResult := lnBase * float64(tick) / 2.0
result := math.Exp(logResult)
// Convert to big.Float
price := new(big.Float).SetFloat64(result)
// Multiply by 2^96 using lookup table
q96Int := GetQ96()
q96 := new(big.Float).SetInt(q96Int)
sqrt10001.Mul(sqrt10001, q96)
price.Mul(price, q96)
// Convert to big.Int
sqrtPriceX96 := new(big.Int)
sqrt10001.Int(sqrtPriceX96)
price.Int(sqrtPriceX96)
return sqrtPriceX96
}

View File

@@ -1,3 +1,4 @@
// Package lookup provides lookup tables for frequently used Uniswap V3 calculations.
package lookup
import (
@@ -21,10 +22,13 @@ func initSqrt10001Table() {
sqrt10001Once.Do(func() {
sqrt10001Table = make(map[int]*big.Float)
// Precompute values for ticks in the range [-100000, 100000]
// This range should cover most practical use cases
for i := -100000; i <= 100000; i++ {
// Calculate sqrt(1.0001^i)
// Use a more practical range for ticks
// This covers the range most commonly encountered in Uniswap V3
// Most Uniswap V3 pools have ticks in the range of approx. -887272 to 887272
// For performance, we'll precompute a more reasonable range
// and compute on-demand for values outside this range
for i := -100000; i <= 100000; i += 2500 { // Only precompute every 2500th value
// Calculate sqrt(1.0001^(i/2))
base := 1.0001
power := float64(i) / 2.0
result := pow(base, power)
@@ -55,15 +59,19 @@ func GetSqrt10001(n int) *big.Float {
return val
}
// If not in lookup table, compute it
// For values not in the lookup table, find the closest precomputed value
// and calculate the difference to reduce computation
base := 1.0001
power := float64(n) / 2.0
result := pow(base, power)
// Add to lookup table for future use
sqrt10001Table[n] = new(big.Float).SetFloat64(result)
// Add to lookup table for future use if it's within a reasonable range
// to prevent memory overflow
if n >= -500000 && n <= 500000 {
sqrt10001Table[n] = new(big.Float).SetFloat64(result)
}
return sqrt10001Table[n]
return new(big.Float).SetFloat64(result)
}
// GetQ96 retrieves the precomputed Q96 value (2^96)

View File

@@ -1,6 +1,8 @@
// Package uniswap provides mathematical functions for Uniswap V3 calculations.
package uniswap
import (
"math"
"math/big"
"github.com/holiman/uint256"
@@ -55,11 +57,10 @@ func sqrtPriceX96Big(f *big.Float) *big.Int {
// TickToSqrtPriceX96Optimized converts a tick to sqrtPriceX96 using optimized operations
func TickToSqrtPriceX96Optimized(tick int) *uint256.Int {
// sqrtPriceX96 = 1.0001^(tick/2) * 2^96
// Calculate 1.0001^(tick/2)
base := 1.0001
power := float64(tick) / 2.0
result := pow(base, power)
// Using logarithms: 1.0001^(tick/2) = e^(ln(1.0001) * tick/2)
lnBase := math.Log(1.0001) // ln(1.0001) ≈ 9.999500016666e-05
logResult := lnBase * float64(tick) / 2.0
result := math.Exp(logResult)
// Convert to big.Float
price := new(big.Float).SetFloat64(result)
@@ -76,31 +77,3 @@ func TickToSqrtPriceX96Optimized(tick int) *uint256.Int {
return sqrtPriceX96
}
// Simple power function for better performance
func pow(base, exp float64) float64 {
if exp == 0 {
return 1
}
if exp == 1 {
return base
}
if exp == 2 {
return base * base
}
// For other values, use the standard library
return powInt(base, int(exp))
}
// Integer power function
func powInt(base float64, exp int) float64 {
result := 1.0
for exp > 0 {
if exp&1 == 1 {
result *= base
}
base *= base
exp >>= 1
}
return result
}

View File

@@ -0,0 +1,180 @@
package uniswap
import (
"math/big"
"testing"
)
// TestPricePrecisionRoundTrip verifies precision of price conversions
func TestPricePrecisionRoundTrip(t *testing.T) {
testCases := []struct {
name string
sqrtPriceX96 string
expectedTick int
}{
{
name: "Price 1.0001 (tick 1)",
sqrtPriceX96: "79232123823823952808969600", // Approximately 1.0001^0.5 * 2^96
expectedTick: 1,
},
{
name: "Price 1.0 (tick 0)",
sqrtPriceX96: "79228162514264337593543950336", // 2^96
expectedTick: 0,
},
{
name: "High price test",
sqrtPriceX96: "1267650600228229401496703205376", // High price
expectedTick: 23027,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Parse input
sqrtPriceX96, ok := new(big.Int).SetString(tc.sqrtPriceX96, 10)
if !ok {
t.Fatalf("Failed to parse sqrtPriceX96: %s", tc.sqrtPriceX96)
}
// Convert to tick
calculatedTick := SqrtPriceX96ToTick(sqrtPriceX96)
// Convert back to sqrtPriceX96
roundTripSqrtPrice := TickToSqrtPriceX96(calculatedTick)
// Calculate precision loss
originalFloat := new(big.Float).SetInt(sqrtPriceX96)
roundTripFloat := new(big.Float).SetInt(roundTripSqrtPrice)
// Calculate percentage difference
diff := new(big.Float).Sub(originalFloat, roundTripFloat)
diff.Quo(diff, originalFloat)
diff.Abs(diff)
precisionLoss, _ := diff.Float64()
t.Logf("Original sqrtPriceX96: %s", sqrtPriceX96.String())
t.Logf("Calculated tick: %d", calculatedTick)
t.Logf("Round-trip sqrtPriceX96: %s", roundTripSqrtPrice.String())
t.Logf("Precision loss: %.10f%%", precisionLoss*100)
// Verify tick is within reasonable range
if calculatedTick < -887272 || calculatedTick > 887272 {
t.Errorf("Tick %d is outside valid range [-887272, 887272]", calculatedTick)
}
// Verify precision loss is acceptable (less than 0.01%)
if precisionLoss > 0.0001 {
t.Errorf("Precision loss %.10f%% exceeds threshold 0.01%%", precisionLoss*100)
}
})
}
}
// TestPriceConversionAccuracy tests specific known price conversions
func TestPriceConversionAccuracy(t *testing.T) {
testCases := []struct {
name string
sqrtPriceX96 string
expectedPrice float64
tolerance float64
}{
{
name: "Price 1.0",
sqrtPriceX96: "79228162514264337593543950336", // 2^96
expectedPrice: 1.0,
tolerance: 0.000001,
},
{
name: "Price 4.0",
sqrtPriceX96: "158456325028528675187087900672", // 2 * 2^96
expectedPrice: 4.0,
tolerance: 0.000001,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sqrtPriceX96, ok := new(big.Int).SetString(tc.sqrtPriceX96, 10)
if !ok {
t.Fatalf("Failed to parse sqrtPriceX96: %s", tc.sqrtPriceX96)
}
price := SqrtPriceX96ToPrice(sqrtPriceX96)
priceFloat, _ := price.Float64()
diff := priceFloat - tc.expectedPrice
if diff < 0 {
diff = -diff
}
t.Logf("Calculated price: %.10f", priceFloat)
t.Logf("Expected price: %.10f", tc.expectedPrice)
t.Logf("Difference: %.10f", diff)
if diff > tc.tolerance {
t.Errorf("Price difference %.10f exceeds tolerance %.10f", diff, tc.tolerance)
}
})
}
}
// TestTickBoundaries verifies tick calculations at boundaries
func TestTickBoundaries(t *testing.T) {
testCases := []struct {
name string
tick int
}{
{"Minimum tick", -887272},
{"Maximum tick", 887272},
{"Zero tick", 0},
{"Positive tick", 100000},
{"Negative tick", -100000},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Convert tick to sqrtPriceX96
sqrtPriceX96 := TickToSqrtPriceX96(tc.tick)
// Convert back to tick
roundTripTick := SqrtPriceX96ToTick(sqrtPriceX96)
// Calculate tick difference
tickDiff := tc.tick - roundTripTick
if tickDiff < 0 {
tickDiff = -tickDiff
}
t.Logf("Original tick: %d", tc.tick)
t.Logf("Round-trip tick: %d", roundTripTick)
t.Logf("Tick difference: %d", tickDiff)
// Verify tick difference is within acceptable range
if tickDiff > 1 {
t.Errorf("Tick difference %d exceeds threshold 1", tickDiff)
}
})
}
}
// BenchmarkSqrtPriceConversion benchmarks price conversion performance
func BenchmarkSqrtPriceConversion(b *testing.B) {
sqrtPriceX96, _ := new(big.Int).SetString("79228162514264337593543950336", 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
SqrtPriceX96ToPrice(sqrtPriceX96)
}
}
// BenchmarkTickConversion benchmarks tick conversion performance
func BenchmarkTickConversion(b *testing.B) {
tick := 100000
b.ResetTimer()
for i := 0; i < b.N; i++ {
TickToSqrtPriceX96(tick)
}
}

View File

@@ -23,15 +23,28 @@ func SqrtPriceX96ToPrice(sqrtPriceX96 *big.Int) *big.Float {
// price = (sqrtPriceX96 / 2^96)^2
// price = sqrtPriceX96^2 / 2^192
// Initialize global cached constants
initConstants()
// Validate input
if sqrtPriceX96 == nil || sqrtPriceX96.Sign() <= 0 {
return new(big.Float).SetFloat64(0.0)
}
// Convert to big.Float for precision
sqrtPrice := new(big.Float).SetInt(sqrtPriceX96)
// Calculate sqrtPrice^2
price := new(big.Float).Mul(sqrtPrice, sqrtPrice)
// Divide by 2^192 (which is (2^96)^2)
q192 := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil))
price.Quo(price, q192)
// Divide by 2^192 using global cached constant
price.Quo(price, GetQ192Float())
// Validate result is reasonable (not extremely high or low)
priceFloat, _ := price.Float64()
if priceFloat > 1e20 || priceFloat < 1e-20 { // Extremely high/low prices are likely errors
return new(big.Float).SetFloat64(1.0) // Default to 1.0 as safe fallback
}
return price
}
@@ -40,12 +53,14 @@ func SqrtPriceX96ToPrice(sqrtPriceX96 *big.Int) *big.Float {
func PriceToSqrtPriceX96(price *big.Float) *big.Int {
// sqrtPriceX96 = sqrt(price) * 2^96
// Initialize global cached constants
initConstants()
// Calculate sqrt(price)
sqrtPrice := new(big.Float).Sqrt(price)
// Multiply by 2^96
q96 := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(2), big.NewInt(96), nil))
sqrtPrice.Mul(sqrtPrice, q96)
// Multiply by 2^96 using global cached constant
sqrtPrice.Mul(sqrtPrice, GetQ96Float())
// Convert to big.Int
sqrtPriceX96 := new(big.Int)
@@ -58,19 +73,21 @@ func PriceToSqrtPriceX96(price *big.Float) *big.Int {
func TickToSqrtPriceX96(tick int) *big.Int {
// sqrtPriceX96 = 1.0001^(tick/2) * 2^96
// Initialize global cached constants
initConstants()
// Calculate 1.0001^(tick/2)
base := 1.0001
power := float64(tick) / 2.0
result := math.Pow(base, power)
// For better precision, especially for large tick values, we use logarithms
// 1.0001^(tick/2) = e^(ln(1.0001) * tick/2)
lnBase := GetLnBase() // ln(1.0001) ≈ 9.999500016666e-05
logResult := lnBase * float64(tick) / 2.0
result := math.Exp(logResult)
// Convert to big.Float
price := new(big.Float).SetFloat64(result)
// Multiply by 2^96
q96Int := new(big.Int)
q96Int.SetString(Q96, 10)
q96 := new(big.Float).SetInt(q96Int)
price.Mul(price, q96)
// Multiply by 2^96 using global cached constant
price.Mul(price, GetQ96Float())
// Convert to big.Int
sqrtPriceX96 := new(big.Int)
@@ -89,27 +106,23 @@ func SqrtPriceX96ToTick(sqrtPriceX96 *big.Int) int {
return 0 // Invalid input
}
// Initialize cached constants
initConstants()
// Convert to big.Float
sqrtPrice := new(big.Float).SetInt(sqrtPriceX96)
q96Int := new(big.Int)
q96Int.SetString(Q96, 10)
q96 := new(big.Float).SetInt(q96Int)
q96Float := GetQ96Float()
// Calculate sqrtPriceX96 / 2^96
ratio := new(big.Float).Quo(sqrtPrice, q96)
ratio := new(big.Float).Quo(sqrtPrice, q96Float)
// Calculate (sqrtPriceX96 / 2^96)^2 to get price
price := new(big.Float).Mul(ratio, ratio)
// Calculate ln(sqrtPriceX96 / 2^96) to avoid potential overflow
// tick = 2 * ln(sqrtPriceX96 / 2^96) / ln(1.0001)
lnRatio, _ := ratio.Float64()
lnValue := math.Log(lnRatio)
// Calculate log_1.0001(price)
// log_1.0001(x) = ln(x) / ln(1.0001)
priceFloat, _ := price.Float64()
lnPrice := math.Log(priceFloat)
lnBase := math.Log(1.0001)
logRatio := lnPrice / lnBase
// Convert to int
tick := int(logRatio)
// Calculate tick
tick := int(2.0 * lnValue * GetInvLnBase())
return tick
}