@livo-build/charts 0.2.1 → 0.2.3
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 +209 -20
- package/dist/core/chart.d.ts +105 -4
- package/dist/core/chart.js +482 -39
- package/dist/core/feed.js +27 -6
- package/dist/core/format.d.ts +13 -1
- package/dist/core/format.js +38 -3
- package/dist/core/indicators.d.ts +50 -0
- package/dist/core/indicators.js +181 -0
- package/dist/core/ohlc.d.ts +10 -0
- package/dist/core/ohlc.js +30 -0
- package/dist/core/polymarket.d.ts +44 -0
- package/dist/core/polymarket.js +92 -0
- package/dist/core/renderer.d.ts +96 -1
- package/dist/core/renderer.js +534 -64
- package/dist/core/signals.d.ts +63 -0
- package/dist/core/signals.js +234 -0
- package/dist/core/theme.d.ts +5 -1
- package/dist/core/theme.js +33 -12
- package/dist/core/types.d.ts +102 -3
- package/dist/index.d.ts +13 -8
- package/dist/index.js +8 -6
- package/dist/react/HyperliquidChart.d.ts +30 -2
- package/dist/react/HyperliquidChart.js +47 -17
- package/dist/react/PolymarketChart.d.ts +40 -0
- package/dist/react/PolymarketChart.js +95 -0
- package/dist/react/PriceChart.d.ts +54 -4
- package/dist/react/PriceChart.js +66 -27
- package/dist/react/SignalsChart.d.ts +37 -0
- package/dist/react/SignalsChart.js +95 -0
- package/dist/react/ui.d.ts +24 -0
- package/dist/react/ui.js +75 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Candle, Point } from "./types";
|
|
2
|
+
import type { ChartFeed } from "./feed";
|
|
3
|
+
/** The public Livo signals engine. */
|
|
4
|
+
export declare const DEFAULT_SIGNALS_URL = "https://signals.livo.build";
|
|
5
|
+
/** The fields of a Signal Radar market this adapter needs. `spark` is recent closes (oldest→newest). */
|
|
6
|
+
export interface SignalsMarket {
|
|
7
|
+
pair: string;
|
|
8
|
+
base: string;
|
|
9
|
+
pool: string;
|
|
10
|
+
price: number;
|
|
11
|
+
spark: number[];
|
|
12
|
+
}
|
|
13
|
+
/** Fetch one Signal Radar market (price + spark) from the engine snapshot, or null if absent. */
|
|
14
|
+
export declare function fetchSignalsMarket(token: string, url?: string): Promise<SignalsMarket | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch a page of indexed swaps for a pool from the engine's GraphQL API (`allSwaps`,
|
|
17
|
+
* newest-first), mapped to priced {@link Point}s. `pool` must be the 0x-prefixed address.
|
|
18
|
+
* Returns one point per swap (unpriceable swaps keep `p: 0` and are dropped by `buildOHLC`),
|
|
19
|
+
* so the caller can advance its offset by `points.length`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function fetchSignalsSwaps(pool: string, opts?: {
|
|
22
|
+
url?: string;
|
|
23
|
+
first?: number;
|
|
24
|
+
offset?: number;
|
|
25
|
+
}): Promise<Point[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Drop swaps whose price is a wild outlier — the engine occasionally mis-prices historical
|
|
28
|
+
* swaps (e.g. a thin pool's old trades priced at $2.5M for a $3 token). Uses the median of
|
|
29
|
+
* the newest swaps as a robust reference (recent prices are reliable) and rejects anything
|
|
30
|
+
* more than `factor`× off it. Input is newest-first; output preserves order.
|
|
31
|
+
*/
|
|
32
|
+
export declare function rejectPriceOutliers(pts: Point[], factor?: number): Point[];
|
|
33
|
+
/** Turn a `spark` (recent closes, oldest→newest) into flat candles ending at `nowSec`. */
|
|
34
|
+
export declare function sparkCandles(spark: number[], bucketSeconds: number, nowSec: number, lastClose?: number): Candle[];
|
|
35
|
+
export interface SignalsFeedOptions {
|
|
36
|
+
/** Token to chart — pool address, base-token address, or pair symbol (e.g. "PEPE"). */
|
|
37
|
+
token: string;
|
|
38
|
+
/** Engine base URL (default https://signals.livo.build). */
|
|
39
|
+
url?: string;
|
|
40
|
+
/** Candle bucket size in seconds (default 300 = 5m). */
|
|
41
|
+
bucketSeconds?: number;
|
|
42
|
+
/** Swaps fetched per history page from the index (default 1500); older pages load lazily. */
|
|
43
|
+
swapPageSize?: number;
|
|
44
|
+
/** Candles to load before the first paint, so the chart opens filled rather than a sliver
|
|
45
|
+
* on the right (default 160). Active pools pack many swaps per bucket, so this may take a
|
|
46
|
+
* few index pages. */
|
|
47
|
+
initialCandles?: number;
|
|
48
|
+
/** Force `"spark"` (snapshot closes only) instead of the default `"index"` (GraphQL history). */
|
|
49
|
+
history?: "index" | "spark";
|
|
50
|
+
/** Live poll cadence in ms (default 15s; 0 disables the live poll). */
|
|
51
|
+
refetchMs?: number;
|
|
52
|
+
/** Override "now" (ms) — for tests/determinism. */
|
|
53
|
+
now?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A {@link ChartFeed} for a Signal Radar token. History comes from the engine's indexed
|
|
57
|
+
* swaps (`/graphql allSwaps` for the pool → bucketed into OHLC at `bucketSeconds`), paged
|
|
58
|
+
* lazily on back-scroll, so it shows real candles days deep. The live candle is polled from
|
|
59
|
+
* `/data` (current price). If the index is unreachable (or `history: "spark"`), it falls back
|
|
60
|
+
* to seeding from the snapshot's `spark`. Pass it to `connectFeed` with `bucketSeconds` as
|
|
61
|
+
* the interval.
|
|
62
|
+
*/
|
|
63
|
+
export declare function signalsFeed(opts: SignalsFeedOptions): ChartFeed;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Signal Radar adapter — chart a token tracked by the Livo on-chain signals engine.
|
|
2
|
+
// The engine ("Signal Radar") indexes every Ethereum mainnet DEX swap into Postgres and
|
|
3
|
+
// exposes it over a PostGraphile GraphQL API (`/graphql`), plus a live `/data` snapshot
|
|
4
|
+
// (no key) with the current USD `price` and a short `spark` of recent closes.
|
|
5
|
+
//
|
|
6
|
+
// This adapter pulls REAL history from the index — `allSwaps` for the pool, bucketed into
|
|
7
|
+
// OHLC candles at any interval — so it shows true 1m / 5m candles days back (the index has
|
|
8
|
+
// no TTL), and lazy-loads older pages as you scroll. It polls `/data` for the live, building
|
|
9
|
+
// candle. If `/graphql` is unreachable (e.g. CORS), it falls back to seeding from `spark`.
|
|
10
|
+
//
|
|
11
|
+
// import { Chart, connectFeed, signalsFeed } from "@livo-build/charts";
|
|
12
|
+
// const chart = new Chart(el, { height: 420 });
|
|
13
|
+
// connectFeed(chart, signalsFeed({ token: "PEPE" }), { interval: 300 });
|
|
14
|
+
import { buildOHLC } from "./ohlc";
|
|
15
|
+
/** The public Livo signals engine. */
|
|
16
|
+
export const DEFAULT_SIGNALS_URL = "https://signals.livo.build";
|
|
17
|
+
/** fetch with an abort timeout so a stalled engine request can't wedge lazy-loading forever. */
|
|
18
|
+
async function fetchT(input, init = {}, timeoutMs = 12000) {
|
|
19
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
20
|
+
const timer = ac ? setTimeout(() => ac.abort(), timeoutMs) : undefined;
|
|
21
|
+
try {
|
|
22
|
+
return await fetch(input, ac ? { ...init, signal: ac.signal } : init);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
if (timer)
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const bare = (s) => s.replace(/^0x/i, "").toLowerCase();
|
|
30
|
+
/** Find the snapshot market for a token by pool address, base-token address, or pair symbol. */
|
|
31
|
+
function findMarket(markets, token) {
|
|
32
|
+
const q = bare(token);
|
|
33
|
+
const byAddr = markets.find((m) => bare(m.pool) === q || bare(m.base) === q);
|
|
34
|
+
if (byAddr)
|
|
35
|
+
return byAddr;
|
|
36
|
+
const sym = token.toLowerCase();
|
|
37
|
+
// symbol match → the deepest (first listed; the snapshot is hottest-first) market.
|
|
38
|
+
return markets.find((m) => m.pair.toLowerCase().split("/")[0] === sym);
|
|
39
|
+
}
|
|
40
|
+
/** Fetch one Signal Radar market (price + spark) from the engine snapshot, or null if absent. */
|
|
41
|
+
export async function fetchSignalsMarket(token, url = DEFAULT_SIGNALS_URL) {
|
|
42
|
+
const res = await fetchT(`${url}/data`);
|
|
43
|
+
if (!res.ok)
|
|
44
|
+
throw new Error("signals /data " + res.status);
|
|
45
|
+
const snap = (await res.json());
|
|
46
|
+
return findMarket(snap.markets ?? [], token) ?? null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetch a page of indexed swaps for a pool from the engine's GraphQL API (`allSwaps`,
|
|
50
|
+
* newest-first), mapped to priced {@link Point}s. `pool` must be the 0x-prefixed address.
|
|
51
|
+
* Returns one point per swap (unpriceable swaps keep `p: 0` and are dropped by `buildOHLC`),
|
|
52
|
+
* so the caller can advance its offset by `points.length`.
|
|
53
|
+
*/
|
|
54
|
+
export async function fetchSignalsSwaps(pool, opts = {}) {
|
|
55
|
+
const url = opts.url ?? DEFAULT_SIGNALS_URL;
|
|
56
|
+
const first = opts.first ?? 1500;
|
|
57
|
+
const offset = opts.offset ?? 0;
|
|
58
|
+
const query = `{ allSwaps(condition:{pool:"${pool}"}, orderBy: BLOCK_TIME_DESC, first:${first}, offset:${offset}){ nodes{ blockTime priceUsd amountUsd } } }`;
|
|
59
|
+
const res = await fetchT(`${url}/graphql`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "content-type": "application/json" },
|
|
62
|
+
body: JSON.stringify({ query }),
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok)
|
|
65
|
+
throw new Error("signals /graphql " + res.status);
|
|
66
|
+
const j = (await res.json());
|
|
67
|
+
const nodes = j?.data?.allSwaps?.nodes;
|
|
68
|
+
if (!nodes)
|
|
69
|
+
throw new Error("signals /graphql: no allSwaps");
|
|
70
|
+
return nodes.map((s) => ({ t: +s.blockTime, p: +(s.priceUsd ?? 0), v: +(s.amountUsd ?? 0) || 0 }));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Drop swaps whose price is a wild outlier — the engine occasionally mis-prices historical
|
|
74
|
+
* swaps (e.g. a thin pool's old trades priced at $2.5M for a $3 token). Uses the median of
|
|
75
|
+
* the newest swaps as a robust reference (recent prices are reliable) and rejects anything
|
|
76
|
+
* more than `factor`× off it. Input is newest-first; output preserves order.
|
|
77
|
+
*/
|
|
78
|
+
export function rejectPriceOutliers(pts, factor = 100) {
|
|
79
|
+
const priced = pts.filter((p) => p.p > 0 && isFinite(p.p));
|
|
80
|
+
if (priced.length < 8)
|
|
81
|
+
return priced;
|
|
82
|
+
const recent = priced
|
|
83
|
+
.slice(0, 200)
|
|
84
|
+
.map((p) => p.p)
|
|
85
|
+
.sort((a, b) => a - b);
|
|
86
|
+
const ref = recent[Math.floor(recent.length / 2)] || 0;
|
|
87
|
+
if (!(ref > 0))
|
|
88
|
+
return priced;
|
|
89
|
+
return priced.filter((p) => p.p >= ref / factor && p.p <= ref * factor);
|
|
90
|
+
}
|
|
91
|
+
/** Turn a `spark` (recent closes, oldest→newest) into flat candles ending at `nowSec`. */
|
|
92
|
+
export function sparkCandles(spark, bucketSeconds, nowSec, lastClose) {
|
|
93
|
+
const n = spark.length;
|
|
94
|
+
const endBucket = Math.floor(nowSec / bucketSeconds) * bucketSeconds;
|
|
95
|
+
return spark.map((p, i) => {
|
|
96
|
+
const c = i === n - 1 && lastClose != null ? lastClose : p;
|
|
97
|
+
const time = endBucket - (n - 1 - i) * bucketSeconds;
|
|
98
|
+
return { time, o: p, h: Math.max(p, c), l: Math.min(p, c), c, vol: 0 };
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A {@link ChartFeed} for a Signal Radar token. History comes from the engine's indexed
|
|
103
|
+
* swaps (`/graphql allSwaps` for the pool → bucketed into OHLC at `bucketSeconds`), paged
|
|
104
|
+
* lazily on back-scroll, so it shows real candles days deep. The live candle is polled from
|
|
105
|
+
* `/data` (current price). If the index is unreachable (or `history: "spark"`), it falls back
|
|
106
|
+
* to seeding from the snapshot's `spark`. Pass it to `connectFeed` with `bucketSeconds` as
|
|
107
|
+
* the interval.
|
|
108
|
+
*/
|
|
109
|
+
export function signalsFeed(opts) {
|
|
110
|
+
const url = opts.url ?? DEFAULT_SIGNALS_URL;
|
|
111
|
+
const secs = opts.bucketSeconds ?? 300;
|
|
112
|
+
const token = opts.token;
|
|
113
|
+
const swapPage = opts.swapPageSize ?? 1500;
|
|
114
|
+
const initialCandles = opts.initialCandles ?? 160;
|
|
115
|
+
const sparkOnly = opts.history === "spark";
|
|
116
|
+
const MAX_SWAPS = 60000; // memory/CPU bound on deep back-scroll
|
|
117
|
+
// Resolve the market once (gives the pool to query + the spark fallback), cached.
|
|
118
|
+
let marketP = null;
|
|
119
|
+
const market = () => (marketP ?? (marketP = fetchSignalsMarket(token, url).catch(() => null)));
|
|
120
|
+
// Accumulate ALL fetched swaps and re-bucket the full set each page, so a candle bucket
|
|
121
|
+
// that straddles a swap-page boundary is always COMPLETE (no partial O/H/L/volume). The
|
|
122
|
+
// feed returns the full ascending candle set; connectFeed merges it (replacing the partial
|
|
123
|
+
// boundary it already showed). `offset` pages older swaps; bounded by MAX_SWAPS.
|
|
124
|
+
// When `token` is a pool address, query the index by it DIRECTLY — the index keys on the
|
|
125
|
+
// pool (permanent) so history works even after the market rotates out of the live `/data`
|
|
126
|
+
// snapshot. /data is then only needed for the spark fallback + the live price.
|
|
127
|
+
const directPool = /^(0x)?[0-9a-fA-F]{40}$/.test(token) ? `0x${bare(token)}` : null;
|
|
128
|
+
let allSwaps = [];
|
|
129
|
+
let offset = 0;
|
|
130
|
+
let sparkMode = sparkOnly; // flipped on if the index is unreachable
|
|
131
|
+
let histLatest = null; // newest indexed candle (seeds the live bucket)
|
|
132
|
+
const spark = async (m) => {
|
|
133
|
+
if (!m)
|
|
134
|
+
return [];
|
|
135
|
+
const nowSec = (opts.now ?? Date.now()) / 1000;
|
|
136
|
+
const cs = sparkCandles(m.spark ?? [], secs, nowSec, m.price);
|
|
137
|
+
histLatest = cs[cs.length - 1] ?? null;
|
|
138
|
+
return cs;
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
async loadHistory({ before }) {
|
|
142
|
+
const m = await market(); // for the spark fallback + the live price (best-effort)
|
|
143
|
+
// Prefer the pool straight from the token (rotation-proof); else resolve it via /data.
|
|
144
|
+
const pool = directPool ?? (m && m.pool ? `0x${bare(m.pool)}` : null);
|
|
145
|
+
if (!pool)
|
|
146
|
+
return [];
|
|
147
|
+
// Spark mode (forced via `history:"spark"`, or fell back): only the initial page has
|
|
148
|
+
// data (the snapshot's recent closes); there are no deeper pages.
|
|
149
|
+
if (sparkMode)
|
|
150
|
+
return before == null ? spark(m) : [];
|
|
151
|
+
if (allSwaps.length >= MAX_SWAPS)
|
|
152
|
+
return []; // depth cap reached
|
|
153
|
+
try {
|
|
154
|
+
// On the FIRST load, keep pulling pages until we have a full screen of candles, so the
|
|
155
|
+
// chart paints filled rather than a sliver on the right (active pools pack many swaps
|
|
156
|
+
// into each bucket, so one page can be only an hour or two). Lazy scroll-back fetches
|
|
157
|
+
// one page at a time. `rejectPriceOutliers` drops engine mis-pricings (e.g. $2.5M).
|
|
158
|
+
const want = before == null ? initialCandles : 0;
|
|
159
|
+
let got = 0;
|
|
160
|
+
do {
|
|
161
|
+
const pts = await fetchSignalsSwaps(pool, { url, first: swapPage, offset });
|
|
162
|
+
offset += pts.length;
|
|
163
|
+
got += pts.length;
|
|
164
|
+
if (pts.length === 0)
|
|
165
|
+
break;
|
|
166
|
+
allSwaps = allSwaps.concat(pts);
|
|
167
|
+
} while (allSwaps.length < MAX_SWAPS && buildOHLC(rejectPriceOutliers(allSwaps), secs).length < want);
|
|
168
|
+
if (got === 0) {
|
|
169
|
+
if (allSwaps.length === 0) {
|
|
170
|
+
sparkMode = true; // index empty for this pool — seed from spark
|
|
171
|
+
return before == null ? spark(m) : [];
|
|
172
|
+
}
|
|
173
|
+
return []; // no older swaps — history exhausted
|
|
174
|
+
}
|
|
175
|
+
const candles = buildOHLC(rejectPriceOutliers(allSwaps), secs); // clean, complete buckets
|
|
176
|
+
histLatest = candles[candles.length - 1] ?? null; // newest (may be the live bucket) — seeds the live poll
|
|
177
|
+
// EXCLUDE the in-progress (current wall-clock) bucket: the live poll owns it. Otherwise a
|
|
178
|
+
// history re-fetch (lazy load) would overwrite the live candle with the stale indexed close,
|
|
179
|
+
// and the next poll would restore the live price — making the latest candle oscillate.
|
|
180
|
+
const curBucket = Math.floor((opts.now ?? Date.now()) / 1000 / secs) * secs;
|
|
181
|
+
return candles.filter((c) => c.time < curBucket);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// index unreachable (e.g. /graphql CORS) — fall back to the snapshot spark, once.
|
|
185
|
+
sparkMode = true;
|
|
186
|
+
return before == null ? spark(m) : [];
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
subscribe(_params, onUpdate) {
|
|
190
|
+
const ms = opts.refetchMs ?? 15000;
|
|
191
|
+
if (ms <= 0)
|
|
192
|
+
return () => { };
|
|
193
|
+
let stopped = false;
|
|
194
|
+
let timer;
|
|
195
|
+
// Track the in-progress bucket so polled prices accumulate high/low instead of
|
|
196
|
+
// overwriting the whole candle each tick.
|
|
197
|
+
let cur = null;
|
|
198
|
+
const tick = async () => {
|
|
199
|
+
if (stopped)
|
|
200
|
+
return;
|
|
201
|
+
try {
|
|
202
|
+
const m = await fetchSignalsMarket(token, url);
|
|
203
|
+
if (m && !stopped) {
|
|
204
|
+
const nowSec = (opts.now ?? Date.now()) / 1000;
|
|
205
|
+
const bt = Math.floor(nowSec / secs) * secs;
|
|
206
|
+
if (!cur || cur.time !== bt) {
|
|
207
|
+
// Seed the in-progress bucket from the latest INDEXED candle when it's the same
|
|
208
|
+
// bucket, so the live price extends real OHLC/volume instead of flattening it.
|
|
209
|
+
const base = histLatest && histLatest.time === bt ? histLatest : null;
|
|
210
|
+
cur = base
|
|
211
|
+
? { ...base, h: Math.max(base.h, m.price), l: Math.min(base.l, m.price), c: m.price }
|
|
212
|
+
: { time: bt, o: m.price, h: m.price, l: m.price, c: m.price, vol: 0 };
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
cur = { ...cur, h: Math.max(cur.h, m.price), l: Math.min(cur.l, m.price), c: m.price };
|
|
216
|
+
}
|
|
217
|
+
onUpdate(cur);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
/* transient fetch error — try again next tick */
|
|
222
|
+
}
|
|
223
|
+
if (!stopped)
|
|
224
|
+
timer = setTimeout(tick, ms);
|
|
225
|
+
};
|
|
226
|
+
void tick(); // fire immediately so the live (current-bucket) candle appears at once
|
|
227
|
+
return () => {
|
|
228
|
+
stopped = true;
|
|
229
|
+
if (timer)
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
package/dist/core/theme.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { ChartTheme } from "./types";
|
|
2
|
-
/** Default dark theme
|
|
2
|
+
/** Default dark theme — a refined, modern crypto-terminal palette. */
|
|
3
3
|
export declare const DEFAULT_THEME: ChartTheme;
|
|
4
|
+
/** Light theme preset (same hues on a clean white plot). Pass to `theme` / `setTheme`. */
|
|
5
|
+
export declare const LIGHT_THEME: ChartTheme;
|
|
6
|
+
/** Built-in theme presets, addressable by name. */
|
|
7
|
+
export declare const THEME_PRESETS: Record<"dark" | "light", ChartTheme>;
|
package/dist/core/theme.js
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
/** Default dark theme
|
|
1
|
+
/** Default dark theme — a refined, modern crypto-terminal palette. */
|
|
2
2
|
export const DEFAULT_THEME = {
|
|
3
|
-
up: "#
|
|
4
|
-
down: "#
|
|
5
|
-
line: "#
|
|
6
|
-
grid: "#
|
|
7
|
-
axis: "#
|
|
8
|
-
crosshair: "#
|
|
9
|
-
volUp: "rgba(
|
|
10
|
-
volDown: "rgba(
|
|
11
|
-
tagText: "#
|
|
12
|
-
axisTagBg: "#
|
|
13
|
-
axisTagText: "#
|
|
3
|
+
up: "#2ebd85",
|
|
4
|
+
down: "#f6465d",
|
|
5
|
+
line: "#5b8def",
|
|
6
|
+
grid: "#1a2030",
|
|
7
|
+
axis: "#8b919e",
|
|
8
|
+
crosshair: "#b3b9c4",
|
|
9
|
+
volUp: "rgba(46,189,133,.28)",
|
|
10
|
+
volDown: "rgba(246,70,93,.28)",
|
|
11
|
+
tagText: "#06080c",
|
|
12
|
+
axisTagBg: "#2b3242",
|
|
13
|
+
axisTagText: "#eaedf2",
|
|
14
|
+
background: "#0b0e14",
|
|
15
|
+
};
|
|
16
|
+
/** Light theme preset (same hues on a clean white plot). Pass to `theme` / `setTheme`. */
|
|
17
|
+
export const LIGHT_THEME = {
|
|
18
|
+
up: "#089981",
|
|
19
|
+
down: "#f23645",
|
|
20
|
+
line: "#3b6fe0",
|
|
21
|
+
grid: "#eceff4",
|
|
22
|
+
axis: "#6b7280",
|
|
23
|
+
crosshair: "#4b5563",
|
|
24
|
+
volUp: "rgba(8,153,129,.22)",
|
|
25
|
+
volDown: "rgba(242,54,69,.22)",
|
|
26
|
+
tagText: "#ffffff",
|
|
27
|
+
axisTagBg: "#1f2733",
|
|
28
|
+
axisTagText: "#ffffff",
|
|
29
|
+
background: "#ffffff",
|
|
30
|
+
};
|
|
31
|
+
/** Built-in theme presets, addressable by name. */
|
|
32
|
+
export const THEME_PRESETS = {
|
|
33
|
+
dark: DEFAULT_THEME,
|
|
34
|
+
light: LIGHT_THEME,
|
|
14
35
|
};
|
package/dist/core/types.d.ts
CHANGED
|
@@ -15,10 +15,30 @@ export interface Point {
|
|
|
15
15
|
v?: number;
|
|
16
16
|
}
|
|
17
17
|
export type Denom = "USD" | "ETH";
|
|
18
|
-
|
|
19
|
-
export type
|
|
18
|
+
/** Price-series style: candlesticks, a line, a two-tone baseline area, or Heikin-Ashi candles. */
|
|
19
|
+
export type ChartType = "candle" | "line" | "baseline" | "heikin";
|
|
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. */
|
|
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). */
|
|
25
|
+
export interface Oscillator {
|
|
26
|
+
type: OscillatorType;
|
|
27
|
+
/** Lookback — RSI/ATR period, or the Stochastic %K period (defaults 14). Ignored by MACD. */
|
|
28
|
+
period?: number;
|
|
29
|
+
/** MACD fast / slow / signal EMA lengths (defaults 12 / 26 / 9). Ignored by the others. */
|
|
30
|
+
fast?: number;
|
|
31
|
+
slow?: number;
|
|
32
|
+
signal?: number;
|
|
33
|
+
/** Stochastic %D period (default 3) and %K smoothing (default 1 = fast). Ignored by others. */
|
|
34
|
+
dPeriod?: number;
|
|
35
|
+
smooth?: number;
|
|
36
|
+
/** Pane height in px (default 84). */
|
|
37
|
+
height?: number;
|
|
38
|
+
/** Primary line color; defaults to a built-in palette slot. */
|
|
39
|
+
color?: string;
|
|
40
|
+
}
|
|
41
|
+
/** An overlay indicator drawn on the price pane (a moving average, VWAP, or Bollinger band). */
|
|
22
42
|
export interface Indicator {
|
|
23
43
|
type: IndicatorType;
|
|
24
44
|
/** Lookback in candles. */
|
|
@@ -27,7 +47,29 @@ export interface Indicator {
|
|
|
27
47
|
color?: string;
|
|
28
48
|
/** Which price to average. Default "close". */
|
|
29
49
|
source?: PriceSource;
|
|
50
|
+
/** Bollinger band width in standard deviations (default 2). Ignored by other types. */
|
|
51
|
+
mult?: number;
|
|
52
|
+
}
|
|
53
|
+
/** A point anchored in data space (so drawings stay attached across pan/zoom). */
|
|
54
|
+
export interface DrawingPoint {
|
|
55
|
+
/** unix seconds. */
|
|
56
|
+
time: number;
|
|
57
|
+
price: number;
|
|
30
58
|
}
|
|
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
|
+
*/
|
|
64
|
+
export interface Drawing {
|
|
65
|
+
id: string;
|
|
66
|
+
type: DrawingType;
|
|
67
|
+
a: DrawingPoint;
|
|
68
|
+
b?: DrawingPoint;
|
|
69
|
+
color?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Drawing interaction mode. `none` = pan/zoom + select/move; the others arm a one-shot draw. */
|
|
72
|
+
export type DrawMode = "none" | "trendline" | "hline" | "fib" | "rect";
|
|
31
73
|
/** Transform applied to raw prices before aggregation. */
|
|
32
74
|
export interface PriceTransform {
|
|
33
75
|
/** "USD" leaves prices as-is; "ETH" divides by `ethUsd`. */
|
|
@@ -56,6 +98,15 @@ export interface ChartTheme {
|
|
|
56
98
|
axisTagText: string;
|
|
57
99
|
background?: string;
|
|
58
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
|
+
};
|
|
59
110
|
export interface ChartOptions {
|
|
60
111
|
/** canvas height in CSS px (default 420). */
|
|
61
112
|
height?: number;
|
|
@@ -66,6 +117,11 @@ export interface ChartOptions {
|
|
|
66
117
|
minBars?: number;
|
|
67
118
|
/** cap on a candle's slot width in px — keeps sparse series tight, right-anchored (default 18). */
|
|
68
119
|
maxBarWidth?: number;
|
|
120
|
+
/** cap on a candle's BODY (and volume bar) width in px (default 40). The body is 70% of
|
|
121
|
+
* the slot up to this cap; raise it for chunkier candles on sparse / `fitContent` series. */
|
|
122
|
+
maxBodyWidth?: number;
|
|
123
|
+
/** show the volume panel (default true). */
|
|
124
|
+
showVolume?: boolean;
|
|
69
125
|
/** fraction of the plot used by the volume panel (default 0.18). */
|
|
70
126
|
volumeRatio?: number;
|
|
71
127
|
rightPad?: number;
|
|
@@ -75,6 +131,49 @@ export interface ChartOptions {
|
|
|
75
131
|
onCrosshair?: (candle: Candle | null) => void;
|
|
76
132
|
/** moving-average overlays drawn on the price pane. */
|
|
77
133
|
indicators?: Indicator[];
|
|
134
|
+
/** oscillators (RSI / MACD) drawn in stacked sub-panes below the volume panel. */
|
|
135
|
+
oscillators?: Oscillator[];
|
|
136
|
+
/** user drawings (trendlines / horizontal price lines) to render on the price pane. */
|
|
137
|
+
drawings?: Drawing[];
|
|
138
|
+
/** fired whenever the user adds, moves, or deletes a drawing. */
|
|
139
|
+
onDrawingsChange?: (drawings: Drawing[]) => void;
|
|
140
|
+
/** fired when the active draw mode changes (e.g. auto-reset to "none" after one draw). */
|
|
141
|
+
onDrawModeChange?: (mode: DrawMode) => void;
|
|
142
|
+
/** logarithmic price axis — equal vertical distance = equal % move (default false). */
|
|
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;
|
|
151
|
+
/** spread candles across the full plot width instead of right-anchoring a capped slot
|
|
152
|
+
* (default false in the core `Chart`; the React wrappers default it on). */
|
|
153
|
+
fitContent?: boolean;
|
|
154
|
+
/** y-axis price label formatter. Default adapts precision to the tick step. */
|
|
155
|
+
priceFormat?: (value: number) => string;
|
|
156
|
+
/** x-axis time label formatter. Default {@link formatTime}. */
|
|
157
|
+
timeFormat?: (time: number, interval: number) => string;
|
|
158
|
+
/** number of horizontal price gridlines / labels (default 5). */
|
|
159
|
+
priceTicks?: number;
|
|
160
|
+
/** approximate number of time-axis labels (default 7). */
|
|
161
|
+
timeTicks?: number;
|
|
162
|
+
/** canvas font for axis labels (default "10px ui-monospace, monospace"); color is `theme.axis`. */
|
|
163
|
+
axisFont?: string;
|
|
78
164
|
/** fired when the user pans/zooms near the start of loaded data (lazy history). */
|
|
79
165
|
onNeedHistory?: () => void;
|
|
80
166
|
}
|
|
167
|
+
/** Axis label/format/layout overrides — the subset of {@link ChartOptions} that styles the ticks. */
|
|
168
|
+
export interface AxisOptions {
|
|
169
|
+
fitContent?: boolean;
|
|
170
|
+
priceFormat?: (value: number) => string;
|
|
171
|
+
timeFormat?: (time: number, interval: number) => string;
|
|
172
|
+
priceTicks?: number;
|
|
173
|
+
timeTicks?: number;
|
|
174
|
+
axisFont?: string;
|
|
175
|
+
/** max candle slot width in px (spacing). */
|
|
176
|
+
maxBarWidth?: number;
|
|
177
|
+
/** max candle body / volume-bar width in px. */
|
|
178
|
+
maxBodyWidth?: number;
|
|
179
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
export { Chart } from "./core/chart";
|
|
2
|
-
export { buildOHLC, transformPrice } from "./core/ohlc";
|
|
3
|
-
export { draw } from "./core/renderer";
|
|
4
|
-
export type { RenderInput, Viewport, Pads, ResolvedOverlay } from "./core/renderer";
|
|
1
|
+
export { Chart, needsHistory, maxPanOffset } from "./core/chart";
|
|
2
|
+
export { buildOHLC, transformPrice, heikinAshi } from "./core/ohlc";
|
|
3
|
+
export { draw, slotWidth, windowOf, priceScale, timeScale, computeProjection, withAlpha, resolveOverlays } from "./core/renderer";
|
|
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
|
-
export { DEFAULT_THEME } from "./core/theme";
|
|
8
|
-
export { formatValue, formatVolume, formatTime } from "./core/format";
|
|
9
|
-
export { sma, ema, wma, vwap, bollingerBands, sourceValues, computeIndicator, INDICATOR_PALETTE } from "./core/indicators";
|
|
7
|
+
export { DEFAULT_THEME, LIGHT_THEME, THEME_PRESETS } from "./core/theme";
|
|
8
|
+
export { formatValue, formatAxisValue, formatVolume, formatTime } from "./core/format";
|
|
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";
|
|
6
|
-
export { draw } from "./core/renderer";
|
|
4
|
+
export { Chart, needsHistory, maxPanOffset } from "./core/chart";
|
|
5
|
+
export { buildOHLC, transformPrice, heikinAshi } from "./core/ohlc";
|
|
6
|
+
export { draw, slotWidth, windowOf, priceScale, timeScale, computeProjection, withAlpha, resolveOverlays } from "./core/renderer";
|
|
7
7
|
export { connectFeed } from "./core/feed";
|
|
8
|
-
export { DEFAULT_THEME } from "./core/theme";
|
|
9
|
-
export { formatValue, formatVolume, formatTime } from "./core/format";
|
|
10
|
-
export { sma, ema, wma, vwap, bollingerBands, sourceValues, computeIndicator, INDICATOR_PALETTE } from "./core/indicators";
|
|
8
|
+
export { DEFAULT_THEME, LIGHT_THEME, THEME_PRESETS } from "./core/theme";
|
|
9
|
+
export { formatValue, formatAxisValue, formatVolume, formatTime } from "./core/format";
|
|
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, Indicator } 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;
|
|
@@ -9,11 +9,39 @@ export interface HyperliquidChartProps {
|
|
|
9
9
|
chartType?: ChartType;
|
|
10
10
|
/** Moving-average overlays, e.g. [{ type: "ema", period: 21 }]. */
|
|
11
11
|
indicators?: Indicator[];
|
|
12
|
+
/** Oscillator sub-panes (RSI / MACD), e.g. [{ type: "macd" }]. */
|
|
13
|
+
oscillators?: Oscillator[];
|
|
14
|
+
/** Initial drawings (trendlines / horizontal lines). */
|
|
15
|
+
drawings?: Drawing[];
|
|
16
|
+
/** Fired whenever the user adds, moves, or deletes a drawing. */
|
|
17
|
+
onDrawingsChange?: (drawings: Drawing[]) => void;
|
|
18
|
+
/** Hide the trendline / h-line / clear drawing-tool buttons (shown by default). */
|
|
19
|
+
hideDrawingTools?: boolean;
|
|
20
|
+
/** Show the volume panel (default true). */
|
|
21
|
+
showVolume?: boolean;
|
|
22
|
+
/** Volume-by-price histogram (default off); the toolbar adds a VP toggle when set. */
|
|
23
|
+
volumeProfile?: VolumeProfileConfig;
|
|
12
24
|
/** Candles per history page; older pages load as you scroll left (default 500). */
|
|
13
25
|
pageSize?: number;
|
|
14
26
|
theme?: Partial<ChartTheme>;
|
|
15
27
|
/** Show the interval / candle-line toolbar. Default true. */
|
|
16
28
|
toolbar?: boolean;
|
|
29
|
+
/** Spread candles across the full width instead of right-anchoring (default true). */
|
|
30
|
+
fitContent?: boolean;
|
|
31
|
+
/** Max candle slot width in px (spacing; default 18). */
|
|
32
|
+
maxBarWidth?: number;
|
|
33
|
+
/** Max candle body / volume-bar width in px (default 40). */
|
|
34
|
+
maxBodyWidth?: number;
|
|
35
|
+
/** y-axis price label formatter (default adapts precision to the tick step). */
|
|
36
|
+
priceFormat?: (value: number) => string;
|
|
37
|
+
/** x-axis time label formatter. */
|
|
38
|
+
timeFormat?: (time: number, interval: number) => string;
|
|
39
|
+
/** number of horizontal price gridlines / labels (default 5). */
|
|
40
|
+
priceTicks?: number;
|
|
41
|
+
/** approximate number of time-axis labels (default 7). */
|
|
42
|
+
timeTicks?: number;
|
|
43
|
+
/** canvas font for axis labels; color is `theme.axis`. */
|
|
44
|
+
axisFont?: string;
|
|
17
45
|
}
|
|
18
46
|
/**
|
|
19
47
|
* A turnkey, REALTIME Hyperliquid trading chart. Streams live candles over WebSocket,
|
|
@@ -25,4 +53,4 @@ export interface HyperliquidChartProps {
|
|
|
25
53
|
* <HyperliquidChart coin="BTC" interval="15m" indicators={[{ type: "ema", period: 21 }]} />
|
|
26
54
|
* ```
|
|
27
55
|
*/
|
|
28
|
-
export declare function HyperliquidChart({ coin, interval, testnet, height, chartType, indicators, pageSize, theme, toolbar, }: 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;
|