@forgecharts/sdk 1.1.23
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/package.json +50 -0
- package/src/__tests__/backwardCompatibility.test.ts +191 -0
- package/src/__tests__/candleInvariant.test.ts +500 -0
- package/src/__tests__/public-api-surface.ts +76 -0
- package/src/__tests__/timeframeBoundary.test.ts +583 -0
- package/src/api/DrawingManager.ts +188 -0
- package/src/api/EventBus.ts +53 -0
- package/src/api/IndicatorDAG.ts +389 -0
- package/src/api/IndicatorRegistry.ts +47 -0
- package/src/api/LayoutManager.ts +72 -0
- package/src/api/PaneManager.ts +129 -0
- package/src/api/ReferenceAPI.ts +195 -0
- package/src/api/TChart.ts +881 -0
- package/src/api/createChart.ts +43 -0
- package/src/api/drawing tools/fib gann menu/fibRetracement.ts +27 -0
- package/src/api/drawing tools/lines menu/crossLine.ts +21 -0
- package/src/api/drawing tools/lines menu/disjointChannel.ts +74 -0
- package/src/api/drawing tools/lines menu/extendedLine.ts +22 -0
- package/src/api/drawing tools/lines menu/flatTopBottom.ts +45 -0
- package/src/api/drawing tools/lines menu/horizontal.ts +24 -0
- package/src/api/drawing tools/lines menu/horizontalRay.ts +25 -0
- package/src/api/drawing tools/lines menu/infoLine.ts +127 -0
- package/src/api/drawing tools/lines menu/insidePitchfork.ts +21 -0
- package/src/api/drawing tools/lines menu/modifiedSchiffPitchfork.ts +18 -0
- package/src/api/drawing tools/lines menu/parallelChannel.ts +47 -0
- package/src/api/drawing tools/lines menu/pitchfork.ts +15 -0
- package/src/api/drawing tools/lines menu/ray.ts +28 -0
- package/src/api/drawing tools/lines menu/regressionTrend.ts +157 -0
- package/src/api/drawing tools/lines menu/schiffPitchfork.ts +18 -0
- package/src/api/drawing tools/lines menu/trendAngle.ts +64 -0
- package/src/api/drawing tools/lines menu/trendline.ts +16 -0
- package/src/api/drawing tools/lines menu/vertical.ts +16 -0
- package/src/api/drawing tools/pointers menu/crosshair.ts +17 -0
- package/src/api/drawing tools/pointers menu/cursor.ts +16 -0
- package/src/api/drawing tools/pointers menu/demonstration.ts +35 -0
- package/src/api/drawing tools/pointers menu/dot.ts +26 -0
- package/src/api/drawing tools/shapes menu/rectangle.ts +24 -0
- package/src/api/drawing tools/shapes menu/text.ts +30 -0
- package/src/api/drawingUtils.ts +82 -0
- package/src/core/CanvasLayer.ts +77 -0
- package/src/core/Chart.ts +917 -0
- package/src/core/CoordTransform.ts +282 -0
- package/src/core/Crosshair.ts +207 -0
- package/src/core/IndicatorEngine.ts +216 -0
- package/src/core/InteractionManager.ts +899 -0
- package/src/core/PriceScale.ts +133 -0
- package/src/core/Series.ts +132 -0
- package/src/core/TimeScale.ts +175 -0
- package/src/datafeed/DatafeedConnector.ts +300 -0
- package/src/engine/CandleEngine.ts +458 -0
- package/src/engine/__tests__/CandleEngine.test.ts +402 -0
- package/src/engine/candleInvariants.ts +172 -0
- package/src/engine/mergeUtils.ts +93 -0
- package/src/engine/timeframeUtils.ts +118 -0
- package/src/index.ts +190 -0
- package/src/internal.ts +41 -0
- package/src/licensing/ChartRuntimeResolver.ts +380 -0
- package/src/licensing/LicenseManager.ts +131 -0
- package/src/licensing/__tests__/ChartRuntimeResolver.test.ts +207 -0
- package/src/licensing/__tests__/LicenseManager.test.ts +180 -0
- package/src/licensing/licenseTypes.ts +19 -0
- package/src/pine/PineCompiler.ts +68 -0
- package/src/pine/diagnostics.ts +30 -0
- package/src/pine/index.ts +7 -0
- package/src/pine/pine-ast.ts +163 -0
- package/src/pine/pine-lexer.ts +265 -0
- package/src/pine/pine-parser.ts +439 -0
- package/src/pine/pine-transpiler.ts +301 -0
- package/src/pixi/LayerName.ts +35 -0
- package/src/pixi/PixiCandlestickRenderer.ts +125 -0
- package/src/pixi/PixiChart.ts +425 -0
- package/src/pixi/PixiCrosshairRenderer.ts +134 -0
- package/src/pixi/PixiDrawingRenderer.ts +121 -0
- package/src/pixi/PixiGridRenderer.ts +136 -0
- package/src/pixi/PixiLayerManager.ts +102 -0
- package/src/renderers/CandlestickRenderer.ts +130 -0
- package/src/renderers/HistogramRenderer.ts +63 -0
- package/src/renderers/LineRenderer.ts +77 -0
- package/src/theme/colors.ts +21 -0
- package/src/tools/barDivergenceCheck.ts +305 -0
- package/src/trading/TradingOverlayStore.ts +161 -0
- package/src/trading/UnmanagedIngestion.ts +156 -0
- package/src/trading/__tests__/ManagedTradingController.test.ts +338 -0
- package/src/trading/__tests__/TradingOverlayStore.test.ts +323 -0
- package/src/trading/__tests__/UnmanagedIngestion.test.ts +205 -0
- package/src/trading/managed/ManagedTradingController.ts +292 -0
- package/src/trading/managed/managedCapabilities.ts +98 -0
- package/src/trading/managed/managedTypes.ts +151 -0
- package/src/trading/tradingTypes.ts +135 -0
- package/src/tscript/TScriptIndicator.ts +54 -0
- package/src/tscript/ast.ts +105 -0
- package/src/tscript/lexer.ts +190 -0
- package/src/tscript/parser.ts +334 -0
- package/src/tscript/runtime.ts +525 -0
- package/src/tscript/series.ts +84 -0
- package/src/types/IChart.ts +56 -0
- package/src/types/IRenderer.ts +16 -0
- package/src/types/ISeries.ts +30 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +15 -0
- package/vitest.config.ts +25 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChartLayout,
|
|
3
|
+
ChartInterval,
|
|
4
|
+
ChartTheme,
|
|
5
|
+
IndicatorInstance,
|
|
6
|
+
Drawing,
|
|
7
|
+
Viewport,
|
|
8
|
+
PaneDescriptor,
|
|
9
|
+
} from '@forgecharts/types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* LayoutManager — pure serialisation / deserialisation of chart state.
|
|
13
|
+
* No side effects; state mutations are the caller's responsibility.
|
|
14
|
+
*/
|
|
15
|
+
export class LayoutManager {
|
|
16
|
+
/**
|
|
17
|
+
* Produces a serialisable snapshot of the current chart state.
|
|
18
|
+
*/
|
|
19
|
+
static save(params: {
|
|
20
|
+
symbol: string;
|
|
21
|
+
interval: ChartInterval;
|
|
22
|
+
theme: ChartTheme;
|
|
23
|
+
indicators: readonly IndicatorInstance[];
|
|
24
|
+
drawings: readonly Drawing[];
|
|
25
|
+
viewport?: Viewport;
|
|
26
|
+
panes?: readonly PaneDescriptor[];
|
|
27
|
+
}): ChartLayout {
|
|
28
|
+
return {
|
|
29
|
+
symbol: params.symbol,
|
|
30
|
+
interval: params.interval,
|
|
31
|
+
theme: params.theme,
|
|
32
|
+
indicators: [...params.indicators],
|
|
33
|
+
drawings: [...params.drawings],
|
|
34
|
+
...(params.viewport !== undefined ? { viewport: params.viewport } : {}),
|
|
35
|
+
...(params.panes !== undefined && params.panes.length > 0 ? { panes: [...params.panes] } : {}),
|
|
36
|
+
savedAt: Date.now(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Serialises a layout to a JSON string safe for `localStorage` / remote persistence.
|
|
42
|
+
*/
|
|
43
|
+
static toJSON(layout: ChartLayout): string {
|
|
44
|
+
return JSON.stringify(layout);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parses a JSON string back into a ChartLayout.
|
|
49
|
+
* Throws if the string is malformed.
|
|
50
|
+
*/
|
|
51
|
+
static fromJSON(raw: string): ChartLayout {
|
|
52
|
+
const parsed: unknown = JSON.parse(raw);
|
|
53
|
+
if (!LayoutManager._isLayout(parsed)) {
|
|
54
|
+
throw new Error('[ForgeCharts] LayoutManager.fromJSON: invalid layout schema');
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private static _isLayout(v: unknown): v is ChartLayout {
|
|
60
|
+
if (typeof v !== 'object' || v === null) return false;
|
|
61
|
+
const l = v as Record<string, unknown>;
|
|
62
|
+
return (
|
|
63
|
+
typeof l['symbol'] === 'string' &&
|
|
64
|
+
typeof l['interval'] === 'string' &&
|
|
65
|
+
typeof l['theme'] === 'string' &&
|
|
66
|
+
Array.isArray(l['indicators']) &&
|
|
67
|
+
Array.isArray(l['drawings']) &&
|
|
68
|
+
typeof l['savedAt'] === 'number' &&
|
|
69
|
+
(l['panes'] === undefined || Array.isArray(l['panes']))
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { PaneDescriptor } from '@forgecharts/types';
|
|
2
|
+
|
|
3
|
+
let _paneId = 0;
|
|
4
|
+
function _nextId(): string {
|
|
5
|
+
return `pane-${(++_paneId).toString()}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PaneManager — manages the collection of sub-pane (indicator panel) descriptors.
|
|
10
|
+
*
|
|
11
|
+
* Each `PaneDescriptor` carries:
|
|
12
|
+
* - a stable `id`
|
|
13
|
+
* - a `heightFraction` (0–1, all panes sum to 1)
|
|
14
|
+
* - the `indicatorIds` assigned to that pane
|
|
15
|
+
*
|
|
16
|
+
* Height redistribution is proportional: when a pane is added/removed/resized,
|
|
17
|
+
* the remaining panes absorb or donate height proportionally to their current sizes.
|
|
18
|
+
*/
|
|
19
|
+
export class PaneManager {
|
|
20
|
+
private _panes: PaneDescriptor[] = [];
|
|
21
|
+
|
|
22
|
+
getPanes(): readonly PaneDescriptor[] {
|
|
23
|
+
return this._panes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new pane, distributing total height evenly across all panes.
|
|
28
|
+
* @returns The new pane's id.
|
|
29
|
+
*/
|
|
30
|
+
createPane(label?: string): string {
|
|
31
|
+
const id = _nextId();
|
|
32
|
+
const newFraction = 1 / (this._panes.length + 1);
|
|
33
|
+
// Shrink existing panes proportionally
|
|
34
|
+
this._panes = this._panes.map((p) => ({
|
|
35
|
+
...p,
|
|
36
|
+
heightFraction: p.heightFraction * (1 - newFraction),
|
|
37
|
+
}));
|
|
38
|
+
this._panes.push({
|
|
39
|
+
id,
|
|
40
|
+
heightFraction: newFraction,
|
|
41
|
+
indicatorIds: [],
|
|
42
|
+
...(label !== undefined ? { label } : {}),
|
|
43
|
+
});
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Removes a pane and redistributes its height fraction to remaining panes.
|
|
49
|
+
*/
|
|
50
|
+
removePane(id: string): void {
|
|
51
|
+
const idx = this._panes.findIndex((p) => p.id === id);
|
|
52
|
+
if (idx === -1) return;
|
|
53
|
+
const freed = this._panes[idx]!.heightFraction;
|
|
54
|
+
this._panes.splice(idx, 1);
|
|
55
|
+
if (this._panes.length > 0) {
|
|
56
|
+
const total = this._panes.reduce((s, p) => s + p.heightFraction, 0);
|
|
57
|
+
if (total > 0) {
|
|
58
|
+
this._panes = this._panes.map((p) => ({
|
|
59
|
+
...p,
|
|
60
|
+
heightFraction: p.heightFraction + freed * (p.heightFraction / total),
|
|
61
|
+
}));
|
|
62
|
+
} else {
|
|
63
|
+
const even = 1 / this._panes.length;
|
|
64
|
+
this._panes = this._panes.map((p) => ({ ...p, heightFraction: even }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Assigns an indicator to a pane, removing it from any previous pane first.
|
|
71
|
+
*/
|
|
72
|
+
assignIndicator(indicatorId: string, paneId: string): void {
|
|
73
|
+
// Remove from every pane first (mutual exclusion)
|
|
74
|
+
this._panes = this._panes.map((p) => ({
|
|
75
|
+
...p,
|
|
76
|
+
indicatorIds: p.indicatorIds.filter((iid) => iid !== indicatorId),
|
|
77
|
+
}));
|
|
78
|
+
const idx = this._panes.findIndex((p) => p.id === paneId);
|
|
79
|
+
if (idx === -1) return;
|
|
80
|
+
const pane = this._panes[idx]!;
|
|
81
|
+
this._panes[idx] = { ...pane, indicatorIds: [...pane.indicatorIds, indicatorId] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Unassigns an indicator from whichever pane currently holds it.
|
|
86
|
+
*/
|
|
87
|
+
unassignIndicator(indicatorId: string): void {
|
|
88
|
+
this._panes = this._panes.map((p) => ({
|
|
89
|
+
...p,
|
|
90
|
+
indicatorIds: p.indicatorIds.filter((iid) => iid !== indicatorId),
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resizes a pane by setting its `heightFraction`, clamped to [0.05, 0.9].
|
|
96
|
+
* Remaining panes absorb the difference proportionally.
|
|
97
|
+
*/
|
|
98
|
+
resizePane(id: string, newFraction: number): void {
|
|
99
|
+
const clamped = Math.min(Math.max(newFraction, 0.05), 0.9);
|
|
100
|
+
const idx = this._panes.findIndex((p) => p.id === id);
|
|
101
|
+
if (idx === -1) return;
|
|
102
|
+
const old = this._panes[idx]!.heightFraction;
|
|
103
|
+
const delta = clamped - old;
|
|
104
|
+
const othersTotal = this._panes.reduce(
|
|
105
|
+
(s, p, i) => (i !== idx ? s + p.heightFraction : s),
|
|
106
|
+
0,
|
|
107
|
+
);
|
|
108
|
+
this._panes = this._panes.map((p, i) => {
|
|
109
|
+
if (i === idx) return { ...p, heightFraction: clamped };
|
|
110
|
+
const share =
|
|
111
|
+
othersTotal > 0
|
|
112
|
+
? p.heightFraction / othersTotal
|
|
113
|
+
: 1 / Math.max(1, this._panes.length - 1);
|
|
114
|
+
return { ...p, heightFraction: Math.max(0.05, p.heightFraction - delta * share) };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
serialize(): readonly PaneDescriptor[] {
|
|
119
|
+
return [...this._panes];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
restore(panes: readonly PaneDescriptor[]): void {
|
|
123
|
+
this._panes = [...panes];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
clear(): void {
|
|
127
|
+
this._panes = [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RefMarket,
|
|
3
|
+
RefExchange,
|
|
4
|
+
RefSymbol,
|
|
5
|
+
SymbolSearchFilters,
|
|
6
|
+
SymbolSearchResult,
|
|
7
|
+
} from '@forgecharts/types';
|
|
8
|
+
|
|
9
|
+
export type ReferenceAPIConfig = {
|
|
10
|
+
/** Base URL of the ForgeCharts API server (e.g. "https://charts.myapp.com"). Defaults to current origin. */
|
|
11
|
+
apiUrl?: string;
|
|
12
|
+
/** Called to retrieve a Bearer token for authenticated endpoints. Falls back to localStorage['forgecharts-token']. */
|
|
13
|
+
getAuthToken?: () => string | Promise<string>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ProviderMapping = {
|
|
17
|
+
providerCode: string;
|
|
18
|
+
providerSymbol: string;
|
|
19
|
+
providerExchange: string | null;
|
|
20
|
+
metadata: unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ReferenceAPI — headless SDK client for the Global Reference Data Platform.
|
|
25
|
+
*
|
|
26
|
+
* Provides symbol lookup, search, market/exchange browsing,
|
|
27
|
+
* provider mapping resolution, plus per-user favorites and recents.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const ref = new ReferenceAPI();
|
|
32
|
+
* const { symbols } = await ref.searchSymbols('BTC');
|
|
33
|
+
* const popular = await ref.getPopularSymbols('crypto', 10);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class ReferenceAPI {
|
|
37
|
+
private readonly _base: string;
|
|
38
|
+
private readonly _getToken: (() => string | Promise<string>) | undefined;
|
|
39
|
+
|
|
40
|
+
constructor(config: ReferenceAPIConfig = {}) {
|
|
41
|
+
this._base = (config.apiUrl ?? '').replace(/\/$/, '') + '/api/reference';
|
|
42
|
+
this._getToken = config.getAuthToken;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Internals ──────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
private async _authHeaders(): Promise<Record<string, string>> {
|
|
48
|
+
const h: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
49
|
+
let token: string | null = null;
|
|
50
|
+
|
|
51
|
+
if (this._getToken) {
|
|
52
|
+
token = await this._getToken();
|
|
53
|
+
} else if (typeof localStorage !== 'undefined') {
|
|
54
|
+
token = localStorage.getItem('forgecharts-token');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (token) h['Authorization'] = `Bearer ${token}`;
|
|
58
|
+
return h;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async _get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {
|
|
62
|
+
let url = this._base + path;
|
|
63
|
+
if (params) {
|
|
64
|
+
const qs = new URLSearchParams();
|
|
65
|
+
for (const [k, v] of Object.entries(params)) {
|
|
66
|
+
if (v !== undefined) qs.set(k, String(v));
|
|
67
|
+
}
|
|
68
|
+
const str = qs.toString();
|
|
69
|
+
if (str) url += '?' + str;
|
|
70
|
+
}
|
|
71
|
+
const res = await fetch(url, { headers: await this._authHeaders() });
|
|
72
|
+
if (!res.ok) throw new Error(`ReferenceAPI GET ${path} → HTTP ${res.status}`);
|
|
73
|
+
return res.json() as Promise<T>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async _post(path: string, body: unknown): Promise<void> {
|
|
77
|
+
const res = await fetch(this._base + path, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: await this._authHeaders(),
|
|
80
|
+
body: JSON.stringify(body),
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok && res.status !== 204) {
|
|
83
|
+
throw new Error(`ReferenceAPI POST ${path} → HTTP ${res.status}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async _delete(path: string): Promise<void> {
|
|
88
|
+
const res = await fetch(this._base + path, {
|
|
89
|
+
method: 'DELETE',
|
|
90
|
+
headers: await this._authHeaders(),
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok && res.status !== 204) {
|
|
93
|
+
throw new Error(`ReferenceAPI DELETE ${path} → HTTP ${res.status}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Markets & Exchanges ────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/** Returns all active market categories (crypto, stocks, forex, …). */
|
|
100
|
+
async getMarkets(): Promise<RefMarket[]> {
|
|
101
|
+
return this._get<RefMarket[]>('/markets');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Returns all active exchanges / venues. */
|
|
105
|
+
async getExchanges(): Promise<RefExchange[]> {
|
|
106
|
+
return this._get<RefExchange[]>('/exchanges');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Symbol catalogue ───────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Full-text search across symbol tickers, names, and aliases.
|
|
113
|
+
* @param query Search term (minimum 1 character).
|
|
114
|
+
* @param filters Optional market / exchange / limit / offset filters.
|
|
115
|
+
*/
|
|
116
|
+
async searchSymbols(query: string, filters?: SymbolSearchFilters): Promise<SymbolSearchResult> {
|
|
117
|
+
return this._get<SymbolSearchResult>('/symbols/search', {
|
|
118
|
+
q: query,
|
|
119
|
+
market: filters?.market,
|
|
120
|
+
exchange: filters?.exchange,
|
|
121
|
+
limit: filters?.limit,
|
|
122
|
+
offset: filters?.offset,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Browse the symbol catalogue with optional filters.
|
|
128
|
+
* Returns symbols ordered alphabetically.
|
|
129
|
+
*/
|
|
130
|
+
async getSymbols(filters?: SymbolSearchFilters): Promise<SymbolSearchResult> {
|
|
131
|
+
return this._get<SymbolSearchResult>('/symbols', {
|
|
132
|
+
market: filters?.market,
|
|
133
|
+
exchange: filters?.exchange,
|
|
134
|
+
assetType: (filters as Record<string, unknown>)?.['assetType'] as string | undefined,
|
|
135
|
+
limit: filters?.limit,
|
|
136
|
+
offset: filters?.offset,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns the most popular / trending symbols.
|
|
142
|
+
* @param market Optional market code to restrict results (e.g. "crypto").
|
|
143
|
+
* @param limit Maximum results to return. Defaults to 20.
|
|
144
|
+
*/
|
|
145
|
+
async getPopularSymbols(market?: string, limit = 20): Promise<RefSymbol[]> {
|
|
146
|
+
return this._get<RefSymbol[]>('/symbols/popular', { market, limit });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fetches full metadata for a single symbol by its global symbol key.
|
|
151
|
+
* @param symbolKey e.g. "CRYPTO:BINANCE:BTCUSDT"
|
|
152
|
+
*/
|
|
153
|
+
async getSymbol(symbolKey: string): Promise<RefSymbol> {
|
|
154
|
+
return this._get<RefSymbol>(`/symbols/${encodeURIComponent(symbolKey)}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Returns all provider-specific mappings for a symbol.
|
|
159
|
+
* Useful for resolving the correct datafeed symbol string at runtime.
|
|
160
|
+
*/
|
|
161
|
+
async getProviderMapping(symbolKey: string): Promise<ProviderMapping[]> {
|
|
162
|
+
return this._get<ProviderMapping[]>(
|
|
163
|
+
`/symbols/${encodeURIComponent(symbolKey)}/provider-mapping`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── User: Recents (requires auth) ─────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/** Returns the authenticated user's recently viewed symbols (newest first). */
|
|
170
|
+
async getRecentSymbols(): Promise<RefSymbol[]> {
|
|
171
|
+
return this._get<RefSymbol[]>('/me/recents');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Records a symbol as recently viewed for the authenticated user. */
|
|
175
|
+
async recordRecentSymbol(symbolKey: string): Promise<void> {
|
|
176
|
+
return this._post('/me/recents', { symbolKey });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── User: Favorites (requires auth) ───────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
/** Returns the authenticated user's favorite symbols. */
|
|
182
|
+
async getFavoriteSymbols(): Promise<RefSymbol[]> {
|
|
183
|
+
return this._get<RefSymbol[]>('/me/favorites');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Adds a symbol to the authenticated user's favorites. */
|
|
187
|
+
async addFavoriteSymbol(symbolKey: string): Promise<void> {
|
|
188
|
+
return this._post('/me/favorites', { symbolKey });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Removes a symbol from the authenticated user's favorites. */
|
|
192
|
+
async removeFavoriteSymbol(symbolKey: string): Promise<void> {
|
|
193
|
+
return this._delete(`/me/favorites/${encodeURIComponent(symbolKey)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|