Created
February 25, 2020 07:38
-
-
Save hkmoon/ac296af2147288fd8f0f11a29040fa83 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"%%html\n<style>\n text.symbol {\n fill: #BBBBBB;\n }\n\n path {\n fill: none;\n stroke-width: 1;\n }\n\n path.candle {\n stroke: #000000;\n }\n\n path.candle.body {\n stroke-width: 0;\n }\n\n path.candle.up {\n fill: #00AA00;\n stroke: #00AA00;\n }\n\n path.candle.down {\n fill: #FF0000;\n stroke: #FF0000;\n }\n\n .close.annotation.up path {\n fill: #00AA00;\n }\n\n path.volume {\n fill: #DDDDDD;\n }\n\n .indicator-plot path.line {\n fill: none;\n stroke-width: 1;\n }\n\n .ma-0 path.line {\n stroke: #1f77b4;\n }\n\n .ma-1 path.line {\n stroke: #aec7e8;\n }\n\n .ma-2 path.line {\n stroke: #ff7f0e;\n }\n\n path.macd {\n stroke: #0000AA;\n }\n\n path.signal {\n stroke: #FF9999;\n }\n\n path.zero {\n stroke: #BBBBBB;\n stroke-dasharray: 0;\n stroke-opacity: 0.5;\n }\n\n path.difference {\n fill: #BBBBBB;\n opacity: 0.5;\n }\n\n path.rsi {\n stroke: #000000;\n }\n\n path.overbought, path.oversold {\n stroke: #FF9999;\n stroke-dasharray: 5, 5;\n }\n\n path.middle, path.zero {\n stroke: #BBBBBB;\n stroke-dasharray: 5, 5;\n }\n\n .analysis path, .analysis circle {\n stroke: blue;\n stroke-width: 0.8;\n }\n\n .trendline circle {\n stroke-width: 0;\n display: none;\n }\n\n .mouseover .trendline path {\n stroke-width: 1.2;\n }\n\n .mouseover .trendline circle {\n stroke-width: 1;\n display: inline;\n }\n\n .dragging .trendline path, .dragging .trendline circle {\n stroke: darkblue;\n }\n\n .interaction path, .interaction circle {\n pointer-events: all;\n }\n\n .interaction .body {\n cursor: move;\n }\n\n .trendlines .interaction .start, .trendlines .interaction .end {\n cursor: nwse-resize;\n }\n\n .supstance path {\n stroke-dasharray: 2, 2;\n }\n\n .supstances .interaction path {\n pointer-events: all;\n cursor: ns-resize;\n }\n\n .mouseover .supstance path {\n stroke-width: 1.5;\n }\n\n .dragging .supstance path {\n stroke: darkblue;\n }\n\n .crosshair {\n cursor: crosshair;\n }\n\n .crosshair path.wire {\n stroke: #DDDDDD;\n stroke-dasharray: 1, 1;\n }\n\n .crosshair .axisannotation path {\n fill: #DDDDDD;\n }\n\n .tradearrow path.tradearrow {\n stroke: none;\n }\n\n .tradearrow path.buy {\n fill: #0000FF;\n }\n\n .tradearrow path.sell {\n fill: #9900FF;\n }\n\n .tradearrow path.highlight {\n fill: none;\n stroke-width: 2;\n }\n\n .tradearrow path.highlight.buy {\n stroke: #0000FF;\n }\n\n .tradearrow path.highlight.sell {\n stroke: #9900FF;\n }\n</style>","execution_count":1,"outputs":[{"output_type":"display_data","data":{"text/plain":"<IPython.core.display.HTML object>","text/html":"<style>\n text.symbol {\n fill: #BBBBBB;\n }\n\n path {\n fill: none;\n stroke-width: 1;\n }\n\n path.candle {\n stroke: #000000;\n }\n\n path.candle.body {\n stroke-width: 0;\n }\n\n path.candle.up {\n fill: #00AA00;\n stroke: #00AA00;\n }\n\n path.candle.down {\n fill: #FF0000;\n stroke: #FF0000;\n }\n\n .close.annotation.up path {\n fill: #00AA00;\n }\n\n path.volume {\n fill: #DDDDDD;\n }\n\n .indicator-plot path.line {\n fill: none;\n stroke-width: 1;\n }\n\n .ma-0 path.line {\n stroke: #1f77b4;\n }\n\n .ma-1 path.line {\n stroke: #aec7e8;\n }\n\n .ma-2 path.line {\n stroke: #ff7f0e;\n }\n\n path.macd {\n stroke: #0000AA;\n }\n\n path.signal {\n stroke: #FF9999;\n }\n\n path.zero {\n stroke: #BBBBBB;\n stroke-dasharray: 0;\n stroke-opacity: 0.5;\n }\n\n path.difference {\n fill: #BBBBBB;\n opacity: 0.5;\n }\n\n path.rsi {\n stroke: #000000;\n }\n\n path.overbought, path.oversold {\n stroke: #FF9999;\n stroke-dasharray: 5, 5;\n }\n\n path.middle, path.zero {\n stroke: #BBBBBB;\n stroke-dasharray: 5, 5;\n }\n\n .analysis path, .analysis circle {\n stroke: blue;\n stroke-width: 0.8;\n }\n\n .trendline circle {\n stroke-width: 0;\n display: none;\n }\n\n .mouseover .trendline path {\n stroke-width: 1.2;\n }\n\n .mouseover .trendline circle {\n stroke-width: 1;\n display: inline;\n }\n\n .dragging .trendline path, .dragging .trendline circle {\n stroke: darkblue;\n }\n\n .interaction path, .interaction circle {\n pointer-events: all;\n }\n\n .interaction .body {\n cursor: move;\n }\n\n .trendlines .interaction .start, .trendlines .interaction .end {\n cursor: nwse-resize;\n }\n\n .supstance path {\n stroke-dasharray: 2, 2;\n }\n\n .supstances .interaction path {\n pointer-events: all;\n cursor: ns-resize;\n }\n\n .mouseover .supstance path {\n stroke-width: 1.5;\n }\n\n .dragging .supstance path {\n stroke: darkblue;\n }\n\n .crosshair {\n cursor: crosshair;\n }\n\n .crosshair path.wire {\n stroke: #DDDDDD;\n stroke-dasharray: 1, 1;\n }\n\n .crosshair .axisannotation path {\n fill: #DDDDDD;\n }\n\n .tradearrow path.tradearrow {\n stroke: none;\n }\n\n .tradearrow path.buy {\n fill: #0000FF;\n }\n\n .tradearrow path.sell {\n fill: #9900FF;\n }\n\n .tradearrow path.highlight {\n fill: none;\n stroke-width: 2;\n }\n\n .tradearrow path.highlight.buy {\n stroke: #0000FF;\n }\n\n .tradearrow path.highlight.sell {\n stroke: #9900FF;\n }\n</style>\n"},"metadata":{}}]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%javascript\n\nelement.append('<div id=\"wrapper\" style=\"height: 500px;\"></div>');\n\nrequire.config({\n paths: {\n d3: '//d3js.org/d3.v4.min',\n techan: '//hkmoon.github.io/static/js/techan.min',\n },\n shim: {\n techan: {\n deps: ['d3'],\n init: function (d3) {\n window.d3 = d3 = require(\"d3\");\n return window.techan;\n }\n }\n }\n});\n\n// require(['techan'], function (techan) {\n// console.log(techan);\n// }, function(error) {\n// console.log(error);\n// })\n\nrequire(['d3', 'techan'], function (d3, techan) {\n\n console.log('d3.version', d3.version);\n// console.log('techan.version', techan.version);\n console.log('techan.version', techan.version);\n\n var dim = {\n width: 960, height: 500,\n margin: { top: 20, right: 50, bottom: 30, left: 50 },\n ohlc: { height: 305 },\n indicator: { height: 65, padding: 5 }\n };\n dim.plot = {\n width: dim.width - dim.margin.left - dim.margin.right,\n height: dim.height - dim.margin.top - dim.margin.bottom\n };\n dim.indicator.top = dim.ohlc.height+dim.indicator.padding;\n dim.indicator.bottom = dim.indicator.top+dim.indicator.height+dim.indicator.padding;\n\n var indicatorTop = d3.scaleLinear()\n .range([dim.indicator.top, dim.indicator.bottom]);\n\n var parseDate = d3.timeParse(\"%d-%b-%y\");\n\n var zoom = d3.zoom()\n .on(\"zoom\", zoomed);\n\n var x = techan.scale.financetime()\n .range([0, dim.plot.width]);\n\n var y = d3.scaleLinear()\n .range([dim.ohlc.height, 0]);\n\n\n var yPercent = y.copy(); // Same as y at this stage, will get a different domain later\n\n var yInit, yPercentInit, zoomableInit;\n\n var yVolume = d3.scaleLinear()\n .range([y(0), y(0.2)]);\n\n var candlestick = techan.plot.candlestick()\n .xScale(x)\n .yScale(y);\n\n var tradearrow = techan.plot.tradearrow()\n .xScale(x)\n .yScale(y)\n .y(function(d) {\n // Display the buy and sell arrows a bit above and below the price, so the price is still visible\n if(d.type === 'buy') return y(d.low)+5;\n if(d.type === 'sell') return y(d.high)-5;\n else return y(d.price);\n });\n\n var sma0 = techan.plot.sma()\n .xScale(x)\n .yScale(y);\n\n var sma1 = techan.plot.sma()\n .xScale(x)\n .yScale(y);\n\n var ema2 = techan.plot.ema()\n .xScale(x)\n .yScale(y);\n\n var volume = techan.plot.volume()\n .accessor(candlestick.accessor()) // Set the accessor to a ohlc accessor so we get highlighted bars\n .xScale(x)\n .yScale(yVolume);\n\n var trendline = techan.plot.trendline()\n .xScale(x)\n .yScale(y);\n\n var supstance = techan.plot.supstance()\n .xScale(x)\n .yScale(y);\n\n var xAxis = d3.axisBottom(x);\n\n var timeAnnotation = techan.plot.axisannotation()\n .axis(xAxis)\n .orient('bottom')\n .format(d3.timeFormat('%Y-%m-%d'))\n .width(65)\n .translate([0, dim.plot.height]);\n\n var yAxis = d3.axisRight(y);\n\n var ohlcAnnotation = techan.plot.axisannotation()\n .axis(yAxis)\n .orient('right')\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var closeAnnotation = techan.plot.axisannotation()\n .axis(yAxis)\n .orient('right')\n .accessor(candlestick.accessor())\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var percentAxis = d3.axisLeft(yPercent)\n .tickFormat(d3.format('+.1%'));\n\n var percentAnnotation = techan.plot.axisannotation()\n .axis(percentAxis)\n .orient('left');\n\n var volumeAxis = d3.axisRight(yVolume)\n .ticks(3)\n .tickFormat(d3.format(\",.3s\"));\n\n var volumeAnnotation = techan.plot.axisannotation()\n .axis(volumeAxis)\n .orient(\"right\")\n .width(35);\n\n var macdScale = d3.scaleLinear()\n .range([indicatorTop(0)+dim.indicator.height, indicatorTop(0)]);\n\n var rsiScale = macdScale.copy()\n .range([indicatorTop(1)+dim.indicator.height, indicatorTop(1)]);\n\n var macd = techan.plot.macd()\n .xScale(x)\n .yScale(macdScale);\n\n var macdAxis = d3.axisRight(macdScale)\n .ticks(3);\n\n var macdAnnotation = techan.plot.axisannotation()\n .axis(macdAxis)\n .orient(\"right\")\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var macdAxisLeft = d3.axisLeft(macdScale)\n .ticks(3);\n\n var macdAnnotationLeft = techan.plot.axisannotation()\n .axis(macdAxisLeft)\n .orient(\"left\")\n .format(d3.format(',.2f'));\n\n var rsi = techan.plot.rsi()\n .xScale(x)\n .yScale(rsiScale);\n\n var rsiAxis = d3.axisRight(rsiScale)\n .ticks(3);\n\n var rsiAnnotation = techan.plot.axisannotation()\n .axis(rsiAxis)\n .orient(\"right\")\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var rsiAxisLeft = d3.axisLeft(rsiScale)\n .ticks(3);\n\n var rsiAnnotationLeft = techan.plot.axisannotation()\n .axis(rsiAxisLeft)\n .orient(\"left\")\n .format(d3.format(',.2f'));\n\n var ohlcCrosshair = techan.plot.crosshair()\n .xScale(timeAnnotation.axis().scale())\n .yScale(ohlcAnnotation.axis().scale())\n .xAnnotation(timeAnnotation)\n .yAnnotation([ohlcAnnotation, percentAnnotation, volumeAnnotation])\n .verticalWireRange([0, dim.plot.height]);\n\n var macdCrosshair = techan.plot.crosshair()\n .xScale(timeAnnotation.axis().scale())\n .yScale(macdAnnotation.axis().scale())\n .xAnnotation(timeAnnotation)\n .yAnnotation([macdAnnotation, macdAnnotationLeft])\n .verticalWireRange([0, dim.plot.height]);\n\n var rsiCrosshair = techan.plot.crosshair()\n .xScale(timeAnnotation.axis().scale())\n .yScale(rsiAnnotation.axis().scale())\n .xAnnotation(timeAnnotation)\n .yAnnotation([rsiAnnotation, rsiAnnotationLeft])\n .verticalWireRange([0, dim.plot.height]);\n\n\n var svg = d3.select(\"#wrapper\").append(\"svg\")\n .attr(\"width\", dim.width)\n .attr(\"height\", dim.height);\n\nvar defs = svg.append(\"defs\");\n\n defs.append(\"clipPath\")\n .attr(\"id\", \"ohlcClip\")\n .append(\"rect\")\n .attr(\"x\", 0)\n .attr(\"y\", 0)\n .attr(\"width\", dim.plot.width)\n .attr(\"height\", dim.ohlc.height);\n\n defs.selectAll(\"indicatorClip\").data([0, 1])\n .enter()\n .append(\"clipPath\")\n .attr(\"id\", function(d, i) { return \"indicatorClip-\" + i; })\n .append(\"rect\")\n .attr(\"x\", 0)\n .attr(\"y\", function(d, i) { return indicatorTop(i); })\n .attr(\"width\", dim.plot.width)\n .attr(\"height\", dim.indicator.height);\n\n svg = svg.append(\"g\")\n .attr(\"transform\", \"translate(\" + dim.margin.left + \",\" + dim.margin.top + \")\");\n\n svg.append('text')\n .attr(\"class\", \"symbol\")\n .attr(\"x\", 20)\n .text(\"Facebook, Inc. (FB)\");\n\n svg.append(\"g\")\n .attr(\"class\", \"x axis\")\n .attr(\"transform\", \"translate(0,\" + dim.plot.height + \")\");\n\n var ohlcSelection = svg.append(\"g\")\n .attr(\"class\", \"ohlc\")\n .attr(\"transform\", \"translate(0,0)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"axis\")\n .attr(\"transform\", \"translate(\" + x(1) + \",0)\")\n .append(\"text\")\n .attr(\"transform\", \"rotate(-90)\")\n .attr(\"y\", -12)\n .attr(\"dy\", \".71em\")\n .style(\"text-anchor\", \"end\")\n .text(\"Price ($)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"close annotation up\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"volume\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"candlestick\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"indicator sma ma-0\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"indicator sma ma-1\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"indicator ema ma-2\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"percent axis\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"volume axis\");\n\n var indicatorSelection = svg.selectAll(\"svg > g.indicator\").data([\"macd\", \"rsi\"]).enter()\n .append(\"g\")\n .attr(\"class\", function(d) { return d + \" indicator\"; });\n\n indicatorSelection.append(\"g\")\n .attr(\"class\", \"axis right\")\n .attr(\"transform\", \"translate(\" + x(1) + \",0)\");\n\n indicatorSelection.append(\"g\")\n .attr(\"class\", \"axis left\")\n .attr(\"transform\", \"translate(\" + x(0) + \",0)\");\n\n indicatorSelection.append(\"g\")\n .attr(\"class\", \"indicator-plot\")\n .attr(\"clip-path\", function(d, i) { return \"url(#indicatorClip-\" + i + \")\"; });\n\n // Add trendlines and other interactions last to be above zoom pane\n svg.append('g')\n .attr(\"class\", \"crosshair ohlc\");\n\n svg.append(\"g\")\n .attr(\"class\", \"tradearrow\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n svg.append('g')\n .attr(\"class\", \"crosshair macd\");\n\n svg.append('g')\n .attr(\"class\", \"crosshair rsi\");\n\n svg.append(\"g\")\n .attr(\"class\", \"trendlines analysis\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n svg.append(\"g\")\n .attr(\"class\", \"supstances analysis\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n d3.select(\"button\").on(\"click\", reset);\n\n d3.csv(\"https://gist.githubusercontent.com/andredumas/27c4a333b0e0813e093d/raw/ca6191ee1820b61dc7722d217ea59a2ee4a9a57c/data.csv\", function(error, data) {\n var accessor = candlestick.accessor(),\n indicatorPreRoll = 33; // Don't show where indicators don't have data\n \n data = data.map(function(d) {\n return {\n date: parseDate(d.Date),\n open: +d.Open,\n high: +d.High,\n low: +d.Low,\n close: +d.Close,\n volume: +d.Volume\n };\n }).sort(function(a, b) { return d3.ascending(accessor.d(a), accessor.d(b)); });\n\n x.domain(techan.scale.plot.time(data).domain());\n y.domain(techan.scale.plot.ohlc(data.slice(indicatorPreRoll)).domain());\n yPercent.domain(techan.scale.plot.percent(y, accessor(data[indicatorPreRoll])).domain());\n yVolume.domain(techan.scale.plot.volume(data).domain());\n\n var trendlineData = [\n { start: { date: new Date(2014, 2, 11), value: 72.50 }, end: { date: new Date(2014, 5, 9), value: 63.34 } },\n { start: { date: new Date(2013, 10, 21), value: 43 }, end: { date: new Date(2014, 2, 17), value: 70.50 } }\n ];\n\n var supstanceData = [\n { start: new Date(2014, 2, 11), end: new Date(2014, 5, 9), value: 63.64 },\n { start: new Date(2013, 10, 21), end: new Date(2014, 2, 17), value: 55.50 }\n ];\n\n var trades = [\n { date: data[67].date, type: \"buy\", price: data[67].low, low: data[67].low, high: data[67].high },\n { date: data[100].date, type: \"sell\", price: data[100].high, low: data[100].low, high: data[100].high },\n { date: data[130].date, type: \"buy\", price: data[130].low, low: data[130].low, high: data[130].high },\n { date: data[170].date, type: \"sell\", price: data[170].low, low: data[170].low, high: data[170].high }\n ];\n\n var macdData = techan.indicator.macd()(data);\n macdScale.domain(techan.scale.plot.macd(macdData).domain());\n var rsiData = techan.indicator.rsi()(data);\n rsiScale.domain(techan.scale.plot.rsi(rsiData).domain());\n\n svg.select(\"g.candlestick\").datum(data).call(candlestick);\n svg.select(\"g.close.annotation\").datum([data[data.length-1]]).call(closeAnnotation);\n svg.select(\"g.volume\").datum(data).call(volume);\n svg.select(\"g.sma.ma-0\").datum(techan.indicator.sma().period(10)(data)).call(sma0);\n svg.select(\"g.sma.ma-1\").datum(techan.indicator.sma().period(20)(data)).call(sma1);\n svg.select(\"g.ema.ma-2\").datum(techan.indicator.ema().period(50)(data)).call(ema2);\n svg.select(\"g.macd .indicator-plot\").datum(macdData).call(macd);\n svg.select(\"g.rsi .indicator-plot\").datum(rsiData).call(rsi);\n\n svg.select(\"g.crosshair.ohlc\").call(ohlcCrosshair).call(zoom);\n svg.select(\"g.crosshair.macd\").call(macdCrosshair).call(zoom);\n svg.select(\"g.crosshair.rsi\").call(rsiCrosshair).call(zoom);\n svg.select(\"g.trendlines\").datum(trendlineData).call(trendline).call(trendline.drag);\n svg.select(\"g.supstances\").datum(supstanceData).call(supstance).call(supstance.drag);\n\n svg.select(\"g.tradearrow\").datum(trades).call(tradearrow);\n\n // Stash for zooming\n zoomableInit = x.zoomable().domain([indicatorPreRoll, data.length]).copy(); // Zoom in a little to hide indicator preroll\n yInit = y.copy();\n yPercentInit = yPercent.copy();\n\n draw();\n });\n \n function reset() {\n zoom.scale(1);\n zoom.translate([0,0]);\n draw();\n }\n\n function zoomed() {\n x.zoomable().domain(d3.event.transform.rescaleX(zoomableInit).domain());\n y.domain(d3.event.transform.rescaleY(yInit).domain());\n yPercent.domain(d3.event.transform.rescaleY(yPercentInit).domain());\n\n draw();\n }\n\n function draw() {\n svg.select(\"g.x.axis\").call(xAxis);\n svg.select(\"g.ohlc .axis\").call(yAxis);\n svg.select(\"g.volume.axis\").call(volumeAxis);\n svg.select(\"g.percent.axis\").call(percentAxis);\n svg.select(\"g.macd .axis.right\").call(macdAxis);\n svg.select(\"g.rsi .axis.right\").call(rsiAxis);\n svg.select(\"g.macd .axis.left\").call(macdAxisLeft);\n svg.select(\"g.rsi .axis.left\").call(rsiAxisLeft);\n\n // We know the data does not change, a simple refresh that does not perform data joins will suffice.\n svg.select(\"g.candlestick\").call(candlestick.refresh);\n svg.select(\"g.close.annotation\").call(closeAnnotation.refresh);\n svg.select(\"g.volume\").call(volume.refresh);\n svg.select(\"g .sma.ma-0\").call(sma0.refresh);\n svg.select(\"g .sma.ma-1\").call(sma1.refresh);\n svg.select(\"g .ema.ma-2\").call(ema2.refresh);\n svg.select(\"g.macd .indicator-plot\").call(macd.refresh);\n svg.select(\"g.rsi .indicator-plot\").call(rsi.refresh);\n svg.select(\"g.crosshair.ohlc\").call(ohlcCrosshair.refresh);\n svg.select(\"g.crosshair.macd\").call(macdCrosshair.refresh);\n svg.select(\"g.crosshair.rsi\").call(rsiCrosshair.refresh);\n svg.select(\"g.trendlines\").call(trendline.refresh);\n svg.select(\"g.supstances\").call(supstance.refresh);\n svg.select(\"g.tradearrow\").call(tradearrow.refresh);\n }\n\n}, function(error) {\n console.log(error);\n})","execution_count":2,"outputs":[{"output_type":"display_data","data":{"text/plain":"<IPython.core.display.Javascript object>","application/javascript":"\nelement.append('<div id=\"wrapper\" style=\"height: 500px;\"></div>');\n\nrequire.config({\n paths: {\n d3: '//d3js.org/d3.v4.min',\n techan: '//hkmoon.github.io/static/js/techan.min',\n },\n shim: {\n techan: {\n deps: ['d3'],\n init: function (d3) {\n window.d3 = d3 = require(\"d3\");\n return window.techan;\n }\n }\n }\n});\n\n// require(['techan'], function (techan) {\n// console.log(techan);\n// }, function(error) {\n// console.log(error);\n// })\n\nrequire(['d3', 'techan'], function (d3, techan) {\n\n console.log('d3.version', d3.version);\n// console.log('techan.version', techan.version);\n console.log('techan.version', techan.version);\n\n var dim = {\n width: 960, height: 500,\n margin: { top: 20, right: 50, bottom: 30, left: 50 },\n ohlc: { height: 305 },\n indicator: { height: 65, padding: 5 }\n };\n dim.plot = {\n width: dim.width - dim.margin.left - dim.margin.right,\n height: dim.height - dim.margin.top - dim.margin.bottom\n };\n dim.indicator.top = dim.ohlc.height+dim.indicator.padding;\n dim.indicator.bottom = dim.indicator.top+dim.indicator.height+dim.indicator.padding;\n\n var indicatorTop = d3.scaleLinear()\n .range([dim.indicator.top, dim.indicator.bottom]);\n\n var parseDate = d3.timeParse(\"%d-%b-%y\");\n\n var zoom = d3.zoom()\n .on(\"zoom\", zoomed);\n\n var x = techan.scale.financetime()\n .range([0, dim.plot.width]);\n\n var y = d3.scaleLinear()\n .range([dim.ohlc.height, 0]);\n\n\n var yPercent = y.copy(); // Same as y at this stage, will get a different domain later\n\n var yInit, yPercentInit, zoomableInit;\n\n var yVolume = d3.scaleLinear()\n .range([y(0), y(0.2)]);\n\n var candlestick = techan.plot.candlestick()\n .xScale(x)\n .yScale(y);\n\n var tradearrow = techan.plot.tradearrow()\n .xScale(x)\n .yScale(y)\n .y(function(d) {\n // Display the buy and sell arrows a bit above and below the price, so the price is still visible\n if(d.type === 'buy') return y(d.low)+5;\n if(d.type === 'sell') return y(d.high)-5;\n else return y(d.price);\n });\n\n var sma0 = techan.plot.sma()\n .xScale(x)\n .yScale(y);\n\n var sma1 = techan.plot.sma()\n .xScale(x)\n .yScale(y);\n\n var ema2 = techan.plot.ema()\n .xScale(x)\n .yScale(y);\n\n var volume = techan.plot.volume()\n .accessor(candlestick.accessor()) // Set the accessor to a ohlc accessor so we get highlighted bars\n .xScale(x)\n .yScale(yVolume);\n\n var trendline = techan.plot.trendline()\n .xScale(x)\n .yScale(y);\n\n var supstance = techan.plot.supstance()\n .xScale(x)\n .yScale(y);\n\n var xAxis = d3.axisBottom(x);\n\n var timeAnnotation = techan.plot.axisannotation()\n .axis(xAxis)\n .orient('bottom')\n .format(d3.timeFormat('%Y-%m-%d'))\n .width(65)\n .translate([0, dim.plot.height]);\n\n var yAxis = d3.axisRight(y);\n\n var ohlcAnnotation = techan.plot.axisannotation()\n .axis(yAxis)\n .orient('right')\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var closeAnnotation = techan.plot.axisannotation()\n .axis(yAxis)\n .orient('right')\n .accessor(candlestick.accessor())\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var percentAxis = d3.axisLeft(yPercent)\n .tickFormat(d3.format('+.1%'));\n\n var percentAnnotation = techan.plot.axisannotation()\n .axis(percentAxis)\n .orient('left');\n\n var volumeAxis = d3.axisRight(yVolume)\n .ticks(3)\n .tickFormat(d3.format(\",.3s\"));\n\n var volumeAnnotation = techan.plot.axisannotation()\n .axis(volumeAxis)\n .orient(\"right\")\n .width(35);\n\n var macdScale = d3.scaleLinear()\n .range([indicatorTop(0)+dim.indicator.height, indicatorTop(0)]);\n\n var rsiScale = macdScale.copy()\n .range([indicatorTop(1)+dim.indicator.height, indicatorTop(1)]);\n\n var macd = techan.plot.macd()\n .xScale(x)\n .yScale(macdScale);\n\n var macdAxis = d3.axisRight(macdScale)\n .ticks(3);\n\n var macdAnnotation = techan.plot.axisannotation()\n .axis(macdAxis)\n .orient(\"right\")\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var macdAxisLeft = d3.axisLeft(macdScale)\n .ticks(3);\n\n var macdAnnotationLeft = techan.plot.axisannotation()\n .axis(macdAxisLeft)\n .orient(\"left\")\n .format(d3.format(',.2f'));\n\n var rsi = techan.plot.rsi()\n .xScale(x)\n .yScale(rsiScale);\n\n var rsiAxis = d3.axisRight(rsiScale)\n .ticks(3);\n\n var rsiAnnotation = techan.plot.axisannotation()\n .axis(rsiAxis)\n .orient(\"right\")\n .format(d3.format(',.2f'))\n .translate([x(1), 0]);\n\n var rsiAxisLeft = d3.axisLeft(rsiScale)\n .ticks(3);\n\n var rsiAnnotationLeft = techan.plot.axisannotation()\n .axis(rsiAxisLeft)\n .orient(\"left\")\n .format(d3.format(',.2f'));\n\n var ohlcCrosshair = techan.plot.crosshair()\n .xScale(timeAnnotation.axis().scale())\n .yScale(ohlcAnnotation.axis().scale())\n .xAnnotation(timeAnnotation)\n .yAnnotation([ohlcAnnotation, percentAnnotation, volumeAnnotation])\n .verticalWireRange([0, dim.plot.height]);\n\n var macdCrosshair = techan.plot.crosshair()\n .xScale(timeAnnotation.axis().scale())\n .yScale(macdAnnotation.axis().scale())\n .xAnnotation(timeAnnotation)\n .yAnnotation([macdAnnotation, macdAnnotationLeft])\n .verticalWireRange([0, dim.plot.height]);\n\n var rsiCrosshair = techan.plot.crosshair()\n .xScale(timeAnnotation.axis().scale())\n .yScale(rsiAnnotation.axis().scale())\n .xAnnotation(timeAnnotation)\n .yAnnotation([rsiAnnotation, rsiAnnotationLeft])\n .verticalWireRange([0, dim.plot.height]);\n\n\n var svg = d3.select(\"#wrapper\").append(\"svg\")\n .attr(\"width\", dim.width)\n .attr(\"height\", dim.height);\n\nvar defs = svg.append(\"defs\");\n\n defs.append(\"clipPath\")\n .attr(\"id\", \"ohlcClip\")\n .append(\"rect\")\n .attr(\"x\", 0)\n .attr(\"y\", 0)\n .attr(\"width\", dim.plot.width)\n .attr(\"height\", dim.ohlc.height);\n\n defs.selectAll(\"indicatorClip\").data([0, 1])\n .enter()\n .append(\"clipPath\")\n .attr(\"id\", function(d, i) { return \"indicatorClip-\" + i; })\n .append(\"rect\")\n .attr(\"x\", 0)\n .attr(\"y\", function(d, i) { return indicatorTop(i); })\n .attr(\"width\", dim.plot.width)\n .attr(\"height\", dim.indicator.height);\n\n svg = svg.append(\"g\")\n .attr(\"transform\", \"translate(\" + dim.margin.left + \",\" + dim.margin.top + \")\");\n\n svg.append('text')\n .attr(\"class\", \"symbol\")\n .attr(\"x\", 20)\n .text(\"Facebook, Inc. (FB)\");\n\n svg.append(\"g\")\n .attr(\"class\", \"x axis\")\n .attr(\"transform\", \"translate(0,\" + dim.plot.height + \")\");\n\n var ohlcSelection = svg.append(\"g\")\n .attr(\"class\", \"ohlc\")\n .attr(\"transform\", \"translate(0,0)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"axis\")\n .attr(\"transform\", \"translate(\" + x(1) + \",0)\")\n .append(\"text\")\n .attr(\"transform\", \"rotate(-90)\")\n .attr(\"y\", -12)\n .attr(\"dy\", \".71em\")\n .style(\"text-anchor\", \"end\")\n .text(\"Price ($)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"close annotation up\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"volume\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"candlestick\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"indicator sma ma-0\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"indicator sma ma-1\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"indicator ema ma-2\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"percent axis\");\n\n ohlcSelection.append(\"g\")\n .attr(\"class\", \"volume axis\");\n\n var indicatorSelection = svg.selectAll(\"svg > g.indicator\").data([\"macd\", \"rsi\"]).enter()\n .append(\"g\")\n .attr(\"class\", function(d) { return d + \" indicator\"; });\n\n indicatorSelection.append(\"g\")\n .attr(\"class\", \"axis right\")\n .attr(\"transform\", \"translate(\" + x(1) + \",0)\");\n\n indicatorSelection.append(\"g\")\n .attr(\"class\", \"axis left\")\n .attr(\"transform\", \"translate(\" + x(0) + \",0)\");\n\n indicatorSelection.append(\"g\")\n .attr(\"class\", \"indicator-plot\")\n .attr(\"clip-path\", function(d, i) { return \"url(#indicatorClip-\" + i + \")\"; });\n\n // Add trendlines and other interactions last to be above zoom pane\n svg.append('g')\n .attr(\"class\", \"crosshair ohlc\");\n\n svg.append(\"g\")\n .attr(\"class\", \"tradearrow\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n svg.append('g')\n .attr(\"class\", \"crosshair macd\");\n\n svg.append('g')\n .attr(\"class\", \"crosshair rsi\");\n\n svg.append(\"g\")\n .attr(\"class\", \"trendlines analysis\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n svg.append(\"g\")\n .attr(\"class\", \"supstances analysis\")\n .attr(\"clip-path\", \"url(#ohlcClip)\");\n\n d3.select(\"button\").on(\"click\", reset);\n\n d3.csv(\"https://gist.githubusercontent.com/andredumas/27c4a333b0e0813e093d/raw/ca6191ee1820b61dc7722d217ea59a2ee4a9a57c/data.csv\", function(error, data) {\n var accessor = candlestick.accessor(),\n indicatorPreRoll = 33; // Don't show where indicators don't have data\n \n data = data.map(function(d) {\n return {\n date: parseDate(d.Date),\n open: +d.Open,\n high: +d.High,\n low: +d.Low,\n close: +d.Close,\n volume: +d.Volume\n };\n }).sort(function(a, b) { return d3.ascending(accessor.d(a), accessor.d(b)); });\n\n x.domain(techan.scale.plot.time(data).domain());\n y.domain(techan.scale.plot.ohlc(data.slice(indicatorPreRoll)).domain());\n yPercent.domain(techan.scale.plot.percent(y, accessor(data[indicatorPreRoll])).domain());\n yVolume.domain(techan.scale.plot.volume(data).domain());\n\n var trendlineData = [\n { start: { date: new Date(2014, 2, 11), value: 72.50 }, end: { date: new Date(2014, 5, 9), value: 63.34 } },\n { start: { date: new Date(2013, 10, 21), value: 43 }, end: { date: new Date(2014, 2, 17), value: 70.50 } }\n ];\n\n var supstanceData = [\n { start: new Date(2014, 2, 11), end: new Date(2014, 5, 9), value: 63.64 },\n { start: new Date(2013, 10, 21), end: new Date(2014, 2, 17), value: 55.50 }\n ];\n\n var trades = [\n { date: data[67].date, type: \"buy\", price: data[67].low, low: data[67].low, high: data[67].high },\n { date: data[100].date, type: \"sell\", price: data[100].high, low: data[100].low, high: data[100].high },\n { date: data[130].date, type: \"buy\", price: data[130].low, low: data[130].low, high: data[130].high },\n { date: data[170].date, type: \"sell\", price: data[170].low, low: data[170].low, high: data[170].high }\n ];\n\n var macdData = techan.indicator.macd()(data);\n macdScale.domain(techan.scale.plot.macd(macdData).domain());\n var rsiData = techan.indicator.rsi()(data);\n rsiScale.domain(techan.scale.plot.rsi(rsiData).domain());\n\n svg.select(\"g.candlestick\").datum(data).call(candlestick);\n svg.select(\"g.close.annotation\").datum([data[data.length-1]]).call(closeAnnotation);\n svg.select(\"g.volume\").datum(data).call(volume);\n svg.select(\"g.sma.ma-0\").datum(techan.indicator.sma().period(10)(data)).call(sma0);\n svg.select(\"g.sma.ma-1\").datum(techan.indicator.sma().period(20)(data)).call(sma1);\n svg.select(\"g.ema.ma-2\").datum(techan.indicator.ema().period(50)(data)).call(ema2);\n svg.select(\"g.macd .indicator-plot\").datum(macdData).call(macd);\n svg.select(\"g.rsi .indicator-plot\").datum(rsiData).call(rsi);\n\n svg.select(\"g.crosshair.ohlc\").call(ohlcCrosshair).call(zoom);\n svg.select(\"g.crosshair.macd\").call(macdCrosshair).call(zoom);\n svg.select(\"g.crosshair.rsi\").call(rsiCrosshair).call(zoom);\n svg.select(\"g.trendlines\").datum(trendlineData).call(trendline).call(trendline.drag);\n svg.select(\"g.supstances\").datum(supstanceData).call(supstance).call(supstance.drag);\n\n svg.select(\"g.tradearrow\").datum(trades).call(tradearrow);\n\n // Stash for zooming\n zoomableInit = x.zoomable().domain([indicatorPreRoll, data.length]).copy(); // Zoom in a little to hide indicator preroll\n yInit = y.copy();\n yPercentInit = yPercent.copy();\n\n draw();\n });\n \n function reset() {\n zoom.scale(1);\n zoom.translate([0,0]);\n draw();\n }\n\n function zoomed() {\n x.zoomable().domain(d3.event.transform.rescaleX(zoomableInit).domain());\n y.domain(d3.event.transform.rescaleY(yInit).domain());\n yPercent.domain(d3.event.transform.rescaleY(yPercentInit).domain());\n\n draw();\n }\n\n function draw() {\n svg.select(\"g.x.axis\").call(xAxis);\n svg.select(\"g.ohlc .axis\").call(yAxis);\n svg.select(\"g.volume.axis\").call(volumeAxis);\n svg.select(\"g.percent.axis\").call(percentAxis);\n svg.select(\"g.macd .axis.right\").call(macdAxis);\n svg.select(\"g.rsi .axis.right\").call(rsiAxis);\n svg.select(\"g.macd .axis.left\").call(macdAxisLeft);\n svg.select(\"g.rsi .axis.left\").call(rsiAxisLeft);\n\n // We know the data does not change, a simple refresh that does not perform data joins will suffice.\n svg.select(\"g.candlestick\").call(candlestick.refresh);\n svg.select(\"g.close.annotation\").call(closeAnnotation.refresh);\n svg.select(\"g.volume\").call(volume.refresh);\n svg.select(\"g .sma.ma-0\").call(sma0.refresh);\n svg.select(\"g .sma.ma-1\").call(sma1.refresh);\n svg.select(\"g .ema.ma-2\").call(ema2.refresh);\n svg.select(\"g.macd .indicator-plot\").call(macd.refresh);\n svg.select(\"g.rsi .indicator-plot\").call(rsi.refresh);\n svg.select(\"g.crosshair.ohlc\").call(ohlcCrosshair.refresh);\n svg.select(\"g.crosshair.macd\").call(macdCrosshair.refresh);\n svg.select(\"g.crosshair.rsi\").call(rsiCrosshair.refresh);\n svg.select(\"g.trendlines\").call(trendline.refresh);\n svg.select(\"g.supstances\").call(supstance.refresh);\n svg.select(\"g.tradearrow\").call(tradearrow.refresh);\n }\n\n}, function(error) {\n console.log(error);\n})\n"},"metadata":{}}]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.6.8","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"toc":{"nav_menu":{},"number_sections":false,"sideBar":false,"skip_h1_title":false,"base_numbering":1,"title_cell":"Table of Contents","title_sidebar":"Contents","toc_cell":false,"toc_position":{},"toc_section_display":false,"toc_window_display":false}},"nbformat":4,"nbformat_minor":2} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment