@funnest/charts 0.6.5 → 0.6.7
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/dist/chart.d.ts +5 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +95 -20
- package/dist/objects/trading.d.ts +25 -0
- package/dist/render/painter.d.ts +2 -0
- package/package.json +1 -1
package/dist/chart.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { TimeScale, type IndexRange, type TimeScaleOptions } from './model/time-
|
|
|
3
3
|
import type { IndicatorDefinition, IndicatorParams, IndicatorType } from './indicators/definitions';
|
|
4
4
|
import { type IndicatorConfig, type IndicatorPatch } from './indicators/manager';
|
|
5
5
|
import { type Drawing, type DrawingDefinition, type DrawingType } from './objects/drawings';
|
|
6
|
-
import { type AlertLineOptions, type ExecutionMarker, type LineHandle, type OrderLineOptions, type PositionLineOptions } from './objects/trading';
|
|
6
|
+
import { type AlertLineOptions, type ExecutionMarker, type LineHandle, type OrderLineOptions, type PositionLineOptions, type TradingLabelOptions } from './objects/trading';
|
|
7
7
|
import { type ChartSettings, type ChartState } from './settings';
|
|
8
8
|
import { type TradingAdapter } from './objects/trading-sync';
|
|
9
9
|
import { type ChartType, type CrosshairInfo, type PaneId } from './render/renderer';
|
|
@@ -411,8 +411,11 @@ export declare class Chart {
|
|
|
411
411
|
addOrderLine(options: OrderLineOptions): LineHandle<OrderLineOptions>;
|
|
412
412
|
/** Price alert line (dotted, gray, "알림" label). Pass `onMove` to make it draggable and `onCancel` for an ×. */
|
|
413
413
|
addAlertLine(options: AlertLineOptions): LineHandle<AlertLineOptions>;
|
|
414
|
-
/** Fill markers (▲ buy under the bar, ▼ sell above). Replaces the previous set. */
|
|
414
|
+
/** Fill markers (▲ buy under the bar, ▼ sell above; ⚡ at the price for liquidations). Replaces the previous set. */
|
|
415
415
|
setExecutions(markers: readonly ExecutionMarker[]): void;
|
|
416
|
+
/** What position/order/alert line labels show and where (direction, price, size, P&L, ×, alignment, line width). */
|
|
417
|
+
setTradingLabels(patch: Partial<TradingLabelOptions>): void;
|
|
418
|
+
get tradingLabels(): TradingLabelOptions;
|
|
416
419
|
setTheme(theme: Partial<ThemeTokens>): void;
|
|
417
420
|
/** Re-reads CSS variables used by the theme (call after changing them outside of class/style attributes). */
|
|
418
421
|
refreshTheme(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export type { PaneCoords } from './objects/types';
|
|
|
19
19
|
export type { Painter } from './render/painter';
|
|
20
20
|
export type { Palette } from './render/renderer';
|
|
21
21
|
export { snapToOhlc } from './objects/magnet';
|
|
22
|
-
export
|
|
22
|
+
export { DEFAULT_TRADING_LABEL_OPTIONS } from './objects/trading';
|
|
23
|
+
export type { AlertLineOptions, ExecutionGroup, ExecutionMarker, LineHandle, OrderLineOptions, PositionLineOptions, TradingLabelOptions } from './objects/trading';
|
|
23
24
|
export type { Execution, OrderInfo, PositionInfo, TradingAdapter, TradingCallbacks } from './objects/trading-sync';
|
|
24
25
|
export { bucketStart, TickAggregator, timeZoneOffset } from './realtime';
|
|
25
26
|
export type { Tick } from './realtime';
|
package/dist/index.js
CHANGED
|
@@ -1554,6 +1554,10 @@ var Painter = class {
|
|
|
1554
1554
|
* A row of joined label segments ("pill") centered vertically on `y`.
|
|
1555
1555
|
* Returns each segment's rect for hit testing (e.g. an order's cancel button).
|
|
1556
1556
|
*/
|
|
1557
|
+
/** Width `pill` will take for these segments. */
|
|
1558
|
+
pillWidth(segments) {
|
|
1559
|
+
return segments.reduce((w, seg) => w + Math.ceil(this.measure(seg.text)) + (seg.padding ?? 8) * 2, 0);
|
|
1560
|
+
}
|
|
1557
1561
|
pill(segments, x, y, height = 20, radius = 4) {
|
|
1558
1562
|
const { ctx } = this;
|
|
1559
1563
|
const rects = [];
|
|
@@ -1579,8 +1583,18 @@ var Painter = class {
|
|
|
1579
1583
|
var inRect = (r, x, y, pad = 0) => x >= r.x - pad && x <= r.x + r.w + pad && y >= r.y - pad && y <= r.y + r.h + pad;
|
|
1580
1584
|
|
|
1581
1585
|
// src/objects/trading.ts
|
|
1586
|
+
var DEFAULT_TRADING_LABEL_OPTIONS = {
|
|
1587
|
+
side: true,
|
|
1588
|
+
price: false,
|
|
1589
|
+
quantity: true,
|
|
1590
|
+
pnl: true,
|
|
1591
|
+
buttons: true,
|
|
1592
|
+
align: "left",
|
|
1593
|
+
fullWidth: true
|
|
1594
|
+
};
|
|
1582
1595
|
var LABEL_X = 12;
|
|
1583
1596
|
var LABEL_HEIGHT = 20;
|
|
1597
|
+
var LABEL_GAP = 6;
|
|
1584
1598
|
var ORDER_DASH = [6, 4];
|
|
1585
1599
|
var ALERT_DASH = [2, 3];
|
|
1586
1600
|
var TradingLayer = class {
|
|
@@ -1595,6 +1609,14 @@ var TradingLayer = class {
|
|
|
1595
1609
|
labelRects = /* @__PURE__ */ new Map();
|
|
1596
1610
|
drag = null;
|
|
1597
1611
|
groups = [];
|
|
1612
|
+
labelOptions = { ...DEFAULT_TRADING_LABEL_OPTIONS };
|
|
1613
|
+
get labelSettings() {
|
|
1614
|
+
return { ...this.labelOptions };
|
|
1615
|
+
}
|
|
1616
|
+
setLabelOptions(patch) {
|
|
1617
|
+
this.labelOptions = { ...this.labelOptions, ...patch };
|
|
1618
|
+
this.onChange();
|
|
1619
|
+
}
|
|
1598
1620
|
addPosition(options) {
|
|
1599
1621
|
const id = newId("pos");
|
|
1600
1622
|
this.positions.set(id, { ...options });
|
|
@@ -1699,19 +1721,21 @@ var TradingLayer = class {
|
|
|
1699
1721
|
const index = barIndexAt(e.time);
|
|
1700
1722
|
const bar = bars[index];
|
|
1701
1723
|
if (!bar) continue;
|
|
1702
|
-
const
|
|
1724
|
+
const liquidation = e.kind === "liquidation";
|
|
1725
|
+
const key = `${bar.time}:${liquidation ? "liq" : e.side}`;
|
|
1703
1726
|
let g = byKey.get(key);
|
|
1704
1727
|
if (!g) {
|
|
1705
1728
|
const buy = e.side === "buy";
|
|
1706
1729
|
g = {
|
|
1707
1730
|
key,
|
|
1708
1731
|
side: e.side,
|
|
1732
|
+
kind: liquidation ? "liquidation" : "fill",
|
|
1709
1733
|
count: 0,
|
|
1710
1734
|
quantity: 0,
|
|
1711
1735
|
averagePrice: 0,
|
|
1712
1736
|
time: e.time,
|
|
1713
1737
|
x: indexToX(index),
|
|
1714
|
-
y: buy ? c.priceToY(bar.low) + 10 : c.priceToY(bar.high) - 10,
|
|
1738
|
+
y: liquidation ? c.priceToY(e.price) : buy ? c.priceToY(bar.low) + 10 : c.priceToY(bar.high) - 10,
|
|
1715
1739
|
weighted: 0,
|
|
1716
1740
|
weight: 0
|
|
1717
1741
|
};
|
|
@@ -1728,6 +1752,23 @@ var TradingLayer = class {
|
|
|
1728
1752
|
const { ctx } = p;
|
|
1729
1753
|
for (const g of this.groups) {
|
|
1730
1754
|
if (g.x < -10 || g.x > c.plotWidth + 10) continue;
|
|
1755
|
+
if (g.kind === "liquidation") {
|
|
1756
|
+
ctx.beginPath();
|
|
1757
|
+
ctx.arc(g.x, g.y, 8, 0, Math.PI * 2);
|
|
1758
|
+
ctx.fillStyle = palette.down;
|
|
1759
|
+
ctx.fill();
|
|
1760
|
+
ctx.beginPath();
|
|
1761
|
+
ctx.moveTo(g.x + 1.5, g.y - 5.5);
|
|
1762
|
+
ctx.lineTo(g.x - 3.5, g.y + 1);
|
|
1763
|
+
ctx.lineTo(g.x - 0.2, g.y + 1);
|
|
1764
|
+
ctx.lineTo(g.x - 1.5, g.y + 5.5);
|
|
1765
|
+
ctx.lineTo(g.x + 3.5, g.y - 1);
|
|
1766
|
+
ctx.lineTo(g.x + 0.2, g.y - 1);
|
|
1767
|
+
ctx.closePath();
|
|
1768
|
+
ctx.fillStyle = "#FFFFFF";
|
|
1769
|
+
ctx.fill();
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1731
1772
|
const buy = g.side === "buy";
|
|
1732
1773
|
ctx.beginPath();
|
|
1733
1774
|
ctx.moveTo(g.x, buy ? g.y - 5 : g.y + 5);
|
|
@@ -1738,15 +1779,41 @@ var TradingLayer = class {
|
|
|
1738
1779
|
ctx.fill();
|
|
1739
1780
|
if (g.count > 1) p.text(String(g.count), g.x + 7, g.y, buy ? palette.up : palette.down, "left", "middle");
|
|
1740
1781
|
}
|
|
1782
|
+
const opts = this.labelOptions;
|
|
1783
|
+
const placed = [];
|
|
1784
|
+
const placeX = (width, y) => {
|
|
1785
|
+
const toLeft = opts.align === "right";
|
|
1786
|
+
let x = opts.align === "left" ? LABEL_X : toLeft ? c.plotWidth - width - LABEL_X : (c.plotWidth - width) / 2;
|
|
1787
|
+
for (let moved = true; moved; ) {
|
|
1788
|
+
moved = false;
|
|
1789
|
+
for (const r of placed) {
|
|
1790
|
+
const overlapsY = Math.abs(r.y + LABEL_HEIGHT / 2 - y) < LABEL_HEIGHT + 2;
|
|
1791
|
+
if (overlapsY && x < r.x + r.w + LABEL_GAP && x + width > r.x - LABEL_GAP) {
|
|
1792
|
+
x = toLeft ? r.x - width - LABEL_GAP : r.x + r.w + LABEL_GAP;
|
|
1793
|
+
moved = true;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
placed.push({ x, y: y - LABEL_HEIGHT / 2, w: width, h: LABEL_HEIGHT });
|
|
1798
|
+
return x;
|
|
1799
|
+
};
|
|
1800
|
+
const drawLine = (y, color, segments, dash) => {
|
|
1801
|
+
if (segments.length === 0) {
|
|
1802
|
+
p.hLine(y, 0, c.plotWidth, color, dash);
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1805
|
+
const x = placeX(p.pillWidth(segments), y);
|
|
1806
|
+
const from = opts.fullWidth ? 0 : Math.min(x + p.pillWidth(segments), c.plotWidth);
|
|
1807
|
+
p.hLine(y, from, c.plotWidth, color, dash);
|
|
1808
|
+
return p.pill(segments, x, y, LABEL_HEIGHT);
|
|
1809
|
+
};
|
|
1741
1810
|
for (const pos of this.positions.values()) {
|
|
1742
1811
|
const y = c.priceToY(pos.price);
|
|
1743
1812
|
badges.push({ y, text: c.formatPrice(pos.price), bg: palette.text, fg: palette.background });
|
|
1744
1813
|
if (!visible(y)) continue;
|
|
1745
|
-
|
|
1746
|
-
const segments = [
|
|
1747
|
-
|
|
1748
|
-
];
|
|
1749
|
-
if (last) {
|
|
1814
|
+
const main = [opts.side ? pos.label ?? labels.position : "", opts.price ? c.formatPrice(pos.price) : "", opts.quantity ? String(pos.quantity) : ""].filter(Boolean).join(" \xB7 ");
|
|
1815
|
+
const segments = main ? [{ text: main, bg: palette.text, fg: palette.background }] : [];
|
|
1816
|
+
if (last && opts.pnl) {
|
|
1750
1817
|
const pnl = (last.close - pos.price) * pos.quantity * (pos.side === "long" ? 1 : -1);
|
|
1751
1818
|
const pct = pos.price ? (last.close - pos.price) / pos.price * 100 * (pos.side === "long" ? 1 : -1) : 0;
|
|
1752
1819
|
const sign = pnl > 0 ? "+" : "";
|
|
@@ -1756,7 +1823,7 @@ var TradingLayer = class {
|
|
|
1756
1823
|
fg: "#FFFFFF"
|
|
1757
1824
|
});
|
|
1758
1825
|
}
|
|
1759
|
-
|
|
1826
|
+
drawLine(y, palette.text, segments);
|
|
1760
1827
|
}
|
|
1761
1828
|
for (const [id, order] of this.orders) {
|
|
1762
1829
|
const price = this.drag?.id === id ? this.drag.price : order.price;
|
|
@@ -1764,15 +1831,15 @@ var TradingLayer = class {
|
|
|
1764
1831
|
const color = order.alert ? palette.textSecondary : order.side === "buy" ? palette.up : palette.down;
|
|
1765
1832
|
badges.push({ y, text: c.formatPrice(price), bg: color, fg: "#FFFFFF" });
|
|
1766
1833
|
if (!visible(y)) continue;
|
|
1767
|
-
|
|
1768
|
-
const
|
|
1769
|
-
const segments = [{ text: main, bg: color, fg: "#FFFFFF" }];
|
|
1770
|
-
if (
|
|
1771
|
-
const rects =
|
|
1772
|
-
if (
|
|
1834
|
+
const main = (order.alert ? [order.label ?? labels.alert, opts.price ? c.formatPrice(price) : ""] : [opts.side ? order.side === "buy" ? labels.buy : labels.sell : "", order.label, opts.price ? c.formatPrice(price) : "", opts.quantity ? String(order.quantity) : ""]).filter(Boolean).join(" ");
|
|
1835
|
+
const cancellable = !!order.onCancel && opts.buttons;
|
|
1836
|
+
const segments = [{ text: main || c.formatPrice(price), bg: color, fg: "#FFFFFF" }];
|
|
1837
|
+
if (cancellable) segments.push({ text: "\xD7", bg: color, fg: "#FFFFFF", padding: 7 });
|
|
1838
|
+
const rects = drawLine(y, color, segments, order.alert ? ALERT_DASH : ORDER_DASH);
|
|
1839
|
+
if (cancellable && rects[1]) p.vLine(rects[1].x, y - 6, y + 6, "rgba(255,255,255,0.4)");
|
|
1773
1840
|
this.labelRects.set(id, {
|
|
1774
1841
|
label: { x: rects[0].x, y: rects[0].y, w: rects.reduce((w, r) => w + r.w, 0), h: LABEL_HEIGHT },
|
|
1775
|
-
cancel:
|
|
1842
|
+
cancel: cancellable ? rects[1] : null
|
|
1776
1843
|
});
|
|
1777
1844
|
}
|
|
1778
1845
|
}
|
|
@@ -2652,8 +2719,8 @@ function firstFinite(values, from) {
|
|
|
2652
2719
|
return Number.NaN;
|
|
2653
2720
|
}
|
|
2654
2721
|
var TOOLTIP_LABELS = {
|
|
2655
|
-
ko: { buy: "\uB9E4\uC218", sell: "\uB9E4\uB3C4", fills: (n) => `${n}\uAC74`, price: "\uCCB4\uACB0\uAC00", avg: "\uD3C9\uADE0\uAC00", qty: "\uC218\uB7C9" },
|
|
2656
|
-
en: { buy: "Buy", sell: "Sell", fills: (n) => `${n} fills`, price: "Price", avg: "Avg", qty: "Qty" }
|
|
2722
|
+
ko: { buy: "\uB9E4\uC218", sell: "\uB9E4\uB3C4", liquidation: "\uCCAD\uC0B0", fills: (n) => `${n}\uAC74`, price: "\uCCB4\uACB0\uAC00", avg: "\uD3C9\uADE0\uAC00", qty: "\uC218\uB7C9" },
|
|
2723
|
+
en: { buy: "Buy", sell: "Sell", liquidation: "Liquidation", fills: (n) => `${n} fills`, price: "Price", avg: "Avg", qty: "Qty" }
|
|
2657
2724
|
};
|
|
2658
2725
|
function fallbackResolution(requested, supported) {
|
|
2659
2726
|
if (!supported?.length || supported.includes(requested)) return requested;
|
|
@@ -3655,10 +3722,17 @@ var Chart = class {
|
|
|
3655
3722
|
addAlertLine(options) {
|
|
3656
3723
|
return this.trading.addAlert(options);
|
|
3657
3724
|
}
|
|
3658
|
-
/** Fill markers (▲ buy under the bar, ▼ sell above). Replaces the previous set. */
|
|
3725
|
+
/** Fill markers (▲ buy under the bar, ▼ sell above; ⚡ at the price for liquidations). Replaces the previous set. */
|
|
3659
3726
|
setExecutions(markers) {
|
|
3660
3727
|
this.trading.setExecutions(markers);
|
|
3661
3728
|
}
|
|
3729
|
+
/** What position/order/alert line labels show and where (direction, price, size, P&L, ×, alignment, line width). */
|
|
3730
|
+
setTradingLabels(patch) {
|
|
3731
|
+
this.trading.setLabelOptions(patch);
|
|
3732
|
+
}
|
|
3733
|
+
get tradingLabels() {
|
|
3734
|
+
return this.trading.labelSettings;
|
|
3735
|
+
}
|
|
3662
3736
|
setTheme(theme) {
|
|
3663
3737
|
this.theme = { ...this.theme, ...theme };
|
|
3664
3738
|
this.colors.clear();
|
|
@@ -4156,14 +4230,14 @@ var Chart = class {
|
|
|
4156
4230
|
const g = this.tooltipKey ? this.trading.executionGroup(this.tooltipKey) : void 0;
|
|
4157
4231
|
if (!g) return null;
|
|
4158
4232
|
const t = TOOLTIP_LABELS[this.locale];
|
|
4159
|
-
const side = g.side === "buy" ? t.buy : t.sell;
|
|
4233
|
+
const side = g.kind === "liquidation" ? t.liquidation : g.side === "buy" ? t.buy : t.sell;
|
|
4160
4234
|
const lines = [
|
|
4161
4235
|
g.count > 1 ? `${side} ${t.fills(g.count)}` : side,
|
|
4162
4236
|
`${g.count > 1 ? t.avg : t.price} ${formatPrice(roundToTick(g.averagePrice, this.tickSize), this.pricePrecision)}`
|
|
4163
4237
|
];
|
|
4164
4238
|
if (g.quantity > 0) lines.push(`${t.qty} ${Number(g.quantity.toFixed(8))}`);
|
|
4165
4239
|
lines.push(this.formatter.crosshairLabel(g.time, true));
|
|
4166
|
-
return { x: g.x, y: g.y, side: g.side, lines };
|
|
4240
|
+
return { x: g.x, y: g.y, side: g.kind === "liquidation" ? "sell" : g.side, lines };
|
|
4167
4241
|
}
|
|
4168
4242
|
emitCrosshair(info) {
|
|
4169
4243
|
const key = info ? `${info.index}|${info.value}|${info.bar.close}|${info.bar.volume ?? ""}` : "";
|
|
@@ -4557,6 +4631,7 @@ export {
|
|
|
4557
4631
|
CHART_THEMES,
|
|
4558
4632
|
Chart,
|
|
4559
4633
|
DEFAULT_CHART_SETTINGS,
|
|
4634
|
+
DEFAULT_TRADING_LABEL_OPTIONS,
|
|
4560
4635
|
FIB_LEVELS,
|
|
4561
4636
|
INDICATORS,
|
|
4562
4637
|
PRICE_SOURCES,
|
|
@@ -37,11 +37,33 @@ export interface ExecutionMarker {
|
|
|
37
37
|
price: number;
|
|
38
38
|
side: 'buy' | 'sell';
|
|
39
39
|
quantity?: number;
|
|
40
|
+
/** 'liquidation': a forced close, drawn as a ⚡ badge at its price instead of ▲/▼. Default 'fill'. */
|
|
41
|
+
kind?: 'fill' | 'liquidation';
|
|
40
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* What the labels of position, order and alert lines show, and where (chart settings like
|
|
45
|
+
* "라벨" in trading terminals). Defaults keep the original look.
|
|
46
|
+
*/
|
|
47
|
+
export interface TradingLabelOptions {
|
|
48
|
+
/** 매수/매도 and the position name. */
|
|
49
|
+
side: boolean;
|
|
50
|
+
/** The line's price inside the label (the price axis always shows it). */
|
|
51
|
+
price: boolean;
|
|
52
|
+
quantity: boolean;
|
|
53
|
+
/** Positions: P&L against the last price. */
|
|
54
|
+
pnl: boolean;
|
|
55
|
+
/** The × on cancellable lines. */
|
|
56
|
+
buttons: boolean;
|
|
57
|
+
align: 'left' | 'center' | 'right';
|
|
58
|
+
/** Draw lines across the whole chart; off, only from the label to the price axis. */
|
|
59
|
+
fullWidth: boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare const DEFAULT_TRADING_LABEL_OPTIONS: TradingLabelOptions;
|
|
41
62
|
/** Fills on the same bar and side, shown as one marker. */
|
|
42
63
|
export interface ExecutionGroup {
|
|
43
64
|
key: string;
|
|
44
65
|
side: 'buy' | 'sell';
|
|
66
|
+
kind: 'fill' | 'liquidation';
|
|
45
67
|
count: number;
|
|
46
68
|
quantity: number;
|
|
47
69
|
/** Quantity-weighted average price (plain average when quantities are missing). */
|
|
@@ -72,7 +94,10 @@ export declare class TradingLayer {
|
|
|
72
94
|
private labelRects;
|
|
73
95
|
private drag;
|
|
74
96
|
private groups;
|
|
97
|
+
private labelOptions;
|
|
75
98
|
constructor(onChange: () => void);
|
|
99
|
+
get labelSettings(): TradingLabelOptions;
|
|
100
|
+
setLabelOptions(patch: Partial<TradingLabelOptions>): void;
|
|
76
101
|
addPosition(options: PositionLineOptions): LineHandle<PositionLineOptions>;
|
|
77
102
|
addOrder(options: OrderLineOptions): LineHandle<OrderLineOptions>;
|
|
78
103
|
addAlert(options: AlertLineOptions): LineHandle<AlertLineOptions>;
|
package/dist/render/painter.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare class Painter {
|
|
|
21
21
|
* A row of joined label segments ("pill") centered vertically on `y`.
|
|
22
22
|
* Returns each segment's rect for hit testing (e.g. an order's cancel button).
|
|
23
23
|
*/
|
|
24
|
+
/** Width `pill` will take for these segments. */
|
|
25
|
+
pillWidth(segments: readonly PillSegment[]): number;
|
|
24
26
|
pill(segments: readonly PillSegment[], x: number, y: number, height?: number, radius?: number): Rect[];
|
|
25
27
|
}
|
|
26
28
|
export interface Rect {
|