klinecharts
Version:
Lightweight k-line chart built with html5 canvas
2,070 lines • 351 kB
JavaScript
/**
* @license
* KLineChart v10.0.0-beta2
* Copyright (c) 2019 lihu.
* Licensed under Apache License 2.0 https://www.apache.org/licenses/LICENSE-2.0
*/
var DEV = process.env.NODE_ENV === "development";
function log(templateText, tagStyle, messageStyle, api, invalidParam, append) {
if (DEV) {
const apiStr = api !== "" ? `Call api \`${api}\`${invalidParam !== "" || append !== "" ? ", " : "."}` : "";
const invalidParamStr = invalidParam !== "" ? `invalid parameter \`${invalidParam}\`${append !== "" ? ", " : "."}` : "";
console.log(templateText, tagStyle, messageStyle, apiStr, invalidParamStr, append !== "" ? append : "");
}
}
function logWarn(api, invalidParam, append) {
log("%c😑 klinecharts warning%c %s%s%s", "padding:3px 4px;border-radius:2px;color:#ffffff;background-color:#FF9600", "color:#FF9600", api, invalidParam, append ?? "");
}
function logError(api, invalidParam, append) {
log("%c😟 klinecharts error%c %s%s%s", "padding:3px 4px;border-radius:2px;color:#ffffff;background-color:#F92855;", "color:#F92855;", api, invalidParam, append ?? "");
}
function logTag() {
log("%c❤️ Welcome to klinecharts. Version is 10.0.0-beta2", "border-radius:4px;border:dashed 1px #1677FF;line-height:70px;padding:0 20px;margin:16px 0;font-size:14px;color:#1677FF;", "", "", "", "");
}
function merge(target, source) {
if (!isObject(target) && !isObject(source)) return;
for (const key in source) if (Object.prototype.hasOwnProperty.call(source, key)) {
const targetProp = target[key];
const sourceProp = source[key];
if (isObject(sourceProp) && isObject(targetProp)) merge(targetProp, sourceProp);
else target[key] = clone(sourceProp);
}
}
function clone(target) {
if (!isObject(target)) return target;
let copy = null;
if (isArray(target)) copy = [];
else copy = {};
for (const key in target) if (Object.prototype.hasOwnProperty.call(target, key)) {
const v = target[key];
if (isObject(v)) copy[key] = clone(v);
else copy[key] = v;
}
return copy;
}
function isArray(value) {
return Object.prototype.toString.call(value) === "[object Array]";
}
function isFunction(value) {
return typeof value === "function";
}
function isObject(value) {
return typeof value === "object" && isValid(value);
}
function isNumber(value) {
return typeof value === "number" && Number.isFinite(value);
}
function isValid(value) {
return value !== null && value !== void 0;
}
function isBoolean(value) {
return typeof value === "boolean";
}
function isString(value) {
return typeof value === "string";
}
var reEscapeChar = /\\(\\)?/g;
var rePropName = RegExp("[^.[\\]]+|\\[(?:([^\"'][^[]*)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", "g");
function formatValue(data, key, defaultValue) {
if (isValid(data)) {
const path = [];
key.replace(rePropName, (subString, ...args) => {
let k = subString;
if (isValid(args[1])) k = args[2].replace(reEscapeChar, "$1");
else if (isValid(args[0])) k = args[0].trim();
path.push(k);
return "";
});
let value = data;
let index = 0;
const length = path.length;
while (isValid(value) && index < length) value = value?.[path[index++]];
return isValid(value) ? value : defaultValue ?? "--";
}
return defaultValue ?? "--";
}
function formatTimestampToDateTime(dateTimeFormat, timestamp) {
const date = {};
dateTimeFormat.formatToParts(new Date(timestamp)).forEach(({ type, value }) => {
switch (type) {
case "year":
date.YYYY = value;
break;
case "month":
date.MM = value;
break;
case "day":
date.DD = value;
break;
case "hour":
date.HH = value === "24" ? "00" : value;
break;
case "minute":
date.mm = value;
break;
case "second":
date.ss = value;
break;
default: break;
}
});
return date;
}
function formatTimestampByTemplate(dateTimeFormat, timestamp, template) {
const date = formatTimestampToDateTime(dateTimeFormat, timestamp);
return template.replace(/YYYY|MM|DD|HH|mm|ss/g, (key) => date[key]);
}
function formatPrecision(value, precision) {
const v = +value;
if (isNumber(v)) return v.toFixed(precision ?? 2);
return `${value}`;
}
function formatBigNumber(value) {
const v = +value;
if (isNumber(v)) {
if (v > 1e9) return `${+(v / 1e9).toFixed(3)}B`;
if (v > 1e6) return `${+(v / 1e6).toFixed(3)}M`;
if (v > 1e3) return `${+(v / 1e3).toFixed(3)}K`;
}
return `${value}`;
}
function formatThousands(value, sign) {
const vl = `${value}`;
if (sign.length === 0) return vl;
if (vl.includes(".")) {
const arr = vl.split(".");
return `${arr[0].replace(/(\d)(?=(\d{3})+$)/g, ($1) => `${$1}${sign}`)}.${arr[1]}`;
}
return vl.replace(/(\d)(?=(\d{3})+$)/g, ($1) => `${$1}${sign}`);
}
function formatFoldDecimal(value, threshold) {
const vl = `${value}`;
if (new RegExp("\\.0{" + threshold + ",}[1-9][0-9]*$").test(vl)) {
const result = vl.split(".");
const lastIndex = result.length - 1;
const v = result[lastIndex];
const match = /0*/.exec(v);
if (isValid(match)) {
const count = match[0].length;
result[lastIndex] = v.replace(/0*/, `0{${count}}`);
return result.join(".");
}
}
return vl;
}
function formatTemplateString(template, params) {
return template.replace(/\{(\w+)\}/g, (_, key) => {
const value = params[key];
if (isValid(value)) return value;
return `{${key}}`;
});
}
var measureCtx = null;
function getPixelRatio(canvas) {
return canvas.ownerDocument.defaultView?.devicePixelRatio ?? 1;
}
function createFont(size, weight, family) {
return `${weight ?? "normal"} ${size ?? 12}px ${family ?? "Helvetica Neue"}`;
}
function calcTextWidth(text, size, weight, family) {
if (!isValid(measureCtx)) {
const canvas = document.createElement("canvas");
const pixelRatio = getPixelRatio(canvas);
measureCtx = canvas.getContext("2d");
measureCtx.scale(pixelRatio, pixelRatio);
}
measureCtx.font = createFont(size, weight, family);
return Math.round(measureCtx.measureText(text).width);
}
function createDefaultBounding(bounding) {
const defaultBounding = {
width: 0,
height: 0,
left: 0,
right: 0,
top: 0,
bottom: 0
};
if (isValid(bounding)) merge(defaultBounding, bounding);
return defaultBounding;
}
var UpdateLevel = function(UpdateLevel) {
UpdateLevel[UpdateLevel["Main"] = 0] = "Main";
UpdateLevel[UpdateLevel["Overlay"] = 1] = "Overlay";
UpdateLevel[UpdateLevel["Separator"] = 2] = "Separator";
UpdateLevel[UpdateLevel["Drawer"] = 3] = "Drawer";
UpdateLevel[UpdateLevel["All"] = 4] = "All";
return UpdateLevel;
}({});
function requestAnimationFrame(fn) {
if (isFunction(window.requestAnimationFrame)) return window.requestAnimationFrame(fn);
return window.setTimeout(fn, 20);
}
function cancelAnimationFrame(id) {
if (isFunction(window.cancelAnimationFrame)) window.cancelAnimationFrame(id);
else window.clearTimeout(id);
}
var Animation = class {
constructor(options) {
this._options = {
duration: 500,
iterationCount: 1
};
this._currentIterationCount = 0;
this._running = false;
this._time = 0;
merge(this._options, options);
}
_loop() {
this._running = true;
const step = () => {
if (this._running) {
const diffTime = (/* @__PURE__ */ new Date()).getTime() - this._time;
if (diffTime < this._options.duration) {
this._doFrameCallback?.(diffTime);
requestAnimationFrame(step);
} else {
this.stop();
this._currentIterationCount++;
if (this._currentIterationCount < this._options.iterationCount) this.start();
}
}
};
requestAnimationFrame(step);
}
doFrame(callback) {
this._doFrameCallback = callback;
return this;
}
setDuration(duration) {
this._options.duration = duration;
return this;
}
setIterationCount(iterationCount) {
this._options.iterationCount = iterationCount;
return this;
}
start() {
if (!this._running) {
this._time = (/* @__PURE__ */ new Date()).getTime();
this._loop();
}
}
stop() {
if (this._running) this._doFrameCallback?.(this._options.duration);
this._running = false;
}
};
var baseId = 1;
var prevIdTimestamp = (/* @__PURE__ */ new Date()).getTime();
function createId(prefix) {
const timestamp = (/* @__PURE__ */ new Date()).getTime();
if (timestamp === prevIdTimestamp) ++baseId;
else baseId = 1;
prevIdTimestamp = timestamp;
return `${prefix ?? ""}${timestamp}_${baseId}`;
}
function createDom(tagName, styles) {
const dom = document.createElement(tagName);
const s = styles ?? {};
for (const key in s) dom.style[key] = s[key] ?? "";
return dom;
}
function binarySearchNearest(dataList, valueKey, targetValue) {
let left = 0;
let right = 0;
for (right = dataList.length - 1; left !== right;) {
const midIndex = Math.floor((right + left) / 2);
const mid = right - left;
const midValue = dataList[midIndex][valueKey];
if (targetValue === dataList[left][valueKey]) return left;
if (targetValue === dataList[right][valueKey]) return right;
if (targetValue === midValue) return midIndex;
if (targetValue > midValue) left = midIndex;
else right = midIndex;
if (mid <= 2) break;
}
return left;
}
function nice(value) {
const exponent = Math.floor(log10(value));
const exp10 = index10(exponent);
const f = value / exp10;
let nf = 0;
if (f < 1.5) nf = 1;
else if (f < 2.5) nf = 2;
else if (f < 3.5) nf = 3;
else if (f < 4.5) nf = 4;
else if (f < 5.5) nf = 5;
else if (f < 6.5) nf = 6;
else nf = 8;
value = nf * exp10;
return +value.toFixed(Math.abs(exponent));
}
function round(value, precision) {
precision = Math.max(0, precision ?? 0);
const pow = Math.pow(10, precision);
return Math.round(value * pow) / pow;
}
function getPrecision(value) {
const str = value.toString();
const eIndex = str.indexOf("e");
if (eIndex > 0) {
const precision = +str.slice(eIndex + 1);
return precision < 0 ? -precision : 0;
}
const dotIndex = str.indexOf(".");
return dotIndex < 0 ? 0 : str.length - 1 - dotIndex;
}
function getMaxMin(dataList, maxKey, minKey) {
const maxMin = [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
const dataLength = dataList.length;
let index = 0;
while (index < dataLength) {
const data = dataList[index];
maxMin[0] = Math.max(data[maxKey] ?? Number.MIN_SAFE_INTEGER, maxMin[0]);
maxMin[1] = Math.min(data[minKey] ?? Number.MAX_SAFE_INTEGER, maxMin[1]);
++index;
}
return maxMin;
}
function log10(value) {
if (value === 0) return 0;
return Math.log10(value);
}
function index10(value) {
return Math.pow(10, value);
}
function getDefaultVisibleRange() {
return {
from: 0,
to: 0,
realFrom: 0,
realTo: 0
};
}
var TaskScheduler = class {
constructor(callback) {
this._holdingTasks = null;
this._running = false;
this._callback = callback;
}
add(tasks) {
if (!this._running) this._runTask(tasks);
else if (isValid(this._holdingTasks)) this._holdingTasks = {
...this._holdingTasks,
...tasks
};
else this._holdingTasks = tasks;
}
async _runTask(tasks) {
this._running = true;
try {
await Promise.all(Object.values(tasks));
} finally {
this._running = false;
this._callback?.();
if (isValid(this._holdingTasks)) {
const next = this._holdingTasks;
this._runTask(next);
this._holdingTasks = null;
}
}
}
clear() {
this._holdingTasks = null;
}
};
var SymbolDefaultPrecisionConstants = {
PRICE: 2,
VOLUME: 0
};
var Action = class {
constructor() {
this._callbacks = [];
}
subscribe(callback) {
if (this._callbacks.indexOf(callback) < 0) this._callbacks.push(callback);
}
unsubscribe(callback) {
if (isFunction(callback)) {
const index = this._callbacks.indexOf(callback);
if (index > -1) this._callbacks.splice(index, 1);
} else this._callbacks = [];
}
execute(data) {
this._callbacks.forEach((callback) => {
callback(data);
});
}
isEmpty() {
return this._callbacks.length === 0;
}
};
function isTransparent(color) {
return color === "transparent" || color === "none" || /^[rR][gG][Bb][Aa]\(([\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?)[\s]*,){3}[\s]*0[\s]*\)$/.test(color) || /^[hH][Ss][Ll][Aa]\(([\s]*(360|3[0-5][0-9]|[012]?[0-9][0-9]?)[\s]*,)([\s]*((100|[0-9][0-9]?)%|0)[\s]*,){2}([\s]*0[\s]*)\)$/.test(color);
}
function hexToRgb(hex, alpha) {
const h = hex.replace(/^#/, "");
const i = parseInt(h, 16);
return `rgba(${i >> 16 & 255}, ${i >> 8 & 255}, ${i & 255}, ${alpha ?? 1})`;
}
var Color = {
RED: "#F92855",
GREEN: "#2DC08E",
WHITE: "#FFFFFF",
GREY: "#76808F",
BLUE: "#1677FF"
};
function getDefaultGridStyle() {
return {
show: true,
horizontal: {
show: true,
size: 1,
color: "#EDEDED",
style: "dashed",
dashedValue: [2, 2]
},
vertical: {
show: true,
size: 1,
color: "#EDEDED",
style: "dashed",
dashedValue: [2, 2]
}
};
}
function getDefaultCandleStyle() {
const highLow = {
show: true,
color: Color.GREY,
textOffset: 5,
textSize: 10,
textFamily: "Helvetica Neue",
textWeight: "normal"
};
return {
type: "candle_solid",
bar: {
compareRule: "current_open",
upColor: Color.GREEN,
downColor: Color.RED,
noChangeColor: Color.GREY,
upBorderColor: Color.GREEN,
downBorderColor: Color.RED,
noChangeBorderColor: Color.GREY,
upWickColor: Color.GREEN,
downWickColor: Color.RED,
noChangeWickColor: Color.GREY
},
area: {
lineSize: 2,
lineColor: Color.BLUE,
smooth: false,
value: "close",
backgroundColor: [{
offset: 0,
color: hexToRgb(Color.BLUE, .01)
}, {
offset: 1,
color: hexToRgb(Color.BLUE, .2)
}],
point: {
show: true,
color: Color.BLUE,
radius: 4,
rippleColor: hexToRgb(Color.BLUE, .3),
rippleRadius: 8,
animation: true,
animationDuration: 1e3
}
},
priceMark: {
show: true,
high: { ...highLow },
low: { ...highLow },
last: {
show: true,
compareRule: "current_open",
upColor: Color.GREEN,
downColor: Color.RED,
noChangeColor: Color.GREY,
line: {
show: true,
style: "dashed",
dashedValue: [4, 4],
size: 1
},
text: {
show: true,
style: "fill",
size: 12,
paddingLeft: 4,
paddingTop: 4,
paddingRight: 4,
paddingBottom: 4,
borderColor: "transparent",
borderStyle: "solid",
borderSize: 0,
borderDashedValue: [2, 2],
color: Color.WHITE,
family: "Helvetica Neue",
weight: "normal",
borderRadius: 2
},
extendTexts: []
}
},
tooltip: {
offsetLeft: 4,
offsetTop: 6,
offsetRight: 4,
offsetBottom: 6,
showRule: "always",
showType: "standard",
rect: {
position: "fixed",
paddingLeft: 4,
paddingRight: 4,
paddingTop: 4,
paddingBottom: 4,
offsetLeft: 4,
offsetTop: 4,
offsetRight: 4,
offsetBottom: 4,
borderRadius: 4,
borderSize: 1,
borderColor: "#F2F3F5",
color: "#FEFEFE"
},
title: {
show: true,
size: 14,
family: "Helvetica Neue",
weight: "normal",
color: Color.GREY,
marginLeft: 8,
marginTop: 4,
marginRight: 8,
marginBottom: 4,
template: "{ticker} · {period}"
},
legend: {
size: 12,
family: "Helvetica Neue",
weight: "normal",
color: Color.GREY,
marginLeft: 8,
marginTop: 4,
marginRight: 8,
marginBottom: 4,
defaultValue: "n/a",
template: [
{
title: "time",
value: "{time}"
},
{
title: "open",
value: "{open}"
},
{
title: "high",
value: "{high}"
},
{
title: "low",
value: "{low}"
},
{
title: "close",
value: "{close}"
},
{
title: "volume",
value: "{volume}"
}
]
},
features: []
}
};
}
function getDefaultIndicatorStyle() {
const alphaGreen = hexToRgb(Color.GREEN, .7);
const alphaRed = hexToRgb(Color.RED, .7);
return {
ohlc: {
compareRule: "current_open",
upColor: alphaGreen,
downColor: alphaRed,
noChangeColor: Color.GREY
},
bars: [{
style: "fill",
borderStyle: "solid",
borderSize: 1,
borderDashedValue: [2, 2],
upColor: alphaGreen,
downColor: alphaRed,
noChangeColor: Color.GREY
}],
lines: [
"#FF9600",
"#935EBD",
Color.BLUE,
"#E11D74",
"#01C5C4"
].map((color) => ({
style: "solid",
smooth: false,
size: 1,
dashedValue: [2, 2],
color
})),
circles: [{
style: "fill",
borderStyle: "solid",
borderSize: 1,
borderDashedValue: [2, 2],
upColor: alphaGreen,
downColor: alphaRed,
noChangeColor: Color.GREY
}],
texts: [{
paddingLeft: 0,
paddingTop: 0,
paddingRight: 0,
paddingBottom: 0,
style: "fill",
size: 12,
color: Color.BLUE,
family: "Helvetica Neue",
weight: "normal",
borderStyle: "solid",
borderDashedValue: [2, 2],
borderSize: 0,
borderColor: "transparent",
borderRadius: 0,
backgroundColor: "transparent"
}],
lastValueMark: {
show: false,
text: {
show: false,
style: "fill",
color: Color.WHITE,
size: 12,
family: "Helvetica Neue",
weight: "normal",
borderStyle: "solid",
borderColor: "transparent",
borderSize: 0,
borderDashedValue: [2, 2],
paddingLeft: 4,
paddingTop: 4,
paddingRight: 4,
paddingBottom: 4,
borderRadius: 2
}
},
tooltip: {
offsetLeft: 4,
offsetTop: 6,
offsetRight: 4,
offsetBottom: 6,
showRule: "always",
showType: "standard",
title: {
show: true,
showName: true,
showParams: true,
size: 12,
family: "Helvetica Neue",
weight: "normal",
color: Color.GREY,
marginLeft: 8,
marginTop: 4,
marginRight: 8,
marginBottom: 4
},
legend: {
size: 12,
family: "Helvetica Neue",
weight: "normal",
color: Color.GREY,
marginLeft: 8,
marginTop: 4,
marginRight: 8,
marginBottom: 4,
defaultValue: "n/a"
},
features: []
}
};
}
function getDefaultAxisStyle() {
return {
show: true,
size: "auto",
axisLine: {
show: true,
color: "#DDDDDD",
size: 1
},
tickText: {
show: true,
color: Color.GREY,
size: 12,
family: "Helvetica Neue",
weight: "normal",
marginStart: 4,
marginEnd: 6
},
tickLine: {
show: true,
size: 1,
length: 3,
color: "#DDDDDD"
}
};
}
function getDefaultCrosshairStyle() {
return {
show: true,
horizontal: {
show: true,
line: {
show: true,
style: "dashed",
dashedValue: [4, 2],
size: 1,
color: Color.GREY
},
text: {
show: true,
style: "fill",
color: Color.WHITE,
size: 12,
family: "Helvetica Neue",
weight: "normal",
borderStyle: "solid",
borderDashedValue: [2, 2],
borderSize: 1,
borderColor: Color.GREY,
borderRadius: 2,
paddingLeft: 4,
paddingRight: 4,
paddingTop: 4,
paddingBottom: 4,
backgroundColor: Color.GREY
},
features: []
},
vertical: {
show: true,
line: {
show: true,
style: "dashed",
dashedValue: [4, 2],
size: 1,
color: Color.GREY
},
text: {
show: true,
style: "fill",
color: Color.WHITE,
size: 12,
family: "Helvetica Neue",
weight: "normal",
borderStyle: "solid",
borderDashedValue: [2, 2],
borderSize: 1,
borderColor: Color.GREY,
borderRadius: 2,
paddingLeft: 4,
paddingRight: 4,
paddingTop: 4,
paddingBottom: 4,
backgroundColor: Color.GREY
}
}
};
}
function getDefaultOverlayStyle() {
const pointBorderColor = hexToRgb(Color.BLUE, .35);
const alphaBg = hexToRgb(Color.BLUE, .25);
function text() {
return {
style: "fill",
color: Color.WHITE,
size: 12,
family: "Helvetica Neue",
weight: "normal",
borderStyle: "solid",
borderDashedValue: [2, 2],
borderSize: 1,
borderRadius: 2,
borderColor: Color.BLUE,
paddingLeft: 4,
paddingRight: 4,
paddingTop: 4,
paddingBottom: 4,
backgroundColor: Color.BLUE
};
}
return {
point: {
color: Color.BLUE,
borderColor: pointBorderColor,
borderSize: 1,
radius: 5,
activeColor: Color.BLUE,
activeBorderColor: pointBorderColor,
activeBorderSize: 3,
activeRadius: 5
},
line: {
style: "solid",
smooth: false,
color: Color.BLUE,
size: 1,
dashedValue: [2, 2]
},
rect: {
style: "fill",
color: alphaBg,
borderColor: Color.BLUE,
borderSize: 1,
borderRadius: 0,
borderStyle: "solid",
borderDashedValue: [2, 2]
},
polygon: {
style: "fill",
color: Color.BLUE,
borderColor: Color.BLUE,
borderSize: 1,
borderStyle: "solid",
borderDashedValue: [2, 2]
},
circle: {
style: "fill",
color: alphaBg,
borderColor: Color.BLUE,
borderSize: 1,
borderStyle: "solid",
borderDashedValue: [2, 2]
},
arc: {
style: "solid",
color: Color.BLUE,
size: 1,
dashedValue: [2, 2]
},
text: text()
};
}
function getDefaultSeparatorStyle() {
return {
size: 1,
color: "#DDDDDD",
fill: true,
activeBackgroundColor: hexToRgb(Color.BLUE, .08)
};
}
function getDefaultStyles() {
return {
grid: getDefaultGridStyle(),
candle: getDefaultCandleStyle(),
indicator: getDefaultIndicatorStyle(),
xAxis: getDefaultAxisStyle(),
yAxis: getDefaultAxisStyle(),
separator: getDefaultSeparatorStyle(),
crosshair: getDefaultCrosshairStyle(),
overlay: getDefaultOverlayStyle()
};
}
var DEFAULT_AXIS_ID = "default";
function getDefaultAxisRange() {
return {
from: 0,
to: 0,
range: 0,
realFrom: 0,
realTo: 0,
realRange: 0,
displayFrom: 0,
displayTo: 0,
displayRange: 0
};
}
var AxisImp = class {
constructor(parent) {
this.scrollZoomEnabled = true;
this._range = getDefaultAxisRange();
this._prevRange = getDefaultAxisRange();
this._ticks = [];
this._autoCalcTickFlag = true;
this._parent = parent;
}
getParent() {
return this._parent;
}
buildTicks(force) {
if (this._autoCalcTickFlag) this._range = this.createRangeImp();
if (this._prevRange.from !== this._range.from || this._prevRange.to !== this._range.to || force) {
this._prevRange = this._range;
this._ticks = this.createTicksImp();
return true;
}
return false;
}
getTicks() {
return this._ticks;
}
setRange(range) {
this._autoCalcTickFlag = false;
this._range = range;
}
getRange() {
return this._range;
}
setAutoCalcTickFlag(flag) {
this._autoCalcTickFlag = flag;
}
getAutoCalcTickFlag() {
return this._autoCalcTickFlag;
}
};
function eachFigures(indicator, dataIndex, barSpace, defaultStyles, eachFigureCallback) {
const result = indicator.result;
const figures = indicator.figures;
const styles = indicator.styles;
const textStyles = formatValue(styles, "texts", defaultStyles.texts);
const textStyleCount = textStyles.length;
const circleStyles = formatValue(styles, "circles", defaultStyles.circles);
const circleStyleCount = circleStyles.length;
const barStyles = formatValue(styles, "bars", defaultStyles.bars);
const barStyleCount = barStyles.length;
const lineStyles = formatValue(styles, "lines", defaultStyles.lines);
const lineStyleCount = lineStyles.length;
let textCount = 0;
let circleCount = 0;
let barCount = 0;
let lineCount = 0;
let defaultFigureStyles;
let figureIndex = 0;
figures.forEach((figure) => {
switch (figure.type) {
case "text":
figureIndex = textCount;
defaultFigureStyles = textStyles[textCount % textStyleCount];
textCount++;
break;
case "circle": {
figureIndex = circleCount;
const styles = circleStyles[circleCount % circleStyleCount];
defaultFigureStyles = {
...styles,
color: styles.noChangeColor
};
circleCount++;
break;
}
case "bar": {
figureIndex = barCount;
const styles = barStyles[barCount % barStyleCount];
defaultFigureStyles = {
...styles,
color: styles.noChangeColor
};
barCount++;
break;
}
case "line":
figureIndex = lineCount;
defaultFigureStyles = lineStyles[lineCount % lineStyleCount];
lineCount++;
break;
default: break;
}
if (isValid(figure.type)) {
const ss = figure.styles?.({
data: {
prev: result[dataIndex - 1],
current: result[dataIndex],
next: result[dataIndex + 1]
},
indicator,
barSpace,
defaultStyles
});
eachFigureCallback(figure, {
...defaultFigureStyles,
...ss
}, figureIndex);
}
});
}
var IndicatorImp = class {
constructor(indicator) {
this.yAxisId = DEFAULT_AXIS_ID;
this.precision = 4;
this.calcParams = [];
this.shouldOhlc = false;
this.shouldFormatBigNumber = false;
this.visible = true;
this.zLevel = 0;
this.series = "normal";
this.figures = [];
this.minValue = null;
this.maxValue = null;
this.styles = null;
this.shouldUpdate = (prev, current) => {
const calc = JSON.stringify(prev.calcParams) !== JSON.stringify(current.calcParams) || prev.figures !== current.figures || prev.calc !== current.calc;
return {
calc,
draw: calc || prev.shortName !== current.shortName || prev.paneId !== current.paneId || prev.yAxisId !== current.yAxisId || prev.series !== current.series || prev.minValue !== current.minValue || prev.maxValue !== current.maxValue || prev.precision !== current.precision || prev.shouldOhlc !== current.shouldOhlc || prev.shouldFormatBigNumber !== current.shouldFormatBigNumber || prev.visible !== current.visible || prev.zLevel !== current.zLevel || prev.extendData !== current.extendData || prev.regenerateFigures !== current.regenerateFigures || prev.createTooltipDataSource !== current.createTooltipDataSource || prev.draw !== current.draw
};
};
this.calc = () => [];
this.regenerateFigures = null;
this.createTooltipDataSource = null;
this.draw = null;
this.result = [];
this._lockSeriesPrecision = false;
this.override(indicator);
this._lockSeriesPrecision = false;
}
override(indicator) {
const { result, ...currentOthers } = this;
this._prevIndicator = {
...clone(currentOthers),
result
};
const { id, name, shortName, precision, styles, figures, calcParams, ...others } = indicator;
if (!isString(this.id) && isString(id)) this.id = id;
if (!isString(this.name)) this.name = name ?? "";
this.shortName = shortName ?? this.shortName ?? this.name;
if (isNumber(precision)) {
this.precision = precision;
this._lockSeriesPrecision = true;
}
if (isValid(styles)) {
this.styles ??= {};
merge(this.styles, styles);
}
merge(this, others);
if (isValid(calcParams)) {
this.calcParams = calcParams;
if (isFunction(this.regenerateFigures)) this.figures = this.regenerateFigures(this.calcParams);
}
this.figures = figures ?? this.figures;
}
setSeriesPrecision(precision) {
if (!this._lockSeriesPrecision) this.precision = precision;
}
shouldUpdateImp() {
const sort = this._prevIndicator.zLevel !== this.zLevel;
const result = this.shouldUpdate(this._prevIndicator, this);
if (isBoolean(result)) return {
calc: result,
draw: result,
sort
};
return {
...result,
sort
};
}
async calcImp(dataList) {
try {
const result = await this.calc(dataList, this);
this.result = result;
return true;
} catch (e) {
return false;
}
}
static extend(template) {
class Custom extends IndicatorImp {
constructor() {
super(template);
}
}
return Custom;
}
};
var averagePrice = {
name: "AVP",
shortName: "AVP",
series: "price",
precision: 2,
figures: [{
key: "avp",
title: "AVP: ",
type: "line"
}],
calc: (dataList) => {
let totalTurnover = 0;
let totalVolume = 0;
return dataList.map((kLineData) => {
const avp = {};
const turnover = kLineData.turnover ?? 0;
const volume = kLineData.volume ?? 0;
totalTurnover += turnover;
totalVolume += volume;
if (totalVolume !== 0) avp.avp = totalTurnover / totalVolume;
return avp;
});
}
};
var awesomeOscillator = {
name: "AO",
shortName: "AO",
calcParams: [5, 34],
figures: [{
key: "ao",
title: "AO: ",
type: "bar",
baseValue: 0,
styles: ({ data, indicator, defaultStyles }) => {
const { prev, current } = data;
const prevAo = prev?.ao ?? Number.MIN_SAFE_INTEGER;
const currentAo = current?.ao ?? Number.MIN_SAFE_INTEGER;
let color = "";
if (currentAo > prevAo) color = formatValue(indicator.styles, "bars[0].upColor", defaultStyles.bars[0].upColor);
else color = formatValue(indicator.styles, "bars[0].downColor", defaultStyles.bars[0].downColor);
return {
color,
style: currentAo > prevAo ? "stroke" : "fill",
borderColor: color
};
}
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const maxPeriod = Math.max(params[0], params[1]);
let shortSum = 0;
let longSum = 0;
let short = 0;
let long = 0;
return dataList.map((kLineData, i) => {
const ao = {};
const middle = (kLineData.low + kLineData.high) / 2;
shortSum += middle;
longSum += middle;
if (i >= params[0] - 1) {
short = shortSum / params[0];
const agoKLineData = dataList[i - (params[0] - 1)];
shortSum -= (agoKLineData.low + agoKLineData.high) / 2;
}
if (i >= params[1] - 1) {
long = longSum / params[1];
const agoKLineData = dataList[i - (params[1] - 1)];
longSum -= (agoKLineData.low + agoKLineData.high) / 2;
}
if (i >= maxPeriod - 1) ao.ao = short - long;
return ao;
});
}
};
var bias = {
name: "BIAS",
shortName: "BIAS",
calcParams: [
6,
12,
24
],
figures: [
{
key: "bias1",
title: "BIAS6: ",
type: "line"
},
{
key: "bias2",
title: "BIAS12: ",
type: "line"
},
{
key: "bias3",
title: "BIAS24: ",
type: "line"
}
],
regenerateFigures: (params) => params.map((p, i) => ({
key: `bias${i + 1}`,
title: `BIAS${p}: `,
type: "line"
})),
calc: (dataList, indicator) => {
const { calcParams: params, figures } = indicator;
const closeSums = [];
return dataList.map((kLineData, i) => {
const bias = {};
const close = kLineData.close;
params.forEach((p, index) => {
closeSums[index] = (closeSums[index] ?? 0) + close;
if (i >= p - 1) {
const mean = closeSums[index] / params[index];
bias[figures[index].key] = (close - mean) / mean * 100;
closeSums[index] -= dataList[i - (p - 1)].close;
}
});
return bias;
});
}
};
function getBollMd(dataList, ma) {
const dataSize = dataList.length;
let sum = 0;
dataList.forEach((data) => {
const closeMa = data.close - ma;
sum += closeMa * closeMa;
});
sum = Math.abs(sum);
return Math.sqrt(sum / dataSize);
}
var bollingerBands = {
name: "BOLL",
shortName: "BOLL",
series: "price",
calcParams: [20, 2],
precision: 2,
shouldOhlc: true,
figures: [
{
key: "up",
title: "UP: ",
type: "line"
},
{
key: "mid",
title: "MID: ",
type: "line"
},
{
key: "dn",
title: "DN: ",
type: "line"
}
],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const p = params[0] - 1;
let closeSum = 0;
return dataList.map((kLineData, i) => {
const close = kLineData.close;
const boll = {};
closeSum += close;
if (i >= p) {
boll.mid = closeSum / params[0];
const md = getBollMd(dataList.slice(i - p, i + 1), boll.mid);
boll.up = boll.mid + params[1] * md;
boll.dn = boll.mid - params[1] * md;
closeSum -= dataList[i - p].close;
}
return boll;
});
}
};
var brar = {
name: "BRAR",
shortName: "BRAR",
calcParams: [26],
figures: [{
key: "br",
title: "BR: ",
type: "line"
}, {
key: "ar",
title: "AR: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let hcy = 0;
let cyl = 0;
let ho = 0;
let ol = 0;
return dataList.map((kLineData, i) => {
const brar = {};
const high = kLineData.high;
const low = kLineData.low;
const open = kLineData.open;
const prevClose = (dataList[i - 1] ?? kLineData).close;
ho += high - open;
ol += open - low;
hcy += high - prevClose;
cyl += prevClose - low;
if (i >= params[0] - 1) {
if (ol !== 0) brar.ar = ho / ol * 100;
else brar.ar = 0;
if (cyl !== 0) brar.br = hcy / cyl * 100;
else brar.br = 0;
const agoKLineData = dataList[i - (params[0] - 1)];
const agoHigh = agoKLineData.high;
const agoLow = agoKLineData.low;
const agoOpen = agoKLineData.open;
const agoPreClose = (dataList[i - params[0]] ?? dataList[i - (params[0] - 1)]).close;
hcy -= agoHigh - agoPreClose;
cyl -= agoPreClose - agoLow;
ho -= agoHigh - agoOpen;
ol -= agoOpen - agoLow;
}
return brar;
});
}
};
var bullAndBearIndex = {
name: "BBI",
shortName: "BBI",
series: "price",
precision: 2,
calcParams: [
3,
6,
12,
24
],
shouldOhlc: true,
figures: [{
key: "bbi",
title: "BBI: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const maxPeriod = Math.max(...params);
const closeSums = [];
const mas = [];
return dataList.map((kLineData, i) => {
const bbi = {};
const close = kLineData.close;
params.forEach((p, index) => {
closeSums[index] = (closeSums[index] ?? 0) + close;
if (i >= p - 1) {
mas[index] = closeSums[index] / p;
closeSums[index] -= dataList[i - (p - 1)].close;
}
});
if (i >= maxPeriod - 1) {
let maSum = 0;
mas.forEach((ma) => {
maSum += ma;
});
bbi.bbi = maSum / 4;
}
return bbi;
});
}
};
var commodityChannelIndex = {
name: "CCI",
shortName: "CCI",
calcParams: [20],
figures: [{
key: "cci",
title: "CCI: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const p = params[0] - 1;
let tpSum = 0;
const tpList = [];
return dataList.map((kLineData, i) => {
const cci = {};
const tp = (kLineData.high + kLineData.low + kLineData.close) / 3;
tpSum += tp;
tpList.push(tp);
if (i >= p) {
const maTp = tpSum / params[0];
const sliceTpList = tpList.slice(i - p, i + 1);
let sum = 0;
sliceTpList.forEach((tp) => {
sum += Math.abs(tp - maTp);
});
const md = sum / params[0];
cci.cci = md !== 0 ? (tp - maTp) / md / .015 : 0;
const agoTp = (dataList[i - p].high + dataList[i - p].low + dataList[i - p].close) / 3;
tpSum -= agoTp;
}
return cci;
});
}
};
var currentRatio = {
name: "CR",
shortName: "CR",
calcParams: [
26,
10,
20,
40,
60
],
figures: [
{
key: "cr",
title: "CR: ",
type: "line"
},
{
key: "ma1",
title: "MA1: ",
type: "line"
},
{
key: "ma2",
title: "MA2: ",
type: "line"
},
{
key: "ma3",
title: "MA3: ",
type: "line"
},
{
key: "ma4",
title: "MA4: ",
type: "line"
}
],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const ma1ForwardPeriod = Math.ceil(params[1] / 2.5 + 1);
const ma2ForwardPeriod = Math.ceil(params[2] / 2.5 + 1);
const ma3ForwardPeriod = Math.ceil(params[3] / 2.5 + 1);
const ma4ForwardPeriod = Math.ceil(params[4] / 2.5 + 1);
let ma1Sum = 0;
const ma1List = [];
let ma2Sum = 0;
const ma2List = [];
let ma3Sum = 0;
const ma3List = [];
let ma4Sum = 0;
const ma4List = [];
const result = [];
dataList.forEach((kLineData, i) => {
const cr = {};
const prevData = dataList[i - 1] ?? kLineData;
const prevMid = (prevData.high + prevData.close + prevData.low + prevData.open) / 4;
const highSubPreMid = Math.max(0, kLineData.high - prevMid);
const preMidSubLow = Math.max(0, prevMid - kLineData.low);
if (i >= params[0] - 1) {
if (preMidSubLow !== 0) cr.cr = highSubPreMid / preMidSubLow * 100;
else cr.cr = 0;
ma1Sum += cr.cr;
ma2Sum += cr.cr;
ma3Sum += cr.cr;
ma4Sum += cr.cr;
if (i >= params[0] + params[1] - 2) {
ma1List.push(ma1Sum / params[1]);
if (i >= params[0] + params[1] + ma1ForwardPeriod - 3) cr.ma1 = ma1List[ma1List.length - 1 - ma1ForwardPeriod];
ma1Sum -= result[i - (params[1] - 1)].cr ?? 0;
}
if (i >= params[0] + params[2] - 2) {
ma2List.push(ma2Sum / params[2]);
if (i >= params[0] + params[2] + ma2ForwardPeriod - 3) cr.ma2 = ma2List[ma2List.length - 1 - ma2ForwardPeriod];
ma2Sum -= result[i - (params[2] - 1)].cr ?? 0;
}
if (i >= params[0] + params[3] - 2) {
ma3List.push(ma3Sum / params[3]);
if (i >= params[0] + params[3] + ma3ForwardPeriod - 3) cr.ma3 = ma3List[ma3List.length - 1 - ma3ForwardPeriod];
ma3Sum -= result[i - (params[3] - 1)].cr ?? 0;
}
if (i >= params[0] + params[4] - 2) {
ma4List.push(ma4Sum / params[4]);
if (i >= params[0] + params[4] + ma4ForwardPeriod - 3) cr.ma4 = ma4List[ma4List.length - 1 - ma4ForwardPeriod];
ma4Sum -= result[i - (params[4] - 1)].cr ?? 0;
}
}
result.push(cr);
});
return result;
}
};
var differentOfMovingAverage = {
name: "DMA",
shortName: "DMA",
calcParams: [
10,
50,
10
],
figures: [{
key: "dma",
title: "DMA: ",
type: "line"
}, {
key: "ama",
title: "AMA: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const maxPeriod = Math.max(params[0], params[1]);
let closeSum1 = 0;
let closeSum2 = 0;
let dmaSum = 0;
const result = [];
dataList.forEach((kLineData, i) => {
const dma = {};
const close = kLineData.close;
closeSum1 += close;
closeSum2 += close;
let ma1 = 0;
let ma2 = 0;
if (i >= params[0] - 1) {
ma1 = closeSum1 / params[0];
closeSum1 -= dataList[i - (params[0] - 1)].close;
}
if (i >= params[1] - 1) {
ma2 = closeSum2 / params[1];
closeSum2 -= dataList[i - (params[1] - 1)].close;
}
if (i >= maxPeriod - 1) {
const dif = ma1 - ma2;
dma.dma = dif;
dmaSum += dif;
if (i >= maxPeriod + params[2] - 2) {
dma.ama = dmaSum / params[2];
dmaSum -= result[i - (params[2] - 1)].dma ?? 0;
}
}
result.push(dma);
});
return result;
}
};
var directionalMovementIndex = {
name: "DMI",
shortName: "DMI",
calcParams: [14, 6],
figures: [
{
key: "pdi",
title: "PDI: ",
type: "line"
},
{
key: "mdi",
title: "MDI: ",
type: "line"
},
{
key: "adx",
title: "ADX: ",
type: "line"
},
{
key: "adxr",
title: "ADXR: ",
type: "line"
}
],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let trSum = 0;
let hSum = 0;
let lSum = 0;
let mtr = 0;
let dmp = 0;
let dmm = 0;
let dxSum = 0;
let adx = 0;
const result = [];
dataList.forEach((kLineData, i) => {
const dmi = {};
const prevKLineData = dataList[i - 1] ?? kLineData;
const preClose = prevKLineData.close;
const high = kLineData.high;
const low = kLineData.low;
const hl = high - low;
const hcy = Math.abs(high - preClose);
const lcy = Math.abs(preClose - low);
const hhy = high - prevKLineData.high;
const lyl = prevKLineData.low - low;
const tr = Math.max(Math.max(hl, hcy), lcy);
const h = hhy > 0 && hhy > lyl ? hhy : 0;
const l = lyl > 0 && lyl > hhy ? lyl : 0;
trSum += tr;
hSum += h;
lSum += l;
if (i >= params[0] - 1) {
if (i > params[0] - 1) {
mtr = mtr - mtr / params[0] + tr;
dmp = dmp - dmp / params[0] + h;
dmm = dmm - dmm / params[0] + l;
} else {
mtr = trSum;
dmp = hSum;
dmm = lSum;
}
let pdi = 0;
let mdi = 0;
if (mtr !== 0) {
pdi = dmp * 100 / mtr;
mdi = dmm * 100 / mtr;
}
dmi.pdi = pdi;
dmi.mdi = mdi;
let dx = 0;
if (mdi + pdi !== 0) dx = Math.abs(mdi - pdi) / (mdi + pdi) * 100;
dxSum += dx;
if (i >= params[0] * 2 - 2) {
if (i > params[0] * 2 - 2) adx = (adx * (params[0] - 1) + dx) / params[0];
else adx = dxSum / params[0];
dmi.adx = adx;
if (i >= params[0] * 2 + params[1] - 3) dmi.adxr = ((result[i - (params[1] - 1)].adx ?? 0) + adx) / 2;
}
}
result.push(dmi);
});
return result;
}
};
var easeOfMovementValue = {
name: "EMV",
shortName: "EMV",
calcParams: [14, 9],
figures: [{
key: "emv",
title: "EMV: ",
type: "line"
}, {
key: "maEmv",
title: "MAEMV: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let emvValueSum = 0;
const emvValueList = [];
return dataList.map((kLineData, i) => {
const emv = {};
if (i > 0) {
const prevKLineData = dataList[i - 1];
const high = kLineData.high;
const low = kLineData.low;
const volume = kLineData.volume ?? 0;
const distanceMoved = (high + low) / 2 - (prevKLineData.high + prevKLineData.low) / 2;
if (volume === 0 || high - low === 0) emv.emv = 0;
else emv.emv = distanceMoved / (volume / 1e8 / (high - low));
emvValueSum += emv.emv;
emvValueList.push(emv.emv);
if (i >= params[0]) {
emv.maEmv = emvValueSum / params[0];
emvValueSum -= emvValueList[i - params[0]];
}
}
return emv;
});
}
};
var exponentialMovingAverage = {
name: "EMA",
shortName: "EMA",
series: "price",
calcParams: [
6,
12,
20
],
precision: 2,
shouldOhlc: true,
figures: [
{
key: "ema1",
title: "EMA6: ",
type: "line"
},
{
key: "ema2",
title: "EMA12: ",
type: "line"
},
{
key: "ema3",
title: "EMA20: ",
type: "line"
}
],
regenerateFigures: (params) => params.map((p, i) => ({
key: `ema${i + 1}`,
title: `EMA${p}: `,
type: "line"
})),
calc: (dataList, indicator) => {
const { calcParams: params, figures } = indicator;
let closeSum = 0;
const emaValues = [];
return dataList.map((kLineData, i) => {
const ema = {};
const close = kLineData.close;
closeSum += close;
params.forEach((p, index) => {
if (i >= p - 1) {
if (i > p - 1) emaValues[index] = (2 * close + (p - 1) * emaValues[index]) / (p + 1);
else emaValues[index] = closeSum / p;
ema[figures[index].key] = emaValues[index];
}
});
return ema;
});
}
};
var momentum = {
name: "MTM",
shortName: "MTM",
calcParams: [12, 6],
figures: [{
key: "mtm",
title: "MTM: ",
type: "line"
}, {
key: "maMtm",
title: "MAMTM: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let mtmSum = 0;
const result = [];
dataList.forEach((kLineData, i) => {
const mtm = {};
if (i >= params[0]) {
mtm.mtm = kLineData.close - dataList[i - params[0]].close;
mtmSum += mtm.mtm;
if (i >= params[0] + params[1] - 1) {
mtm.maMtm = mtmSum / params[1];
mtmSum -= result[i - (params[1] - 1)].mtm ?? 0;
}
}
result.push(mtm);
});
return result;
}
};
var movingAverage = {
name: "MA",
shortName: "MA",
series: "price",
calcParams: [
5,
10,
30,
60
],
precision: 2,
shouldOhlc: true,
figures: [
{
key: "ma1",
title: "MA5: ",
type: "line"
},
{
key: "ma2",
title: "MA10: ",
type: "line"
},
{
key: "ma3",
title: "MA30: ",
type: "line"
},
{
key: "ma4",
title: "MA60: ",
type: "line"
}
],
regenerateFigures: (params) => params.map((p, i) => ({
key: `ma${i + 1}`,
title: `MA${p}: `,
type: "line"
})),
calc: (dataList, indicator) => {
const { calcParams: params, figures } = indicator;
const closeSums = [];
return dataList.map((kLineData, i) => {
const ma = {};
const close = kLineData.close;
params.forEach((p, index) => {
closeSums[index] = (closeSums[index] ?? 0) + close;
if (i >= p - 1) {
ma[figures[index].key] = closeSums[index] / p;
closeSums[index] -= dataList[i - (p - 1)].close;
}
});
return ma;
});
}
};
var movingAverageConvergenceDivergence = {
name: "MACD",
shortName: "MACD",
calcParams: [
12,
26,
9
],
figures: [
{
key: "dif",
title: "DIF: ",
type: "line"
},
{
key: "dea",
title: "DEA: ",
type: "line"
},
{
key: "macd",
title: "MACD: ",
type: "bar",
baseValue: 0,
styles: ({ data, indicator, defaultStyles }) => {
const { prev, current } = data;
const prevMacd = prev?.macd ?? Number.MIN_SAFE_INTEGER;
const currentMacd = current?.macd ?? Number.MIN_SAFE_INTEGER;
let color = "";
if (currentMacd > 0) color = formatValue(indicator.styles, "bars[0].upColor", defaultStyles.bars[0].upColor);
else if (currentMacd < 0) color = formatValue(indicator.styles, "bars[0].downColor", defaultStyles.bars[0].downColor);
else color = formatValue(indicator.styles, "bars[0].noChangeColor", defaultStyles.bars[0].noChangeColor);
return {
style: prevMacd < currentMacd ? "stroke" : "fill",
color,
borderColor: color
};
}
}
],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let closeSum = 0;
let emaShort = 0;
let emaLong = 0;
let dif = 0;
let difSum = 0;
let dea = 0;
const maxPeriod = Math.max(params[0], params[1]);
return dataList.map((kLineData, i) => {
const macd = {};
const close = kLineData.close;
closeSum += close;
if (i >= params[0] - 1) if (i > params[0] - 1) emaShort = (2 * close + (params[0] - 1) * emaShort) / (params[0] + 1);
else emaShort = closeSum / params[0];
if (i >= params[1] - 1) if (i > params[1] - 1) emaLong = (2 * close + (params[1] - 1) * emaLong) / (params[1] + 1);
else emaLong = closeSum / params[1];
if (i >= maxPeriod - 1) {
dif = emaShort - emaLong;
macd.dif = dif;
difSum += dif;
if (i >= maxPeriod + params[2] - 2) {
if (i > maxPeriod + params[2] - 2) dea = (dif * 2 + dea * (params[2] - 1)) / (params[2] + 1);
else dea = difSum / params[2];
macd.macd = (dif - dea) * 2;
macd.dea = dea;
}
}
return macd;
});
}
};
var onBalanceVolume = {
name: "OBV",
shortName: "OBV",
calcParams: [30],
figures: [{
key: "obv",
title: "OBV: ",
type: "line"
}, {
key: "maObv",
title: "MAOBV: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let obvSum = 0;
let oldObv = 0;
const result = [];
dataList.forEach((kLineData, i) => {
const prevKLineData = dataList[i - 1] ?? kLineData;
if (kLineData.close < prevKLineData.close) oldObv -= kLineData.volume ?? 0;
else if (kLineData.close > prevKLineData.close) oldObv += kLineData.volume ?? 0;
const obv = { obv: oldObv };
obvSum += oldObv;
if (i >= params[0] - 1) {
obv.maObv = obvSum / params[0];
obvSum -= result[i - (params[0] - 1)].obv ?? 0;
}
result.push(obv);
});
return result;
}
};
var priceAndVolumeTrend = {
name: "PVT",
shortName: "PVT",
figures: [{
key: "pvt",
title: "PVT: ",
type: "line"
}],
calc: (dataList) => {
let sum = 0;
return dataList.map((kLineData, i) => {
const pvt = {};
const close = kLineData.close;
const volume = kLineData.volume ?? 1;
const prevClose = (dataList[i - 1] ?? kLineData).close;
let x = 0;
const total = prevClose * volume;
if (total !== 0) x = (close - prevClose) / total;
sum += x;
pvt.pvt = sum;
return pvt;
});
}
};
var psychologicalLine = {
name: "PSY",
shortName: "PSY",
calcParams: [12, 6],
figures: [{
key: "psy",
title: "PSY: ",
type: "line"
}, {
key: "maPsy",
title: "MAPSY: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
let upCount = 0;
let psySum = 0;
const upList = [];
const result = [];
dataList.forEach((kLineData, i) => {
const psy = {};
const prevClose = (dataList[i - 1] ?? kLineData).close;
const upFlag = kLineData.close - prevClose > 0 ? 1 : 0;
upList.push(upFlag);
upCount += upFlag;
if (i >= params[0] - 1) {
psy.psy = upCount / params[0] * 100;
psySum += psy.psy;
if (i >= params[0] + params[1] - 2) {
psy.maPsy = psySum / params[1];
psySum -= result[i - (params[1] - 1)].psy ?? 0;
}
upCount -= upList[i - (params[0] - 1)];
}
result.push(psy);
});
return result;
}
};
var rateOfChange = {
name: "ROC",
shortName: "ROC",
calcParams: [12, 6],
figures: [{
key: "roc",
title: "ROC: ",
type: "line"
}, {
key: "maRoc",
title: "MAROC: ",
type: "line"
}],
calc: (dataList, indicator) => {
const params = indicator.calcParams;
const result = [];
let rocSum = 0;
dataList.forEach((kLineData, i) => {
const roc = {};
if (i >= params[0] - 1) {
const close = kLineData.close;
const agoClose = (dataList[i - params[0]] ?? dataList[i - (params[0] - 1)]).close;
if (agoClose !== 0) roc.roc = (close - agoClose) / agoClose * 100;
else roc.roc = 0;
rocSum += roc.roc;
if (i >= params[0] - 1 + params[1] - 1) {
roc.maRoc = rocSum / params[1];
rocSum -= result[i - (params[1] - 1)].roc ?? 0;
}
}
result.push(roc);
});
return result;
}
};
var relativeStrengthIndex = {
name: "RSI",
shortName: "RSI",
calcParams: [
6,
12,
24
],
figures: [
{
key: "rsi1",
title: "RSI1: ",
type: "line"
},
{
key: "rsi2",
title: "RSI2: ",
type: "line"
},
{
key: "rsi3",
title: "RSI3: ",
type: "line"
}
],
regenerateFigures: (params) => params.map((_, index) => {
const num = index + 1;
return {
key: `rsi${num}`,
title: `RSI${num}: `,
type: "line"
};
}),
calc: (dataList, indicator) => {
const { calcParams: params, figures } = indicator;
const sumCloseAs = [];
const sumCloseBs = [];
return dataList.map((kLineData, i) => {
const rsi = {};
const prevClose = (dataList[i - 1] ?? kLineData).close;
const tmp = kLineData.close - prevClose;
params.forEach((p, index) => {
if (tmp > 0) sumCloseAs[index] = (sumCloseAs[index] ?? 0) + tmp;
else sumCloseBs[index] = (sumCloseBs[index] ?? 0) + Math.abs(tmp);
if (i >= p - 1) {
if (sumCloseBs[index] !== 0) rsi[figures[index].key] = 100 - 100 / (1 + sumCloseAs[index] / sumCloseBs[index]);
else rsi[figures[index].key] = 0;
const agoData = dataList[i - (p - 1)];
const agoPreData = dataList[i - p] ?? agoData;
const agoTmp = agoData.close - agoPreData.close;
if (agoTmp > 0) sumCloseAs[index] -= agoTmp;
else sumCloseBs[index] -= Math.abs(agoTmp);
}
});
return rsi;
});
}
}