@funnest/charts 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +28 -2
- package/dist/chart.d.ts +300 -0
- package/dist/datafeed/combine.d.ts +7 -0
- package/dist/datafeed/latency.d.ts +5 -0
- package/dist/datafeed/socket.d.ts +60 -0
- package/dist/events.d.ts +7 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +3749 -0
- package/dist/indicators/definitions.d.ts +74 -0
- package/dist/indicators/manager.d.ts +26 -0
- package/dist/indicators/math.d.ts +21 -0
- package/dist/interaction.d.ts +81 -0
- package/dist/model/bar-store.d.ts +36 -0
- package/dist/model/price-scale.d.ts +35 -0
- package/dist/model/time-format.d.ts +50 -0
- package/dist/model/time-scale.d.ts +42 -0
- package/dist/objects/drawings.d.ts +52 -0
- package/dist/objects/magnet.d.ts +6 -0
- package/dist/objects/trading-sync.d.ts +72 -0
- package/dist/objects/trading.d.ts +76 -0
- package/dist/objects/types.d.ts +42 -0
- package/dist/price.d.ts +17 -0
- package/dist/realtime.d.ts +39 -0
- package/dist/render/canvas-layer.d.ts +13 -0
- package/dist/render/color-resolver.d.ts +14 -0
- package/dist/render/painter.d.ts +38 -0
- package/dist/render/renderer.d.ts +100 -0
- package/dist/resolution.d.ts +8 -0
- package/dist/session.d.ts +12 -0
- package/dist/settings.d.ts +40 -0
- package/dist/theme.d.ts +24 -0
- package/dist/types.d.ts +106 -0
- package/package.json +47 -3
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Locale } from '../model/time-format';
|
|
2
|
+
import type { Bar } from '../types';
|
|
3
|
+
export type IndicatorType = 'ma' | 'ema' | 'bb' | 'rsi' | 'macd';
|
|
4
|
+
/** Which price an indicator reads from each bar. */
|
|
5
|
+
export type PriceSource = 'close' | 'open' | 'high' | 'low' | 'hl2' | 'hlc3' | 'ohlc4';
|
|
6
|
+
export declare const PRICE_SOURCES: {
|
|
7
|
+
value: PriceSource;
|
|
8
|
+
label: Record<Locale, string>;
|
|
9
|
+
}[];
|
|
10
|
+
/** One moving-average line: period + source. Its color/width/visibility live in the config's `styles`. */
|
|
11
|
+
export interface MaLine {
|
|
12
|
+
period: number;
|
|
13
|
+
source: PriceSource;
|
|
14
|
+
}
|
|
15
|
+
export type IndicatorParamValue = number | string | MaLine[];
|
|
16
|
+
export type IndicatorParams = Record<string, IndicatorParamValue>;
|
|
17
|
+
export interface IndicatorParamSpec {
|
|
18
|
+
key: string;
|
|
19
|
+
label: Record<Locale, string>;
|
|
20
|
+
/** 'lines' = a list of {period, source} rows (moving averages). */
|
|
21
|
+
kind: 'number' | 'source' | 'lines';
|
|
22
|
+
min?: number;
|
|
23
|
+
max?: number;
|
|
24
|
+
step?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Per-series overrides set by the user. */
|
|
27
|
+
export interface SeriesStyle {
|
|
28
|
+
color?: string;
|
|
29
|
+
/** Line width in CSS px. */
|
|
30
|
+
width?: number;
|
|
31
|
+
visible?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface IndicatorSeries {
|
|
34
|
+
key: string;
|
|
35
|
+
label: string;
|
|
36
|
+
values: Float64Array;
|
|
37
|
+
/** Ignored for histograms, which are colored up/down by sign. */
|
|
38
|
+
color: string;
|
|
39
|
+
width: number;
|
|
40
|
+
visible: boolean;
|
|
41
|
+
kind: 'line' | 'histogram';
|
|
42
|
+
}
|
|
43
|
+
export interface IndicatorOutput {
|
|
44
|
+
series: IndicatorSeries[];
|
|
45
|
+
/** Shaded area between two series (Bollinger band). */
|
|
46
|
+
fill?: {
|
|
47
|
+
upper: string;
|
|
48
|
+
lower: string;
|
|
49
|
+
color: string;
|
|
50
|
+
};
|
|
51
|
+
/** Horizontal reference lines (RSI 30/70). */
|
|
52
|
+
levels?: number[];
|
|
53
|
+
/** Fixed value range instead of auto-scaling (RSI 0–100). */
|
|
54
|
+
range?: [number, number];
|
|
55
|
+
/** Decimals for labels; null = the symbol's price precision. */
|
|
56
|
+
precision: number | null;
|
|
57
|
+
}
|
|
58
|
+
export interface IndicatorDefinition {
|
|
59
|
+
type: IndicatorType;
|
|
60
|
+
name: Record<Locale, string>;
|
|
61
|
+
description: Record<Locale, string>;
|
|
62
|
+
/** Drawn over the candles (true) or in its own pane below (false). */
|
|
63
|
+
overlay: boolean;
|
|
64
|
+
params: IndicatorParamSpec[];
|
|
65
|
+
defaults: IndicatorParams;
|
|
66
|
+
title(params: IndicatorParams): string;
|
|
67
|
+
compute(bars: readonly Bar[], params: IndicatorParams): IndicatorOutput;
|
|
68
|
+
}
|
|
69
|
+
/** Distinct line colors that avoid the up/down candle colors. */
|
|
70
|
+
export declare const SERIES_COLORS: string[];
|
|
71
|
+
export declare function priceSource(bars: readonly Bar[], source?: PriceSource): Float64Array;
|
|
72
|
+
/** Reads `lines`, accepting the legacy `periods: number[]` shape too. */
|
|
73
|
+
export declare function maLines(p: IndicatorParams): MaLine[];
|
|
74
|
+
export declare const INDICATORS: Record<IndicatorType, IndicatorDefinition>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Bar } from '../types';
|
|
2
|
+
import { type IndicatorOutput, type IndicatorParams, type IndicatorType, type SeriesStyle } from './definitions';
|
|
3
|
+
export interface IndicatorConfig {
|
|
4
|
+
id: string;
|
|
5
|
+
type: IndicatorType;
|
|
6
|
+
params: IndicatorParams;
|
|
7
|
+
/** Hidden indicators keep their pane and legend but draw nothing. Default true. */
|
|
8
|
+
visible?: boolean;
|
|
9
|
+
/** Per-series color/width/visibility, keyed by series key (MA rows: 'l0', 'l1', …). */
|
|
10
|
+
styles?: Record<string, SeriesStyle>;
|
|
11
|
+
}
|
|
12
|
+
export type IndicatorPatch = Partial<Pick<IndicatorConfig, 'params' | 'visible' | 'styles'>>;
|
|
13
|
+
/** Indicator instances plus a cache of computed series keyed by data version + settings. */
|
|
14
|
+
export declare class IndicatorManager {
|
|
15
|
+
list: IndicatorConfig[];
|
|
16
|
+
private readonly cache;
|
|
17
|
+
add(type: IndicatorType, params?: IndicatorParams): string;
|
|
18
|
+
remove(id: string): boolean;
|
|
19
|
+
update(id: string, patch: IndicatorPatch): boolean;
|
|
20
|
+
set(configs: readonly IndicatorConfig[]): void;
|
|
21
|
+
snapshot(): IndicatorConfig[];
|
|
22
|
+
get overlays(): IndicatorConfig[];
|
|
23
|
+
get panes(): IndicatorConfig[];
|
|
24
|
+
/** Computed series with user styles applied; recomputed only when data or settings change. */
|
|
25
|
+
output(cfg: IndicatorConfig, bars: readonly Bar[], dataVersion: number): IndicatorOutput;
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indicator math over plain number arrays. Outputs are aligned with the input
|
|
3
|
+
* (same length); positions without enough data are NaN.
|
|
4
|
+
*/
|
|
5
|
+
export declare function sma(src: ArrayLike<number>, period: number): Float64Array;
|
|
6
|
+
/** EMA seeded with the SMA of the first `period` valid values (leading NaNs are skipped). */
|
|
7
|
+
export declare function ema(src: ArrayLike<number>, period: number): Float64Array;
|
|
8
|
+
/** Rolling population standard deviation. */
|
|
9
|
+
export declare function stdev(src: ArrayLike<number>, period: number): Float64Array;
|
|
10
|
+
export declare function bollinger(src: ArrayLike<number>, period: number, mult: number): {
|
|
11
|
+
middle: Float64Array<ArrayBufferLike>;
|
|
12
|
+
upper: Float64Array<ArrayBuffer>;
|
|
13
|
+
lower: Float64Array<ArrayBuffer>;
|
|
14
|
+
};
|
|
15
|
+
/** Wilder's RSI. */
|
|
16
|
+
export declare function rsi(src: ArrayLike<number>, period: number): Float64Array;
|
|
17
|
+
export declare function macd(src: ArrayLike<number>, fast: number, slow: number, signal: number): {
|
|
18
|
+
macd: Float64Array<ArrayBuffer>;
|
|
19
|
+
signal: Float64Array<ArrayBufferLike>;
|
|
20
|
+
histogram: Float64Array<ArrayBuffer>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ObjectHit } from './objects/types';
|
|
2
|
+
import type { PaneId } from './render/renderer';
|
|
3
|
+
export interface HitRegion {
|
|
4
|
+
area: 'plot' | 'priceAxis' | 'timeAxis' | 'none';
|
|
5
|
+
pane: PaneId | null;
|
|
6
|
+
}
|
|
7
|
+
/** What the interaction layer needs from the chart. Kept narrow so gestures stay testable. */
|
|
8
|
+
export interface InteractionHost {
|
|
9
|
+
hitTest(x: number, y: number): HitRegion;
|
|
10
|
+
plotWidth(): number;
|
|
11
|
+
setCrosshair(x: number, y: number, source: 'mouse' | 'touch'): void;
|
|
12
|
+
clearCrosshair(): void;
|
|
13
|
+
panTime(dx: number): void;
|
|
14
|
+
zoomTime(anchorX: number, factor: number): void;
|
|
15
|
+
/** Pans the price axis only if it is in manual (non-auto) mode. */
|
|
16
|
+
panPrice(pane: PaneId, dy: number): void;
|
|
17
|
+
scalePrice(pane: PaneId, factor: number): void;
|
|
18
|
+
resetPriceScale(pane: PaneId): void;
|
|
19
|
+
resetView(): void;
|
|
20
|
+
/** A drawing tool is armed: presses create drawings instead of panning. */
|
|
21
|
+
toolActive(): boolean;
|
|
22
|
+
hitObject(x: number, y: number, touch: boolean): ObjectHit | null;
|
|
23
|
+
drawAt(x: number, y: number): void;
|
|
24
|
+
drawPreview(x: number, y: number): void;
|
|
25
|
+
drawRelease(x: number, y: number, moved: boolean): void;
|
|
26
|
+
objectDown(hit: ObjectHit, x: number, y: number): void;
|
|
27
|
+
objectMove(x: number, y: number): void;
|
|
28
|
+
objectUp(moved: boolean): void;
|
|
29
|
+
/** Mouse hover over an object (or nothing) — e.g. to show a fill tooltip. */
|
|
30
|
+
hoverObject(hit: ObjectHit | null): void;
|
|
31
|
+
clearSelection(): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Unified mouse / touch / pen gestures on top of Pointer Events.
|
|
35
|
+
*
|
|
36
|
+
* Desktop: hover = crosshair, drag = pan, wheel = zoom (trackpad pinch too), horizontal wheel = pan,
|
|
37
|
+
* axis drag = axis scale, double-click = reset.
|
|
38
|
+
* Touch: drag = pan with inertia, pinch = zoom, long-press = crosshair (magnet to close),
|
|
39
|
+
* double-tap = reset. Vertical swipes are left to the page (`touch-action: pan-y`).
|
|
40
|
+
*/
|
|
41
|
+
export declare class Interaction {
|
|
42
|
+
private readonly host;
|
|
43
|
+
private readonly el;
|
|
44
|
+
private readonly pointers;
|
|
45
|
+
private mode;
|
|
46
|
+
private touch;
|
|
47
|
+
private moved;
|
|
48
|
+
private start;
|
|
49
|
+
private last;
|
|
50
|
+
private region;
|
|
51
|
+
private longPressTimer;
|
|
52
|
+
private samples;
|
|
53
|
+
private kineticFrame;
|
|
54
|
+
private pinchDistance;
|
|
55
|
+
private pinchMid;
|
|
56
|
+
private lastTap;
|
|
57
|
+
private readonly cleanups;
|
|
58
|
+
constructor(host: InteractionHost, el: HTMLElement);
|
|
59
|
+
destroy(): void;
|
|
60
|
+
private listen;
|
|
61
|
+
private local;
|
|
62
|
+
private onPointerDown;
|
|
63
|
+
private onLongPress;
|
|
64
|
+
private onPointerMove;
|
|
65
|
+
private onPointerUp;
|
|
66
|
+
private onPointerCancel;
|
|
67
|
+
private onPointerLeave;
|
|
68
|
+
private hover;
|
|
69
|
+
private pan;
|
|
70
|
+
private handleTap;
|
|
71
|
+
private pinchPoints;
|
|
72
|
+
private beginPinch;
|
|
73
|
+
private updatePinch;
|
|
74
|
+
private endPinch;
|
|
75
|
+
private startKinetic;
|
|
76
|
+
private stopKinetic;
|
|
77
|
+
private onWheel;
|
|
78
|
+
private onDoubleClick;
|
|
79
|
+
private onTouchMove;
|
|
80
|
+
private onContextMenu;
|
|
81
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Bar } from '../types';
|
|
2
|
+
export type UpsertResult = 'append' | 'update' | 'ignored';
|
|
3
|
+
/** Time-ordered bar series. Index 0 is the oldest bar. */
|
|
4
|
+
export declare class BarStore {
|
|
5
|
+
private items;
|
|
6
|
+
get bars(): readonly Bar[];
|
|
7
|
+
get length(): number;
|
|
8
|
+
at(index: number): Bar | undefined;
|
|
9
|
+
first(): Bar | undefined;
|
|
10
|
+
last(): Bar | undefined;
|
|
11
|
+
set(bars: readonly Bar[]): void;
|
|
12
|
+
/** Adds older bars in front. Bars not strictly older than the current first bar are dropped. Returns the count added. */
|
|
13
|
+
prepend(bars: readonly Bar[]): number;
|
|
14
|
+
/**
|
|
15
|
+
* Newer than the last bar → append. Same time as any existing bar → update in place
|
|
16
|
+
* (late/out-of-order WebSocket messages can still correct a closed bar).
|
|
17
|
+
* Older time that doesn't exist → ignored (use `merge` to fill gaps).
|
|
18
|
+
*/
|
|
19
|
+
upsert(bar: Bar): UpsertResult;
|
|
20
|
+
/**
|
|
21
|
+
* Merges a batch that may overlap, extend, or fill gaps in the series
|
|
22
|
+
* (e.g. bars re-fetched after a WebSocket reconnect). Incoming bars win on equal time.
|
|
23
|
+
*/
|
|
24
|
+
merge(bars: readonly Bar[]): void;
|
|
25
|
+
/** Index of the bar containing `time` (largest bar.time <= time); -1 if before the first bar. */
|
|
26
|
+
indexAtOrBefore(time: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Fractional index for any timestamp: interpolated between bars, extrapolated
|
|
29
|
+
* beyond either end using `interval` (ms per bar). Lets drawings anchor to time.
|
|
30
|
+
*/
|
|
31
|
+
indexFloatOfTime(time: number, interval: number): number;
|
|
32
|
+
/** Inverse of `indexFloatOfTime`. */
|
|
33
|
+
timeOfIndexFloat(index: number, interval: number): number;
|
|
34
|
+
/** Binary search for an exact timestamp; -1 when absent. */
|
|
35
|
+
indexOfTime(time: number): number;
|
|
36
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface PriceScaleMargins {
|
|
2
|
+
/** Fraction of pane height kept empty above the max price. */
|
|
3
|
+
top: number;
|
|
4
|
+
/** Fraction of pane height kept empty below the min price. */
|
|
5
|
+
bottom: number;
|
|
6
|
+
}
|
|
7
|
+
/** Vertical scale for one pane. y is relative to the pane's top. */
|
|
8
|
+
export declare class PriceScale {
|
|
9
|
+
readonly margins: PriceScaleMargins;
|
|
10
|
+
height: number;
|
|
11
|
+
/** Pixels reserved at the top for overlays (e.g. a legend); margins apply below it. */
|
|
12
|
+
insetTop: number;
|
|
13
|
+
autoScale: boolean;
|
|
14
|
+
private lo;
|
|
15
|
+
private hi;
|
|
16
|
+
constructor(margins: PriceScaleMargins);
|
|
17
|
+
get min(): number;
|
|
18
|
+
get max(): number;
|
|
19
|
+
setRange(min: number, max: number): void;
|
|
20
|
+
/** Height left after the inset (never less than half the pane, so tiny panes still show data). */
|
|
21
|
+
private get available();
|
|
22
|
+
private get innerTop();
|
|
23
|
+
private get innerHeight();
|
|
24
|
+
priceToY(price: number): number;
|
|
25
|
+
yToPrice(y: number): number;
|
|
26
|
+
/** Scales around the center. factor > 1 shows a wider price range. Disables auto-scale. */
|
|
27
|
+
zoom(factor: number): void;
|
|
28
|
+
/** Moves the content by `dy` pixels (positive = content moves down). Disables auto-scale. */
|
|
29
|
+
pan(dy: number): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* "Nice" tick values (1/2/2.5/5 × 10ⁿ) covering [min, max] with at most ~maxCount ticks.
|
|
33
|
+
* `minStep` (e.g. the instrument's tick size) prevents sub-tick grid lines.
|
|
34
|
+
*/
|
|
35
|
+
export declare function niceTicks(min: number, max: number, maxCount: number, minStep?: number): number[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Bar } from '../types';
|
|
2
|
+
import type { TimeScale } from './time-scale';
|
|
3
|
+
export type Locale = 'ko' | 'en';
|
|
4
|
+
export interface TimeParts {
|
|
5
|
+
year: number;
|
|
6
|
+
/** 1–12 */
|
|
7
|
+
month: number;
|
|
8
|
+
day: number;
|
|
9
|
+
hour: number;
|
|
10
|
+
minute: number;
|
|
11
|
+
second: number;
|
|
12
|
+
/** 0 = Sunday */
|
|
13
|
+
weekday: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function defaultTimeZone(): string;
|
|
16
|
+
/** Timezone-aware calendar parts with a per-timestamp cache (Intl is slow per call). */
|
|
17
|
+
export declare class TimeFormatter {
|
|
18
|
+
readonly timeZone: string;
|
|
19
|
+
readonly locale: Locale;
|
|
20
|
+
private readonly dtf;
|
|
21
|
+
private readonly cache;
|
|
22
|
+
constructor(timeZone: string, locale: Locale);
|
|
23
|
+
parts(time: number): TimeParts;
|
|
24
|
+
/** Full label for the crosshair, e.g. "2026.11.09 (월) 14:30". */
|
|
25
|
+
crosshairLabel(time: number, intraday: boolean): string;
|
|
26
|
+
tickLabel(p: TimeParts, weight: number): string;
|
|
27
|
+
}
|
|
28
|
+
export declare const TickWeight: {
|
|
29
|
+
readonly Year: 60;
|
|
30
|
+
readonly Month: 50;
|
|
31
|
+
readonly Day: 40;
|
|
32
|
+
readonly Hour: 30;
|
|
33
|
+
readonly Minute30: 25;
|
|
34
|
+
readonly Minute15: 22;
|
|
35
|
+
readonly Minute5: 20;
|
|
36
|
+
readonly Minute: 10;
|
|
37
|
+
readonly None: 0;
|
|
38
|
+
};
|
|
39
|
+
/** How "important" the boundary between two consecutive bars is (bigger = coarser unit changed). */
|
|
40
|
+
export declare function boundaryWeight(prev: TimeParts | null, cur: TimeParts): number;
|
|
41
|
+
export interface TimeTick {
|
|
42
|
+
index: number;
|
|
43
|
+
x: number;
|
|
44
|
+
label: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Picks time-axis labels: most significant boundaries first (year > month > day > hour…),
|
|
48
|
+
* keeping at least `minGap` px between labels.
|
|
49
|
+
*/
|
|
50
|
+
export declare function computeTimeTicks(bars: readonly Bar[], timeScale: TimeScale, formatter: TimeFormatter, minGap: number): TimeTick[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface TimeScaleOptions {
|
|
2
|
+
/** Pixels per bar. */
|
|
3
|
+
barSpacing: number;
|
|
4
|
+
minBarSpacing: number;
|
|
5
|
+
maxBarSpacing: number;
|
|
6
|
+
/** Empty space right of the last bar, in bars. */
|
|
7
|
+
rightOffset: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT_TIME_SCALE_OPTIONS: TimeScaleOptions;
|
|
10
|
+
export interface IndexRange {
|
|
11
|
+
from: number;
|
|
12
|
+
to: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Index-based horizontal scale: bars are evenly spaced regardless of time gaps
|
|
16
|
+
* (closed market hours collapse). `rightOffset` is stored relative to the last
|
|
17
|
+
* bar so appending keeps the realtime edge pinned and prepending history keeps
|
|
18
|
+
* the view still.
|
|
19
|
+
*/
|
|
20
|
+
export declare class TimeScale {
|
|
21
|
+
readonly options: TimeScaleOptions;
|
|
22
|
+
width: number;
|
|
23
|
+
barCount: number;
|
|
24
|
+
barSpacing: number;
|
|
25
|
+
rightOffset: number;
|
|
26
|
+
constructor(options?: TimeScaleOptions);
|
|
27
|
+
/** Logical (fractional) bar index at the right edge. */
|
|
28
|
+
get rightIndex(): number;
|
|
29
|
+
/** Center x of bar `index`. */
|
|
30
|
+
indexToX(index: number): number;
|
|
31
|
+
xToIndexFloat(x: number): number;
|
|
32
|
+
xToIndex(x: number): number;
|
|
33
|
+
/** Visible bar indices clamped to existing data. `to < from` when nothing is visible. */
|
|
34
|
+
visibleRange(): IndexRange;
|
|
35
|
+
pan(dx: number): void;
|
|
36
|
+
/** Zooms by `factor` keeping the bar under `anchorX` fixed. */
|
|
37
|
+
zoom(anchorX: number, factor: number): void;
|
|
38
|
+
reset(): void;
|
|
39
|
+
fitContent(): void;
|
|
40
|
+
/** Keeps at least a couple of bars on screen. */
|
|
41
|
+
clamp(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AxisBadge, type ObjectHit, type ObjectRenderContext, type PaneCoords } from './types';
|
|
2
|
+
export type DrawingType = 'trendline' | 'horizontal' | 'fibonacci';
|
|
3
|
+
export interface DrawingPoint {
|
|
4
|
+
/** UTC ms — anchored to time, so drawings survive history loads and resolution changes. */
|
|
5
|
+
time: number;
|
|
6
|
+
price: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Drawing {
|
|
9
|
+
id: string;
|
|
10
|
+
type: DrawingType;
|
|
11
|
+
points: DrawingPoint[];
|
|
12
|
+
color?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const DRAWING_POINTS: Record<DrawingType, number>;
|
|
15
|
+
export declare const FIB_LEVELS: number[];
|
|
16
|
+
export type DrawingsChange = 'preview' | 'commit' | 'tool' | 'selection';
|
|
17
|
+
/**
|
|
18
|
+
* Owns drawings: creation (click-click or drag), selection, handle/body dragging,
|
|
19
|
+
* hit testing and rendering. Pure state + canvas; pointer routing lives in Interaction.
|
|
20
|
+
*/
|
|
21
|
+
export declare class DrawingManager {
|
|
22
|
+
private readonly onChange;
|
|
23
|
+
drawings: Drawing[];
|
|
24
|
+
tool: DrawingType | null;
|
|
25
|
+
selectedId: string | null;
|
|
26
|
+
private draft;
|
|
27
|
+
private drag;
|
|
28
|
+
constructor(onChange: (change: DrawingsChange) => void);
|
|
29
|
+
get selected(): Drawing | null;
|
|
30
|
+
setTool(tool: DrawingType | null): void;
|
|
31
|
+
select(id: string | null): void;
|
|
32
|
+
set(drawings: readonly Drawing[]): void;
|
|
33
|
+
update(id: string, patch: Partial<Omit<Drawing, 'id'>>): void;
|
|
34
|
+
remove(id: string): void;
|
|
35
|
+
clear(): void;
|
|
36
|
+
/** Esc: abort an in-progress drawing, else drop the tool, else deselect. */
|
|
37
|
+
cancel(): void;
|
|
38
|
+
createAt(c: PaneCoords, x: number, y: number): void;
|
|
39
|
+
/** Moves the not-yet-placed points (live preview while hovering or dragging). */
|
|
40
|
+
previewAt(c: PaneCoords, x: number, y: number): void;
|
|
41
|
+
/** Pointer released: a drag since the first point places the next one (drag-to-draw). */
|
|
42
|
+
releaseAt(c: PaneCoords, x: number, y: number, moved: boolean): void;
|
|
43
|
+
private finish;
|
|
44
|
+
hitTest(c: PaneCoords, x: number, y: number, tolerance: number): ObjectHit | null;
|
|
45
|
+
beginDrag(c: PaneCoords, id: string, part: 'body' | number, x: number, y: number): void;
|
|
46
|
+
dragTo(c: PaneCoords, x: number, y: number): void;
|
|
47
|
+
endDrag(moved: boolean): void;
|
|
48
|
+
private screenPoints;
|
|
49
|
+
private distance;
|
|
50
|
+
render(rc: ObjectRenderContext, badges: AxisBadge[]): void;
|
|
51
|
+
private renderOne;
|
|
52
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Bar } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Magnet mode: the bar's open/high/low/close closest to pointer y (in pixels).
|
|
4
|
+
* Used so drawing points land exactly on candle extremes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function snapToOhlc(bar: Bar, y: number, priceToY: (price: number) => number): number;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SymbolInfo, Unsubscribe } from '../types';
|
|
2
|
+
import type { ExecutionMarker, TradingLayer } from './trading';
|
|
3
|
+
export interface PositionInfo {
|
|
4
|
+
price: number;
|
|
5
|
+
quantity: number;
|
|
6
|
+
side: 'long' | 'short';
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface OrderInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
price: number;
|
|
12
|
+
quantity: number;
|
|
13
|
+
side: 'buy' | 'sell';
|
|
14
|
+
/** e.g. "지정가", "STOP". */
|
|
15
|
+
label?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface Execution extends ExecutionMarker {
|
|
18
|
+
/** Used to de-duplicate fills that arrive both from history and the live stream. */
|
|
19
|
+
id?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface TradingCallbacks {
|
|
22
|
+
onExecution(execution: Execution): void;
|
|
23
|
+
onPositions(positions: PositionInfo[]): void;
|
|
24
|
+
onOrders(orders: OrderInfo[]): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The user's account data for the chart. Implement this once against your own backend
|
|
28
|
+
* (auth, broker API…); the chart handles loading per symbol, paging fills as history
|
|
29
|
+
* scrolls in, live updates and all the drawing/interaction.
|
|
30
|
+
*/
|
|
31
|
+
export interface TradingAdapter {
|
|
32
|
+
/** Fills within [from, to] (UTC ms). Called when a symbol loads and as older history pages in. */
|
|
33
|
+
getExecutions?(symbol: SymbolInfo, range: {
|
|
34
|
+
from: number;
|
|
35
|
+
to: number;
|
|
36
|
+
}): Promise<Execution[]>;
|
|
37
|
+
/** Open position(s) → average-price lines with live P&L. */
|
|
38
|
+
getPositions?(symbol: SymbolInfo): Promise<PositionInfo[]>;
|
|
39
|
+
/** Working orders → order lines. */
|
|
40
|
+
getOrders?(symbol: SymbolInfo): Promise<OrderInfo[]>;
|
|
41
|
+
/** Live account updates. Pass full position/order lists; fills one at a time. */
|
|
42
|
+
subscribe?(symbol: SymbolInfo, callbacks: TradingCallbacks): Unsubscribe;
|
|
43
|
+
/** Makes order lines draggable. Resolve `false` (or reject) to snap the line back. */
|
|
44
|
+
modifyOrder?(order: OrderInfo, price: number): void | boolean | Promise<void | boolean>;
|
|
45
|
+
/** Shows an × on order lines. Without `subscribe`, the line is removed once this resolves. */
|
|
46
|
+
cancelOrder?(order: OrderInfo): void | Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/** Mirrors a TradingAdapter's state for the current symbol into the TradingLayer. */
|
|
49
|
+
export declare class TradingSync {
|
|
50
|
+
private readonly layer;
|
|
51
|
+
private readonly onError;
|
|
52
|
+
adapter: TradingAdapter | null;
|
|
53
|
+
private token;
|
|
54
|
+
private symbol;
|
|
55
|
+
private unsubscribe;
|
|
56
|
+
private positions;
|
|
57
|
+
private readonly orders;
|
|
58
|
+
private readonly executions;
|
|
59
|
+
private loadedFrom;
|
|
60
|
+
constructor(layer: TradingLayer, onError: (error: unknown) => void);
|
|
61
|
+
/** (Re)loads everything for a symbol whose bars start at `from`. */
|
|
62
|
+
load(symbol: SymbolInfo, from: number): Promise<void>;
|
|
63
|
+
/** Fetches fills for newly loaded older history (bars now start at `from`). */
|
|
64
|
+
loadOlder(from: number): Promise<void>;
|
|
65
|
+
reset(): void;
|
|
66
|
+
/** Drops the adapter's lines and stops updates (e.g. adapter removed, chart destroyed). */
|
|
67
|
+
detach(): void;
|
|
68
|
+
private setPositions;
|
|
69
|
+
private setOrders;
|
|
70
|
+
private orderOptions;
|
|
71
|
+
private addExecutions;
|
|
72
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Bar } from '../types';
|
|
2
|
+
import { type AxisBadge, type ObjectHit, type ObjectRenderContext } from './types';
|
|
3
|
+
export interface PositionLineOptions {
|
|
4
|
+
/** Average entry price. */
|
|
5
|
+
price: number;
|
|
6
|
+
quantity: number;
|
|
7
|
+
side: 'long' | 'short';
|
|
8
|
+
/** Overrides the default "평균단가" / "Avg" text. */
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface OrderLineOptions {
|
|
12
|
+
price: number;
|
|
13
|
+
quantity: number;
|
|
14
|
+
side: 'buy' | 'sell';
|
|
15
|
+
/** Extra text such as "지정가" or "STOP". */
|
|
16
|
+
label?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Called when the user drags the line to a new (tick-snapped) price. The line moves
|
|
19
|
+
* immediately; return `false` (or reject) to snap it back, e.g. when the broker refuses.
|
|
20
|
+
*/
|
|
21
|
+
onMove?: (price: number) => void | boolean | Promise<void | boolean>;
|
|
22
|
+
/** Shows an × button on the label. */
|
|
23
|
+
onCancel?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export interface ExecutionMarker {
|
|
26
|
+
time: number;
|
|
27
|
+
price: number;
|
|
28
|
+
side: 'buy' | 'sell';
|
|
29
|
+
quantity?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Fills on the same bar and side, shown as one marker. */
|
|
32
|
+
export interface ExecutionGroup {
|
|
33
|
+
key: string;
|
|
34
|
+
side: 'buy' | 'sell';
|
|
35
|
+
count: number;
|
|
36
|
+
quantity: number;
|
|
37
|
+
/** Quantity-weighted average price (plain average when quantities are missing). */
|
|
38
|
+
averagePrice: number;
|
|
39
|
+
/** Earliest fill time in the group. */
|
|
40
|
+
time: number;
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
}
|
|
44
|
+
export interface LineHandle<T> {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
update(patch: Partial<T>): void;
|
|
47
|
+
remove(): void;
|
|
48
|
+
}
|
|
49
|
+
export interface TradingLabels {
|
|
50
|
+
buy: string;
|
|
51
|
+
sell: string;
|
|
52
|
+
position: string;
|
|
53
|
+
}
|
|
54
|
+
/** Position (average price) lines, draggable order lines and execution markers. */
|
|
55
|
+
export declare class TradingLayer {
|
|
56
|
+
private readonly onChange;
|
|
57
|
+
private readonly positions;
|
|
58
|
+
private readonly orders;
|
|
59
|
+
private executions;
|
|
60
|
+
private labelRects;
|
|
61
|
+
private drag;
|
|
62
|
+
private groups;
|
|
63
|
+
constructor(onChange: () => void);
|
|
64
|
+
addPosition(options: PositionLineOptions): LineHandle<PositionLineOptions>;
|
|
65
|
+
addOrder(options: OrderLineOptions): LineHandle<OrderLineOptions>;
|
|
66
|
+
setExecutions(markers: readonly ExecutionMarker[]): void;
|
|
67
|
+
private handle;
|
|
68
|
+
get executionList(): readonly ExecutionMarker[];
|
|
69
|
+
executionGroup(key: string): ExecutionGroup | undefined;
|
|
70
|
+
hitTest(priceToY: (p: number) => number, x: number, y: number, tolerance: number): ObjectHit | null;
|
|
71
|
+
beginDrag(id: string): void;
|
|
72
|
+
dragTo(price: number): void;
|
|
73
|
+
endDrag(): void;
|
|
74
|
+
cancel(id: string): void;
|
|
75
|
+
render({ painter: p, coords: c, palette }: ObjectRenderContext, bars: readonly Bar[], barIndexAt: (time: number) => number, indexToX: (index: number) => number, labels: TradingLabels, badges: AxisBadge[]): void;
|
|
76
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Painter } from '../render/painter';
|
|
2
|
+
import type { Palette } from '../render/renderer';
|
|
3
|
+
/** Coordinate helpers for objects living in the main price pane. */
|
|
4
|
+
export interface PaneCoords {
|
|
5
|
+
plotWidth: number;
|
|
6
|
+
height: number;
|
|
7
|
+
timeToX(time: number): number;
|
|
8
|
+
/** Point under (x, y): time snapped to the nearest bar (extrapolated into the future), price to the tick size. */
|
|
9
|
+
pointAt(x: number, y: number): {
|
|
10
|
+
time: number;
|
|
11
|
+
price: number;
|
|
12
|
+
};
|
|
13
|
+
priceToY(price: number): number;
|
|
14
|
+
yToPrice(y: number): number;
|
|
15
|
+
snapPrice(price: number): number;
|
|
16
|
+
formatPrice(price: number): string;
|
|
17
|
+
}
|
|
18
|
+
export interface ObjectRenderContext {
|
|
19
|
+
painter: Painter;
|
|
20
|
+
coords: PaneCoords;
|
|
21
|
+
palette: Palette;
|
|
22
|
+
}
|
|
23
|
+
export type ObjectHit = {
|
|
24
|
+
kind: 'drawing';
|
|
25
|
+
id: string;
|
|
26
|
+
part: 'body' | number;
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'order';
|
|
29
|
+
id: string;
|
|
30
|
+
part: 'line' | 'cancel';
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'execution';
|
|
33
|
+
key: string;
|
|
34
|
+
};
|
|
35
|
+
/** A badge to draw on the price axis (collected so they render above axis labels). */
|
|
36
|
+
export interface AxisBadge {
|
|
37
|
+
y: number;
|
|
38
|
+
text: string;
|
|
39
|
+
bg: string;
|
|
40
|
+
fg: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const newId: (prefix: string) => string;
|
package/dist/price.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TickSize } from './types';
|
|
2
|
+
/** Number of decimal places in a finite number (handles exponent notation like 1e-8). */
|
|
3
|
+
export declare function decimalsOf(value: number): number;
|
|
4
|
+
export declare function resolveTickSize(tickSize: TickSize, price: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Round `price` to the nearest multiple of the tick size, avoiding float drift
|
|
7
|
+
* (e.g. 0.1 + 0.2) by working in scaled integers.
|
|
8
|
+
*/
|
|
9
|
+
export declare function roundToTick(price: number, tickSize: TickSize): number;
|
|
10
|
+
/** KRX equity tick size table (effective 2023-01-25). */
|
|
11
|
+
export declare function krxTickSize(price: number): number;
|
|
12
|
+
export interface FormatPriceOptions {
|
|
13
|
+
thousandsSeparator?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function formatPrice(price: number, precision: number, { thousandsSeparator }?: FormatPriceOptions): string;
|
|
16
|
+
/** Compact number for volumes: 1234567 → "123.5만" (ko) / "1.23M" (en). */
|
|
17
|
+
export declare function formatCompact(value: number, locale?: 'ko' | 'en'): string;
|