@devexperts/dxcharts-lite
Version:
137 lines (136 loc) • 5.47 kB
JavaScript
/*
* Copyright (C) 2019 - 2026 Devexperts Solutions IE Limited
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (C) 2019 - 2024 Devexperts Solutions IE Limited
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { lastOf } from './array.utils';
import { floor, MathUtils } from './math.utils';
export class PriceIncrementsUtils {
/**
* Automatically detects increment of provided value range.
* Naive algorithm takes 1st mantissa number's :
* range=124.14 => increment=1
* range=6300.02931 => increment=10
* range=0.0018 => increment=0.00001
*
* @param valueRange
* @doc-tags tricky,y-axis
*/
static autoDetectIncrementOfValueRange(valueRange) {
const extent = this.getDigitsInNumber(valueRange);
if (valueRange === 0) {
return 0.01;
}
else {
return Math.pow(10, extent - 3);
}
}
/**
* Gets number of digits in number.
* 20 => 2
* 100 => 3
* 7 => 1
* 1.123 => 1
* -200 => 3
* -1234567.00031 => 7
* 0.0001 = > -4
* @param x - any number
*/
static getDigitsInNumber(x) {
// eslint-disable-next-line no-bitwise
const getDigits = (val) => floor(Math.log10((val ^ (val >> 31)) - (val >> 31))) + 1;
let numberDigitsIntegerPart = getDigits(x);
if (numberDigitsIntegerPart === 1 && x < 1) {
numberDigitsIntegerPart = getDigits(x * 10e8) - 9;
}
return numberDigitsIntegerPart;
}
// Price increment represent array of increment-price couples and trailing increment
// e.g. [(priceIncrement, price,)* incrementForOtherPrices]
// [0.01, 1, 0.1, 10, 1] means
// there are three increments (0.01 for price < 1 ), (0.1 for price < 10), (1 for any other price)
static getPriceIncrement(price, increments = []) {
if (!this.validatePriceIncrementsOrPrecisions(increments)) {
return this.DEFAULT_INCREMENT;
}
if (isNaN(price)) {
return Math.round(increments[0]);
}
price = Math.abs(price);
let i = 1;
const RELATIVE_EPS = 1e-6;
while (i < increments.length &&
price > increments[i] + Math.min(increments[i - 1], increments[i + 1]) * RELATIVE_EPS) {
i += 2;
}
if (i >= increments.length ||
price < increments[i] - Math.min(increments[i - 1], increments[i + 1]) * RELATIVE_EPS) {
return increments[i - 1];
}
return Math.min(increments[i - 1], increments[i + 1]);
}
static getPricePrecision(price, precisions) {
if (!this.validatePriceIncrementsOrPrecisions(precisions)) {
return 0;
}
if (isNaN(price)) {
return Math.round(precisions[0]);
}
price = Math.abs(price);
let i = 1;
while (i < precisions.length && price > precisions[i]) {
i += 2;
}
return Math.round(precisions[i - 1]);
}
static roundPriceToIncrement(price, increments, incrementReferencePrice) {
const increment = PriceIncrementsUtils.getPriceIncrement(incrementReferencePrice ? incrementReferencePrice : price, increments);
return MathUtils.roundToNearest(price, increment);
}
static computePrecisions(increments) {
const precisions = [...increments];
for (let i = 0; i < increments.length; i += 2) {
precisions[i] = PriceIncrementsUtils.calculatePrecision(increments[i]);
}
for (let i = 1; i < increments.length; i += 2) {
precisions[i] =
increments[i] + Math.min(increments[i - 1], increments[i + 1]) * PriceIncrementsUtils.RELATIVE_EPS;
}
return precisions;
}
static calculatePrecision(value) {
// in case value is a huge number we need to perform normalization so that relative epsilon still works
let val = value > 1 ? Math.abs(Math.floor(value) - value) : value;
for (let i = 0; i < PriceIncrementsUtils.MAXIMUM_PRECISION; i++) {
const round = Math.floor(val + 0.5);
const eps = Math.abs(val * PriceIncrementsUtils.RELATIVE_EPS);
if (round >= val - eps && round <= val + eps) {
return i;
}
val *= 10;
}
return PriceIncrementsUtils.MAXIMUM_PRECISION;
}
/**
* checks if the received from feed increments/precisions data is valid to display
* examples of wrong data: [], [0], not an array
*/
static validatePriceIncrementsOrPrecisions(data) {
return !(data.length === 0 || !Array.isArray(data) || data.findIndex((i) => i !== 0) === -1);
}
}
PriceIncrementsUtils.DEFAULT_INCREMENT = 0.01;
PriceIncrementsUtils.DEFAULT_PRECISION = 2;
PriceIncrementsUtils.RELATIVE_EPS = 1e-8;
PriceIncrementsUtils.MAXIMUM_PRECISION = 10;
// TODO review dxFeed precision format
export const precisionsToIncrement = (price, precisions) => {
var _a;
const precision = (_a = lastOf(precisions)) !== null && _a !== void 0 ? _a : 1;
return Math.pow(10, -precision);
};