var NOW = new Date(); function years(d) { return dateDiff(NOW, d) / 365; } function dateDiff(first, second) { // Take the difference between the dates and divide by milliseconds per day. // Round to nearest whole number to deal with DST. return Math.round((second - first) / (1000 * 60 * 60 * 24)); } function firstofMonth(dt) { var y = dt.getFullYear(); var m = dt.getMonth(); return new Date(y, m, 1); } function toNextDay(dt) { var nextDay = new Date(dt); nextDay.setDate(day.getDate() + 1); return nextDay; } function formatDate(dt) { var m = dt.getMonth() + 1; var y = dt.getFullYear(); var d = dt.getDate(); if (m < 10) { m = "0" + m; } if (d < 10) { d = "0" + d; } return y + "-" + m + "-" + d; } function toNextDayofWeek(dt, dow) { var newDate = new Date(dt); var day = newDate.getDay(); while (day !== dow) { newDate.setDate(newDate.getDate() + 1); day = newDate.getDay(); } return newDate; } function toCBOEQuarterDate(d) { d = firstofMonth(d); d = toNextQuarter(d); d = toNextDayofWeek(daysForward(d, 1), 5); d = toNextDayofWeek(daysForward(d, 1), 5); return toNextDayofWeek(daysForward(d, 1), 5); } function daysForward(dt, d) { var newDate = new Date(dt); newDate.setDate(newDate.getDate() + d); return newDate; } function isQuarter(d) { var m = d.getMonth() + 1; return m === 3 || m === 6 || m === 9 | m === 12; } function toNextQuarter(dt) { var newDate = monthForward(new Date(dt), 1); while (!isQuarter(newDate)) { newDate = monthForward(newDate, 1); } return newDate; } function nextMonth(d) { return monthForward(d, 1); } function monthForward(dt, d) { var newDate = new Date(dt); newDate.setMonth(newDate.getMonth() + d); return newDate; } function addTableItems(t, type, items, f, showzeros) { if (items !== undefined) { if (isNumber(items)) { items = [items]; } var tr = document.createElement("tr"); showzeros = showzeros === undefined ? true : showzeros; for (var i = 0; i < items.length; i++) { var th = document.createElement(type); tr.appendChild(th); if (typeof (items[i]) === 'string' || typeof (items[i]) === 'number') { if (typeof (f) === 'function') { th.style.background = f(i); } var n = items[i]; if (typeof (n) === 'number') { if (n === 0) { n = showzeros ? n : ""; } } th.appendChild(document.createTextNode(n)); } else if (items[i] instanceof HTMLElement) { th.appendChild(items[i]); } else { var x = items[i] == undefined ? "" : items[i].toString() th.appendChild(document.createTextNode(x)); } } t.appendChild(tr); } return t; } function createSVG(o) { return d3.create(o); } function createDOM(o) { return document.createElement(o); } function createButton(t, f, s) { var b = createDOM('button'); b.innerHTML = t; b.onclick = f; b.source = s; return b; } function removeAppend(p, c) { if (typeof (p) === 'string') { p = document.getElementById(p); } removeChildNodes(p); return p.appendChild(c); } function addLabelInput(d, t, i, addBreak) { append(d, t = document.createTextNode(t)); append(d, i); if (addBreak === undefined || addBreak) append(d, document.createElement('br')); } class PlotTool { Margin; Width; Height; SVG; G; X; Y; constructor(t, r, b, l, w, h) { this.Margin = {top: t, right: r, bottom: b, left: l}; this.Width = w - this.Margin.left - this.Margin.right; this.Height = h - this.Margin.top - this.Margin.bottom; this.SVG = createSVG('svg'); this.SVG.attr("width", this.Width + this.Margin.left + this.Margin.right) .attr("height", this.Height + this.Margin.top + this.Margin.bottom); this.G = this.SVG.append("g") .attr("transform", "translate(" + this.Margin.left + "," + this.Margin.top + ")"); } getWidth() { return this.Width; } getHeight() { return this.Height; } setTitle(t) { this.SVG.append("text") .attr("x", (this.getWidth() / 2)) .attr("y", (this.Margin.top / 2)) .attr("text-anchor", "middle") .style("font-size", "16px") .style("text-decoration", "underline") .text(t); } makeXY(xA, yA) { var d = []; for (var i = 0; i < xA.length; i++) { d.push({x: xA[i], y: yA[i]}); } return d; } plot(xA, yA) { var g = this.G; var x = this.X = d3.scaleLinear() .domain([d3.min(xA), d3.max(xA)]) .range([0, this.getWidth()]); g.append("g") .attr("transform", "translate(" + 0 + "," + this.getHeight() + ")") .call(d3.axisBottom(x)); // Add Y axis var maxY = d3.max(yA); var inc = .10 * Math.abs(maxY); var y = this.Y = d3.scaleLinear() .domain([d3.min(yA) - inc, maxY + inc]) .range([this.getHeight(), 0]); g.append("g") .call(d3.axisLeft(y)); var data = this.makeXY(xA, yA); // Add the line g.append("path") .datum(data) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-width", 1.5) .attr("d", d3.line() .x(function (d) { return x(d.x); }) .y(function (d) { return y(d.y); }) ); } addCircles(xA, yA) { var g = this.G; var data = this.makeXY(xA, yA); var x = this.X; var y = this.Y; data.forEach(function (d) { g.append('circle') .attr("cx", x(d.x)) .attr("cy", y(d.y)) .attr("r", 3) .style("fill", "red"); }); } addLine(xA, yA, c) { var data = this.makeXY(xA, yA); var x = this.X; var y = this.Y; var g = this.G; g.append("path") .datum(data) .attr("fill", "none") .attr("stroke", c) .attr("stroke-width", 1) .attr("d", d3.line() .x(function (d) { return x(d.x); }) .y(function (d) { return y(d.y); }) ); } node() { return this.SVG.node(); } } function plot(xA, yA, t, r, b, l, w, h) { var p = new PlotTool(t, r, b, l, w, h); p.plot(xA, yA); return p; } function append(p, c) { if (typeof (p) === 'string') { p = document.getElementById(p); } p.appendChild(c); return p; } function removeChildNodes(parent) { if (typeof (parent) === 'string') { parent = document.getElementById(parent); } while (parent.firstChild) { parent.removeChild(parent.firstChild); } return parent; } function createHeader(s, t) { var h = document.createElement(s); h.setAttribute('class', 'Header'); h.innerHTML = t; return h; } function createMatrixTableDiv(m, title, h) { var d = createDOM('div'); if (h !== undefined) append(d, createHeader("h5", h)); var t = createDOM('table'); addTableItems(t, 'th', title, null, true); m.forEach(function (r) { addTableItems(t, 'td', r, null, true); }); append(d, t); return d; } function erf(z) { var t = 1.0 / (1.0 + 0.5 * Math.abs(z)); var ans = 1.0 - t * Math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277))))))))); if (z >= 0.0) return ans; return -ans; } function phi(z) { return 0.5 * (1.0 + erf(z / Math.sqrt(2.0))); } function getEuroCallPriceForward(expirationDays, forw, volatility, rate, strike) { if (expirationDays <= 0.0) { return Math.max(0.0, forw - strike); } var discount = Math.exp(-rate * expirationDays / 365); var t = expirationDays / 365.0; var vsqrtT = volatility * Math.sqrt(t); var d1 = (Math.log(forw / strike) + 0.5 * t * volatility * volatility) / vsqrtT; var d2 = d1 - vsqrtT; return discount * (forw * phi(d1) - strike * phi(d2)); } function getPutParity(years, rate, forw, strike, call) { var dis = Math.exp(-rate * years); var box = dis * (forw - strike); var put = call - box; return Math.min(put, Math.max(forw - call / dis, 0)); } function getEuroCallPriceSpot(expirationDays, deliveryDays, spot, volatility, rate, yld, strike) { if (deliveryDays <= 0.0) { return Math.max(0.0, spot - strike); } var forw = spot * Math.exp((rate - yld) * deliveryDays / 365); var discount = Math.exp(-rate * expirationDays / 365); if (expirationDays <= 0.0) { return Math.max(0.0, (forw - strike) * discount); } return getEuroCallPriceForward(expirationDays, forw, volatility, rate, strike); } var MAXVOLCOUNT = 100; var OPTIONTOLERANCE = 0.00001; function getEuroCallVolatilitySpot(expirationDays, deliveryDays, spot, rate, yld, strike, target) { var max = 3; var min = 0.001; var vol = (max + min) / 2; var cnt = 0; var cp = getEuroCallPriceSpot(expirationDays, deliveryDays, spot, vol, rate, yld, strike); //while (((Math.abs(cp - target) > 0.001D ? 1 : 0) & (cnt < 50 ? 1 : 0)) != 0) { while ((Math.abs(cp - target) > OPTIONTOLERANCE) && (cnt < MAXVOLCOUNT)) { if (cp < target) { min = vol; } else { max = vol; } vol = (max + min) / 2; cp = getEuroCallPriceSpot(expirationDays, deliveryDays, spot, vol, rate, yld, strike); cnt++; } return vol; } function getEuroPutVolatilitySpot(expirationDays, deliveryDays, spot, rate, yld, strike, target) { var max = 3; var min = 0.001; var vol = (max + min) / 2; var cnt = 0; var cp = getEuroPutPriceSpot(expirationDays, deliveryDays, spot, vol, rate, yld, strike); //while (((Math.abs(cp - target) > 0.001D ? 1 : 0) & (cnt < 50 ? 1 : 0)) != 0) { while ((Math.abs(cp - target) > OPTIONTOLERANCE) && (cnt < MAXVOLCOUNT)) { if (cp < target) { min = vol; } else { max = vol; } vol = (max + min) / 2; cp = getEuroPutPriceSpot(expirationDays, deliveryDays, spot, vol, rate, yld, strike); cnt++; } return vol; } function getEuroPutPriceForward(expirationDays, forw, volatility, rate, strike) { if (expirationDays <= 0.0) { return Math.max(0.0, strike - forw); } var discount = Math.exp(-rate * expirationDays / 365); var t = expirationDays / 365.0; var vsqrtT = volatility * Math.sqrt(t); var d1 = (Math.log(forw / strike) + 0.5 * t * volatility * volatility) / vsqrtT; var d2 = d1 - vsqrtT; return discount * (strike * phi(-d2) - forw * phi(-d1)); } function getEuroPutPriceSpot(expirationDays, deliveryDays, spot, volatility, rate, yld, strike) { if (deliveryDays <= 0.0) { return Math.max(0.0, strike - spot); } var forw = spot * Math.exp((rate - yld) * deliveryDays / 365); if (expirationDays <= 0.0) { var discount = Math.exp(-rate * expirationDays / 365); return Math.max(0.0, (strike - forw) * discount); } return getEuroPutPriceForward(expirationDays, forw, volatility, rate, strike); } class ComplexNumber { constructor(re, im) { this.re = re; this.im = im; } add(addend) { // Make sure we're dealing with complex number. var complexAddend = this.toComplexNumber(addend); return new ComplexNumber(this.re + complexAddend.re, this.im + complexAddend.im); } subtract(subtrahend) { // Make sure we're dealing with complex number. var complexSubtrahend = this.toComplexNumber(subtrahend); return new ComplexNumber(this.re - complexSubtrahend.re, this.im - complexSubtrahend.im); } toExp() { var e = Math.exp(this.re); return new ComplexNumber(e * Math.cos(this.im), e * Math.sin(this.im)); } toRealPower(exponent) { if (exponent === 2) { return this.multiply(this); } var re = exponent * Math.log(this.abs()); var im = exponent * this.arg(); var scalar = Math.exp(re); return new ComplexNumber(scalar * Math.cos(im), scalar * Math.sin(im)); } toLog() { var magnitude = Math.sqrt(this.re * this.re + this.im * this.im); var angle = this.arg(); if (angle > Math.PI) { angle -= 2 * Math.PI; } return new ComplexNumber(Math.log(magnitude), angle); } arg() { return Math.atan2(this.im, this.re); } abs() { return this.abs2(this.re, this.im); } abs2(x, y) { var absX = Math.abs(x); var absY = Math.abs(y); if ((absX === 0.0) && (absY === 0.0)) { return 0.0; } if (absX >= absY) { var d = y / x; return absX * Math.sqrt(1.0 + d * d); } var d = x / y; return absY * Math.sqrt(1.0 + d * d); } multiply(multiplicand) { // Make sure we're dealing with complex number. var x = this.toComplexNumber(multiplicand); return new ComplexNumber(this.re * x.re - this.im * x.im, this.re * x.im + this.im * x.re); } divide(divider) { // Make sure we're dealing with complex number. var complexDivider = this.toComplexNumber(divider); // Get divider conjugate. var dividerConjugate = this.conjugate(complexDivider); // Multiply dividend by divider's conjugate. var finalDivident = this.multiply(dividerConjugate); // Calculating final divider using formula (a + bi)(a − bi) = a^2 + b^2 var finalDivider = (complexDivider.re * complexDivider.re) + (complexDivider.im * complexDivider.im); return new ComplexNumber(finalDivident.re / finalDivider, finalDivident.im / finalDivider); } /** * @param {ComplexNumber|number} number */ conjugate(number) { // Make sure we're dealing with complex number. var complexNumber = this.toComplexNumber(number); return new ComplexNumber(complexNumber.re, -1 * complexNumber.im); } /** * @return {number} */ getRadius() { return Math.sqrt((this.re * this.re) + (this.im * this.im)); } /** * @param {boolean} [inRadians] * @return {number} */ getPhase(inRadians = true) { let phase = Math.atan(Math.abs(this.im) / Math.abs(this.re)); if (this.re < 0 && this.im > 0) { phase = Math.PI - phase; } else if (this.re < 0 && this.im < 0) { phase = -(Math.PI - phase); } else if (this.re > 0 && this.im < 0) { phase = -phase; } else if (this.re === 0 && this.im > 0) { phase = Math.PI / 2; } else if (this.re === 0 && this.im < 0) { phase = -Math.PI / 2; } else if (this.re < 0 && this.im === 0) { phase = Math.PI; } else if (this.re > 0 && this.im === 0) { phase = 0; } else if (this.re === 0 && this.im === 0) { // More correctly would be to set 'indeterminate'. // But just for simplicity reasons let's set zero. phase = 0; } if (!inRadians) { phase = radianToDegree(phase); } return phase; } /** * @param {boolean} [inRadians] * @return {{radius: number, phase: number}} */ getPolarForm(inRadians = true) { return { radius: this.getRadius(), phase: this.getPhase(inRadians) }; } /** * Convert real numbers to complex number. * In case if complex number is provided then lefts it as is. * * @param {ComplexNumber|number} number * @return {ComplexNumber} */ toComplexNumber(number) { if (number instanceof ComplexNumber) { return number; } return new ComplexNumber(number, 0); } toString() { if (this.im > 0) { return "c(" + this.re + "+i*" + this.im + ")"; } if (this.im < 0) { return "c(" + this.re + "-i*" + -(this.im) + ")"; } return "c(" + this.re + ")"; } } function complexToArray(c) { var a = []; for (var i = 0; i < c.length; i++) { a.push([c[i].re, c[i].im]); } return a; } function fft_1d(array) { var u_r, u_i, w_r, w_i, t_r, t_i; var ln, nv2, k, l, le, le1, j, ip, i, n; n = array.length; ln = Math.floor(Math.log(n) / Math.LN2 + 0.5); nv2 = n / 2; j = 1; for (i = 1; i < n; i++) { if (i < j) { t_r = array[i - 1][0]; t_i = array[i - 1][1]; array[i - 1][0] = array[j - 1][0]; array[i - 1][1] = array[j - 1][1]; array[j - 1][0] = t_r; array[j - 1][1] = t_i; } k = nv2; while (k < j) { j -= k; k /= 2; } j = j + k; } for (l = 1; l <= ln; l++) /* loops thru stages */ { le = Math.floor(Math.exp(l * Math.LN2) + 0.5); le1 = le / 2; u_r = 1.0; u_i = 0.0; w_r = Math.cos(Math.PI / le1); w_i = -Math.sin(Math.PI / le1); for (j = 1; j <= le1; j++) /* loops thru 1/2 twiddle values per stage */ { for (i = j; i <= n; i += le) /* loops thru points per 1/2 twiddle */ { ip = i + le1; t_r = array[ip - 1][0] * u_r - u_i * array[ip - 1][1]; t_i = array[ip - 1][1] * u_r + u_i * array[ip - 1][0]; array[ip - 1][0] = array[i - 1][0] - t_r; array[ip - 1][1] = array[i - 1][1] - t_i; array[i - 1][0] = array[i - 1][0] + t_r; array[i - 1][1] = array[i - 1][1] + t_i; } t_r = u_r * w_r - w_i * u_i; u_i = w_r * u_i + w_i * u_r; u_r = t_r; } } return array; } function ifft_1d(array) { var LN_2 = Math.log(2.0); var u_r, u_i, w_r, w_i, t_r, t_i; var ln, nv2, k, l, le, le1, j, ip, i, n; n = array.length; ln = parseInt((Math.log(n) / LN_2 + 0.5)); nv2 = n / 2; j = 1; for (i = 1; i < n; i++) { if (i < j) { t_r = array[i - 1][0]; t_i = array[i - 1][1]; array[i - 1][0] = array[j - 1][0]; array[i - 1][1] = array[j - 1][1]; array[j - 1][0] = t_r; array[j - 1][1] = t_i; } k = nv2; while (k < j) { j = j - k; k = k / 2; } j = j + k; } for (l = 1; l <= ln; l++) /* loops thru stages */ { le = parseInt((Math.exp(l * LN_2) + 0.5)); le1 = le / 2; u_r = 1.0; u_i = 0.0; w_r = Math.cos(Math.PI / le1); w_i = Math.sin(Math.PI / le1); for (j = 1; j <= le1; j++) /* loops thru 1/2 twiddle values per stage */ { for (i = j; i <= n; i += le) /* loops thru points per 1/2 twiddle */ { ip = i + le1; t_r = array[ip - 1][0] * u_r - u_i * array[ip - 1][1]; t_i = array[ip - 1][1] * u_r + u_i * array[ip - 1][0]; array[ip - 1][0] = array[i - 1][0] - t_r; array[ip - 1][1] = array[i - 1][1] - t_i; array[i - 1][0] = array[i - 1][0] + t_r; array[i - 1][1] = array[i - 1][1] + t_i; } t_r = u_r * w_r - w_i * u_i; u_i = w_r * u_i + w_i * u_r; u_r = t_r; } } return array; } class CharacteristicFunction { Probabilities = null; DistDiv = null; getCharacteristicValue(c) { return c; } getProbabilities() { var n = 4096; if (this.Probabilities !== null) { return Probabilities; } //double min,max; var min = -Math.PI; var max = Math.PI; var b = []; // min = -4.0D; // max = 4.0D; var dx = max - min; var dxn = dx / n; var n2 = n / 2; var p2 = 2 * max; var c; for (var k = 0; k < n; k++) { var l = p2 * (k - n2) / dx; var line = this.getCharacteristicValue(new ComplexNumber(l, 0.0)); var exp = new ComplexNumber(0.0, -1.0 * l * (min + dxn / 2.0)).toExp(); var v = line.multiply(exp); b.push(v); // console.log(v.toString()); } var realNums = complexToArray(b); // append(document.body, createMatrixTableDiv(realNums, ["Real", "Img"], "Input")); // var fft = numeric.ifftpow2(realNums[0], realNums[1]); // console.log(fft); var ffx = ifft_1d(realNums); // append(document.body, createMatrixTableDiv(ffx, ["Real", "Img"], "Output")); // console.log(ffx); //FFT fft = new FFT(); //Complex[] bi = Complex.toComplex(fft.ifft_1d(Complex.toMatrix(b))); var probs = [];//new double[bi.length][3]; var p = 0.0, v, x, z; //var bi = fft.x; for (var i = 0; i < ffx.length; i++) { x = Math.abs(ffx[i][0] / dx); p += x * dxn; probs.push(z = [min + dxn * i, x, p]); } // append(document.body, createMatrixTableDiv(probs, ["x", "Denisty", "Cum"], "Probabilities")); this.mProbabilities = probs; return probs; } getDistributionName() { return "Generic Distribution"; } getDistGraph() { var p = this.getProbabilities(); var x = []; var y = []; for (var i = 0; i < p.length; i++) { x.push(p[i][0]); y.push(p[i][1]); } var plt = plot(x, y, 30, 30, 30, 50, 800, 500); plt.setTitle(this.getDistributionName()); var d = createDOM('div'); append(d, plt.node()); return d; } getDistDiv() { if (this.DistDiv === null) { var d = createDOM('div'); this.DistDiv = d; append(d, createHeader('h5', this.getDistributionName())); append(d, this.getDistGraph()); } return this.DistDiv; } } ; class NormalCharacteristic extends CharacteristicFunction { getCharacteristicValue(c) { return new ComplexNumber(Math.exp(-(c.re * c.re) / 2.0), 0.0); } getDistributionName() { return "Normal Distribution"; } } ; class GammaCharacteristic extends CharacteristicFunction { Shape = 3.0; Scale = 2.0; getCharacteristicValue(u) { var ONE = new ComplexNumber(1, 0); var I = new ComplexNumber(0, 1); return ONE.subtract(new ComplexNumber(0, u.re * this.Scale)).toRealPower(-this.Shape); } getDistributionName() { return "Gamma Distribution"; } } ; class AlphaCurve { AlphaName; Points = []; Model; ShortPoint ShortMem; LongPoint; LongMem; OuterDiv = createDOM('div'); MinError = createButton("Min Error", this.minError, this); SetToAlpha = createButton("Set to Alpha Curve ", this.setToAlpha, this); constructor(n, p, m) { this.AlphaName = n; this.Points = p; this.Model = m; this.ShortPoint = createNumberInput(.18, 0, 300, this.doPlot, n + '_SP', this, 0.001); this.LongPoint = createNumberInput(.18, 0, 300, this.doPlot, n + '_LP', this, 0.001); this.ShortMem = createNumberInput(.18, 0, 300, this.doPlot, n + '_SM', this, 0.001); this.LongMem = createNumberInput(.18, 0, 300, this.doPlot, n + '_LM', this, 0.001); this.ShortPoint.setAttribute('class', 'input'); this.LongPoint.setAttribute('class', 'input'); this.ShortMem.setAttribute('class', 'input'); this.LongMem.setAttribute('class', 'input'); } loadData(jo) { setInputValue(this.ShortPoint, jo.ShortPoint, .18); setInputValue(this.LongPoint, jo.LongPoint, .18); setInputValue(this.ShortMem, jo.ShortMem, 0.18); setInputValue(this.LongMem, jo.LongMem, 0.18); } getAlphaDiv() { var d = createDOM("div"); d.style = "margin-left: 20px"; append(d, this.getTable()); append(d, this.getGraphDiv()); return d; } getTable() { var t = createDOM('table'); addTableItems(t, 'th', [this.AlphaName, 'Point', 'Mem', 'Commands']); addTableItems(t, 'th', ['Short', this.ShortPoint, this.ShortMem, this.MinError]); addTableItems(t, 'th', ['Long', this.LongPoint, this.LongMem, this.SetToAlpha]); return t; } doPlot(e) { var t = e === undefined ? this : e.source; var d = t.OuterDiv; //this.minError(); removeAppend(d, t.getGraph()); return d; } minError(e) { var t = e.srcElement.source; var s = numeric.uncmin(function (x) { return t.getErrorX(x); }, [t.getAlpha(), t.getOmega(), t.getAlphaMem(), t.getOmegaMem()]).solution; t.ShortPoint.value = s[0]; t.LongPoint.value = s[1]; t.ShortMem.value = s[2]; t.LongMem.value = s[3]; t.doPlot(); } setToAlpha(e) { var t = e.srcElement.source; for (var i = 0; i < t.Points.length; i++) { if (t.Points[i] instanceof HTMLElement) { var m = t.Points[i].source; var y = m.getYears(); var p = t.getValue(y); t.Points[i].valueAsNumber = p; } } t.doPlot(); } getErrorX(x) { var e = 0; for (var i = 0; i < this.Points.length; i++) { if (this.Points[i] instanceof HTMLElement) { var m = this.Points[i].source; var y = m.getYears(); var p = this.getValueX(y, x); var v = this.Points[i].valueAsNumber; var d = (p - v); e += d * d; } } return e; } getGraph() { var d = createDOM('div'); var t = []; var p = []; var v = []; for (var i = 0; i < this.Points.length; i++) { if (this.Points[i] instanceof HTMLElement) { var m = this.Points[i].source; var y = m.getYears(); t.push(y); p.push(this.getValue(y)); v.push(this.Points[i].valueAsNumber); } } var svg = plot(t, p, 30, 30, 30, 50, 500, 300); svg.addCircles(t, v); svg.setTitle("Alpha Plot for " + this.AlphaName); append(d, svg.node()); return d; } getGraphDiv() { return this.doPlot(); } getAlpha() { return this.ShortPoint.valueAsNumber; } getOmega() { return this.LongPoint.valueAsNumber; } getAlphaMem() { return this.ShortMem.valueAsNumber; } getOmegaMem() { return this.LongMem.valueAsNumber; } getAlphaName() { return this.AlphaName; } getJsonObject() { return {AlphaName: this.getAlphaName(), ShortPoint: this.getAlpha(), LongPoint: this.getOmega(), ShortMem: this.getAlphaMem(), LongMem: this.getOmegaMem()}; } getValue(t) { return this.getValueX(t, [this.getAlpha(), this.getOmega(), this.getAlphaMem(), this.getOmegaMem()]); } getValueX(t, x) { var a2 = x[0]; a2 *= a2; var o2 = x[1]; o2 *= o2; var r = a2 * Math.exp(-t * x[2]) + o2 * (1 - Math.exp(-t * x[3])); return Math.sqrt(r); } } class PoissonCharacteristic extends CharacteristicFunction { Alpha = 2.0; getCharacteristicValue(u) { var ONE = new ComplexNumber(1, 0); var ue = new ComplexNumber(0, u.re).toExp(); var uem1Alpha = ue.subtract(ONE).multiply(this.Alpha); return uem1Alpha.toExp(); } getDistributionName() { return "Poisson Distribution"; } } ; function isNumber(x) { return Number.isFinite(x); } function add(a, b) { if (isNumber(a) && isNumber(b)) { return a + b; } if (a instanceof ComplexNumber && (isNumber(b) || b instanceof ComplexNumber)) { return a.add(b); } if (b instanceof ComplexNumber && (isNumber(a) || a instanceof ComplexNumber)) { return b.add(a); } var x = []; if (Array.isArray(a) && Array.isArray(b)) { for (var i = 0; i < a.length; i++) x.push(add(a[i], b[i])); return x; } else if (Array.isArray(a) && (isNumber(b) || b instanceof ComplexNumber)) { for (var i = 0; i < a.length; i++) x.push(add(a[i], b)); return x; } else if (Array.isArray(b) && (isNumber(a) || a instanceof ComplexNumber)) { for (var i = 0; i < b.length; i++) x.push(add(b[i], a)); return x; } console.log("need to handle add with " + typeof (a) + " and " + typeof (b)); return x; } function subtract(a, b) { if (isNumber(a) && isNumber(b)) { return a - b; } if (a instanceof ComplexNumber && (isNumber(b) || b instanceof ComplexNumber)) { return a.subtract(b); } if (b instanceof ComplexNumber && (isNumber(a) || a instanceof ComplexNumber)) { return ComplexNumber.toComplexNumber(a).subtract(b); } var x = []; if (Array.isArray(a) && Array.isArray(b)) { for (var i = 0; i < a.length; i++) x.push(subtract(a[i], b[i])); return x; } else if (Array.isArray(a) && (isNumber(b) || b instanceof ComplexNumber)) { for (var i = 0; i < a.length; i++) x.push(subtract(a[i], b)); return x; } else if (Array.isArray(b) && (isNumber(a) || a instanceof ComplexNumber)) { for (var i = 0; i < b.length; i++) x.push(subtract(a, b[i])); return x; } console.log("need to handle add with " + typeof (a) + " and " + typeof (b)); return x; } function multiply(a, b) { if (isNumber(a) && isNumber(b)) { return a * b; } if (a instanceof ComplexNumber && (isNumber(b) || b instanceof ComplexNumber)) { return a.multiply(b); } if (b instanceof ComplexNumber && (isNumber(a) || a instanceof ComplexNumber)) { return b.multiply(a); } var x = []; if (Array.isArray(a) && Array.isArray(b)) { for (var i = 0; i < a.length; i++) x.push(multiply(a[i], b[i])); return x; } else if (Array.isArray(a) && (isNumber(b) || b instanceof ComplexNumber)) { for (var i = 0; i < a.length; i++) x.push(multiply(a[i], b)); return x; } else if (Array.isArray(b) && (isNumber(a) || a instanceof ComplexNumber)) { for (var i = 0; i < b.length; i++) x.push(multiply(b[i], a)); return x; } console.log("need to handle multiply with " + typeof (a) + " and " + typeof (b)); return x; } function divide(a, b) { if (isNumber(a) && isNumber(b)) { return a / b; } if (a instanceof ComplexNumber && (isNumber(b) || b instanceof ComplexNumber)) { return a.divide(b); } if (b instanceof ComplexNumber && (isNumber(a) || a instanceof ComplexNumber)) { return b.divide(a); } var x = []; if (Array.isArray(a) && Array.isArray(b)) { for (var i = 0; i < a.length; i++) x.push(divide(a[i], b[i])); return x; } else if (Array.isArray(a) && (isNumber(b) || b instanceof ComplexNumber)) { for (var i = 0; i < a.length; i++) x.push(divide(a[i], b)); return x; } else if (Array.isArray(b) && (isNumber(a) || a instanceof ComplexNumber)) { for (var i = 0; i < b.length; i++) x.push(divide(a, b[i])); return x; } console.log("need to handle divide with " + typeof (a) + " and " + typeof (b)); return x; } function getIndexArray(n) { a = []; for (var i = 0; i < n; i++) { a.push(i + 1); } return a; } function getPowerXtoArray(x, a) { var c = []; for (var i = 0; i < a.length; i++) { c.push(Math.pow(x, a[i])); } return c; } const TINY = 0.00000001; function isEqual(a, b) { return Math.abs(a - b) < TINY; } function zeroOneEquality(x, y) { var z = []; for (var i = 0; i < x.length; i++) { z.push(x[i] === y ? 1.0 : 0.0); } return z; } function complexArraytoExp(a) { var z = []; for (var i = 0; i < a.length; i++) { z.push(a[i].toExp()); } return z; } function setInputValue(inp, v, alterV) { inp.value = v !== undefined ? v : alterV; } function createDateInput(d, id, f, s) { var o = createDOM('input'); o.setAttribute("type", "date"); var sd = formatDate(d); o.setAttribute("value", sd); o.source = s; o.onchange = f; return o; } function createNumberInput(v, min, max, f, id, s, step) { var o = createDOM("Input"); o.type = 'Number'; o.minValue = min; o.maxValue = max; o.value = v; if (f !== undefined) { o.onchange = function () { f(o); }; } if (id !== undefined) { o.id = id; } if (s !== undefined) { o.source = s; } if (step !== undefined) { o.step = step; } return o; } class OptionCharacteristicFunction extends CharacteristicFunction { alpha = 1.25; Rate; ExpDate; Yld; Vol; Spot; CurrentSpot; RawPrices = null; FilteredPrices = null; Lag = 5; LogSpot; Period; constructor(date, pSpot, pCurrentSpot, pRate, pYield, pVol, pYears) { super(); this.ExpDate = createDateInput(date, 'ExpDate', doChange, this); this.Spot = isNumber(pSpot) ? createNumberInput(pSpot, 0, .10, doChange, 'Spot', this, 0.10) : pSpot; this.CurrentSpot = isNumber(pCurrentSpot) ? createNumberInput(pCurrentSpot, 0, .10, doChange, 'CurrentSpot', this, 0.10) : pCurrentSpot; this.LogSpot = Math.log(this.getSpot()); this.Rate = createNumberInput(pRate, 0, .10, doChange, 'Rate', this, 0.001); this.Years = createNumberInput(pYears, 1.0 / 365, 30, doChange, 'Years', this, 1 / 365); this.Yld = createNumberInput(pYield, 0, .10, doChange, 'Yield', this, 0.001); this.Vol = createNumberInput(pVol, 0.001, 3, doChange, 'Vol', this, 0.001); this.Vol.setAttribute('class', 'input'); this.Rate.setAttribute('class', 'input'); this.Yld.setAttribute('class', 'input'); this.ExpDate.setAttribute('class', 'input'); } getJsonObject() { var jo = {ExpDate: this.ExpDate.value, Rate: this.Rate.valueAsNumber, Yld: this.Yld.valueAsNumber, Vol: this.Vol.valueAsNumber}; return jo; } setValue(jo) { this.Rate.value = jo.Rate; this.Yld.value = jo.Yld; this.Vol.value = jo.Vol; } setPeriod(p) { this.Period = p; } getPeriod() { return this.Period; } getLogSpot() { return this.LogSpot === null ? this.LogSpot = Math.log(this.getSpot()) : this.LogSpot; } reset() { this.RawPrices = null; this.FilteredPrices = null; this.LogSpot = null; } getSpot() { return this.Spot.valueAsNumber; } getRate() { return this.Rate.valueAsNumber; } getYield() { return this.Yld.valueAsNumber; } getStdMove(sd) { var y = this.getYears(); var spot = this.getSpot(); var vol = this.getIVol(); return sd * spot * (Math.exp(vol * Math.sqrt(y)) - 1); } getExpDate() { return this.ExpDate.valueAsDate; } getYears() { return years(this.getExpDate()); } getIVol() { return this.Vol.valueAsNumber; } getVol2() { return Math.pow(this.getIVol(), 2); } getDiscount() { return Math.exp(-this.getRate() * this.getYears()); } getForward() { return this.getSpot() * Math.exp(this.getYears() * (this.getRate() - this.getYield())); } getFFTPSI(v) { var dis = new ComplexNumber(this.getDiscount(), 0.0); var y = [];//new Complex[v.length]; var bottom = [];// new Complex[v.length]; for (var i = 0; i < v.length; i++) { y.push(this.getCharacteristicValue(new ComplexNumber(v[i], -(this.alpha + 1.0)))); bottom.push(new ComplexNumber(this.alpha * this.alpha + this.alpha - v[i] * v[i], (2.0 * this.alpha + 1.0) * v[i])); } var y1 = multiply(y, dis); var result = divide(y1, bottom); return result; } getVolSlope(k) { var d = 0.001 * k; var p0 = this.getInterpolatedPrice(k - d); var p1 = this.getInterpolatedPrice(k + d); return (p1[3] - p0[3]) / (p1[0] - p0[0]); } getForwardSlope() { return this.getVolSlope(this.getForward()); } getForwardSlopeLine() { var f = this.getForward(); var s = this.getForwardSlope(); var v = this.getInterpolatedPrice(f)[3]; var a = (v - s * f); var l = {alpha: a, beta: s, BegPoint: [0.9 * f, a + s * f * .90], EndPoint: [1.1 * f, a + s * f * 1.1], MidPoint: [f, v]}; l.x = [l.BegPoint[0], l.MidPoint[0], l.EndPoint[0]]; l.y = [l.BegPoint[1], l.MidPoint[1], l.EndPoint[1]]; return l; } getPriceDiv(a, z, inc) { var d = createDOM('div'); var v = [], p; while (a <= z) { p = this.getInterpolatedPrice(a); v.push([p[0], p[1].toFixed(2), p[2].toFixed(2), p[3].toFixed(4)]); a += inc; } append(d, createMatrixTableDiv(v, ["Strike", "Call", "Put", "Vol"], "Prices")); append(d, createDOM('br')); append(d, document.createTextNode("Period Vol Swap:" + this.getVolIndicatorStrip().toFixed(3))); return d; } getVolIndicatorStrip() { var f = this.getForward(); var p = this.getFilteredPrices(); var spot = this.getSpot(); var sd = this.getStdMove(3); var alpha = spot - sd; var omega = spot + sd; var z = 200; var diff = (omega - alpha) / z; var k; var dis = 1 / this.getDiscount(); var sum = 0; var kl = alpha; var price; for (var i = 0; i < z; i++) { k = alpha + i * diff; if (k <= f) { price = this.getInterpolatedPrice(k)[2]; kl = k; } else { price = this.getInterpolatedPrice(k)[1]; } sum += price / (k * k); } var t = this.getYears(); sum *= (2 * diff / t) * dis; sum -= Math.pow(f / kl - 1, 2) / t; return 100 * Math.sqrt(sum); } getVolGraphItems(a, z) { var sd = this.getStdMove(3); var alpha = a === undefined ? this.getSpot() - sd : a; var omega = z === undefined ? this.getSpot() + sd : z; var z = 200; var diff = (omega - alpha) / z; var k = []; var v = []; while (alpha <= omega) { k.push(alpha); v.push(this.getInterpolatedPrice(alpha)[3]); alpha += diff; } return {k: k, v: v}; } getFilteredPrices() { if (this.FilteredPrices !== null) { return this.FilteredPrices; } var lag = this.Lag; if (lag === 0) { return x; } var x = this.getRawPrices(); var v = []; var y = this.getYears(); var rate = this.getRate(); var days = 365.0 * y; var vol, call, put, k; var forw = this.getForward(); var spot = this.getSpot(); var yld = this.getYield(); var iVol = this.getIVol(); for (var i = 0; i < x.length; i++) { k = x[i][0]; if (k > 0.01 * spot) { call = x[i][1]; put = getPutParity(y, rate, forw, k, call); if (call > 0.01 && put > .01) { if (k < forw) { vol = getEuroPutVolatilitySpot(days, days, spot, rate, yld, k, put); } else { vol = getEuroCallVolatilitySpot(days, days, spot, rate, yld, k, call); } if (vol > iVol / 5 && iVol < 3) v.push([k, call, put, vol]); } } } var v2 = [], rv, ma = 0; if (v.length > lag + 5) { for (var i = 0; i < lag; i++) ma += v[i][3]; for (var i = lag; i < v.length; i++) { rv = v[i]; vol = ma / lag; ma += rv[3] - v[i - lag][3]; k = rv[0]; call = getEuroCallPriceSpot(days, days, spot, vol, rate, yld, k); put = getEuroPutPriceSpot(days, days, spot, vol, rate, yld, k); v2.push([k, call, put, vol]); } } return this.FilteredPrices = v2; } getInterpolatedPrice(k) { var p = this.getFilteredPrices(); if (p.length < 10) { return [k, 0, 0, 0]; } var a = 0, z = p.length - 1, i; while (z - a > 1) { i = parseInt((a + z) / 2); if (k > p[i][0]) { a = i; } else { z = i; } } var y = this.getYears(); var rate = this.getRate(); var days = 365.0 * y; var vol, call, put, k; var spot = this.getSpot(); var yld = this.getYield(); var top = p[z]; var bottom = p[a]; var vol = bottom[3] + (k - bottom[0]) * (top[3] - bottom[3]) / (top[0] - bottom[0]); var call = getEuroCallPriceSpot(days, days, spot, vol, rate, yld, k); var put = getEuroPutPriceSpot(days, days, spot, vol, rate, yld, k); return[k, call, put, vol]; } setStraddle(targetStraddle) { var f = this.getForward(); var days = this.getYears() * 365; var p = this.getInterpolatedPrice(f); var currentStraddle = p[1] + p[2]; var cnt = 0; while (cnt < 10 && Math.abs(targetStraddle - currentStraddle) > 0.01) { var targetCall = p[1] + (targetStraddle - currentStraddle) / 2; var vol = getEuroCallVolatilitySpot(days, days, this.getSpot(), this.getRate(), this.getYield(), f, targetCall); var changeVol = p[3] - vol; this.Vol.value = this.Vol.valueAsNumber - changeVol; this.reset(); p = this.getInterpolatedPrice(f); currentStraddle = p[1] + p[2]; cnt++; } } getRawPrices() { var n = 4096; if (this.RawPrices === null) { // Complex[] c = new Complex[n]; var lambda = 0.01; var eta = 2 * Math.PI / lambda / n; var b = n * lambda / 2.0; var j = getIndexArray(n); //double[] etas = Stats.mult(Stats.subtract(Stats.add(Stats.getPowerXtoAarray(-1.0D, j), 3.0D), Stats.zeroOneEquality(j, 1.0D)), eta / 3.0D); // append(document.body, createMatrixTableDiv(getPowerXtoArray(-1.0, j), ["PowerJ"], "PowerJ")); var powJ = getPowerXtoArray(-1.0, j); var powJplus3 = add(powJ, 3); var zero1 = zeroOneEquality(j, 1.0); var powJplus3MinusZero1 = subtract(powJplus3, zero1); var etas = multiply(powJplus3MinusZero1, eta / 3.0); // append(document.body, createMatrixTableDiv(etas, ["etas"], "etas")); // var vj = multiplyDoubleArrayWithConstant(addConstanttoArray(j, -1.0), eta); var vj = multiply(add(j, -1.0), eta); var ib = new ComplexNumber(0, 1).multiply(new ComplexNumber(b, 0.0)); var eib = complexArraytoExp(multiply(vj, ib)); //var eib = complexArraytoExp(multDoubleArrayComplex(vj, ib)); // append(document.body, createMatrixTableDiv(eib, ["complex"], "eib")); //append(document.body, createMatrixTableDiv(vj, ["vj"], "vj")); var PSI = this.getFFTPSI(vj); // append(document.body, createMatrixTableDiv(complexToArray(PSI), ["Real","Img"], "PSI")); var xj = multiply(eib, PSI); // append(document.body, createMatrixTableDiv(PSI, ["PSI"], "xj")); xj = multiply(xj, etas); var realNums = complexToArray(xj); // append(document.body, createMatrixTableDiv(realNums, ["real", "Img"], "realNums")); var fft = fft_1d(realNums); // var test = numeric.transpose(new numeric.T(numeric.transpose(realNums)).fft()); // append(document.body, createMatrixTableDiv(test, ["real", "Img"], "test")); // append(document.body, createMatrixTableDiv(fft, ["Real", "Img"], "fft")); //Complex[] fftResults = Complex.toComplex(fft.fft_1d(Complex.toMatrix(xj))); var res = []; for (var i = 0; i < fft.length; i++) { var ku = -b + lambda * i; res.push([Math.exp(ku), Math.exp(-this.alpha * ku) * fft[i][0] / Math.PI]); } this.RawPrices = res; } return this.RawPrices; } } class BlackScholesCharacteristic extends OptionCharacteristicFunction { Vol2; YearsC; Vol2C; YearVol2C; constructor(d, pSpot, pCurrentSpot, pRate, pYield, pVol, pYear) { super(d, pSpot, pCurrentSpot, pRate, pYield, pVol, pYear); this.Vol2 = (this.getIVol() * this.getIVol()); var drift = this.getRate() - this.getYield() - this.Vol2 / 2.0; var driftT = drift * this.getYears(); this.Drift = new ComplexNumber(this.getLogSpot() + driftT, 0.0); this.YearsC = new ComplexNumber(this.getYears(), 0); this.Vol2C = new ComplexNumber(this.Vol2 * 0.5, 0); this.YearVol2C = this.YearsC.multiply(this.Vol2C); } reset() { super.reset(); this.Vol2 = (this.getIVol() * this.getIVol()); var drift = this.getRate() - this.getYield() - this.Vol2 / 2.0; var driftT = drift * this.getYears(); this.Drift = new ComplexNumber(this.getLogSpot() + driftT, 0.0); this.YearsC = new ComplexNumber(this.getYears(), 0); this.Vol2C = new ComplexNumber(this.Vol2 * 0.5, 0); this.YearVol2C = this.YearsC.multiply(this.Vol2C); } getCharacteristicValue(u) { var ui = new ComplexNumber(0, 1).multiply(u); var nud = ui.multiply(this.Drift); var uu = u.multiply(u); uu = uu.multiply(this.YearVol2C); var result = nud.subtract(uu).toExp(); return result; } } class HestonCharacteristic extends OptionCharacteristicFunction { LongVol; Speed; VolVol; Corr; constructor(d, pSpot, pCurrentSpot, pRate, pYield, pShortVol, pLongVol, pSpeed, pVolVol, pCorr) { super(d, pSpot, pCurrentSpot, pRate, pYield, pShortVol); this.Corr = createNumberInput(pCorr, -1, 1, doChange, 'Corr', this, 0.001); this.Speed = createNumberInput(pSpeed, 0, 1, doChange, 'Speed', this, 0.001); this.LongVol = createNumberInput(pLongVol, 0, 3, doChange, 'LongVol', this, 0.001); this.VolVol = createNumberInput(pVolVol, 0, .10, doChange, 'VolVol', this, 0.001); this.LongVol.setAttribute('class', 'input'); this.Speed.setAttribute('class', 'input'); this.VolVol.setAttribute('class', 'input'); this.Corr.setAttribute('class', 'input'); } getJsonObject() { var jo = super.getJsonObject(); jo.Corr = this.getCorrelation(); jo.Speed = this.getSpeed(); jo.LongVol = this.getLongVol(); jo.VolVol = this.getVolVol(); return jo; } setValue(jo) { super.setValue(jo); this.Corr.value = jo.Corr; this.Speed.value = jo.Speed; this.LongVol.value = jo.LongVol; this.VolVol.value = jo.VolVol; } getSpeed() { return this.Speed.valueAsNumber; } getVolVol() { return this.VolVol.valueAsNumber; } getEta() { return this.getVolVol() * this.getVolVol(); } getEta2() { return Math.pow(this.getEta(), 2); } getCorrelation() { return this.Corr.valueAsNumber; } getLongVol() { return this.LongVol.valueAsNumber; } getVBar() { return Math.pow(this.getLongVol(), 2); } getCharacteristicValue(u) { var uu = u.multiply(u); var K = new ComplexNumber(this.getSpeed(), 0); // Complex L = new Complex(eta, 0); var LL = new ComplexNumber(this.getEta2(), 0); var I = new ComplexNumber(0, 1); var ONE = new ComplexNumber(1, 0); var Line1 = (I.multiply(u).multiply(new ComplexNumber(this.getLogSpot() + (this.getRate() - this.getYield()) * this.getYears(), 0)).toExp()); var b = I.multiply(u).multiply(new ComplexNumber(this.getCorrelation() * this.getEta(), 0)); var BK2 = b.subtract(K).toRealPower(2); var d = BK2.add(LL.multiply(uu.add(I.multiply(u)))).toRealPower(0.5); var JJ = K.subtract(b).add(d); var JJM = K.subtract(b).subtract(d); var g1 = JJ.divide(JJM); var g2 = ONE.divide(g1); var oneMinusG2 = ONE.subtract(g2); var TAU = new ComplexNumber(this.getYears(), 0); var eDT = d.multiply(TAU).multiply(new ComplexNumber(-1, 0)).toExp(); var ratio = ONE.subtract(g2.multiply(eDT)).divide(oneMinusG2); var ratio1 = ratio.toLog(); var Line2 = new ComplexNumber(this.getVBar() * this.getSpeed() / this.getEta2(), 0).multiply(JJM.multiply(TAU).subtract((new ComplexNumber(2, 0)).multiply(ratio1))).toExp(); var Line3 = (new ComplexNumber(this.getVol2() / this.getEta2(), 0).multiply(JJM).multiply(ONE.subtract(eDT)).divide(ONE.subtract(g2.multiply(eDT)))).toExp(); return Line1.multiply(Line2).multiply(Line3); } } class VarianceGammaCharacteristic extends OptionCharacteristicFunction { GammaRate; GammaVariance; constructor(d, spot, pCurrentSpot, rate, yld, sigma, gammaRate, gammaVariance, years) { super(d, spot, pCurrentSpot, rate, yld, sigma, years); this.GammaRate = createNumberInput(gammaRate, -1, 1, doChange, 'GammaRate', this, 0.001); this.GammaVariance = createNumberInput(gammaVariance, 0, 1, doChange, 'GammaVariance', this, 0.001); this.GammaRate.setAttribute('class', 'input'); this.GammaVariance.setAttribute('class', 'input'); } getJsonObject() { var jo = super.getJsonObject(); jo.GammaRate = this.getGammaRate(); jo.GammaVariance = this.getGammaVariance(); return jo; } setValue(jo) { super.setValue(jo); this.GammaRate.value = jo.GammaRate; this.GammaVariance.value = jo.GammaVariance; } getSigma() { return this.getIVol(); } getGammaVariance() { return this.GammaVariance.valueAsNumber; } getGammaRate() { return this.GammaRate.valueAsNumber; } getCharacteristicValue(u) { var sigma = this.getSigma(); var rate = this.getRate(); var yld = this.getYield(); var years = this.getYears(); var gammaVariance = this.getGammaVariance(); var gammaRate = this.getGammaRate(); var w = new ComplexNumber(this.getLogSpot() + years * (rate - yld) + years / gammaVariance * Math.log(1.0 - gammaRate * gammaVariance - 0.5 * gammaVariance * sigma * sigma), 0.0); var a = new ComplexNumber(1, 0); var I = new ComplexNumber(0, 1); var b = new ComplexNumber(0.5 * sigma * sigma * gammaVariance * gammaVariance, 0.0).multiply(u).multiply(u); var c = new ComplexNumber(0.0, -gammaVariance * gammaRate).multiply(u); var y0 = I.multiply(w).multiply(u).toExp(); var result = a.add(b).add(c).toRealPower(-years / gammaVariance).multiply(y0); return result; } } class CompleteModel { Spot = createNumberInput(100, 0, .10, doChange, 'Spot', this); LiveSpot = createNumberInput(100, 0, .10, doChange, 'LiveSpot', this); VolAlphaCurve; Periods = []; LastPeriod = 2; BegStrike = createNumberInput(25, 0, .10, doChange, 'BegStrike_' + this.getModelCode(), this); EndStrike = createNumberInput(200, 0, .10, doChange, 'EndStrike_' + this.getModelCode(), this); StrikeInc = createNumberInput(5, 0, .10, doChange, 'StrikeInc_' + this.getModelCode(), this); Alphas = []; constructor() { this.BegStrike.setAttribute('class', '12px'); this.EndStrike.setAttribute('class', '12px'); this.StrikeInc.setAttribute('class', '12px'); } addAlpha(a) { this.Alphas.push(a); } getJsonObject() { var jo = {ModelName: this.getModelCode(), Spot: this.getSpot(), BegStrike: this.getBegStrike(), EndStrike: this.getEndStrike(), StrikeInc: this.getStrikeInc() }; jo.Periods = []; for (var i = 0; i < this.Periods.length; i++) { jo.Periods.push(this.Periods[i].getJsonObject()); } jo.Alphas = []; for (var i = 0; i < this.Alphas.length; i++) { jo.Alphas.push({AP: i, Alpha: this.Alphas[i].getJsonObject()}); } return jo; } getLiveSpot() { return this.LiveSpot.valueAsNumber; } getSpot() { return this.Spot.valueAsNumber; } saveData() { var s = "IgoeCharacteristic_" + this.getModelCode(); try { window.localStorage.setItem(s, JSON.stringify(this.getJsonObject())); console.log('Stored Data Model' + this.getModelCode()); } catch (error) { console.log(error); } } readData() { var s = "IgoeCharacteristic_" + this.getModelCode(); try { var jo = JSON.parse(window.localStorage.getItem(s)); this.loadData(jo); this.reset(); console.log('Read Data Model' + this.getModelCode()); } catch (error) { console.log(error); } return this; } loadData(jo) { setInputValue(this.Spot, jo.Spot, 100); setInputValue(this.StrikeInc, jo.StrikeInc, 5); setInputValue(this.BegStrike, jo.BegStrike, 50); setInputValue(this.EndStrike, jo.EndStrike, 150); if (jo.Periods !== undefined) { for (var i = 0; i < jo.Periods.length; i++) { var p = this.getPeriodExpDate(jo.Periods[i].ExpDate); if (p !== null) { p.setValue(jo.Periods[i]); } } } if (jo.Alphas !== undefined) { for (var i = 0; i < jo.Alphas.length; i++) { var a = this.getAlpha(jo.Alphas[i].Alpha.AlphaName); if (a !== null) { a.loadData(jo.Alphas[i].Alpha); } } } } getAlpha(n) { for (var i = 0; i < this.Alphas.length; i++) { if (this.Alphas[i].getAlphaName() === n) { return this.Alphas[i]; } } return null; } getPeriodExpDate(d) { for (var i = 0; i < this.Periods.length; i++) { if (this.Periods[i].ExpDate.value === d) { return this.Periods[i]; } } return null; } setAlphas() { this.VolAlphaCurve = new AlphaCurve("Vol", this.getInputRow("Vol"), this); this.addAlpha(this.VolAlphaCurve); } getBegStrike() { return this.BegStrike.valueAsNumber; } getEndStrike() { return this.EndStrike.valueAsNumber; } setLastPeriod(p) { this.LastPeriod = p; } getLastPeriod() { return this.LastPeriod; } getStrikeInc() { return this.StrikeInc.valueAsNumber; } getGraphDiv(p) { var outer = createDOM('div'); outer.style = "margin-left: 200px"; var m = this.Periods[this.getLastPeriod()]; var vp = m.getVolGraphItems(); var svg = plot(vp.k, vp.v, 30, 30, 30, 50, 500, 300); var d = createDOM('div'); d.setAttribute('class', 'LeftDiv'); // d.setAttribute('class','RightDiv'); svg.setTitle("Vol Graph Period:" + (1 + this.getLastPeriod())); append(d, svg.node()); append(outer, d); append(outer, d = this.VolAlphaCurve.getAlphaDiv()); var sl = m.getForwardSlopeLine(); svg.addLine(sl.x, sl.y, 'blue'); d.style = "margin-left: 200px"; return outer; } getStrikeDiv() { var d = createDOM('div'); d.setAttribute("class", 'input'); d.style.width = '100px'; d.style.float = 'left'; addLabelInput(d, "Beg Strike", this.BegStrike); addLabelInput(d, "End Strike", this.EndStrike); addLabelInput(d, "Strike Incr", this.StrikeInc); return d; } getCompleteDiv() { var d = createDOM('div'); append(d, createHeader('h5', this.getModelName())); var top = createDOM('div'); append(top, this.getStrikeDiv()); append(top, this.getGraphDiv(3)); append(d, top); addLabelInput(d, "Spot", this.Spot, false); addLabelInput(d, "Live Spot", this.LiveSpot, false); append(d, createButton("Rip Staddles", this.ripStraddles, this)); var t = this; append(d, createButton("Back to Table", function () { removeAppend('StrikeDiv', t.getPriceTable()); }, this)); append(d, this.getTableDiv()); return d; } ripStraddles(e) { var t = e.srcElement === undefined ? e.source : e.srcElement.source; var sd = t.getStraddleDiv(); removeAppend('StrikeDiv', sd); } finishTableDiv(t) { var a = this.getBegStrike(); var z = this.getEndStrike(); var inc = this.getStrikeInc(); var tt = this; while (a <= z) { var r = this.getRow(a); addTableItems(t, 'td', r, function (d) { if (d === 0 || d === r.length - 1) { return 'lightblue'; } d--; return d % 2 === 0 ? tt.getColor(a, d) : 'pink'; }, false); a += inc; } return t; } setPeriodStraddle(e) { var t = e.source; var p = e.period; var change = e.valueAsNumber; var period = t.Periods[p]; var p = period.getInterpolatedPrice(period.getForward()); var currentStraddle = p[1] + p[2]; period.setStraddle(currentStraddle + change); t.ripStraddles(e); } getStraddleDiv() { var d = createDOM('div'); var t = createDOM('table'); append(d, t); addTableItems(t, 'th', ['Date', 'Strike', 'Call', 'Put', 'Straddle', 'Vol', 'Increase']); var input; for (var i = 0; i < this.Periods.length; i++) { var dt = this.Periods[i].ExpDate.value; var f = this.Periods[i].getForward(); var p = this.Periods[i].getInterpolatedPrice(f); input = createNumberInput(0, -20, 20, this.setPeriodStraddle, 'setPeriodStraddle' + i, this, 0.25); input.period = i addTableItems(t, 'td', [dt, f.toFixed(2), p[1].toFixed(2), p[1].toFixed(2), (p[1] + p[2]).toFixed(2), p[3].toFixed(4), input], function (d) { return d === 0 ? 'lightblue' : 'white'; }); } return d; } getColor(k, d) { var f = this.Periods[parseInt(d / 2)].getForward(); return Math.abs((k - f) / k) < 0.01 ? 'lightgrey' : 'white'; } finishAddParam(t) { } getPriceTable() { var t = createDOM('table'); var tHead = createDOM('thead'); append(t, tHead); tHead.position = 'sticky'; t.setAttribute('class', 'PriceTable'); addTableItems(tHead, 'td', this.getInputRow("ExpDate")); addTableItems(tHead, 'td', this.getInputRow("Vol")); addTableItems(tHead, 'td', this.getInputRow("Rate")); addTableItems(tHead, 'td', this.getInputRow("Yield")); this.finishAddParam(tHead); var items = ["Strike"]; for (var i = 0; i < this.getPeriodCount(); i++) { items.push("Call"); items.push("Strike"); } items.push("Strike"); addTableItems(tHead, 'th', items); var items = ["Vol Swap"]; var slope = ["Forw Slope"]; ; for (var i = 0; i < this.getPeriodCount(); i++) { items.push(this.Periods[i].getVolIndicatorStrip().toFixed(4)); items.push(""); slope.push(this.Periods[i].getForwardSlope().toFixed(4)); slope.push(""); } addTableItems(tHead, 'th', items); addTableItems(tHead, 'th', slope); this.finishTableDiv(t); return t; } getTableDiv() { var d = createDOM('div'); d.id = 'StrikeDiv'; // d.setAttribute('class', "tableContainer"); append(d, this.getPriceTable()); return d; } getPeriodCount() { return this.Periods.length; } getInputRow(s) { var items = [s]; if (s === "Vol") { this.Periods.forEach(function (p) { items.push(p.Vol); items.push(""); }); } else if (s === "Rate") { this.Periods.forEach(function (p) { items.push(p.Rate); items.push(""); }); } else if (s === "Yield") { this.Periods.forEach(function (p) { items.push(p.Yld); items.push(""); }); } else if (s === "ExpDate") { this.Periods.forEach(function (p) { items.push(p.ExpDate); items.push(""); }); } return items.length > 1 ? items : []; } getRow(k) { var items = []; items.push(k.toFixed(2)); for (var i = 0; i < this.Periods.length; i++) { var cp = this.Periods[i].getInterpolatedPrice(k); items.push(cp[1].toFixed(2)); items.push(cp[2].toFixed(2)); } items.push(k.toFixed(2)); return items; } getModelCode() { return "Blank"; } reset() { for (var i = 0; i < this.Periods.length; i++) { this.Periods[i].reset(); } } } class BSModel extends CompleteModel { constructor() { super(); var m; var d = new Date(); for (var i = 0; i < 12; i++) { d = nextMonth(d); this.Periods.push(m = new BlackScholesCharacteristic(d, this.Spot, this.LiveSpot, 0.01, 0.012, 0.18, (i + 1) / 12)); m.setPeriod(i); } this.setAlphas(); } getModelCode() { return "BS"; } getModelName() { return "Black Scholes Characteristic Function Model"; } } class HestonModel extends CompleteModel { constructor() { super(); var m; var d = new Date(); for (var i = 0; i < 12; i++) { d = nextMonth(d); this.Periods.push(m = new HestonCharacteristic(d, this.Spot, this.LiveSpot, 0.01, 0.012, 0.18, 0.18, 0.1, 0.8, -0.8, (i + 1) / 12)); m.setPeriod(i); } this.setAlphas(); } getModelCode() { return "Heston"; } getModelName() { return "Heston Characteristic Function Model"; } finishAddParam(t) { addTableItems(t, 'td', this.getInputRow("LongVol")); addTableItems(t, 'td', this.getInputRow("VolVol")); addTableItems(t, 'td', this.getInputRow("Corr")); addTableItems(t, 'td', this.getInputRow("Speed")); } setAlphas() { super.setAlphas(); this.addAlpha(new AlphaCurve("LongVol", this.getInputRow("LongVol"), this)); this.addAlpha(new AlphaCurve("VolVol", this.getInputRow("VolVol"), this)); this.addAlpha(new AlphaCurve("Speed", this.getInputRow("Speed"), this)); this.addAlpha(new AlphaCurve("Corr", this.getInputRow("Corr"), this)); } getInputRow(s) { var items = super.getInputRow(s); if (items.length > 0) { return items; } items.push(s); if (s === "VolVol") { this.Periods.forEach(function (p) { items.push(p.VolVol); items.push(""); }); } else if (s == "Corr") { this.Periods.forEach(function (p) { items.push(p.Corr); items.push(""); }); } else if (s === "Speed") { this.Periods.forEach(function (p) { items.push(p.Speed); items.push(""); }); } else if (s === "LongVol") { this.Periods.forEach(function (p) { items.push(p.LongVol); items.push(""); }); } return items.length ? items : []; } } class VarianceGammaModel extends CompleteModel { constructor() { super(); var m; var d = new Date(); for (var i = 0; i < 12; i++) { d = nextMonth(d); this.Periods.push(m = new VarianceGammaCharacteristic(d, this.Spot, this.LiveSpot, 0.0525, 0.02, 0.15 + i * .002, -0.1, 1.0, (i + 1) / 12)); m.setPeriod(i); } this.setAlphas(); } getModelCode() { return "VG"; } getModelName() { return "Variance Gamma Function Model"; } setAlphas() { super.setAlphas(); this.addAlpha(new AlphaCurve("GammaRate", this.getInputRow("GammaRate"), this)); this.addAlpha(new AlphaCurve("GammaVariance", this.getInputRow("GammaVariance"), this)); } finishAddParam(t) { addTableItems(t, 'td', this.getInputRow("GammaRate")); addTableItems(t, 'td', this.getInputRow("GammaVariance")); } getInputRow(s) { var items = super.getInputRow(s); if (items.length > 0) { return items; } items.push(s); if (s === "GammaRate") { this.Periods.forEach(function (p) { items.push(p.GammaRate); items.push(""); }); } else if (s === "GammaVariance") { this.Periods.forEach(function (p) { items.push(p.GammaVariance); items.push(""); }); } return items.length ? items : []; } } var workingModel; function doHeston() { workingModel.saveData(); workingModel = hestonModel; removeAppend('solutionDiv', workingModel.getCompleteDiv()); } function doVarianceGamma() { workingModel.saveData(); workingModel = varianceGammaModel; removeAppend('solutionDiv', workingModel.getCompleteDiv()); } function doBlackScholes() { workingModel.saveData(); workingModel = bsModel; removeAppend('solutionDiv', workingModel.getCompleteDiv()); } function doNormal() { if (normalDist === null) { normalDist = new NormalCharacteristic(); } removeAppend('solutionDiv', normalDist.getDistDiv()); } function doGamma() { if (gammaDist === null) { gammaDist = new GammaCharacteristic(); } removeAppend('solutionDiv', gammaDist.getDistDiv()); } function doPoisson() { if (poissonDist === null) { poissonDist = new PoissonCharacteristic(); } removeAppend('solutionDiv', poissonDist.getDistDiv()); } var doChange = function doChange(x) { if (x.source instanceof OptionCharacteristicFunction) { workingModel.setLastPeriod(x.source.getPeriod()); } workingModel.reset(); removeAppend('solutionDiv', workingModel.getCompleteDiv()); }; var normalDist = null, gammaDist = null, poissonDist = null, bsModel, hestonModel, varianceGammaModel; function letsBoogie() { document.getElementById("NormalButton").onclick = function () { doNormal(); }; document.getElementById("GammaButton").onclick = function () { doGamma(); }; document.getElementById("PoissonButton").onclick = function () { doPoisson(); }; document.getElementById("BlackScholesButton").onclick = function () { doBlackScholes(); }; document.getElementById("HestonButton").onclick = function () { doHeston(); }; document.getElementById("VarianceGammaButton").onclick = function () { doVarianceGamma(); }; bsModel = new BSModel().readData(); hestonModel = new HestonModel().readData(); varianceGammaModel = new VarianceGammaModel().readData(); workingModel = varianceGammaModel; removeAppend('solutionDiv', workingModel.getCompleteDiv()); }