293 lines
13 KiB
HTML
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>
|
|
|