@tony01/astroneum 0.4.1-beta.2

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +237 -0
  2. package/LICENSE +21 -0
  3. package/README.md +198 -0
  4. package/dist/Indicator-BBI3L_lx.d.ts +1181 -0
  5. package/dist/ar-SA-AEPUSRMF.js +2 -0
  6. package/dist/astroneum.css +1 -0
  7. package/dist/chunk-2SN3RH42.js +2 -0
  8. package/dist/chunk-2YYGUSVX.js +3 -0
  9. package/dist/chunk-3GJSZFLM.js +2 -0
  10. package/dist/chunk-4YOLQ5DC.js +2 -0
  11. package/dist/chunk-I4YQBP2M.js +3 -0
  12. package/dist/chunk-OOMZRONH.js +35 -0
  13. package/dist/chunk-PV2GAINM.js +2 -0
  14. package/dist/chunk-SDXH2WAI.js +3 -0
  15. package/dist/chunk-YFPXTHZX.js +2 -0
  16. package/dist/chunk-YW3STIPY.js +653 -0
  17. package/dist/de-DE-4PR3NRQ7.js +2 -0
  18. package/dist/entries/alerts.d.ts +93 -0
  19. package/dist/entries/alerts.js +2 -0
  20. package/dist/entries/datafeeds/crypto.d.ts +65 -0
  21. package/dist/entries/datafeeds/crypto.js +2 -0
  22. package/dist/entries/datafeeds/polygon.d.ts +113 -0
  23. package/dist/entries/datafeeds/polygon.js +2 -0
  24. package/dist/entries/multichart.d.ts +96 -0
  25. package/dist/entries/multichart.js +2 -0
  26. package/dist/entries/portfolio.d.ts +55 -0
  27. package/dist/entries/portfolio.js +2 -0
  28. package/dist/entries/replay.d.ts +141 -0
  29. package/dist/entries/replay.js +2 -0
  30. package/dist/entries/script.d.ts +68 -0
  31. package/dist/entries/script.js +2 -0
  32. package/dist/entries/watchlist.d.ts +43 -0
  33. package/dist/entries/watchlist.js +2 -0
  34. package/dist/es-ES-HNDVPSLW.js +2 -0
  35. package/dist/fr-FR-Y7XWRGF2.js +2 -0
  36. package/dist/hi-IN-KEGIKIMV.js +2 -0
  37. package/dist/id-ID-QXDIUQUZ.js +2 -0
  38. package/dist/index.d.ts +1434 -0
  39. package/dist/index.js +2 -0
  40. package/dist/it-IT-UDTDFSRB.js +2 -0
  41. package/dist/ja-JP-NIODTBW3.js +2 -0
  42. package/dist/ko-KR-DQHPXKBA.js +2 -0
  43. package/dist/nl-NL-BB3ZHCXS.js +2 -0
  44. package/dist/pl-PL-I5MZDLFD.js +2 -0
  45. package/dist/pt-BR-MAMRTOYR.js +2 -0
  46. package/dist/ru-RU-F43D6B7G.js +2 -0
  47. package/dist/th-TH-KTFIUAD6.js +2 -0
  48. package/dist/tr-TR-WFTKJ22Y.js +2 -0
  49. package/dist/vi-VN-CD3WI2FQ.js +2 -0
  50. package/dist/zh-CN-Y2C6RSOI.js +3 -0
  51. package/docs/STRUCTURE.md +231 -0
  52. package/docs/TODO-DESIGN.md +261 -0
  53. package/docs/TODO.md +222 -0
  54. package/docs/api.md +528 -0
  55. package/docs/datafeed-guide.md +311 -0
  56. package/docs/design-astroneum.md +479 -0
  57. package/docs/plugin-development.md +565 -0
  58. package/docs/tv-functions-skill.md +706 -0
  59. package/package.json +147 -0
