Skip to content

Instantly share code, notes, and snippets.

@yongkangc
Created March 24, 2025 02:12
Show Gist options
  • Save yongkangc/80e98ce2261babc746f0458f0c321a42 to your computer and use it in GitHub Desktop.
Save yongkangc/80e98ce2261babc746f0458f0c321a42 to your computer and use it in GitHub Desktop.
auction
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bond Auction Simulator</title>
<style>
:root {
--primary-color: #1e88e5;
--primary-dark: #1565c0;
--secondary-color: #26a69a;
--secondary-dark: #00897b;
--accent-color: #ff9800;
--success-color: #43a047;
--danger-color: #e53935;
--light-bg: #f5f9ff;
--card-bg: #ffffff;
--text-color: #2e3440;
--text-light: #4c566a;
--border-radius: 10px;
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
--transition: all 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--light-bg);
padding: 30px;
max-width: 1300px;
margin: 0 auto;
}
.container {
display: flex;
flex-direction: column;
gap: 25px;
}
.header {
text-align: center;
margin-bottom: 10px;
}
h1 {
font-size: 2.2rem;
margin-bottom: 10px;
color: var(--primary-dark);
font-weight: 600;
}
h2 {
font-size: 1.5rem;
margin-bottom: 15px;
color: var(--primary-dark);
font-weight: 600;
}
h3 {
font-size: 1.2rem;
margin-bottom: 10px;
color: var(--primary-dark);
font-weight: 500;
}
h4 {
font-size: 1.1rem;
margin-bottom: 10px;
color: var(--text-color);
font-weight: 600;
}
.intro {
font-size: 1.1rem;
max-width: 900px;
margin: 0 auto 20px;
color: var(--text-light);
text-align: center;
}
.card {
background: var(--card-bg);
border-radius: var(--border-radius);
padding: 25px;
box-shadow: var(--box-shadow);
transition: var(--transition);
}
.card:hover {
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
}
.buttons-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.control-info {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: space-between;
align-items: center;
}
.issue-details {
background-color: #f0f7ff;
padding: 10px 15px;
border-radius: 8px;
font-weight: 500;
border-left: 4px solid var(--primary-color);
}
button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
font-weight: 500;
transition: var(--transition);
min-width: 130px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
button:hover {
background-color: var(--primary-dark);
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
button:disabled {
background-color: #b0bec5;
cursor: not-allowed;
transform: none;
}
button.reset {
background-color: var(--danger-color);
}
button.reset:hover:not(:disabled) {
background-color: #c62828;
}
button.auto {
background-color: var(--secondary-color);
}
button.auto:hover:not(:disabled) {
background-color: var(--secondary-dark);
}
.auction-container {
display: flex;
flex-wrap: wrap;
gap: 25px;
}
.auction-type {
flex: 1;
min-width: 500px;
position: relative;
}
.auction-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.auction-tag {
font-size: 14px;
padding: 5px 10px;
border-radius: 4px;
margin-left: 10px;
background-color: var(--primary-color);
color: white;
font-weight: 500;
}
.english-tag {
background-color: var(--secondary-color);
}
.auction-description {
color: var(--text-light);
margin-bottom: 20px;
font-size: 15px;
}
.yield-axis {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
color: var(--text-light);
}
.axis-labels {
display: flex;
justify-content: space-between;
padding: 0 5px;
}
.axis-tick {
position: relative;
text-align: center;
color: var(--text-light);
font-size: 12px;
width: 40px;
transform: translateX(-50%);
}
.axis-tick::before {
content: "";
position: absolute;
height: 5px;
width: 1px;
background-color: #ccc;
bottom: 100%;
left: 50%;
}
.auction-visualization {
height: 280px;
position: relative;
background-color: #f8faff;
border-radius: 8px;
border: 1px solid #e3e8ef;
overflow: hidden;
margin-bottom: 20px;
}
.bid {
position: absolute;
height: 32px;
background-color: var(--primary-color);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 13px;
font-weight: 500;
transition: all 0.5s ease;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 0 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.bid:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
z-index: 10;
}
.bid.accepted {
background-color: var(--success-color);
}
.bid.rejected {
background-color: var(--danger-color);
}
#english-visualization .bid {
background-color: var(--secondary-color);
}
#english-visualization .bid.accepted {
background-color: var(--success-color);
}
#english-visualization .bid.rejected {
background-color: var(--danger-color);
}
.bid-tracker {
height: 100%;
width: 2px;
background-color: var(--danger-color);
position: absolute;
transition: left 0.5s ease;
z-index: 5;
}
.tracker-label {
position: absolute;
top: 10px;
background-color: var(--danger-color);
color: white;
padding: 5px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
transform: translateX(-50%);
white-space: nowrap;
}
.results {
background-color: #f5f8ff;
border-radius: 8px;
padding: 20px;
min-height: 100px;
border-left: 4px solid var(--primary-color);
}
#english-results {
border-left: 4px solid var(--secondary-color);
}
.results p {
margin-bottom: 8px;
font-size: 15px;
}
.results em {
color: var(--text-light);
}
.results-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 15px;
}
.result-item {
display: flex;
flex-direction: column;
}
.result-label {
font-size: 14px;
color: var(--text-light);
margin-bottom: 3px;
}
.result-value {
font-size: 20px;
font-weight: 600;
color: var(--primary-dark);
}
#english-results .result-value {
color: var(--secondary-dark);
}
.result-special {
color: var(--success-color);
}
.info-panel {
margin-top: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
box-shadow: var(--box-shadow);
border-radius: var(--border-radius);
overflow: hidden;
}
th, td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #e3e8ef;
}
th {
background-color: #f0f7ff;
font-weight: 600;
color: var(--primary-dark);
}
tr:last-child td {
border-bottom: none;
}
tr:nth-child(even) {
background-color: #f8faff;
}
.highlight-dutch {
background-color: rgba(30, 136, 229, 0.05);
}
.highlight-english {
background-color: rgba(38, 166, 154, 0.05);
}
.tooltip {
position: absolute;
padding: 8px 12px;
background-color: rgba(46, 52, 64, 0.95);
color: white;
border-radius: 4px;
font-size: 13px;
max-width: 250px;
z-index: 100;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
white-space: pre-line;
}
@media (max-width: 1100px) {
.auction-container {
flex-direction: column;
}
.auction-type {
min-width: auto;
}
.results-grid {
grid-template-columns: 1fr;
gap: 10px;
}
body {
padding: 15px;
}
}
@media (max-width: 600px) {
.control-info {
flex-direction: column;
align-items: flex-start;
}
.buttons-row {
width: 100%;
justify-content: space-between;
}
button {
min-width: 0;
flex: 1;
padding: 10px;
font-size: 14px;
}
.auction-visualization {
height: 320px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Bond Auction Simulator</h1>
<p class="intro">Visualizing how Dutch (single-price) and English (multiple-price) auctions work in bond markets</p>
</div>
<div class="card controls">
<h3>Simulation Controls</h3>
<div class="control-info">
<div class="issue-details">
<div>Bond issuance: $10 billion U.S. Treasury 10-year notes</div>
<div>Yield range: 3.0% to 4.0%</div>
</div>
<div class="buttons-row">
<button id="init-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 12H3"/><path d="M16 6H3"/><path d="M16 18H3"/><circle cx="18.5" cy="6.5" r="2.5"/><circle cx="18.5" cy="17.5" r="2.5"/></svg>
Initialize
</button>
<button id="step-btn" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
Step
</button>
<button id="auto-btn" class="auto" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Auto-Run
</button>
<button id="reset-btn" class="reset" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 2v6h6"/><path d="M2.66 15.57a10 10 0 1 0-1.66-8.06"/></svg>
Reset
</button>
</div>
</div>
</div>
<div class="auction-container">
<div class="auction-type card">
<div class="auction-header">
<h2>Dutch Auction</h2>
<span class="auction-tag">Single-Price</span>
</div>
<p class="auction-description">All successful bidders pay the same yield (the highest accepted yield)</p>
<div class="yield-axis">
<span>← Lower Yield (3.0%)</span>
<span>Higher Yield (4.0%) →</span>
</div>
<div id="dutch-visualization" class="auction-visualization">
<div class="bid-tracker"></div>
<div class="axis-labels">
<span class="axis-tick" style="left: 0%">3.0%</span>
<span class="axis-tick" style="left: 25%">3.25%</span>
<span class="axis-tick" style="left: 50%">3.5%</span>
<span class="axis-tick" style="left: 75%">3.75%</span>
<span class="axis-tick" style="left: 100%">4.0%</span>
</div>
</div>
<div id="dutch-results" class="results">
<em>Initialize the auction to see results...</em>
</div>
</div>
<div class="auction-type card">
<div class="auction-header">
<h2>English Auction</h2>
<span class="auction-tag english-tag">Multiple-Price</span>
</div>
<p class="auction-description">Each successful bidder pays their own bid yield</p>
<div class="yield-axis">
<span>← Lower Yield (3.0%)</span>
<span>Higher Yield (4.0%) →</span>
</div>
<div id="english-visualization" class="auction-visualization">
<div class="bid-tracker"></div>
<div class="axis-labels">
<span class="axis-tick" style="left: 0%">3.0%</span>
<span class="axis-tick" style="left: 25%">3.25%</span>
<span class="axis-tick" style="left: 50%">3.5%</span>
<span class="axis-tick" style="left: 75%">3.75%</span>
<span class="axis-tick" style="left: 100%">4.0%</span>
</div>
</div>
<div id="english-results" class="results">
<em>Initialize the auction to see results...</em>
</div>
</div>
</div>
<div class="card info-panel">
<h3>Key Auction Characteristics</h3>
<table>
<tr>
<th>Feature</th>
<th>Dutch Auction (Single-Price)</th>
<th>English Auction (Multiple-Price)</th>
</tr>
<tr class="highlight-dutch">
<td>Price Mechanism</td>
<td>Starts with low yield, accepts bids in order of increasing yield until fully subscribed</td>
<td>Starts with high yield, bidders compete by offering progressively lower yields</td>
</tr>
<tr class="highlight-english">
<td>Pricing Outcome</td>
<td>All successful bidders pay the clearing yield (highest accepted)</td>
<td>Each successful bidder pays their own bid yield</td>
</tr>
<tr class="highlight-dutch">
<td>Bidding Strategy</td>
<td>Bid true valuation (reduced risk of "winner's curse")</td>
<td>May strategically overbid to ensure allocation</td>
</tr>
<tr class="highlight-english">
<td>Transparency</td>
<td>Bids typically sealed until close</td>
<td>Bids are open and visible to all participants</td>
</tr>
<tr class="highlight-dutch">
<td>Real World Example</td>
<td>U.S. Treasury auctions, Netherlands government bonds</td>
<td>Some UK government bonds (gilts)</td>
</tr>
</table>
</div>
</div>
<div class="tooltip" id="tooltip"></div>
<script>
// Configuration
const TOTAL_BOND_AMOUNT = 10; // $10 billion
const MIN_YIELD = 3.0;
const MAX_YIELD = 4.0;
const NUM_BIDDERS = 8;
// Auction state
let dutchBids = [];
let englishBids = [];
let currentStep = 0;
let autoRunInterval = null;
let dutchClearingYield = 0;
let dutchTotalAccepted = 0;
let englishTotalAccepted = 0;
// DOM elements
const initBtn = document.getElementById('init-btn');
const stepBtn = document.getElementById('step-btn');
const autoBtn = document.getElementById('auto-btn');
const resetBtn = document.getElementById('reset-btn');
const dutchVis = document.getElementById('dutch-visualization');
const englishVis = document.getElementById('english-visualization');
const dutchResults = document.getElementById('dutch-results');
const englishResults = document.getElementById('english-results');
const tooltip = document.getElementById('tooltip');
// Initialize event listeners
initBtn.addEventListener('click', initializeAuction);
stepBtn.addEventListener('click', stepForward);
autoBtn.addEventListener('click', toggleAutoRun);
resetBtn.addEventListener('click', resetAuction);
// Tooltip functionality
document.addEventListener('mousemove', (e) => {
tooltip.style.left = `${e.pageX + 15}px`;
tooltip.style.top = `${e.pageY + 15}px`;
});
function showTooltip(content) {
tooltip.textContent = content;
tooltip.style.opacity = 1;
}
function hideTooltip() {
tooltip.style.opacity = 0;
}
// Helper functions
function randomInRange(min, max) {
return Math.random() * (max - min) + min;
}
function formatCurrency(amount) {
return `$${amount.toFixed(2)}B`;
}
function formatYield(yieldValue) {
return `${yieldValue.toFixed(2)}%`;
}
function mapYieldToPosition(yield) {
const range = MAX_YIELD - MIN_YIELD;
const percentage = (yield - MIN_YIELD) / range;
return percentage * 100;
}
// Auction functions
function initializeAuction() {
resetAuction();
// Generate institutional investor names for more realistic scenario
const investorTypes = [
"Pension Fund", "Asset Manager", "Hedge Fund", "Insurance Co.",
"Sovereign Fund", "Bank", "Investment Fund", "Corp. Treasury"
];
// Create bids for Dutch auction (sorted by yield ascending)
for (let i = 0; i < NUM_BIDDERS; i++) {
const bidAmount = randomInRange(0.8, 2.5);
const bidYield = randomInRange(MIN_YIELD, MAX_YIELD);
dutchBids.push({
id: `dutch-${i}`,
bidder: `${investorTypes[i]}`,
amount: bidAmount,
yield: bidYield,
status: 'pending'
});
}
dutchBids.sort((a, b) => a.yield - b.yield);
// Create bids for English auction (start with high yields)
for (let i = 0; i < NUM_BIDDERS; i++) {
const bidAmount = randomInRange(0.8, 2.5);
// English auctions typically start with higher yields (lower prices)
const bidYield = randomInRange(MIN_YIELD + 0.5, MAX_YIELD);
englishBids.push({
id: `english-${i}`,
bidder: `${investorTypes[i]}`,
amount: bidAmount,
yield: bidYield,
status: 'pending',
originalYield: bidYield // Track original yield for English auction
});
}
englishBids.sort((a, b) => b.yield - a.yield);
// Render initial state
renderDutchAuction();
renderEnglishAuction();
// Update button states
initBtn.disabled = true;
stepBtn.disabled = false;
autoBtn.disabled = false;
resetBtn.disabled = false;
// Update results
dutchResults.innerHTML = '<p>Dutch auction initialized with 8 institutional bidders.</p><p>Click "Step" to conduct the auction and determine the clearing yield.</p>';
englishResults.innerHTML = '<p>English auction initialized with 8 institutional bidders.</p><p>Click "Step" to simulate bidders lowering their yields to compete for allocation.</p>';
}
function stepForward() {
currentStep++;
// Process Dutch auction (one-step process)
if (currentStep === 1) {
processDutchAuction();
}
// Process English auction (multi-step process, one bidder per step)
if (currentStep <= englishBids.length) {
processEnglishAuctionStep();
}
// Check if auction is complete
if (currentStep > englishBids.length) {
stepBtn.disabled = true;
if (autoRunInterval) {
clearInterval(autoRunInterval);
autoRunInterval = null;
autoBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto-Run';
}
}
}
function processDutchAuction() {
let totalAmount = 0;
let lastAcceptedBid = null;
// Process bids in order of yield (lowest to highest)
for (let i = 0; i < dutchBids.length; i++) {
const bid = dutchBids[i];
if (totalAmount + bid.amount <= TOTAL_BOND_AMOUNT) {
bid.status = 'accepted';
totalAmount += bid.amount;
lastAcceptedBid = bid;
} else {
// Partial allocation for the last bid that pushes over the limit
if (totalAmount < TOTAL_BOND_AMOUNT) {
bid.partialAmount = TOTAL_BOND_AMOUNT - totalAmount;
bid.status = 'accepted';
bid.isPartial = true;
totalAmount = TOTAL_BOND_AMOUNT;
lastAcceptedBid = bid;
} else {
bid.status = 'rejected';
}
}
}
// Set the clearing yield
if (lastAcceptedBid) {
dutchClearingYield = lastAcceptedBid.yield;
dutchTotalAccepted = totalAmount;
}
// Update visualization and results
renderDutchAuction();
updateDutchResults();
}
function processEnglishAuctionStep() {
// In each step, one bidder lowers their yield bid
const stepIndex = currentStep - 1;
if (stepIndex < englishBids.length) {
const bid = englishBids[stepIndex];
// English auction dynamics: bidders lower yields strategically based on current allocation
// Bidders aim for a yield that's competitive enough to get allocation but not too low
// Find current lowest yield that would get allocation
let currentLowestAcceptedYield = MAX_YIELD;
let totalRequestedAmount = 0;
englishBids.forEach(b => {
totalRequestedAmount += b.amount;
if (b.status === 'accepted' && b.yield < currentLowestAcceptedYield) {
currentLowestAcceptedYield = b.yield;
}
});
// Strategic behavior - bid based on market competition
if (totalRequestedAmount > TOTAL_BOND_AMOUNT * 1.5) {
// Highly competitive - bid more aggressively
bid.yield = Math.max(MIN_YIELD,
currentLowestAcceptedYield - randomInRange(0.05, 0.2));
} else {
// Less competitive - bid more conservatively
bid.yield = Math.max(MIN_YIELD,
Math.min(bid.originalYield - randomInRange(0.1, 0.4),
currentLowestAcceptedYield + randomInRange(0, 0.1)));
}
// Re-sort bids by yield (lowest to highest for allocation purposes)
englishBids.sort((a, b) => a.yield - b.yield);
// Process allocation
let totalAmount = 0;
englishTotalAccepted = 0;
for (let i = 0; i < englishBids.length; i++) {
const currentBid = englishBids[i];
if (totalAmount + currentBid.amount <= TOTAL_BOND_AMOUNT) {
currentBid.status = 'accepted';
totalAmount += currentBid.amount;
englishTotalAccepted = totalAmount;
} else {
// Partial allocation for the last bid that pushes over the limit
if (totalAmount < TOTAL_BOND_AMOUNT) {
currentBid.partialAmount = TOTAL_BOND_AMOUNT - totalAmount;
currentBid.status = 'accepted';
currentBid.isPartial = true;
totalAmount = TOTAL_BOND_AMOUNT;
englishTotalAccepted = totalAmount;
} else {
currentBid.status = 'rejected';
}
}
}
// Update visualization and results
renderEnglishAuction();
updateEnglishResults();
}
}
function toggleAutoRun() {
if (autoRunInterval) {
clearInterval(autoRunInterval);
autoRunInterval = null;
autoBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto-Run';
} else {
autoRunInterval = setInterval(() => {
if (currentStep <= englishBids.length) {
stepForward();
} else {
clearInterval(autoRunInterval);
autoRunInterval = null;
autoBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto-Run';
}
}, 1000);
autoBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg> Stop';
}
}
function resetAuction() {
currentStep = 0;
dutchBids = [];
englishBids = [];
dutchClearingYield = 0;
dutchTotalAccepted = 0;
englishTotalAccepted = 0;
// Clear visualizations
dutchVis.innerHTML = `
<div class="bid-tracker"></div>
<div class="axis-labels">
<span class="axis-tick" style="left: 0%">3.0%</span>
<span class="axis-tick" style="left: 25%">3.25%</span>
<span class="axis-tick" style="left: 50%">3.5%</span>
<span class="axis-tick" style="left: 75%">3.75%</span>
<span class="axis-tick" style="left: 100%">4.0%</span>
</div>
`;
englishVis.innerHTML = `
<div class="bid-tracker"></div>
<div class="axis-labels">
<span class="axis-tick" style="left: 0%">3.0%</span>
<span class="axis-tick" style="left: 25%">3.25%</span>
<span class="axis-tick" style="left: 50%">3.5%</span>
<span class="axis-tick" style="left: 75%">3.75%</span>
<span class="axis-tick" style="left: 100%">4.0%</span>
</div>
`;
// Reset results
dutchResults.innerHTML = '<em>Initialize the auction to see results...</em>';
englishResults.innerHTML = '<em>Initialize the auction to see results...</em>';
// Update button states
initBtn.disabled = false;
stepBtn.disabled = true;
autoBtn.disabled = true;
resetBtn.disabled = true;
if (autoRunInterval) {
clearInterval(autoRunInterval);
autoRunInterval = null;
autoBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto-Run';
}
}
function renderDutchAuction() {
// Prepare bid tracker and labels container
dutchVis.innerHTML = `
<div class="bid-tracker"></div>
<div class="axis-labels">
<span class="axis-tick" style="left: 0%">3.0%</span>
<span class="axis-tick" style="left: 25%">3.25%</span>
<span class="axis-tick" style="left: 50%">3.5%</span>
<span class="axis-tick" style="left: 75%">3.75%</span>
<span class="axis-tick" style="left: 100%">4.0%</span>
</div>
`;
// Add bids to visualization
dutchBids.forEach((bid, index) => {
const bidElement = document.createElement('div');
bidElement.className = `bid ${bid.status}`;
bidElement.id = bid.id;
const leftPosition = mapYieldToPosition(bid.yield);
const amountToShow = bid.isPartial ? bid.partialAmount : bid.amount;
bidElement.style.left = `${leftPosition}%`;
bidElement.style.width = `${Math.min(amountToShow * 10, 30)}%`;
bidElement.style.bottom = `${index * 36 + 30}px`;
// Display text based on available space
const displayAmount = bid.isPartial
? `${formatCurrency(bid.partialAmount)} (partial)`
: formatCurrency(bid.amount);
bidElement.textContent = `${bid.bidder}: ${displayAmount}`;
// Detailed tooltip
const tooltipContent = `${bid.bidder}\nAmount: ${formatCurrency(bid.amount)}${bid.isPartial ? ' (Partial allocation: ' + formatCurrency(bid.partialAmount) + ')' : ''}\nYield: ${formatYield(bid.yield)}\nStatus: ${bid.status}`;
bidElement.addEventListener('mouseenter', () => showTooltip(tooltipContent));
bidElement.addEventListener('mouseleave', hideTooltip);
dutchVis.appendChild(bidElement);
});
// Show clearing yield line if auction has completed
if (dutchClearingYield > 0) {
const tracker = dutchVis.querySelector('.bid-tracker');
const leftPosition = mapYieldToPosition(dutchClearingYield);
tracker.style.left = `${leftPosition}%`;
// Add label to the tracker
const trackerLabel = document.createElement('div');
trackerLabel.className = 'tracker-label';
trackerLabel.textContent = `Clearing: ${formatYield(dutchClearingYield)}`;
trackerLabel.style.left = `${leftPosition}%`;
tracker.appendChild(trackerLabel);
}
}
function renderEnglishAuction() {
// Prepare bid tracker and labels container
englishVis.innerHTML = `
<div class="bid-tracker"></div>
<div class="axis-labels">
<span class="axis-tick" style="left: 0%">3.0%</span>
<span class="axis-tick" style="left: 25%">3.25%</span>
<span class="axis-tick" style="left: 50%">3.5%</span>
<span class="axis-tick" style="left: 75%">3.75%</span>
<span class="axis-tick" style="left: 100%">4.0%</span>
</div>
`;
// Add bids to visualization
englishBids.forEach((bid, index) => {
const bidElement = document.createElement('div');
bidElement.className = `bid ${bid.status}`;
bidElement.id = bid.id;
const leftPosition = mapYieldToPosition(bid.yield);
const amountToShow = bid.isPartial ? bid.partialAmount : bid.amount;
bidElement.style.left = `${leftPosition}%`;
bidElement.style.width = `${Math.min(amountToShow * 10, 30)}%`;
bidElement.style.bottom = `${index * 36 + 30}px`;
// Display text based on available space
const displayAmount = bid.isPartial
? `${formatCurrency(bid.partialAmount)} (partial)`
: formatCurrency(bid.amount);
bidElement.textContent = `${bid.bidder}: ${displayAmount}`;
// Show yield reduction if this is not the original yield
const yieldChange = bid.originalYield - bid.yield;
const tooltipContent = `${bid.bidder}\nAmount: ${formatCurrency(bid.amount)}${bid.isPartial ? ' (Partial allocation: ' + formatCurrency(bid.partialAmount) + ')' : ''}\nCurrent Yield: ${formatYield(bid.yield)}\nOriginal Yield: ${formatYield(bid.originalYield)}\nYield Reduction: ${formatYield(yieldChange)}\nStatus: ${bid.status}`;
bidElement.addEventListener('mouseenter', () => showTooltip(tooltipContent));
bidElement.addEventListener('mouseleave', hideTooltip);
englishVis.appendChild(bidElement);
});
// If we have processed at least one step, show a cut-off line at the allocation boundary
if (currentStep > 0) {
// Find the highest accepted yield (cut-off point)
let cutOffYield = MIN_YIELD;
for (const bid of englishBids) {
if (bid.status === 'accepted' && bid.yield > cutOffYield) {
cutOffYield = bid.yield;
}
}
const tracker = englishVis.querySelector('.bid-tracker');
const leftPosition = mapYieldToPosition(cutOffYield);
tracker.style.left = `${leftPosition}%`;
// Add label to the tracker
const trackerLabel = document.createElement('div');
trackerLabel.className = 'tracker-label';
trackerLabel.textContent = `Cut-off: ${formatYield(cutOffYield)}`;
trackerLabel.style.left = `${leftPosition}%`;
tracker.appendChild(trackerLabel);
}
}
function updateDutchResults() {
// Calculate auction metrics
let totalAccepted = 0;
let totalRequested = 0;
dutchBids.forEach(bid => {
totalRequested += bid.amount;
if (bid.status === 'accepted') {
totalAccepted += bid.isPartial ? bid.partialAmount : bid.amount;
}
});
const bidToCoverRatio = totalRequested / totalAccepted;
// Create results HTML with improved layout
let resultsHTML = `
<h4>Dutch Auction Results</h4>
<p>All successful bidders pay the same single clearing yield.</p>
<div class="results-grid">
<div class="result-item">
<div class="result-label">Total Bonds Issued</div>
<div class="result-value">${formatCurrency(totalAccepted)}</div>
</div>
<div class="result-item">
<div class="result-label">Clearing Yield</div>
<div class="result-value result-special">${formatYield(dutchClearingYield)}</div>
</div>
<div class="result-item">
<div class="result-label">Bid-to-Cover Ratio</div>
<div class="result-value">${bidToCoverRatio.toFixed(2)}x</div>
</div>
<div class="result-item">
<div class="result-label">Number of Bidders</div>
<div class="result-value">${dutchBids.length}</div>
</div>
</div>
`;
dutchResults.innerHTML = resultsHTML;
}
function updateEnglishResults() {
// Calculate auction metrics
let totalAccepted = 0;
let totalRequested = 0;
let weightedAvgYield = 0;
let acceptedBids = 0;
englishBids.forEach(bid => {
totalRequested += bid.amount;
if (bid.status === 'accepted') {
const amount = bid.isPartial ? bid.partialAmount : bid.amount;
totalAccepted += amount;
weightedAvgYield += amount * bid.yield;
acceptedBids++;
}
});
const bidToCoverRatio = totalRequested / totalAccepted;
weightedAvgYield = weightedAvgYield / totalAccepted;
// Create results HTML with improved layout
let resultsHTML = `
<h4>English Auction Results (Step ${currentStep}/${englishBids.length})</h4>
<p>Each successful bidder pays their own bid yield.</p>
<div class="results-grid">
<div class="result-item">
<div class="result-label">Total Bonds Allocated</div>
<div class="result-value">${formatCurrency(totalAccepted)}</div>
</div>
<div class="result-item">
<div class="result-label">Weighted Avg Yield</div>
<div class="result-value result-special">${formatYield(weightedAvgYield)}</div>
</div>
<div class="result-item">
<div class="result-label">Accepted Bidders</div>
<div class="result-value">${acceptedBids}/${englishBids.length}</div>
</div>
<div class="result-item">
<div class="result-label">Bid-to-Cover Ratio</div>
<div class="result-value">${bidToCoverRatio.toFixed(2)}x</div>
</div>
</div>
`;
englishResults.innerHTML = resultsHTML;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment