@livo-build/charts 0.2.2 → 0.2.4
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/README.md +116 -19
- package/dist/core/chart.d.ts +44 -2
- package/dist/core/chart.js +123 -11
- package/dist/core/feed.js +27 -6
- package/dist/core/indicators.d.ts +36 -0
- package/dist/core/indicators.js +119 -0
- package/dist/core/ohlc.d.ts +10 -0
- package/dist/core/ohlc.js +30 -0
- package/dist/core/polymarket.d.ts +53 -0
- package/dist/core/polymarket.js +173 -0
- package/dist/core/renderer.d.ts +19 -4
- package/dist/core/renderer.js +217 -34
- package/dist/core/signals.d.ts +63 -0
- package/dist/core/signals.js +234 -0
- package/dist/core/types.d.ts +31 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -3
- package/dist/react/HyperliquidChart.d.ts +4 -2
- package/dist/react/HyperliquidChart.js +11 -3
- package/dist/react/PolymarketChart.d.ts +45 -0
- package/dist/react/PolymarketChart.js +95 -0
- package/dist/react/PriceChart.d.ts +18 -3
- package/dist/react/PriceChart.js +16 -4
- package/dist/react/SignalsChart.d.ts +37 -0
- package/dist/react/SignalsChart.js +95 -0
- package/dist/react/ui.d.ts +13 -2
- package/dist/react/ui.js +46 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2 -0
- package/package.json +2 -2
package/dist/core/types.d.ts
CHANGED
|
@@ -15,20 +15,24 @@ export interface Point {
|
|
|
15
15
|
v?: number;
|
|
16
16
|
}
|
|
17
17
|
export type Denom = "USD" | "ETH";
|
|
18
|
-
|
|
18
|
+
/** Price-series style: candlesticks, a line, a two-tone baseline area, or Heikin-Ashi candles. */
|
|
19
|
+
export type ChartType = "candle" | "line" | "baseline" | "heikin";
|
|
19
20
|
export type IndicatorType = "sma" | "ema" | "wma" | "vwap" | "bollinger";
|
|
20
21
|
export type PriceSource = "open" | "high" | "low" | "close";
|
|
21
22
|
/** Oscillators render in their own stacked sub-pane below the volume panel. */
|
|
22
|
-
export type OscillatorType = "rsi" | "macd";
|
|
23
|
-
/** An oscillator drawn in a dedicated sub-pane (RSI
|
|
23
|
+
export type OscillatorType = "rsi" | "macd" | "stoch" | "atr";
|
|
24
|
+
/** An oscillator drawn in a dedicated sub-pane (RSI/Stochastic 0–100 band, MACD, or ATR). */
|
|
24
25
|
export interface Oscillator {
|
|
25
26
|
type: OscillatorType;
|
|
26
|
-
/** RSI
|
|
27
|
+
/** Lookback — RSI/ATR period, or the Stochastic %K period (defaults 14). Ignored by MACD. */
|
|
27
28
|
period?: number;
|
|
28
|
-
/** MACD fast / slow / signal EMA lengths (defaults 12 / 26 / 9). Ignored by
|
|
29
|
+
/** MACD fast / slow / signal EMA lengths (defaults 12 / 26 / 9). Ignored by the others. */
|
|
29
30
|
fast?: number;
|
|
30
31
|
slow?: number;
|
|
31
32
|
signal?: number;
|
|
33
|
+
/** Stochastic %D period (default 3) and %K smoothing (default 1 = fast). Ignored by others. */
|
|
34
|
+
dPeriod?: number;
|
|
35
|
+
smooth?: number;
|
|
32
36
|
/** Pane height in px (default 84). */
|
|
33
37
|
height?: number;
|
|
34
38
|
/** Primary line color; defaults to a built-in palette slot. */
|
|
@@ -52,8 +56,11 @@ export interface DrawingPoint {
|
|
|
52
56
|
time: number;
|
|
53
57
|
price: number;
|
|
54
58
|
}
|
|
55
|
-
export type DrawingType = "trendline" | "hline";
|
|
56
|
-
/**
|
|
59
|
+
export type DrawingType = "trendline" | "hline" | "fib" | "rect";
|
|
60
|
+
/**
|
|
61
|
+
* A user-drawn annotation on the price pane. `hline` uses `a.price`; `trendline`, `fib`
|
|
62
|
+
* (retracement levels between the two prices) and `rect` (box) use both `a` and `b`.
|
|
63
|
+
*/
|
|
57
64
|
export interface Drawing {
|
|
58
65
|
id: string;
|
|
59
66
|
type: DrawingType;
|
|
@@ -62,7 +69,7 @@ export interface Drawing {
|
|
|
62
69
|
color?: string;
|
|
63
70
|
}
|
|
64
71
|
/** Drawing interaction mode. `none` = pan/zoom + select/move; the others arm a one-shot draw. */
|
|
65
|
-
export type DrawMode = "none" | "trendline" | "hline";
|
|
72
|
+
export type DrawMode = "none" | "trendline" | "hline" | "fib" | "rect";
|
|
66
73
|
/** Transform applied to raw prices before aggregation. */
|
|
67
74
|
export interface PriceTransform {
|
|
68
75
|
/** "USD" leaves prices as-is; "ETH" divides by `ethUsd`. */
|
|
@@ -91,6 +98,15 @@ export interface ChartTheme {
|
|
|
91
98
|
axisTagText: string;
|
|
92
99
|
background?: string;
|
|
93
100
|
}
|
|
101
|
+
/** Volume-by-price overlay config. `true` uses defaults; an object tunes it. */
|
|
102
|
+
export type VolumeProfileConfig = boolean | {
|
|
103
|
+
/** number of price bands (default 24). */
|
|
104
|
+
buckets?: number;
|
|
105
|
+
/** fraction of the plot width the widest bar occupies (default 0.16). */
|
|
106
|
+
width?: number;
|
|
107
|
+
/** bar fill color (default a translucent `theme.axis`). */
|
|
108
|
+
color?: string;
|
|
109
|
+
};
|
|
94
110
|
export interface ChartOptions {
|
|
95
111
|
/** canvas height in CSS px (default 420). */
|
|
96
112
|
height?: number;
|
|
@@ -125,6 +141,13 @@ export interface ChartOptions {
|
|
|
125
141
|
onDrawModeChange?: (mode: DrawMode) => void;
|
|
126
142
|
/** logarithmic price axis — equal vertical distance = equal % move (default false). */
|
|
127
143
|
logScale?: boolean;
|
|
144
|
+
/** centered text shown when there are no candles (default "no priced trades yet"). Feeds
|
|
145
|
+
* set this to "Loading…" while the first page is in flight. Pass "" to draw nothing. */
|
|
146
|
+
emptyText?: string;
|
|
147
|
+
/** reference price for the `baseline` chart type (default: the first visible candle's close). */
|
|
148
|
+
baselinePrice?: number;
|
|
149
|
+
/** volume-by-price histogram drawn on the price pane (default off). */
|
|
150
|
+
volumeProfile?: VolumeProfileConfig;
|
|
128
151
|
/** spread candles across the full plot width instead of right-anchoring a capped slot
|
|
129
152
|
* (default false in the core `Chart`; the React wrappers default it on). */
|
|
130
153
|
fitContent?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
export { Chart } from "./core/chart";
|
|
2
|
-
export { buildOHLC, transformPrice } from "./core/ohlc";
|
|
1
|
+
export { Chart, needsHistory, maxPanOffset } from "./core/chart";
|
|
2
|
+
export { buildOHLC, transformPrice, heikinAshi } from "./core/ohlc";
|
|
3
3
|
export { draw, slotWidth, windowOf, priceScale, timeScale, computeProjection, withAlpha, resolveOverlays } from "./core/renderer";
|
|
4
4
|
export type { RenderInput, Viewport, Pads, ResolvedOverlay, Projection } from "./core/renderer";
|
|
5
5
|
export { connectFeed } from "./core/feed";
|
|
6
6
|
export type { ChartFeed, ConnectFeedOptions, FeedConnection, LoadHistoryParams } from "./core/feed";
|
|
7
7
|
export { DEFAULT_THEME, LIGHT_THEME, THEME_PRESETS } from "./core/theme";
|
|
8
8
|
export { formatValue, formatAxisValue, formatVolume, formatTime } from "./core/format";
|
|
9
|
-
export { sma, ema, wma, vwap, bollingerBands, rsi, macd, sourceValues, computeIndicator, INDICATOR_PALETTE } from "./core/indicators";
|
|
9
|
+
export { sma, ema, wma, vwap, bollingerBands, rsi, macd, stochastic, atr, volumeProfile, sourceValues, computeIndicator, INDICATOR_PALETTE } from "./core/indicators";
|
|
10
|
+
export type { VolumeBucket, VolumeProfileResult } from "./core/indicators";
|
|
10
11
|
export { fetchHyperliquidCandles, fetchHlCandleWindow, hyperliquidFeed, mapHlCandles, HL_INTERVAL_SECONDS } from "./core/live";
|
|
11
12
|
export type { HlRawCandle, FetchHlCandlesOptions, HyperliquidFeedOptions } from "./core/live";
|
|
12
|
-
export
|
|
13
|
+
export { fetchPolymarketPriceHistory, polymarketFeed, POLYMARKET_CLOB } from "./core/polymarket";
|
|
14
|
+
export type { PolyHistoryPoint, FetchPolyHistoryOptions, PolymarketFeedOptions } from "./core/polymarket";
|
|
15
|
+
export { fetchSignalsMarket, fetchSignalsSwaps, signalsFeed, sparkCandles, rejectPriceOutliers, DEFAULT_SIGNALS_URL } from "./core/signals";
|
|
16
|
+
export type { SignalsMarket, SignalsFeedOptions } from "./core/signals";
|
|
17
|
+
export type { Candle, Point, Denom, ChartType, PriceTransform, ChartTheme, ChartOptions, Indicator, IndicatorType, PriceSource, Oscillator, OscillatorType, Drawing, DrawingType, DrawingPoint, DrawMode, AxisOptions, VolumeProfileConfig, } from "./core/types";
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// livo-charts — framework-agnostic core.
|
|
2
2
|
// React bindings live in the `@livo-build/charts/react` subpath so non-React
|
|
3
3
|
// consumers never pull React into their bundle.
|
|
4
|
-
export { Chart } from "./core/chart";
|
|
5
|
-
export { buildOHLC, transformPrice } from "./core/ohlc";
|
|
4
|
+
export { Chart, needsHistory, maxPanOffset } from "./core/chart";
|
|
5
|
+
export { buildOHLC, transformPrice, heikinAshi } from "./core/ohlc";
|
|
6
6
|
export { draw, slotWidth, windowOf, priceScale, timeScale, computeProjection, withAlpha, resolveOverlays } from "./core/renderer";
|
|
7
7
|
export { connectFeed } from "./core/feed";
|
|
8
8
|
export { DEFAULT_THEME, LIGHT_THEME, THEME_PRESETS } from "./core/theme";
|
|
9
9
|
export { formatValue, formatAxisValue, formatVolume, formatTime } from "./core/format";
|
|
10
|
-
export { sma, ema, wma, vwap, bollingerBands, rsi, macd, sourceValues, computeIndicator, INDICATOR_PALETTE } from "./core/indicators";
|
|
10
|
+
export { sma, ema, wma, vwap, bollingerBands, rsi, macd, stochastic, atr, volumeProfile, sourceValues, computeIndicator, INDICATOR_PALETTE } from "./core/indicators";
|
|
11
11
|
export { fetchHyperliquidCandles, fetchHlCandleWindow, hyperliquidFeed, mapHlCandles, HL_INTERVAL_SECONDS } from "./core/live";
|
|
12
|
+
export { fetchPolymarketPriceHistory, polymarketFeed, POLYMARKET_CLOB } from "./core/polymarket";
|
|
13
|
+
export { fetchSignalsMarket, fetchSignalsSwaps, signalsFeed, sparkCandles, rejectPriceOutliers, DEFAULT_SIGNALS_URL } from "./core/signals";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChartTheme, ChartType, Drawing, Indicator, Oscillator } from "../core/types";
|
|
1
|
+
import type { ChartTheme, ChartType, Drawing, Indicator, Oscillator, VolumeProfileConfig } from "../core/types";
|
|
2
2
|
export interface HyperliquidChartProps {
|
|
3
3
|
/** Hyperliquid coin symbol, e.g. "BTC", "ETH", or a builder-dex name like "xyz:TSLA". */
|
|
4
4
|
coin: string;
|
|
@@ -19,6 +19,8 @@ export interface HyperliquidChartProps {
|
|
|
19
19
|
hideDrawingTools?: boolean;
|
|
20
20
|
/** Show the volume panel (default true). */
|
|
21
21
|
showVolume?: boolean;
|
|
22
|
+
/** Volume-by-price histogram (default off); the toolbar adds a VP toggle when set. */
|
|
23
|
+
volumeProfile?: VolumeProfileConfig;
|
|
22
24
|
/** Candles per history page; older pages load as you scroll left (default 500). */
|
|
23
25
|
pageSize?: number;
|
|
24
26
|
theme?: Partial<ChartTheme>;
|
|
@@ -51,4 +53,4 @@ export interface HyperliquidChartProps {
|
|
|
51
53
|
* <HyperliquidChart coin="BTC" interval="15m" indicators={[{ type: "ema", period: 21 }]} />
|
|
52
54
|
* ```
|
|
53
55
|
*/
|
|
54
|
-
export declare function HyperliquidChart({ coin, interval, testnet, height, chartType, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools, showVolume: showVolumeProp, pageSize, theme, toolbar, fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, }: HyperliquidChartProps): import("react").JSX.Element;
|
|
56
|
+
export declare function HyperliquidChart({ coin, interval, testnet, height, chartType, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools, showVolume: showVolumeProp, volumeProfile: volumeProfileProp, pageSize, theme, toolbar, fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, }: HyperliquidChartProps): import("react").JSX.Element;
|
|
@@ -5,7 +5,7 @@ import { connectFeed } from "../core/feed";
|
|
|
5
5
|
import { hyperliquidFeed, HL_INTERVAL_SECONDS } from "../core/live";
|
|
6
6
|
import { formatValue, formatVolume } from "../core/format";
|
|
7
7
|
import { DEFAULT_THEME } from "../core/theme";
|
|
8
|
-
import { toolbarUi } from "./ui";
|
|
8
|
+
import { toolbarUi, typeButtons, drawButtons, loadingOverlay } from "./ui";
|
|
9
9
|
const IVS = ["1m", "5m", "15m", "1h", "4h", "1d"];
|
|
10
10
|
/**
|
|
11
11
|
* A turnkey, REALTIME Hyperliquid trading chart. Streams live candles over WebSocket,
|
|
@@ -17,11 +17,12 @@ const IVS = ["1m", "5m", "15m", "1h", "4h", "1d"];
|
|
|
17
17
|
* <HyperliquidChart coin="BTC" interval="15m" indicators={[{ type: "ema", period: 21 }]} />
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
export function HyperliquidChart({ coin, interval = "1h", testnet, height = 420, chartType = "candle", indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools = false, showVolume: showVolumeProp = true, pageSize = 500, theme, toolbar = true, fitContent = true, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, }) {
|
|
20
|
+
export function HyperliquidChart({ coin, interval = "1h", testnet, height = 420, chartType = "candle", indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools = false, showVolume: showVolumeProp = true, volumeProfile: volumeProfileProp, pageSize = 500, theme, toolbar = true, fitContent = true, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, }) {
|
|
21
21
|
const [iv, setIv] = useState(interval);
|
|
22
22
|
const [type, setType] = useState(chartType);
|
|
23
23
|
const [log, setLog] = useState(false);
|
|
24
24
|
const [showVolume, setShowVolume] = useState(showVolumeProp);
|
|
25
|
+
const [vpOn, setVpOn] = useState(!!volumeProfileProp);
|
|
25
26
|
const [drawMode, setDrawMode] = useState("none");
|
|
26
27
|
const [last, setLast] = useState(null);
|
|
27
28
|
const [legend, setLegend] = useState(null);
|
|
@@ -37,12 +38,14 @@ export function HyperliquidChart({ coin, interval = "1h", testnet, height = 420,
|
|
|
37
38
|
const c = new Chart(host.current, {
|
|
38
39
|
height,
|
|
39
40
|
theme: th,
|
|
41
|
+
emptyText: "Loading…",
|
|
40
42
|
indicators,
|
|
41
43
|
oscillators,
|
|
42
44
|
drawings,
|
|
43
45
|
onDrawingsChange,
|
|
44
46
|
onDrawModeChange: setDrawMode,
|
|
45
47
|
showVolume: showVolumeProp,
|
|
48
|
+
volumeProfile: volumeProfileProp,
|
|
46
49
|
onCrosshair: setLegend,
|
|
47
50
|
fitContent,
|
|
48
51
|
maxBarWidth,
|
|
@@ -68,6 +71,10 @@ export function HyperliquidChart({ coin, interval = "1h", testnet, height = 420,
|
|
|
68
71
|
useEffect(() => { chart.current?.setDrawMode(drawMode); }, [drawMode]);
|
|
69
72
|
useEffect(() => { chart.current?.setShowVolume(showVolume); }, [showVolume]);
|
|
70
73
|
useEffect(() => { chart.current?.setLogScale(log); }, [log]);
|
|
74
|
+
useEffect(() => { chart.current?.setEmptyText(loading ? "" : "no data"); }, [loading]); // overlay handles loading
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
chart.current?.setVolumeProfile(vpOn ? (typeof volumeProfileProp === "object" ? volumeProfileProp : true) : false);
|
|
77
|
+
}, [vpOn, volumeProfileProp]);
|
|
71
78
|
useEffect(() => {
|
|
72
79
|
chart.current?.setAxis({ fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont });
|
|
73
80
|
}, [fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont]);
|
|
@@ -85,6 +92,7 @@ export function HyperliquidChart({ coin, interval = "1h", testnet, height = 420,
|
|
|
85
92
|
onCandles: (cs) => {
|
|
86
93
|
setLast(cs[cs.length - 1] ?? null);
|
|
87
94
|
setLoading(false);
|
|
95
|
+
setError(null); // a successful page clears a prior transient error
|
|
88
96
|
},
|
|
89
97
|
onError: (e) => {
|
|
90
98
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -97,5 +105,5 @@ export function HyperliquidChart({ coin, interval = "1h", testnet, height = 420,
|
|
|
97
105
|
const lc = info && info.c >= info.o ? th.up : th.down;
|
|
98
106
|
const { btn, sepStyle, barStyle, muted } = toolbarUi(th);
|
|
99
107
|
const sep = _jsx("span", { style: sepStyle });
|
|
100
|
-
return (_jsxs("div", { style: { position: "relative", userSelect: "none" }, children: [toolbar && (_jsxs("div", { style: barStyle, children: [IVS.map((l) => (_jsx("button", { style: btn(iv === l), onClick: () => setIv(l), children: l }, l))), sep,
|
|
108
|
+
return (_jsxs("div", { style: { position: "relative", userSelect: "none" }, children: [toolbar && (_jsxs("div", { style: barStyle, children: [IVS.map((l) => (_jsx("button", { style: btn(iv === l), onClick: () => setIv(l), children: l }, l))), sep, typeButtons(th, type, setType), sep, _jsx("button", { style: btn(showVolume), title: "Toggle volume panel", onClick: () => setShowVolume((v) => !v), children: "Vol" }), _jsx("button", { style: btn(vpOn), title: "Volume-by-price histogram", onClick: () => setVpOn((v) => !v), children: "VP" }), _jsx("button", { style: btn(log), title: "Logarithmic price axis", onClick: () => setLog((v) => !v), children: "Log" }), !hideDrawingTools ? (_jsxs(_Fragment, { children: [sep, drawButtons(th, drawMode, setDrawMode, () => chart.current?.clearDrawings())] })) : null] })), _jsxs("div", { style: { position: "relative" }, children: [_jsxs("div", { style: { position: "absolute", left: 8, top: 4, zIndex: 10, pointerEvents: "none", fontSize: 11, fontFamily: "ui-monospace, monospace" }, children: [_jsxs("div", { style: { color: muted }, children: [coin, " \u00B7 ", iv, testnet ? " · testnet" : ""] }), error ? (_jsx("div", { style: { color: th.down }, children: error })) : info ? (_jsxs("div", { style: { color: lc }, children: ["O ", formatValue(info.o), " H ", formatValue(info.h), " L ", formatValue(info.l), " C ", formatValue(info.c), " ", _jsxs("span", { style: { color: muted }, children: ["Vol ", formatVolume(info.vol)] })] })) : null] }), _jsx("div", { ref: host }), loading ? loadingOverlay(th) : null] })] }));
|
|
101
109
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ChartTheme, ChartType, Drawing, Indicator, Oscillator, VolumeProfileConfig } from "../core/types";
|
|
2
|
+
export interface PolymarketChartProps {
|
|
3
|
+
/** The Polymarket CLOB token id (an outcome of a market) to chart. */
|
|
4
|
+
tokenId: string;
|
|
5
|
+
/** Initial candle bucket in seconds (default 3600 = 1h); the toolbar can switch it. */
|
|
6
|
+
bucketSeconds?: number;
|
|
7
|
+
/** Override the CLOB host. */
|
|
8
|
+
host?: string;
|
|
9
|
+
/** Live poll cadence in ms (default 30s; 0 = fetch once). Ignored when `liveUrl` is set. */
|
|
10
|
+
refetchMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Optional Livo indexer base (e.g. "https://polymarket.livo.build") — upgrades the live
|
|
13
|
+
* candle from the 30s poll to INSTANT SSE updates. History still loads from the CLOB.
|
|
14
|
+
*/
|
|
15
|
+
liveUrl?: string;
|
|
16
|
+
/** Candles per history page; older pages load as you scroll left (default 500). */
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
chartType?: ChartType;
|
|
20
|
+
indicators?: Indicator[];
|
|
21
|
+
oscillators?: Oscillator[];
|
|
22
|
+
drawings?: Drawing[];
|
|
23
|
+
onDrawingsChange?: (drawings: Drawing[]) => void;
|
|
24
|
+
hideDrawingTools?: boolean;
|
|
25
|
+
/** Show the (always-empty) volume panel — off by default since price-history has no volume. */
|
|
26
|
+
showVolume?: boolean;
|
|
27
|
+
volumeProfile?: VolumeProfileConfig;
|
|
28
|
+
theme?: Partial<ChartTheme>;
|
|
29
|
+
toolbar?: boolean;
|
|
30
|
+
fitContent?: boolean;
|
|
31
|
+
/** Outcome label shown in the legend (e.g. "Yes"). */
|
|
32
|
+
label?: string;
|
|
33
|
+
/** y-axis formatter (default a 0–100% probability). */
|
|
34
|
+
priceFormat?: (value: number) => string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A turnkey, live chart for a Polymarket outcome. Bucketed OHLC from the public CLOB
|
|
38
|
+
* price-history endpoint (no key), polled for a building candle. Prices are probabilities,
|
|
39
|
+
* so the y-axis defaults to a percentage. Renders via the framework-agnostic {@link Chart}.
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <PolymarketChart tokenId={yesTokenId} bucketSeconds={3600} label="Yes" />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function PolymarketChart({ tokenId, bucketSeconds, host, refetchMs, liveUrl, pageSize, height, chartType, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools, showVolume: showVolumeProp, volumeProfile: volumeProfileProp, theme, toolbar, fitContent, label, priceFormat, }: PolymarketChartProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Chart } from "../core/chart";
|
|
4
|
+
import { connectFeed } from "../core/feed";
|
|
5
|
+
import { polymarketFeed } from "../core/polymarket";
|
|
6
|
+
import { DEFAULT_THEME } from "../core/theme";
|
|
7
|
+
import { toolbarUi, typeButtons, drawButtons, loadingOverlay } from "./ui";
|
|
8
|
+
const BUCKETS = [["5m", 300], ["1h", 3600], ["6h", 21600], ["1d", 86400]];
|
|
9
|
+
const pct = (v) => `${(v * 100).toFixed(1)}%`;
|
|
10
|
+
/**
|
|
11
|
+
* A turnkey, live chart for a Polymarket outcome. Bucketed OHLC from the public CLOB
|
|
12
|
+
* price-history endpoint (no key), polled for a building candle. Prices are probabilities,
|
|
13
|
+
* so the y-axis defaults to a percentage. Renders via the framework-agnostic {@link Chart}.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <PolymarketChart tokenId={yesTokenId} bucketSeconds={3600} label="Yes" />
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function PolymarketChart({ tokenId, bucketSeconds = 3600, host, refetchMs, liveUrl, pageSize = 500, height = 420, chartType = "line", indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools = false, showVolume: showVolumeProp = false, volumeProfile: volumeProfileProp, theme, toolbar = true, fitContent = true, label, priceFormat = pct, }) {
|
|
20
|
+
const [secs, setSecs] = useState(bucketSeconds);
|
|
21
|
+
const [type, setType] = useState(chartType);
|
|
22
|
+
const [log, setLog] = useState(false);
|
|
23
|
+
const [vpOn, setVpOn] = useState(!!volumeProfileProp);
|
|
24
|
+
const [drawMode, setDrawMode] = useState("none");
|
|
25
|
+
const [last, setLast] = useState(null);
|
|
26
|
+
const [legend, setLegend] = useState(null);
|
|
27
|
+
const [error, setError] = useState(null);
|
|
28
|
+
const [loading, setLoading] = useState(true);
|
|
29
|
+
const host_ = useRef(null);
|
|
30
|
+
const chart = useRef(null);
|
|
31
|
+
const th = useMemo(() => ({ ...DEFAULT_THEME, ...(theme || {}) }), [theme]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!host_.current)
|
|
34
|
+
return;
|
|
35
|
+
const c = new Chart(host_.current, {
|
|
36
|
+
height,
|
|
37
|
+
theme: th,
|
|
38
|
+
emptyText: "Loading…",
|
|
39
|
+
indicators,
|
|
40
|
+
oscillators,
|
|
41
|
+
drawings,
|
|
42
|
+
onDrawingsChange,
|
|
43
|
+
onDrawModeChange: setDrawMode,
|
|
44
|
+
showVolume: showVolumeProp,
|
|
45
|
+
volumeProfile: volumeProfileProp,
|
|
46
|
+
onCrosshair: setLegend,
|
|
47
|
+
fitContent,
|
|
48
|
+
priceFormat,
|
|
49
|
+
});
|
|
50
|
+
chart.current = c;
|
|
51
|
+
return () => {
|
|
52
|
+
c.destroy();
|
|
53
|
+
chart.current = null;
|
|
54
|
+
};
|
|
55
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
+
}, []);
|
|
57
|
+
useEffect(() => { chart.current?.setHeight(height); }, [height]);
|
|
58
|
+
useEffect(() => { chart.current?.setChartType(type); }, [type]);
|
|
59
|
+
useEffect(() => { chart.current?.setTheme(th); }, [th]);
|
|
60
|
+
useEffect(() => { chart.current?.setIndicators(indicators ?? []); }, [indicators]);
|
|
61
|
+
useEffect(() => { chart.current?.setOscillators(oscillators ?? []); }, [oscillators]);
|
|
62
|
+
useEffect(() => { chart.current?.setDrawMode(drawMode); }, [drawMode]);
|
|
63
|
+
useEffect(() => { chart.current?.setLogScale(log); }, [log]);
|
|
64
|
+
useEffect(() => { chart.current?.setEmptyText(loading ? "" : "no data"); }, [loading]); // overlay handles loading
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
chart.current?.setVolumeProfile(vpOn ? (typeof volumeProfileProp === "object" ? volumeProfileProp : true) : false);
|
|
67
|
+
}, [vpOn, volumeProfileProp]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!chart.current)
|
|
70
|
+
return;
|
|
71
|
+
setLoading(true);
|
|
72
|
+
setError(null);
|
|
73
|
+
const feed = polymarketFeed({ tokenId, bucketSeconds: secs, host, refetchMs, liveUrl });
|
|
74
|
+
const conn = connectFeed(chart.current, feed, {
|
|
75
|
+
interval: secs,
|
|
76
|
+
pageSize,
|
|
77
|
+
onCandles: (cs) => {
|
|
78
|
+
setLast(cs[cs.length - 1] ?? null);
|
|
79
|
+
setLoading(false);
|
|
80
|
+
setError(null); // a successful page clears a prior transient error
|
|
81
|
+
},
|
|
82
|
+
onError: (e) => {
|
|
83
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
84
|
+
setLoading(false);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return () => conn.disconnect();
|
|
88
|
+
}, [tokenId, secs, host, refetchMs, liveUrl, pageSize]);
|
|
89
|
+
const info = legend ?? last;
|
|
90
|
+
const lc = info && info.c >= info.o ? th.up : th.down;
|
|
91
|
+
const { btn, sepStyle, barStyle, muted } = toolbarUi(th);
|
|
92
|
+
const sep = _jsx("span", { style: sepStyle });
|
|
93
|
+
const ivLabel = BUCKETS.find(([, s]) => s === secs)?.[0] ?? `${secs}s`;
|
|
94
|
+
return (_jsxs("div", { style: { position: "relative", userSelect: "none" }, children: [toolbar && (_jsxs("div", { style: barStyle, children: [BUCKETS.map(([l, s]) => (_jsx("button", { style: btn(secs === s), onClick: () => setSecs(s), children: l }, s))), sep, typeButtons(th, type, setType), sep, _jsx("button", { style: btn(vpOn), title: "Volume-by-price histogram", onClick: () => setVpOn((v) => !v), children: "VP" }), _jsx("button", { style: btn(log), title: "Logarithmic price axis", onClick: () => setLog((v) => !v), children: "Log" }), !hideDrawingTools ? (_jsxs(_Fragment, { children: [sep, drawButtons(th, drawMode, setDrawMode, () => chart.current?.clearDrawings())] })) : null] })), _jsxs("div", { style: { position: "relative" }, children: [_jsxs("div", { style: { position: "absolute", left: 8, top: 4, zIndex: 10, pointerEvents: "none", fontSize: 11, fontFamily: "ui-monospace, monospace" }, children: [_jsxs("div", { style: { color: muted }, children: [label ? `${label} · ` : "", "prob \u00B7 ", ivLabel] }), error ? (_jsx("div", { style: { color: th.down }, children: error })) : info ? (_jsxs("div", { style: { color: lc }, children: ["O ", pct(info.o), " H ", pct(info.h), " L ", pct(info.l), " C ", pct(info.c)] })) : null] }), _jsx("div", { ref: host_ }), loading ? loadingOverlay(th) : null] })] }));
|
|
95
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChartOptions, ChartTheme, ChartType, Drawing, Indicator, Oscillator, Point } from "../core/types";
|
|
1
|
+
import type { ChartOptions, ChartTheme, ChartType, Drawing, Indicator, Oscillator, Point, VolumeProfileConfig } from "../core/types";
|
|
2
2
|
export interface PriceChartProps {
|
|
3
3
|
/** Priced trades — aggregated into candles client-side. `v` is per-trade volume (quote/USD). */
|
|
4
4
|
swaps: Point[];
|
|
@@ -30,14 +30,29 @@ export interface PriceChartProps {
|
|
|
30
30
|
drawings?: Drawing[];
|
|
31
31
|
/** Fired whenever the user adds, moves, or deletes a drawing. */
|
|
32
32
|
onDrawingsChange?: (drawings: Drawing[]) => void;
|
|
33
|
-
/** Hide the trendline / h-line / clear drawing-tool buttons (shown by default). */
|
|
33
|
+
/** Hide the trendline / h-line / fib / rect / clear drawing-tool buttons (shown by default). */
|
|
34
34
|
hideDrawingTools?: boolean;
|
|
35
35
|
/** Show the volume panel (default true). */
|
|
36
36
|
showVolume?: boolean;
|
|
37
|
+
/** Volume-by-price histogram (default off); the toolbar adds a VP toggle when set. */
|
|
38
|
+
volumeProfile?: VolumeProfileConfig;
|
|
39
|
+
/** Reference price for the `baseline` chart type (default: the first visible close). */
|
|
40
|
+
baselinePrice?: number;
|
|
37
41
|
/** Initial series style (default "candle"); the toolbar can switch it after mount. */
|
|
38
42
|
defaultChartType?: ChartType;
|
|
39
43
|
/** Initial bucket interval in seconds (default 300 = 5m); the toolbar can switch it. */
|
|
40
44
|
defaultInterval?: number;
|
|
45
|
+
/** Toolbar interval presets as `[label, seconds]`. Default `1s…1d`. Scope this to the
|
|
46
|
+
* intervals your data actually covers — a thin dataset bucketed at `1h` collapses into a
|
|
47
|
+
* couple of fat candles. (`PriceChart` aggregates the `swaps` you pass; it doesn't fetch
|
|
48
|
+
* more — supply a wider range via `onIntervalChange` if you want coarser buckets to fill.) */
|
|
49
|
+
intervals?: [string, number][];
|
|
50
|
+
/** Fired when the user picks a different interval (seconds) — load a wider range here. */
|
|
51
|
+
onIntervalChange?: (seconds: number) => void;
|
|
52
|
+
/** Fired when the user pans/zooms within ~one viewport of the oldest candle. Prepend
|
|
53
|
+
* older trades to `swaps` here for infinite back-history (`PriceChart` re-aggregates and
|
|
54
|
+
* the right-anchored view keeps the position stable). No-op if you pass a fixed array. */
|
|
55
|
+
onLoadOlder?: () => void;
|
|
41
56
|
/** Spread candles across the full width instead of right-anchoring a capped slot.
|
|
42
57
|
* Default `true` — fills the container for sparse series. */
|
|
43
58
|
fitContent?: boolean;
|
|
@@ -63,4 +78,4 @@ export interface PriceChartProps {
|
|
|
63
78
|
* OHLCV legend, X/Y zoom + pan, interval / USD-ETH / price-mcap / fullscreen toolbar.
|
|
64
79
|
* Wraps the framework-agnostic {@link Chart}; styled inline (no CSS framework needed).
|
|
65
80
|
*/
|
|
66
|
-
export declare function PriceChart({ swaps, ethUsd, supply: supplyProp, tokenAddress, decimals, symbol, quote, height, rpcUrl, theme, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools, showVolume: showVolumeProp, defaultChartType, defaultInterval, fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, options, }: PriceChartProps): import("react").JSX.Element;
|
|
81
|
+
export declare function PriceChart({ swaps, ethUsd, supply: supplyProp, tokenAddress, decimals, symbol, quote, height, rpcUrl, theme, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools, showVolume: showVolumeProp, volumeProfile: volumeProfileProp, baselinePrice, defaultChartType, defaultInterval, intervals, onIntervalChange, onLoadOlder, fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, options, }: PriceChartProps): import("react").JSX.Element;
|
package/dist/react/PriceChart.js
CHANGED
|
@@ -5,14 +5,15 @@ import { buildOHLC } from "../core/ohlc";
|
|
|
5
5
|
import { formatValue, formatVolume } from "../core/format";
|
|
6
6
|
import { DEFAULT_THEME } from "../core/theme";
|
|
7
7
|
import { toolbarUi } from "./ui";
|
|
8
|
-
|
|
8
|
+
import { typeButtons, drawButtons } from "./ui";
|
|
9
|
+
const DEFAULT_IVS = [["1s", 1], ["1m", 60], ["5m", 300], ["15m", 900], ["1h", 3600], ["4h", 14400], ["1d", 86400]];
|
|
9
10
|
const supplyCache = new Map();
|
|
10
11
|
/**
|
|
11
12
|
* A self-contained trading chart: candles/line, volume panel, crosshair + axis labels,
|
|
12
13
|
* OHLCV legend, X/Y zoom + pan, interval / USD-ETH / price-mcap / fullscreen toolbar.
|
|
13
14
|
* Wraps the framework-agnostic {@link Chart}; styled inline (no CSS framework needed).
|
|
14
15
|
*/
|
|
15
|
-
export function PriceChart({ swaps, ethUsd = 0, supply: supplyProp = 0, tokenAddress, decimals = 18, symbol = "", quote = "WETH", height = 420, rpcUrl = "https://cloudflare-eth.com", theme, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools = false, showVolume: showVolumeProp = true, defaultChartType = "candle", defaultInterval = 300, fitContent = true, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, options, }) {
|
|
16
|
+
export function PriceChart({ swaps, ethUsd = 0, supply: supplyProp = 0, tokenAddress, decimals = 18, symbol = "", quote = "WETH", height = 420, rpcUrl = "https://cloudflare-eth.com", theme, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools = false, showVolume: showVolumeProp = true, volumeProfile: volumeProfileProp, baselinePrice, defaultChartType = "candle", defaultInterval = 300, intervals = DEFAULT_IVS, onIntervalChange, onLoadOlder, fitContent = true, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont, options, }) {
|
|
16
17
|
const [iv, setIv] = useState(defaultInterval);
|
|
17
18
|
const [type, setType] = useState(defaultChartType);
|
|
18
19
|
const [denom, setDenom] = useState("USD");
|
|
@@ -20,12 +21,16 @@ export function PriceChart({ swaps, ethUsd = 0, supply: supplyProp = 0, tokenAdd
|
|
|
20
21
|
const [flip, setFlip] = useState(false);
|
|
21
22
|
const [log, setLog] = useState(!!options?.logScale);
|
|
22
23
|
const [showVolume, setShowVolume] = useState(showVolumeProp);
|
|
24
|
+
const [vpOn, setVpOn] = useState(!!volumeProfileProp);
|
|
23
25
|
const [drawMode, setDrawMode] = useState("none");
|
|
24
26
|
const [full, setFull] = useState(false);
|
|
25
27
|
const [fetchedSupply, setFetchedSupply] = useState(0);
|
|
26
28
|
const [legend, setLegend] = useState(null);
|
|
27
29
|
const host = useRef(null);
|
|
28
30
|
const chart = useRef(null);
|
|
31
|
+
// keep the latest onLoadOlder callable from the once-created chart without re-mounting it.
|
|
32
|
+
const onLoadOlderRef = useRef(onLoadOlder);
|
|
33
|
+
onLoadOlderRef.current = onLoadOlder;
|
|
29
34
|
const th = useMemo(() => ({ ...DEFAULT_THEME, ...(theme || {}) }), [theme]);
|
|
30
35
|
const H = full && typeof window !== "undefined" ? window.innerHeight - 46 : height;
|
|
31
36
|
// A direct `supply` prop wins; otherwise market cap needs circulating supply, read once
|
|
@@ -68,12 +73,15 @@ export function PriceChart({ swaps, ethUsd = 0, supply: supplyProp = 0, tokenAdd
|
|
|
68
73
|
height: H,
|
|
69
74
|
theme: th,
|
|
70
75
|
onCrosshair: setLegend,
|
|
76
|
+
onNeedHistory: () => onLoadOlderRef.current?.(),
|
|
71
77
|
indicators,
|
|
72
78
|
oscillators,
|
|
73
79
|
drawings,
|
|
74
80
|
onDrawingsChange,
|
|
75
81
|
onDrawModeChange: setDrawMode,
|
|
76
82
|
showVolume: showVolumeProp,
|
|
83
|
+
volumeProfile: volumeProfileProp,
|
|
84
|
+
baselinePrice,
|
|
77
85
|
fitContent,
|
|
78
86
|
maxBarWidth,
|
|
79
87
|
maxBodyWidth,
|
|
@@ -100,6 +108,10 @@ export function PriceChart({ swaps, ethUsd = 0, supply: supplyProp = 0, tokenAdd
|
|
|
100
108
|
useEffect(() => { chart.current?.setDrawMode(drawMode); }, [drawMode]);
|
|
101
109
|
useEffect(() => { chart.current?.setShowVolume(showVolume); }, [showVolume]);
|
|
102
110
|
useEffect(() => { chart.current?.setLogScale(log); }, [log]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
chart.current?.setVolumeProfile(vpOn ? (typeof volumeProfileProp === "object" ? volumeProfileProp : true) : false);
|
|
113
|
+
}, [vpOn, volumeProfileProp]);
|
|
114
|
+
useEffect(() => { chart.current?.setBaseline(baselinePrice); }, [baselinePrice]);
|
|
103
115
|
useEffect(() => {
|
|
104
116
|
chart.current?.setAxis({ fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont });
|
|
105
117
|
}, [fitContent, maxBarWidth, maxBodyWidth, priceFormat, timeFormat, priceTicks, timeTicks, axisFont]);
|
|
@@ -107,8 +119,8 @@ export function PriceChart({ swaps, ethUsd = 0, supply: supplyProp = 0, tokenAdd
|
|
|
107
119
|
const { btn, sepStyle, barStyle, muted } = toolbarUi(th);
|
|
108
120
|
const sep = _jsx("span", { style: sepStyle });
|
|
109
121
|
const lc = legend && legend.c >= legend.o ? th.up : th.down;
|
|
110
|
-
const ivLabel =
|
|
122
|
+
const ivLabel = intervals.find(([, s]) => s === iv)?.[0] || "";
|
|
111
123
|
return (_jsxs("div", { style: full
|
|
112
124
|
? { position: "fixed", inset: 0, zIndex: 50, background: th.background ?? "#0a0a0a", padding: 8, userSelect: "none" }
|
|
113
|
-
: { position: "relative", userSelect: "none" }, children: [_jsxs("div", { style: barStyle, children: [
|
|
125
|
+
: { position: "relative", userSelect: "none" }, children: [_jsxs("div", { style: barStyle, children: [intervals.map(([l, s]) => (_jsx("button", { style: btn(iv === s), onClick: () => { setIv(s); onIntervalChange?.(s); }, children: l }, s))), sep, typeButtons(th, type, setType), canMcap ? (_jsxs(_Fragment, { children: [sep, _jsx("button", { style: btn(!mcap), onClick: () => setMcap(false), children: "Price" }), _jsx("button", { style: btn(mcap), title: "Market cap (price \u00D7 supply)", onClick: () => setMcap(true), children: "MCap" })] })) : null, ethUsd > 0 ? (_jsxs(_Fragment, { children: [sep, _jsx("button", { style: btn(denom === "USD"), onClick: () => setDenom("USD"), children: "USD" }), _jsx("button", { style: btn(denom === "ETH"), title: "Denominate in ETH (price \u00F7 ETH/USD)", onClick: () => setDenom("ETH"), children: "ETH" })] })) : null, !showMcap ? (_jsxs(_Fragment, { children: [sep, _jsx("button", { style: btn(flip), title: "Invert pair", onClick: () => setFlip((f) => !f), children: "\u21C4" })] })) : null, sep, _jsx("button", { style: btn(showVolume), title: "Toggle volume panel", onClick: () => setShowVolume((v) => !v), children: "Vol" }), _jsx("button", { style: btn(vpOn), title: "Volume-by-price histogram", onClick: () => setVpOn((v) => !v), children: "VP" }), _jsx("button", { style: btn(log), title: "Logarithmic price axis", onClick: () => setLog((v) => !v), children: "Log" }), !hideDrawingTools ? (_jsxs(_Fragment, { children: [sep, drawButtons(th, drawMode, setDrawMode, () => chart.current?.clearDrawings())] })) : null, _jsx("button", { style: { ...btn(full), marginLeft: "auto" }, title: "Fullscreen", onClick: () => setFull((f) => !f), children: "\u26F6" })] }), _jsxs("div", { style: { position: "relative" }, children: [_jsxs("div", { style: { position: "absolute", left: 8, top: 4, zIndex: 10, pointerEvents: "none", fontSize: 11, fontFamily: "ui-monospace, monospace" }, children: [_jsxs("div", { style: { color: muted }, children: [symbol || "—", showMcap ? " · mcap" : flip ? ` ${quote}/${symbol}` : ` /${quote}`, " \u00B7 ", ivLabel] }), legend ? (_jsxs("div", { style: { color: lc }, children: ["O ", formatValue(legend.o), " H ", formatValue(legend.h), " L ", formatValue(legend.l), " C ", formatValue(legend.c), " ", _jsxs("span", { style: { color: muted }, children: ["Vol ", formatVolume(legend.vol)] })] })) : null] }), _jsx("div", { ref: host })] })] }));
|
|
114
126
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ChartTheme, ChartType, Drawing, Indicator, Oscillator, VolumeProfileConfig } from "../core/types";
|
|
2
|
+
export interface SignalsChartProps {
|
|
3
|
+
/** Token to chart — pool address, base-token address, or pair symbol (e.g. "PEPE"). */
|
|
4
|
+
token: string;
|
|
5
|
+
/** Signal Radar engine base URL (default https://signals.livo.build). */
|
|
6
|
+
url?: string;
|
|
7
|
+
/** Initial candle bucket in seconds (default 300 = 5m); the toolbar can switch it. */
|
|
8
|
+
bucketSeconds?: number;
|
|
9
|
+
/** Live poll cadence in ms (default 15s; 0 = fetch once). */
|
|
10
|
+
refetchMs?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
chartType?: ChartType;
|
|
13
|
+
indicators?: Indicator[];
|
|
14
|
+
oscillators?: Oscillator[];
|
|
15
|
+
drawings?: Drawing[];
|
|
16
|
+
onDrawingsChange?: (drawings: Drawing[]) => void;
|
|
17
|
+
hideDrawingTools?: boolean;
|
|
18
|
+
/** Show the (always-empty) volume panel — off by default since the snapshot has no volume. */
|
|
19
|
+
showVolume?: boolean;
|
|
20
|
+
volumeProfile?: VolumeProfileConfig;
|
|
21
|
+
theme?: Partial<ChartTheme>;
|
|
22
|
+
toolbar?: boolean;
|
|
23
|
+
fitContent?: boolean;
|
|
24
|
+
/** y-axis price formatter (default a range-aware USD value). */
|
|
25
|
+
priceFormat?: (value: number) => string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A turnkey, live chart for a token tracked by the Livo signals engine ("Signal Radar").
|
|
29
|
+
* Seeds from the engine snapshot's recent-closes sparkline and polls for a building candle
|
|
30
|
+
* (no key). History is shallow (the snapshot's `spark`) — for deep history use a chart
|
|
31
|
+
* backed by an indexer feed. Renders via the framework-agnostic {@link Chart}.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <SignalsChart token="PEPE" bucketSeconds={300} />
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function SignalsChart({ token, url, bucketSeconds, refetchMs, height, chartType, indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools, showVolume: showVolumeProp, volumeProfile: volumeProfileProp, theme, toolbar, fitContent, priceFormat, }: SignalsChartProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Chart } from "../core/chart";
|
|
4
|
+
import { connectFeed } from "../core/feed";
|
|
5
|
+
import { signalsFeed } from "../core/signals";
|
|
6
|
+
import { formatValue } from "../core/format";
|
|
7
|
+
import { DEFAULT_THEME } from "../core/theme";
|
|
8
|
+
import { toolbarUi, typeButtons, drawButtons, loadingOverlay } from "./ui";
|
|
9
|
+
const BUCKETS = [["1m", 60], ["5m", 300], ["15m", 900], ["1h", 3600], ["4h", 14400], ["1d", 86400]];
|
|
10
|
+
/**
|
|
11
|
+
* A turnkey, live chart for a token tracked by the Livo signals engine ("Signal Radar").
|
|
12
|
+
* Seeds from the engine snapshot's recent-closes sparkline and polls for a building candle
|
|
13
|
+
* (no key). History is shallow (the snapshot's `spark`) — for deep history use a chart
|
|
14
|
+
* backed by an indexer feed. Renders via the framework-agnostic {@link Chart}.
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <SignalsChart token="PEPE" bucketSeconds={300} />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function SignalsChart({ token, url, bucketSeconds = 300, refetchMs, height = 420, chartType = "line", indicators, oscillators, drawings, onDrawingsChange, hideDrawingTools = false, showVolume: showVolumeProp = false, volumeProfile: volumeProfileProp, theme, toolbar = true, fitContent = true, priceFormat, }) {
|
|
21
|
+
const [secs, setSecs] = useState(bucketSeconds);
|
|
22
|
+
const [type, setType] = useState(chartType);
|
|
23
|
+
const [log, setLog] = useState(false);
|
|
24
|
+
const [vpOn, setVpOn] = useState(!!volumeProfileProp);
|
|
25
|
+
const [drawMode, setDrawMode] = useState("none");
|
|
26
|
+
const [last, setLast] = useState(null);
|
|
27
|
+
const [legend, setLegend] = useState(null);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
const host = useRef(null);
|
|
31
|
+
const chart = useRef(null);
|
|
32
|
+
const th = useMemo(() => ({ ...DEFAULT_THEME, ...(theme || {}) }), [theme]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!host.current)
|
|
35
|
+
return;
|
|
36
|
+
const c = new Chart(host.current, {
|
|
37
|
+
height,
|
|
38
|
+
theme: th,
|
|
39
|
+
emptyText: "Loading…",
|
|
40
|
+
indicators,
|
|
41
|
+
oscillators,
|
|
42
|
+
drawings,
|
|
43
|
+
onDrawingsChange,
|
|
44
|
+
onDrawModeChange: setDrawMode,
|
|
45
|
+
showVolume: showVolumeProp,
|
|
46
|
+
volumeProfile: volumeProfileProp,
|
|
47
|
+
onCrosshair: setLegend,
|
|
48
|
+
fitContent,
|
|
49
|
+
priceFormat,
|
|
50
|
+
});
|
|
51
|
+
chart.current = c;
|
|
52
|
+
return () => {
|
|
53
|
+
c.destroy();
|
|
54
|
+
chart.current = null;
|
|
55
|
+
};
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
}, []);
|
|
58
|
+
useEffect(() => { chart.current?.setHeight(height); }, [height]);
|
|
59
|
+
useEffect(() => { chart.current?.setChartType(type); }, [type]);
|
|
60
|
+
useEffect(() => { chart.current?.setTheme(th); }, [th]);
|
|
61
|
+
useEffect(() => { chart.current?.setIndicators(indicators ?? []); }, [indicators]);
|
|
62
|
+
useEffect(() => { chart.current?.setOscillators(oscillators ?? []); }, [oscillators]);
|
|
63
|
+
useEffect(() => { chart.current?.setDrawMode(drawMode); }, [drawMode]);
|
|
64
|
+
useEffect(() => { chart.current?.setLogScale(log); }, [log]);
|
|
65
|
+
useEffect(() => { chart.current?.setEmptyText(loading ? "" : "no data"); }, [loading]); // overlay handles loading
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
chart.current?.setVolumeProfile(vpOn ? (typeof volumeProfileProp === "object" ? volumeProfileProp : true) : false);
|
|
68
|
+
}, [vpOn, volumeProfileProp]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!chart.current)
|
|
71
|
+
return;
|
|
72
|
+
setLoading(true);
|
|
73
|
+
setError(null);
|
|
74
|
+
const feed = signalsFeed({ token, url, bucketSeconds: secs, refetchMs });
|
|
75
|
+
const conn = connectFeed(chart.current, feed, {
|
|
76
|
+
interval: secs,
|
|
77
|
+
onCandles: (cs) => {
|
|
78
|
+
setLast(cs[cs.length - 1] ?? null);
|
|
79
|
+
setLoading(false);
|
|
80
|
+
setError(null); // a successful page clears a prior transient error
|
|
81
|
+
},
|
|
82
|
+
onError: (e) => {
|
|
83
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
84
|
+
setLoading(false);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return () => conn.disconnect();
|
|
88
|
+
}, [token, url, secs, refetchMs]);
|
|
89
|
+
const info = legend ?? last;
|
|
90
|
+
const lc = info && info.c >= info.o ? th.up : th.down;
|
|
91
|
+
const { btn, sepStyle, barStyle, muted } = toolbarUi(th);
|
|
92
|
+
const sep = _jsx("span", { style: sepStyle });
|
|
93
|
+
const ivLabel = BUCKETS.find(([, s]) => s === secs)?.[0] ?? `${secs}s`;
|
|
94
|
+
return (_jsxs("div", { style: { position: "relative", userSelect: "none" }, children: [toolbar && (_jsxs("div", { style: barStyle, children: [BUCKETS.map(([l, s]) => (_jsx("button", { style: btn(secs === s), onClick: () => setSecs(s), children: l }, s))), sep, typeButtons(th, type, setType), sep, _jsx("button", { style: btn(vpOn), title: "Volume-by-price histogram", onClick: () => setVpOn((v) => !v), children: "VP" }), _jsx("button", { style: btn(log), title: "Logarithmic price axis", onClick: () => setLog((v) => !v), children: "Log" }), !hideDrawingTools ? (_jsxs(_Fragment, { children: [sep, drawButtons(th, drawMode, setDrawMode, () => chart.current?.clearDrawings())] })) : null] })), _jsxs("div", { style: { position: "relative" }, children: [_jsxs("div", { style: { position: "absolute", left: 8, top: 4, zIndex: 10, pointerEvents: "none", fontSize: 11, fontFamily: "ui-monospace, monospace" }, children: [_jsxs("div", { style: { color: muted }, children: [token.length > 14 ? `${token.slice(0, 6)}…${token.slice(-4)}` : token, " \u00B7 ", ivLabel, " \u00B7 radar"] }), error ? (_jsx("div", { style: { color: th.down }, children: error })) : info ? (_jsxs("div", { style: { color: lc }, children: ["O ", formatValue(info.o), " H ", formatValue(info.h), " L ", formatValue(info.l), " C ", formatValue(info.c)] })) : null] }), _jsx("div", { ref: host }), loading ? loadingOverlay(th) : null] })] }));
|
|
95
|
+
}
|