@@ -0,0 +1,311 @@
1
+ # Building a Datafeed
2
+
3
+ A **Datafeed** is a simple interface that lets Astroneum fetch historical data and subscribe to real-time ticks from any source. This guide shows 3 patterns: minimal mock, REST API, and real-time stream.
4
+
5
+ ---
6
+
7
+ ## The Datafeed Interface
8
+
9
+ ```ts
10
+ interface Datafeed {
11
+ searchSymbols(search?: string): Promise<SymbolInfo[]>
12
+ getHistoryData(
13
+ symbol: SymbolInfo,
14
+ period: Period,
15
+ from: number,
16
+ to: number
17
+ ): Promise<CandleData[]>
18
+ subscribe(
19
+ symbol: SymbolInfo,
20
+ period: Period,
21
+ callback: DatafeedSubscribeCallback
22
+ ): void
23
+ unsubscribe(symbol: SymbolInfo, period: Period): void
24
+ }
25
+ ```
26
+
27
+ **Key methods:**
28
+ - `searchSymbols(search)` — Return matching symbols (used in symbol search bar).
29
+ - `getHistoryData(symbol, period, from, to)` — Fetch bars between timestamps (Unix ms).
30
+ - `subscribe(symbol, period, callback)` — Stream real-time ticks; call `callback(tick)` on each update.
31
+ - `unsubscribe(symbol, period)` — Stop streaming for this symbol/period pair.
32
+
33
+ ---
34
+
35
+ ## Pattern 1: Minimal Mock (for demos)
36
+
37
+ The simplest datafeed just generates fake data locally:
38
+
39
+ ```ts
40
+ import type {
41
+ Datafeed,
42
+ SymbolInfo,
43
+ Period,
44
+ CandleData,
45
+ DatafeedSubscribeCallback
46
+ } from '@tony01/astroneum'
47
+
48
+ const SYMBOLS: SymbolInfo[] = [
49
+ {
50
+ ticker: 'BTC',
51
+ shortName: 'BTC',
52
+ name: 'Bitcoin',
53
+ exchange: 'DEMO',
54
+ market: 'crypto'
55
+ }
56
+ ]
57
+
58
+ const myDatafeed: Datafeed = {
59
+ searchSymbols(search) {
60
+ const q = search?.toLowerCase() ?? ''
61
+ return Promise.resolve(
62
+ SYMBOLS.filter(s => s.ticker.toLowerCase().includes(q))
63
+ )
64
+ },
65
+
66
+ getHistoryData(symbol, period, from, to) {
67
+ // Generate synthetic bars between `from` and `to` timestamps
68
+ const step = period.multiplier * 60_000 // assume period is in minutes for demo
69
+ const bars: CandleData[] = []
70
+ let price = 50000
71
+
72
+ for (let ts = from; ts <= to; ts += step) {
73
+ const change = (Math.random() - 0.5) * 1000
74
+ bars.push({
75
+ timestamp: ts,
76
+ open: price,
77
+ high: price + Math.abs(change),
78
+ low: price - Math.abs(change),
79
+ close: price + change,
80
+ volume: Math.random() * 10000,
81
+ turnover: Math.random() * 100000,
82
+ })
83
+ price += change
84
+ }
85
+ return Promise.resolve(bars)
86
+ },
87
+
88
+ subscribe(symbol, period, callback) {
89
+ // Emit a tick every 100ms
90
+ let price = 50000
91
+ const interval = setInterval(() => {
92
+ price += (Math.random() - 0.5) * 100
93
+ callback({
94
+ timestamp: Date.now(),
95
+ open: price,
96
+ high: price + 50,
97
+ low: price - 50,
98
+ close: price,
99
+ volume: Math.random() * 100,
100
+ turnover: Math.random() * 1000,
101
+ })
102
+ }, 100)
103
+
104
+ // Cleanup when unsubscribed
105
+ const originalUnsubscribe = myDatafeed.unsubscribe
106
+ myDatafeed.unsubscribe = (s, p) => {
107
+ if (s.ticker === symbol.ticker && p.text === period.text) {
108
+ clearInterval(interval)
109
+ }
110
+ originalUnsubscribe(s, p)
111
+ }
112
+ },
113
+
114
+ unsubscribe() {
115
+ // Cleanup happens in subscribe() above
116
+ }
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Pattern 2: REST API (static bars only)
123
+
124
+ Fetch bars from an HTTP API (e.g., `GET /api/bars?symbol=BTC&from=123&to=456`):
125
+
126
+ ```ts
127
+ const myDatafeed: Datafeed = {
128
+ async searchSymbols(search) {
129
+ const res = await fetch(`/api/symbols?q=${search}`)
130
+ return res.json()
131
+ },
132
+
133
+ async getHistoryData(symbol, period, from, to) {
134
+ const res = await fetch('/api/bars', {
135
+ method: 'POST',
136
+ headers: { 'Content-Type': 'application/json' },
137
+ body: JSON.stringify({
138
+ ticker: symbol.ticker,
139
+ timeframe: period.text,
140
+ from,
141
+ to
142
+ })
143
+ })
144
+ const bars = await res.json() as CandleData[]
145
+ return bars
146
+ },
147
+
148
+ subscribe(symbol, period, callback) {
149
+ // Poll for new bars every 5 seconds
150
+ const interval = setInterval(async () => {
151
+ const now = Date.now()
152
+ const res = await fetch('/api/bars', {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({
156
+ ticker: symbol.ticker,
157
+ timeframe: period.text,
158
+ from: now - 60_000,
159
+ to: now
160
+ })
161
+ })
162
+ const bars = await res.json() as CandleData[]
163
+ if (bars.length > 0) {
164
+ callback(bars[bars.length - 1])
165
+ }
166
+ }, 5000)
167
+
168
+ // Store interval ID so unsubscribe can clear it
169
+ const unsubKey = `${symbol.ticker}::${period.text}`
170
+ const intervals = (myDatafeed as any)._intervals ??= new Map()
171
+ intervals.set(unsubKey, interval)
172
+ },
173
+
174
+ unsubscribe(symbol, period) {
175
+ const unsubKey = `${symbol.ticker}::${period.text}`
176
+ const intervals = (myDatafeed as any)._intervals
177
+ if (intervals?.has(unsubKey)) {
178
+ clearInterval(intervals.get(unsubKey))
179
+ intervals.delete(unsubKey)
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Pattern 3: WebSocket Streaming (real-time)
188
+
189
+ Connect to a live market data stream (e.g., Binance WebSocket):
190
+
191
+ ```ts
192
+ const myDatafeed: Datafeed = {
193
+ searchSymbols(search) {
194
+ return Promise.resolve([
195
+ {
196
+ ticker: 'BTCUSDT',
197
+ shortName: 'BTC',
198
+ name: 'Bitcoin USDT',
199
+ exchange: 'BINANCE',
200
+ market: 'crypto'
201
+ }
202
+ ])
203
+ },
204
+
205
+ async getHistoryData(symbol, period, from, to) {
206
+ // Fetch from REST: GET https://api.binance.com/api/v3/klines
207
+ const interval = '1m' // or '5m', '1h', etc.
208
+ const res = await fetch(
209
+ `https://api.binance.com/api/v3/klines?` +
210
+ `symbol=${symbol.ticker}&interval=${interval}&startTime=${from}&endTime=${to}`
211
+ )
212
+ const rows = await res.json() as any[][]
213
+ return rows.map(row => ({
214
+ timestamp: row[0] as number,
215
+ open: parseFloat(row[1]),
216
+ high: parseFloat(row[2]),
217
+ low: parseFloat(row[3]),
218
+ close: parseFloat(row[4]),
219
+ volume: parseFloat(row[7]),
220
+ turnover: parseFloat(row[7]) // or custom calculation
221
+ }))
222
+ },
223
+
224
+ subscribe(symbol, period, callback) {
225
+ const interval = '1m' // map period to interval
226
+ const stream = `${symbol.ticker.toLowerCase()}@kline_${interval}`
227
+ const ws = new WebSocket(`wss://stream.binance.com/ws/${stream}`)
228
+
229
+ ws.onmessage = (event) => {
230
+ try {
231
+ const data = JSON.parse(event.data)
232
+ if (data.e === 'kline') {
233
+ callback({
234
+ timestamp: data.k.t,
235
+ open: parseFloat(data.k.o),
236
+ high: parseFloat(data.k.h),
237
+ low: parseFloat(data.k.l),
238
+ close: parseFloat(data.k.c),
239
+ volume: parseFloat(data.k.v),
240
+ turnover: parseFloat(data.k.q)
241
+ })
242
+ }
243
+ } catch (e) {
244
+ console.error('Parse error:', e)
245
+ }
246
+ }
247
+
248
+ ws.onerror = () => ws.close()
249
+ ws.onclose = () => {
250
+ // Implement reconnect logic if needed
251
+ }
252
+
253
+ // Store socket so unsubscribe can close it
254
+ const key = `${symbol.ticker}::${period.text}`
255
+ const sockets = (myDatafeed as any)._sockets ??= new Map()
256
+ sockets.set(key, ws)
257
+ },
258
+
259
+ unsubscribe(symbol, period) {
260
+ const key = `${symbol.ticker}::${period.text}`
261
+ const sockets = (myDatafeed as any)._sockets
262
+ if (sockets?.has(key)) {
263
+ sockets.get(key).close()
264
+ sockets.delete(key)
265
+ }
266
+ }
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Using a Datafeed in AstroneumChart
273
+
274
+ ```tsx
275
+ import { AstroneumChart } from '@tony01/astroneum'
276
+ import { myDatafeed } from './datafeed'
277
+
278
+ export function Chart() {
279
+ return (
280
+ <AstroneumChart
281
+ symbol={{ ticker: 'BTC', shortName: 'BTC', name: 'Bitcoin', market: 'crypto' }}
282
+ period={{ multiplier: 1, timespan: 'minute', text: '1m' }}
283
+ datafeed={myDatafeed}
284
+ locale="en-US"
285
+ style={{ width: '100%', height: 600 }}
286
+ />
287
+ )
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Tips
294
+
295
+ 1. **Start simple** — Begin with a REST API; add WebSocket if you need real-time.
296
+ 2. **Handle errors gracefully** — Return empty arrays if data fetches fail; reconnect on socket close.
297
+ 3. **Cache symbols** — Don't re-fetch the symbol list on every search.
298
+ 4. **Timestamp precision** — Use milliseconds (Unix ms) throughout; ensure `from` and `to` are in ms.
299
+ 5. **Smooth real-time** — Emit ticks smoothly at ~100–200 ms intervals instead of burst updates.
300
+ 6. **Use TickAnimator** — The library exports `TickAnimator` to interpolate ticks smoothly across frames (see `../src/engine/common/TickAnimator.ts`).
301
+
302
+ ---
303
+
304
+ ## Advanced: Mock with Smooth Animation
305
+
306
+ For testing, the demo `mockDatafeed.ts` uses:
307
+ - **Seeded random generation** — Reproducible fake data across reloads
308
+ - **TickAnimator** — Smooth frame-based interpolation of real-time ticks
309
+ - **Fallback simulation** — Local bar generation if Binance fails
310
+
311
+ See `demo/src/mockDatafeed.ts` for the full implementation.