<div class="demo-wrap">
<div class="agents">
<div class="agent-card" id="card-a">
<div class="agent-label">Agent A</div>
<div class="endow">Endowment: <b>6 units of good 1</b>, <b>2 units of good 2</b></div>
<div class="pref">Preferences: u(x₁,x₂) = x₁<sup>0.6</sup> x₂<sup>0.4</sup></div>
<div class="demand-row"><span>Demand good 1:</span><span id="da1">—</span></div>
<div class="demand-row"><span>Demand good 2:</span><span id="da2">—</span></div>
</div>
<div class="agent-card" id="card-b">
<div class="agent-label">Agent B</div>
<div class="endow">Endowment: <b>2 units of good 1</b>, <b>6 units of good 2</b></div>
<div class="pref">Preferences: u(x₁,x₂) = x₁<sup>0.4</sup> x₂<sup>0.6</sup></div>
<div class="demand-row"><span>Demand good 1:</span><span id="db1">—</span></div>
<div class="demand-row"><span>Demand good 2:</span><span id="db2">—</span></div>
</div>
</div>
<div class="price-section">
<div class="price-row">
<label>Price of good 1 (p₁): <b id="p1val">1.00</b></label>
<input type="range" id="p1" min="10" max="400" value="100" step="1">
</div>
<div class="price-row">
<label>Price of good 2 (p₂): <b id="p2val">1.00</b></label>
<input type="range" id="p2" min="10" max="400" value="100" step="1">
</div>
<div class="walras-note">Walras's Law: value of excess demand = <b id="walras">0.00</b> (always ≈ 0)</div>
</div>
<div class="excess-section">
<div class="excess-row">
<div class="excess-label">Excess demand — good 1</div>
<div class="bar-wrap"><div class="bar-track"><div class="bar-fill" id="bar1"></div><div class="bar-zero"></div></div></div>
<div class="excess-val" id="ex1">—</div>
</div>
<div class="excess-row">
<div class="excess-label">Excess demand — good 2</div>
<div class="bar-wrap"><div class="bar-track"><div class="bar-fill" id="bar2"></div><div class="bar-zero"></div></div></div>
<div class="excess-val" id="ex2">—</div>
</div>
</div>
<div class="status-row" id="status-row">Adjust prices to find equilibrium.</div>
<button id="btn-find" type="button">Find equilibrium automatically</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.demo-wrap { padding: .5rem; max-width: 560px; margin: 0 auto; }
.agents { display: flex; gap: .6rem; margin-bottom: .8rem; }
.agent-card { flex: 1; border: 1px solid #c8d4de; border-radius: 10px; padding: .6rem .75rem; background: #f0f5f9; }
.agent-label { font-weight: 700; font-size: .92rem; color: #1d3557; margin-bottom: .25rem; }
.endow { font-size: .78rem; color: #444; margin-bottom: .15rem; }
.pref { font-size: .78rem; color: #666; margin-bottom: .4rem; }
.demand-row { display: flex; justify-content: space-between; font-size: .82rem; padding: .1rem 0; }
.demand-row b, .demand-row span:last-child { font-weight: 600; color: #1d3557; }
.price-section { margin-bottom: .75rem; }
.price-row { display: flex; flex-direction: column; margin-bottom: .35rem; }
.price-row label { font-size: .85rem; margin-bottom: .18rem; }
.price-row input[type=range] { width: 100%; accent-color: #1d3557; cursor: pointer; }
.walras-note { font-size: .78rem; color: #555; margin-top: .35rem; }
.walras-note b { color: #0a7d33; }
.excess-section { margin-bottom: .65rem; }
.excess-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.excess-label { width: 160px; font-size: .8rem; flex-shrink: 0; }
.bar-wrap { flex: 1; }
.bar-track { position: relative; height: 14px; background: #dde5ec; border-radius: 7px; overflow: hidden; }
.bar-fill { position: absolute; top: 0; height: 100%; border-radius: 7px; transition: width .15s, left .15s, background .15s; }
.bar-fill.pos { background: #e63946; left: 50%; }
.bar-fill.neg { background: #1d7ab8; right: 50%; }
.bar-zero { position: absolute; left: 50%; top: 0; width: 2px; height: 100%; background: #8899a6; }
.excess-val { width: 54px; font-size: .82rem; font-weight: 600; text-align: right; flex-shrink: 0; }
.excess-val.pos { color: #e63946; }
.excess-val.neg { color: #1d7ab8; }
.excess-val.zero { color: #0a7d33; }
.status-row { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin-bottom: .5rem; color: #444; }
.status-row.ok { color: #0a7d33; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:hover { background: #162842; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Arrow-Debreu two-agent, two-good exchange economy.
// Agent A: endow (6,2), Cobb-Douglas alpha=0.6 for good1.
// Agent B: endow (2,6), Cobb-Douglas alpha=0.4 for good1.
// Cobb-Douglas demand: x1 = alpha * I / p1, x2 = (1-alpha) * I / p2
// where I = p1*e1 + p2*e2 (income from selling endowment at market prices).
// Excess demand: z_k = total_demand_k - total_endowment_k.
// Equilibrium: z1 = 0 AND z2 = 0. Walras's Law: p1*z1 + p2*z2 = 0 always.
// Analytical equilibrium: by market clearing z1=0 => p1/p2 = (alpha_A*e1_A + alpha_B*e1_B - e1_A - e1_B) / ...
// Solved below analytically then verified.
const agents = [
{ e1: 6, e2: 2, alpha: 0.6 },
{ e1: 2, e2: 6, alpha: 0.4 }
];
function demand(agent, p1, p2) {
const income = p1 * agent.e1 + p2 * agent.e2;
return {
x1: agent.alpha * income / p1,
x2: (1 - agent.alpha) * income / p2
};
}
function excessDemand(p1, p2) {
let td1 = 0, td2 = 0;
agents.forEach(a => { const d = demand(a, p1, p2); td1 += d.x1; td2 += d.x2; });
const te1 = agents.reduce((s,a) => s + a.e1, 0);
const te2 = agents.reduce((s,a) => s + a.e2, 0);
return { z1: td1 - te1, z2: td2 - te2 };
}
// Analytical equilibrium price ratio p1/p2.
// z1=0: sum(alpha_i * (p1*e1_i + p2*e2_i) / p1) = E1
// => sum(alpha_i*e1_i) + (p2/p1)*sum(alpha_i*e2_i) = E1
// => p2/p1 = (E1 - sum(alpha_i*e1_i)) / sum(alpha_i*e2_i)
function analyticalEquilibrium() {
const E1 = agents.reduce((s,a) => s + a.e1, 0);
const sumAE1 = agents.reduce((s,a) => s + a.alpha * a.e1, 0);
const sumAE2 = agents.reduce((s,a) => s + a.alpha * a.e2, 0);
const p2_over_p1 = (E1 - sumAE1) / sumAE2;
// Normalise: p1 = 1
return { p1: 1.0, p2: p2_over_p1 };
}
function fmt(v) { return v.toFixed(3); }
function barUpdate(barEl, valEl, z) {
const MAX = 4;
const pct = Math.min(Math.abs(z) / MAX * 50, 50);
barEl.className = 'bar-fill ' + (z > 0.01 ? 'pos' : z < -0.01 ? 'neg' : '');
if (z > 0.01) { barEl.style.left = '50%'; barEl.style.right = ''; barEl.style.width = pct + '%'; }
else if (z < -0.01) { barEl.style.right = '50%'; barEl.style.left = ''; barEl.style.width = pct + '%'; }
else { barEl.style.width = '0'; }
const isZero = Math.abs(z) < 0.02;
valEl.textContent = (z >= 0 ? '+' : '') + fmt(z);
valEl.className = 'excess-val ' + (isZero ? 'zero' : z > 0 ? 'pos' : 'neg');
}
function update() {
const p1 = parseFloat(document.getElementById('p1').value) / 100;
const p2 = parseFloat(document.getElementById('p2').value) / 100;
document.getElementById('p1val').textContent = p1.toFixed(2);
document.getElementById('p2val').textContent = p2.toFixed(2);
const dA = demand(agents[0], p1, p2);
const dB = demand(agents[1], p1, p2);
document.getElementById('da1').textContent = fmt(dA.x1);
document.getElementById('da2').textContent = fmt(dA.x2);
document.getElementById('db1').textContent = fmt(dB.x1);
document.getElementById('db2').textContent = fmt(dB.x2);
const { z1, z2 } = excessDemand(p1, p2);
barUpdate(document.getElementById('bar1'), document.getElementById('ex1'), z1);
barUpdate(document.getElementById('bar2'), document.getElementById('ex2'), z2);
const walras = p1 * z1 + p2 * z2;
document.getElementById('walras').textContent = walras.toFixed(4);
const atEq = Math.abs(z1) < 0.05 && Math.abs(z2) < 0.05;
const sr = document.getElementById('status-row');
sr.textContent = atEq
? '✓ Equilibrium found! Both markets clear simultaneously.'
: i18nFmt('Excess demand in good 1: %v%%fmt% | Good 2: %v2%%fmt2%', { v: z1 >= 0 ? '+' : '', fmt: fmt(z1), v2: z2 >= 0 ? '+' : '', fmt2: fmt(z2) });
sr.className = 'status-row' + (atEq ? ' ok' : '');
}
document.getElementById('p1').addEventListener('input', update);
document.getElementById('p2').addEventListener('input', update);
document.getElementById('btn-find').addEventListener('click', () => {
const eq = analyticalEquilibrium();
// Map to slider: p1=1.00 => slider=100; p2=eq.p2 => slider=eq.p2*100
document.getElementById('p1').value = Math.round(eq.p1 * 100);
document.getElementById('p2').value = Math.min(400, Math.max(10, Math.round(eq.p2 * 100)));
update();
// Verify: log equilibrium details
console.log(i18nFmt('Equilibrium p1=%p1% p2=%p2%', { p1: eq.p1.toFixed(4), p2: eq.p2.toFixed(4) }));
const { z1, z2 } = excessDemand(eq.p1, eq.p2);
console.log(i18nFmt('Excess demand z1=%z1% z2=%z2%', { z1: z1.toFixed(6), z2: z2.toFixed(6) }));
});
update();
<div class="demo-wrap">
<div class="agents">
<div class="agent-card" id="card-a">
<div class="agent-label">Agent A</div>
<div class="endow">Endowment: <b>6 units of good 1</b>, <b>2 units of good 2</b></div>
<div class="pref">Preferences: u(x₁,x₂) = x₁<sup>0.6</sup> x₂<sup>0.4</sup></div>
<div class="demand-row"><span>Demand good 1:</span><span id="da1">—</span></div>
<div class="demand-row"><span>Demand good 2:</span><span id="da2">—</span></div>
</div>
<div class="agent-card" id="card-b">
<div class="agent-label">Agent B</div>
<div class="endow">Endowment: <b>2 units of good 1</b>, <b>6 units of good 2</b></div>
<div class="pref">Preferences: u(x₁,x₂) = x₁<sup>0.4</sup> x₂<sup>0.6</sup></div>
<div class="demand-row"><span>Demand good 1:</span><span id="db1">—</span></div>
<div class="demand-row"><span>Demand good 2:</span><span id="db2">—</span></div>
</div>
</div>
<div class="price-section">
<div class="price-row">
<label>Price of good 1 (p₁): <b id="p1val">1.00</b></label>
<input type="range" id="p1" min="10" max="400" value="100" step="1">
</div>
<div class="price-row">
<label>Price of good 2 (p₂): <b id="p2val">1.00</b></label>
<input type="range" id="p2" min="10" max="400" value="100" step="1">
</div>
<div class="walras-note">Walras's Law: value of excess demand = <b id="walras">0.00</b> (always ≈ 0)</div>
</div>
<div class="excess-section">
<div class="excess-row">
<div class="excess-label">Excess demand — good 1</div>
<div class="bar-wrap"><div class="bar-track"><div class="bar-fill" id="bar1"></div><div class="bar-zero"></div></div></div>
<div class="excess-val" id="ex1">—</div>
</div>
<div class="excess-row">
<div class="excess-label">Excess demand — good 2</div>
<div class="bar-wrap"><div class="bar-track"><div class="bar-fill" id="bar2"></div><div class="bar-zero"></div></div></div>
<div class="excess-val" id="ex2">—</div>
</div>
</div>
<div class="status-row" id="status-row">Adjust prices to find equilibrium.</div>
<button id="btn-find" type="button">Find equilibrium automatically</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.demo-wrap { padding: .5rem; max-width: 560px; margin: 0 auto; }
.agents { display: flex; gap: .6rem; margin-bottom: .8rem; }
.agent-card { flex: 1; border: 1px solid #c8d4de; border-radius: 10px; padding: .6rem .75rem; background: #f0f5f9; }
.agent-label { font-weight: 700; font-size: .92rem; color: #1d3557; margin-bottom: .25rem; }
.endow { font-size: .78rem; color: #444; margin-bottom: .15rem; }
.pref { font-size: .78rem; color: #666; margin-bottom: .4rem; }
.demand-row { display: flex; justify-content: space-between; font-size: .82rem; padding: .1rem 0; }
.demand-row b, .demand-row span:last-child { font-weight: 600; color: #1d3557; }
.price-section { margin-bottom: .75rem; }
.price-row { display: flex; flex-direction: column; margin-bottom: .35rem; }
.price-row label { font-size: .85rem; margin-bottom: .18rem; }
.price-row input[type=range] { width: 100%; accent-color: #1d3557; cursor: pointer; }
.walras-note { font-size: .78rem; color: #555; margin-top: .35rem; }
.walras-note b { color: #0a7d33; }
.excess-section { margin-bottom: .65rem; }
.excess-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.excess-label { width: 160px; font-size: .8rem; flex-shrink: 0; }
.bar-wrap { flex: 1; }
.bar-track { position: relative; height: 14px; background: #dde5ec; border-radius: 7px; overflow: hidden; }
.bar-fill { position: absolute; top: 0; height: 100%; border-radius: 7px; transition: width .15s, left .15s, background .15s; }
.bar-fill.pos { background: #e63946; left: 50%; }
.bar-fill.neg { background: #1d7ab8; right: 50%; }
.bar-zero { position: absolute; left: 50%; top: 0; width: 2px; height: 100%; background: #8899a6; }
.excess-val { width: 54px; font-size: .82rem; font-weight: 600; text-align: right; flex-shrink: 0; }
.excess-val.pos { color: #e63946; }
.excess-val.neg { color: #1d7ab8; }
.excess-val.zero { color: #0a7d33; }
.status-row { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin-bottom: .5rem; color: #444; }
.status-row.ok { color: #0a7d33; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:hover { background: #162842; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Arrow-Debreu two-agent, two-good exchange economy.
// Agent A: endow (6,2), Cobb-Douglas alpha=0.6 for good1.
// Agent B: endow (2,6), Cobb-Douglas alpha=0.4 for good1.
// Cobb-Douglas demand: x1 = alpha * I / p1, x2 = (1-alpha) * I / p2
// where I = p1*e1 + p2*e2 (income from selling endowment at market prices).
// Excess demand: z_k = total_demand_k - total_endowment_k.
// Equilibrium: z1 = 0 AND z2 = 0. Walras's Law: p1*z1 + p2*z2 = 0 always.
// Analytical equilibrium: by market clearing z1=0 => p1/p2 = (alpha_A*e1_A + alpha_B*e1_B - e1_A - e1_B) / ...
// Solved below analytically then verified.
const agents = [
{ e1: 6, e2: 2, alpha: 0.6 },
{ e1: 2, e2: 6, alpha: 0.4 }
];
function demand(agent, p1, p2) {
const income = p1 * agent.e1 + p2 * agent.e2;
return {
x1: agent.alpha * income / p1,
x2: (1 - agent.alpha) * income / p2
};
}
function excessDemand(p1, p2) {
let td1 = 0, td2 = 0;
agents.forEach(a => { const d = demand(a, p1, p2); td1 += d.x1; td2 += d.x2; });
const te1 = agents.reduce((s,a) => s + a.e1, 0);
const te2 = agents.reduce((s,a) => s + a.e2, 0);
return { z1: td1 - te1, z2: td2 - te2 };
}
// Analytical equilibrium price ratio p1/p2.
// z1=0: sum(alpha_i * (p1*e1_i + p2*e2_i) / p1) = E1
// => sum(alpha_i*e1_i) + (p2/p1)*sum(alpha_i*e2_i) = E1
// => p2/p1 = (E1 - sum(alpha_i*e1_i)) / sum(alpha_i*e2_i)
function analyticalEquilibrium() {
const E1 = agents.reduce((s,a) => s + a.e1, 0);
const sumAE1 = agents.reduce((s,a) => s + a.alpha * a.e1, 0);
const sumAE2 = agents.reduce((s,a) => s + a.alpha * a.e2, 0);
const p2_over_p1 = (E1 - sumAE1) / sumAE2;
// Normalise: p1 = 1
return { p1: 1.0, p2: p2_over_p1 };
}
function fmt(v) { return v.toFixed(3); }
function barUpdate(barEl, valEl, z) {
const MAX = 4;
const pct = Math.min(Math.abs(z) / MAX * 50, 50);
barEl.className = 'bar-fill ' + (z > 0.01 ? 'pos' : z < -0.01 ? 'neg' : '');
if (z > 0.01) { barEl.style.left = '50%'; barEl.style.right = ''; barEl.style.width = pct + '%'; }
else if (z < -0.01) { barEl.style.right = '50%'; barEl.style.left = ''; barEl.style.width = pct + '%'; }
else { barEl.style.width = '0'; }
const isZero = Math.abs(z) < 0.02;
valEl.textContent = (z >= 0 ? '+' : '') + fmt(z);
valEl.className = 'excess-val ' + (isZero ? 'zero' : z > 0 ? 'pos' : 'neg');
}
function update() {
const p1 = parseFloat(document.getElementById('p1').value) / 100;
const p2 = parseFloat(document.getElementById('p2').value) / 100;
document.getElementById('p1val').textContent = p1.toFixed(2);
document.getElementById('p2val').textContent = p2.toFixed(2);
const dA = demand(agents[0], p1, p2);
const dB = demand(agents[1], p1, p2);
document.getElementById('da1').textContent = fmt(dA.x1);
document.getElementById('da2').textContent = fmt(dA.x2);
document.getElementById('db1').textContent = fmt(dB.x1);
document.getElementById('db2').textContent = fmt(dB.x2);
const { z1, z2 } = excessDemand(p1, p2);
barUpdate(document.getElementById('bar1'), document.getElementById('ex1'), z1);
barUpdate(document.getElementById('bar2'), document.getElementById('ex2'), z2);
const walras = p1 * z1 + p2 * z2;
document.getElementById('walras').textContent = walras.toFixed(4);
const atEq = Math.abs(z1) < 0.05 && Math.abs(z2) < 0.05;
const sr = document.getElementById('status-row');
sr.textContent = atEq
? '✓ Equilibrium found! Both markets clear simultaneously.'
: i18nFmt('Excess demand in good 1: %v%%fmt% | Good 2: %v2%%fmt2%', { v: z1 >= 0 ? '+' : '', fmt: fmt(z1), v2: z2 >= 0 ? '+' : '', fmt2: fmt(z2) });
sr.className = 'status-row' + (atEq ? ' ok' : '');
}
document.getElementById('p1').addEventListener('input', update);
document.getElementById('p2').addEventListener('input', update);
document.getElementById('btn-find').addEventListener('click', () => {
const eq = analyticalEquilibrium();
// Map to slider: p1=1.00 => slider=100; p2=eq.p2 => slider=eq.p2*100
document.getElementById('p1').value = Math.round(eq.p1 * 100);
document.getElementById('p2').value = Math.min(400, Math.max(10, Math.round(eq.p2 * 100)));
update();
// Verify: log equilibrium details
console.log(i18nFmt('Equilibrium p1=%p1% p2=%p2%', { p1: eq.p1.toFixed(4), p2: eq.p2.toFixed(4) }));
const { z1, z2 } = excessDemand(eq.p1, eq.p2);
console.log(i18nFmt('Excess demand z1=%z1% z2=%z2%', { z1: z1.toFixed(6), z2: z2.toFixed(6) }));
});
update();
Comments
Loading comments...