"use strict";
var _ = require("underscore");
var Layer = require("../../layer/Layer");
var Arc = require("../../element/Arc");
const _default_config = {
input: "close",
output: "_rsi",
period: 14,
color: "red",
minValue: 0.0,
maxValue: 100.0
};
/**
* Represents a RSI chart layer.
*
* @extends layer.Layer
* @memberof layer.indicator
*/
class RSILayer extends Layer {
/**
* Instantiate RSILayer
* @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 input_field = field_map[this.input]; // i.e. close
var output_field = this.output;
var period = this.period;
var queue = [], gain_queue = [], loss_queue = [];
var prev_close, prev_avg_gain, prev_avg_loss;
var loss_sum = 0.0, gain_sum = 0.0;
for(var i = 1; i < data_arr.length; i++)
{
var dat = data_arr[i];
var dat_prev = data_arr[i - 1];
var change = dat[input_field] - dat_prev[input_field];
var abs_change = Math.abs(change);
var current_gain = 0.0;
var current_loss = 0.0;
var avg_gain = 0.0;
var avg_loss = 0.0;
var rs;
if(change >= 0.0)
{
current_gain = abs_change;
}
else
{
current_loss = abs_change;
}
if(queue.length < period)
{
queue.push(change);
if(change >= 0.0)
{
gain_queue.push(abs_change);
gain_sum += abs_change;
}
else
{
loss_queue.push(abs_change);
loss_sum += abs_change;
}
}
else if(queue.length === period)
{
rs = gain_sum / loss_sum;
dat[output_field] = 100.0 - (100.0 / (1.0 + rs));
if(isNaN(dat[output_field]))
{
dat[output_field] = 0.0;
}
queue.push(change); // length=(data.periods + 1)
}
else
{
avg_gain = (prev_avg_gain * (period - 1) + current_gain) / period;
avg_loss = (prev_avg_loss * (period - 1) + current_loss) / period;
rs = avg_gain / avg_loss;
dat[output_field] = 100.0 - (100.0 / (1.0 + rs));
if(isNaN(dat[output_field]))
{
dat[output_field] = 0.0;
}
}
prev_avg_gain = avg_gain;
prev_avg_loss = avg_loss;
prev_close = dat[output_field];
}
}
/**
* 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 = [];
var prev_arc = null;
for(var i = offset >= 0 ? offset : 0; i < offset + count && i < data_arr.length; i++)
{
var dat = data_arr[i];
var arc = new Arc(
this,
i,
dat[field_map.time],
dat[this.output],
prev_arc);
this.elements.push(arc);
arc.draw(context, valueToPixel, indexToPixel, this);
prev_arc = arc;
}
}
}
module.exports = RSILayer;