harb/onchain/analysis/scenario-visualizer.html
2025-07-06 11:20:35 +02:00

293 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Liquidity Position Simulator</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
textarea {
width: 100%;
box-sizing: border-box;
height: 100px;
}
button {
margin-top: 10px;
}
.chart-container {
margin-bottom: 20px;
}
.chart-container h3 {
margin: 0;
}
</style>
</head>
<body>
<h2>Liquidity Position Simulator</h2>
<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>
// Auto-load CSV data on page load
document.addEventListener('DOMContentLoaded', function() {
loadCSVData();
});
function loadCSVData() {
const statusDiv = document.getElementById('status');
statusDiv.textContent = 'Loading profitable scenario data...';
// Try to load the CSV file generated by the analysis script
fetch('./profitable_scenario.csv')
.then(response => {
if (!response.ok) {
throw new Error('CSV file not found');
}
return response.text();
})
.then(csvText => {
statusDiv.textContent = 'Profitable scenario data loaded successfully!';
statusDiv.style.color = 'green';
const data = parseCSV(csvText);
simulateCSVData(data);
})
.catch(error => {
statusDiv.innerHTML = `
<div style="color: orange;">
<strong>Cannot load CSV automatically due to browser security restrictions.</strong><br>
<br>
<strong>Solution 1:</strong> Run a local server:<br>
<code>cd analysis && python3 -m http.server 8000</code><br>
Then open: <a href="http://localhost:8000/scenario-visualizer.html">http://localhost:8000/scenario-visualizer.html</a><br>
<br>
<strong>Solution 2:</strong> Use manual input mode below<br>
<br>
<em>If no CSV exists, run: forge script analysis/SimpleAnalysis.s.sol --ffi</em>
</div>
`;
console.log('CSV load error:', error);
});
}
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');
const headers = lines[0].split(',').map(h => h.trim());
const data = lines.slice(1).map(line => {
const values = line.split(',').map(v => v.trim());
const entry = {};
headers.forEach((header, index) => {
entry[header] = values[index];
});
return entry;
});
return data;
}
function parseAndSimulateCSV() {
const csvInput = document.getElementById('csvInput').value;
const data = parseCSV(csvInput);
simulateCSVData(data);
// Clear input field after processing
document.getElementById('csvInput').value = '';
}
function simulateCSVData(data) {
let previousRow = null;
data.forEach(row => {
const precedingAction = row.precedingAction;
const currentTick = parseFloat(row.currentTick);
const floorTickLower = parseFloat(row.floorTickLower);
const floorTickUpper = parseFloat(row.floorTickUpper);
const floorEth = parseFloat(row.floorEth) / 1e18;
const floorHarb = parseFloat(row.floorHarb) / 1e18;
const anchorTickLower = parseFloat(row.anchorTickLower);
const anchorTickUpper = parseFloat(row.anchorTickUpper);
const anchorEth = parseFloat(row.anchorEth) / 1e18;
const anchorHarb = parseFloat(row.anchorHarb) / 1e18;
const discoveryTickLower = parseFloat(row.discoveryTickLower);
const discoveryTickUpper = parseFloat(row.discoveryTickUpper);
const discoveryEth = parseFloat(row.discoveryEth) / 1e18;
const discoveryHarb = parseFloat(row.discoveryHarb) / 1e18;
let actionAmount = '';
let additionalInfo = '';
if (previousRow) {
const prevFloorEth = parseFloat(previousRow.floorEth) / 1e18;
const prevFloorHarb = parseFloat(previousRow.floorHarb) / 1e18;
const prevAnchorEth = parseFloat(previousRow.anchorEth) / 1e18;
const prevAnchorHarb = parseFloat(previousRow.anchorHarb) / 1e18;
const prevDiscoveryEth = parseFloat(previousRow.discoveryEth) / 1e18;
const prevDiscoveryHarb = parseFloat(previousRow.discoveryHarb) / 1e18;
const ethDifference = (floorEth + anchorEth + discoveryEth) - (prevFloorEth + prevAnchorEth + prevDiscoveryEth);
const harbDifference = (floorHarb + anchorHarb + discoveryHarb) - (prevFloorHarb + prevAnchorHarb + prevDiscoveryHarb);
if (precedingAction.startsWith('buy')) {
actionAmount = `${precedingAction} ETH`;
additionalInfo = `(${Math.abs(harbDifference).toFixed(18)} HARB bought)`;
} else if (precedingAction.startsWith('sell')) {
actionAmount = `${precedingAction} HARB`;
additionalInfo = `(${Math.abs(ethDifference).toFixed(18)} ETH bought)`;
} else {
actionAmount = precedingAction;
}
}
const ethFormatted = (floorEth + anchorEth + discoveryEth).toFixed(18);
const harbFormatted = (floorHarb + anchorHarb + discoveryHarb).toFixed(18);
const headline = `${precedingAction} ${additionalInfo} | Total ETH: ${ethFormatted}, Total HARB: ${harbFormatted}`;
simulate(headline, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb);
previousRow = row;
});
}
function simulate(precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb) {
var tick_starts = [discoveryTickLower, anchorTickLower, floorTickLower];
var tick_ends = [discoveryTickUpper, anchorTickUpper, floorTickUpper];
var liquidity = [
discoveryEth + discoveryHarb,
anchorEth + anchorHarb,
floorEth + floorHarb
];
var eth = [discoveryEth, anchorEth, floorEth];
var harb = [discoveryHarb, anchorHarb, floorHarb];
var widths = tick_ends.map((end, i) => end - tick_starts[i]);
var data = [
{
x: tick_starts.map((start, i) => start + widths[i] / 2),
y: harb,
width: widths,
type: 'bar',
marker: {
color: ['green', 'red', 'blue'],
opacity: 0.6
},
text: harb.map((h, i) => `ETH: ${eth[i].toFixed(18)}<br>HARB: ${h.toFixed(18)}<br>Range: [${tick_starts[i]}, ${tick_ends[i]}]`),
hoverinfo: 'text',
name: 'Liquidity'
},
{
x: [currentTick, currentTick],
y: [0, Math.max(...harb) * 1.1],
mode: 'lines',
line: {
color: 'black',
width: 2,
dash: 'dash'
},
name: 'Current Tick',
hoverinfo: 'x',
text: [`Current Price: ${currentTick}`]
}
];
var layout = {
title: `Liquidity, ETH, and HARB Distribution - ${precedingAction}`,
xaxis: {
title: 'Ticks',
tickvals: tick_starts.concat([currentTick], tick_ends),
ticktext: tick_starts.map(String).concat([`${currentTick}\n(Current Price)`], tick_ends.map(String))
},
yaxis: {
title: 'Liquidity (HARB)'
}
};
var newDiv = document.createElement('div');
newDiv.className = 'chart-container';
var newHeader = document.createElement('h3');
newHeader.textContent = precedingAction;
var newChart = document.createElement('div');
newChart.style.width = '100%';
newChart.style.height = '600px';
var toggleButton = document.createElement('button');
toggleButton.textContent = 'Toggle ETH/HARB';
toggleButton.setAttribute('data-isethdisplayed', 'false');
toggleButton.onclick = function() { toggleData(newChart, eth, harb, tick_starts, tick_ends, widths, currentTick, precedingAction, toggleButton); };
newDiv.appendChild(newHeader);
newDiv.appendChild(toggleButton);
newDiv.appendChild(newChart);
document.getElementById('simulations').appendChild(newDiv);
Plotly.newPlot(newChart, data, layout);
}
function toggleData(chart, eth, harb, tick_starts, tick_ends, widths, currentTick, precedingAction, button) {
var isETHDisplayed = button.getAttribute('data-isethdisplayed') === 'true';
var currentData = isETHDisplayed ? harb : eth;
var data = [
{
x: tick_starts.map((start, i) => start + widths[i] / 2),
y: currentData,
width: widths,
type: 'bar',
marker: {
color: ['green', 'red', 'blue'],
opacity: 0.6
},
text: currentData.map((value, i) => `ETH: ${eth[i].toFixed(18)}<br>HARB: ${harb[i].toFixed(18)}<br>Range: [${tick_starts[i]}, ${tick_ends[i]}]`),
hoverinfo: 'text',
name: 'Liquidity'
},
{
x: [currentTick, currentTick],
y: [0, Math.max(...currentData) * 1.1],
mode: 'lines',
line: {
color: 'black',
width: 2,
dash: 'dash'
},
name: 'Current Tick',
hoverinfo: 'x',
text: [`Current Price: ${currentTick}`]
}
];
var layout = {
title: `Liquidity, ${isETHDisplayed ? 'HARB' : 'ETH'}, and ${isETHDisplayed ? 'ETH' : 'HARB'} Distribution - ${precedingAction}`,
xaxis: {
title: 'Ticks',
tickvals: tick_starts.concat([currentTick], tick_ends),
ticktext: tick_starts.map(String).concat([`${currentTick}\n(Current Price)`], tick_ends.map(String))
},
yaxis: {
title: `Liquidity (${isETHDisplayed ? 'HARB' : 'ETH'})`
}
};
button.setAttribute('data-isethdisplayed', (!isETHDisplayed).toString());
Plotly.react(chart, data, layout); // Use Plotly.react to update the existing chart
}
</script>
</body>
</html>