html `<style>
.plot-title-small {
font-size: 13px;
font-weight: 600;
}
.plot-subtitle-small {
font-size: 11px;
font-weight: 400;
}
</style>`
plotTitle = text => html `<span class="plot-title-small"> ${ text} </span>` ;
plotSubtitle = text => html `<br><span class="plot-subtitle-small"> ${ text} </span>` ;
This interactive simulation demonstrates the mechanics of margin accounts for both Buying on Margin and Short Selling .
Interactive Simulation
Choose your strategy and move the slider to see how the stock price evolution affects your margin account over 22 days.
Assumptions:
Initial Stock Price: $100
Number of Shares: 100
Initial Margin Requirement: 50%
Maintenance Margin: 30%
Annual Interest Rate on Loan (Buy on Margin): 8%
Interest on Short Proceeds: 0%
Stock Dividends: None
Time Horizon: 63 days (approx. 3 months)
Year: 365 days
viewof newSim = {
const btn = html `<button style="background: #2f71d5; color: white; border: none; border-radius: 10px; padding: 0.55rem 1.3rem; font-weight: 600; cursor: pointer;">New Simulation</button>` ;
btn. value = 0 ;
btn. onclick = () => {
btn. value ++;
btn. dispatchEvent (new Event ("input" , {bubbles : true }));
};
return btn;
}
initialPrice = 100
nShares = 100
initialMarginReq = 0.5
maintenanceMargin = 0.3
interestRate = 0.08
daysInYear = 365
sigma = sigmaInput
mu = muInput
mulberry32 = seed => {
return function () {
let t = seed += 0x6D2B79F5
t = Math . imul (t ^ t >>> 15 , t | 1 )
t ^= t + Math . imul (t ^ t >>> 7 , t | 61 )
return ((t ^ t >>> 14 ) >>> 0 ) / 4294967296
}
}
randomNormal = rng => {
let u = 0 , v = 0
while (u === 0 ) u = rng ()
while (v === 0 ) v = rng ()
return Math . sqrt (- 2.0 * Math . log (u)) * Math . cos (2.0 * Math . PI * v)
}
simSeed = {
newSim; // Update seed only when button is clicked
return Math . floor (Math . random () * 1000000 );
}
// Generate Price Path
path = {
const rng = mulberry32 (simSeed);
const dt = 1 / daysInYear;
let prices = [{day : 0 , price : initialPrice}];
let currentPrice = initialPrice;
for (let i= 1 ; i<= 63 ; i++ ) {
const drift = (mu - 0.5 * sigma * sigma) * dt;
const diffusion = sigma * Math . sqrt (dt) * randomNormal (rng);
currentPrice = currentPrice * Math . exp (drift + diffusion);
prices. push ({day : i, price : currentPrice});
}
return prices;
}
currentPath = path. slice (0 , days + 1 )
data = {
let computed = [];
let currentLoan = initialPrice * nShares * (1 - initialMarginReq);
let currentCash = initialPrice * nShares * (1 + initialMarginReq); // For short sale
let initialEquity = initialPrice * nShares * initialMarginReq;
let prevPrice = initialPrice;
let prevEquityPostCall = initialEquity; // Tracks equity after margin call top-up
// Daily interest factor
let dailyFactor = Math . pow (1 + interestRate, 1 / daysInYear);
for (let i = 0 ; i < path. length ; i++ ) {
let d = path[i];
let equityBeforeCall, equityAfterCall, marginPercentBeforeCall, marginPercentAfterCall;
let marginCall, liability, assets, callPrice, callAmount = 0 ;
if (strategy === "Buy on Margin" ) {
if (i > 0 ) currentLoan = currentLoan * dailyFactor;
assets = d. price * nShares;
liability = currentLoan;
equityBeforeCall = assets - liability;
marginPercentBeforeCall = equityBeforeCall / assets;
if (marginPercentBeforeCall < maintenanceMargin) {
marginCall = true ;
callAmount = assets * initialMarginReq - equityBeforeCall;
currentLoan = currentLoan - callAmount;
} else {
marginCall = false ;
}
equityAfterCall = equityBeforeCall + callAmount;
marginPercentAfterCall = equityAfterCall / assets;
callPrice = currentLoan / (nShares * (1 - maintenanceMargin));
} else {
// Short Sale
liability = d. price * nShares;
assets = currentCash;
equityBeforeCall = assets - liability;
marginPercentBeforeCall = equityBeforeCall / liability;
if (marginPercentBeforeCall < maintenanceMargin) {
marginCall = true ;
callAmount = liability * initialMarginReq - equityBeforeCall;
currentCash = currentCash + callAmount;
} else {
marginCall = false ;
}
equityAfterCall = equityBeforeCall + callAmount;
marginPercentAfterCall = equityAfterCall / liability;
callPrice = currentCash / (nShares * (1 + maintenanceMargin));
}
let stockReturn = 0 ;
let accountReturn = 0 ;
if (i > 0 ) {
stockReturn = (d. price - prevPrice) / prevPrice;
accountReturn = (equityBeforeCall - prevEquityPostCall) / prevEquityPostCall;
}
prevPrice = d. price ;
prevEquityPostCall = equityAfterCall; // Next day starts with this equity base
computed. push ({
... d,
equity : equityAfterCall,
marginPercent : marginPercentAfterCall * 100 ,
marginCall : marginCall,
callAmount : callAmount,
liability : liability,
assets : assets,
callPrice : callPrice,
equityBeforeCall : equityBeforeCall,
marginPercentBeforeCall : marginPercentBeforeCall * 100 ,
stockReturn : stockReturn * 100 ,
accountReturn : accountReturn * 100
});
}
return computed;
}
currentData = data. slice (0 , days + 1 )
lastPoint = currentData[currentData. length - 1 ]
callPriceCurrent = lastPoint. callPrice
accountDataTidy = currentData. flatMap (d => [
{day : d. day , value : d. assets , type : "Assets" },
{day : d. day , value : d. liability , type : "Liability" },
{day : d. day , value : d. equity , type : "Equity" }
])
priceDataTidy = currentData. flatMap (d => [
{day : d. day , value : d. price , type : "Stock Price" },
{day : d. day , value : d. callPrice , type : "Margin Call Price" }
])
Plot. plot ({
title : plotTitle ("Stock Price & Margin Call Threshold" ),
grid : true ,
color : {legend : true , domain : ["Stock Price" , "Margin Call Price" ], range : ["#2f71d5" , "#d00000" ]},
x : {label : "Day" , domain : [0 , 63 ]},
y : {label : "Price ($)" , domain : [Math . min (... path. map (d => d. price ), callPriceCurrent) * 0.9 , Math . max (... path. map (d => d. price ), callPriceCurrent) * 1.1 ]},
marks : [
Plot. line (currentData, {x : "day" , y : "price" , stroke : "#2f71d5" , strokeWidth : 2 }),
Plot. line (currentData, {x : "day" , y : "callPrice" , stroke : "#d00000" , strokeDasharray : "4" , strokeWidth : 1.5 }),
Plot. ruleX ([days], {stroke : "gray" , strokeDasharray : "4" }),
Plot. dot (currentData. filter (d => d. marginCall ), {x : "day" , y : "price" , stroke : "red" , r : 6 , title : "Margin Call" }),
Plot. tip (priceDataTidy, Plot. pointerX ({x : "day" , y : "value" , stroke : "type" , title : d => `Day ${ d. day }\n${ d. type } : $ ${ d. value . toFixed (2 )} ` }))
]
})
Plot. plot ({
title : plotTitle ("Account Value Evolution" ),
grid : true ,
color : {legend : true , domain : ["Assets" , "Liability" , "Equity" ], range : ["#2b8a3e" , "#c92a2a" , "#1c7ed6" ]},
x : {label : "Day" , domain : [0 , 63 ]},
y : {label : "Value ($)" },
marks : [
Plot. line (currentData, {x : "day" , y : "assets" , stroke : "#2b8a3e" , strokeWidth : 2 }),
Plot. line (currentData, {x : "day" , y : "liability" , stroke : "#c92a2a" , strokeWidth : 2 }),
Plot. line (currentData, {x : "day" , y : "equity" , stroke : "#1c7ed6" , strokeWidth : 2 }),
Plot. ruleX ([days], {stroke : "gray" , strokeDasharray : "4" }),
Plot. dot (currentData. filter (d => d. marginCall ), {x : "day" , y : "equity" , stroke : "red" , r : 6 , title : "Margin Call" }),
Plot. tip (accountDataTidy, Plot. pointerX ({x : "day" , y : "value" , stroke : "type" , title : d => `Day ${ d. day }\n${ d. type } : $ ${ d. value . toFixed (2 )} ` }))
]
})
Account Details
html `
<table style="width: 100%; border-collapse: collapse; font-size: 0.92rem;">
<thead>
<tr style="text-align: left; border-bottom: 2px solid #adb5bd;">
<th style="padding: 0.5rem;">Day</th>
<th style="padding: 0.5rem;">Stock Price</th>
<th style="padding: 0.5rem;">Stock Return</th>
<th style="padding: 0.5rem;">Account Equity /<br>Margin Account (post-call)</th>
<th style="padding: 0.5rem;">Pre-call Equity</th>
<th style="padding: 0.5rem;">Account Return</th>
<th style="padding: 0.5rem;">Margin Level (post-call)</th>
<th style="padding: 0.5rem;">Pre-call Margin</th>
<th style="padding: 0.5rem;">Margin Call</th>
<th style="padding: 0.5rem;">Call Amount</th>
</tr>
</thead>
<tbody>
${ currentData. map (d => html `
<tr style="border-bottom: 1px solid #e5e7eb;">
<td style="padding: 0.5rem;"> ${ d. day } </td>
<td style="padding: 0.5rem;"> ${ d. price . toFixed (2 )} </td>
<td style="padding: 0.5rem; color: ${ d. day === 0 ? 'inherit' : (d. stockReturn >= 0 ? 'green' : 'red' )} ;">
${ d. day === 0 ? '-' : d. stockReturn . toFixed (2 ) + '%' }
</td>
<td style="padding: 0.5rem;"> ${ d. equity . toFixed (2 )} </td>
<td style="padding: 0.5rem;"> ${ d. equityBeforeCall . toFixed (2 )} </td>
<td style="padding: 0.5rem; color: ${ d. day === 0 ? 'inherit' : (d. accountReturn >= 0 ? 'green' : 'red' )} ;">
${ d. day === 0 ? '-' : d. accountReturn . toFixed (2 ) + '%' }
</td>
<td style="padding: 0.5rem;"> ${ d. marginPercent . toFixed (2 )} %</td>
<td style="padding: 0.5rem;"> ${ d. marginPercentBeforeCall . toFixed (2 )} %</td>
<td style="padding: 0.5rem;"> ${ d. marginCall ? "⚠️ YES" : "No" } </td>
<td style="padding: 0.5rem;"> ${ d. marginCall ? d. callAmount . toFixed (2 ) : "-" } </td>
</tr>
` )}
</tbody>
</table>
`
Notes:
Stock Return: Daily percentage change: \((P_t - P_{t-1}) / P_{t-1}\) . Recall that the stock does not pay dividends in this simulation.
Account Return: Daily return on account value: \((Equity_t - \text{PrevEquity}^*) / \text{PrevEquity}^*\) , where \(\text{PrevEquity}^*\) is the equity at the end of the previous day plus any margin call deposit.
Account Equity / Margin Account: Reported after any margin call deposit is added for the day.
Pre-call Equity: Equity before any margin call deposit.
Margin Level (Buy): \(\frac{\text{Equity}}{\text{Market Stock Value}}\) .
Margin Level (Short): \(\frac{\text{Equity}}{\text{Market Stock Value}} = \frac{\text{Total Cash} - \text{Stock Value}}{\text{Market Stock Value}}\) .
Pre-call Margin: Margin level before any margin call deposit.
Leverage: Both buying on margin and short selling are leveraged positions, so account returns can be amplified relative to stock returns, increasing both upside potential and downside risk.
For more details on how these calculations are done, refer to buying on margin and short sales pages.