1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
export type OptionType = 'C' | 'P';
export interface Greeks {
delta: number;
gamma: number;
vega: number;
theta: number;
rho: number;
}
function erf(x: number): number {
const sign = x < 0 ? -1 : 1;
x = Math.abs(x);
const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741,
a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911;
const t = 1.0 / (1.0 + p * x);
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y;
}
function normCdf(x: number): number {
return 0.5 * (1 + erf(x / Math.SQRT2));
}
function normPdf(x: number): number {
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
}
function d1d2(S: number, K: number, T: number, r: number, q: number, sigma: number): [number, number] {
const vt = sigma * Math.sqrt(T);
const d1 = (Math.log(S / K) + (r - q + 0.5 * sigma * sigma) * T) / vt;
return [d1, d1 - vt];
}
export function bsPrice(S: number, K: number, T: number, r: number, q: number, sigma: number, type: OptionType): number {
if (T <= 0 || sigma <= 0) return type === 'C' ? Math.max(S - K, 0) : Math.max(K - S, 0);
const [d1, d2] = d1d2(S, K, T, r, q, sigma);
const eqt = Math.exp(-q * T), ert = Math.exp(-r * T);
if (type === 'C') return S * eqt * normCdf(d1) - K * ert * normCdf(d2);
return K * ert * normCdf(-d2) - S * eqt * normCdf(-d1);
}
export function bsGreeks(S: number, K: number, T: number, r: number, q: number, sigma: number, type: OptionType): Greeks {
const safeT = Math.max(T, 1e-6), safeS = Math.max(sigma, 1e-6);
const [d1, d2] = d1d2(S, K, safeT, r, q, safeS);
const eqt = Math.exp(-q * safeT), ert = Math.exp(-r * safeT);
const pdf1 = normPdf(d1);
const delta = type === 'C' ? eqt * normCdf(d1) : -eqt * normCdf(-d1);
const gamma = eqt * pdf1 / (S * safeS * Math.sqrt(safeT));
const vega = S * eqt * pdf1 * Math.sqrt(safeT) * 0.01;
const thetaYr = type === 'C'
? (-S * pdf1 * safeS * eqt / (2 * Math.sqrt(safeT)) - r * K * ert * normCdf(d2) + q * S * eqt * normCdf(d1))
: (-S * pdf1 * safeS * eqt / (2 * Math.sqrt(safeT)) + r * K * ert * normCdf(-d2) - q * S * eqt * normCdf(-d1));
const theta = thetaYr / 365;
const rho = type === 'C'
? K * safeT * ert * normCdf(d2) * 0.01
: -K * safeT * ert * normCdf(-d2) * 0.01;
return { delta, gamma, vega, theta, rho };
}
export function bsImpliedVol(S: number, K: number, T: number, r: number, q: number, mktPrice: number, type: OptionType): number {
if (T <= 0) return NaN;
let lo = 0.0001, hi = 5.0;
if (mktPrice < bsPrice(S, K, T, r, q, lo, type) || mktPrice > bsPrice(S, K, T, r, q, hi, type)) return NaN;
for (let i = 0; i < 100; i++) {
const mid = (lo + hi) / 2;
const pMid = bsPrice(S, K, T, r, q, mid, type);
if (Math.abs(pMid - mktPrice) < 1e-6) return mid;
if (pMid < mktPrice) lo = mid; else hi = mid;
}
return (lo + hi) / 2;
}
export function bsSynthIV(S: number, K: number, T: number, atmSigma: number): number {
const logM = Math.log(K / S);
const t = Math.max(T, 1 / 365);
const termLift = 0.014 * (Math.sqrt(t) - Math.sqrt(30 / 365));
const skew = -0.085 * logM / Math.sqrt(t * 4);
const smile = 0.32 * (logM * logM) / Math.sqrt(t);
return Math.max(0.04, Math.min(1.5, atmSigma + termLift + skew + smile));
}
|