Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss with unbounded detection, breakevens, net Greeks, auto-detected strategy name) - chain.html: per-row Buy/Sell buttons add legs to a localStorage basket; basket badge in toolbar; auto-scroll to ATM row on load - Persist per-page view state (symbol, expiry, loaded data, charts) across navigation via viewstate-store.js for chain/surface/tracker/dashboard - New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js - Strategy P/L nav link added to all sidebars Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
89
frontend/assets/blackscholes.js
Normal file
89
frontend/assets/blackscholes.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Black-Scholes pricing & Greeks — vanilla JS port of backend/src/lib/blackscholes.ts.
|
||||
* Exposed as window.BS. No modules, no deps.
|
||||
*
|
||||
* Conventions (match backend):
|
||||
* - theta returned PER CALENDAR DAY (annual / 365)
|
||||
* - vega returned PER 1% VOL MOVE (raw / 100)
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function normalPDF(x) {
|
||||
return Math.exp(-0.5 * x * x) / Math.sqrt(2.0 * Math.PI);
|
||||
}
|
||||
|
||||
// Abramowitz & Stegun 26.2.17, max error ~7.5e-8
|
||||
function normalCDF(x) {
|
||||
const sign = x >= 0 ? 1 : -1;
|
||||
const absX = Math.abs(x);
|
||||
const a1 = 0.319381530, a2 = -0.356563782, a3 = 1.781477937,
|
||||
a4 = -1.821255978, a5 = 1.330274429, p = 0.2316419;
|
||||
const t = 1.0 / (1.0 + p * absX);
|
||||
const poly = t * (a1 + t * (a2 + t * (a3 + t * (a4 + t * a5))));
|
||||
const approx = 1.0 - normalPDF(absX) * poly;
|
||||
return sign === 1 ? approx : 1.0 - approx;
|
||||
}
|
||||
|
||||
function d1d2(S, K, T, r, sigma) {
|
||||
const sqrtT = Math.sqrt(T);
|
||||
const d1 = (Math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrtT);
|
||||
return { d1, d2: d1 - sigma * sqrtT, sqrtT };
|
||||
}
|
||||
|
||||
/** Intrinsic payoff per share at expiry. */
|
||||
function intrinsic(S, K, type) {
|
||||
return type === "call" ? Math.max(S - K, 0) : Math.max(K - S, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Black-Scholes theoretical price (per share).
|
||||
* If T <= 0 (or sigma <= 0), returns intrinsic value.
|
||||
*/
|
||||
function bsPrice(S, K, T, r, sigma, type) {
|
||||
if (T <= 0 || sigma <= 0) return intrinsic(S, K, type);
|
||||
const { d1, d2 } = d1d2(S, K, T, r, sigma);
|
||||
const disc = Math.exp(-r * T);
|
||||
return type === "call"
|
||||
? S * normalCDF(d1) - K * disc * normalCDF(d2)
|
||||
: K * disc * normalCDF(-d2) - S * normalCDF(-d1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Black-Scholes Greeks (per share). theta per day, vega per 1% vol.
|
||||
* If T <= 0, returns degenerate Greeks (delta ±1/0, rest 0).
|
||||
*/
|
||||
function bsGreeks(S, K, T, r, sigma, type) {
|
||||
if (T <= 0 || sigma <= 0) {
|
||||
const itm = intrinsic(S, K, type) > 0;
|
||||
return {
|
||||
delta: type === "call" ? (itm ? 1 : 0) : (itm ? -1 : 0),
|
||||
gamma: 0, theta: 0, vega: 0, rho: 0,
|
||||
};
|
||||
}
|
||||
const { d1, d2, sqrtT } = d1d2(S, K, T, r, sigma);
|
||||
const nd1 = normalPDF(d1);
|
||||
const disc = Math.exp(-r * T);
|
||||
|
||||
const delta = type === "call" ? normalCDF(d1) : normalCDF(d1) - 1;
|
||||
const gamma = nd1 / (S * sigma * sqrtT);
|
||||
|
||||
let thetaAnnual;
|
||||
if (type === "call") {
|
||||
thetaAnnual = -(S * nd1 * sigma) / (2 * sqrtT) - r * K * disc * normalCDF(d2);
|
||||
} else {
|
||||
thetaAnnual = -(S * nd1 * sigma) / (2 * sqrtT) + r * K * disc * normalCDF(-d2);
|
||||
}
|
||||
const theta = thetaAnnual / 365;
|
||||
const vega = (S * nd1 * sqrtT) / 100;
|
||||
|
||||
let rhoRaw;
|
||||
if (type === "call") rhoRaw = K * T * disc * normalCDF(d2);
|
||||
else rhoRaw = -K * T * disc * normalCDF(-d2);
|
||||
const rho = rhoRaw / 100;
|
||||
|
||||
return { delta, gamma, theta, vega, rho };
|
||||
}
|
||||
|
||||
window.BS = { normalCDF, normalPDF, bsPrice, bsGreeks, intrinsic };
|
||||
})();
|
||||
Reference in New Issue
Block a user