@tradecanvas/svelte 1.1.0 → 1.2.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/README.md CHANGED
@@ -35,7 +35,7 @@ Streams live Binance data by default. Pass `data={bars}` for static OHLC, or `ad
35
35
 
36
36
  ## Props
37
37
 
38
- `symbol`, `timeframe`, `theme`, `chartType`, `indicators`, `data`, `adapter`, `historyLimit`, `features`, `autoScale`, `signalMarkers`, `signalMarkerStyle`, `tradeZones`, `tradeZoneStyle`, `overrides` (style overrides by key, all of your app's), `watermarkText`, `onReady`, and `bind:chart`.
38
+ `symbol`, `timeframe`, `theme`, `chartType`, `indicators` (ids, or `{ id, params, position }`), `data` (bars of your own; no stream), `adapter`, `stream` (`false`: no stream of its own), `historyLimit`, `features` (changes apply after mount too), `autoScale`, `signalMarkers`, `signalMarkerStyle`, `tradeZones`, `tradeZoneStyle`, `overrides` (style overrides by key, all of your app's), `watermarkText`, `onReady`, and `bind:chart`.
39
39
 
40
40
  Reach the underlying `Chart` (drawings, trading, execution adapters, plugins, resizable panes) via `bind:chart` or `onReady`. See the [main docs](https://github.com/bonguynvan/tradecanvas).
41
41
 
@@ -5,6 +5,7 @@
5
5
  BinanceAdapter,
6
6
  DARK_THEME,
7
7
  LIGHT_THEME,
8
+ syncIndicators,
8
9
  } from '@tradecanvas/chart';
9
10
  import type {
10
11
  ChartType,
@@ -18,6 +19,7 @@
18
19
  TradeZoneStyle,
19
20
  DataAdapter,
20
21
  ChartStyleOverrides,
22
+ IndicatorSpec,
21
23
  } from '@tradecanvas/chart';
22
24
 
23
25
  interface Props {
@@ -25,10 +27,15 @@
25
27
  timeframe?: TimeFrame;
26
28
  theme?: 'dark' | 'light' | Theme;
27
29
  chartType?: ChartType;
28
- indicators?: string[];
30
+ /** Indicators by id (`'rsi'`), or with their inputs (`{ id: 'ema', params: { period: 50 } }`). */
31
+ indicators?: IndicatorSpec[];
32
+ /** Static bars: the chart shows these, and opens no stream of its own when it has them at mount. */
29
33
  data?: OHLCBar[];
30
34
  adapter?: DataAdapter;
35
+ /** `false`: no stream until you say so (no Binance by default); the chart waits for `data`. Default `true`. */
36
+ stream?: boolean;
31
37
  historyLimit?: number;
38
+ /** What users can do on the chart; changes after mount are applied too (the keys given). */
32
39
  features?: FeaturesConfig;
33
40
  autoScale?: boolean;
34
41
  signalMarkers?: SignalMarker[];
@@ -47,9 +54,10 @@
47
54
  timeframe = '5m' as TimeFrame,
48
55
  theme = 'dark' as 'dark' | 'light' | Theme,
49
56
  chartType = 'candlestick' as ChartType,
50
- indicators = [] as string[],
57
+ indicators = [] as IndicatorSpec[],
51
58
  data = undefined as OHLCBar[] | undefined,
52
59
  adapter = undefined as DataAdapter | undefined,
60
+ stream = true,
53
61
  historyLimit = 500,
54
62
  features = undefined as FeaturesConfig | undefined,
55
63
  autoScale = true,
@@ -72,8 +80,10 @@
72
80
  let container: HTMLDivElement;
73
81
  let instance: Chart | null = null;
74
82
  const indicatorIds = new Map<string, string>();
75
- let prevSymbol = symbol;
76
- let prevTimeframe = timeframe;
83
+ /** The stream open now (`symbol|timeframe`), or null. */
84
+ let streamKey: string | null = null;
85
+ /** The features last applied, as text. */
86
+ let featuresKey: string | null = null;
77
87
 
78
88
  onMount(() => {
79
89
  instance = new Chart(container, {
@@ -108,10 +118,12 @@
108
118
 
109
119
  if (data) {
110
120
  instance.setData(data);
111
- } else {
121
+ } else if (stream) {
112
122
  const dataAdapter = adapter ?? new BinanceAdapter();
113
123
  instance.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });
124
+ streamKey = `${symbol}|${timeframe}`;
114
125
  }
126
+ featuresKey = JSON.stringify($state.snapshot(features) ?? null);
115
127
 
116
128
  onReady?.(instance);
117
129
  });
@@ -125,38 +137,40 @@
125
137
  }
126
138
  });
127
139
 
140
+ // A stream of its own while it has no data; `stream={false}` closes it. Data given later leaves it be.
128
141
  $effect(() => {
129
- if (!instance || data) return;
130
- const _s = symbol;
131
- const _tf = timeframe;
132
- if (_s === prevSymbol && _tf === prevTimeframe) return;
133
- prevSymbol = _s;
134
- prevTimeframe = _tf;
142
+ const key = `${symbol}|${timeframe}`;
143
+ const on = stream;
144
+ const hasData = !!data;
145
+ if (!instance) return;
146
+ if (!on) {
147
+ if (streamKey !== null) instance.disconnectStream();
148
+ streamKey = null;
149
+ return;
150
+ }
151
+ if (hasData || streamKey === key) return;
152
+ streamKey = key;
135
153
  instance.disconnectStream();
136
154
  const dataAdapter = adapter ?? new BinanceAdapter();
137
- instance.connect({ adapter: dataAdapter, symbol: _s, timeframe: _tf, historyLimit });
155
+ instance.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });
138
156
  if (watermarkText) instance.setWatermark(watermarkText);
139
157
  });
140
158
 
159
+ $effect(() => {
160
+ const next = $state.snapshot(features);
161
+ const key = JSON.stringify(next ?? null);
162
+ if (!instance || key === featuresKey) return;
163
+ featuresKey = key;
164
+ if (next) instance.setFeatures(next);
165
+ });
166
+
141
167
  $effect(() => { instance?.setTheme(resolveTheme(theme)); });
142
168
  $effect(() => { instance?.setChartType(chartType); });
143
169
  $effect(() => { if (data) instance?.setData(data); });
144
170
 
145
171
  $effect(() => {
146
- if (!instance) return;
147
- const desired = new Set(indicators);
148
- for (const [name, instanceId] of indicatorIds) {
149
- if (!desired.has(name)) {
150
- instance.removeIndicator(instanceId);
151
- indicatorIds.delete(name);
152
- }
153
- }
154
- for (const name of indicators) {
155
- if (!indicatorIds.has(name)) {
156
- const instanceId = instance.addIndicator(name);
157
- if (instanceId) indicatorIds.set(name, instanceId);
158
- }
159
- }
172
+ const specs = $state.snapshot(indicators) as IndicatorSpec[];
173
+ if (instance) syncIndicators(instance, specs, indicatorIds);
160
174
  });
161
175
 
162
176
  $effect(() => { if (signalMarkers) instance?.setSignalMarkers(signalMarkers); });
@@ -1,14 +1,19 @@
1
1
  import { Chart } from '@tradecanvas/chart';
2
- import type { ChartType, TimeFrame, FeaturesConfig, Theme, OHLCBar, SignalMarker, TradeZone, SignalMarkerStyle, TradeZoneStyle, DataAdapter, ChartStyleOverrides } from '@tradecanvas/chart';
2
+ import type { ChartType, TimeFrame, FeaturesConfig, Theme, OHLCBar, SignalMarker, TradeZone, SignalMarkerStyle, TradeZoneStyle, DataAdapter, ChartStyleOverrides, IndicatorSpec } from '@tradecanvas/chart';
3
3
  interface Props {
4
4
  symbol?: string;
5
5
  timeframe?: TimeFrame;
6
6
  theme?: 'dark' | 'light' | Theme;
7
7
  chartType?: ChartType;
8
- indicators?: string[];
8
+ /** Indicators by id (`'rsi'`), or with their inputs (`{ id: 'ema', params: { period: 50 } }`). */
9
+ indicators?: IndicatorSpec[];
10
+ /** Static bars: the chart shows these, and opens no stream of its own when it has them at mount. */
9
11
  data?: OHLCBar[];
10
12
  adapter?: DataAdapter;
13
+ /** `false`: no stream until you say so (no Binance by default); the chart waits for `data`. Default `true`. */
14
+ stream?: boolean;
11
15
  historyLimit?: number;
16
+ /** What users can do on the chart; changes after mount are applied too (the keys given). */
12
17
  features?: FeaturesConfig;
13
18
  autoScale?: boolean;
14
19
  signalMarkers?: SignalMarker[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradecanvas/svelte",
3
- "version": "1.1.0",
3
+ "version": "1.2.2",
4
4
  "type": "module",
5
5
  "description": "Svelte 5 component for @tradecanvas/chart — reactive props, runes, zero boilerplate.",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "README.md"
36
36
  ],
37
37
  "dependencies": {
38
- "@tradecanvas/chart": "1.13.0"
38
+ "@tradecanvas/chart": "1.16.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "svelte": ">=5.0.0"