better visualizer
This commit is contained in:
parent
6a012c5fd9
commit
50eac74b18
8 changed files with 542 additions and 626 deletions
|
|
@ -1,131 +1,56 @@
|
|||
# KRAIKEN LiquidityManager Fuzzing Analysis
|
||||
|
||||
Tools for testing the KRAIKEN LiquidityManager's resilience against various trading strategies to identify scenarios where traders can profit.
|
||||
|
||||
Tools for testing KRAIKEN's three-position strategy resilience against various market conditions and trading patterns.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using run-fuzzing.sh (Recommended)
|
||||
|
||||
The `run-fuzzing.sh` script provides an easy way to run fuzzing campaigns with different market optimizers:
|
||||
|
||||
```bash
|
||||
# Basic usage - run with specific optimizer
|
||||
# Run with specific optimizer (50 runs default)
|
||||
./analysis/run-fuzzing.sh BullMarketOptimizer
|
||||
|
||||
# Specify number of runs (default: 50)
|
||||
./analysis/run-fuzzing.sh WhaleOptimizer runs=100
|
||||
# Custom runs and trades
|
||||
./analysis/run-fuzzing.sh WhaleOptimizer runs=100 trades=30
|
||||
|
||||
# Specify trades per run (default: 20, actual will be ±5)
|
||||
./analysis/run-fuzzing.sh BearMarketOptimizer runs=10 trades=50
|
||||
|
||||
# Debug mode - generates position tracking CSV (forces runs=1)
|
||||
# Debug mode with position tracking CSV (forces runs=1)
|
||||
./analysis/run-fuzzing.sh NeutralMarketOptimizer debugCSV
|
||||
|
||||
# Multiple parameters
|
||||
./analysis/run-fuzzing.sh BullMarketOptimizer runs=25 trades=30
|
||||
```
|
||||
|
||||
**Available optimizers:**
|
||||
- `BullMarketOptimizer` - Biased towards buying
|
||||
- `NeutralMarketOptimizer` - Balanced trading
|
||||
- `BearMarketOptimizer` - Biased towards selling
|
||||
- `WhaleOptimizer` - Large position trading
|
||||
- `MockOptimizer` - Test optimizer
|
||||
## Available Optimizers
|
||||
|
||||
- `BullMarketOptimizer` - Buying bias
|
||||
- `NeutralMarketOptimizer` - Balanced trading
|
||||
- `BearMarketOptimizer` - Selling bias
|
||||
- `WhaleOptimizer` - Large positions
|
||||
- `RandomScenarioOptimizer` - Random behavior
|
||||
|
||||
**Features:**
|
||||
- Automatic results aggregation and summary generation
|
||||
- Progress tracking with colored output
|
||||
- Cumulative P&L calculation across all runs
|
||||
- Automatic visualization launch for profitable scenarios
|
||||
- Organized output in timestamped directories
|
||||
## Output Structure
|
||||
|
||||
### Manual Fuzzing (Advanced)
|
||||
|
||||
```bash
|
||||
# Run fuzzing analysis with default settings (100 runs per market)
|
||||
forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
|
||||
# Custom configuration
|
||||
FUZZING_RUNS=500 forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
|
||||
# With position tracking (generates detailed CSV for each scenario)
|
||||
TRACK_POSITIONS=true FUZZING_RUNS=50 forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### run-fuzzing.sh Parameters
|
||||
- **optimizer_class**: Required. The optimizer class to use (e.g., BullMarketOptimizer)
|
||||
- **runs=N**: Optional. Number of fuzzing runs (default: 50)
|
||||
- **trades=N**: Optional. Trades per run (default: 20, actual will be ±5)
|
||||
- **debugCSV**: Optional. Enable debug mode with position tracking CSV (forces runs=1)
|
||||
|
||||
### Environment Variables (Manual Mode)
|
||||
- **FUZZING_RUNS**: Number of random trading scenarios per market type (default: 100)
|
||||
- **TRACK_POSITIONS**: Enable detailed position tracking CSV output (default: false)
|
||||
- **OPTIMIZER_CLASS**: The optimizer to use (default: BullMarketOptimizer)
|
||||
- **TRADES_PER_RUN**: Number of trades per run (default: 20)
|
||||
- **SEED_OFFSET**: Starting seed for random number generation (default: 0)
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Real Deployments**: Deploys actual Uniswap V3 factory, pool, and LiquidityManager
|
||||
2. **Random Trading**: Generates random buy/sell patterns with varying amounts and timing
|
||||
3. **Recenter Calls**: Triggers `lm.recenter()` at random intervals
|
||||
4. **Profit Detection**: Identifies scenarios where traders end with more ETH than they started
|
||||
5. **CSV Export**: Saves all profitable scenarios to `profitable_scenarios_[timestamp].csv`
|
||||
|
||||
## Output Files
|
||||
|
||||
### Using run-fuzzing.sh
|
||||
Each campaign creates a timestamped directory: `fuzzing_results_[optimizer]_[timestamp]/`
|
||||
- `config.txt` - Campaign configuration
|
||||
Each campaign creates `fuzzing_results_[optimizer]_[timestamp]/`:
|
||||
- `config.txt` - Campaign parameters
|
||||
- `run_*.log` - Individual run logs
|
||||
- `merged_profitable_scenarios.csv` - All profitable scenarios combined
|
||||
- `summary.txt` - Campaign summary with statistics
|
||||
- `debug_positions_*.csv` - Position tracking data (when debugCSV is used)
|
||||
|
||||
### Manual Mode
|
||||
- `profitable_scenarios_[timestamp].csv` - Details of all profitable trading sequences
|
||||
- `positions_[scenario]_[seed].csv` - Liquidity position data (only with TRACK_POSITIONS=true)
|
||||
- `merged_profitable_scenarios.csv` - Profitable scenarios combined
|
||||
- `summary.txt` - Statistics and cumulative P&L
|
||||
- `debug_positions_*.csv` - Position data (debugCSV mode only)
|
||||
|
||||
## Visualization
|
||||
|
||||
```bash
|
||||
# View results in browser
|
||||
python3 -m http.server 8000
|
||||
# Open http://localhost:8000/scenario-visualizer.html
|
||||
# Automatic launch with debugCSV
|
||||
./analysis/run-fuzzing.sh [optimizer] debugCSV
|
||||
|
||||
# Or use the shell script
|
||||
./view-scenarios.sh
|
||||
# Manual server (port 8000)
|
||||
./analysis/view-scenarios.sh
|
||||
```
|
||||
|
||||
## Analysis Tools
|
||||
|
||||
- `AnalysisVisualizer.py` - Generates charts from CSV data
|
||||
- `scenario-visualizer.html` - Interactive web visualization
|
||||
- `RISK_ANALYSIS_FINDINGS.md` - Summary of discovered vulnerabilities
|
||||
|
||||
## Components
|
||||
|
||||
- `run-fuzzing.sh` - Main campaign runner with automatic visualization
|
||||
- `FuzzingAnalysis.s.sol` - Core fuzzing script
|
||||
- `helpers/SwapExecutor.sol` - Shared swap execution logic
|
||||
- `helpers/CSVManager.sol` - CSV generation utilities
|
||||
- `helpers/CSVHelper.sol` - CSV formatting helpers
|
||||
|
||||
## Example Campaign Comparison
|
||||
|
||||
To run fuzzing campaigns comparing different market optimizers:
|
||||
## Advanced Usage
|
||||
|
||||
```bash
|
||||
# Run campaigns for all three market conditions
|
||||
./analysis/run-fuzzing.sh BullMarketOptimizer runs=100
|
||||
./analysis/run-fuzzing.sh NeutralMarketOptimizer runs=100
|
||||
./analysis/run-fuzzing.sh BearMarketOptimizer runs=100
|
||||
|
||||
# Check results
|
||||
cat fuzzing_results_*/summary.txt | grep -E "(Optimizer:|Success rate:|Average P&L)"
|
||||
# Manual fuzzing with environment variables
|
||||
FUZZING_RUNS=500 TRACK_POSITIONS=true forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
```
|
||||
|
||||
Environment variables:
|
||||
- `FUZZING_RUNS` - Scenarios per market (default: 100)
|
||||
- `TRACK_POSITIONS` - Enable position CSV (default: false)
|
||||
- `OPTIMIZER_CLASS` - Optimizer to use
|
||||
- `TRADES_PER_RUN` - Trades per run (default: 20)
|
||||
|
|
@ -343,14 +343,6 @@ contract FuzzingAnalysis is Test, CSVManager {
|
|||
(uint128 anchorLiq, int24 anchorLower, int24 anchorUpper) = lm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
(uint128 discoveryLiq, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
||||
|
||||
// Calculate ETH and HARB amounts in each position using proper Uniswap math
|
||||
uint256 floorEth = 0;
|
||||
uint256 floorHarb = 0;
|
||||
uint256 anchorEth = 0;
|
||||
uint256 anchorHarb = 0;
|
||||
uint256 discoveryEth = 0;
|
||||
uint256 discoveryHarb = 0;
|
||||
|
||||
// Debug: Log liquidity values
|
||||
if (keccak256(bytes(label)) == keccak256(bytes("Initial")) || keccak256(bytes(label)) == keccak256(bytes("Recenter_2"))) {
|
||||
console.log("=== LIQUIDITY VALUES ===");
|
||||
|
|
@ -372,125 +364,19 @@ contract FuzzingAnalysis is Test, CSVManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Calculate amounts for each position using LiquidityAmounts library
|
||||
if (floorLiq > 0) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorUpper);
|
||||
|
||||
// Calculate actual deposited amounts based on position relative to current price
|
||||
if (token0isWeth) {
|
||||
if (currentTick < floorLower) {
|
||||
// Position is above current price - contains only token1 (KRAIKEN)
|
||||
floorEth = 0;
|
||||
// Use position's lower tick for actual deposited amount
|
||||
floorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
} else if (currentTick >= floorUpper) {
|
||||
// Position is below current price - contains only token0 (WETH)
|
||||
// Use position's upper tick for actual deposited amount
|
||||
floorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
floorHarb = 0;
|
||||
} else {
|
||||
// Current price is within the position
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
floorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, floorLiq);
|
||||
floorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, floorLiq);
|
||||
}
|
||||
} else {
|
||||
if (currentTick < floorLower) {
|
||||
// Position is above current price
|
||||
floorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
floorEth = 0;
|
||||
} else if (currentTick >= floorUpper) {
|
||||
// Position is below current price
|
||||
floorHarb = 0;
|
||||
floorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
} else {
|
||||
// Current price is within the position
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
floorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, floorLiq);
|
||||
floorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, floorLiq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorLiq > 0) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(anchorLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(anchorUpper);
|
||||
|
||||
if (token0isWeth) {
|
||||
if (currentTick < anchorLower) {
|
||||
anchorEth = 0;
|
||||
anchorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
} else if (currentTick >= anchorUpper) {
|
||||
anchorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorHarb = 0;
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
anchorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, anchorLiq);
|
||||
}
|
||||
} else {
|
||||
if (currentTick < anchorLower) {
|
||||
anchorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorEth = 0;
|
||||
} else if (currentTick >= anchorUpper) {
|
||||
anchorHarb = 0;
|
||||
anchorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
anchorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, anchorLiq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discoveryLiq > 0) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(discoveryLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(discoveryUpper);
|
||||
|
||||
if (token0isWeth) {
|
||||
if (currentTick < discoveryLower) {
|
||||
discoveryEth = 0;
|
||||
discoveryHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
} else if (currentTick >= discoveryUpper) {
|
||||
discoveryEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryHarb = 0;
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
discoveryEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, discoveryLiq);
|
||||
}
|
||||
} else {
|
||||
if (currentTick < discoveryLower) {
|
||||
discoveryHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryEth = 0;
|
||||
} else if (currentTick >= discoveryUpper) {
|
||||
discoveryHarb = 0;
|
||||
discoveryEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
discoveryHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, discoveryLiq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create position data row matching the expected CSV format
|
||||
// Create position data row with liquidity values directly
|
||||
string memory row = string.concat(
|
||||
label, ", ",
|
||||
vm.toString(currentTick), ", ",
|
||||
vm.toString(floorLower), ", ",
|
||||
vm.toString(floorUpper), ", ",
|
||||
vm.toString(floorEth), ", ",
|
||||
vm.toString(floorHarb), ", ",
|
||||
vm.toString(uint256(floorLiq)), ", ",
|
||||
vm.toString(anchorLower), ", ",
|
||||
vm.toString(anchorUpper), ", ",
|
||||
vm.toString(anchorEth), ", ",
|
||||
vm.toString(anchorHarb), ", ",
|
||||
vm.toString(uint256(anchorLiq)), ", ",
|
||||
vm.toString(discoveryLower), ", ",
|
||||
vm.toString(discoveryUpper), ", ",
|
||||
vm.toString(discoveryEth), ", ",
|
||||
vm.toString(discoveryHarb), ", ",
|
||||
vm.toString(uint256(discoveryLiq)), ", ",
|
||||
token0isWeth ? "true" : "false"
|
||||
);
|
||||
appendCSVRow(row);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ library CSVHelper {
|
|||
*/
|
||||
function createPositionsHeader() internal pure returns (string memory) {
|
||||
return
|
||||
"precedingAction, currentTick, floorTickLower, floorTickUpper, floorToken0, floorToken1, anchorTickLower, anchorTickUpper, anchorToken0, anchorToken1, discoveryTickLower, discoveryTickUpper, discoveryToken0, discoveryToken1, token0isWeth";
|
||||
"precedingAction, currentTick, floorTickLower, floorTickUpper, floorLiquidity, anchorTickLower, anchorTickUpper, anchorLiquidity, discoveryTickLower, discoveryTickUpper, discoveryLiquidity, token0isWeth";
|
||||
}
|
||||
|
||||
function createTimeSeriesHeader() internal pure returns (string memory) {
|
||||
|
|
|
|||
|
|
@ -343,36 +343,66 @@ if [ "$CSV_GENERATED" = true ] && [ -n "$LATEST_CSV" ]; then
|
|||
# Use absolute path for the symlink
|
||||
ln -s "$(pwd)/$LATEST_CSV" "$TEMP_LINK"
|
||||
|
||||
# Start the viewer in background in its own process group
|
||||
setsid ./view-scenarios.sh &
|
||||
VIEWER_PID=$!
|
||||
# Check if server is already running on common ports
|
||||
SERVER_RUNNING=false
|
||||
EXISTING_PORT=""
|
||||
for PORT in 8000 8001 8002; do
|
||||
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||||
# Check if it's a python http server in our analysis directory
|
||||
if lsof -Pi :$PORT -sTCP:LISTEN 2>/dev/null | grep -q "python.*http.server"; then
|
||||
SERVER_RUNNING=true
|
||||
EXISTING_PORT=$PORT
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Give the server time to start and browser to open
|
||||
sleep 2
|
||||
|
||||
# Show the URL
|
||||
echo ""
|
||||
echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}"
|
||||
echo "(If port 8000 was busy, check the port number mentioned above)"
|
||||
echo "If browser didn't open, manually navigate to that URL"
|
||||
|
||||
# Wait for user input
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}"
|
||||
read -r
|
||||
|
||||
# Kill the viewer process and its children
|
||||
if [ -n "$VIEWER_PID" ]; then
|
||||
# Kill the entire process group (includes python server)
|
||||
pkill -TERM -g $VIEWER_PID 2>/dev/null || true
|
||||
# Give it a moment to clean up
|
||||
sleep 1
|
||||
# Force kill if still running
|
||||
pkill -KILL -g $VIEWER_PID 2>/dev/null || true
|
||||
if [ "$SERVER_RUNNING" = true ]; then
|
||||
echo -e "${YELLOW}Server already running on port $EXISTING_PORT${NC}"
|
||||
echo -e "${GREEN}Browser should open to: http://localhost:$EXISTING_PORT/scenario-visualizer.html${NC}"
|
||||
|
||||
# Try to open browser to existing server
|
||||
if command -v xdg-open &> /dev/null; then
|
||||
xdg-open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null &
|
||||
elif command -v open &> /dev/null; then
|
||||
open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null &
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Enter to exit (server will keep running)...${NC}"
|
||||
read -r
|
||||
else
|
||||
# Start the viewer in background in its own process group
|
||||
setsid ./view-scenarios.sh &
|
||||
VIEWER_PID=$!
|
||||
|
||||
# Give the server time to start and browser to open
|
||||
sleep 2
|
||||
|
||||
# Show the URL
|
||||
echo ""
|
||||
echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}"
|
||||
echo "(If port 8000 was busy, check the port number mentioned above)"
|
||||
echo "If browser didn't open, manually navigate to that URL"
|
||||
|
||||
# Wait for user input
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}"
|
||||
read -r
|
||||
|
||||
# Kill the viewer process and its children
|
||||
if [ -n "$VIEWER_PID" ]; then
|
||||
# Kill the entire process group (includes python server)
|
||||
pkill -TERM -g $VIEWER_PID 2>/dev/null || true
|
||||
# Give it a moment to clean up
|
||||
sleep 1
|
||||
# Force kill if still running
|
||||
pkill -KILL -g $VIEWER_PID 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Viewer stopped.${NC}"
|
||||
fi
|
||||
|
||||
# Clean up the symlink
|
||||
rm -f "$TEMP_LINK"
|
||||
|
||||
echo -e "${GREEN}Viewer stopped.${NC}"
|
||||
fi
|
||||
|
|
@ -133,16 +133,17 @@
|
|||
<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 = ETH is cheaper than current price (positions below current hold ETH)<br>
|
||||
• 1x = Current ETH price (red dashed line)<br>
|
||||
• 2x = ETH is twice as expensive (Discovery position holds KRAIKEN)<br>
|
||||
• > 1x = ETH is more expensive than current price (positions above current hold KRAIKEN)<br>
|
||||
<br>
|
||||
<em>Note: The x-axis automatically adjusts based on token ordering in the pool</em>
|
||||
<em>Note: The x-axis automatically adjusts based on token ordering in the pool</em><br>
|
||||
<br>
|
||||
<strong>Navigation:</strong> Use the Previous/Next buttons or URL parameter <code>?row=N</code> to view specific CSV rows
|
||||
</div>
|
||||
<div id="status">Loading profitable scenario data...</div>
|
||||
<textarea id="csvInput" placeholder="Paste CSV formatted data here..." style="display: none;"></textarea>
|
||||
<button onclick="parseAndSimulateCSV()" style="display: none;">Simulate CSV Data</button>
|
||||
<button onclick="toggleManualInput()" id="manualButton">Manual Input Mode</button>
|
||||
<div id="simulations"></div>
|
||||
|
||||
<script>
|
||||
|
|
@ -171,6 +172,13 @@
|
|||
// - Anchor Position: Mixed tokens around current price for shallow liquidity
|
||||
// - Discovery Position: Edge liquidity - holds ETH above price, KRAIKEN below price
|
||||
|
||||
// Get row parameter from URL
|
||||
function getRowParameter() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const row = urlParams.get('row');
|
||||
return row ? parseInt(row) - 2 : 0; // Convert CSV line number to array index
|
||||
}
|
||||
|
||||
// Auto-load CSV data on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCSVData();
|
||||
|
|
@ -208,25 +216,10 @@
|
|||
<em>If no CSV exists, run: forge script analysis/SimpleAnalysis.s.sol --ffi</em>
|
||||
</div>
|
||||
`;
|
||||
console.log('CSV load error:', error);
|
||||
// CSV load error - handled by status message above
|
||||
});
|
||||
}
|
||||
|
||||
function toggleManualInput() {
|
||||
const csvInput = document.getElementById('csvInput');
|
||||
const button = document.getElementById('manualButton');
|
||||
const parseButton = document.querySelector('button[onclick="parseAndSimulateCSV()"]');
|
||||
|
||||
if (csvInput.style.display === 'none') {
|
||||
csvInput.style.display = 'block';
|
||||
parseButton.style.display = 'inline-block';
|
||||
button.textContent = 'Hide Manual Input';
|
||||
} else {
|
||||
csvInput.style.display = 'none';
|
||||
parseButton.style.display = 'none';
|
||||
button.textContent = 'Manual Input Mode';
|
||||
}
|
||||
}
|
||||
|
||||
function parseCSV(csv) {
|
||||
const lines = csv.trim().split('\n');
|
||||
|
|
@ -252,37 +245,121 @@
|
|||
}
|
||||
|
||||
function simulateCSVData(data) {
|
||||
let previousRow = null;
|
||||
|
||||
data.forEach((row, index) => {
|
||||
// Get selected row from URL parameter
|
||||
const selectedIndex = getRowParameter();
|
||||
|
||||
// Add current row info and navigation
|
||||
const currentRowInfo = document.createElement('div');
|
||||
currentRowInfo.style.cssText = 'margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 4px; text-align: center;';
|
||||
currentRowInfo.innerHTML = `
|
||||
<strong>Currently viewing: Line ${selectedIndex + 2} - ${data[selectedIndex].precedingAction}</strong><br>
|
||||
<span style="color: #666; font-size: 14px;">Total rows: ${data.length} (Lines 2-${data.length + 1})</span>
|
||||
`;
|
||||
document.getElementById('simulations').appendChild(currentRowInfo);
|
||||
|
||||
// Add navigation buttons above if not first row
|
||||
if (selectedIndex > 0) {
|
||||
const topNavDiv = document.createElement('div');
|
||||
topNavDiv.style.cssText = 'margin: 20px 0; text-align: center;';
|
||||
topNavDiv.innerHTML = `
|
||||
<button onclick="navigateRow(-1)" style="padding: 10px 20px; font-size: 16px;">
|
||||
← Previous Row (Line ${selectedIndex + 1})
|
||||
</button>
|
||||
`;
|
||||
document.getElementById('simulations').appendChild(topNavDiv);
|
||||
}
|
||||
|
||||
// Process only the selected row
|
||||
let previousRow = selectedIndex > 0 ? data[selectedIndex - 1] : null;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < data.length) {
|
||||
const row = data[selectedIndex];
|
||||
const index = selectedIndex;
|
||||
const precedingAction = row.precedingAction;
|
||||
const currentTick = parseFloat(row.currentTick);
|
||||
const token0isWeth = row.token0isWeth === 'true' || row.token0isWeth === true;
|
||||
const floorTickLower = parseFloat(row.floorTickLower);
|
||||
const floorTickUpper = parseFloat(row.floorTickUpper);
|
||||
// Swap floor values to match expected behavior
|
||||
const floorEth = parseFloat(row.floorToken1 || 0) / 1e18;
|
||||
const floorKraiken = parseFloat(row.floorToken0 || 0) / 1e18;
|
||||
const floorLiquidity = parseFloat(row.floorLiquidity || 0);
|
||||
const anchorTickLower = parseFloat(row.anchorTickLower);
|
||||
const anchorTickUpper = parseFloat(row.anchorTickUpper);
|
||||
const anchorEth = parseFloat(row.anchorToken0 || 0) / 1e18;
|
||||
const anchorKraiken = parseFloat(row.anchorToken1 || 0) / 1e18;
|
||||
const anchorLiquidity = parseFloat(row.anchorLiquidity || 0);
|
||||
const discoveryTickLower = parseFloat(row.discoveryTickLower);
|
||||
const discoveryTickUpper = parseFloat(row.discoveryTickUpper);
|
||||
// Swap discovery values to match expected behavior
|
||||
const discoveryEth = parseFloat(row.discoveryToken1 || 0) / 1e18;
|
||||
const discoveryKraiken = parseFloat(row.discoveryToken0 || 0) / 1e18;
|
||||
const discoveryLiquidity = parseFloat(row.discoveryLiquidity || 0);
|
||||
|
||||
// Calculate token amounts from liquidity
|
||||
const floorAmounts = getAmountsForLiquidity(floorLiquidity, floorTickLower, floorTickUpper, currentTick);
|
||||
const anchorAmounts = getAmountsForLiquidity(anchorLiquidity, anchorTickLower, anchorTickUpper, currentTick);
|
||||
const discoveryAmounts = getAmountsForLiquidity(discoveryLiquidity, discoveryTickLower, discoveryTickUpper, currentTick);
|
||||
|
||||
|
||||
// FIXED calculation - properly determine ETH amounts based on token ordering
|
||||
let floorEthAmount, anchorEthAmount, discoveryEthAmount;
|
||||
let floorKraikenAmount, anchorKraikenAmount, discoveryKraikenAmount;
|
||||
|
||||
// Simply use the amounts from getAmountsForLiquidity and determine which is ETH
|
||||
if (token0isWeth) {
|
||||
// token0 is WETH, token1 is KRAIKEN
|
||||
floorEthAmount = floorAmounts.amount0 / 1e18;
|
||||
floorKraikenAmount = floorAmounts.amount1 / 1e18;
|
||||
anchorEthAmount = anchorAmounts.amount0 / 1e18;
|
||||
anchorKraikenAmount = anchorAmounts.amount1 / 1e18;
|
||||
discoveryEthAmount = discoveryAmounts.amount0 / 1e18;
|
||||
discoveryKraikenAmount = discoveryAmounts.amount1 / 1e18;
|
||||
} else {
|
||||
// token0 is KRAIKEN, token1 is WETH
|
||||
floorEthAmount = floorAmounts.amount1 / 1e18;
|
||||
floorKraikenAmount = floorAmounts.amount0 / 1e18;
|
||||
anchorEthAmount = anchorAmounts.amount1 / 1e18;
|
||||
anchorKraikenAmount = anchorAmounts.amount0 / 1e18;
|
||||
discoveryEthAmount = discoveryAmounts.amount1 / 1e18;
|
||||
discoveryKraikenAmount = discoveryAmounts.amount0 / 1e18;
|
||||
}
|
||||
|
||||
const totalEth = floorEthAmount + anchorEthAmount + discoveryEthAmount;
|
||||
|
||||
|
||||
|
||||
// Use the already calculated ETH and KRAIKEN amounts from above
|
||||
let floorEth = floorEthAmount;
|
||||
let floorKraiken = floorKraikenAmount;
|
||||
let anchorEth = anchorEthAmount;
|
||||
let anchorKraiken = anchorKraikenAmount;
|
||||
let discoveryEth = discoveryEthAmount;
|
||||
let discoveryKraiken = discoveryKraikenAmount;
|
||||
|
||||
let actionAmount = '';
|
||||
let additionalInfo = '';
|
||||
|
||||
if (previousRow) {
|
||||
const prevFloorEth = parseFloat(previousRow.floorToken1 || 0) / 1e18;
|
||||
const prevFloorKraiken = parseFloat(previousRow.floorToken0 || 0) / 1e18;
|
||||
const prevAnchorEth = parseFloat(previousRow.anchorToken0 || 0) / 1e18;
|
||||
const prevAnchorKraiken = parseFloat(previousRow.anchorToken1 || 0) / 1e18;
|
||||
const prevDiscoveryEth = parseFloat(previousRow.discoveryToken1 || 0) / 1e18;
|
||||
const prevDiscoveryKraiken = parseFloat(previousRow.discoveryToken0 || 0) / 1e18;
|
||||
// Calculate previous token amounts from liquidity
|
||||
const prevCurrentTick = parseFloat(previousRow.currentTick);
|
||||
const prevFloorLiquidity = parseFloat(previousRow.floorLiquidity || 0);
|
||||
const prevAnchorLiquidity = parseFloat(previousRow.anchorLiquidity || 0);
|
||||
const prevDiscoveryLiquidity = parseFloat(previousRow.discoveryLiquidity || 0);
|
||||
|
||||
const prevFloorAmounts = getAmountsForLiquidity(prevFloorLiquidity, floorTickLower, floorTickUpper, prevCurrentTick);
|
||||
const prevAnchorAmounts = getAmountsForLiquidity(prevAnchorLiquidity, anchorTickLower, anchorTickUpper, prevCurrentTick);
|
||||
const prevDiscoveryAmounts = getAmountsForLiquidity(prevDiscoveryLiquidity, discoveryTickLower, discoveryTickUpper, prevCurrentTick);
|
||||
|
||||
let prevFloorEth, prevFloorKraiken, prevAnchorEth, prevAnchorKraiken, prevDiscoveryEth, prevDiscoveryKraiken;
|
||||
|
||||
if (token0isWeth === true) {
|
||||
prevFloorEth = prevFloorAmounts.amount0 / 1e18;
|
||||
prevFloorKraiken = prevFloorAmounts.amount1 / 1e18;
|
||||
prevAnchorEth = prevAnchorAmounts.amount0 / 1e18;
|
||||
prevAnchorKraiken = prevAnchorAmounts.amount1 / 1e18;
|
||||
prevDiscoveryEth = prevDiscoveryAmounts.amount0 / 1e18;
|
||||
prevDiscoveryKraiken = prevDiscoveryAmounts.amount1 / 1e18;
|
||||
} else {
|
||||
prevFloorEth = prevFloorAmounts.amount1 / 1e18;
|
||||
prevFloorKraiken = prevFloorAmounts.amount0 / 1e18;
|
||||
prevAnchorEth = prevAnchorAmounts.amount1 / 1e18;
|
||||
prevAnchorKraiken = prevAnchorAmounts.amount0 / 1e18;
|
||||
prevDiscoveryEth = prevDiscoveryAmounts.amount1 / 1e18;
|
||||
prevDiscoveryKraiken = prevDiscoveryAmounts.amount0 / 1e18;
|
||||
}
|
||||
|
||||
const ethDifference = (floorEth + anchorEth + discoveryEth) - (prevFloorEth + prevAnchorEth + prevDiscoveryEth);
|
||||
const kraikenDifference = (floorKraiken + anchorKraiken + discoveryKraiken) - (prevFloorKraiken + prevAnchorKraiken + prevDiscoveryKraiken);
|
||||
|
|
@ -302,19 +379,83 @@
|
|||
const lineNumber = index + 2;
|
||||
const headline = `Line ${lineNumber}: ${precedingAction} ${additionalInfo}`;
|
||||
|
||||
|
||||
simulateEnhanced(headline, currentTick,
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, token0isWeth);
|
||||
previousRow = row;
|
||||
});
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken, floorLiquidity,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken, anchorLiquidity,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, discoveryLiquidity, token0isWeth, index, precedingAction);
|
||||
|
||||
// Add navigation buttons below
|
||||
const bottomNavDiv = document.createElement('div');
|
||||
bottomNavDiv.style.cssText = 'margin: 20px 0; text-align: center;';
|
||||
const prevButton = selectedIndex > 0 ?
|
||||
`<button onclick="navigateRow(-1)" style="padding: 10px 20px; font-size: 16px; margin-right: 20px;">
|
||||
← Previous Row (Line ${selectedIndex + 1})
|
||||
</button>` : '';
|
||||
const nextButton = selectedIndex < data.length - 1 ?
|
||||
`<button onclick="navigateRow(1)" style="padding: 10px 20px; font-size: 16px;">
|
||||
Next Row (Line ${selectedIndex + 3}) →
|
||||
</button>` : '';
|
||||
bottomNavDiv.innerHTML = prevButton + nextButton;
|
||||
document.getElementById('simulations').appendChild(bottomNavDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to navigate between rows
|
||||
function navigateRow(direction) {
|
||||
const currentIndex = getRowParameter();
|
||||
const newLineNumber = currentIndex + direction + 2; // Convert back to CSV line number
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('row', newLineNumber);
|
||||
window.location = url;
|
||||
}
|
||||
|
||||
// Uniswap V3 liquidity calculation functions
|
||||
function tickToPrice(tick) {
|
||||
return Math.pow(1.0001, tick);
|
||||
}
|
||||
|
||||
|
||||
function tickToSqrtPriceX96(tick) {
|
||||
return Math.sqrt(Math.pow(1.0001, tick)) * (2 ** 96);
|
||||
}
|
||||
|
||||
// Calculate token amounts from liquidity for a position
|
||||
function getAmountsForLiquidity(liquidity, tickLower, tickUpper, currentTick) {
|
||||
// Sort ticks to ensure tickLower < tickUpper
|
||||
if (tickLower > tickUpper) {
|
||||
[tickLower, tickUpper] = [tickUpper, tickLower];
|
||||
}
|
||||
|
||||
const sqrtRatioAX96 = tickToSqrtPriceX96(tickLower);
|
||||
const sqrtRatioBX96 = tickToSqrtPriceX96(tickUpper);
|
||||
const sqrtRatioX96 = tickToSqrtPriceX96(currentTick);
|
||||
|
||||
let amount0 = 0;
|
||||
let amount1 = 0;
|
||||
|
||||
const Q96 = 2 ** 96;
|
||||
|
||||
|
||||
if (currentTick < tickLower) {
|
||||
// Current price is below the range, position holds only token0
|
||||
// When position was created above current price, ETH was used as amount1
|
||||
// Use the amount1 formula to get the ETH that was deposited
|
||||
amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96;
|
||||
} else if (currentTick >= tickUpper) {
|
||||
// Current price is above the range, position holds only token1
|
||||
// amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96
|
||||
amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96;
|
||||
} else {
|
||||
// Current price is within the range
|
||||
// amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioX96) / (sqrtRatioX96 * sqrtRatioBX96) * Q96
|
||||
amount0 = liquidity * Q96 * (sqrtRatioBX96 - sqrtRatioX96) / sqrtRatioBX96 / sqrtRatioX96;
|
||||
// amount1 = liquidity * (sqrtRatioX96 - sqrtRatioAX96) / Q96
|
||||
amount1 = liquidity * (sqrtRatioX96 - sqrtRatioAX96) / Q96;
|
||||
}
|
||||
|
||||
return { amount0, amount1 };
|
||||
}
|
||||
|
||||
function priceToSqrtPrice(price) {
|
||||
return Math.sqrt(price);
|
||||
}
|
||||
|
|
@ -372,18 +513,6 @@
|
|||
}
|
||||
|
||||
// Debug logging
|
||||
if (positionName) {
|
||||
console.log(`${positionName} liquidity calculation:`, {
|
||||
token0Amount,
|
||||
token1Amount,
|
||||
tickRange: [tickLower, tickUpper],
|
||||
sqrtPriceLower,
|
||||
sqrtPriceUpper,
|
||||
sqrtPriceDiff: sqrtPriceUpper - sqrtPriceLower,
|
||||
liquidity,
|
||||
calculatedFrom
|
||||
});
|
||||
}
|
||||
|
||||
return liquidity;
|
||||
}
|
||||
|
|
@ -438,9 +567,9 @@
|
|||
}
|
||||
|
||||
function simulateEnhanced(precedingAction, currentTick,
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, token0isWeth) {
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken, floorLiquidity,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken, anchorLiquidity,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, discoveryLiquidity, token0isWeth, index, originalAction) {
|
||||
|
||||
// Position data structure with liquidity calculations
|
||||
const positions = {
|
||||
|
|
@ -450,9 +579,7 @@
|
|||
eth: floorEth,
|
||||
kraiken: floorKraiken,
|
||||
name: 'Floor',
|
||||
liquidity: token0isWeth ?
|
||||
calculateInvariantLiquidity(floorEth, floorKraiken, floorTickLower, floorTickUpper, 'Floor') :
|
||||
calculateInvariantLiquidity(floorKraiken, floorEth, floorTickLower, floorTickUpper, 'Floor')
|
||||
liquidity: floorLiquidity
|
||||
},
|
||||
anchor: {
|
||||
tickLower: anchorTickLower,
|
||||
|
|
@ -460,9 +587,7 @@
|
|||
eth: anchorEth,
|
||||
kraiken: anchorKraiken,
|
||||
name: 'Anchor (Shallow Pool)',
|
||||
liquidity: token0isWeth ?
|
||||
calculateInvariantLiquidity(anchorEth, anchorKraiken, anchorTickLower, anchorTickUpper, 'Anchor') :
|
||||
calculateInvariantLiquidity(anchorKraiken, anchorEth, anchorTickLower, anchorTickUpper, 'Anchor')
|
||||
liquidity: anchorLiquidity
|
||||
},
|
||||
discovery: {
|
||||
tickLower: discoveryTickLower,
|
||||
|
|
@ -470,35 +595,10 @@
|
|||
eth: discoveryEth,
|
||||
kraiken: discoveryKraiken,
|
||||
name: 'Discovery',
|
||||
liquidity: token0isWeth ?
|
||||
calculateInvariantLiquidity(discoveryEth, discoveryKraiken, discoveryTickLower, discoveryTickUpper, 'Discovery') :
|
||||
calculateInvariantLiquidity(discoveryKraiken, discoveryEth, discoveryTickLower, discoveryTickUpper, 'Discovery')
|
||||
liquidity: discoveryLiquidity
|
||||
}
|
||||
};
|
||||
|
||||
// Debug logging for all positions
|
||||
console.log('Position liquidity values:', {
|
||||
floor: {
|
||||
liquidity: positions.floor.liquidity,
|
||||
eth: floorEth,
|
||||
kraiken: floorKraiken,
|
||||
range: [floorTickLower, floorTickUpper]
|
||||
},
|
||||
anchor: {
|
||||
liquidity: positions.anchor.liquidity,
|
||||
eth: anchorEth,
|
||||
kraiken: anchorKraiken,
|
||||
range: [anchorTickLower, anchorTickUpper]
|
||||
},
|
||||
discovery: {
|
||||
liquidity: positions.discovery.liquidity,
|
||||
eth: discoveryEth,
|
||||
kraiken: discoveryKraiken,
|
||||
range: [discoveryTickLower, discoveryTickUpper]
|
||||
},
|
||||
currentTick: currentTick,
|
||||
token0isWeth: token0isWeth
|
||||
});
|
||||
|
||||
// Calculate total active liquidity
|
||||
const totalLiquidity = Object.values(positions).reduce((sum, pos) => sum + pos.liquidity, 0);
|
||||
|
|
@ -535,7 +635,7 @@
|
|||
scenarioContainer.appendChild(chartsContainer);
|
||||
|
||||
// Create summary panel
|
||||
const summaryPanel = createSummaryPanel(positions, currentTick, token0isWeth);
|
||||
const summaryPanel = createSummaryPanel(positions, currentTick, token0isWeth, originalAction || precedingAction, index);
|
||||
scenarioContainer.appendChild(summaryPanel);
|
||||
|
||||
// Add to page
|
||||
|
|
@ -565,18 +665,7 @@
|
|||
...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
|
||||
|
|
@ -663,14 +752,6 @@
|
|||
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
|
||||
console.log('Chart x-axis range:', { xAxisMin, xAxisMax });
|
||||
console.log('Bar positions:', barPositions);
|
||||
console.log('Bar widths:', barWidths);
|
||||
console.log('ETH values:', positionKeys.map(key => positions[key].eth));
|
||||
console.log('KRAIKEN values:', positionKeys.map(key => positions[key].kraiken));
|
||||
console.log('ETH trace y values (with min):', ethTrace.y);
|
||||
console.log('KRAIKEN trace y values (with min):', kraikenTrace.y);
|
||||
|
||||
// Calculate max values for proper y-axis alignment
|
||||
const maxEth = Math.max(...positionKeys.map(key => positions[key].eth));
|
||||
|
|
@ -692,17 +773,6 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Debug logging for very small or large values
|
||||
if (totalLiquidity < 1 || tickRange > 10000 || pos.tickLower > 180000) {
|
||||
console.log(`Warning: ${key} position has unusual values:`, {
|
||||
liquidity: pos.liquidity,
|
||||
tickRange: tickRange,
|
||||
totalLiquidity: totalLiquidity,
|
||||
ticks: [pos.tickLower, pos.tickUpper],
|
||||
lowerMultiple: pos.lowerMultiple,
|
||||
upperMultiple: pos.upperMultiple
|
||||
});
|
||||
}
|
||||
|
||||
// Create a filled area for each position to show its exact range
|
||||
// Cap display coordinates to keep within visible range
|
||||
|
|
@ -1027,7 +1097,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
function createSummaryPanel(positions, currentTick, token0isWeth) {
|
||||
function createSummaryPanel(positions, currentTick, token0isWeth, precedingAction, index) {
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'summary-panel';
|
||||
|
||||
|
|
@ -1043,16 +1113,17 @@
|
|||
const totalEth = Object.values(positions).reduce((sum, pos) => sum + pos.eth, 0);
|
||||
const totalKraiken = Object.values(positions).reduce((sum, pos) => sum + pos.kraiken, 0);
|
||||
const totalUniV3Liquidity = Object.values(positions).reduce((sum, pos) => sum + pos.liquidity, 0);
|
||||
|
||||
|
||||
// Add total summary
|
||||
const totalItem = document.createElement('div');
|
||||
totalItem.className = 'summary-item';
|
||||
totalItem.innerHTML = `
|
||||
const totalHtml = `
|
||||
<strong>Total Portfolio</strong><br>
|
||||
Token ETH: ${totalEth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||
Token KRAIKEN: ${totalKraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||
Uniswap V3 Liquidity: ${totalUniV3Liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}
|
||||
ETH: ${totalEth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||
KRAIKEN: ${totalKraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||
Uniswap V3 Liquidity: ${totalUniV3Liquidity.toExponential(2)}
|
||||
`;
|
||||
totalItem.innerHTML = totalHtml;
|
||||
grid.appendChild(totalItem);
|
||||
|
||||
// Add position summaries
|
||||
|
|
@ -1070,8 +1141,8 @@
|
|||
<strong>${pos.name} Position</strong><br>
|
||||
ETH: ${pos.eth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||
KRAIKEN: ${pos.kraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||
Uniswap V3 Liquidity: ${pos.liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} (${liquidityPercent}%)<br>
|
||||
Range: ${lowerMultiple.toFixed(3)}x - ${upperMultiple.toFixed(3)}x
|
||||
Liquidity: ${pos.liquidity.toExponential(2)} (${liquidityPercent}%)<br>
|
||||
Ticks: [${pos.tickLower.toLocaleString()}, ${pos.tickUpper.toLocaleString()}]
|
||||
`;
|
||||
grid.appendChild(item);
|
||||
});
|
||||
|
|
@ -1079,10 +1150,26 @@
|
|||
// Add current price info
|
||||
const priceItem = document.createElement('div');
|
||||
priceItem.className = 'summary-item';
|
||||
|
||||
// Calculate current price
|
||||
const currentPrice = tickToPrice(currentTick);
|
||||
let ethPriceInKraiken, kraikenPriceInEth;
|
||||
|
||||
if (token0isWeth) {
|
||||
// price = KRAIKEN/ETH
|
||||
ethPriceInKraiken = currentPrice;
|
||||
kraikenPriceInEth = 1 / currentPrice;
|
||||
} else {
|
||||
// price = ETH/KRAIKEN
|
||||
kraikenPriceInEth = currentPrice;
|
||||
ethPriceInKraiken = 1 / currentPrice;
|
||||
}
|
||||
|
||||
priceItem.innerHTML = `
|
||||
<strong>Current Price</strong><br>
|
||||
Tick: ${currentTick}<br>
|
||||
<small>Price line shown in red</small>
|
||||
Tick: ${currentTick.toLocaleString()}<br>
|
||||
1 ETH = ${ethPriceInKraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} KRAIKEN<br>
|
||||
1 KRAIKEN = ${kraikenPriceInEth.toExponential(3)} ETH
|
||||
`;
|
||||
grid.appendChild(priceItem);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue