-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #863 from tradingstrategy-ai/862-fix-profit-format…
…ting Fix profit / price change formatting issues
- Loading branch information
Showing
39 changed files
with
561 additions
and
520 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<script lang="ts"> | ||
import type { ChartCursor } from './ChartIQ.svelte'; | ||
import Marker from './Marker.svelte'; | ||
import Timestamp from '$lib/components/Timestamp.svelte'; | ||
type Props = { | ||
cursor: ChartCursor; | ||
withTime?: boolean; | ||
formatValue: Formatter<MaybeNumber>; | ||
}; | ||
let { cursor, withTime, formatValue }: Props = $props(); | ||
let { position, data } = $derived(cursor); | ||
let direction = $derived(Math.sign(data?.Close - data?.iqPrevClose || 0)); | ||
let directionClass = $derived(direction === 0 ? 'neutral' : direction > 0 ? 'bullish' : 'bearish'); | ||
</script> | ||
|
||
{#if data} | ||
<Marker x={position.DateX} y={position.CloseY} size={4.5} /> | ||
|
||
<div class="chart-tooltip" style:--x="{position.cx}px" style:--y="{position.CloseY}px"> | ||
<div class="content {directionClass}"> | ||
<div class="timestamp"> | ||
<Timestamp date={data.adjustedDate} {withTime} /> | ||
</div> | ||
<div class="value"> | ||
{formatValue(data.Close, 2)} | ||
</div> | ||
</div> | ||
</div> | ||
{/if} | ||
|
||
<style> | ||
.chart-tooltip { | ||
position: absolute; | ||
left: var(--x); | ||
top: var(--y); | ||
transform: translate(-50%, calc(-100% - var(--space-md))); | ||
/* opaque (no transparency) variant of chart container background color */ | ||
--background-opaque: color-mix(in srgb, var(--c-body), hsl(var(--hsl-box)) var(--box-1-alpha)); | ||
/* semi-opaque base background color (allows chart lines to partially bleed through) */ | ||
--background-base: color-mix(in srgb, transparent, var(--background-opaque) 75%); | ||
.content { | ||
display: grid; | ||
gap: 0.25rem; | ||
border-radius: var(--radius-sm); | ||
padding: 0.5rem 0.75rem; | ||
text-align: right; | ||
&.neutral { | ||
color: var(--c-text); | ||
background: color-mix(in srgb, var(--background-base), var(--c-box-4)); | ||
} | ||
&:is(.bullish, .bearish) { | ||
background: color-mix(in srgb, var(--background-base), currentColor 15%); | ||
} | ||
} | ||
.timestamp { | ||
font: var(--f-ui-sm-medium); | ||
letter-spacing: var(--f-ui-sm-spacing); | ||
color: var(--c-text-extra-light); | ||
} | ||
.value { | ||
font: var(--f-ui-md-medium); | ||
letter-spacing: var(--f-ui-md-spacing); | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<!-- | ||
@component | ||
Display profit/loss or price change value, formatted as percent, with color class | ||
and ▼ ◼︎ ▲ direction markers. | ||
The `boxed` prop displays the value with padding and a background color. | ||
The module also exports a `getProfitInfo` utility function for scenarios where | ||
using the component isn't practical. | ||
@example | ||
```svelte | ||
<Profitability of={profitValue} /> | ||
<Profitability of={profitValue} boxed> | ||
{someOtherValue} with profit/loss color coding | ||
</Profitability> | ||
<Profitability of={profitValue}> | ||
{#snippet children(profitInfo, getLabel)} | ||
{profitInfo} | ||
<span class="custom-label"> | ||
{getLabel('down', 'neutral', 'up')} | ||
</span> | ||
{/snippet} | ||
</Profitability> | ||
``` | ||
--> | ||
<script module lang="ts"> | ||
import { toFloatingPoint, isNumber, notFilledMarker } from '$lib/helpers/formatters'; | ||
export type ProfitInfo = ReturnType<typeof getProfitInfo>; | ||
/** | ||
* Get information used to display profit/loss or price change values. | ||
* | ||
* This encapsulates the core display logic of the Profitability component as | ||
* a utility function for scenarios where using the component isn't practical. | ||
* | ||
* The returned object provides a `getLabel` method, which accepts a tuple of | ||
* of ('down', 'neutral', 'up') labels and returns the appropriate option. | ||
* | ||
* The object also includes a `toString` method, enabling it to be used | ||
* directly in template or string interpolation contexts. | ||
* | ||
* @param n decimal representation profit or price change value | ||
*/ | ||
export function getProfitInfo(n: MaybeNumberlike) { | ||
const value = toFloatingPoint(n); | ||
const formatted = formatProfitability(value); | ||
const direction = getDirection(value, formatted); | ||
const getLabel = (...labels: string[]) => labels[direction + 1]; | ||
const marker = getLabel('▼', '◼︎', '▲'); | ||
const directionClass = getLabel('bearish', 'neutral', 'bullish'); | ||
const toString = () => (value === undefined ? notFilledMarker : `${marker} ${formatted}`); | ||
return { value, formatted, direction, marker, directionClass, getLabel, toString }; | ||
} | ||
function formatProfitability(value: number | undefined) { | ||
if (!isNumber(value)) return; | ||
return value.toLocaleString('en-us', { | ||
minimumFractionDigits: 1, | ||
maximumFractionDigits: Math.abs(value) < 0.001 ? 2 : 1, | ||
style: 'percent', | ||
signDisplay: 'never' | ||
}); | ||
} | ||
function getDirection(value: number | undefined, formatted: string | undefined) { | ||
if (!value || formatted === '0.0%') return 0; | ||
return Math.sign(value); | ||
} | ||
</script> | ||
|
||
<script lang="ts"> | ||
import type { Snippet } from 'svelte'; | ||
type Props = { | ||
of: MaybeNumberlike; | ||
boxed?: boolean; | ||
class?: string; | ||
children?: Snippet<[ProfitInfo, ProfitInfo['getLabel']]>; | ||
}; | ||
let { of, boxed = false, class: classes, children }: Props = $props(); | ||
let profitInfo = $derived(getProfitInfo(of)); | ||
</script> | ||
|
||
<span class="profitability {profitInfo.directionClass} {classes}" class:boxed class:default={!children}> | ||
{#if children} | ||
{@render children?.(profitInfo, profitInfo.getLabel)} | ||
{:else} | ||
{profitInfo} | ||
{/if} | ||
</span> | ||
|
||
<style> | ||
.profitability { | ||
&.default { | ||
white-space: nowrap; | ||
} | ||
&.boxed { | ||
border-radius: var(--radius-sm); | ||
padding: 0.5em 0.75em; | ||
&.neutral { | ||
background: var(--c-box-2); | ||
--background-hover: var(--c-box-4); | ||
} | ||
&:is(.bullish, .bearish) { | ||
background: color-mix(in srgb, transparent, currentColor 12%); | ||
--background-hover: color-mix(in srgb, transparent, currentColor 24%); | ||
} | ||
} | ||
} | ||
</style> |
Oops, something went wrong.