@luxalgo/vela 0.5.0
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/LICENSE +201 -0
- package/NOTICE +28 -0
- package/README.md +135 -0
- package/dist/DataProvider-DKNDHpNv.d.cts +134 -0
- package/dist/DataProvider-Dzd-Erlk.d.ts +134 -0
- package/dist/chunk-7UFX5ZIG.js +5976 -0
- package/dist/chunk-GYD2THPV.js +252 -0
- package/dist/chunk-KCCZNKH7.js +19137 -0
- package/dist/chunk-KM6LHB3Y.js +76 -0
- package/dist/chunk-OVQKKXLZ.js +3978 -0
- package/dist/chunk-Q3XQHLIH.js +7 -0
- package/dist/chunk-RHIDOUFL.js +698 -0
- package/dist/contributions-hX3EUyjG.d.ts +1528 -0
- package/dist/contributions-o1GRKPI_.d.cts +1528 -0
- package/dist/history-Dzxz-MQj.d.ts +298 -0
- package/dist/history-LJkz4-sS.d.cts +298 -0
- package/dist/icons-BZYbJXSV.d.cts +10 -0
- package/dist/icons-BZYbJXSV.d.ts +10 -0
- package/dist/index.cjs +25294 -0
- package/dist/index.d.cts +1189 -0
- package/dist/index.d.ts +1189 -0
- package/dist/index.js +5 -0
- package/dist/keymap-CGOz5F5f.d.cts +64 -0
- package/dist/keymap-CGOz5F5f.d.ts +64 -0
- package/dist/options-Q-576hIi.d.cts +1545 -0
- package/dist/options-Q-576hIi.d.ts +1545 -0
- package/dist/plugin-D94muTV-.d.cts +405 -0
- package/dist/plugin-aGUD1epn.d.ts +405 -0
- package/dist/plugin.cjs +5967 -0
- package/dist/plugin.d.cts +7 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +3 -0
- package/dist/providers/binance.cjs +390 -0
- package/dist/providers/binance.d.cts +62 -0
- package/dist/providers/binance.d.ts +62 -0
- package/dist/providers/binance.js +388 -0
- package/dist/providers/coinbase.cjs +462 -0
- package/dist/providers/coinbase.d.cts +59 -0
- package/dist/providers/coinbase.d.ts +59 -0
- package/dist/providers/coinbase.js +460 -0
- package/dist/providers/hyperliquid.cjs +361 -0
- package/dist/providers/hyperliquid.d.cts +54 -0
- package/dist/providers/hyperliquid.d.ts +54 -0
- package/dist/providers/hyperliquid.js +359 -0
- package/dist/side-panel-CT9ZwIGz.d.cts +63 -0
- package/dist/side-panel-CT9ZwIGz.d.ts +63 -0
- package/dist/ui.cjs +1030 -0
- package/dist/ui.d.cts +188 -0
- package/dist/ui.d.ts +188 -0
- package/dist/ui.js +3 -0
- package/dist/vela.global.js +26509 -0
- package/dist/vela.global.min.js +230 -0
- package/dist/widget.cjs +31178 -0
- package/dist/widget.d.cts +799 -0
- package/dist/widget.d.ts +799 -0
- package/dist/widget.js +1060 -0
- package/dist/workspace.cjs +31692 -0
- package/dist/workspace.d.cts +579 -0
- package/dist/workspace.d.ts +579 -0
- package/dist/workspace.js +1694 -0
- package/package.json +95 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { A as ACCENT, a as ACCENT_BRIGHT, B as BEARISH, b as BULLISH, c as BarTransform, C as CATEGORICAL, d as CROSSHAIR, e as ChartTypeDefinition, f as ChartTypeSettingsSection, D as DrawingTypeMeta, H as HIGHLIGHT, I as INFO, g as INVALID, h as IdentifiableKind, M as MARKER, N as NEUTRAL, R as RendererLayerArgs, i as RendererLayerDefinition, j as RendererLayerInstance, S as SERIES_LINE, k as SESSION_OFF, l as SESSION_POST, m as SESSION_PRE, n as SLATE, o as SLATE_DEEP, p as SeriesDataEngine, q as SeriesDataEngineHost, r as SettingsRowDescriptor, T as TRADE_EXIT, s as TRADE_LONG, t as TRADE_SHORT, V as VALID, W as WARNING, u as categoricalColor, v as chartType, w as chartTypes, z as drawingTypes, E as getDrawingType, F as registerChartType, J as registerRendererDefaults, K as registerRendererLayer, L as rendererDefaults, O as rendererLayers, P as stableSeriesId, Q as tickerModifierIds, U as unregisterChartType, X as unregisterRendererDefaults, Y as unregisterRendererLayer } from './plugin-D94muTV-.cjs';
|
|
2
|
+
export { B as BarsChangeReason, C as ContextSelect, E as EngineAlert, d as EngineCapabilities, e as EngineContextSnapshot, f as EngineFactory, g as EngineWarning, h as ExecutionHandlers, i as ExecutionMarket, j as ExecutionRequest, k as ExecutionSession, F as FetchSeries, L as LegendActionDescriptor, n as LegendIndicatorInfo, N as NativeIndicator, o as NativeIndicatorContext, p as NativeIndicatorDescriptor, q as NativeIndicatorInfo, r as NativeIndicatorOutput, s as PreparedScript, a as ScriptRun, w as ScriptRunCause, x as ScriptRunResult, S as ScriptingEngine, y as SidePanelButton, z as SidePanelDescriptor, A as SidePanelHandle, G as SidePanelHeader, H as StrategyFill, J as StrategyState, K as StrategyTrade, Q as VisibleBarRange, U as WidgetActionDescriptor, X as WidgetActionTarget, Y as WidgetAttachment, W as WidgetContext, Z as getNativeIndicator, _ as legendActions, a0 as nativeIndicatorTypes, a1 as registerDefaultEngine, a2 as registerLegendAction, a3 as registerNativeIndicator, a4 as registerSidePanel, a5 as registerWidgetAction, a6 as registerWidgetAttachment, a7 as resolveEngines, a8 as sidePanels, a9 as unregisterDefaultEngine, aa as unregisterLegendAction, ab as unregisterNativeIndicator, ac as unregisterSidePanel, ad as unregisterWidgetAction, ae as unregisterWidgetAttachment, af as widgetActions, ag as widgetAttachments } from './contributions-o1GRKPI_.cjs';
|
|
3
|
+
export { D as DEFAULT_PANEL_MAX_WIDTH, a as DEFAULT_PANEL_MIN_WIDTH, b as DEFAULT_PANEL_WIDTH, S as SidePanelOptions, c as clampPanelWidth } from './side-panel-CT9ZwIGz.cjs';
|
|
4
|
+
export { B as Background, w as BoxFontFamily, x as BoxHAlign, y as BoxTextSize, z as BoxVAlign, E as CandleBarColor, F as CandleSeries, G as CandleStyle, Q as DirtyRange, W as DrawingBox, X as DrawingExtend, Z as DrawingLabel, _ as DrawingLine, $ as DrawingLinefill, a2 as DrawingPolyline, a4 as DrawingTable, u as DrawingTypeKey, a6 as DrawingXLoc, a8 as Fill, a9 as FillGradientStop, aa as IndicatorMeta, j as IndicatorModel, ab as InputSchema, ac as InputType, l as InputValue, ad as LabelStyle, ae as LabelYLoc, af as LineLikeKind, ag as LineLikeSeries, ah as LineLikeStyle, L as LineStyle, ai as MarkerPoint, aj as MarkerSeries, s as Millis, O as OHLCV, g as Pane, am as PaneHint, an as PaneKind, ao as PolylinePoint, ap as PriceLine, P as PriceStyle, at as Scene, k as ScenePatch, au as SchemaPatch, av as SeriesKind, aw as SeriesPoint, ax as SeriesSpec, ay as SeriesValueDelta, aB as TableCell, aC as TableMerge, aD as TablePosition, aF as TradeExecution, aG as ValuePatch } from './options-Q-576hIi.cjs';
|
|
5
|
+
export { i as iconMarkup, r as registerIcon } from './icons-BZYbJXSV.cjs';
|
|
6
|
+
export { a as KeyBindingDescriptor, R as ResolvedBinding } from './keymap-CGOz5F5f.cjs';
|
|
7
|
+
export { B as BarRange, D as DataProvider, M as MarketDataFeed, b as ProviderCapabilities, P as ProviderInfo, a as SymbolDescriptor, S as SymbolInfo } from './DataProvider-DKNDHpNv.cjs';
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { A as ACCENT, a as ACCENT_BRIGHT, B as BEARISH, b as BULLISH, c as BarTransform, C as CATEGORICAL, d as CROSSHAIR, e as ChartTypeDefinition, f as ChartTypeSettingsSection, D as DrawingTypeMeta, H as HIGHLIGHT, I as INFO, g as INVALID, h as IdentifiableKind, M as MARKER, N as NEUTRAL, R as RendererLayerArgs, i as RendererLayerDefinition, j as RendererLayerInstance, S as SERIES_LINE, k as SESSION_OFF, l as SESSION_POST, m as SESSION_PRE, n as SLATE, o as SLATE_DEEP, p as SeriesDataEngine, q as SeriesDataEngineHost, r as SettingsRowDescriptor, T as TRADE_EXIT, s as TRADE_LONG, t as TRADE_SHORT, V as VALID, W as WARNING, u as categoricalColor, v as chartType, w as chartTypes, z as drawingTypes, E as getDrawingType, F as registerChartType, J as registerRendererDefaults, K as registerRendererLayer, L as rendererDefaults, O as rendererLayers, P as stableSeriesId, Q as tickerModifierIds, U as unregisterChartType, X as unregisterRendererDefaults, Y as unregisterRendererLayer } from './plugin-aGUD1epn.js';
|
|
2
|
+
export { B as BarsChangeReason, C as ContextSelect, E as EngineAlert, d as EngineCapabilities, e as EngineContextSnapshot, f as EngineFactory, g as EngineWarning, h as ExecutionHandlers, i as ExecutionMarket, j as ExecutionRequest, k as ExecutionSession, F as FetchSeries, L as LegendActionDescriptor, n as LegendIndicatorInfo, N as NativeIndicator, o as NativeIndicatorContext, p as NativeIndicatorDescriptor, q as NativeIndicatorInfo, r as NativeIndicatorOutput, s as PreparedScript, a as ScriptRun, w as ScriptRunCause, x as ScriptRunResult, S as ScriptingEngine, y as SidePanelButton, z as SidePanelDescriptor, A as SidePanelHandle, G as SidePanelHeader, H as StrategyFill, J as StrategyState, K as StrategyTrade, Q as VisibleBarRange, U as WidgetActionDescriptor, X as WidgetActionTarget, Y as WidgetAttachment, W as WidgetContext, Z as getNativeIndicator, _ as legendActions, a0 as nativeIndicatorTypes, a1 as registerDefaultEngine, a2 as registerLegendAction, a3 as registerNativeIndicator, a4 as registerSidePanel, a5 as registerWidgetAction, a6 as registerWidgetAttachment, a7 as resolveEngines, a8 as sidePanels, a9 as unregisterDefaultEngine, aa as unregisterLegendAction, ab as unregisterNativeIndicator, ac as unregisterSidePanel, ad as unregisterWidgetAction, ae as unregisterWidgetAttachment, af as widgetActions, ag as widgetAttachments } from './contributions-hX3EUyjG.js';
|
|
3
|
+
export { D as DEFAULT_PANEL_MAX_WIDTH, a as DEFAULT_PANEL_MIN_WIDTH, b as DEFAULT_PANEL_WIDTH, S as SidePanelOptions, c as clampPanelWidth } from './side-panel-CT9ZwIGz.js';
|
|
4
|
+
export { B as Background, w as BoxFontFamily, x as BoxHAlign, y as BoxTextSize, z as BoxVAlign, E as CandleBarColor, F as CandleSeries, G as CandleStyle, Q as DirtyRange, W as DrawingBox, X as DrawingExtend, Z as DrawingLabel, _ as DrawingLine, $ as DrawingLinefill, a2 as DrawingPolyline, a4 as DrawingTable, u as DrawingTypeKey, a6 as DrawingXLoc, a8 as Fill, a9 as FillGradientStop, aa as IndicatorMeta, j as IndicatorModel, ab as InputSchema, ac as InputType, l as InputValue, ad as LabelStyle, ae as LabelYLoc, af as LineLikeKind, ag as LineLikeSeries, ah as LineLikeStyle, L as LineStyle, ai as MarkerPoint, aj as MarkerSeries, s as Millis, O as OHLCV, g as Pane, am as PaneHint, an as PaneKind, ao as PolylinePoint, ap as PriceLine, P as PriceStyle, at as Scene, k as ScenePatch, au as SchemaPatch, av as SeriesKind, aw as SeriesPoint, ax as SeriesSpec, ay as SeriesValueDelta, aB as TableCell, aC as TableMerge, aD as TablePosition, aF as TradeExecution, aG as ValuePatch } from './options-Q-576hIi.js';
|
|
5
|
+
export { i as iconMarkup, r as registerIcon } from './icons-BZYbJXSV.js';
|
|
6
|
+
export { a as KeyBindingDescriptor, R as ResolvedBinding } from './keymap-CGOz5F5f.js';
|
|
7
|
+
export { B as BarRange, D as DataProvider, M as MarketDataFeed, b as ProviderCapabilities, P as ProviderInfo, a as SymbolDescriptor, S as SymbolInfo } from './DataProvider-Dzd-Erlk.js';
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { stableSeriesId } from './chunk-Q3XQHLIH.js';
|
|
2
|
+
export { DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_WIDTH, chartType, chartTypes, clampPanelWidth, drawingTypes, getDrawingType, getNativeIndicator, legendActions, nativeIndicatorTypes, registerChartType, registerDefaultEngine, registerLegendAction, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resolveEngines, sidePanels, tickerModifierIds, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments } from './chunk-7UFX5ZIG.js';
|
|
3
|
+
export { ACCENT, ACCENT_BRIGHT, BEARISH, BULLISH, CATEGORICAL, CROSSHAIR, HIGHLIGHT, INFO, INVALID, MARKER, NEUTRAL, SERIES_LINE, SESSION_OFF, SESSION_POST, SESSION_PRE, SLATE, SLATE_DEEP, TRADE_EXIT, TRADE_LONG, TRADE_SHORT, VALID, WARNING, categoricalColor, iconMarkup, registerIcon } from './chunk-GYD2THPV.js';
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/data/providers/binance/BinanceProvider.ts
|
|
4
|
+
var SPOT_BASE = "https://api.binance.com/api/v3";
|
|
5
|
+
var SPOT_BASE_US = "https://api.binance.us/api/v3";
|
|
6
|
+
var FUTURES_BASE = "https://fapi.binance.com/fapi/v1";
|
|
7
|
+
var SPOT_WS = "wss://stream.binance.com:9443";
|
|
8
|
+
var SPOT_WS_US = "wss://stream.binance.us:9443";
|
|
9
|
+
var FUTURES_WS = "wss://fstream.binance.com";
|
|
10
|
+
var STREAM_STALL_MS = 15e3;
|
|
11
|
+
var STREAM_RECONNECT_MS = 2e3;
|
|
12
|
+
var TF_TO_INTERVAL = {
|
|
13
|
+
"1": "1m",
|
|
14
|
+
"3": "3m",
|
|
15
|
+
"5": "5m",
|
|
16
|
+
"15": "15m",
|
|
17
|
+
"30": "30m",
|
|
18
|
+
"60": "1h",
|
|
19
|
+
"120": "2h",
|
|
20
|
+
"240": "4h",
|
|
21
|
+
"360": "6h",
|
|
22
|
+
"480": "8h",
|
|
23
|
+
"720": "12h",
|
|
24
|
+
D: "1d",
|
|
25
|
+
W: "1w",
|
|
26
|
+
M: "1M"
|
|
27
|
+
};
|
|
28
|
+
var TF_NORMALIZE = {
|
|
29
|
+
"1m": "1",
|
|
30
|
+
"3m": "3",
|
|
31
|
+
"5m": "5",
|
|
32
|
+
"15m": "15",
|
|
33
|
+
"30m": "30",
|
|
34
|
+
"45m": "45",
|
|
35
|
+
"1h": "60",
|
|
36
|
+
"2h": "120",
|
|
37
|
+
"3h": "180",
|
|
38
|
+
"4h": "240",
|
|
39
|
+
"6h": "360",
|
|
40
|
+
"8h": "480",
|
|
41
|
+
"12h": "720",
|
|
42
|
+
"1d": "D",
|
|
43
|
+
"1w": "W",
|
|
44
|
+
"1mo": "M",
|
|
45
|
+
"1D": "D",
|
|
46
|
+
"1W": "W",
|
|
47
|
+
"4H": "240",
|
|
48
|
+
D: "D",
|
|
49
|
+
W: "W",
|
|
50
|
+
M: "M"
|
|
51
|
+
};
|
|
52
|
+
var NATIVE_MINUTES = [1, 3, 5, 15, 30, 60, 120, 240, 360, 480, 720];
|
|
53
|
+
var MIN_TO_INTERVAL = {
|
|
54
|
+
1: "1m",
|
|
55
|
+
3: "3m",
|
|
56
|
+
5: "5m",
|
|
57
|
+
15: "15m",
|
|
58
|
+
30: "30m",
|
|
59
|
+
60: "1h",
|
|
60
|
+
120: "2h",
|
|
61
|
+
240: "4h",
|
|
62
|
+
360: "6h",
|
|
63
|
+
480: "8h",
|
|
64
|
+
720: "12h"
|
|
65
|
+
};
|
|
66
|
+
var SUPPORTED_TIMEFRAMES = ["1", "3", "5", "15", "30", "45", "60", "120", "180", "240", "D", "W", "M"];
|
|
67
|
+
function normalizeTf(tf) {
|
|
68
|
+
return TF_NORMALIZE[tf] ?? TF_NORMALIZE[tf.toLowerCase()] ?? tf;
|
|
69
|
+
}
|
|
70
|
+
function parseTicker(ticker) {
|
|
71
|
+
const t = ticker.trim().toUpperCase();
|
|
72
|
+
if (t.endsWith(".P")) return { apiSymbol: t.slice(0, -2), isFutures: true };
|
|
73
|
+
return { apiSymbol: t, isFutures: false };
|
|
74
|
+
}
|
|
75
|
+
function klinesToOHLCV(raw) {
|
|
76
|
+
return raw.map((k) => ({
|
|
77
|
+
time: Number(k[0]),
|
|
78
|
+
open: Number(k[1]),
|
|
79
|
+
high: Number(k[2]),
|
|
80
|
+
low: Number(k[3]),
|
|
81
|
+
close: Number(k[4]),
|
|
82
|
+
volume: Number(k[5])
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
function klineEventToOHLCV(k) {
|
|
86
|
+
return { time: Number(k.t), open: Number(k.o), high: Number(k.h), low: Number(k.l), close: Number(k.c), volume: Number(k.v) };
|
|
87
|
+
}
|
|
88
|
+
function dedupeSorted(bars) {
|
|
89
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const b of bars) byTime.set(b.time, b);
|
|
91
|
+
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
92
|
+
}
|
|
93
|
+
function aggregate(sub, bucketMs) {
|
|
94
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const b of sub) {
|
|
96
|
+
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
97
|
+
const cur = buckets.get(key);
|
|
98
|
+
if (!cur) {
|
|
99
|
+
buckets.set(key, { time: key, open: b.open, high: b.high, low: b.low, close: b.close, volume: b.volume ?? 0 });
|
|
100
|
+
} else {
|
|
101
|
+
cur.high = Math.max(cur.high, b.high);
|
|
102
|
+
cur.low = Math.min(cur.low, b.low);
|
|
103
|
+
cur.close = b.close;
|
|
104
|
+
cur.volume = (cur.volume ?? 0) + (b.volume ?? 0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return [...buckets.values()].sort((a, b) => a.time - b.time);
|
|
108
|
+
}
|
|
109
|
+
function selectSubTf(targetMin) {
|
|
110
|
+
return NATIVE_MINUTES.filter((m) => m < targetMin && targetMin % m === 0).sort((a, b) => b - a)[0] ?? null;
|
|
111
|
+
}
|
|
112
|
+
var BinanceProvider = class {
|
|
113
|
+
constructor() {
|
|
114
|
+
/** The spot endpoint confirmed reachable (cached after the first probe). */
|
|
115
|
+
this.spotBaseUrl = null;
|
|
116
|
+
/** The in-flight endpoint probe, shared so concurrent first calls don't each ping. */
|
|
117
|
+
this.spotBaseProbe = null;
|
|
118
|
+
/** Cached symbol enumeration (exchangeInfo is large; fetch once). */
|
|
119
|
+
this.symbolsPromise = null;
|
|
120
|
+
}
|
|
121
|
+
info() {
|
|
122
|
+
return {
|
|
123
|
+
name: "binance",
|
|
124
|
+
displayName: "Binance",
|
|
125
|
+
requiresApiKey: false,
|
|
126
|
+
supportedTimeframes: SUPPORTED_TIMEFRAMES,
|
|
127
|
+
capabilities: { enumerate: true, stream: true, symbolInfo: true }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async getBars(ticker, timeframe, range) {
|
|
131
|
+
try {
|
|
132
|
+
const { apiSymbol, isFutures } = parseTicker(ticker);
|
|
133
|
+
const base = isFutures ? FUTURES_BASE : await this.spotBase();
|
|
134
|
+
const tf = normalizeTf(timeframe);
|
|
135
|
+
const nativeInterval = TF_TO_INTERVAL[tf];
|
|
136
|
+
if (nativeInterval) {
|
|
137
|
+
return dedupeSorted(klinesToOHLCV(await this.fetchKlines(base, apiSymbol, nativeInterval, range)));
|
|
138
|
+
}
|
|
139
|
+
const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
|
|
140
|
+
const subMin = targetMin != null ? selectSubTf(targetMin) : null;
|
|
141
|
+
if (targetMin == null || subMin == null) {
|
|
142
|
+
console.warn(`[vela] Binance: timeframe "${timeframe}" is not supported and cannot be aggregated.`);
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const ratio = targetMin / subMin;
|
|
146
|
+
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
147
|
+
const sub = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
|
|
148
|
+
const agg = aggregate(sub, targetMin * 6e4);
|
|
149
|
+
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async getSymbolInfo(ticker) {
|
|
156
|
+
const { apiSymbol, isFutures } = parseTicker(ticker);
|
|
157
|
+
const base = isFutures ? FUTURES_BASE : await this.spotBase();
|
|
158
|
+
const url = isFutures ? `${base}/exchangeInfo` : `${base}/exchangeInfo?symbol=${apiSymbol}`;
|
|
159
|
+
const data = await this.json(url).catch(() => null);
|
|
160
|
+
const symbols = data?.symbols ?? [];
|
|
161
|
+
const s = isFutures ? symbols.find((x) => x.symbol === apiSymbol) : symbols[0];
|
|
162
|
+
if (!s) return void 0;
|
|
163
|
+
const priceFilter = s.filters?.find((f) => f.filterType === "PRICE_FILTER");
|
|
164
|
+
const tickSize = priceFilter ? parseFloat(priceFilter.tickSize ?? "0.01") : 0.01;
|
|
165
|
+
return {
|
|
166
|
+
ticker,
|
|
167
|
+
// keep the original, incl. any .P (as Pine Script expects)
|
|
168
|
+
tickerid: `BINANCE:${ticker}`,
|
|
169
|
+
prefix: "BINANCE",
|
|
170
|
+
description: `${s.baseAsset} / ${s.quoteAsset}${isFutures ? " Perpetual" : ""}`,
|
|
171
|
+
type: isFutures ? "futures" : "crypto",
|
|
172
|
+
basecurrency: s.baseAsset,
|
|
173
|
+
currency: s.quoteAsset,
|
|
174
|
+
mintick: tickSize,
|
|
175
|
+
pricescale: Math.round(1 / tickSize),
|
|
176
|
+
timezone: "Etc/UTC",
|
|
177
|
+
session: "24x7"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
listSymbols() {
|
|
181
|
+
if (!this.symbolsPromise) {
|
|
182
|
+
this.symbolsPromise = Promise.all([
|
|
183
|
+
this.listSpot().catch(() => []),
|
|
184
|
+
this.listFutures().catch(() => [])
|
|
185
|
+
]).then(([spot, futures]) => [...spot, ...futures]);
|
|
186
|
+
}
|
|
187
|
+
return this.symbolsPromise;
|
|
188
|
+
}
|
|
189
|
+
subscribe(ticker, timeframe, onBar) {
|
|
190
|
+
const { apiSymbol, isFutures } = parseTicker(ticker);
|
|
191
|
+
const interval = TF_TO_INTERVAL[normalizeTf(timeframe)];
|
|
192
|
+
if (interval && typeof WebSocket !== "undefined") return this.streamKlines(ticker, timeframe, apiSymbol, isFutures, interval, onBar);
|
|
193
|
+
return this.pollBars(ticker, timeframe, onBar);
|
|
194
|
+
}
|
|
195
|
+
// ── internals ────────────────────────────────────────────────────────
|
|
196
|
+
/**
|
|
197
|
+
* Open a Binance kline WebSocket (single-stream URL form — the stream is in the
|
|
198
|
+
* path, so no subscribe frame is sent). Perpetuals use `fstream`; spot uses the
|
|
199
|
+
* host matching the resolved REST endpoint (`.com`/`.us`). Reconnects on an
|
|
200
|
+
* unexpected close until unsubscribed; the browser auto-answers Binance's
|
|
201
|
+
* protocol-level pings, so no manual keepalive is needed.
|
|
202
|
+
*
|
|
203
|
+
* A stall watchdog falls the subscription back to polling if the socket opens but
|
|
204
|
+
* never delivers a candle — so a blocked/silent stream (e.g. geo-restricted
|
|
205
|
+
* futures) still yields live updates instead of a silently-dead feed.
|
|
206
|
+
*/
|
|
207
|
+
streamKlines(ticker, timeframe, apiSymbol, isFutures, interval, onBar) {
|
|
208
|
+
let closed = false;
|
|
209
|
+
let ws = null;
|
|
210
|
+
let reconnect = null;
|
|
211
|
+
let stall = null;
|
|
212
|
+
let polling = null;
|
|
213
|
+
const stream = `${apiSymbol.toLowerCase()}@kline_${interval}`;
|
|
214
|
+
const clearStall = () => {
|
|
215
|
+
if (stall) {
|
|
216
|
+
clearTimeout(stall);
|
|
217
|
+
stall = null;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const fallToPolling = () => {
|
|
221
|
+
if (closed || polling) return;
|
|
222
|
+
clearStall();
|
|
223
|
+
if (reconnect) {
|
|
224
|
+
clearTimeout(reconnect);
|
|
225
|
+
reconnect = null;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
ws?.close();
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
ws = null;
|
|
232
|
+
console.warn(`[vela] Binance: ${ticker} ${timeframe} stream delivered no data; falling back to polling.`);
|
|
233
|
+
polling = this.pollBars(ticker, timeframe, onBar);
|
|
234
|
+
};
|
|
235
|
+
const open = async () => {
|
|
236
|
+
if (closed) return;
|
|
237
|
+
const base = isFutures ? FUTURES_WS : await this.spotWsBase();
|
|
238
|
+
if (closed) return;
|
|
239
|
+
ws = new WebSocket(`${base}/ws/${stream}`);
|
|
240
|
+
stall = setTimeout(fallToPolling, STREAM_STALL_MS);
|
|
241
|
+
ws.onmessage = (ev) => {
|
|
242
|
+
if (closed || polling) return;
|
|
243
|
+
try {
|
|
244
|
+
const msg = JSON.parse(typeof ev.data === "string" ? ev.data : "");
|
|
245
|
+
if (msg.k) {
|
|
246
|
+
clearStall();
|
|
247
|
+
onBar(klineEventToOHLCV(msg.k));
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
ws.onclose = () => {
|
|
253
|
+
if (!closed && !polling) reconnect = setTimeout(() => void open(), STREAM_RECONNECT_MS);
|
|
254
|
+
};
|
|
255
|
+
ws.onerror = () => {
|
|
256
|
+
try {
|
|
257
|
+
ws?.close();
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
void open();
|
|
263
|
+
return () => {
|
|
264
|
+
closed = true;
|
|
265
|
+
clearStall();
|
|
266
|
+
if (reconnect) clearTimeout(reconnect);
|
|
267
|
+
polling?.();
|
|
268
|
+
try {
|
|
269
|
+
ws?.close();
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/** WebSocket host matching the resolved REST spot endpoint (`.com` default, else `.us`). */
|
|
275
|
+
async spotWsBase() {
|
|
276
|
+
const base = await this.spotBase();
|
|
277
|
+
return base.includes("binance.us") ? SPOT_WS_US : SPOT_WS;
|
|
278
|
+
}
|
|
279
|
+
/** Poll the forming candle (for aggregated timeframes Binance has no native kline stream for). */
|
|
280
|
+
pollBars(ticker, timeframe, onBar) {
|
|
281
|
+
let stopped = false;
|
|
282
|
+
let timer = null;
|
|
283
|
+
const poll = async () => {
|
|
284
|
+
if (stopped) return;
|
|
285
|
+
try {
|
|
286
|
+
const bars = await this.getBars(ticker, timeframe, { limit: 2 });
|
|
287
|
+
for (const b of bars) onBar(b);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
if (!stopped) timer = setTimeout(() => void poll(), 3e3);
|
|
291
|
+
};
|
|
292
|
+
timer = setTimeout(() => void poll(), 3e3);
|
|
293
|
+
return () => {
|
|
294
|
+
stopped = true;
|
|
295
|
+
if (timer) clearTimeout(timer);
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async listSpot() {
|
|
299
|
+
const base = await this.spotBase();
|
|
300
|
+
const data = await this.json(`${base}/exchangeInfo`);
|
|
301
|
+
return (data.symbols ?? []).filter((s) => s.status === "TRADING").map((s) => ({ ticker: s.symbol, description: `${s.baseAsset} / ${s.quoteAsset}`, type: "crypto" }));
|
|
302
|
+
}
|
|
303
|
+
async listFutures() {
|
|
304
|
+
const data = await this.json(`${FUTURES_BASE}/exchangeInfo`);
|
|
305
|
+
return (data.symbols ?? []).filter((s) => s.contractType === "PERPETUAL" && s.status === "TRADING").map((s) => ({ ticker: `${s.symbol}.P`, description: `${s.baseAsset} / ${s.quoteAsset} Perpetual`, type: "futures" }));
|
|
306
|
+
}
|
|
307
|
+
/** Fetch klines, paginating past Binance's 1000-row cap. */
|
|
308
|
+
async fetchKlines(base, symbol, interval, range) {
|
|
309
|
+
if (range.from != null) return this.paginateForward(base, symbol, interval, range.from, range.to ?? Date.now(), range.limit);
|
|
310
|
+
const limit = range.limit ?? 500;
|
|
311
|
+
if (limit > 1e3) return this.paginateBackward(base, symbol, interval, limit, range.to);
|
|
312
|
+
return this.klinesChunk(base, { symbol, interval, limit, endTime: range.to });
|
|
313
|
+
}
|
|
314
|
+
/** Forward pagination from `from` to `to` (used by ranged/tail fetches). */
|
|
315
|
+
async paginateForward(base, symbol, interval, from, to, limit) {
|
|
316
|
+
const out = [];
|
|
317
|
+
let cursor = from;
|
|
318
|
+
while (cursor < to) {
|
|
319
|
+
const chunk = await this.klinesChunk(base, { symbol, interval, limit: 1e3, startTime: cursor, endTime: to });
|
|
320
|
+
if (chunk.length === 0) break;
|
|
321
|
+
out.push(...chunk);
|
|
322
|
+
cursor = Number(chunk[chunk.length - 1][6]) + 1;
|
|
323
|
+
if (chunk.length < 1e3) break;
|
|
324
|
+
if (limit && out.length >= limit) break;
|
|
325
|
+
}
|
|
326
|
+
return limit ? out.slice(0, limit) : out;
|
|
327
|
+
}
|
|
328
|
+
/** Backward pagination to assemble the most-recent `limit` bars. */
|
|
329
|
+
async paginateBackward(base, symbol, interval, limit, endTime) {
|
|
330
|
+
let out = [];
|
|
331
|
+
let remaining = limit;
|
|
332
|
+
let cursor = endTime;
|
|
333
|
+
let guard = Math.ceil(limit / 1e3) + 5;
|
|
334
|
+
while (remaining > 0 && guard-- > 0) {
|
|
335
|
+
const size = Math.min(remaining, 1e3);
|
|
336
|
+
const chunk = await this.klinesChunk(base, { symbol, interval, limit: size, endTime: cursor });
|
|
337
|
+
if (chunk.length === 0) break;
|
|
338
|
+
out = chunk.concat(out);
|
|
339
|
+
remaining -= chunk.length;
|
|
340
|
+
cursor = Number(chunk[0][0]) - 1;
|
|
341
|
+
if (chunk.length < size) break;
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
async klinesChunk(base, params) {
|
|
346
|
+
const url = new URL(`${base}/klines`);
|
|
347
|
+
url.searchParams.set("symbol", params.symbol);
|
|
348
|
+
url.searchParams.set("interval", params.interval);
|
|
349
|
+
if (params.limit) url.searchParams.set("limit", String(Math.min(params.limit, 1e3)));
|
|
350
|
+
if (params.startTime != null) url.searchParams.set("startTime", String(params.startTime));
|
|
351
|
+
if (params.endTime != null) url.searchParams.set("endTime", String(params.endTime));
|
|
352
|
+
const data = await this.json(url.toString());
|
|
353
|
+
if (!Array.isArray(data)) return [];
|
|
354
|
+
if (data.length > 0 && !Array.isArray(data[0])) return [];
|
|
355
|
+
return data;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Resolve the reachable spot endpoint (default, else the US mirror). The in-flight
|
|
359
|
+
* probe is shared, so a burst of concurrent first calls issues ONE ping pair, not one
|
|
360
|
+
* per call. A confirmed endpoint is cached permanently; a total failure clears the
|
|
361
|
+
* probe so a later call can retry.
|
|
362
|
+
*/
|
|
363
|
+
spotBase() {
|
|
364
|
+
if (this.spotBaseUrl) return Promise.resolve(this.spotBaseUrl);
|
|
365
|
+
if (!this.spotBaseProbe) {
|
|
366
|
+
this.spotBaseProbe = (async () => {
|
|
367
|
+
try {
|
|
368
|
+
for (const url of [SPOT_BASE, SPOT_BASE_US]) {
|
|
369
|
+
try {
|
|
370
|
+
const res = await fetch(`${url}/ping`, { signal: AbortSignal.timeout(5e3) });
|
|
371
|
+
if (res.ok) return this.spotBaseUrl = url;
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return SPOT_BASE;
|
|
376
|
+
} finally {
|
|
377
|
+
this.spotBaseProbe = null;
|
|
378
|
+
}
|
|
379
|
+
})();
|
|
380
|
+
}
|
|
381
|
+
return this.spotBaseProbe;
|
|
382
|
+
}
|
|
383
|
+
async json(url) {
|
|
384
|
+
const res = await fetch(url);
|
|
385
|
+
if (!res.ok) throw new Error(`Binance HTTP ${res.status} for ${url}`);
|
|
386
|
+
return res.json();
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
exports.BinanceProvider = BinanceProvider;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { O as OHLCV, U as Unsubscribe } from '../options-Q-576hIi.cjs';
|
|
2
|
+
import { D as DataProvider, P as ProviderInfo, B as BarRange, S as SymbolInfo, a as SymbolDescriptor } from '../DataProvider-DKNDHpNv.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Binance market-data provider, built from scratch on the public REST + WebSocket
|
|
6
|
+
* APIs — no third-party market-data SDK. Serves spot and USDT-margined perpetual futures (`SYMBOL.P`),
|
|
7
|
+
* paginates past the 1000-kline cap, falls back `api.binance.com → api.binance.us`,
|
|
8
|
+
* and aggregates timeframes Binance doesn't serve natively (e.g. `45`, `180`). No API
|
|
9
|
+
* key; live ticks stream from a native kline WebSocket (`subscribe`), with a poll
|
|
10
|
+
* fallback for aggregated timeframes.
|
|
11
|
+
*
|
|
12
|
+
* import { BinanceProvider } from 'vela/providers/binance';
|
|
13
|
+
* chart.data.registerProvider('binance', new BinanceProvider());
|
|
14
|
+
*/
|
|
15
|
+
declare class BinanceProvider implements DataProvider {
|
|
16
|
+
/** The spot endpoint confirmed reachable (cached after the first probe). */
|
|
17
|
+
private spotBaseUrl;
|
|
18
|
+
/** The in-flight endpoint probe, shared so concurrent first calls don't each ping. */
|
|
19
|
+
private spotBaseProbe;
|
|
20
|
+
/** Cached symbol enumeration (exchangeInfo is large; fetch once). */
|
|
21
|
+
private symbolsPromise;
|
|
22
|
+
info(): ProviderInfo;
|
|
23
|
+
getBars(ticker: string, timeframe: string, range: BarRange): Promise<OHLCV[]>;
|
|
24
|
+
getSymbolInfo(ticker: string): Promise<SymbolInfo | undefined>;
|
|
25
|
+
listSymbols(): Promise<SymbolDescriptor[]>;
|
|
26
|
+
subscribe(ticker: string, timeframe: string, onBar: (bar: OHLCV) => void): Unsubscribe;
|
|
27
|
+
/**
|
|
28
|
+
* Open a Binance kline WebSocket (single-stream URL form — the stream is in the
|
|
29
|
+
* path, so no subscribe frame is sent). Perpetuals use `fstream`; spot uses the
|
|
30
|
+
* host matching the resolved REST endpoint (`.com`/`.us`). Reconnects on an
|
|
31
|
+
* unexpected close until unsubscribed; the browser auto-answers Binance's
|
|
32
|
+
* protocol-level pings, so no manual keepalive is needed.
|
|
33
|
+
*
|
|
34
|
+
* A stall watchdog falls the subscription back to polling if the socket opens but
|
|
35
|
+
* never delivers a candle — so a blocked/silent stream (e.g. geo-restricted
|
|
36
|
+
* futures) still yields live updates instead of a silently-dead feed.
|
|
37
|
+
*/
|
|
38
|
+
private streamKlines;
|
|
39
|
+
/** WebSocket host matching the resolved REST spot endpoint (`.com` default, else `.us`). */
|
|
40
|
+
private spotWsBase;
|
|
41
|
+
/** Poll the forming candle (for aggregated timeframes Binance has no native kline stream for). */
|
|
42
|
+
private pollBars;
|
|
43
|
+
private listSpot;
|
|
44
|
+
private listFutures;
|
|
45
|
+
/** Fetch klines, paginating past Binance's 1000-row cap. */
|
|
46
|
+
private fetchKlines;
|
|
47
|
+
/** Forward pagination from `from` to `to` (used by ranged/tail fetches). */
|
|
48
|
+
private paginateForward;
|
|
49
|
+
/** Backward pagination to assemble the most-recent `limit` bars. */
|
|
50
|
+
private paginateBackward;
|
|
51
|
+
private klinesChunk;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the reachable spot endpoint (default, else the US mirror). The in-flight
|
|
54
|
+
* probe is shared, so a burst of concurrent first calls issues ONE ping pair, not one
|
|
55
|
+
* per call. A confirmed endpoint is cached permanently; a total failure clears the
|
|
56
|
+
* probe so a later call can retry.
|
|
57
|
+
*/
|
|
58
|
+
private spotBase;
|
|
59
|
+
private json;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { BinanceProvider, DataProvider, ProviderInfo, SymbolDescriptor };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { O as OHLCV, U as Unsubscribe } from '../options-Q-576hIi.js';
|
|
2
|
+
import { D as DataProvider, P as ProviderInfo, B as BarRange, S as SymbolInfo, a as SymbolDescriptor } from '../DataProvider-Dzd-Erlk.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Binance market-data provider, built from scratch on the public REST + WebSocket
|
|
6
|
+
* APIs — no third-party market-data SDK. Serves spot and USDT-margined perpetual futures (`SYMBOL.P`),
|
|
7
|
+
* paginates past the 1000-kline cap, falls back `api.binance.com → api.binance.us`,
|
|
8
|
+
* and aggregates timeframes Binance doesn't serve natively (e.g. `45`, `180`). No API
|
|
9
|
+
* key; live ticks stream from a native kline WebSocket (`subscribe`), with a poll
|
|
10
|
+
* fallback for aggregated timeframes.
|
|
11
|
+
*
|
|
12
|
+
* import { BinanceProvider } from 'vela/providers/binance';
|
|
13
|
+
* chart.data.registerProvider('binance', new BinanceProvider());
|
|
14
|
+
*/
|
|
15
|
+
declare class BinanceProvider implements DataProvider {
|
|
16
|
+
/** The spot endpoint confirmed reachable (cached after the first probe). */
|
|
17
|
+
private spotBaseUrl;
|
|
18
|
+
/** The in-flight endpoint probe, shared so concurrent first calls don't each ping. */
|
|
19
|
+
private spotBaseProbe;
|
|
20
|
+
/** Cached symbol enumeration (exchangeInfo is large; fetch once). */
|
|
21
|
+
private symbolsPromise;
|
|
22
|
+
info(): ProviderInfo;
|
|
23
|
+
getBars(ticker: string, timeframe: string, range: BarRange): Promise<OHLCV[]>;
|
|
24
|
+
getSymbolInfo(ticker: string): Promise<SymbolInfo | undefined>;
|
|
25
|
+
listSymbols(): Promise<SymbolDescriptor[]>;
|
|
26
|
+
subscribe(ticker: string, timeframe: string, onBar: (bar: OHLCV) => void): Unsubscribe;
|
|
27
|
+
/**
|
|
28
|
+
* Open a Binance kline WebSocket (single-stream URL form — the stream is in the
|
|
29
|
+
* path, so no subscribe frame is sent). Perpetuals use `fstream`; spot uses the
|
|
30
|
+
* host matching the resolved REST endpoint (`.com`/`.us`). Reconnects on an
|
|
31
|
+
* unexpected close until unsubscribed; the browser auto-answers Binance's
|
|
32
|
+
* protocol-level pings, so no manual keepalive is needed.
|
|
33
|
+
*
|
|
34
|
+
* A stall watchdog falls the subscription back to polling if the socket opens but
|
|
35
|
+
* never delivers a candle — so a blocked/silent stream (e.g. geo-restricted
|
|
36
|
+
* futures) still yields live updates instead of a silently-dead feed.
|
|
37
|
+
*/
|
|
38
|
+
private streamKlines;
|
|
39
|
+
/** WebSocket host matching the resolved REST spot endpoint (`.com` default, else `.us`). */
|
|
40
|
+
private spotWsBase;
|
|
41
|
+
/** Poll the forming candle (for aggregated timeframes Binance has no native kline stream for). */
|
|
42
|
+
private pollBars;
|
|
43
|
+
private listSpot;
|
|
44
|
+
private listFutures;
|
|
45
|
+
/** Fetch klines, paginating past Binance's 1000-row cap. */
|
|
46
|
+
private fetchKlines;
|
|
47
|
+
/** Forward pagination from `from` to `to` (used by ranged/tail fetches). */
|
|
48
|
+
private paginateForward;
|
|
49
|
+
/** Backward pagination to assemble the most-recent `limit` bars. */
|
|
50
|
+
private paginateBackward;
|
|
51
|
+
private klinesChunk;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the reachable spot endpoint (default, else the US mirror). The in-flight
|
|
54
|
+
* probe is shared, so a burst of concurrent first calls issues ONE ping pair, not one
|
|
55
|
+
* per call. A confirmed endpoint is cached permanently; a total failure clears the
|
|
56
|
+
* probe so a later call can retry.
|
|
57
|
+
*/
|
|
58
|
+
private spotBase;
|
|
59
|
+
private json;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { BinanceProvider, DataProvider, ProviderInfo, SymbolDescriptor };
|