Created
March 24, 2025 02:12
-
-
Save yongkangc/80e98ce2261babc746f0458f0c321a42 to your computer and use it in GitHub Desktop.
auction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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