var getText = function (url, callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'text'; xhr.onload = function () { var status = xhr.status; if (status === 200) { callback(xhr.response); } else { callback(status); } }; xhr.send(); }; function createSVG(o) { return d3.create(o); } function getInputValue(t) { return Number(document.getElementById(t).value); } function histDoubles(out, t, r, b, l, w, h, title) { var margin = {top: t, right: r, bottom: b, left: l}, width = w - margin.left - margin.right, height = h - margin.top - margin.bottom; var svg = createSVG('svg'); var g = svg.attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var minValue = d3.min(out, function (d) { return d; }); var maxValue = d3.max(out, function (d) { return d; }); var X = d3.scaleLinear().domain([minValue, maxValue]).range([0, width]); var histogram = d3.histogram().value(function (d) { return d; }).domain(X.domain()).thresholds(X.ticks(70)); g.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(X)); var bins = histogram(out); var Y = d3.scaleLinear().range([height, 0]); Y.domain([0, d3.max(bins, function (d) { return d.length; })]); g.append("g").call(d3.axisLeft(Y)); g.selectAll("rect") .data(bins) .enter() .append("rect") .attr("x", 1) .attr("transform", function (d) { return "translate(" + X(d.x0) + "," + Y(d.length) + ")"; }) .attr("width", function (d) { return X(d.x1) - X(d.x0) - 1; }) .attr("height", function (d) { return height - Y(d.length); }) .style("fill", "#69b3a2"); g.append("text") .attr("x", (width / 2)) .attr("y", -(margin.top / 2)) .attr("text-anchor", "middle") .style("font-size", "16px") .style("text-decoration", "underline") .text(title); return svg; } function removeAllChildNodes(parent) { while (parent.firstChild) { parent.removeChild(parent.firstChild); } return parent; } function logDiff(x) { var l = []; for (var i = 1; i < x.length; i++) { l.push(Math.log(x[i] / x[i - 1])); } return l; } function sum(x) { var t = 0; for (var i = 0; i < x.length; i++) { t += x[i]; } return t; } function mean(x) { return x.length === 0 ? 0.0 : sum(x) / x.length; } function sumProduct(a, b) { var t = 0; for (var i = 0; i < Math.min(a.length, b.length); i++) { t += a[i] * b[i]; } return t; } function sumSquares(x) { return sumProduct(x, x); } function variance(x) { if (x !== null && x.length > 2) { var sX = sum(x), ssX = sumSquares(x); return (ssX - sX * sX / x.length) / (x.length - 1); } return 0.0; } function covariance(x, y) { if (x !== null && x.length > 2) { var sX = sum(x), sY = sum(y), cross = sumProduct(x, y); return (cross - sX * sY / x.length) / (x.length - 1); } return 0.0; } function correlation(x, y) { if (x !== null && x.length > 2) { var vX = variance(x), vY = variance(y), cov = covariance(x, y); return cov / Math.sqrt(vX, vY); } return 0.0; } function normalize(a) { var b = [], m = mean(a), s = Math.sqrt(variance(a)); for (var i = 0; i < a.length; i++) { b.push((a[i] - m) / s); } return b; } function addCells(t, titles, type) { var row = document.createElement('tr'), cell, cellText; t.appendChild(row); for (var i = 0; i < titles.length; i++) { cell = document.createElement(type); cellText = document.createTextNode(titles[i]); cell.appendChild(cellText); row.appendChild(cell); } } class NormalArray { haveNextGaussian = false; nextGauss = null; precalcNormal = []; normcount = 0; constructor(n) { var a = []; for (var i = 0; i < n; i++) { a.push(this.nextGaussian()); } this.precalcNormal = normalize(a); } nextNormal() { var n = this.precalcNormal[this.normcount]; this.normcount++; return n; } nextGaussian() { if (this.haveNextNextGaussian) { this.haveNextNextGaussian = false; return this.nextGauss; } else { var v1, v2, s; do { v1 = 2 * Math.random() - 1; // between -1.0 and 1.0 v2 = 2 * Math.random() - 1; // between -1.0 and 1.0 s = v1 * v1 + v2 * v2; } while (s >= 1 || s === 0); var multiplier = Math.sqrt(-2 * Math.log(s) / s); this.nextGauss = v2 * multiplier; this.haveNextNextGaussian = true; return v1 * multiplier; } } createNormalArray(n) { var a = []; for (var i = 0; i < n; i++) { a.push(this.nextNormal()); } return a; } } class HestonEuler { normalSpot; normalVols; constructor(spot, vol, volvol, days, rho, rate, yld, speedAdj, ltVol) { this.spot = spot; this.vol = vol * vol; this.volvol = volvol * volvol; this.days = days; this.years = days / 365.0; this.rho = rho; this.rate = rate; this.yld = yld; this.speedAdj = speedAdj; this.ltVol = ltVol * ltVol; this.drift = rate - yld; } preCalcNormals(iters, days) { this.normalSpot = new NormalArray(iters * days); this.normalVols = new NormalArray(iters * days); } volatilityPath(path) { var v = Math.sqrt(variance(logDiff(path.sp))); return v * Math.sqrt(365) * 100; } volatilityOutcomes(iters) { var o = []; for (var i = 0; i < iters.length; i++) { o.push(this.volatilityPath(iters[i])); } return o; } correlatedArray(x, rho) { var na = this.normalVols.createNormalArray(x.length); var a = []; var rStar = Math.sqrt(1.0 - rho * rho); for (var i = 0; i < na.length; i++) { a.push(rho * x[i] + rStar * na[i]); } return a; } outcomeArray(iters) { var o = [], path; for (var i = 0; i < iters.length; i++) { path = iters[i]; o.push(path.sp[path.sp.length - 1]); } return o; } histOutcomes(iters, t, r, b, l, w, h) { var out = this.outcomeArray(iters); return histDoubles(out, t, r, b, l, w, h, 'Simulation Outcomes'); } histVols(iters, t, r, b, l, w, h) { var out = this.volatilityOutcomes(iters); return histDoubles(out, t, r, b, l, w, h, 'Volatility Outcomes'); } pathIter() { var spotNorm = this.normalSpot.createNormalArray(this.days); var correlatedNorm = this.correlatedArray(spotNorm, this.rho); var spotPath = [this.spot]; var volPath = [this.vol]; var dt = this.years / spotNorm.length, im1; for (var i = 1; i < spotNorm.length; i++) { im1 = i - 1; var v_max = Math.max(volPath[im1], 0.0); volPath[i] = volPath[im1] + this.speedAdj * dt * (this.ltVol - v_max) + this.volvol * Math.sqrt(v_max * dt) * correlatedNorm[im1]; spotPath[i] = spotPath[im1] * Math.exp((this.drift - 0.5 * v_max) * dt + Math.sqrt(v_max * dt) * spotNorm[im1]); } return {vp: volPath, sp: spotPath}; } iter(n) { var r = []; this.preCalcNormals(n, this.days); for (var i = 0; i < n; i++) { r.push(this.pathIter()); } this.precalcNormal = []; return r; } outcomePercentLess(k, iters) { var t = 0, path; for (var i = 0; i < iters.length; i++) { path = iters[i]; if (path.sp[path.sp.length - 1] < k) { t += 1; } } return t / iters.length; } callPayoffs(k, iters) { var cp = [], path; for (var i = 0; i < iters.length; i++) { path = iters[i]; cp.push(Math.max(0, path.sp[path.sp.length - 1] - k)); } return cp; } putPayoffs(k, iters) { var cp = [], path; for (var i = 0; i < iters.length; i++) { path = iters[i]; cp.push(Math.max(0, k - path.sp[path.sp.length - 1])); } return cp; } callValue(k, iters) { var cv = mean(this.callPayoffs(k, iters)); return cv * Math.exp(-this.rate * this.days / 365.00); } putValue(k, iters) { var pv = mean(this.putPayoffs(k, iters)); return pv * Math.exp(-this.rate * this.days / 365.00); } getTable(iters, a, z, d) { var t = document.createElement('table'), c; addCells(t, ['Strike', 'Calls', 'Puts', 'Put Outcomes', 'Call Outcomes'], 'th'); while (a <= z) { c = this.outcomePercentLess(a, iters); if (c > 0 && c < 1) { addCells(t, [a.toFixed(2), this.callValue(a, iters).toFixed(3), this.putValue(a, iters).toFixed(3), c.toFixed(3), (1 - c).toFixed(3)], 'td'); } a += d; } return t; } }