Skip to content

Instantly share code, notes, and snippets.

@ZJONSSON
Last active April 29, 2016 16:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ZJONSSON/5260594 to your computer and use it in GitHub Desktop.
Save ZJONSSON/5260594 to your computer and use it in GitHub Desktop.
Brownian Bridge

Pan and Zoom (mousewheel) to explore the randomized data.

This experiment uses d3.js, dpl.js and pyfy.js to simulate geometric brownian motion with brownian bridge between any "known points" (either given values or already-randomized values). Active caching is utilized to ensure that any base random number stays in place until the world is re-randomized (i.e. I keep filling in missing information). This means that when volatility or drift is changed, the path is recalculated using the previously established Gaussian random outcomes.

The blue dots are known monthly closing values for APPL and the gray line is the simulated brownian motion Pan and zoom to look deeper and further. As you move around the cache will grow larger, but pressing "randomize" will reset the world to a new initial vector.

/*global d3,dpl,pyfy*/
var options = {
drift : 0,
vol : 20,
randomize : function() {
delete q.cache[b.ID];
chart.render();
b.args.random.arg("bump",0);
chart.render(500);
}
};
var zoom = d3.behavior.zoom(),
q = pyfy.query(),
a=pyfy.flow(APPL),
b=a.lognorm(0.2,0.0);
var chart = dpl.chart(d3.select("svg"))
.setTitle("chart","APPL - Brownian Bridge Experiment")
.scale("x",d3.time.scale());
var path = chart.graph
.append("path")
.classed("brownian",true);
var circle = chart.graph
.selectAll("circle")
.data(q.val(a))
.enter()
.append("circle")
.attr("r",5)
.classed("points",true)
.call(dpl.fitScale("x"));
chart.on("resize.autofit",resize);
function resize() {
var domain = chart.scale("x").domain(),
first = new Date(domain[0].getFullYear()-1,1,1);
var points = [];
points = d3.range(first,domain[1],pyfy.util.DAYMS *100 / Math.round(zoom.scale()))
.filter(function(d) { return d > domain[0] && d < domain[1]; });
q.get(b,points);
var data = q.val(b)
.filter(function(d) {
return d.x > domain[0] && d.x < domain[1];
});
path.datum(data)
.call(dpl.fitScale("y",function(d) {
return [d[0]*0.95,d[1]*1.05];
}));
}
chart.render();
zoom.x(chart.scale("x"))
.y(chart.scale("y"))
.on("zoom", chart.render);
chart.g.call(zoom);
var gui = new dat.GUI()
gui.add(options, 'vol', 0, 100,0.5).onChange(changeArg("vol"));
gui.add(options,'drift',0,20,0.5).onChange(changeArg("drift"));
gui.add(options, 'randomize', 0, 100);
function changeArg(name) {
return function() {
b.arg(name,options[name]/100);
if (q.cache[b.args.random.ID]) {
q.val(b,Object.keys(q.cache[b.args.random.ID].values).map(function(d) { return +d; }));
}
chart.render();
};
}
var dpl={};(function(){function t(t,e,a){return(a||Object.keys(e)).forEach(function(a){t[a]=function(){var r=e[a].apply(t,arguments);return r===e?t:r}}),t.on=function(){return e.on.apply(e,arguments),t},t}function e(e){var a={top:40,bottom:60,left:40,right:40},r=d3.dispatch("resize","render"),n="*",l=dpl.set(),c=l.scale;return t(l,r,["resize","render"]),l.g=e=d3.select(e),l.all=function(){return e.selectAll(n)},l.margin=function(t){return 0==arguments.length?a:(["left","top","right","bottom"].forEach(function(e,r){"number"==typeof t?a[e]=t:null!=t[e]?a[e]=t[e]:null!=t[r]&&(a[e]=t[r])}),l.resize(),l)},l.on("resize.frame",function(){var t=e.attr("height")||e.property("offsetHeight")||2e3,r=e.attr("width")||e.property("offsetWidth"),n=[a.top,t-a.top-a.bottom],c=[a.left,r-(a.right+a.left)];return l.scale("w").range([0,r]),l.scale("h").range([0,t]),l.scale("cw").range(c),l.scale("bw").range(c),l.scale("pw").range(c).domain(c),l.scale("ch").range(n),l.scale("bh").range(n),l.scale("ph").range(n).domain(n),l}),l.on("resize.autorange",function(){var t=c("cw").range(),e=c("ch").range();l.scale().forEach(function(a){var r=l.scale(a),n=r.rangeBands?function(t){return r.rangeBands(t,r.padding||.1)}:function(t){return r.range(t)};"x"==a[0]&&n(t),"y"==a[0]&&n([e[1],e[0]])})}),l.on("render.frame",function(t,e){l.resize();var a=l.all();(t||e)&&(a=a.transition().duration(t).delay(e)),a.call(dpl.render)}),l.add=function(t,a){return e.selectAll(".__newdata__").data(t,a).enter()},c("x"),c("y"),c("y2"),l.resize(),l}dpl.rebind=t,dpl.set=function(){function e(t,e,a){return 0==arguments.length?Object.keys(l):1==arguments.length?l[t]||(l[t]=d3.scale.linear()):(l[t]=e||l[t],a&&Object.keys(a).forEach(function(e){l[t][e]=a[e]}),n)}function a(t,a){return a=a||t,function(r){var n=e(t);return n(void 0!=r[a]?r[a]:void 0!=r[t]?r[t]:r)}}function r(t,e){return function(r){return Math.abs(a(t,e)(r)-a(t)(0))}}var n={},l={};return n.rebind=function(e){return t(n,e)},n.scale=e,["range","domain"].forEach(function(t){n[t]=function(a,r){var l=e(a);return 1==arguments.length?l[t]():(l[t](d3.functor(r)(l[t]())||l[t]()),n)}}),n.project=a,n.interval=r,n.axis=dpl.axis,n.render=dpl.render,n},dpl.axis=function(e){function a(t){var a=dpl.frame(t);if(n.scale(a.scale(e)),r){var l=a.scale(r).range();n.tickSize(Math.abs(l[1]-l[0])*("bottom"==n.orient()||"left"==n.orient()?-1:1))}return n(t)}var r,n=d3.svg.axis();return t(a,n),a.scale=function(t){return 0==arguments.length?e:(e=t,a)},a.tickScale=function(t){return 0==arguments.length?r:(r=t,a)},a},dpl.frame=function(t){return t.select&&(t=t[0][0]),"svg"!=t.tagName&&t.nearestViewportElement&&(t=t.nearestViewportElement),t.frame||(t.frame=e(t))},dpl.render=function(t){t.each&&t.each(function(){if(this.nearestViewportElement){var t=this.nearestViewportElement.frame,e=d3.select(this),a=e.datum(),r=d3.transition(e);if(a&&(a.attr&&e.attr(a.attr),a.style&&e.attr(a.style),"function"==typeof a.render&&a.render.call(e.datum(),r),"ignore"!=e.attr("data-scale"))){var n=e.attr("data-scale-x")||"x",l=e.attr("data-scale-y")||"y";if("chart"==e.attr("data-scale")&&(n="cw",l="ch"),"box"==e.attr("data-scale")&&(n="bw",l="bh"),"g"==this.tagName||"text"==this.tagName){var c=void 0!=a.x?t.project(n,"x")(a):0,i=void 0!=a.y?t.project(l,"y")(a):0;(c||i)&&r.attr("transform","translate("+c+","+i+")rotate("+(a.rotate||0)+")")}else if("path"==this.tagName)a[0]&&void 0!=a[0].y1?r.attr("d",d3.svg.area().x(t.project(n,"x")).y(t.project(l,"y1")).y0(t.project(l,"y0"))):r.attr("d",d3.svg.line().x(t.project(n,"x")).y(t.project(l,"y")).interpolate(e.attr("data-interpolate")||"linear"));else if("circle"==this.tagName)void 0!=a.x&&r.attr("cx",t.project(n,"x")),void 0!=a.y&&r.attr("cy",t.project(l,"y"));else if("rect"==this.tagName||"svg"==this.tagName){if(void 0==a.x&&void 0==a.x0)return;if(t.scale(n).rangeBands)r.attr("x",t.scale(n)(void 0!=a.x?a.x:a.x0)),r.attr("width",t.scale(n).rangeBand);else{var d=a.x0||a.x||0,o=void 0!=a.x1?a.x1:d+(a.width||a.x),s=void 0!=a.width?a.width:Math.abs(o-d);r.attr("x",t.scale(n)(d)),r.attr("width",t.interval(n)(s))}if(t.scale(l).rangeBands)r.attr("y",t.scale(l)(void 0!=a.y?a.y:a.y0)),r.attr("height",t.scale(l).rangeBand);else{var u=a.y0||0,f=void 0!=a.y1?a.y1:u+(a.height||a.y),x=void 0!=a.height?a.height:Math.abs(f-u);r.attr("y",t.scale(l)(f)),r.attr("height",t.interval(l)(x))}}else void 0!=a.x&&r.attr("x",t.project(n,"x")),void 0!=a.y&&r.attr("y",t.project(l,"y"))}}})},dpl.fitScale=function(t,e){return function(a){if(!a.empty()){var r,n,l=dpl.frame(a);if(l.scale(t).rangeBand)r=[],n=function(t,e){-1==r.indexOf(e[t])&&r.push(e[t])};else{r=[+1/0,-1/0];var c=function(t){isNaN(t)||(r[0]=Math.min(r[0],t),r[1]=Math.max(r[1],t))};n=function(t,e){e&&(e.length||(e=[e]),e.forEach(function(e){c(e[t]),c(e[t+"0"]),c(e[t+"1"]),c(e[t+"2"])}))}}return a.each(function(e){var a=d3.select(this),r=a.attr("data-scale-x"),l=a.attr("data-scale-y");(r==t||!r&&"x"==t)&&n("x",e),(l==t||!l&&"y"==t)&&n("y",e)}),e&&(r=d3.functor(e)(r)),1/0!=r[0]&&r[1]!=-1/0&&l.scale(t).domain(r),l}}};var a={chart:{x:.5,y:0,attr:{dy:"-1em","data-scale-x":"cw","data-scale-y":"ch"},style:{"text-anchor":"middle"}},x:{x:.5,y:1,attr:{dy:"0.7em","data-scale-x":"cw","data-scale-y":"bh"},style:{"text-anchor":"middle"}},y:{x:0,y:.5,rotate:-90,attr:{dy:"-0.5em","data-scale-x":"bw","data-scale-y":"ch"},style:{"text-anchor":"middle"}},y2:{x:1,y:.5,attr:{dy:"-0.5em","data-scale-x":"bw","data-scale-y":"ch"},rotate:90,style:{"text-anchor":"middle"}},y_top:{x:0,y:0,attr:{dy:"-0.7em","data-scale-x":"cw","data-scale-y":"ch"},style:{"text-anchor":"end"}},y_inside:{x:0,y:0,attr:{dy:"1em",dx:"-0.5em","data-scale":"chart"},rotate:-90,style:{"text-anchor":"end"}},y2_inside:{x:1,y:0,attr:{dy:"-0.5em",dx:"-0.5em","data-scale":"chart"},rotate:-90,style:{"text-anchor":"end"}},x_inside:{x:1,y:1,attr:{dy:"-0.5em","data-scale":"chart"},style:{"text-anchor":"end"}},y2_top:{x:1,y:0,attr:{dy:"-0.7em","data-scale-x":"cw","data-scale-y":"ch"},style:{"text-anchor":"start"}},center:{x:.5,y:.5,attr:{"data-scale-x":"cw","data-scale-y":"ch"},style:{"text-anchor":"middle"}}};Object.keys(a).forEach(function(t){a[t].id=t}),dpl.setTitle=function(t,e){return function(r){r.each(function(r,n){d3.select(this).selectAll(".title."+t).data([a[t]]).call(function(e){e.enter().append("text").attr("class","title "+t)}).text(d3.functor(e)(r,n)).call(dpl.render)})}},dpl.def||(dpl.def={}),dpl.def.axis={x:function(){return{scale:"x",tickScale:"ch",y:1,orient:"bottom",attr:{"data-scale-x":"cw","data-scale-y":"ch"}}},y:function(){return{scale:"y",tickScale:"cw",x:0,orient:"left",attr:{"data-scale-x":"cw","data-scale-y":"ch"}}},y2:function(){return{scale:"y2",x:1,orient:"right",attr:{"data-scale-x":"cw","data-scale-y":"ch"}}}},dpl.showAxes=function(t){return t=[].concat(t).map(function(t){var e=dpl.def.axis[t]();return e.render=dpl.axis(t).orient(e.orient).tickScale(e.tickScale),e}).filter(function(t){return t}),function(e){e.each(function(){d3.select(this).selectAll(".axis").data(t).call(function(t){t.exit().remove()}).enter().append("g").attr("class",function(t){return"axis "+t.scale})})}},dpl.legend=function(t){var e=5,a="dr",r=function(r){if(r&&r.each){var n={},l=t.selectAll(".legend-container").data([!0]).call(function(t){t.enter().append("g").classed("legend-container",!0)}),c=l.selectAll(".legend-box").data([!0]),i=l.selectAll(".legend-items").data([!0]);c.enter().append("rect").classed("legend-box",!0),i.enter().append("g").classed("legend-items",!0),r.each(function(){var t=d3.select(this);t.attr("data-legend")&&(n[t.attr("data-legend")]={pos:t.attr("data-legend-pos"),by:this.getBBox().y,bh:this.getBBox().height,color:void 0!=t.attr("data-legend-color")?t.attr("data-legend-color"):"none"!=t.style("fill")?t.style("fill"):t.style("stroke")})}),n=d3.entries(n).sort(function(t,e){return t.value.pos-e.value.pos||t.value.by-e.value.by||t.value.bh-e.value.bh}),i.selectAll("text").data(n,function(t){return t.key}).call(function(t){t.enter().append("text")}).call(function(t){t.exit().remove()}).attr("y",function(t,e){return e+"em"}).attr("x","1em").text(function(t){return t.key}),i.selectAll("circle").data(n,function(t){return t.key}).call(function(t){t.enter().append("circle")}).call(function(t){t.exit().remove()}).attr("cy",function(t,e){return e-.25+"em"}).attr("cx",0).attr("r","0.4em").style("fill",function(t){return t.value.color});var d=i[0][0].getBBox(),o=d.x-e,s=d.y-e,u=d.height+2*e,f=d.width+2*e;c.attr("x",o).attr("y",s).attr("height",u).attr("width",f),s=a[0]&&"u"==a[0]?-u-s:-s,o=a[1]&&"l"==a[1]?-f-o:-o,l.attr("transform","translate("+o+","+s+")")}};return r.padding=function(t){return 0==arguments.length?t:(e=t,r)},r.orient=function(t){return 0==arguments.length?t:(a=t,r)},r},dpl.bbox=function(t){var e=[1/0,-1/0],a=[1/0,-1/0];return t.each(function(){var t=this.bbox;t&&(e[0]=Min(e[0],t.x),e[1]=Max(e[1],t.x+t.width),a[0]=Min(a[0],t.y),a[1]=Max(a[1],t.y+t.width))}),{x:e,y:a}},dpl.subplot=function(t,e,a,r){var n=t.g.selectAll(".subplot").data(d3.range(e*a));return n.enter().append("svg").call(dpl.chart()).attr("class",function(t,e){return"subplot subplot"+e}),n.exit().remove(),sublots[0][r].frame},dpl.lineEdit=function(){function t(t){function a(e,a){console.log(e,a),e.x=l.scale("x").invert(d3.event.x),e.x=Math.max(e.x,t.datum()[a-1]?t.datum()[a-1].x:l.domain("x")[0]),e.x=Math.min(e.x,t.datum()[a+1]?t.datum()[a+1].x:l.domain("x")[1]),e.y=l.scale("y").invert(d3.event.y),c.call(dpl.render),d3.select(this).call(dpl.render)}function r(){i.selectAll("circle").data(Object).call(function(t){t.exit().remove()}).call(function(t){t.enter().append("circle").attr("r",d).call(e).on("mousedown.selected",function(t){n=t,r()})}).classed("selected",function(t){return t===n})}var n,l=dpl.chart(t),c=t.append("path"),i=t.append("g"),d=10;e.origin(function(t){var e={x:l.project("x")(t),y:l.project("y")(t)};return e}).on("drag.lineEdit",a),r()}var e=d3.behavior.drag();return dpl.rebind(t,e,[]),t.radius=function(e){return 0==arguments.length?radius:(radius=e,circles.selectAll("circle").attr("r",radius),t)},t.datum=function(){return datum},t},dpl.pathbox=function(t,e){var a=[],r=[];return void 0==e&&(e=2),t.forEach(function(n,l){var c,i;c=t[l-1]?{x:(n.x+t[l-1].x)/2,y:(n.y+t[l-1].y)/2}:{x:n.x,y:n.y},i=t[l+1]?{x:(t[l+1].x+n.x)/2,y:(t[l+1].y+n.y)/2}:{x:n.x,y:n.y},a.push([c,c,i,i,c]),c=t[l-1]?c.x+e:n.x-(t[l+1].x-n.x)/2+e,i=t[l+1]?i.x-e:n.x+(n.x-t[l-1].x)/2-e,r.push([{x:c,y:n.y},{x:c,y:0},{x:i,y:0},{x:i,y:n.y},{x:c,y:n.y}])}),{line:a,box:r}};var r=0,n=function(t){return[.9*t[0],1.1*t[1]]};dpl.chart=function(t){t.select||(t=d3.select(t));var e=dpl.frame(t);if(e.graph)return e;["back","cliparea","graph","overlay"].forEach(function(a){e[a]=t.append("g").classed(a,!0)}),e.showAxes=function(){return e.back.call(dpl.showAxes.apply(e,arguments)),e},e.setTitle=function(){return e.overlay.call(dpl.setTitle.apply(e,arguments)),e},e.fitScale=function(t,a){return e.all().call(dpl.fitScale(t,a)),e};var a=e.overlay.append("g").classed("legend",!0).datum({x:.05,y:.05}).attr("data-scale","chart");return r++,e.cliparea.append("clipPath").attr("id","graphClip"+r).append("rect").classed("graphClip",!0).attr("data-scale-x","cw").attr("data-scale-y","ch").datum({x0:0,x1:1,y0:1,y1:0}),e.graph.attr("clip-path","url(#graphClip"+r+")"),e.enter=function(t,a){var r=e.graph.selectAll(".__newdata__").data(t).enter();return 1==arguments.length?r:(d3.functor(a)(r),e)},e.axis=function(t,a){var t=e.back.select(".axis."+t).datum();return 1==arguments.length?t.render:(t.render=d3.functor(a)(t.render)||t.render,e)},e.tickFormat=function(t,a){return e.axis(t).tickFormat(a),e},e.on("resize.autofit",function(){var t=e.graph.selectAll(":not(.exiting)");t.call(dpl.fitScale("x")),t.call(dpl.fitScale("y",n)),t.call(dpl.fitScale("y2",n))}),e.on("resize.cliparea",function(){e.cliparea.selectAll("rect").call(dpl.render)}),e.legend=dpl.legend(a),e.on("render.autolegend",function(){var t=e.graph.selectAll("[data-legend]");return t.empty()?a.selectAll("*").remove():(t.call(e.legend),a.call(dpl.render),void 0)}),e.on("render.autobbox",function(t,a){var r=e.back[0][0].getBBox();e.scale("bw").range([r.x,r.x+r.width]),e.scale("bh").range([r.y,r.y+r.height]),e.overlay.selectAll("*").transition().duration(t).delay(a).call(dpl.render)}),e.add=e.enter,e.showAxes(["x","y"]),e},"undefined"!=typeof module&&(module.exports=dpl)})();
<html>
<meta charset="utf-8">
<script src="pyfy.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="dpl.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<link rel="stylesheet" href ="style.css">
<body>
<svg height=500 width=960></svg>
<script src="price.js"></script>
<script src="app.js"></script>
</body>
<html>
APPL = [
[new Date(2013,3,1),461.14],
[new Date(2013,2,1),441.4],
[new Date(2013,1,2),452.85],
[new Date(2012,12,3),529.09],
[new Date(2012,11,1),581.89],
[new Date(2012,10,1),589.18],
[new Date(2012,9,4),660.22],
[new Date(2012,8,1),658.38],
[new Date(2012,7,2),601.88],
[new Date(2012,6,1),575.51],
[new Date(2012,5,1),569.33],
[new Date(2012,4,2),575.49],
[new Date(2012,3,1),590.83],
[new Date(2012,2,1),534.55],
[new Date(2012,1,3),449.84],
[new Date(2011,12,1),399.11],
[new Date(2011,11,1),376.64],
[new Date(2011,10,3),398.89],
[new Date(2011,9,1),375.77],
[new Date(2011,8,1),379.23],
[new Date(2011,7,1),384.8],
[new Date(2011,6,1),330.79],
[new Date(2011,5,2),342.77],
[new Date(2011,4,1),345.04],
[new Date(2011,3,1),343.44],
[new Date(2011,2,1),348.07],
[new Date(2011,1,3),334.38],
[new Date(2010,12,1),317.87],
[new Date(2010,11,1),306.62],
[new Date(2010,10,1),296.6],
[new Date(2010,9,1),279.62],
[new Date(2010,8,2),239.56],
[new Date(2010,7,1),253.51],
[new Date(2010,6,1),247.87],
[new Date(2010,5,3),253.14],
[new Date(2010,4,1),257.29],
[new Date(2010,3,1),231.58],
[new Date(2010,2,1),201.64],
[new Date(2010,1,4),189.27],
[new Date(2009,12,1),207.66],
[new Date(2009,11,2),197],
[new Date(2009,10,1),185.76],
[new Date(2009,9,1),182.65],
[new Date(2009,8,3),165.76],
[new Date(2009,7,1),161.01],
[new Date(2009,6,1),140.36],
[new Date(2009,5,1),133.83],
[new Date(2009,4,1),124],
[new Date(2009,3,2),103.59],
[new Date(2009,2,2),88.01],
[new Date(2009,1,2),88.82],
[new Date(2008,12,1),84.11],
[new Date(2008,11,3),91.32],
[new Date(2008,10,1),106.03],
[new Date(2008,9,2),112.01],
[new Date(2008,8,1),167.06],
[new Date(2008,7,1),156.64],
[new Date(2008,6,2),165],
[new Date(2008,5,1),186],
[new Date(2008,4,1),171.42],
[new Date(2008,3,3),141.41],
[new Date(2008,2,1),123.2],
[new Date(2008,1,2),133.39],
[new Date(2007,12,3),195.2],
[new Date(2007,11,1),179.57],
[new Date(2007,10,1),187.19],
[new Date(2007,9,4),151.24],
[new Date(2007,8,1),136.47],
[new Date(2007,7,2),129.84],
[new Date(2007,6,1),120.26],
[new Date(2007,5,1),119.43],
[new Date(2007,4,2),98.35],
[new Date(2007,3,1),91.56],
[new Date(2007,2,1),83.38],
[new Date(2007,1,3),84.48],
[new Date(2006,12,1),83.61],
[new Date(2006,11,1),90.33],
[new Date(2006,10,2),79.9],
[new Date(2006,9,1),75.86],
[new Date(2006,8,1),66.86],
[new Date(2006,7,3),66.97],
[new Date(2006,6,1),56.44],
[new Date(2006,5,1),58.9],
[new Date(2006,4,3),69.37],
[new Date(2006,3,1),61.81],
[new Date(2006,2,1),67.49],
[new Date(2006,1,3),74.41],
[new Date(2005,12,1),70.84],
[new Date(2005,11,1),66.83],
[new Date(2005,10,3),56.75],
[new Date(2005,9,1),52.83],
[new Date(2005,8,1),46.21],
[new Date(2005,7,1),42.03],
[new Date(2005,6,1),36.27],
[new Date(2005,5,2),39.18],
[new Date(2005,4,1),35.54],
[new Date(2005,3,1),41.06],
[new Date(2005,2,1),44.21],
[new Date(2005,1,3),37.89],
[new Date(2004,12,1),31.73],
[new Date(2004,11,1),33.04],
[new Date(2004,10,1),25.82],
[new Date(2004,9,1),19.09],
[new Date(2004,8,2),16.99],
[new Date(2004,7,1),15.93],
[new Date(2004,6,1),16.03],
[new Date(2004,5,3),13.83],
[new Date(2004,4,1),12.7],
[new Date(2004,3,1),13.32],
[new Date(2004,2,2),11.79],
[new Date(2004,1,2),11.12],
[new Date(2003,12,1),10.53],
[new Date(2003,11,3),10.3],
[new Date(2003,10,1),11.28],
[new Date(2003,9,2),10.21],
[new Date(2003,8,1),11.14],
[new Date(2003,7,1),10.39],
[new Date(2003,6,2),9.39],
[new Date(2003,5,1),8.84],
[new Date(2003,4,1),7.01],
[new Date(2003,3,3),6.97],
[new Date(2003,2,3),7.4],
[new Date(2003,1,2),7.08],
[new Date(2002,12,2),7.06],
[new Date(2002,11,1),7.64],
[new Date(2002,10,1),7.92],
[new Date(2002,9,3),7.14],
[new Date(2002,8,1),7.27],
[new Date(2002,7,1),7.52],
[new Date(2002,6,3),8.73],
[new Date(2002,5,1),11.48],
[new Date(2002,4,1),11.96],
[new Date(2002,3,1),11.66],
[new Date(2002,2,1),10.69],
[new Date(2002,1,2),12.18],
[new Date(2001,12,3),10.79],
[new Date(2001,11,1),10.5],
[new Date(2001,10,1),8.65],
[new Date(2001,9,4),7.64],
[new Date(2001,8,1),9.14],
[new Date(2001,7,2),9.26],
[new Date(2001,6,1),11.46],
[new Date(2001,5,1),9.83],
[new Date(2001,4,2),12.56],
[new Date(2001,3,1),10.87],
[new Date(2001,2,1),8.99],
[new Date(2001,1,2),10.65],
[new Date(2000,12,1),7.33],
[new Date(2000,11,1),8.13],
[new Date(2000,10,2),9.64],
[new Date(2000,9,1),12.69],
[new Date(2000,8,1),30.03],
[new Date(2000,7,3),25.04],
[new Date(2000,6,1),25.81],
[new Date(2000,5,1),20.69],
[new Date(2000,4,3),30.56],
[new Date(2000,3,1),33.46],
[new Date(2000,2,1),28.24],
[new Date(2000,1,3),25.56],
[new Date(1999,12,1),25.33],
[new Date(1999,11,1),24.11],
[new Date(1999,10,1),19.74],
[new Date(1999,9,1),15.6],
[new Date(1999,8,2),16.08],
[new Date(1999,7,1),13.72],
[new Date(1999,6,1),11.41],
[new Date(1999,5,3),10.85],
[new Date(1999,4,1),11.33],
[new Date(1999,3,1),8.85],
[new Date(1999,2,1),8.58],
[new Date(1999,1,4),10.15],
[new Date(1998,12,1),10.09],
[new Date(1998,11,2),7.87],
[new Date(1998,10,1),9.15],
[new Date(1998,9,1),9.39],
[new Date(1998,8,3),7.68],
[new Date(1998,7,1),8.53],
[new Date(1998,6,1),7.07],
[new Date(1998,5,1),6.56],
[new Date(1998,4,1),6.74],
[new Date(1998,3,2),6.77],
[new Date(1998,2,2),5.82],
[new Date(1998,1,2),4.51],
[new Date(1997,12,1),3.23],
[new Date(1997,11,3),4.37],
[new Date(1997,10,1),4.2],
[new Date(1997,9,2),5.34],
[new Date(1997,8,1),5.36],
[new Date(1997,7,1),4.31],
[new Date(1997,6,2),3.51],
[new Date(1997,5,1),4.09],
[new Date(1997,4,1),4.19],
[new Date(1997,3,3),4.5],
[new Date(1997,2,3),4],
[new Date(1997,1,2),4.09],
[new Date(1996,12,2),5.14],
[new Date(1996,11,1),5.94],
[new Date(1996,10,1),5.67],
[new Date(1996,9,3),5.47],
[new Date(1996,8,1),5.97],
[new Date(1996,7,1),5.42],
[new Date(1996,6,3),5.17],
[new Date(1996,5,1),6.44],
[new Date(1996,4,1),6],
[new Date(1996,3,1),6.05],
[new Date(1996,2,1),6.77],
[new Date(1996,1,2),6.8],
[new Date(1995,12,1),7.85],
[new Date(1995,11,1),9.39],
[new Date(1995,10,2),8.92],
[new Date(1995,9,1),9.15],
[new Date(1995,8,1),10.56],
[new Date(1995,7,3),11.02],
[new Date(1995,6,1),11.37],
[new Date(1995,5,1),10.18],
[new Date(1995,4,3),9.34],
[new Date(1995,3,1),8.61],
[new Date(1995,2,1),9.65],
[new Date(1995,1,3),9.84],
[new Date(1994,12,1),9.5],
[new Date(1994,11,1),9.07],
[new Date(1994,10,3),10.49],
[new Date(1994,9,1),8.18],
[new Date(1994,8,1),8.79],
[new Date(1994,7,1),8.15],
[new Date(1994,6,1),6.41],
[new Date(1994,5,2),7.08],
[new Date(1994,4,4),7.23],
[new Date(1994,3,1),8.02],
[new Date(1994,2,1),8.8],
[new Date(1994,1,3),7.87],
[new Date(1993,12,1),7.03],
[new Date(1993,11,1),7.57],
[new Date(1993,10,1),7.36],
[new Date(1993,9,1),5.59],
[new Date(1993,8,2),6.34],
[new Date(1993,7,1),6.61],
[new Date(1993,6,1),9.41],
[new Date(1993,5,3),13.49],
[new Date(1993,4,1),12.19],
[new Date(1993,3,1),12.25],
[new Date(1993,2,1),12.6],
[new Date(1993,1,4),14.12],
[new Date(1992,12,1),14.18],
[new Date(1992,11,2),13.64],
[new Date(1992,10,1),12.43],
[new Date(1992,9,1),10.69],
[new Date(1992,8,3),10.89],
[new Date(1992,7,1),11.04],
[new Date(1992,6,1),11.33],
[new Date(1992,5,1),14.08],
[new Date(1992,4,1),14.17],
[new Date(1992,3,2),13.73],
[new Date(1992,2,3),15.91],
[new Date(1992,1,2),15.23],
[new Date(1991,12,2),13.26],
[new Date(1991,11,1),11.94],
[new Date(1991,10,1),12.08],
[new Date(1991,9,3),11.62],
[new Date(1991,8,1),12.44],
[new Date(1991,7,1),10.83],
[new Date(1991,6,3),9.72],
[new Date(1991,5,1),11],
[new Date(1991,4,1),12.84],
[new Date(1991,3,1),15.88],
[new Date(1991,2,1),13.37],
[new Date(1991,1,2),12.93],
[new Date(1990,12,3),10.02],
[new Date(1990,11,1),8.56],
[new Date(1990,10,1),7.14],
[new Date(1990,9,4),6.74],
[new Date(1990,8,1),8.59],
[new Date(1990,7,2),9.73],
[new Date(1990,6,1),10.36],
[new Date(1990,5,1),9.55],
[new Date(1990,4,2),9.09],
[new Date(1990,3,1),9.29],
[new Date(1990,2,1),7.85],
[new Date(1990,1,2),7.83],
[new Date(1989,12,1),8.11],
[new Date(1989,11,1),10.19],
[new Date(1989,10,2),10.68],
[new Date(1989,9,1),10.22],
[new Date(1989,8,1),10.22],
[new Date(1989,7,3),9.11],
[new Date(1989,6,1),9.45],
[new Date(1989,5,1),10.94],
[new Date(1989,4,3),8.91],
[new Date(1989,3,1),8.14],
[new Date(1989,2,1),8.29],
[new Date(1989,1,3),8.6],
[new Date(1988,12,1),9.17],
[new Date(1988,11,1),8.58],
[new Date(1988,10,3),8.78],
[new Date(1988,9,1),9.83],
[new Date(1988,8,1),9.07],
[new Date(1988,7,1),10.07],
[new Date(1988,6,1),10.49],
[new Date(1988,5,2),9.42],
[new Date(1988,4,4),9.29],
[new Date(1988,3,1),9.06],
[new Date(1988,2,1),9.74],
[new Date(1988,1,4),9.38],
[new Date(1987,12,1),9.49],
[new Date(1987,11,2),7.46],
[new Date(1987,10,1),8.71],
[new Date(1987,9,1),12.74],
[new Date(1987,8,3),12.18],
[new Date(1987,7,1),9.29],
[new Date(1987,6,1),9.12],
[new Date(1987,5,1),8.9],
[new Date(1987,4,1),8.91],
[new Date(1987,3,2),7.25],
[new Date(1987,2,2),7.87],
[new Date(1987,1,2),6.24],
[new Date(1986,12,1),4.55],
[new Date(1986,11,3),4.5],
[new Date(1986,10,1),3.89],
[new Date(1986,9,2),3.77],
[new Date(1986,8,1),4.16],
[new Date(1986,7,1),3.51],
[new Date(1986,6,2),4.03],
[new Date(1986,5,1),4.16],
[new Date(1986,4,1),3.4],
[new Date(1986,3,3),3.18],
[new Date(1986,2,3),2.81],
[new Date(1986,1,2),2.6],
[new Date(1985,12,2),2.47],
[new Date(1985,11,1),2.26],
[new Date(1985,10,1),2.09],
[new Date(1985,9,3),1.77],
[new Date(1985,8,1),1.69],
[new Date(1985,7,1),1.79],
[new Date(1985,6,3),2.02],
[new Date(1985,5,1),1.95],
[new Date(1985,4,1),2.39],
[new Date(1985,3,1),2.49],
[new Date(1985,2,1),2.78],
[new Date(1985,1,2),3.26],
[new Date(1984,12,3),3.27],
[new Date(1984,11,1),2.78],
[new Date(1984,10,1),2.8],
[new Date(1984,9,7),2.82]
];
/**
* pyfy.js (c) 2008-2013 Sigurgeir Orn Jonsson (ziggy.jonsson.nyc@gmail.com)
* @license http://creativecommons.org/licenses/by-nc-sa/3.0/
* Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
*/
(function() {
var pyfy = this.pyfy = {};
if (typeof module !== "undefined") module.exports = pyfy;
pyfy.util = {};
pyfy.util.DAYMS = 1e3 * 60 * 60 * 24;
pyfy.util.dateParts = function(d) {
d = new Date(d);
var query = {
y: d.getFullYear(),
m: d.getMonth(),
d: d.getDate()
};
query.date = new Date(query.y, query.m, query.d);
query.lastofMonth = new Date(query.y, query.m, query.d + 1).getMonth() == query.m + 1;
query.lastFeb = query.m == 2 && query.lastofMonth;
return query;
};
pyfy.util.today = function() {
return pyfy.util.dateParts(new Date()).date;
};
pyfy.util.nextDay = function(d, i) {
if (i === undefined) i = 0;
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + i);
};
pyfy.util.nextWeekday = function(date, weekday) {
var currentWeekday = date.getDay();
var dt = currentWeekday > weekday ? weekday + 7 - currentWeekday : weekday - currentWeekday;
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + dt);
};
function ascending(a, b) {
return +a - b;
}
function f(d) {
return d;
}
pyfy.util.bisect = function(a, x, lo, hi) {
if (arguments.length < 3) lo = 0;
if (arguments.length < 4) hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid;
}
return lo;
};
pyfy.calendar = {};
pyfy.calendar.weekday = function(d) {
var weekday = d.getDay();
return weekday !== 0 && weekday != 6;
};
pyfy.calendar.easter = function(date, dt) {
dt = dt || [ 1, -2 ];
var y, c, n, k, i, j, l, m, d;
if (1 < date.getMonth() < 4) {
y = date.getFullYear();
c = ~~(y / 100);
n = y - 19 * ~~(y / 19);
k = ~~((c - 17) / 25);
i = c - ~~(c / 4) - ~~((c - k) / 3) + 19 * n + 15;
i = i - 30 * ~~(i / 30);
i = i - i / 28 * (1 - ~~(i / 28) * ~~(29 / (i + 1)) * ~~((21 - n) / 11));
j = y + ~~(y / 4) + i + 2 - c + ~~(c / 4);
j = j - 7 * ~~(j / 7);
l = i - j;
m = 3 + ~~((l + 40) / 44) - 1;
d = Math.round(l + 28 - 31 * ~~(m / 4));
return dt.every(function(dt) {
return date - new Date(y, m, d + dt);
});
}
return true;
};
pyfy.calendar.target = function(date) {
var m = date.getMonth(), d = date.getDate();
return pyfy.calendar.weekday(date) && pyfy.calendar.easter(date, [ +1, -2 ]) && !(m === 0 && d == 1) && !(m == 11 && (d == 26 || d == 25)) && !(m == 4 && d == 1);
};
pyfy.calendar.is = function(date) {
var y = date.getFullYear(), m = date.getMonth(), d = date.getDate();
return pyfy.calendar.weekday(date) && pyfy.calendar.easter(date, [ +1, -2, -3, +39, 50 ]) && !(m == 5 && d == 17) && !(m === 0 && d == 1) && !(m == 11 && (d == 26 || d == 25)) && !(m == 4 && d == 1) && !(m == 7 && date - pyfy.util.nextWeekday(new Date(y, 7, 1), 1) === 0) && !(m == 3 && date - pyfy.util.nextWeekday(new Date(y, 3, 19), 4) === 0);
};
pyfy.query = Query;
function Query(options) {
if (!(this instanceof Query)) return new Query(options);
this.cache = {
options: options
};
}
Query.prototype.initCache = function(obj) {
if (!obj.ID) return false;
var clear = !this.cache[obj.ID] || this.cache[obj.ID].version != obj.version;
if (obj.args) Object.keys(obj.args).forEach(function(key) {
var parent = obj.args[key];
if (this.initCache(parent)) clear = true;
}, this);
if (clear) {
this.cache[obj.ID] = {
obj: obj,
values: {},
version: obj.version
};
}
return clear;
};
Query.prototype.getCache = function(obj) {
this.initCache(obj);
return this.cache[obj.ID];
};
Query.prototype.dates = function(obj) {
var cache = this.getCache(obj);
if (!cache.dates) {
var rawDates = this.rawDates(obj), dates = cache.dates = [];
for (var date in rawDates) {
dates.push(rawDates[date]);
}
dates.sort(ascending);
}
return cache.dates;
};
Query.prototype.rawDates = function(obj, rawDates) {
var cache = this.getCache(obj);
rawDates = rawDates || {};
if (cache && !cache.rawDates) {
if (obj.args) Object.keys(obj.args).forEach(function(key) {
rawDates = this.rawDates(obj.args[key], rawDates);
}, this);
if (obj.rawDates) rawDates = obj.rawDates(rawDates, this);
}
return rawDates;
};
Query.prototype.y = function(obj, dates) {
return this.get(obj, dates);
};
Query.prototype.x = function(obj) {
return this.dates(obj).map(function(d) {
return new Date(d);
});
};
Query.prototype.val = function(obj, dates) {
dates = dates || this.dates(obj);
dates = [].concat(dates);
return this.get(obj, dates).map(function(d, i) {
return {
x: new Date(dates[i]),
y: d
};
}, this);
};
Query.prototype.get = function(obj, d) {
this.initCache(obj);
if (!d) d = this.dates(obj);
return [].concat(d).map(function(d) {
return this.fetch(obj, d.valueOf());
}, this);
};
Query.prototype.fetch = function(obj, d) {
if (!isNaN(obj)) return obj;
var values = this.cache[obj.ID].values;
if (values[d] === undefined) {
var fn = obj.fn(this, d.valueOf());
if (fn !== undefined) values[d] = fn;
}
return values[d];
};
var ID = 0;
pyfy.base = pyfy.Base = Base;
function Base() {
if (!(this instanceof Base)) return new Base();
this.ID = ID++;
this.args = {};
this.version = 0;
}
Base.prototype.arg = function(d, v) {
this.args[d] = v;
this.version += 1;
};
Base.prototype.fn = function() {
return 0;
};
Base.prototype.rawDates = undefined;
[ Cumul, Diff, Prev, Max, Min, Neg, Calendar, Dcf, Period, Derived, TimeDiff, Call, Put, LogNorm ].forEach(function(Fn) {
Base.prototype[Fn.name.toLowerCase()] = function(a, b, c) {
return new Fn(this, a, b, c);
};
});
Base.prototype.pv = function(curve) {
var pv = 0;
if (!isNaN(curve)) curve = pyfy.ir(curve);
this.mul(curve.df).y().forEach(function(cf) {
pv += cf;
});
return pv;
};
Base.prototype.y = function(dates) {
return pyfy.query().get(this, dates);
};
Base.prototype.x = function(dates) {
return pyfy.query().x(this, dates);
};
Base.prototype.val = function(dates) {
return pyfy.query().val(this, dates);
};
Base.prototype.dates = function() {
return pyfy.query().dates(this).map(function(d) {
return new Date(d);
});
};
pyfy.const = Const;
function Const(data, options) {
if (!(this instanceof Const)) return new Const(data, options);
Base.call(this);
this.args.const = data || 0;
}
Const.prototype = new Base();
Const.prototype.fn = function() {
return this.args.const;
};
Const.prototype.update = Const.prototype.set = function(d) {
this.args.const = d;
return this;
};
pyfy.sum = Sum;
function Sum(a, b, c, d, e, f, g, h) {
if (!(this instanceof Sum)) return new Sum(a, b, c, d, e, f, g, h);
Base.call(this);
Array.prototype.slice.call(arguments).map(function(d, i) {
if (d) this.args[i] = d;
}, this);
}
Sum.prototype = new Base();
Sum.prototype.fn = function(query, d, i) {
var sum = 0;
Object.keys(this.args).forEach(function(key) {
sum += query.fetch(this.args[key], d, i);
}, this);
return sum;
};
pyfy.data = pyfy.Data = Data;
function Data(data, options) {
if (!(this instanceof Data)) return new Data(data, options);
Base.apply(this, arguments);
this.args.data = {};
if (data) this.update(data);
}
Data.prototype = new Base();
Data.prototype.rawDates = function(rawDates, query) {
var settle = query && query.options && query.options.settle || new Date();
rawDates = rawDates || {};
Object.keys(this.args.data).forEach(function(e) {
rawDates[+e] = +e;
});
return rawDates;
};
Data.prototype.update = function(a) {
if (arguments.length === 0) return this;
if (!isNaN(a)) {
var x = pyfy.util.today().valueOf();
this.args.data[x] = a;
} else {
[].concat(a).forEach(function(d) {
this.args.data[(d.x || d[0]).valueOf()] = d.y || d[1];
}, this);
}
return this;
};
Data.prototype.fn = function(query, d) {
if (!Object.keys(this.args.data).length) return 0;
if (this.args.data[d]) return this.args.data[d];
var dates = query.dates(this), next = pyfy.util.bisect(dates, d), prev = next - 1;
if (next == dates.length) next -= 1;
if (next === 0) prev = 0;
return this._fn(d, dates[prev] || dates[next], dates[next]);
};
Data.prototype._fn = function(d, prev, next) {
return undefined;
};
pyfy.flow = Flow;
function Flow(data, options) {
if (!(this instanceof Flow)) return new Flow(data, options);
Data.apply(this, arguments);
}
Flow.prototype = new Data();
Flow.prototype.fn = function(query, d) {
return this.args.data[d] || 0;
};
pyfy.stock = Stock;
function Stock(data, options) {
if (!(this instanceof Stock)) return new Stock(data, options);
Data.apply(this, arguments);
}
Stock.prototype = new Data();
Stock.prototype._fn = function(d, last) {
return this.args.data[last];
};
pyfy.price = Price;
function Price(data, options) {
if (!(this instanceof Price)) return new Price(data, options);
Data.apply(this, arguments);
}
Price.prototype = new Data();
Price.prototype._fn = function(d, prev, next) {
var prevVal = this.args.data[prev], nextVal = this.args.data[next];
if (prev == next) return nextVal;
return prev < d < next ? prevVal + (nextVal - prevVal) * (d - prev) / (next - prev) : nextVal;
};
pyfy.stream = Stream;
function Stream(data, options) {
if (!(this instanceof Stream)) return new Stream(data, options);
Data.apply(this, arguments);
}
Stream.prototype = new Data();
pyfy.interval = function(start, dm, no, val) {
start = start || pyfy.util.today();
var interval = [];
for (var i = 0; i < no + 1; i++) {
interval.push({
x: new Date(start.getFullYear(), start.getMonth() + i * dm, start.getDate()),
y: !i ? 0 : val || 1
});
}
return pyfy.flow(interval);
};
pyfy.derived = pyfy.Derived = Derived;
function Derived(d) {
if (!(this instanceof Derived)) return new Derived(d, fn);
Base.call(this, arguments);
this.args.parent = d || new Base();
}
Derived.prototype = new Base();
Derived.prototype.fn = function(query, d, i) {
return query.fetch(this.args.parent, d, i);
};
Derived.prototype.setParent = function(d) {
this.args.parent = d;
};
pyfy.period = Period;
function Period(d, start, fin) {
if (!(this instanceof Period)) return new Period(d, start, fin);
Derived.call(this, d);
this.args.start = start || -Infinity;
this.args.fin = fin || Infinity;
}
Period.prototype = new Derived();
Period.prototype.rawDates = function(rawDates) {
var res = {};
if (rawDates) Object.keys(rawDates).forEach(function(key) {
var d = rawDates[key];
if (d >= this.args.start && d <= this.args.fin) res[d] = d;
}, this);
return res;
};
Period.prototype.fn = function(query, d, i) {
return d >= this.args.start && d <= this.args.fin ? query.fetch(this.args.parent, d, i) : 0;
};
pyfy.prev = Prev;
function Prev(d, start) {
if (!(this instanceof Prev)) return new Prev(d, start);
Derived.call(this, d);
this.default = start || 0;
}
Prev.prototype = new Derived();
Prev.prototype.fn = function(query, d, i) {
var dates = query.dates(this), datePos = pyfy.util.bisect(dates, d);
return datePos > 0 ? query.fetch(this.args.parent, dates[datePos - 1], i) : this.default;
};
pyfy.cumul = Cumul;
function Cumul(d) {
if (!(this instanceof Cumul)) return new Cumul(d);
Derived.call(this, d);
}
Cumul.prototype = new Derived();
Cumul.prototype.fn = function(query, d, i) {
var dates = query.dates(this), datePos = pyfy.util.bisect(dates, d);
return query.fetch(this.args.parent, d, i) + (datePos ? query.fetch(this, dates[datePos - 1], i) : 0);
};
pyfy.diff = Diff;
function Diff(d) {
if (!(this instanceof Diff)) return new Diff();
Derived.call(this, d);
}
Diff.prototype = new Derived();
Diff.prototype.fn = function(query, d, i) {
var dates = query.dates(this), datePos = pyfy.util.bisect(dates, d);
return datePos ? query.fetch(this.args.parent, d, i) - query.fetch(this.args.parent, dates[datePos - 1], i) : 0;
};
pyfy.max = Max;
function Max(d, max) {
if (!(this instanceof Max)) return new Max(d, max);
Derived.call(this, d);
this.args.max = max || 0;
}
Max.prototype = new Derived();
Max.prototype.fn = function(query, d, i) {
return Math.max(query.fetch(this.args.parent, d, i), this.args.max);
};
pyfy.min = Min;
function Min(d, min) {
if (!(this instanceof Min)) return new Min(d, min);
Derived.call(this, d);
this.args.min = min || 0;
}
Min.prototype = new Derived();
Min.prototype.fn = function(query, d, i) {
return Math.min(query.fetch(this.args.parent, d, i), this.args.min);
};
pyfy.Neg = Neg;
function Neg(d) {
if (!(this instanceof Neg)) return new Neg();
Derived.call(this, d);
}
Neg.prototype = new Derived();
Neg.prototype.fn = function(query, d, i) {
return -query.fetch(this.args.parent, d, i);
};
pyfy.acct = Acct;
function Acct(d) {
if (!(this instanceof Acct)) return new Acct(d);
Derived.call(this, d);
this.args.start = d || 0;
}
Acct.prototype = new Derived();
Acct.prototype.fn = function(query, d, i) {
var dates = query.dates(this.args.parent), pos = pyfy.util.bisect(dates, d);
if (pos < 1) return this.start;
if (dates[pos] == d) return query.fetch(this, dates[pos - 1], i) + query.fetch(this.args.parent, d, i);
return query.fetch(this, dates[pos - 1], i);
};
pyfy.call = Call;
function Call(d, strike) {
if (!(this instanceof Call)) return new Call(d);
Derived.call(this, d);
this.args.strike = strike || 0;
}
Call.prototype = new Derived();
Call.prototype.fn = function(query, d, i) {
return Math.max(query.fetch(this.args.parent, d, i) - this.args.strike, 0);
};
pyfy.call = Put;
function Put(d, strike) {
if (!(this instanceof Call)) return new Put(d);
Derived.call(this, d);
this.args.strike = strike || 0;
}
Put.prototype = new Derived();
Put.prototype.fn = function(query, d, i) {
return Math.max(this.args.strike - query.fetch(this.args.parent, d, i), 0);
};
pyfy.Calendar = Calendar;
function Calendar(d, calendar) {
if (!(this instanceof Calendar)) return new Calendar(d, calendar);
Derived.call(this, d);
this.args.calendar = calendar || pyfy.calendar.weekday;
}
Calendar.prototype = new Derived();
Calendar.prototype.checkDate = function(date) {
date = new Date(date);
var calendar = [].concat(this.calendar), i = 0;
function isHoliday() {
return !calendar.every(function(d) {
var fn = typeof d === "string" ? pyfy.calendar[d] : d;
return fn(date);
}, this);
}
function nextDay() {
date = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
if (i++ > 31) throw "Calendar function always returns false";
}
while (isHoliday()) nextDay();
return date.valueOf();
};
Calendar.prototype.rawDates = function(rawDates, query) {
var cache = query.cache[this.ID];
rawDates = {};
cache.dateMap = {};
for (var pd in this.args.parent.rawDates()) {
var date = this.checkDate(+pd);
rawDates[date] = +date;
cache.dateMap[date] = +pd;
}
return rawDates;
};
Calendar.prototype.fn = function(query, d) {
var cache = query.cache[this.ID];
return query.fetch(this.args.parent, cache.dateMap[d] || this.calendar(d));
};
pyfy.operator = pyfy.Operator = Operator;
var ops = {
add: function(a, b) {
return a + b;
},
sub: function(a, b) {
return a - b;
},
mul: function(a, b) {
return a * b;
},
div: function(a, b) {
return a / b;
},
pow: function(a, b) {
return Math.pow(a, b);
}
};
Object.keys(ops).forEach(function(op) {
Base.prototype[op] = function(d) {
return new Operator(op, this, d);
};
});
function Operator(op, left, right) {
if (!(this instanceof Operator)) return new Operator(op, left, right);
Base.apply(this);
this.args.left = left;
this.args.right = right;
this.op = op;
}
Operator.prototype = new Base();
Operator.prototype.fn = function(query, d, i) {
var left, right;
right = query.fetch(this.args.right, d, i);
if (!right && this.op == "mul") return 0;
left = query.fetch(this.args.left, d, i);
return ops[this.op](left, right);
};
pyfy.daycount = pyfy.daycount || {};
pyfy.daycount.d_30_360 = function(d1, d2) {
if (d1.d == 31) d1.d = 30;
if (d1.d == 30 && d2.d == 31) d2.d = 30;
return (360 * (d2.y - d1.y) + 30 * (d2.m - d1.m) + (d2.d - d1.d)) / 360;
};
pyfy.daycount.d_30E_360 = function(d1, d2) {
if (d1.d == 31) d1.d = 30;
if (d2.d == 31) d2.d = 30;
return (360 * (d2.y - d1.y) + 30 * (d2.m - d1.m) + (d2.d - d1.d)) / 360;
};
pyfy.daycount.d_30_360US = function(d1, d2) {
if (d1.lastofFeb && d2.lastofFeb) d2.d = 30;
if (d1.lastofFeb) d1.d = 30;
if (d2.d == 31 && (d1.d == 30 || d1.d == 31)) d2.d = 30;
if (d1.d == 31) d1.d = 30;
return (360 * (d2.y - d1.y) + 30 * (d2.m - d1.m) + (d2.d - d1.d)) / 360;
};
pyfy.daycount.d_act_360 = function(d1, d2) {
return Math.round((d2.date - d1.date) / pyfy.util.DAYMS) / 360;
};
pyfy.daycount.d_act_act = function(d1, d2) {
var dct = 0, _d1 = d1.date, _d2 = new Date(Math.min(new Date(_d1.getFullYear() + 1, 0, 1), d2.date));
while (_d1 < d2.date) {
var testDate = new Date(_d1.getFullYear(), 1, 29), denom = testDate.getDate() == 29 ? 366 : 365;
dct += Math.round((_d2 - _d1) / pyfy.util.DAYMS) / denom;
_d1 = _d2;
_d2 = new Date(Math.min(new Date(_d1.getFullYear() + 1, 0, 1), d2.date));
}
return dct;
};
pyfy.dcf = Dcf;
function Dcf(parent, daycount) {
if (!(this instanceof Dcf)) return new Operator(parent, daycount);
Derived.call(this, parent);
if (daycount !== undefined) this.setDaycount(daycount);
}
Dcf.prototype = new Derived();
Dcf.prototype.fn = function(query, d) {
var cache = query.cache[this.ID];
if (!Object.keys(cache.values).length) {
var dates = query.dates(this);
cache.values = {};
dates.slice(1).forEach(function(d, i) {
var d1 = pyfy.util.dateParts(dates[i]), d2 = pyfy.util.dateParts(d);
cache.values[d] = dates[i] ? this.daycount(d1, d2) : 0;
}, this);
}
return cache.values[d] || 0;
};
Dcf.prototype.daycount = pyfy.daycount.d_30_360;
Dcf.prototype.setDaycount = function(d) {
this.daycount = typeof d === "string" ? pyfy.daycount[d] : d;
return this;
};
pyfy.timeDiff = TimeDiff;
function TimeDiff(parent, date, daycount) {
if (!(this instanceof TimeDiff)) return new TimeDiff(parent, date, daycount);
Derived.call(this, parent);
this.args.daycount = daycount || pyfy.daycount.d_30_360;
this.args.date = date || new Date();
}
TimeDiff.prototype = new Derived();
TimeDiff.prototype.fn = function(query, d) {
var date = this.args.date;
if (typeof date === "string") {
var parentDates = query.dates(this.args.parent);
date = new Date(date.slice(0, 1).toUpperCase() === "L" ? parentDates[parentDates.length - 1] : parentDates[0]);
}
return Math.abs(this.args.daycount(pyfy.util.dateParts(date), pyfy.util.dateParts(new Date(d))));
};
pyfy.ir = Ir;
function Ir(data, options) {
if (!(this instanceof Ir)) return new Ir(data, options);
Stock.apply(this, arguments);
this.df = new Df(this);
this.args.daycount = pyfy.daycount.d_30_360;
}
Ir.prototype = new Stock();
Ir.prototype.setDaycount = function(d) {
this.args.daycount = typeof d === "string" ? pyfy.daycount[d] : d;
return this;
};
Ir.prototype._fn = function(d, last, next) {
return this.args.data[next];
};
pyfy.df = function(d) {
return new Derived(d);
};
function Df(d, val) {
Derived.call(this, d);
this.args.val = val;
}
Df.prototype = new Dcf();
Df.prototype.fn = function(query, d, i) {
var dates = query.dates(this);
var pos = pyfy.util.bisect(dates, d);
var last = dates[pos - 1];
if (!last) return d == dates[pos] ? 1 : 0;
var dcf = this.parent.daycount(pyfy.util.dateParts(last), pyfy.util.dateParts(d));
return query.fetch(this, last, i) * Math.exp(-query.fetch(this.args.parent, d, i) * dcf);
};
pyfy.bond = Bond;
function Bond(start, dm, num, rate, notional, daycount) {
if (!(this instanceof Bond)) return new Bond(start, dm, num, rate, notional, daycount);
pyfy.derived.apply(this);
this.notional = notional || 1;
this.daycount = daycount;
this.rate = rate;
this.schedule = pyfy.interval(start, dm, num).div(num);
this.generate();
}
Bond.prototype = new pyfy.derived();
Bond.prototype.generate = function() {
this.bal = pyfy.acct(this.notional || 1);
this.p_scheduled = this.schedule.mul(this.notional);
this.p = this.schedule.mul(this.notional);
this.bal.setParent(this.p.neg());
this.i = this.bal.prev().dcf().mul(this.rate);
this.setParent(this.p.add(this.i));
};
Bond.prototype.setSchedule = function(d) {
this.schedule = d;
this.generate();
return this;
};
function Ziggurat() {
var jsr = 123456789;
var wn = new Array(128);
var fn = new Array(128);
var kn = new Array(128);
function RNOR() {
var hz = SHR3();
var iz = hz & 127;
return Math.abs(hz) < kn[iz] ? hz * wn[iz] : nfix(hz, iz);
}
this.nextGaussian = function() {
return RNOR();
};
function nfix(hz, iz) {
var r = 3.442619855899;
var r1 = 1 / r;
var x;
var y;
while (true) {
x = hz * wn[iz];
if (iz === 0) {
x = -Math.log(UNI()) * r1;
y = -Math.log(UNI());
while (y + y < x * x) {
x = -Math.log(UNI()) * r1;
y = -Math.log(UNI());
}
return hz > 0 ? r + x : -r - x;
}
if (fn[iz] + UNI() * (fn[iz - 1] - fn[iz]) < Math.exp(-.5 * x * x)) {
return x;
}
hz = SHR3();
iz = hz & 127;
if (Math.abs(hz) < kn[iz]) {
return hz * wn[iz];
}
}
}
function SHR3() {
var jz = jsr;
var jzr = jsr;
jzr ^= jzr << 13;
jzr ^= jzr >>> 17;
jzr ^= jzr << 5;
jsr = jzr;
return jz + jzr | 0;
}
function UNI() {
return .5 * (1 + SHR3() / -Math.pow(2, 31));
}
function zigset() {
jsr ^= new Date().getTime();
var m1 = 2147483648;
var dn = 3.442619855899;
var tn = dn;
var vn = .00991256303526217;
var q = vn / Math.exp(-.5 * dn * dn);
kn[0] = Math.floor(dn / q * m1);
kn[1] = 0;
wn[0] = q / m1;
wn[127] = dn / m1;
fn[0] = 1;
fn[127] = Math.exp(-.5 * dn * dn);
for (var i = 126; i >= 1; i--) {
dn = Math.sqrt(-2 * Math.log(vn / dn + Math.exp(-.5 * dn * dn)));
kn[i + 1] = Math.floor(dn / tn * m1);
tn = dn;
fn[i] = Math.exp(-.5 * dn * dn);
wn[i] = dn / m1;
}
}
zigset();
}
var z = new Ziggurat();
var rndNorm = pyfy.rndNorm = z.nextGaussian;
function Random() {
if (!(this instanceof Random)) return new Random();
Base.call(this);
}
pyfy.random = Random;
Random.prototype = new Base();
Random.prototype.fn = function() {
var x, y, r;
do {
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
r = x * x + y * y;
} while (!r || r > 1);
return x * Math.sqrt(-2 * Math.log(r) / r);
};
Random.prototype.correl = function(correl) {
return pyfy.correl(this, correl);
};
Random.prototype.wiener = function() {
return pyfy.wiener(this);
};
pyfy.wiener = Wiener;
function Wiener(random) {
if (!(this instanceof Wiener)) return new Wiener(random);
Base.apply(this);
this.change = this.diff(this);
this.random = random || new Random();
this.inputs = [ random ];
}
Wiener.prototype = new Base();
Wiener.prototype.rawDates = function(query) {
var res = {};
if (query && query.cache[this.ID] && query.cache[this.ID].dates) {
query.cache[this.ID].dates.forEach(function(d) {
res[d] = d;
});
}
return res;
};
Wiener.prototype.fn = function(query, d) {
var cache = query.cache[this.ID], val;
if (!cache.dates) {
cache.dates = [ d ];
cache.first = {
x: d,
y: 0
};
cache.last = {
x: d,
y: 0
};
return 0;
}
if (d > cache.last.x) {
cache.dates.push(d);
val = cache.last.y + query.fetch(this.random, d) * (d - cache.last.x) / pyfy.util.DAYMS / 365.25;
cache.last = {
x: d,
y: val
};
return val;
}
if (d < cache.first.x) {
cache.dates.slice(0, 0, d);
val = cache.min.y - query.fetch(this.random, d) * (cache.first.x - d) / pyfy.util.DAYMS / 365.25;
cache.first = {
x: d,
y: val
};
return val;
}
var datePos = pyfy.util.bisect(cache.dates, d), prev = {
x: cache.dates[datePos - 1]
}, next = {
x: cache.dates[datePos]
};
prev.y = query.fetch(this, prev.x);
next.y = query.fetch(this, next.x);
cache.dates.splice(datePos, 0, d);
return prev.y + (d - prev.x) / (next.x - prev.x) * (next.y - prev.y);
};
Wiener.prototype.dates = function(query) {
return query ? query.cache[this.ID].dates.all : [];
};
"use strict";
pyfy.logNorm = LogNorm;
function LogNorm(spot, vol, drift, random) {
if (!(this instanceof LogNorm)) return new LogNorm(spot, vol, drift, random);
Base.apply(this);
this.args.spot = spot;
this.args.vol = vol;
this.args.drift = drift;
this.args.random = random || new Random();
}
LogNorm.prototype = new Base();
LogNorm.prototype.fn = function(query, d, i) {
var cache = query.cache[this.ID];
i = i || 0;
if (!cache.extent) {
var spotDates = query.dates(this.args.spot);
if (spotDates.length) {
cache.extent = {
first: spotDates[0],
last: spotDates[spotDates.length - 1]
};
} else {
throw "no date information in spot";
}
}
var res = query.fetch(this.args.spot, d, i);
if (res) return res;
var dates = query.dates(this), l = dates.length, datePos = pyfy.util.bisect(cache.dates, d), prev, next, drift, rnd, dt, vol;
var pd = d < cache.extent.first ? dates[datePos] : d;
vol = query.fetch(this.args.vol, pd, i);
rnd = query.fetch(this.args.random, pd, i);
drift = query.fetch(this.args.drift, pd, i);
dt = d < cache.extent.first ? dates[datePos] - d : d - dates[datePos - 1];
dt = dt / pyfy.util.DAYMS / 365.25;
if (d > cache.extent.last) {
prev = query.fetch(this, dates[datePos - 1], i);
if (!i) {
dates.push(d);
cache.extent.last = d;
}
return prev * pyfy_exp(vol, drift, rnd, dt);
} else if (d < cache.extent.first) {
next = query.fetch(this, dates[datePos], i);
if (!i) {
dates.splice(0, 0, d);
cache.extent.first = d;
}
console.log(next, vol, drift, rnd, dt);
return next / pyfy_exp(vol, drift, rnd, dt);
} else {
prev = query.fetch(this, dates[datePos - 1], i);
next = query.fetch(this, dates[datePos], i);
drift = Math.log(next / prev) / ((dates[datePos] - dates[datePos - 1]) / pyfy.util.DAYMS / 365.25);
if (!i) dates.splice(datePos, 0, d);
return prev * pyfy_exp(vol, drift, rnd, dt);
}
};
function pyfy_exp(vol, drift, rnd, dt) {
return Math.exp((drift - vol * vol / 2) * dt + rnd * vol * Math.sqrt(dt));
}
function Correl(parent, correl, random) {
if (!(this instanceof Correl)) return new Correl(parent, correl);
Random.call(this);
this.args.parent = parent;
this.args.random = random || new Random();
this.args.correl = correl;
}
pyfy.correl = Correl;
Correl.prototype = new Random();
Correl.prototype.fn = function(query, d, i) {
var correl = query.fetch(this.args.correl, d, i);
return correl * query.fetch(this.args.parent, d, i) + Math.sqrt(1 - correl * correl) * query.fetch(this.args.random, d, i);
};
pyfy.simul = Simul;
function Simul(num) {
if (!(this instanceof Simul)) return new Simul(num);
Query.call(this);
this.num = num;
}
Simul.prototype = new Query();
Simul.prototype.fetch = function(obj, d, i) {
if (!isNaN(obj)) return obj;
var values = this.cache[obj.ID].values;
values[d] = values[d] || [];
if (values[d][i] === undefined) {
var fn = obj.fn(this, d.valueOf(), i);
if (fn !== undefined) values[d][i] = fn;
}
return values[d][i];
};
Simul.prototype.get = function(obj, dates) {
this.initCache(obj);
return [].concat(dates || this.dates(obj)).map(function(d) {
var res = [], i = this.num;
while (i--) res.push(this.fetch(obj, d.valueOf(), i));
return res;
}, this);
};
})();
svg {
width: 100%;
height: 100%;
}
.axis path, .axis line {
fill: none;
stroke: #000;
opacity:0.4;
shape-rendering: crispEdges;
}
.tick {
stroke-dasharray:2;
color:gray;
opacity:0.4;
}
.brownian {
stroke : gray;
fill : none;
}
.points {
fill : blue;
stroke : black;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment