"use strict"; var _ = require("underscore"); var Layer = require("../../layer/Layer"); var Arc = require("../../element/Arc"); const _default_config = { lowInput: "low", highInput: "high", closeInput: "close", kOutput: "_stoch14_k", dOutput: "_stoch14_d", period: 14, kMa: 3, dMa: 5, kColor: "orange", dColor: "lime", minValue: 0.0, maxValue: 100.0 }; /** * Represents a Stochastic chart layer. *

* @extends layer.Layer * @memberof layer.indicator */ class StochasticLayer extends Layer { /** * Instantiate StochasticLayer * @constructor * @param {Object} config */ constructor(config) { config = _.extend({}, _default_config, config); super(config); } /** * 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 k_field = this.kOutput; var d_field = this.dOutput; var low_input = field_map[this.lowInput]; var high_input = field_map[this.highInput]; var close_input = field_map[this.closeInput]; var period = this.period, k_ma = this.kMa, d_ma = this.dMa; var low_queue = [], high_queue = [], k_queue = [], k_sum = 0.0, d_queue = [], d_sum = 0.0; var low, high; for(var i = 0; i < data_arr.length; i++) { var dat = data_arr[i]; // Low low_queue.push(dat[low_input]); // e.g. low if(low_queue.length === period) // e.g. 14 { low = Math.min.apply(Math, low_queue); low_queue.shift(); // remove first } // High high_queue.push(dat[high_input]); // e.g. high if(high_queue.length === period) // e.g. 14 { high = Math.max.apply(Math, high_queue); high_queue.shift(); // remove first } // k and d if(low !== undefined && high !== undefined) { var k = (dat[close_input] - low) / (high - low) * 100.0; if(isNaN(k)) { k = 0.0; } // Stochastic %K applied with SMA(3) k_queue.push(k); k_sum += k; if(k_queue.length === k_ma + 1) { k_sum -= k_queue.shift(); dat[k_field] = k_sum / k_ma; } if(dat[k_field] !== undefined) { // MA3 of %K d_queue.push(dat[k_field]); d_sum += dat[k_field]; if(d_queue.length === d_ma + 1) { d_sum -= d_queue.shift(); dat[d_field] = d_sum / d_ma; } } } } } /** * 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.d_elements = []; var prev_k_arc = null, prev_d_arc = null; for(var i = offset >= 0 ? offset : 0; i < offset + count && i < data_arr.length; i++) { var dat = data_arr[i]; // d var d_arc = new Arc( this, i, dat[field_map.time], dat[this.dOutput], prev_d_arc); this.d_elements.push(d_arc); d_arc.draw(context, valueToPixel, indexToPixel, {color:this.dColor}); // k var k_arc = new Arc( this, i, dat[field_map.time], dat[this.kOutput], prev_k_arc); this.elements.push(k_arc); k_arc.draw(context, valueToPixel, indexToPixel, {color:this.kColor}); prev_k_arc = k_arc; prev_d_arc = d_arc; } } } module.exports = StochasticLayer;