trading-vue3-js
Version:
Customizable charting lib for traders. Based on https://github.com/C451/trading-vue-js by C451.
245 lines (198 loc) • 6.61 kB
JavaScript
// Time-index mapping (for non-linear t-axis)
import Utils from '../../stuff/utils.js'
const MAX_ARR = Math.pow(2, 32)
// 3 MODES of index calculation for overlays/subcharts:
// ::: indexSrc :::
// * "map" -> use TI mapping functions to detect index
// (slowest, for stocks only. DEFAULT)
//
// * "calc" -> calculate shift between sub & data
// (faster, but overlay data should be perfectly
// align with the main chart,
// 1-1 candle/data point. Supports Renko)
//
// * "data" -> overlay data should come with candle index
// (fastest, supports Renko)
export default class TI {
constructor() {
this.ib = false
}
init(params, res) {
let {
sub, interval, meta, $props:$p, interval_ms, sub_start, ib
} = params
this.ti_map = []
this.it_map = []
this.sub_i = []
this.ib = ib
this.sub = res
this.ss = sub_start
this.tf = interval_ms
let start = meta.sub_start
// Skip mapping for the regular mode
if (this.ib) {
this.map_sub(res)
}
}
// Make maps for the main subset
map_sub(res) {
for (var i = 0; i < res.length; i++) {
let t = res[i][0]
let _i = (this.ss + i)
this.ti_map[t] = _i
this.it_map[_i] = t
// Overwrite t with i
let copy = [...res[i]]
copy[0] = _i
this.sub_i.push(copy)
}
}
// Map overlay data
// TODO: parse() called 3 times instead of 2 for 'spx_sample.json'
parse(data, mode) {
if (!this.ib || !this.sub[0] || mode === 'data') return data
let res = []
let k = 0 // Candlestick index
if (mode === 'calc') {
let shift = Utils.index_shift(this.sub, data)
for (var i = 0; i < data.length; i++) {
let _i = (this.ss + i)
let copy = [...data[i]]
copy[0] = _i + shift
res.push(copy)
}
return res
}
// If indicator data starts after ohlcv, calc the first index
if (data.length) {
try {
let k1 = Utils.fast_nearest(this.sub, data[0][0])[0]
if (k1 !== null && k1 >= 0) k = k1
} catch(e) { }
}
let t0 = this.sub[0][0]
let tN = this.sub[this.sub.length - 1][0]
for (var i = 0; i < data.length; i++) {
let copy = [...data[i]]
let tk = this.sub[k][0]
let t = data[i][0]
let index = this.ti_map[t]
if (index === undefined) {
// Linear extrapolation
if (t < t0 || t > tN) {
index = this.ss + k - (tk - t) / this.tf
t = data[i+1] ? data[i+1][0] : undefined
}
// Linear interpolation
else {
let tk2 = this.sub[k + 1][0]
index = tk === tk2 ? this.ss + k :
this.ss + k + (t - tk) / (tk2 - tk)
t = data[i+1] ? data[i+1][0] : undefined
}
}
// Race of data points & sub points (ohlcv)
// (like turn based increments)
while (k+1 < this.sub.length - 1 && t > this.sub[k+1][0]) {
k++
tk = this.sub[k][0]
}
copy[0] = index
res.push(copy)
}
return res
}
// index => time
i2t(i) {
if (!this.ib || !this.sub.length) return i // Regular mode
// Discrete mapping
let res = this.it_map[i]
if (res !== undefined) return res
// Linear extrapolation
else if (i >= this.ss + this.sub_i.length) {
let di = i - (this.ss + this.sub_i.length) + 1
let last = this.sub[this.sub.length - 1]
return last[0] + di * this.tf
}
else if (i < this.ss) {
let di = i - this.ss
return this.sub[0][0] + di * this.tf
}
// Linear Interpolation
let i1 = Math.floor(i) - this.ss
let i2 = i1 + 1
let len = this.sub.length
if (i2 >= len) i2 = len - 1
let sub1 = this.sub[i1]
let sub2 = this.sub[i2]
if (sub1 && sub2) {
let t1 = sub1[0]
let t2 = sub2[0]
return t1 + (t2 - t1) * (i - i1 - this.ss)
}
return undefined
}
// Map or bypass depending on the mode
i2t_mode(i, mode) {
return mode === 'data' ? i : this.i2t(i)
}
// time => index
// TODO: when switch from IB mode to regular tools
// disappear (bc there is no more mapping)
t2i(t) {
if (!this.sub.length) return undefined
// Discrete mapping
let res = this.ti_map[t]
if (res !== undefined) return res
let t0 = this.sub[0][0]
let tN = this.sub[this.sub.length - 1][0]
// Linear extrapolation
if (t < t0) {
return this.ss - (t0 - t) / this.tf
}
else if (t > tN) {
let k = this.sub.length - 1
return this.ss + k - (tN - t) / this.tf
}
try {
// Linear Interpolation
let i = Utils.fast_nearest(this.sub, t)
let tk = this.sub[i[0]][0]
let tk2 = this.sub[i[1]][0]
let k = (t - tk) / (tk2 - tk)
return this.ss + i[0] + k * (i[1] - i[0])
} catch(e) { }
return undefined
}
// Auto detect: is it time or index?
// Assuming that index-based mode is ON
smth2i(smth) {
if (smth > MAX_ARR) {
return this.t2i(smth) // it was time
} else {
return smth // it was an index
}
}
smth2t(smth) {
if (smth < MAX_ARR) {
return this.i2t(smth) // it was an index
} else {
return smth // it was time
}
}
// Global Time => Index (uses all data, approx. method)
// Used by tv.goto()
gt2i(smth, ohlcv) {
if (smth > MAX_ARR) {
let E = 0.1 // Fixes the arrayslicer bug
let [i1, i2] = Utils.fast_nearest(ohlcv, smth+E)
if (typeof i1 === 'number') {
return i1
} else {
return this.t2i(smth) // fallback
}
} else {
return smth // it was an index
}
}
}