A market profile chart, using d3 and d3fc, as described in this blog post.
Built with blockbuilder.org
| license: mit |
A market profile chart, using d3 and d3fc, as described in this blog post.
Built with blockbuilder.org
| const createMarketProfile = (data, priceBuckets) => { | |
| // find the price bucket size | |
| const priceStep = priceBuckets[1] - priceBuckets[0]; | |
| // determine whether a datapoint is within a bucket | |
| const inBucket = (datum, priceBucket) => | |
| datum.low < priceBucket && datum.high > (priceBucket - priceStep); | |
| // the volume contribution for this range | |
| const volumeInBucket = (datum, priceBucket) => | |
| // inBucket(datum, priceBucket) ? 1 : 0; | |
| inBucket(datum, priceBucket) ? datum.volume / Math.ceil((datum.high - datum.low) / priceStep) : 0; | |
| // map each point in our time series, to construct the market profile | |
| const marketProfile = data.map( | |
| (datum, index) => priceBuckets.map(priceBucket => { | |
| // determine how many points to the left are also within this time bucket | |
| const base = d3.sum(data.slice(0, index) | |
| .map(d => volumeInBucket(d, priceBucket))); | |
| return { | |
| base, | |
| value: base + volumeInBucket(datum, priceBucket), | |
| price: priceBucket | |
| }; | |
| }) | |
| ); | |
| // similar to d3-stack - cache the underlying data | |
| marketProfile.data = data; | |
| return marketProfile; | |
| }; | |
| const timePeriods = 40; | |
| // create some random financial data | |
| const generator = fc.randomFinancial() | |
| .interval(d3.timeMinute) | |
| const timeSeries = generator(timePeriods); | |
| // determine the price range | |
| const extent = fc.extentLinear() | |
| .accessors([d => d.high, d => d.low]); | |
| const priceRange = extent(timeSeries); | |
| // use a d3 scale to create a set of price buckets | |
| const priceScale = d3.scaleLinear() | |
| .domain(priceRange); | |
| const priceBuckets = priceScale.ticks(20); | |
| const marketProfile = createMarketProfile(timeSeries, priceBuckets); | |
| const colorScale = d3.scaleSequential(d3.interpolateSpectral) | |
| .domain([0, timePeriods]); | |
| const barSeries = fc.autoBandwidth(fc.seriesSvgBar()) | |
| .orient('horizontal') | |
| .align('left') | |
| .crossValue(d => d.price) | |
| .mainValue(d => d.value) | |
| .baseValue(d => d.base); | |
| const repeat = fc.seriesSvgRepeat() | |
| .series(barSeries) | |
| .orient('horizontal') | |
| .decorate((selection) => { | |
| selection.enter() | |
| .each((data, index, group) => | |
| d3.select(group[index]) | |
| .selectAll('g.bar') | |
| .attr('fill', () => colorScale(index)) | |
| ); | |
| }); | |
| const xExtent = fc.extentLinear() | |
| .accessors([d => d.value]) | |
| .include([0]) | |
| const profileChart = fc.chartCartesian( | |
| d3.scaleLinear(), | |
| d3.scaleBand() | |
| ) | |
| .xDomain(xExtent(_.flattenDeep(marketProfile))) | |
| .yDomain(priceBuckets) | |
| .yTickValues(priceBuckets.filter((d, i) => i % 4 == 0)) | |
| .svgPlotArea(repeat); | |
| d3.select('#chart') | |
| .datum(marketProfile) | |
| .call(profileChart); |
| <!DOCTYPE html> | |
| <!-- include polyfills for custom event, Symbol and Custom Elements --> | |
| <script src="//unpkg.com/babel-polyfill@6.26.0/dist/polyfill.js"></script> | |
| <script src="//unpkg.com/custom-event-polyfill@0.3.0/custom-event-polyfill.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/document-register-element/1.8.0/document-register-element.js"></script> | |
| <!-- use babel so that we can use arrow functions and other goodness in this block! --> | |
| <script src="//unpkg.com/babel-standalone@6/babel.min.js"></script> | |
| <script src="//unpkg.com/d3@5.5.0"></script> | |
| <script src="//unpkg.com/d3fc@14.0.41"></script> | |
| <script src="https://unpkg.com/lodash@4.17.4"></script> | |
| <script src="https://unpkg.com/d3-scale-chromatic@1.1.1"></script> | |
| <style> | |
| g.multi { | |
| opacity: 0.8; | |
| } | |
| g.multi:hover { | |
| opacity: 1.0; | |
| } | |
| </style> | |
| <div id='chart' style='height: 500px'></div> | |
| <script src='chart.js' type='text/babel'></script> |