"use strict"; var _ = require("underscore"); var Stats = require("fast-stats").Stats; var Layer = require("../../layer/Layer"); var Arc = require("../../element/Arc"); const _default_config = { input: "close", upperBandOutputPrefix: "_bb_upper", midOutputPrefix: "_bb_mid", lowerBandOutputPrefix: "_bb_lower", period: 20, multiplier: 2, bandColor: "rgba(0, 0, 0, 0.3)", midColor: "rgba(0, 0, 0, 0.3)" }; /** * Represents a Bollinger Bands chart layer. *

* @extends layer.Layer * @memberof layer.indicator */ class BollingerBandsLayer extends Layer { /** * Instantiate BollingerBandsLayer * @constructor * @param {object} config */ constructor(config) { config = _.extend({}, _default_config, config); super(config); this.upperBandOutput = this.upperBandOutputPrefix + "" + this.period + "x" + this.multiplier; this.midOutput = this.midOutputPrefix + "" + this.period + "x" + this.multiplier; this.lowerBandOutput = this.lowerBandOutputPrefix + "" + this.period + "x" + this.multiplier; this.minField = this.lowerBandOutput; this.maxField = this.upperBandOutput; } /** * Precompute indicator fields using time series OHLCV data. *

* This is invoked before draw(). * @param {timeseries.TimeSeriesData} data */ precompute(data) { var data_arr = data.getRawData(); var field_map = data.getFieldMap(); var input_field = field_map[this.input]; var upper_band_field = this.upperBandOutput; var mid_field = this.midOutput; var lower_band_field = this.lowerBandOutput; var period = this.period; var mean = new Stats(); var sd = new Stats(); for(var i = 0; i < data_arr.length; i++) { var dat = data_arr[i]; mean.push(dat[input_field]); sd.push(dat[input_field]); if(sd.length === period) { var ma = mean.amean(); var std_dev = sd.stddev(); dat[upper_band_field] = ma + 2 * std_dev; dat[mid_field] = ma; dat[lower_band_field] = ma - 2 * std_dev; if(isNaN(dat[upper_band_field])) { dat[upper_band_field] = 0.0; } if(isNaN(dat[lower_band_field])) { dat[lower_band_field] = 0.0; } sd.shift(); mean.shift(); } } } /** * Render layer onto canvas * @param {timeseries.TimeSeriesData} data * @param {number} count * @param {number} offset * @param {valueToPixel} valueToPixel * @param {indexToPixel} indexToPixel */ draw(data, count, offset, valueToPixel, indexToPixel) { var context = this._getContext(); var field_map = data.getFieldMap(); var data_arr = data.getRawData(); this.elements = [], this.elements_upper = [], this.elements_lower = []; var prev_arc_upper = null, prev_arc_mid = null, prev_arc_lower = null; for(var i = offset >= 0 ? offset : 0; i < offset + count && i < data_arr.length; i++) { var dat = data_arr[i]; // upper var arc_upper = new Arc( this, i, dat[field_map.time], dat[this.upperBandOutput], prev_arc_upper); this.elements_upper.push(arc_upper); arc_upper.draw(context, valueToPixel, indexToPixel, {color:this.bandColor}); // mid (simple moving average) var arc_mid = new Arc( this, i, dat[field_map.time], dat[this.midOutput], prev_arc_mid); this.elements.push(arc_mid); arc_mid.draw(context, valueToPixel, indexToPixel, {color:this.midColor}); // lower var arc_lower = new Arc( this, i, dat[field_map.time], dat[this.lowerBandOutput], prev_arc_lower); this.elements_lower.push(arc_lower); arc_lower.draw(context, valueToPixel, indexToPixel, {color:this.bandColor}); prev_arc_upper = arc_upper; prev_arc_mid = arc_mid; prev_arc_lower = arc_lower; } } } module.exports = BollingerBandsLayer;