@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.
- package/CHANGELOG.md +237 -0
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/Indicator-BBI3L_lx.d.ts +1181 -0
- package/dist/ar-SA-AEPUSRMF.js +2 -0
- package/dist/astroneum.css +1 -0
- package/dist/chunk-2SN3RH42.js +2 -0
- package/dist/chunk-2YYGUSVX.js +3 -0
- package/dist/chunk-3GJSZFLM.js +2 -0
- package/dist/chunk-4YOLQ5DC.js +2 -0
- package/dist/chunk-I4YQBP2M.js +3 -0
- package/dist/chunk-OOMZRONH.js +35 -0
- package/dist/chunk-PV2GAINM.js +2 -0
- package/dist/chunk-SDXH2WAI.js +3 -0
- package/dist/chunk-YFPXTHZX.js +2 -0
- package/dist/chunk-YW3STIPY.js +653 -0
- package/dist/de-DE-4PR3NRQ7.js +2 -0
- package/dist/entries/alerts.d.ts +93 -0
- package/dist/entries/alerts.js +2 -0
- package/dist/entries/datafeeds/crypto.d.ts +65 -0
- package/dist/entries/datafeeds/crypto.js +2 -0
- package/dist/entries/datafeeds/polygon.d.ts +113 -0
- package/dist/entries/datafeeds/polygon.js +2 -0
- package/dist/entries/multichart.d.ts +96 -0
- package/dist/entries/multichart.js +2 -0
- package/dist/entries/portfolio.d.ts +55 -0
- package/dist/entries/portfolio.js +2 -0
- package/dist/entries/replay.d.ts +141 -0
- package/dist/entries/replay.js +2 -0
- package/dist/entries/script.d.ts +68 -0
- package/dist/entries/script.js +2 -0
- package/dist/entries/watchlist.d.ts +43 -0
- package/dist/entries/watchlist.js +2 -0
- package/dist/es-ES-HNDVPSLW.js +2 -0
- package/dist/fr-FR-Y7XWRGF2.js +2 -0
- package/dist/hi-IN-KEGIKIMV.js +2 -0
- package/dist/id-ID-QXDIUQUZ.js +2 -0
- package/dist/index.d.ts +1434 -0
- package/dist/index.js +2 -0
- package/dist/it-IT-UDTDFSRB.js +2 -0
- package/dist/ja-JP-NIODTBW3.js +2 -0
- package/dist/ko-KR-DQHPXKBA.js +2 -0
- package/dist/nl-NL-BB3ZHCXS.js +2 -0
- package/dist/pl-PL-I5MZDLFD.js +2 -0
- package/dist/pt-BR-MAMRTOYR.js +2 -0
- package/dist/ru-RU-F43D6B7G.js +2 -0
- package/dist/th-TH-KTFIUAD6.js +2 -0
- package/dist/tr-TR-WFTKJ22Y.js +2 -0
- package/dist/vi-VN-CD3WI2FQ.js +2 -0
- package/dist/zh-CN-Y2C6RSOI.js +3 -0
- package/docs/STRUCTURE.md +231 -0
- package/docs/TODO-DESIGN.md +261 -0
- package/docs/TODO.md +222 -0
- package/docs/api.md +528 -0
- package/docs/datafeed-guide.md +311 -0
- package/docs/design-astroneum.md +479 -0
- package/docs/plugin-development.md +565 -0
- package/docs/tv-functions-skill.md +706 -0
- package/package.json +147 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
# Plugin & Indicator Development Guide
|
|
2
|
+
|
|
3
|
+
This guide shows how to extend Astroneum with custom indicators, WebGL renderers, chart plugins, and script-based indicators.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Concepts overview](#concepts-overview)
|
|
10
|
+
2. [IndicatorPlugin — typed TypeScript indicator](#indicatorplugin--typed-typescript-indicator)
|
|
11
|
+
- [Simple scalar output](#simple-scalar-output)
|
|
12
|
+
- [Multi-series output](#multi-series-output)
|
|
13
|
+
- [Canvas 2D renderer (render2D)](#canvas-2d-renderer-render2d)
|
|
14
|
+
- [WebGL2 renderer (renderGL)](#webgl2-renderer-rendergl)
|
|
15
|
+
3. [ChartPlugin — per-chart lifecycle](#chartplugin--per-chart-lifecycle)
|
|
16
|
+
4. [Raw engine indicator (IndicatorTemplate)](#raw-engine-indicator-indicatortemplate)
|
|
17
|
+
5. [ScriptEngine — runtime scripting](#scriptengine--runtime-scripting)
|
|
18
|
+
6. [Built-in indicator reference](#built-in-indicator-reference)
|
|
19
|
+
7. [Full API reference](#full-api-reference)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Concepts overview
|
|
24
|
+
|
|
25
|
+
| Layer | When to use |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `IndicatorPlugin` | Custom indicator written in TypeScript with optional Canvas 2D or WebGL2 renderer. **Recommended entry point.** |
|
|
28
|
+
| `ChartPlugin` | Bundle one or more `IndicatorPlugin`s with chart lifecycle hooks (e.g. auto-create on mount, teardown on unmount). |
|
|
29
|
+
| `IndicatorTemplate` (engine) | Direct access to the engine's low-level indicator format. Use when you need full control over figures, drawing, and pane behaviour. |
|
|
30
|
+
| `ScriptEngine` | Let end-users write Pine-Script-inspired JS snippets at runtime without a build step. |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## IndicatorPlugin — typed TypeScript indicator
|
|
35
|
+
|
|
36
|
+
### Interface
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
interface IndicatorPlugin<TOutput> {
|
|
40
|
+
name: string // unique registry key — used in createIndicator()
|
|
41
|
+
shortName?: string // display name in chart legend (defaults to name)
|
|
42
|
+
calcParams?: number[] // default params, overridable by user
|
|
43
|
+
calc(data: CandleData[], params: number[]): TOutput[]
|
|
44
|
+
render2D?(ctx: CanvasRenderingContext2D, output: TOutput[], viewport: Viewport): void
|
|
45
|
+
renderGL?(gl: WebGL2RenderingContext, output: TOutput[], viewport: Viewport, vbo: WebGLBuffer): void
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`calc` is the only required method. `render2D` and `renderGL` are optional custom draw passes.
|
|
50
|
+
When **neither** renderer is provided, Astroneum auto-plots the output values as line(s).
|
|
51
|
+
When `renderGL` is present it runs on a dedicated WebGL2 layer with a stable per-indicator `vbo`.
|
|
52
|
+
If WebGL2 is unavailable in the browser, `render2D` is used as fallback when available.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### Simple scalar output
|
|
57
|
+
|
|
58
|
+
Each `calc` return element maps to one candle bar. Return a plain `number` (or `null`) per bar.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { registerIndicatorPlugin, type IndicatorPlugin, type CandleData } from '@tony01/astroneum'
|
|
62
|
+
|
|
63
|
+
const spreadIndicator: IndicatorPlugin<number> = {
|
|
64
|
+
name: 'SPREAD',
|
|
65
|
+
shortName: 'Spread',
|
|
66
|
+
calc(data: CandleData[]): number[] {
|
|
67
|
+
return data.map(bar => bar.high - bar.low)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
registerIndicatorPlugin(spreadIndicator)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then add it to any chart:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<AstroneumChart
|
|
78
|
+
subIndicators={['SPREAD']}
|
|
79
|
+
// or via ref:
|
|
80
|
+
// ref.current.createIndicator('SPREAD')
|
|
81
|
+
...
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Multi-series output
|
|
88
|
+
|
|
89
|
+
Return an object per bar to plot multiple named lines simultaneously.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
interface BandOutput {
|
|
93
|
+
upper: number | null
|
|
94
|
+
middle: number | null
|
|
95
|
+
lower: number | null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const donchianChannel: IndicatorPlugin<BandOutput> = {
|
|
99
|
+
name: 'DONCHIAN',
|
|
100
|
+
shortName: 'Donchian',
|
|
101
|
+
calcParams: [20],
|
|
102
|
+
calc(data: CandleData[], [len]: number[]): BandOutput[] {
|
|
103
|
+
return data.map((_, index) => {
|
|
104
|
+
const slice = data.slice(Math.max(0, index - len + 1), index + 1)
|
|
105
|
+
const highs = slice.map(b => b.high)
|
|
106
|
+
const lows = slice.map(b => b.low)
|
|
107
|
+
return {
|
|
108
|
+
upper: Math.max(...highs),
|
|
109
|
+
middle: (Math.max(...highs) + Math.min(...lows)) / 2,
|
|
110
|
+
lower: Math.min(...lows)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
registerIndicatorPlugin(donchianChannel)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each key in the returned object becomes a separate series in the chart legend.
|
|
120
|
+
Return `null` for bars where a value is not yet computable (e.g. warm-up period).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Canvas 2D renderer (render2D)
|
|
125
|
+
|
|
126
|
+
Use `render2D` for lightweight overlays (< 10 k points) or any drawing that doesn't need GPU acceleration.
|
|
127
|
+
|
|
128
|
+
The `Viewport` argument describes the currently visible region:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
interface Viewport {
|
|
132
|
+
priceMin: number // visible price range bottom
|
|
133
|
+
priceMax: number // visible price range top
|
|
134
|
+
timeMin: number // earliest visible candle timestamp (ms)
|
|
135
|
+
timeMax: number // latest visible candle timestamp (ms)
|
|
136
|
+
resolution: readonly [width: number, height: number] // canvas size in device pixels
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import {
|
|
142
|
+
registerIndicatorPlugin,
|
|
143
|
+
type IndicatorPlugin,
|
|
144
|
+
type CandleData,
|
|
145
|
+
type Viewport
|
|
146
|
+
} from '@tony01/astroneum'
|
|
147
|
+
|
|
148
|
+
interface PivotOutput {
|
|
149
|
+
pivot: number | null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const pivotLines: IndicatorPlugin<PivotOutput> = {
|
|
153
|
+
name: 'PIVOT_LINES',
|
|
154
|
+
shortName: 'Pivot',
|
|
155
|
+
calc(data: CandleData[]): PivotOutput[] {
|
|
156
|
+
return data.map((bar, i) => {
|
|
157
|
+
if (i === 0) return { pivot: null }
|
|
158
|
+
const prev = data[i - 1]
|
|
159
|
+
return { pivot: (prev.high + prev.low + prev.close) / 3 }
|
|
160
|
+
})
|
|
161
|
+
},
|
|
162
|
+
render2D(ctx: CanvasRenderingContext2D, output: PivotOutput[], vp: Viewport): void {
|
|
163
|
+
const [width, height] = vp.resolution
|
|
164
|
+
const priceRange = vp.priceMax - vp.priceMin
|
|
165
|
+
|
|
166
|
+
ctx.save()
|
|
167
|
+
ctx.strokeStyle = '#f5a623'
|
|
168
|
+
ctx.lineWidth = 1
|
|
169
|
+
ctx.setLineDash([4, 4])
|
|
170
|
+
|
|
171
|
+
output.forEach(row => {
|
|
172
|
+
if (row.pivot === null) return
|
|
173
|
+
const y = height - ((row.pivot - vp.priceMin) / priceRange) * height
|
|
174
|
+
ctx.beginPath()
|
|
175
|
+
ctx.moveTo(0, y)
|
|
176
|
+
ctx.lineTo(width, y)
|
|
177
|
+
ctx.stroke()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
ctx.restore()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
registerIndicatorPlugin(pivotLines)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
> **Important:** always call `ctx.save()` / `ctx.restore()` around your draw code.
|
|
188
|
+
> Do not clear the canvas — Astroneum composites multiple layers.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### WebGL2 renderer (renderGL)
|
|
193
|
+
|
|
194
|
+
Use `renderGL` for high-density data, smooth anti-aliased lines, or GPU-intensive effects.
|
|
195
|
+
|
|
196
|
+
Astroneum provides a stable `vbo` (vertex buffer object) that persists across frames — upload new data only when your output changes, not every frame.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import {
|
|
200
|
+
registerIndicatorPlugin,
|
|
201
|
+
type IndicatorPlugin,
|
|
202
|
+
type CandleData,
|
|
203
|
+
type Viewport
|
|
204
|
+
} from '@tony01/astroneum'
|
|
205
|
+
|
|
206
|
+
const VERT_SHADER = `#version 300 es
|
|
207
|
+
in vec2 a_pos;
|
|
208
|
+
uniform vec2 u_resolution;
|
|
209
|
+
void main() {
|
|
210
|
+
vec2 clip = (a_pos / u_resolution) * 2.0 - 1.0;
|
|
211
|
+
gl_Position = vec4(clip.x, -clip.y, 0, 1);
|
|
212
|
+
}`
|
|
213
|
+
|
|
214
|
+
const FRAG_SHADER = `#version 300 es
|
|
215
|
+
precision mediump float;
|
|
216
|
+
uniform vec4 u_color;
|
|
217
|
+
out vec4 fragColor;
|
|
218
|
+
void main() { fragColor = u_color; }`
|
|
219
|
+
|
|
220
|
+
function compileProgram (gl: WebGL2RenderingContext): WebGLProgram {
|
|
221
|
+
function shader (type: number, src: string) {
|
|
222
|
+
const sh = gl.createShader(type)!
|
|
223
|
+
gl.shaderSource(sh, src)
|
|
224
|
+
gl.compileShader(sh)
|
|
225
|
+
return sh
|
|
226
|
+
}
|
|
227
|
+
const prog = gl.createProgram()!
|
|
228
|
+
gl.attachShader(prog, shader(gl.VERTEX_SHADER, VERT_SHADER))
|
|
229
|
+
gl.attachShader(prog, shader(gl.FRAGMENT_SHADER, FRAG_SHADER))
|
|
230
|
+
gl.linkProgram(prog)
|
|
231
|
+
return prog
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let _program: WebGLProgram | null = null
|
|
235
|
+
|
|
236
|
+
interface SMAOutput { value: number | null }
|
|
237
|
+
|
|
238
|
+
const smaGL: IndicatorPlugin<SMAOutput> = {
|
|
239
|
+
name: 'SMA_GL',
|
|
240
|
+
shortName: 'SMA (GL)',
|
|
241
|
+
calcParams: [20],
|
|
242
|
+
calc(data: CandleData[], [len]: number[]): SMAOutput[] {
|
|
243
|
+
return data.map((_, i) => {
|
|
244
|
+
if (i < len - 1) return { value: null }
|
|
245
|
+
const slice = data.slice(i - len + 1, i + 1)
|
|
246
|
+
return { value: slice.reduce((s, b) => s + b.close, 0) / len }
|
|
247
|
+
})
|
|
248
|
+
},
|
|
249
|
+
renderGL(
|
|
250
|
+
gl: WebGL2RenderingContext,
|
|
251
|
+
output: SMAOutput[],
|
|
252
|
+
vp: Viewport,
|
|
253
|
+
vbo: WebGLBuffer
|
|
254
|
+
): void {
|
|
255
|
+
if (!_program) _program = compileProgram(gl)
|
|
256
|
+
|
|
257
|
+
const [width, height] = vp.resolution
|
|
258
|
+
const priceRange = vp.priceMax - vp.priceMin
|
|
259
|
+
const timeRange = vp.timeMax - vp.timeMin
|
|
260
|
+
|
|
261
|
+
// Build vertex array — one x,y pair per visible bar
|
|
262
|
+
const vertices: number[] = []
|
|
263
|
+
output.forEach((row, i) => {
|
|
264
|
+
if (row.value === null) return
|
|
265
|
+
// map to pixel space; use index ratio as a simple x approximation
|
|
266
|
+
const x = (i / (output.length - 1)) * width
|
|
267
|
+
const y = height - ((row.value - vp.priceMin) / priceRange) * height
|
|
268
|
+
vertices.push(x, y)
|
|
269
|
+
void timeRange // suppress unused warning; use real time mapping in production
|
|
270
|
+
})
|
|
271
|
+
if (vertices.length < 4) return
|
|
272
|
+
|
|
273
|
+
// Upload vertices to the reusable VBO
|
|
274
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo)
|
|
275
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW)
|
|
276
|
+
|
|
277
|
+
gl.useProgram(_program)
|
|
278
|
+
|
|
279
|
+
const resLoc = gl.getUniformLocation(_program, 'u_resolution')
|
|
280
|
+
const colorLoc = gl.getUniformLocation(_program, 'u_color')
|
|
281
|
+
const posLoc = gl.getAttribLocation(_program, 'a_pos')
|
|
282
|
+
|
|
283
|
+
gl.uniform2f(resLoc, width, height)
|
|
284
|
+
gl.uniform4f(colorLoc, 0.16, 0.38, 1.0, 1.0) // #2962ff
|
|
285
|
+
|
|
286
|
+
gl.enableVertexAttribArray(posLoc)
|
|
287
|
+
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0)
|
|
288
|
+
|
|
289
|
+
gl.drawArrays(gl.LINE_STRIP, 0, vertices.length / 2)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
registerIndicatorPlugin(smaGL)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
> **Tips:**
|
|
297
|
+
> - Compile shaders once and cache them outside the render function.
|
|
298
|
+
> - The `vbo` is created per indicator and destroyed when the indicator is removed — never delete it manually.
|
|
299
|
+
> - `gl.clear()` is called by Astroneum before your pass; do not call it yourself.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## ChartPlugin — per-chart lifecycle
|
|
304
|
+
|
|
305
|
+
Use `ChartPlugin` when you want to:
|
|
306
|
+
- Bundle one or more indicators that auto-create when a chart mounts
|
|
307
|
+
- Run setup logic (subscribe to external data, register hotkeys, etc.)
|
|
308
|
+
- Guarantee cleanup when the chart unmounts
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import {
|
|
312
|
+
type ChartPlugin,
|
|
313
|
+
type ChartPluginContext,
|
|
314
|
+
type IndicatorPlugin
|
|
315
|
+
} from '@tony01/astroneum'
|
|
316
|
+
|
|
317
|
+
const vwapIndicator: IndicatorPlugin<number | null> = {
|
|
318
|
+
name: 'VWAP',
|
|
319
|
+
shortName: 'VWAP',
|
|
320
|
+
calc(data) {
|
|
321
|
+
let cumPV = 0, cumVol = 0
|
|
322
|
+
return data.map(bar => {
|
|
323
|
+
const vol = bar.volume ?? 0
|
|
324
|
+
cumPV += ((bar.high + bar.low + bar.close) / 3) * vol
|
|
325
|
+
cumVol += vol
|
|
326
|
+
return cumVol === 0 ? null : cumPV / cumVol
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const vwapPlugin: ChartPlugin = {
|
|
332
|
+
name: 'vwap-plugin',
|
|
333
|
+
// 1. Register indicator templates before chart creates any indicators
|
|
334
|
+
indicators: [vwapIndicator],
|
|
335
|
+
// 2. Called once after the chart is mounted
|
|
336
|
+
onInit({ chart }: ChartPluginContext) {
|
|
337
|
+
chart.createIndicator('VWAP', true) // true = overlay on main pane
|
|
338
|
+
|
|
339
|
+
// Return a disposer — called when the chart unmounts
|
|
340
|
+
return () => {
|
|
341
|
+
chart.removeIndicator({ name: 'VWAP' })
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Pass the plugin as a prop:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
<AstroneumChart
|
|
351
|
+
plugins={[vwapPlugin]}
|
|
352
|
+
symbol={...}
|
|
353
|
+
period={...}
|
|
354
|
+
datafeed={...}
|
|
355
|
+
/>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Multiple plugins are supported and executed in order. Disposers run in **reverse** order (LIFO).
|
|
359
|
+
|
|
360
|
+
### `ChartPluginContext`
|
|
361
|
+
|
|
362
|
+
| Property | Type | Description |
|
|
363
|
+
|---|---|---|
|
|
364
|
+
| `chart` | `Chart` | The engine chart instance |
|
|
365
|
+
| `registerIndicatorPlugin` | `(plugin) => void` | Register a single plugin at runtime |
|
|
366
|
+
| `registerIndicatorPlugins` | `(plugins) => void` | Register multiple plugins at runtime |
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Raw engine indicator (IndicatorTemplate)
|
|
371
|
+
|
|
372
|
+
For full control — custom figure types, tooltip labels, drawing primitives — use `registerIndicator` directly.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { registerIndicator, type CandleData } from '@tony01/astroneum'
|
|
376
|
+
import type { IndicatorTemplate } from '@tony01/astroneum' // re-exported from engine
|
|
377
|
+
|
|
378
|
+
const myTemplate: IndicatorTemplate = {
|
|
379
|
+
name: 'MY_INDICATOR',
|
|
380
|
+
shortName: 'My Ind',
|
|
381
|
+
series: 'normal', // 'normal' = sub-pane, 'price' = overlay on candles
|
|
382
|
+
precision: 2,
|
|
383
|
+
calcParams: [14],
|
|
384
|
+
figures: [
|
|
385
|
+
{ key: 'value', title: 'Value: ', type: 'line' }
|
|
386
|
+
],
|
|
387
|
+
calc(dataList: CandleData[], indicator) {
|
|
388
|
+
const [len] = indicator.calcParams
|
|
389
|
+
return dataList.map((_, i) => {
|
|
390
|
+
if (i < len - 1) return { value: null }
|
|
391
|
+
const slice = dataList.slice(i - len + 1, i + 1)
|
|
392
|
+
return { value: slice.reduce((s, b) => s + b.close, 0) / len }
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
registerIndicator(myTemplate)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`getSupportedIndicators()` returns the list of all registered names (built-in + custom).
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## ScriptEngine — runtime scripting
|
|
405
|
+
|
|
406
|
+
`ScriptEngine` lets end-users write Pine-Script-inspired indicator logic in plain JavaScript without a build step. Scripts run in a strict sandbox with no access to `window`, `document`, `fetch`, or `eval`.
|
|
407
|
+
|
|
408
|
+
### Usage
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { ScriptEngine, AstroneumChart } from '@tony01/astroneum'
|
|
412
|
+
|
|
413
|
+
const engine = ScriptEngine.getInstance()
|
|
414
|
+
|
|
415
|
+
const template = engine.compile(`
|
|
416
|
+
study('My Oscillator', { overlay: false })
|
|
417
|
+
|
|
418
|
+
const len = input('Length', 14)
|
|
419
|
+
const highs = ta.highest(high, len)
|
|
420
|
+
const lows = ta.lowest(low, len)
|
|
421
|
+
const range = highs.map((h, i) => h - lows[i])
|
|
422
|
+
|
|
423
|
+
plot(range, { title: 'Range', color: '#2962ff' })
|
|
424
|
+
`)
|
|
425
|
+
|
|
426
|
+
// template.name is set from study()
|
|
427
|
+
// Immediately usable in any chart
|
|
428
|
+
chartRef.current.createIndicator(template.name)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Script API
|
|
432
|
+
|
|
433
|
+
#### `study(name, options?)`
|
|
434
|
+
|
|
435
|
+
Declares the indicator metadata.
|
|
436
|
+
|
|
437
|
+
| Parameter | Type | Description |
|
|
438
|
+
|---|---|---|
|
|
439
|
+
| `name` | `string` | Indicator name used as registry key |
|
|
440
|
+
| `options.shortName` | `string` | Display label (defaults to `name`) |
|
|
441
|
+
| `options.overlay` | `boolean` | `true` to render on the main price pane |
|
|
442
|
+
| `options.precision` | `number` | Decimal places in tooltip (default `4`) |
|
|
443
|
+
|
|
444
|
+
#### `input(title, defaultValue, options?)`
|
|
445
|
+
|
|
446
|
+
Declares a user-editable parameter. Returns `defaultValue` at compile time.
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
const length = input('Length', 14)
|
|
450
|
+
const source = input('Source', 'close', { options: ['open', 'high', 'low', 'close'] })
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### `plot(values, options?)`
|
|
454
|
+
|
|
455
|
+
Registers a series to render as a line.
|
|
456
|
+
|
|
457
|
+
| Parameter | Type | Description |
|
|
458
|
+
|---|---|---|
|
|
459
|
+
| `values` | `number[]` | One value per bar; use `null`/`NaN` to skip a bar |
|
|
460
|
+
| `options.title` | `string` | Legend label |
|
|
461
|
+
| `options.color` | `string` | CSS hex color |
|
|
462
|
+
| `options.lineWidth` | `number` | Line thickness in pixels |
|
|
463
|
+
|
|
464
|
+
Call `plot()` multiple times to add multiple lines.
|
|
465
|
+
|
|
466
|
+
#### Built-in data arrays
|
|
467
|
+
|
|
468
|
+
Inside a script the following arrays are automatically available, each with one element per bar in chronological order:
|
|
469
|
+
|
|
470
|
+
| Variable | Description |
|
|
471
|
+
|---|---|
|
|
472
|
+
| `open` | Open prices |
|
|
473
|
+
| `high` | High prices |
|
|
474
|
+
| `low` | Low prices |
|
|
475
|
+
| `close` | Close prices |
|
|
476
|
+
| `volume` | Volume values |
|
|
477
|
+
|
|
478
|
+
#### `ta` — technical analysis helpers
|
|
479
|
+
|
|
480
|
+
| Function | Signature | Description |
|
|
481
|
+
|---|---|---|
|
|
482
|
+
| `ta.sma` | `(src, len)` | Simple moving average |
|
|
483
|
+
| `ta.ema` | `(src, len)` | Exponential moving average |
|
|
484
|
+
| `ta.rma` | `(src, len)` | Wilder's moving average (used in RSI) |
|
|
485
|
+
| `ta.wma` | `(src, len)` | Weighted moving average |
|
|
486
|
+
| `ta.rsi` | `(src, len)` | Relative Strength Index |
|
|
487
|
+
| `ta.bbands` | `(src, len, mult?)` | Bollinger Bands → `{ upper, middle, lower }` |
|
|
488
|
+
| `ta.macd` | `(src, fast?, slow?, signal?)` | MACD → `{ macd, signal, histogram }` |
|
|
489
|
+
| `ta.highest` | `(src, len)` | Rolling maximum |
|
|
490
|
+
| `ta.lowest` | `(src, len)` | Rolling minimum |
|
|
491
|
+
| `ta.stdev` | `(src, len)` | Rolling standard deviation |
|
|
492
|
+
| `ta.cross` | `(a, b)` | Cross in either direction → `boolean[]` |
|
|
493
|
+
| `ta.crossover` | `(a, b)` | Cross upward → `boolean[]` |
|
|
494
|
+
| `ta.crossunder` | `(a, b)` | Cross downward → `boolean[]` |
|
|
495
|
+
|
|
496
|
+
`math` is also available as an alias for `Math`.
|
|
497
|
+
|
|
498
|
+
### Complete script example — MACD
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
study('MACD', { overlay: false })
|
|
502
|
+
|
|
503
|
+
const fast = input('Fast', 12)
|
|
504
|
+
const slow = input('Slow', 26)
|
|
505
|
+
const signal = input('Signal', 9)
|
|
506
|
+
|
|
507
|
+
const { macd, signal: signalLine, histogram } = ta.macd(close, fast, slow, signal)
|
|
508
|
+
|
|
509
|
+
plot(macd, { title: 'MACD', color: '#2962ff' })
|
|
510
|
+
plot(signalLine, { title: 'Signal', color: '#ff6b35' })
|
|
511
|
+
plot(histogram, { title: 'Hist', color: '#26a69a' })
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### ScriptEngine instance methods
|
|
515
|
+
|
|
516
|
+
| Method | Description |
|
|
517
|
+
|---|---|
|
|
518
|
+
| `compile(source, name?)` | Compile source string, register indicator, return `CompiledIndicator`. Throws on syntax/runtime error. |
|
|
519
|
+
| `get(name)` | Return a previously compiled template by name |
|
|
520
|
+
| `list()` | Return all compiled indicator names |
|
|
521
|
+
| `remove(name)` | Remove a compiled indicator from the registry |
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Built-in indicator reference
|
|
526
|
+
|
|
527
|
+
All built-in indicators are available by name via `createIndicator()` or the `subIndicators` / `mainIndicators` props.
|
|
528
|
+
|
|
529
|
+
| Name | Description |
|
|
530
|
+
|---|---|
|
|
531
|
+
| `MA` | Moving Average |
|
|
532
|
+
| `EMA` | Exponential Moving Average |
|
|
533
|
+
| `SMA` | Simple Moving Average |
|
|
534
|
+
| `BOLL` | Bollinger Bands |
|
|
535
|
+
| `MACD` | MACD |
|
|
536
|
+
| `RSI` | Relative Strength Index |
|
|
537
|
+
| `KDJ` | Stochastic KDJ |
|
|
538
|
+
| `VOL` | Volume |
|
|
539
|
+
| `OBV` | On Balance Volume |
|
|
540
|
+
| `AVP` | Average Price |
|
|
541
|
+
| `AO` | Awesome Oscillator |
|
|
542
|
+
| `BIAS` | BIAS |
|
|
543
|
+
| `BRAR` | BRAR |
|
|
544
|
+
| `BBI` | Bull & Bear Index |
|
|
545
|
+
| `CCI` | Commodity Channel Index |
|
|
546
|
+
| `CR` | Current Ratio |
|
|
547
|
+
| `DMA` | Different of Moving Average |
|
|
548
|
+
| `DMI` | Directional Movement Index |
|
|
549
|
+
| `EMV` | Ease of Movement |
|
|
550
|
+
| `MTM` | Momentum |
|
|
551
|
+
| `PVT` | Price & Volume Trend |
|
|
552
|
+
| `PSY` | Psychological Line |
|
|
553
|
+
| `ROC` | Rate of Change |
|
|
554
|
+
| `SAR` | Stop and Reverse |
|
|
555
|
+
| `TRIX` | Triple Exponentially Smoothed Average |
|
|
556
|
+
| `VR` | Volume Ratio |
|
|
557
|
+
| `WR` | Williams %R |
|
|
558
|
+
|
|
559
|
+
Use `getSupportedIndicators()` at runtime to retrieve the complete list including any custom-registered indicators.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Full API reference
|
|
564
|
+
|
|
565
|
+
See [api.md](api.md) for the complete type reference including `IndicatorPlugin`, `ChartPlugin`, `ChartPluginContext`, `Viewport`, `registerIndicatorPlugin`, `registerIndicatorPlugins`, `createIndicatorTemplateFromPlugin`, `registerIndicator`, `getSupportedIndicators`, and `ScriptEngine`.
|