Skip to content

Commit

Permalink
Merge pull request #595 from tradingstrategy-ai/trade-page-update
Browse files Browse the repository at this point in the history
Update the trade page stats and layout
  • Loading branch information
kenkunz authored Sep 25, 2023
2 parents 13c1460 + ddfb765 commit b60d6e0
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 78 deletions.
11 changes: 7 additions & 4 deletions src/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Ambient type declarations (separate from app.d.ts)
// see: https://kit.svelte.dev/docs/types#app
declare global {
// some numbers (e.g. from API) are given as strings
type Numberlike = number | string;

type Maybe<T> = T | null | undefined;
type MaybeNumber = MaybeType<number>;
type MaybeString = MaybeType<string>;
type MaybeNumberOrString = MaybeType<number | string>;
type MaybeDate = MaybeType<Date>;
type MaybeNumber = Maybe<number>;
type MaybeNumberlike = Maybe<Numberlike>;
type MaybeString = Maybe<string>;
type MaybeDate = Maybe<Date>;
type MaybePromise<T> = T | Promise<T>;

type Formatter<T> = (value: T) => string;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/DataBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Uses together with SummaryBox or DataBoxes to display a set of properties / stat
<script lang="ts">
export let label: string;
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
export let value: MaybeNumberOrString = undefined;
export let value: Maybe<number | string> = undefined;
</script>

<div class="data-box {size}">
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/Timestamp.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ by JS Date) or a Date object.
span + span {
&::before {
content: ' ';
white-space: normal;
}
}
</style>
6 changes: 3 additions & 3 deletions src/lib/components/UpDownIndicator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ a lightweight visual representation is needed. See `UpDownCell.svelte` for a mor
```
-->
<script lang="ts">
export let value: MaybeNumberOrString;
export let value: MaybeNumberlike;
type ValueFormatter = Formatter<MaybeNumberOrString>;
type ValueFormatter = Formatter<MaybeNumberlike>;
export let formatter: Maybe<ValueFormatter> = undefined;
type CompareFn = (value: MaybeNumberOrString) => number;
type CompareFn = (value: MaybeNumberlike) => number;
const defaultCompare: CompareFn = (value) => (value ? Math.sign(Number(value)) : 0);
export let compareFn: CompareFn = defaultCompare;
Expand Down
12 changes: 12 additions & 0 deletions src/lib/helpers/formatters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,16 @@ describe('formatNumber', () => {
expect(() => formatNumber(123.456, -1)).toThrowError();
expect(() => formatNumber(123.456, 2, 1)).toThrowError();
});

test('should coerce string argument to number', () => {
expect(formatNumber('123.456')).toEqual('123.46');
});

test('should return "---" for non-numeric values', () => {
expect(formatNumber(null)).toEqual('---');
expect(formatNumber(undefined)).toEqual('---');
expect(formatNumber(NaN)).toEqual('---');
expect(formatNumber('')).toEqual('---');
expect(formatNumber('foo')).toEqual('---');
});
});
99 changes: 83 additions & 16 deletions src/lib/helpers/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ const MINUTE = 60;
const HOUR = MINUTE * 60;
const DAY = HOUR * 24;

function toFloatingPoint(n: MaybeNumberlike) {
if (typeof n == 'string') {
return parseFloat(n);
}

return n;
}

/**
* Convert number to thousands.
*
Expand Down Expand Up @@ -72,7 +80,7 @@ export function formatSizeGigabytes(n: MaybeNumber): string {
* @param maxPrecision - maximum number of significant digits (default = minDigits)
* @param showPrefix - whether to show "$" prefix (default = true)
*/
export function formatDollar(n: MaybeNumber, minDigits = 2, maxPrecision = minDigits, showPrefix = true) {
export function formatDollar(n: MaybeNumberlike, minDigits = 2, maxPrecision = minDigits, showPrefix = true) {
const style = showPrefix ? 'currency' : 'decimal';
const options = { style, currency: 'USD', notation: 'compact', compactDisplay: 'short' };
return formatNumber(n, minDigits, maxPrecision, options);
Expand All @@ -81,26 +89,27 @@ export function formatDollar(n: MaybeNumber, minDigits = 2, maxPrecision = minDi
/**
* Format a number with appropriate number of digits based on its magnitude.
* - larger numbers display `minDigits` after the decimal point
* - smaller numbers display from `minDigits` up to `maxDigits`
* - smaller numbers display from `minDigits` up to `maxPrecision`
* significant digits (to retain precision)
*
* @example
* With min and maxDigits = 2 (the default):
* With minDigits and maxPrecision = 2 (the default):
* 12 -> 12.00
* 1.234 -> 1.23
* 0.1 -> 0.10
* 0.123 -> 0.12
* 0.0123 -> 0.012
*
* @param n - number to format
* @param n - number to format (Numberlike - may be a string)
* @param minDigits - minimum number of digits to display (default = 2)
* @param maxPrecision - maximum number of significant digits (default = minDigits)
* @param options - additional options to pass through to `toLocaleString()`
*/
export function formatNumber(n: MaybeNumber, minDigits = 2, maxPrecision = minDigits, options = {}) {
export function formatNumber(n: MaybeNumberlike, minDigits = 2, maxPrecision = minDigits, options = {}) {
if (minDigits < 0) throw new RangeError('minDigits must be >= 0');
if (maxPrecision < minDigits) throw new RangeError('maxDigits must be >= minDigits');
if (maxPrecision < minDigits) throw new RangeError('maxPrecision must be >= minDigits');

n = toFloatingPoint(n);
if (!Number.isFinite(n)) return notFilledMarker;

// Don't format -0.00
Expand Down Expand Up @@ -132,23 +141,25 @@ export function formatNumber(n: MaybeNumber, minDigits = 2, maxPrecision = minDi
* Format price with '$' prefix, thousands separator, and useful
* number of digits.
*/
export function formatPrice(n: MaybeNumber, minDigits = 2, maxDigits = 4) {
maxDigits = Math.max(minDigits, maxDigits);
return formatNumber(n, minDigits, maxDigits, {
export function formatPrice(n: MaybeNumberlike, minDigits = 2, maxPrecision = 4) {
maxPrecision = Math.max(minDigits, maxPrecision);
return formatNumber(n, minDigits, maxPrecision, {
style: 'currency',
currency: 'USD'
});
}

export function formatPriceChange(n: MaybeNumber): string {
export function formatPriceChange(n: MaybeNumberlike, minDigits = 1, maxPrecision = minDigits) {
n = toFloatingPoint(n);
if (!Number.isFinite(n)) return notFilledMarker;
return `${n > 0 ? '▲' : '▼'} ${formatPercent(Math.abs(n))}`;
return `${n > 0 ? '▲' : '▼'} ${formatPercent(Math.abs(n), minDigits, maxPrecision)}`;
}

/**
* Format number using an English thousand separation
*/
export function formatAmount(n: MaybeNumber): string {
export function formatAmount(n: MaybeNumberlike): string {
n = toFloatingPoint(n);
if (!Number.isFinite(n)) return notFilledMarker;

return n.toLocaleString('en');
Expand Down Expand Up @@ -199,17 +210,17 @@ export function formatShortAddress(address: MaybeString): string {
*
* Like average winning profit.
*/
export function formatPercent(n: MaybeNumber, minDigits = 1, maxDigits = minDigits) {
return formatNumber(n, minDigits, maxDigits, {
export function formatPercent(n: MaybeNumberlike, minDigits = 1, maxPrecision = minDigits) {
return formatNumber(n, minDigits, maxPrecision, {
style: 'percent'
});
}

/**
* Format interest rate value given as percent-form value
*/
export function formatInterestRate(n: MaybeNumber, minDigits = 2, maxDigits = minDigits) {
return formatPercent(n / 100, minDigits, maxDigits);
export function formatInterestRate(n: MaybeNumber, minDigits = 2, maxPrecision = minDigits) {
return formatPercent(n / 100, minDigits, maxPrecision);
}

/**
Expand Down Expand Up @@ -253,6 +264,19 @@ export function formatDuration(seconds: number): string {
return `${dayStr}${hours}h ${minutes}m`;
}

/**
* Formats minute and seconds timespans.
*
* Used formatting trade execution delays.
*
* E.g. 1m 2.34s
*/
export function formatDurationMinutesSeconds(seconds: number): string {
const minutes = Math.floor(seconds / MINUTE);
const secondsRemainder = (seconds - minutes * MINUTE).toFixed(2);
return `${minutes}m ${secondsRemainder}s`;
}

/**
* Formats the time duration string in day granularity.
*
Expand All @@ -271,3 +295,46 @@ export function formatDaysAgo(unixTimestamp: number): string {
export function formatValue(value: any): string {
return value?.toString() ?? notFilledMarker;
}

/**
* Formats the difference as minutes and seconds.
*
* E.g. 1m 2s
*
* @param before UNIX timestamp
* @param after UNIX timestamp
*/
export function formatTimeDiffMinutesSeconds(before: MaybeNumber, after: MaybeNumber): string {
if (!Number.isFinite(before) || !Number.isFinite(after)) {
return '---';
}

const duration = after - before;

return formatDurationMinutesSeconds(duration);
}

/**
* Formats the price difference between expected and executed
*
* E.g. +0.30%
*
* @param before Dollar value
* @param after Dollar value
*/
export function formatPriceDifference(before: MaybeNumber, after: MaybeNumber): string {
if (!Number.isFinite(before) || !Number.isFinite(after)) {
return '---';
}

const diff = ((after - before) / before) * 100;

const formatted = diff.toLocaleString('en-US', {
minimumSignificantDigits: 2,
maximumSignificantDigits: 2
});

const sign = diff > 0 ? '+' : '';

return `${sign}${formatted} %`;
}
4 changes: 2 additions & 2 deletions src/lib/trade-executor/helpers/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { PROFITABILITY_THRESHOLD } from './profit';
* @param minDigits - minimum number of digits to display (default = 2)
* @param maxPrecision - maximum number of significant digits (default = minDigits)
*/
export function formatTokenAmount(n: MaybeNumberOrString, minDigits = 2, maxPrecision = minDigits) {
export function formatTokenAmount(n: MaybeNumberlike, minDigits = 2, maxPrecision = minDigits) {
// Token quantities come from the API as strings. Because JavaScript numbers (IEEE 754 floats)
// cannot represent quantities accurately, some precision may be lost in the conversion.
return formatDollar(parseFloat(n), minDigits, maxPrecision, false);
return formatDollar(n, minDigits, maxPrecision, false);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/wallet/TokenBalance.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{#await data}
<Spinner size="2rem" color="hsla(var(--hsl-text-light))" />
{:then { symbol, formatted }}
<EntitySymbol slug={symbol.toLowerCase()} type="token">{formatNumber(Number(formatted), 2, 4)} {symbol}</EntitySymbol>
<EntitySymbol slug={symbol.toLowerCase()} type="token">{formatNumber(formatted, 2, 4)} {symbol}</EntitySymbol>
{:catch error}
<Alert size="xs">Error loading data</Alert>
{/await}
4 changes: 2 additions & 2 deletions src/lib/wallet/WalletBalance.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
{#await fetchNativeCurrency(address)}
<Spinner size="30" color="hsla(var(--hsl-text-light))" />
{:then balance}
{formatNumber(Number(balance.formatted), 2, 4)}
{formatNumber(balance.formatted, 2, 4)}
{/await}
</WalletInfoItem>

Expand All @@ -56,7 +56,7 @@
{#await fetchDenominationToken(address)}
<Spinner size="30" color="hsla(var(--hsl-text-light))" />
{:then balance}
{formatNumber(Number(balance.formatted), 2, 4)}
{formatNumber(balance.formatted, 2, 4)}
{:catch}
---
{/await}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@
table.column({
id: 'price',
header: 'Price',
accessor: ({ executed_price, planned_price }) => formatPrice(parseFloat(executed_price || planned_price), 5)
accessor: ({ executed_price, planned_price }) => formatPrice(executed_price ?? planned_price, 5)
}),
table.column({
id: 'value',
header: 'Value',
accessor: ({ executed_reserve, planned_reserve }) =>
formatPrice(parseFloat(executed_reserve || planned_reserve), 5)
accessor: ({ executed_reserve, planned_reserve }) => formatPrice(executed_reserve ?? planned_reserve, 5)
}),
table.column({
header: '',
Expand Down
Loading

0 comments on commit b60d6e0

Please sign in to comment.