toptrader-vue-js
Version:
1,605 lines (1,451 loc) • 43.7 kB
JavaScript
// Script std-lib (built-in functions)
import se from "./script_engine.js";
import linreg from "../stuff/linreg.js";
import * as u from "./script_utils.js";
import Sampler from "./sampler.js";
import { Sym, ARR, TSS, NUM } from "./symbol.js";
const BUF_INC = 5;
export default class ScriptStd {
constructor(env) {
this.env = env;
this.se = se;
this.SWMA = [1 / 6, 2 / 6, 2 / 6, 1 / 6];
this.STDEV_EPS = 1e-10;
this.STDEV_Z = 1e-4;
this._index_tracking();
}
// Wrap every index with index-tracking function
// That way we will know exact index ranges
_index_tracking() {
let proto = Object.getPrototypeOf(this);
let std = ``;
for (var k of Object.getOwnPropertyNames(proto)) {
switch (k) {
case "constructor":
case "ts":
case "tstf":
case "sample":
case "_index_tracking":
case "_tsid":
case "_i":
case "_v":
case "_add_i":
case "chart":
case "onchart":
case "offchart":
case "sym":
continue;
}
let f = this._add_i(k, this[k].toString());
if (f) this[k] = f;
}
}
// Add index tracking to the function
_add_i(name, src) {
let args = u.f_args(src);
src = u.f_body(src);
let src2 = u.wrap_idxs(src, "this.");
if (src2 !== src) {
return new Function(...args, src2);
}
return null;
}
// Generate the next timeseries id
_tsid(prev, next) {
// TODO: prev presence check
return `${prev}<-${next}`;
}
// Index-tracker
_i(i, x) {
// If an object is actually a timeseries
if (x != undefined && x === x && x.__id__) {
// Increase TS buff length
if (!x.__len__ || i >= x.__len__) {
x.__len__ = i + BUF_INC;
}
}
return i;
}
// Index-tracker (object-based)
_v(x, i) {
// If an object is actually a timeseries
if (x != undefined && x === x && x.__id__) {
// Increase TS buff length
if (!x.__len__ || i >= x.__len__) {
x.__len__ = i + BUF_INC;
}
}
return x;
}
/**
* Creates a new time-series & records each x.
* Returns an array. Id is auto-genrated
* @param {*} x - A variable to sample from
* @return {TS} - New time-series
*/
ts(x, _id, _tf) {
if (_tf) return this.tstf(x, _tf, _id);
let ts = this.env.tss[_id];
if (!ts) {
ts = this.env.tss[_id] = [x];
ts.__id__ = _id;
} else {
ts[0] = x;
}
return ts;
}
/**
* Creates a new time-series & records each x.
* Uses Sampler to aggregate the values
* Return the an array. Id is auto-genrated
* @param {*} x - A variable to sample from
* @param {(number|string)} tf - Timeframe in ms or as a string
* @return {TS} - New time-series
*/
tstf(x, tf, _id) {
let ts = this.env.tss[_id];
if (!ts) {
ts = this.env.tss[_id] = [x];
ts.__id__ = _id;
ts.__tf__ = u.tf_from_str(tf);
ts.__fn__ = Sampler("close").bind(ts);
} else {
ts.__fn__(x);
}
return ts;
}
/**
* Creates a new custom sampler.
* Return the an array. Id is auto-genrated
* @param {*} x - A variable to sample from
* @param {string} type - Sampler type
* @param {(number|string)} tf - Timeframe in ms or as a string
* @return {TS} - New time-series
*/
sample(x, type, tf, _id) {
let ts = this.env.tss[_id];
if (!ts) {
ts = this.env.tss[_id] = [x];
ts.__id__ = _id;
ts.__tf__ = u.tf_from_str(tf);
ts.__fn__ = Sampler(type).bind(ts);
} else {
ts.__fn__(x);
}
return ts;
}
/**
* Replaces the variable if it's NaN
* @param {*} x - The variable
* @param {*} [v] - A value to replace with
* @return {*} - New value
*/
nz(x, v) {
if (x == undefined || x !== x) {
return v || 0;
}
return x;
}
/**
* Is the variable NaN ?
* @param {*} x - The variable
* @return {boolean} - New value
*/
na(x) {
return x == undefined || x !== x;
}
/** Replaces the var with NaN if Infinite
* @param {*} x - The variable
* @param {*} [v] - A value to replace with
* @return {*} - New value
*/
nf(x, v) {
if (!isFinite(x)) {
return v !== undefined ? v : NaN;
}
return x;
}
// Math operators on t-series and numbers
/** Adds values / time-series
* @param {(TS|*)} x - First input
* @param {(TS|*)} y - Second input
* @return {TS} - New time-series
*/
add(x, y, _id) {
// __id__ means this is a time-series
let id = this._tsid(_id, `add`);
let x0 = this.na(x) ? NaN : x.__id__ ? x[0] : x;
let y0 = this.na(y) ? NaN : y.__id__ ? y[0] : y;
return this.ts(x0 + y0, id, x.__tf__);
}
/** Subtracts values / time-series
* @param {(TS|*)} x - First input
* @param {(TS|*)} y - Second input
* @return {TS} - New time-series
*/
sub(x, y, _id) {
let id = this._tsid(_id, `sub`);
let x0 = this.na(x) ? NaN : x.__id__ ? x[0] : x;
let y0 = this.na(y) ? NaN : y.__id__ ? y[0] : y;
return this.ts(x0 - y0, id, x.__tf__);
}
/** Multiplies values / time-series
* @param {(TS|*)} x - First input
* @param {(TS|*)} y - Second input
* @return {TS} - New time-series
*/
mult(x, y, _id) {
let id = this._tsid(_id, `mult`);
let x0 = this.na(x) ? NaN : x.__id__ ? x[0] : x;
let y0 = this.na(y) ? NaN : y.__id__ ? y[0] : y;
return this.ts(x0 * y0, id, x.__tf__);
}
/** Divides values / time-series
* @param {(TS|*)} x - First input
* @param {(TS|*)} y - Second input
* @return {TS} - New time-series
*/
div(x, y, _id) {
let id = this._tsid(_id, `div`);
let x0 = this.na(x) ? NaN : x.__id__ ? x[0] : x;
let y0 = this.na(y) ? NaN : y.__id__ ? y[0] : y;
return this.ts(x0 / y0, id, x.__tf__);
}
/** Returns a negative value / time-series
* @param {(TS|*)} x - Input
* @return {TS} - New time-series
*/
neg(x, _id) {
let id = this._tsid(_id, `neg`);
let x0 = this.na(x) ? NaN : x.__id__ ? x[0] : x;
return this.ts(-x0, id, x.__tf__);
}
/** Absolute value
* @param {number} x - Input
* @return {number} - Absolute value
*/
abs(x) {
return Math.abs(x);
}
/** Arccosine function
* @param {number} x - Input
* @return {number} - Arccosine of x
*/
acos(x) {
return Math.acos(x);
}
/** Emits an event to DataCube
* @param {string} type - Signal type
* @param {*} data - Signal data
*/
signal(type, data = {}) {
if (this.se.shared.event !== "update") return;
this.se.send("script-signal", { type, data });
}
/** Emits an event if cond === true
* @param {(boolean|TS)} cond - The condition
* @param {string} type - Signal type
* @param {*} data - Signal data
*/
signalif(cond, type, data = {}) {
if (this.se.shared.event !== "update") return;
if (cond && cond.__id__) cond = cond[0];
if (cond) {
this.se.send("script-signal", { type, data });
}
}
/** Arnaud Legoux Moving Average
* @param {TS} src - Input
* @param {number} len - Length
* @param {number} offset - Offset
* @param {number} sigma - Sigma
* @return {TS} - New time-series
*/
alma(src, len, offset, sigma, _id) {
let id = this._tsid(_id, `alma(${len},${offset},${sigma})`);
let m = Math.floor(offset * (len - 1));
let s = len / sigma;
let norm = 0;
let sum = 0;
for (var i = 0; i < len; i++) {
let w = Math.exp((-1 * Math.pow(i - m, 2)) / (2 * Math.pow(s, 2)));
norm = norm + w;
sum = sum + src[len - i - 1] * w;
}
return this.ts(sum / norm, id, src.__tf__);
}
/** Arcsine function
* @param {number} x - Input
* @return {number} - Arcsine of x
*/
asin(x) {
return Math.asin(x);
}
/** Arctangent function
* @param {number} x - Input
* @return {number} - Arctangent of x
*/
atan(x) {
return Math.atan(x);
}
/** Average True Range
* @param {number} len - Length
* @return {TS} - New time-series
*/
atr(len, _id, _tf) {
let tfs = _tf || "";
let id = this._tsid(_id, `atr(${len})`);
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let close = this.env.shared[`close${tfs}`];
let tr = this.ts(0, id, _tf);
tr[0] = this.na(high[1])
? high[0] - low[0]
: Math.max(
Math.max(high[0] - low[0], Math.abs(high[0] - close[1])),
Math.abs(low[0] - close[1])
);
return this.rma(tr, len, id);
}
/** Average of arguments
* @param {...number} args - Numeric values
* @return {number}
*/
avg(...args) {
args.pop(); // Remove _id
let sum = 0;
for (var i = 0; i < args.length; i++) {
sum += args[i];
}
return sum / args.length;
}
/** Candles since the event occured (cond === true)
* @param {(boolean|TS)} cond - the condition
*/
since(cond, _id) {
let id = this._tsid(_id, `since()`);
if (cond && cond.__id__) cond = cond[0];
let s = this.ts(undefined, id);
s[0] = cond ? 0 : s[1] + 1;
return s;
}
/** Bollinger Bands
* @param {TS} src - Input
* @param {number} len - Length
* @param {number} mult - Multiplier
* @return {TS[]} - Array of new time-series (3 bands)
*/
bb(src, len, mult, _id) {
let id = this._tsid(_id, `bb(${len},${mult})`);
let basis = this.sma(src, len, id);
let dev = this.stdev(src, len, id)[0] * mult;
return [
basis,
this.ts(basis[0] + dev, id + "1", src.__tf__),
this.ts(basis[0] - dev, id + "2", src.__tf__)
];
}
/** Bollinger Bands Width
* @param {TS} src - Input
* @param {number} len - Length
* @param {number} mult - Multiplier
* @return {TS} - New time-series
*/
bbw(src, len, mult, _id) {
let id = this._tsid(_id, `bbw(${len},${mult})`);
let basis = this.sma(src, len, id)[0];
let dev = this.stdev(src, len, id)[0] * mult;
return this.ts((2 * dev) / basis, id, src.__tf__);
}
/** Converts the variable to Boolean
* @param {number} x The variable
* @return {number}
*/
bool(x) {
return !!x;
}
/** Commodity Channel Index
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
cci(src, len, _id) {
// TODO: Not exactly precise, but pretty damn close
let id = this._tsid(_id, `cci(${len})`);
let ma = this.sma(src, len, id);
let dev = this.dev(src, len, id);
let cci = (src[0] - ma[0]) / (0.015 * dev[0]);
return this.ts(cci, id, src.__tf__);
}
/** Shortcut for Math.ceil()
* @param {number} x The variable
* @return {number}
*/
ceil(x) {
return Math.ceil(x);
}
/** Change: x[0] - x[len]
* @param {TS} src - Input
* @param {number} [len] - Length
* @return {TS} - New time-series
*/
change(src, len = 1, _id) {
let id = this._tsid(_id, `change(${len})`);
return this.ts(src[0] - src[len], id, src.__tf__);
}
/** Chande Momentum Oscillator
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
cmo(src, len, _id) {
let id = this._tsid(_id, `cmo(${len})`);
let mom = this.change(src, 1, id);
let g = this.ts(mom[0] >= 0 ? mom[0] : 0.0, id + "g", src.__tf__);
let l = this.ts(mom[0] >= 0 ? 0.0 : -mom[0], id + "l", src.__tf__);
let sm1 = this.sum(g, len, id + "1")[0];
let sm2 = this.sum(l, len, id + "2")[0];
return this.ts((100 * (sm1 - sm2)) / (sm1 + sm2), id, src.__tf__);
}
/** Center of Gravity
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
cog(src, len, _id) {
let id = this._tsid(_id, `cmo(${len})`);
let sum = this.sum(src, len, id)[0];
let num = 0;
for (var i = 0; i < len; i++) {
num += src[i] * (i + 1);
}
return this.ts(-num / sum, id, src.__tf__);
}
// Correlation
corr() {
// TODO: this
}
/** Cosine function
* @param {number} x - Input
* @return {number} - Cosine of x
*/
cos(x) {
return Math.cos(x);
}
/** When one time-series crosses another
* @param {TS} src1 - TS1
* @param {TS} src2 - TS2
* @return {TS} - New time-series
*/
cross(src1, src2, _id) {
let id = this._tsid(_id, `cross`);
let x = src1[0] > src2[0] !== src1[1] > src2[1];
return this.ts(x, id, src1.__tf__);
}
/** When one time-series goes over another one
* @param {TS} src1 - TS1
* @param {TS} src2 - TS2
* @return {TS} - New time-series
*/
crossover(src1, src2, _id) {
let id = this._tsid(_id, `crossover`);
let x = src1[0] > src2[0] && src1[1] <= src2[1];
return this.ts(x, id, src1.__tf__);
}
/** When one time-series goes under another one
* @param {TS} src1 - TS1
* @param {TS} src2 - TS2
* @return {TS} - New time-series
*/
crossunder(src1, src2, _id) {
let id = this._tsid(_id, `crossunder`);
let x = src1[0] < src2[0] && src1[1] >= src2[1];
return this.ts(x, id, src1.__tf__);
}
/** Sum of all elements of src
* @param {TS} src1 - Input
* @return {TS} - New time-series
*/
cum(src, _id) {
let id = this._tsid(_id, `cum`);
let res = this.ts(0, id, src.__tf__);
res[0] = this.nz(src[0]) + this.nz(res[1]);
return res;
}
/** Day of month, literally
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Day
*/
dayofmonth(time) {
return new Date(time || se.t).getUTCDate();
}
/** Day of week, literally
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Day
*/
dayofweek(time) {
return new Date(time || se.t).getUTCDay() + 1;
}
/** Deviation from SMA
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
dev(src, len, _id) {
let id = this._tsid(_id, `dev(${len})`);
let mean = this.sma(src, len, id)[0];
let sum = 0;
for (var i = 0; i < len; i++) {
sum += Math.abs(src[i] - mean);
}
return this.ts(sum / len, id, src.__tf__);
}
/** Directional Movement Index ADX, +DI, -DI
* @param {number} len - Length
* @param {number} smooth - Smoothness
* @return {TS} - New time-series
*/
dmi(len, smooth, _id, _tf) {
let id = this._tsid(_id, `dmi(${len},${smooth})`);
let tfs = _tf || "";
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let up = this.change(high, 1, id + "1")[0];
let down = this.neg(this.change(low, 1, id + "2"), id)[0];
let plusDM = this.ts(
100 * (this.na(up) ? NaN : up > down && up > 0 ? up : 0),
id + "3",
_tf
);
let minusDM = this.ts(
100 * (this.na(down) ? NaN : down > up && down > 0 ? down : 0),
id + "4",
_tf
);
let trur = this.rma(this.tr(false, id, _tf), len, id + "5");
let plus = this.div(this.rma(plusDM, len, id + "6"), trur, id + "8");
let minus = this.div(this.rma(minusDM, len, id + "7"), trur, id + "9");
let sum = this.add(plus, minus, id + "10")[0];
let adx = this.rma(
this.ts(
(100 * Math.abs(plus[0] - minus[0])) / (sum === 0 ? 1 : sum),
id + "11",
_tf
),
smooth,
id + "12"
);
return [adx, plus, minus];
}
/** Exponential Moving Average with alpha = 2 / (y + 1)
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
ema(src, len, _id) {
let id = this._tsid(_id, `ema(${len})`);
let a = 2 / (len + 1);
let ema = this.ts(0, id, src.__tf__);
ema[0] = this.na(ema[1])
? this.sma(src, len, id)[0]
: a * src[0] + (1 - a) * this.nz(ema[1]);
return ema;
}
/** Shortcut for Math.exp()
* @param {number} x The variable
* @return {number}
*/
exp(x) {
return Math.exp(x);
}
/** Test if "src" TS is falling for "len" candles
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
falling(src, len, _id) {
let id = this._tsid(_id, `falling(${len})`);
let bot = src[0];
for (var i = 1; i < len + 1; i++) {
if (bot >= src[i]) {
return this.ts(false, id, src.__tf__);
}
}
return this.ts(true, id, src.__tf__);
}
/** For a given series replaces NaN values with
* previous nearest non-NaN value
* @param {TS} src - Input time-series
* @return {TS}
*/
fixnan(src) {
if (this.na(src[0])) {
for (var i = 1; i < src.length; i++) {
if (!this.na(src[i])) {
src[0] = src[i];
break;
}
}
}
return src;
}
/* TODO: think
skipnan(x, _id) {
let id = this._tsid(_id, `skipnan()`)
return this.ts(true, id, src.__tf__)
}*/
/** Shortcut for Math.floor()
* @param {number} x The variable
* @return {number}
*/
floor(x) {
Math.floor(x);
}
/** Highest value for a given number of candles back
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
highest(src, len, _id) {
let id = this._tsid(_id, `highest(${len})`);
let high = -Infinity;
for (var i = 0; i < len; i++) {
if (src[i] > high) high = src[i];
}
return this.ts(high, id, src.__tf__);
}
/** Highest value offset for a given number of bars back
* @param {TS} src - Input
* @param {number} len - Length
*/
highestbars(src, len, _id) {
let id = this._tsid(_id, `highestbars(${len})`);
let high = -Infinity;
let hi = 0;
for (var i = 0; i < len; i++) {
if (src[i] > high) {
(high = src[i]), (hi = i);
}
}
return this.ts(-hi, id, src.__tf__);
}
/** Hull Moving Average
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
hma(src, len, _id) {
let id = this._tsid(_id, `hma(${len})`);
let len2 = Math.floor(len / 2);
let len3 = Math.round(Math.sqrt(len));
let a = this.mult(this.wma(src, len2, id + "1"), 2, id);
let b = this.wma(src, len, id + "2");
let delt = this.sub(a, b, id + "3");
return this.wma(delt, len3, id + "4");
}
/** Returns hours of a given timestamp
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Hour
*/
hour(time) {
return new Date(time || se.t).getUTCHours();
}
/** Returns x or y depending on the condition
* @param {(boolean|TS)} cond - Condition
* @param {*} x - Frist value
* @param {*} y - Second value
* @return {*}
*/
iff(cond, x, y) {
if (cond && cond.__id__) cond = cond[0];
return cond ? x : y;
}
/** Keltner Channels
* @param {TS} src - Input
* @param {number} len - Length
* @param {number} mult - Multiplier
* @param {boolean} [use_tr] - Use true range
* @return {TS[]} - Array of new time-series (3 bands)
*/
kc(src, len, mult, use_tr = true, _id, _tf) {
let id = this._tsid(_id, `kc(${len},${mult},${use_tr})`);
let tfs = _tf || "";
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let basis = this.ema(src, len, id + "1");
let range = use_tr
? this.tr(false, id + "2", _tf)
: this.ts(high[0] - low[0], id + "3", src.__tf__);
let ema = this.ema(range, len, id + "4");
return [
basis,
this.ts(basis[0] + ema[0] * mult, id + "5", src.__tf__),
this.ts(basis[0] - ema[0] * mult, id + "6", src.__tf__)
];
}
/** Keltner Channels Width
* @param {TS} src - Input
* @param {number} len - Length
* @param {number} mult - Multiplier
* @param {boolean} [use_tr] - Use true range
* @return {TS} - New time-series
*/
kcw(src, len, mult, use_tr = true, _id, _tf) {
let id = this._tsid(_id, `kcw(${len},${mult},${use_tr})`);
let kc = this.kc(src, len, mult, use_tr, `kcw`, _tf);
return this.ts((kc[1][0] - kc[2][0]) / kc[0][0], id, src.__tf__);
}
/** Linear Regression
* @param {TS} src - Input
* @param {number} len - Length
* @param {number} offset - Offset
* @return {TS} - New time-series
*/
linreg(src, len, offset = 0, _id) {
let id = this._tsid(_id, `linreg(${len})`);
src.__len__ = Math.max(src.__len__ || 0, len);
let lr = linreg(src, len, offset);
return this.ts(lr, id, src.__tf__);
}
/** Shortcut for Math.log()
* @param {number} x The variable
* @return {number}
*/
log(x) {
return Math.log(x);
}
/** Shortcut for Math.log10()
* @param {number} x The variable
* @return {number}
*/
log10(x) {
return Math.log10(x);
}
/** Lowest value for a given number of candles back
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
lowest(src, len, _id) {
let id = this._tsid(_id, `lowest(${len})`);
let low = Infinity;
for (var i = 0; i < len; i++) {
if (src[i] < low) low = src[i];
}
return this.ts(low, id, src.__tf__);
}
/** Lowest value offset for a given number of bars back
* @param {TS} src - Input
* @param {number} len - Length
*/
lowestbars(src, len, _id) {
let id = this._tsid(_id, `lowestbars(${len})`);
let low = Infinity;
let li = 0;
for (var i = 0; i < len; i++) {
if (src[i] < low) {
(low = src[i]), (li = i);
}
}
return this.ts(-li, id, src.__tf__);
}
/** Moving Average Convergence/Divergence
* @param {TS} src - Input
* @param {number} fast - Fast EMA
* @param {number} slow - Slow EMA
* @param {number} sig - Signal
* @return {TS[]} - [macd, signal, hist]
*/
macd(src, fast, slow, sig, _id) {
let id = this._tsid(_id, `macd(${fast}${slow}${sig})`);
let fast_ma = this.ema(src, fast, id + "1");
let slow_ma = this.ema(src, slow, id + "2");
let macd = this.sub(fast_ma, slow_ma, id + "3");
let signal = this.ema(macd, sig, id + "4");
let hist = this.sub(macd, signal, id + "5");
return [macd, signal, hist];
}
/** Max of arguments
* @param {...number} args - Numeric values
* @return {number}
*/
max(...args) {
args.pop(); // Remove _id
return Math.max(...args);
}
/** Sends update to some overlay / main chart
* @param {string} id - Overlay id
* @param {Object} fields - Fields to be overwritten
*/
modify(id, fields) {
se.send("modify-overlay", { uuid: id, fields });
}
/** Sets the reverse buffer size for a given
* time-series (default = 5, grows on demand)
* @param {TS} src - Input
* @param {number} len - New length
*/
buffsize(src, len) {
src.__len__ = len;
}
/** Money Flow Index
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
mfi(src, len, _id) {
let id = this._tsid(_id, `mfi(${len})`);
let vol = this.env.shared.vol;
let ch = this.change(src, 1, id + "1")[0];
let ts1 = this.mult(vol, ch <= 0.0 ? 0.0 : src[0], id + "2");
let ts2 = this.mult(vol, ch >= 0.0 ? 0.0 : src[0], id + "3");
let upper = this.sum(ts1, len, id + "4");
let lower = this.sum(ts2, len, id + "5");
let res = undefined;
if (!this.na(lower)) {
res = this.rsi(upper, lower, id + "6")[0];
}
return this.ts(res, id, src.__tf__);
}
/** Min of arguments
* @param {...number} args - Numeric values
* @return {number}
*/
min(...args) {
args.pop(); // Remove _id
return Math.min(...args);
}
/** Returns minutes of a given timestamp
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Hour
*/
minute(time) {
return new Date(time || se.t).getUTCMinutes();
}
/** Momentum
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
mom(src, len, _id) {
let id = this._tsid(_id, `mom(${len})`);
return this.ts(src[0] - src[len], id, src.__tf__);
}
/** Month
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Day
*/
month(time) {
return new Date(time || se.t).getUTCMonth();
}
// Display data point as the main chart
chart() {
// TODO: this
}
// TODO: optionally enable scripts for $synth ovs
// TODO: add indexBased option
/** Display data point onchart
* (create a new overlay in DataCube)
* @param {(TS|TS[]|*)} x - Data point / TS / array of TS
* @param {string} [name] - Overlay name
* @param {Object} [sett] - Object with settings & OV type
*/
onchart(x, name, sett = {}, _id) {
let off = 0;
name = name || u.get_fn_id("Onchart", _id);
if (x && x.__id__) {
off = x.__offset__ || 0;
x = x[0];
}
if (Array.isArray(x) && x[0] && x[0].__id__) {
off = x[0].__offset__ || 0;
x = x.map(x => x[0]);
}
if (!this.env.onchart[name]) {
let type = sett.type;
delete sett.type;
sett.$synth = true;
sett.skipNaN = true;
let post = Array.isArray(x) ? "s" : "";
this.env.onchart[name] = {
name: name,
type: type || "Spline" + post,
data: [],
settings: sett,
scripts: false,
grid: sett.grid || {}
};
}
off *= se.tf;
let v = Array.isArray(x) ? [se.t + off, ...x] : [se.t + off, x];
u.update(this.env.onchart[name].data, v);
}
/** Display data point offchart
* (create a new overlay in DataCube)
* @param {(TS|TS[]|*)} x - Data point / TS / array of TS
* @param {string} [name] - Overlay name
* @param {Object} [sett] - Object with settings & OV type
*/
offchart(x, name, sett = {}, _id) {
name = name || u.get_fn_id("Offchart", _id);
let off = 0;
if (x && x.__id__) {
off = x.__offset__ || 0;
x = x[0];
}
if (Array.isArray(x) && x[0] && x[0].__id__) {
off = x[0].__offset__ || 0;
x = x.map(x => x[0]);
}
if (!this.env.offchart[name]) {
let type = sett.type;
delete sett.type;
sett.$synth = true;
sett.skipNaN = true;
let post = Array.isArray(x) ? "s" : "";
this.env.offchart[name] = {
name: name,
type: type || "Spline" + post,
data: [],
settings: sett,
scripts: false,
grid: sett.grid || {}
};
}
off *= se.tf;
let v = Array.isArray(x) ? [se.t + off, ...x] : [se.t + off, x];
u.update(this.env.offchart[name].data, v);
}
/** Returns true when the candle(<tf>) is being closed
* (create a new overlay in DataCube)
* @param {(number|string)} tf - Timeframe in ms or as a string
* @return {boolean}
*/
onclose(tf) {
if (!this.env.shared.onclose) return false;
if (!tf) tf = se.tf;
return (se.t + se.tf) % u.tf_from_str(tf) === 0;
}
/** Sends settings update
* (can be called from init(), update() or post())
* @param {Object} upd - Settings update (object to merge)
*/
settings(upd) {
this.env.send_modify({ settings: upd });
Object.assign(this.env.src.sett, upd);
}
/** Shifts TS left or right by "num" candles
* @param {number} num - Offset measured in candles
* @return {TS} - New / existing time-series
*/
offset(src, num, _id) {
if (src.__id__) {
src.__offset__ = num;
return src;
}
let id = this._tsid(_id, `offset(${num})`);
let out = ts(src, id);
out.__offset__ = num;
return out;
}
// percentile_linear_interpolation
linearint() {
// TODO: this
}
// percentile_nearest_rank
nearestrank() {
// TODO: this
}
/** The current time
* @return {number} - timestamp
*/
now() {
return new Date().getTime();
}
percentrank() {
// TODO: this
}
/** Returns price of the pivot high point
* Tip: works best with `offset` function
* @param {TS} src - Input
* @param {number} left - left threshold, candles
* @param {number} right - right threshold, candles
* @return {TS} - New time-series
*/
pivothigh(src, left, right, _id) {
let id = this._tsid(_id, `pivothigh(${left},${right})`);
let len = left + right + 1;
let top = src[right];
for (var i = 0; i < len; i++) {
if (top <= src[i] && i !== right) {
return this.ts(NaN, id, src.__tf__);
}
}
return this.ts(top, id, src.__tf__);
}
/** Returns price of the pivot low point
* Tip: works best with `offset` function
* @param {TS} src - Input
* @param {number} left - left threshold, candles
* @param {number} right - right threshold, candles
* @return {TS} - New time-series
*/
pivotlow(src, left, right, _id) {
let id = this._tsid(_id, `pivotlow(${left},${right})`);
let len = left + right + 1;
let bot = src[right];
for (var i = 0; i < len; i++) {
if (bot >= src[i] && i !== right) {
return this.ts(NaN, id, src.__tf__);
}
}
return this.ts(bot, id, src.__tf__);
}
/** Shortcut for Math.pow()
* @param {number} x The variable
* @return {number}
*/
pow(x) {
return Math.pow(x);
}
/** Test if "src" TS is rising for "len" candles
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
rising(src, len, _id) {
let id = this._tsid(_id, `rising(${len})`);
let top = src[0];
for (var i = 1; i < len + 1; i++) {
if (top <= src[i]) {
return this.ts(false, id, src.__tf__);
}
}
return this.ts(true, id, src.__tf__);
}
/** Exponentially MA with alpha = 1 / length
* Used in RSI
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
rma(src, len, _id) {
let id = this._tsid(_id, `rma(${len})`);
let a = len;
let sum = this.ts(0, id, src.__tf__);
sum[0] = this.na(sum[1])
? this.sma(src, len, id)[0]
: (src[0] + (a - 1) * this.nz(sum[1])) / a;
return sum;
}
/** Rate of Change
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
roc(src, len, _id) {
let id = this._tsid(_id, `roc(${len})`);
return this.ts((100 * (src[0] - src[len])) / src[len], id, src.__tf__);
}
/** Shortcut for Math.round()
* @param {number} x The variable
* @return {number}
*/
round(x) {
return Math.round(x);
}
/** Relative Strength Index
* @param {TS} x - First Input
* @param {number|TS} y - Second Input
* @return {TS} - New time-series
*/
rsi(x, y, _id) {
// Check if y is a timeseries
if (!this.na(y) && y.__id__) {
var id = this._tsid(_id, `rsi(x,y)`);
var rsi = 100 - 100 / (1 + this.div(x, y, id)[0]);
} else {
var id = this._tsid(_id, `rsi(${y})`);
let ch = this.change(x, 1, _id)[0];
let pc = this.ts(Math.max(ch, 0), id + "1", x.__tf__);
let nc = this.ts(-Math.min(ch, 0), id + "2", x.__tf__);
let up = this.rma(pc, y, id + "3")[0];
let down = this.rma(nc, y, id + "4")[0];
var rsi = down === 0 ? 100 : up === 0 ? 0 : 100 - 100 / (1 + up / down);
}
return this.ts(rsi, id + "5", x.__tf__);
}
/** Parabolic SAR
* @param {number} start - Start
* @param {number} inc - Increment
* @param {number} max - Maximum
* @return {TS} - New time-series
*/
sar(start, inc, max, _id, _tf) {
// Source: Parabolic SAR by imuradyan
// TODO: simplify the code
// TODO: fix the custom TF mode
let id = this._tsid(_id, `sar(${start},${inc},${max})`);
let tfs = _tf || "";
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let close = this.env.shared[`close${tfs}`];
let minTick = 0; //1e-7
let out = this.ts(undefined, id + "1", _tf);
let pos = this.ts(undefined, id + "2", _tf);
let maxMin = this.ts(undefined, id + "3", _tf);
let acc = this.ts(undefined, id + "4", _tf);
let n = _tf ? out.__len__ - 1 : se.iter;
let prev;
let outSet = false;
if (n >= 1) {
prev = out[1];
if (n === 1) {
if (close[0] > close[1]) {
pos[0] = 1;
maxMin[0] = Math.max(high[0], high[1]);
prev = Math.min(low[0], low[1]);
} else {
pos[0] = -1;
maxMin[0] = Math.min(low[0], low[1]);
prev = Math.max(high[0], high[1]);
}
acc[0] = start;
} else {
pos[0] = pos[1];
acc[0] = acc[1];
maxMin[0] = maxMin[1];
}
if (pos[0] === 1) {
if (high[0] > maxMin[0]) {
maxMin[0] = high[0];
acc[0] = Math.min(acc[0] + inc, max);
}
if (low[0] <= prev) {
pos[0] = -1;
out[0] = maxMin[0];
maxMin[0] = low[0];
acc[0] = start;
outSet = true;
}
} else {
if (low[0] < maxMin[0]) {
maxMin[0] = low[0];
acc[0] = Math.min(acc[0] + inc, max);
}
if (high[0] >= prev) {
pos[0] = 1;
out[0] = maxMin[0];
maxMin[0] = high[0];
acc[0] = start;
outSet = true;
}
}
if (!outSet) {
out[0] = prev + acc[0] * (maxMin[0] - prev);
if (pos[0] === 1) if (out[0] >= low[0]) out[0] = low[0] - minTick;
if (pos[0] === -1) if (out[0] <= high[0]) out[0] = high[0] + minTick;
}
}
return out;
}
/** Returns seconds of a given timestamp
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Hour
*/
second(time) {
return new Date(time || se.t).getUTCSeconds();
}
/** Shortcut for Math.sing()
* @param {number} x The variable
* @return {number}
*/
sign(x) {
return Math.sign(x);
}
/** Sine function
* @param {number} x The variable
* @return {number}
*/
sin(x) {
return Math.sin(x);
}
/** Simple Moving Average
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
sma(src, len, _id) {
let id = this._tsid(_id, `sma(${len})`);
let sum = 0;
for (var i = 0; i < len; i++) {
sum = sum + src[i];
}
return this.ts(sum / len, id, src.__tf__);
}
/** Shortcut for Math.sqrt()
* @param {number} x The variable
* @return {number}
*/
sqrt(x) {
return Math.sqrt(x);
}
/** Standard deviation
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
stdev(src, len, _id) {
let sumf = (x, y) => {
let res = x + y;
return res;
};
let id = this._tsid(_id, `stdev(${len})`);
let avg = this.sma(src, len, id);
let sqd = 0;
for (var i = 0; i < len; i++) {
let sum = sumf(src[i], -avg[0]);
sqd += sum * sum;
}
return this.ts(Math.sqrt(sqd / len), id, src.__tf__);
}
/** Stochastic
* @param {TS} src - Input
* @param {TS} high - TS of high
* @param {TS} low - TS of low
* @param {number} len - Length
* @return {TS} - New time-series
*/
stoch(src, high, low, len, _id) {
let id = this._tsid(_id, `sum(${len})`);
let x = 100 * (src[0] - this.lowest(low, len)[0]);
let y = this.highest(high, len)[0] - this.lowest(low, len)[0];
return this.ts(x / y, id, src.__tf__);
}
/** Returns the sliding sum of last "len" values of the source
* @param {TS} src - Input
* @param {number} len - Length
* @return {TS} - New time-series
*/
sum(src, len, _id) {
let id = this._tsid(_id, `sum(${len})`);
let sum = 0;
for (var i = 0; i < len; i++) {
sum = sum + src[i];
}
return this.ts(sum, id, src.__tf__);
}
/** Supertrend Indicator
* @param {number} factor - ATR multiplier
* @param {number} atrlen - Length of ATR
* @return {TS[]} - Supertrend line and direction of trend
*/
supertrend(factor, atrlen, _id, _tf) {
let id = this._tsid(_id, `supertrend(${factor},${atrlen})`);
let tfs = _tf || "";
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let close = this.env.shared[`close${tfs}`];
let hl2 = (high[0] + low[0]) * 0.5;
let atr = factor * this.atr(atrlen, id + "1", _tf)[0];
let ls = this.ts(hl2 - atr, id + "2", _tf);
let ls1 = this.nz(ls[1], ls[0]);
ls[0] = close[1] > ls1 ? Math.max(ls[0], ls1) : ls[0];
let ss = this.ts(hl2 + atr, id + "3", _tf);
let ss1 = this.nz(ss[1], ss);
ss[0] = close[1] < ss1 ? Math.min(ss[0], ss1) : ss[0];
let dir = this.ts(1, id + "4", _tf);
dir[0] = this.nz(dir[1], dir[0]);
dir[0] =
dir[0] === -1 && close[0] > ss1
? 1
: dir[0] === 1 && close[0] < ls1
? -1
: dir[0];
let plot = this.ts(dir[0] === 1 ? ls[0] : ss[0], id + "5", _tf);
return [plot, this.neg(dir, id + "6")];
}
/** Symmetrically Weighted Moving Average
* @param {TS} src - Input
* @return {TS} - New time-series
*/
swma(src, _id) {
let id = this._tsid(_id, `swma`);
let sum =
src[3] * this.SWMA[0] +
src[2] * this.SWMA[1] +
src[1] * this.SWMA[2] +
src[0] * this.SWMA[3];
return this.ts(sum, id, src.__tf__);
}
/** Creates a new Symbol.
* @param {*} x - Something, depends on arg variation
* @param {*} y - Something, depends on arg variation
* @return {Sym}
* Argument variations:
* <data>(Array), [<params>(Object)]
* <ts>(TS), [<params>(Object)]
* <point>(Number), [<params>(Object)]
* <tf>(String) 1m, 5m, 1H, etc. (uses main OHLCV)
* Params object: {
* id: <String>,
* tf: <String|Number>,
* aggtype: <String> (TODO: Type of aggregation)
* format: <String> (Data format, e.g. "time:price:vol")
* window: <String|Number> (Aggregation window)
* main <true|false> (Use as the main chart)
* }
*/
sym(x, y = {}, _id) {
let id = y.id || this._tsid(_id, `sym`);
y.id = id;
if (this.env.syms[id]) {
this.env.syms[id].update(x);
return this.env.syms[id];
}
switch (typeof x) {
case "object":
var sym = new Sym(x, y);
this.env.syms[id] = sym;
if (x.__id__) {
sym.data_type = TSS;
} else {
sym.data_type = ARR;
}
break;
case "number":
sym = new Sym(null, y);
sym.data_type = NUM;
break;
case "string":
y.tf = x;
sym = new Sym(se.data.ohlcv.data, y);
sym.data_type = ARR;
break;
}
this.env.syms[id] = sym;
return sym;
}
/** Tangent function
* @param {number} x The variable
* @return {number}
*/
tan(x) {
return Math.tan(x);
}
time(res, sesh) {
// TODO: this
}
timestamp() {
// TODO: this
}
/** True Range
* @param {TS} fixnan - Fix NaN values
* @return {TS} - New time-series
*/
tr(fixnan = false, _id, _tf) {
let id = this._tsid(_id, `tr(${fixnan})`);
let tfs = _tf || "";
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let close = this.env.shared[`close${tfs}`];
let res = 0;
if (this.na(close[1]) && fixnan) {
res = high[0] - low[0];
} else {
res = Math.max(
high[0] - low[0],
Math.abs(high[0] - close[1]),
Math.abs(low[0] - close[1])
);
}
return this.ts(res, id, _tf);
}
/** True strength index
* @param {TS} src - Input
* @param {number} short - Short length
* @param {number} long - Long length
* @return {TS} - New time-series
*/
tsi(src, short, long, _id) {
let id = this._tsid(_id, `tsi(${short},${long})`);
let m = this.change(src, 1, id + "0");
let m_abs = this.ts(Math.abs(m[0]), id + "1", src.__tf__);
let tsi =
this.ema(this.ema(m, long, id + "1"), short, id + "2")[0] /
this.ema(this.ema(m_abs, long, id + "3"), short, id + "4")[0];
return this.ts(tsi, id, src.__tf__);
}
variance(src, len) {
// TODO: this
}
vwap(src) {
// TODO: this
}
/** Volume Weighted Moving Average
* @param {TS} src - Input
* @param {number} len - length
* @return {TS} - New time-series
*/
vwma(src, len, _id) {
let id = this._tsid(_id, `vwma(${len})`);
let vol = this.env.shared.vol;
let sxv = this.ts(src[0] * vol[0], id + "1", src.__tf__);
let res = this.sma(sxv, len, id + "2")[0] / this.sma(vol, len, id + "3")[0];
return this.ts(res, id + "4", src.__tf__);
}
/** Week of year, literally
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Week
*/
weekofyear(time) {
let date = new Date(time || se.t);
date.setUTCHours(0, 0, 0, 0);
date.setDate(date.getUTCDate() + 3 - ((date.getUTCDay() + 6) % 7));
let week1 = new Date(date.getUTCFullYear(), 0, 4);
return (
1 +
Math.round(
((date - week1) / 86400000 - 3 + ((week1.getUTCDay() + 6) % 7)) / 7
)
);
}
/** Weighted moving average
* @param {TS} src - Input
* @param {number} len - length
* @return {TS} - New time-series
*/
wma(src, len, _id) {
let id = this._tsid(_id, `wma(${len})`);
let norm = 0;
let sum = 0;
for (var i = 0; i < len; i++) {
let w = (len - i) * len;
norm += w;
sum += src[i] * w;
}
return this.ts(sum / norm, id, src.__tf__);
}
/** Williams %R
* @param {number} len - length
* @return {TS} - New time-series
*/
wpr(len, _id, _tf) {
let id = this._tsid(_id, `wpr(${len})`);
let tfs = _tf || "";
let high = this.env.shared[`high${tfs}`];
let low = this.env.shared[`low${tfs}`];
let close = this.env.shared[`close${tfs}`];
let hh = this.highest(high, len, id);
let ll = this.lowest(low, len, id);
let res = (hh[0] - close[0]) / (hh[0] - ll[0]);
return this.ts(-res * 100, id, _tf);
}
/** Year
* @param {number} [time] - Time in ms (current t, if not defined)
* @return {number} - Year
*/
year(time) {
return new Date(time || se.t).getUTCFullYear();
}
}