Skip to content

Commit

Permalink
use logarithmic chart scale for extreme price changes
Browse files Browse the repository at this point in the history
fixes: #554
  • Loading branch information
kenkunz committed Nov 10, 2023
1 parent 3cd4a19 commit 01e81f9
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 4 deletions.
10 changes: 9 additions & 1 deletion src/lib/chart/PairCandleChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,18 @@ Display trading pair candles (ohlc+v) charts, with attached quoteFeed for chart
const options = {
layout: { crosshair: true },
controls: { chartControls: null },
dontRoll: true
dontRoll: true,
chart: {
yAxis: {
priceFormatter: (...args: any[]) => formatDollar(args[2], 1, 3, false)
}
}
};
const init = (chartEngine: any) => {
// prevent chart from hanging due to extreme variance in prices
chartEngine.setChartScale('log');
// match the current price label precision to other yAxis labels
chartEngine.addEventListener('symbolChange', () => {
chartEngine.chart.yAxis.maxDecimalPlaces = chartEngine.chart.yAxis.printDecimalPlaces;
Expand Down
5 changes: 5 additions & 0 deletions src/lib/helpers/formatters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ describe('formatDollar', () => {
expect(formatDollar(123.456)).toEqual('$123.46');
});

test('should format 0 and -0 correctly', () => {
expect(formatDollar(0)).toEqual('$0.00');
expect(formatDollar(-0)).toEqual('$0.00');
});

test('should use compact format magnitude abbreviations', () => {
expect(formatDollar(1234.56)).toEqual('$1.23K');
expect(formatDollar(12345.67)).toEqual('$12.35K');
Expand Down
19 changes: 16 additions & 3 deletions src/lib/helpers/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ const HOUR = MINUTE * 60;
const DAY = HOUR * 24;

// thresholds for determining when to use scientific notation
const VERY_SMALL_NUMBER = 1e-6; // 0.000001
const VERY_LARGE_NUMBER = 1e15; // 1,000,000,000,000 = 1,000T
const VERY_SMALL_USD_VALUE = 1e-6; // 0.000001
const VERY_LARGE_USD_VALUE = 1e15; // 1,000,000,000,000 = 1,000T
const VERY_SMALL_PERCENT = 1e-8; // 0.000001%
const VERY_LARGE_PERCENT = 1e4; // 1,000,000%

function toFloatingPoint(n: MaybeNumberlike) {
if (typeof n == 'string') {
Expand All @@ -22,6 +24,12 @@ function isNumber(n: MaybeNumber): n is number {
return Number.isFinite(n);
}

// Check if number is extremely small or large
function isExtreme(n: number, small: number, large: number) {
const absN = Math.abs(n);
return n !== 0 && (absN < small || absN >= large);
}

/**
* Convert number to thousands.
*
Expand Down Expand Up @@ -94,7 +102,7 @@ export function formatDollar(n: MaybeNumberlike, minDigits = 2, maxPrecision = m
if (!isNumber(n)) return notFilledMarker;

// If n is very small or large, use scientific notation
const notation = n < VERY_SMALL_NUMBER || n >= VERY_LARGE_NUMBER ? 'scientific' : 'compact';
const notation = isExtreme(n, VERY_SMALL_USD_VALUE, VERY_LARGE_USD_VALUE) ? 'scientific' : 'compact';
const style = showPrefix ? 'currency' : 'decimal';
const options: Intl.NumberFormatOptions = { notation, style, compactDisplay: 'short', currency: 'USD' };
return formatNumber(n, minDigits, maxPrecision, options);
Expand Down Expand Up @@ -225,7 +233,12 @@ export function formatShortAddress(address: MaybeString): string {
* Like average winning profit.
*/
export function formatPercent(n: MaybeNumberlike, minDigits = 1, maxPrecision = minDigits) {
n = toFloatingPoint(n);
if (!isNumber(n)) return notFilledMarker;

const notation = isExtreme(n, VERY_SMALL_PERCENT, VERY_LARGE_PERCENT) ? 'scientific' : 'standard';
return formatNumber(n, minDigits, maxPrecision, {
notation,
style: 'percent'
});
}
Expand Down

0 comments on commit 01e81f9

Please sign in to comment.