Skip to content

Instantly share code, notes, and snippets.

@ColinEberhardt
Last active January 21, 2024 22:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ColinEberhardt/d25e78dcdaf05e67cd40c7d3a4d803f6 to your computer and use it in GitHub Desktop.
Save ColinEberhardt/d25e78dcdaf05e67cd40c7d3a4d803f6 to your computer and use it in GitHub Desktop.
Market Profile Chart
license: mit
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment