price multipliers instead of ticks
This commit is contained in:
parent
2205ae719b
commit
6a012c5fd9
2 changed files with 186 additions and 129 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -23,3 +23,5 @@ docs/
|
||||||
*~
|
*~
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
.playwright-mcp/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,19 @@
|
||||||
<body>
|
<body>
|
||||||
<h2>Kraiken Liquidity Position Simulator</h2>
|
<h2>Kraiken Liquidity Position Simulator</h2>
|
||||||
<div style="background-color: #e3f2fd; border-radius: 4px; padding: 15px; margin-bottom: 20px; border-left: 4px solid #2196f3;">
|
<div style="background-color: #e3f2fd; border-radius: 4px; padding: 15px; margin-bottom: 20px; border-left: 4px solid #2196f3;">
|
||||||
<strong>📊 Anti-Arbitrage Three-Position Strategy</strong><br>
|
<strong>📊 Anti-Arbitrage Three-Position Strategy (Uniswap V3 1% Pool)</strong><br>
|
||||||
<em>Floor</em>: Deep liquidity position - contains ETH when below current price, KRAIKEN when above<br>
|
<br>
|
||||||
<em>Anchor</em>: Shallow liquidity around current price for fast slippage<br>
|
<strong>Position Strategy:</strong><br>
|
||||||
<em>Discovery</em>: Edge liquidity position - contains KRAIKEN when below current price, ETH when above
|
<em>Floor</em>: Deep liquidity position - holds ETH when ETH is cheap (below current price)<br>
|
||||||
|
<em>Anchor</em>: Shallow liquidity around current price for fast price discovery<br>
|
||||||
|
<em>Discovery</em>: Edge liquidity position - holds KRAIKEN when ETH is expensive (above current price)<br>
|
||||||
|
<br>
|
||||||
|
<strong>Price Multiples:</strong> Shows ETH price relative to current (1x):<br>
|
||||||
|
• 0.5x = ETH is half as expensive (Floor position holds ETH)<br>
|
||||||
|
• 1x = Current ETH price (red dashed line)<br>
|
||||||
|
• 2x = ETH is twice as expensive (Discovery position holds KRAIKEN)<br>
|
||||||
|
<br>
|
||||||
|
<em>Note: The x-axis automatically adjusts based on token ordering in the pool</em>
|
||||||
</div>
|
</div>
|
||||||
<div id="status">Loading profitable scenario data...</div>
|
<div id="status">Loading profitable scenario data...</div>
|
||||||
<textarea id="csvInput" placeholder="Paste CSV formatted data here..." style="display: none;"></textarea>
|
<textarea id="csvInput" placeholder="Paste CSV formatted data here..." style="display: none;"></textarea>
|
||||||
|
|
@ -310,6 +319,24 @@
|
||||||
return Math.sqrt(price);
|
return Math.sqrt(price);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert tick to price multiple relative to current price
|
||||||
|
// This represents ETH price multiples (how expensive ETH is relative to current)
|
||||||
|
function tickToPriceMultiple(tick, currentTick, token0isWeth) {
|
||||||
|
const price = tickToPrice(tick);
|
||||||
|
const currentPrice = tickToPrice(currentTick);
|
||||||
|
|
||||||
|
if (token0isWeth) {
|
||||||
|
// When ETH is token0, price = KRAIKEN/ETH
|
||||||
|
// We want ETH price multiple, so we need to invert
|
||||||
|
// Higher tick = more KRAIKEN per ETH = cheaper ETH
|
||||||
|
return currentPrice / price;
|
||||||
|
} else {
|
||||||
|
// When KRAIKEN is token0, price = ETH/KRAIKEN
|
||||||
|
// This is already ETH price, so just divide
|
||||||
|
return price / currentPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the invariant liquidity value from token amounts
|
// Calculate the invariant liquidity value from token amounts
|
||||||
// This represents the actual liquidity deployed to the position, independent of current price
|
// This represents the actual liquidity deployed to the position, independent of current price
|
||||||
function calculateInvariantLiquidity(token0Amount, token1Amount, tickLower, tickUpper, positionName = '') {
|
function calculateInvariantLiquidity(token0Amount, token1Amount, tickLower, tickUpper, positionName = '') {
|
||||||
|
|
@ -486,24 +513,6 @@
|
||||||
header.innerHTML = `<h3>${precedingAction}</h3>`;
|
header.innerHTML = `<h3>${precedingAction}</h3>`;
|
||||||
scenarioContainer.appendChild(header);
|
scenarioContainer.appendChild(header);
|
||||||
|
|
||||||
// Create legend
|
|
||||||
const legend = document.createElement('div');
|
|
||||||
legend.className = 'legend';
|
|
||||||
legend.innerHTML = `
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color floor"></div>
|
|
||||||
<span>Floor Position (Foundation)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color anchor"></div>
|
|
||||||
<span>Anchor Position (Current Price)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color discovery"></div>
|
|
||||||
<span>Discovery Position (Growth)</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
scenarioContainer.appendChild(legend);
|
|
||||||
|
|
||||||
// Create charts container
|
// Create charts container
|
||||||
const chartsContainer = document.createElement('div');
|
const chartsContainer = document.createElement('div');
|
||||||
|
|
@ -526,29 +535,60 @@
|
||||||
scenarioContainer.appendChild(chartsContainer);
|
scenarioContainer.appendChild(chartsContainer);
|
||||||
|
|
||||||
// Create summary panel
|
// Create summary panel
|
||||||
const summaryPanel = createSummaryPanel(positions, currentTick);
|
const summaryPanel = createSummaryPanel(positions, currentTick, token0isWeth);
|
||||||
scenarioContainer.appendChild(summaryPanel);
|
scenarioContainer.appendChild(summaryPanel);
|
||||||
|
|
||||||
// Add to page
|
// Add to page
|
||||||
document.getElementById('simulations').appendChild(scenarioContainer);
|
document.getElementById('simulations').appendChild(scenarioContainer);
|
||||||
|
|
||||||
// Create the combined chart
|
// Create the combined chart
|
||||||
createCombinedChart(combinedChart, positions, currentTick, totalLiquidity);
|
createCombinedChart(combinedChart, positions, currentTick, totalLiquidity, token0isWeth);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCombinedChart(chartDiv, positions, currentTick, totalLiquidity) {
|
function createCombinedChart(chartDiv, positions, currentTick, totalLiquidity, token0isWeth) {
|
||||||
const positionKeys = ['floor', 'anchor', 'discovery'];
|
const positionKeys = ['floor', 'anchor', 'discovery'];
|
||||||
|
|
||||||
// Calculate bar widths to represent actual tick ranges
|
// Convert positions to price multiples
|
||||||
const barWidths = positionKeys.map(key => {
|
const priceMultiplePositions = {};
|
||||||
|
|
||||||
|
positionKeys.forEach(key => {
|
||||||
const pos = positions[key];
|
const pos = positions[key];
|
||||||
return pos.tickUpper - pos.tickLower; // Width = actual tick range
|
const lowerMultiple = tickToPriceMultiple(pos.tickLower, currentTick, token0isWeth);
|
||||||
|
const upperMultiple = tickToPriceMultiple(pos.tickUpper, currentTick, token0isWeth);
|
||||||
|
const centerMultiple = tickToPriceMultiple(pos.tickLower + (pos.tickUpper - pos.tickLower) / 2, currentTick, token0isWeth);
|
||||||
|
|
||||||
|
priceMultiplePositions[key] = {
|
||||||
|
lowerMultiple: lowerMultiple,
|
||||||
|
upperMultiple: upperMultiple,
|
||||||
|
centerMultiple: centerMultiple,
|
||||||
|
width: upperMultiple - lowerMultiple,
|
||||||
|
...pos
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`Position ${key}:`, {
|
||||||
|
ticks: [pos.tickLower, pos.tickUpper],
|
||||||
|
currentTick: currentTick,
|
||||||
|
multiples: [lowerMultiple, upperMultiple],
|
||||||
|
centerMultiple: centerMultiple,
|
||||||
|
token0isWeth: token0isWeth
|
||||||
|
});
|
||||||
|
|
||||||
|
// Warn about extreme positions
|
||||||
|
if (pos.tickLower > 180000 || pos.tickUpper > 180000) {
|
||||||
|
console.warn(`EXTREME TICKS: ${key} position has ticks above 180000, which represents extreme price multiples`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate bar widths to represent actual price multiple ranges
|
||||||
|
const barWidths = positionKeys.map(key => {
|
||||||
|
const pos = priceMultiplePositions[key];
|
||||||
|
return pos.width; // Width in price multiple space
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate bar positions (centered in tick ranges)
|
// Calculate bar positions (centered in price multiple ranges)
|
||||||
const barPositions = positionKeys.map(key => {
|
const barPositions = positionKeys.map(key => {
|
||||||
const pos = positions[key];
|
const pos = priceMultiplePositions[key];
|
||||||
return pos.tickLower + (pos.tickUpper - pos.tickLower) / 2;
|
return pos.centerMultiple;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ETH trace (left y-axis)
|
// ETH trace (left y-axis)
|
||||||
|
|
@ -572,8 +612,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: positionKeys.map(key => {
|
text: positionKeys.map(key => {
|
||||||
const pos = positions[key];
|
const pos = priceMultiplePositions[key];
|
||||||
return `${pos.name}<br>ETH: ${pos.eth.toFixed(6)}<br>Range: [${pos.tickLower}, ${pos.tickUpper}]`;
|
return `${pos.name}<br>ETH: ${pos.eth.toFixed(6)}<br>Range: ${pos.lowerMultiple.toFixed(3)}x - ${pos.upperMultiple.toFixed(3)}x`;
|
||||||
}),
|
}),
|
||||||
hoverinfo: 'text'
|
hoverinfo: 'text'
|
||||||
};
|
};
|
||||||
|
|
@ -603,20 +643,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: positionKeys.map(key => {
|
text: positionKeys.map(key => {
|
||||||
const pos = positions[key];
|
const pos = priceMultiplePositions[key];
|
||||||
return `${pos.name}<br>KRAIKEN: ${pos.kraiken.toFixed(6)}<br>Range: [${pos.tickLower}, ${pos.tickUpper}]`;
|
return `${pos.name}<br>KRAIKEN: ${pos.kraiken.toFixed(6)}<br>Range: ${pos.lowerMultiple.toFixed(3)}x - ${pos.upperMultiple.toFixed(3)}x`;
|
||||||
}),
|
}),
|
||||||
hoverinfo: 'text'
|
hoverinfo: 'text'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate x-axis range based on position ranges with some padding
|
// Calculate x-axis range based on position ranges with some padding
|
||||||
const allTicks = positionKeys.flatMap(key => [positions[key].tickLower, positions[key].tickUpper]);
|
// Cap extreme values to keep chart readable
|
||||||
const minTick = Math.min(...allTicks);
|
const MAX_REASONABLE_MULTIPLE = 50; // Cap at 50x for readability
|
||||||
const maxTick = Math.max(...allTicks);
|
const MIN_REASONABLE_MULTIPLE = 0.02; // Cap at 0.02x for readability
|
||||||
const tickRange = maxTick - minTick;
|
|
||||||
const padding = tickRange * 0.1; // 10% padding on each side
|
const allMultiples = positionKeys.flatMap(key => [priceMultiplePositions[key].lowerMultiple, priceMultiplePositions[key].upperMultiple]);
|
||||||
const xAxisMin = minTick - padding;
|
const cappedMultiples = allMultiples.map(m => Math.min(MAX_REASONABLE_MULTIPLE, Math.max(MIN_REASONABLE_MULTIPLE, m)));
|
||||||
const xAxisMax = maxTick + padding;
|
const minMultiple = Math.min(...cappedMultiples);
|
||||||
|
const maxMultiple = Math.max(...cappedMultiples);
|
||||||
|
const multipleRange = maxMultiple - minMultiple;
|
||||||
|
const padding = multipleRange * 0.1; // 10% padding on each side
|
||||||
|
const xAxisMin = Math.max(0.01, minMultiple - padding); // Don't go below 0.01x
|
||||||
|
const xAxisMax = Math.min(100, maxMultiple + padding); // Cap at 100x max
|
||||||
|
|
||||||
// Debug logging for chart range
|
// Debug logging for chart range
|
||||||
console.log('Chart x-axis range:', { xAxisMin, xAxisMax });
|
console.log('Chart x-axis range:', { xAxisMin, xAxisMax });
|
||||||
|
|
@ -630,14 +675,23 @@
|
||||||
// Calculate max values for proper y-axis alignment
|
// Calculate max values for proper y-axis alignment
|
||||||
const maxEth = Math.max(...positionKeys.map(key => positions[key].eth));
|
const maxEth = Math.max(...positionKeys.map(key => positions[key].eth));
|
||||||
const maxKraiken = Math.max(...positionKeys.map(key => positions[key].kraiken));
|
const maxKraiken = Math.max(...positionKeys.map(key => positions[key].kraiken));
|
||||||
const showPriceLine = currentTick >= xAxisMin && currentTick <= xAxisMax;
|
const showPriceLine = true; // Always show price line at 1x
|
||||||
|
|
||||||
// Create liquidity × ticks traces for each position
|
// Create liquidity × ticks traces for each position using shape/filled area approach
|
||||||
const liquidityTraces = positionKeys.map(key => {
|
const liquidityTraces = [];
|
||||||
const pos = positions[key];
|
const shapes = [];
|
||||||
|
|
||||||
|
positionKeys.forEach((key, index) => {
|
||||||
|
const pos = priceMultiplePositions[key];
|
||||||
const tickRange = pos.tickUpper - pos.tickLower;
|
const tickRange = pos.tickUpper - pos.tickLower;
|
||||||
const totalLiquidity = pos.liquidity * tickRange;
|
const totalLiquidity = pos.liquidity * tickRange;
|
||||||
|
|
||||||
|
// Skip positions with zero liquidity
|
||||||
|
if (pos.liquidity === 0 || totalLiquidity === 0) {
|
||||||
|
console.warn(`Skipping ${key} position: zero liquidity`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Debug logging for very small or large values
|
// Debug logging for very small or large values
|
||||||
if (totalLiquidity < 1 || tickRange > 10000 || pos.tickLower > 180000) {
|
if (totalLiquidity < 1 || tickRange > 10000 || pos.tickLower > 180000) {
|
||||||
console.log(`Warning: ${key} position has unusual values:`, {
|
console.log(`Warning: ${key} position has unusual values:`, {
|
||||||
|
|
@ -645,108 +699,104 @@
|
||||||
tickRange: tickRange,
|
tickRange: tickRange,
|
||||||
totalLiquidity: totalLiquidity,
|
totalLiquidity: totalLiquidity,
|
||||||
ticks: [pos.tickLower, pos.tickUpper],
|
ticks: [pos.tickLower, pos.tickUpper],
|
||||||
tickCenter: pos.tickLower + (pos.tickUpper - pos.tickLower) / 2
|
lowerMultiple: pos.lowerMultiple,
|
||||||
|
upperMultiple: pos.upperMultiple
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: minVisibleLiquidity will be calculated after all positions are processed
|
// Create a filled area for each position to show its exact range
|
||||||
|
// Cap display coordinates to keep within visible range
|
||||||
|
// For extremely low positions, ensure they're visible at the left edge
|
||||||
|
let displayLower = pos.lowerMultiple;
|
||||||
|
let displayUpper = pos.upperMultiple;
|
||||||
|
|
||||||
// Note: Width adjustment will be done after x-axis range is calculated
|
// Ensure positions are visible even at extreme values
|
||||||
const visibleWidth = tickRange;
|
if (pos.upperMultiple < 0.01) {
|
||||||
|
// Position is entirely below 0.01x - show it at the left edge
|
||||||
|
displayLower = xAxisMin;
|
||||||
|
displayUpper = xAxisMin * 1.5;
|
||||||
|
} else if (pos.lowerMultiple > 50) {
|
||||||
|
// Position is entirely above 50x - show it at the right edge
|
||||||
|
displayLower = xAxisMax * 0.8;
|
||||||
|
displayUpper = xAxisMax;
|
||||||
|
} else {
|
||||||
|
// Normal capping for positions that span the visible range
|
||||||
|
displayLower = Math.max(xAxisMin, Math.min(xAxisMax, pos.lowerMultiple));
|
||||||
|
displayUpper = Math.max(xAxisMin, Math.min(xAxisMax, pos.upperMultiple));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
// Add indicator if position extends beyond visible range
|
||||||
x: [pos.tickLower + (pos.tickUpper - pos.tickLower) / 2],
|
let rangeText = `Range: ${pos.lowerMultiple.toFixed(3)}x - ${pos.upperMultiple.toFixed(3)}x`;
|
||||||
y: [totalLiquidity], // Will be adjusted later
|
let extendsBeyond = false;
|
||||||
width: visibleWidth,
|
if (pos.lowerMultiple < xAxisMin || pos.upperMultiple > xAxisMax) {
|
||||||
type: 'bar',
|
rangeText += ' <b>(extends beyond chart)</b>';
|
||||||
name: `${pos.name} Total Liquidity`,
|
extendsBeyond = true;
|
||||||
marker: {
|
}
|
||||||
color: POSITION_COLORS[key],
|
|
||||||
opacity: 0.8,
|
// For extremely high or low multiples, show in scientific notation
|
||||||
line: {
|
if (pos.upperMultiple > 100 || pos.lowerMultiple < 0.01) {
|
||||||
color: 'white',
|
const lowerStr = pos.lowerMultiple < 0.01 ? pos.lowerMultiple.toExponential(2) : pos.lowerMultiple.toFixed(3) + 'x';
|
||||||
width: 2
|
const upperStr = pos.upperMultiple > 100 ? pos.upperMultiple.toExponential(2) : pos.upperMultiple.toFixed(3) + 'x';
|
||||||
}
|
rangeText = `Range: ${lowerStr} - ${upperStr}`;
|
||||||
|
|
||||||
|
// Check if this is likely a VWAP protection position
|
||||||
|
if (key === 'floor' && (pos.lowerMultiple < 0.01 || pos.upperMultiple > 100)) {
|
||||||
|
rangeText += ' <b>(VWAP Protection - ETH Scarcity)</b>';
|
||||||
|
} else {
|
||||||
|
rangeText += ' <b>(extreme range)</b>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const trace = {
|
||||||
|
x: [displayLower, displayLower, displayUpper, displayUpper],
|
||||||
|
y: [0, totalLiquidity, totalLiquidity, 0],
|
||||||
|
fill: 'toself',
|
||||||
|
fillcolor: POSITION_COLORS[key],
|
||||||
|
opacity: extendsBeyond ? 0.5 : 0.7,
|
||||||
|
type: 'scatter',
|
||||||
|
mode: 'lines',
|
||||||
|
line: {
|
||||||
|
color: POSITION_COLORS[key],
|
||||||
|
width: 2,
|
||||||
|
dash: extendsBeyond ? 'dash' : 'solid'
|
||||||
},
|
},
|
||||||
text: `${pos.name}<br>Liquidity: ${pos.liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>Tick Range: ${tickRange.toLocaleString()}<br>Total (L×Ticks): ${totalLiquidity.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}<br>ETH: ${pos.eth.toFixed(6)}<br>KRAIKEN: ${pos.kraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>Range: [${pos.tickLower.toLocaleString()}, ${pos.tickUpper.toLocaleString()}]`,
|
name: pos.name,
|
||||||
|
text: `${pos.name}<br>Liquidity: ${pos.liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>Tick Range: ${tickRange.toLocaleString()}<br>Total (L×Ticks): ${totalLiquidity.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}<br>ETH: ${pos.eth.toFixed(6)}<br>KRAIKEN: ${pos.kraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>${rangeText}`,
|
||||||
hoverinfo: 'text',
|
hoverinfo: 'text',
|
||||||
showlegend: false
|
showlegend: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
liquidityTraces.push(trace);
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = liquidityTraces;
|
const data = liquidityTraces;
|
||||||
|
|
||||||
// Calculate max and min total liquidity (L × ticks) for y-axis scaling
|
// Calculate max and min total liquidity (L × ticks) for y-axis scaling
|
||||||
const totalLiquidities = positionKeys.map(key => {
|
const totalLiquidities = positionKeys.map(key => {
|
||||||
const pos = positions[key];
|
const pos = priceMultiplePositions[key];
|
||||||
return pos.liquidity * (pos.tickUpper - pos.tickLower);
|
const tickRange = pos.tickUpper - pos.tickLower;
|
||||||
});
|
return pos.liquidity * tickRange;
|
||||||
const maxTotalLiquidity = Math.max(...totalLiquidities);
|
}).filter(l => l > 0); // Only consider non-zero liquidities
|
||||||
const minTotalLiquidity = Math.min(...totalLiquidities.filter(l => l > 0));
|
|
||||||
|
|
||||||
// Calculate x-axis range first
|
const maxTotalLiquidity = totalLiquidities.length > 0 ? Math.max(...totalLiquidities) : 1;
|
||||||
const allTicksForRange = [];
|
const minTotalLiquidity = totalLiquidities.length > 0 ? Math.min(...totalLiquidities) : 0.1;
|
||||||
positionKeys.forEach(key => {
|
|
||||||
allTicksForRange.push(positions[key].tickLower, positions[key].tickUpper);
|
|
||||||
});
|
|
||||||
allTicksForRange.push(currentTick);
|
|
||||||
|
|
||||||
const minTickForWidth = Math.min(...allTicksForRange);
|
|
||||||
const maxTickForWidth = Math.max(...allTicksForRange);
|
|
||||||
const tickRangeTotal = maxTickForWidth - minTickForWidth;
|
|
||||||
const paddingForWidth = tickRangeTotal * 0.1;
|
|
||||||
const xAxisMinForWidth = minTickForWidth - paddingForWidth;
|
|
||||||
const xAxisMaxForWidth = maxTickForWidth + paddingForWidth;
|
|
||||||
const xRange = xAxisMaxForWidth - xAxisMinForWidth;
|
|
||||||
|
|
||||||
// Calculate minimum visible width as 2% of x-axis range
|
|
||||||
const minVisibleWidth = xRange * 0.02;
|
|
||||||
|
|
||||||
// Adjust bar widths to ensure visibility
|
|
||||||
liquidityTraces.forEach((trace, index) => {
|
|
||||||
const pos = positions[positionKeys[index]];
|
|
||||||
const actualWidth = pos.tickUpper - pos.tickLower;
|
|
||||||
if (actualWidth < minVisibleWidth) {
|
|
||||||
trace.width = minVisibleWidth;
|
|
||||||
// Add note about width adjustment
|
|
||||||
trace.text += `<br><i>(Width expanded for visibility - actual: ${actualWidth} ticks)</i>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort liquidity values to find appropriate thresholds
|
|
||||||
const sortedLiquidities = [...totalLiquidities].sort((a, b) => a - b);
|
|
||||||
|
|
||||||
// Ensure all bars are visible on the log scale
|
|
||||||
// Set minimum height to be 2% of the median value, which should make all bars clearly visible
|
|
||||||
const medianLiquidity = sortedLiquidities[Math.floor(sortedLiquidities.length / 2)];
|
|
||||||
const minVisibleLiquidity = medianLiquidity * 0.02;
|
|
||||||
|
|
||||||
// Adjust y values to ensure minimum visibility
|
|
||||||
liquidityTraces.forEach((trace, index) => {
|
|
||||||
const actualValue = totalLiquidities[index];
|
|
||||||
if (actualValue < minVisibleLiquidity) {
|
|
||||||
trace.y[0] = minVisibleLiquidity;
|
|
||||||
// Add a note to the hover text that this value was adjusted for visibility
|
|
||||||
trace.text += `<br><i>(Height adjusted for visibility - actual: ${actualValue.toExponential(2)})</i>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure minimum is at least 1e-10 for log scale
|
// Ensure minimum is at least 1e-10 for log scale
|
||||||
const yMin = Math.max(1e-10, Math.min(minTotalLiquidity / 100, minVisibleLiquidity / 10));
|
const yMin = Math.max(1e-10, minTotalLiquidity / 100);
|
||||||
|
const yMax = maxTotalLiquidity * 10;
|
||||||
|
|
||||||
if (showPriceLine) {
|
if (showPriceLine) {
|
||||||
const priceLineTrace = {
|
const priceLineTrace = {
|
||||||
x: [currentTick, currentTick],
|
x: [1, 1], // Current price is always at 1x
|
||||||
y: [yMin, maxTotalLiquidity * 10], // Use log scale range
|
y: [yMin, yMax], // Use calculated y range
|
||||||
mode: 'lines',
|
mode: 'lines',
|
||||||
line: {
|
line: {
|
||||||
color: 'red',
|
color: 'red',
|
||||||
width: 3,
|
width: 3,
|
||||||
dash: 'dash'
|
dash: 'dash'
|
||||||
},
|
},
|
||||||
name: 'Current Price',
|
name: 'Current Price (1x)',
|
||||||
hoverinfo: 'x',
|
hoverinfo: 'name',
|
||||||
text: [`Current Price: ${currentTick}`],
|
|
||||||
showlegend: true
|
showlegend: true
|
||||||
};
|
};
|
||||||
data.push(priceLineTrace);
|
data.push(priceLineTrace);
|
||||||
|
|
@ -754,14 +804,17 @@
|
||||||
|
|
||||||
const layout = {
|
const layout = {
|
||||||
title: {
|
title: {
|
||||||
text: `Total Liquidity Distribution (L × Tick Range) - Current Price: ${currentTick}${showPriceLine ? '' : ' - Outside Range'}`,
|
text: `Total Liquidity Distribution (L × Tick Range)`,
|
||||||
font: { size: 16 }
|
font: { size: 16 }
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: 'Price Ticks',
|
title: 'Price Multiple (relative to current price)',
|
||||||
showgrid: true,
|
showgrid: true,
|
||||||
gridcolor: '#e0e0e0',
|
gridcolor: '#e0e0e0',
|
||||||
range: [xAxisMin, xAxisMax]
|
range: [Math.log10(xAxisMin), Math.log10(xAxisMax)],
|
||||||
|
tickformat: '.2f',
|
||||||
|
ticksuffix: 'x',
|
||||||
|
type: 'log' // Use log scale for better visualization of price multiples
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
title: 'Total Liquidity (L × Ticks)',
|
title: 'Total Liquidity (L × Ticks)',
|
||||||
|
|
@ -770,7 +823,7 @@
|
||||||
gridcolor: '#e0e0e0',
|
gridcolor: '#e0e0e0',
|
||||||
dtick: 1, // Major gridlines at powers of 10
|
dtick: 1, // Major gridlines at powers of 10
|
||||||
tickformat: '.0e', // Scientific notation
|
tickformat: '.0e', // Scientific notation
|
||||||
range: [Math.log10(yMin), Math.log10(maxTotalLiquidity * 10)]
|
range: [Math.log10(yMin), Math.log10(yMax)]
|
||||||
},
|
},
|
||||||
showlegend: true,
|
showlegend: true,
|
||||||
legend: {
|
legend: {
|
||||||
|
|
@ -783,7 +836,7 @@
|
||||||
plot_bgcolor: 'white',
|
plot_bgcolor: 'white',
|
||||||
paper_bgcolor: 'white',
|
paper_bgcolor: 'white',
|
||||||
margin: { l: 60, r: 60, t: 60, b: 50 },
|
margin: { l: 60, r: 60, t: 60, b: 50 },
|
||||||
barmode: 'group'
|
hovermode: 'closest'
|
||||||
};
|
};
|
||||||
|
|
||||||
Plotly.newPlot(chartDiv, data, layout, {responsive: true});
|
Plotly.newPlot(chartDiv, data, layout, {responsive: true});
|
||||||
|
|
@ -974,7 +1027,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSummaryPanel(positions, currentTick) {
|
function createSummaryPanel(positions, currentTick, token0isWeth) {
|
||||||
const panel = document.createElement('div');
|
const panel = document.createElement('div');
|
||||||
panel.className = 'summary-panel';
|
panel.className = 'summary-panel';
|
||||||
|
|
||||||
|
|
@ -1010,13 +1063,15 @@
|
||||||
// Calculate position-specific liquidity percentage
|
// Calculate position-specific liquidity percentage
|
||||||
const liquidityPercent = totalUniV3Liquidity > 0 ? (pos.liquidity / totalUniV3Liquidity * 100).toFixed(1) : '0.0';
|
const liquidityPercent = totalUniV3Liquidity > 0 ? (pos.liquidity / totalUniV3Liquidity * 100).toFixed(1) : '0.0';
|
||||||
const tickRange = pos.tickUpper - pos.tickLower;
|
const tickRange = pos.tickUpper - pos.tickLower;
|
||||||
|
const lowerMultiple = tickToPriceMultiple(pos.tickLower, currentTick, token0isWeth);
|
||||||
|
const upperMultiple = tickToPriceMultiple(pos.tickUpper, currentTick, token0isWeth);
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<strong>${pos.name} Position</strong><br>
|
<strong>${pos.name} Position</strong><br>
|
||||||
ETH: ${pos.eth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
ETH: ${pos.eth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||||
KRAIKEN: ${pos.kraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
KRAIKEN: ${pos.kraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||||
Uniswap V3 Liquidity: ${pos.liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} (${liquidityPercent}%)<br>
|
Uniswap V3 Liquidity: ${pos.liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} (${liquidityPercent}%)<br>
|
||||||
Range: ${tickRange} ticks
|
Range: ${lowerMultiple.toFixed(3)}x - ${upperMultiple.toFixed(3)}x
|
||||||
`;
|
`;
|
||||||
grid.appendChild(item);
|
grid.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue