Skip to content

Instantly share code, notes, and snippets.

@hkmoon
Created February 25, 2020 07:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hkmoon/ac296af2147288fd8f0f11a29040fa83 to your computer and use it in GitHub Desktop.
Save hkmoon/ac296af2147288fd8f0f11a29040fa83 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{"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