binbot-charts 0.12.11 → 0.12.13
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 +5 -5
- package/dist/main.cjs +111 -65
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.cts +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.js +112 -66
- package/dist/main.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -3,26 +3,26 @@ Import it in your project as a React component
|
|
|
3
3
|
|
|
4
4
|
`import TVChartContainer from 'binbot-charts'`
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
## Multi-Exchange Support
|
|
8
7
|
|
|
9
8
|
This library now supports multiple cryptocurrency exchanges. You can configure which exchange to use and which exchanges to make available for symbol search.
|
|
10
9
|
|
|
11
10
|
### Supported Exchanges
|
|
11
|
+
|
|
12
12
|
- **Binance** - Default exchange
|
|
13
13
|
- **KuCoin** - Added in v0.7.3+
|
|
14
14
|
|
|
15
15
|
### Usage Example
|
|
16
16
|
|
|
17
17
|
```jsx
|
|
18
|
-
import TVChartContainer, { SUPPORTED_EXCHANGES } from
|
|
18
|
+
import TVChartContainer, { SUPPORTED_EXCHANGES } from "binbot-charts";
|
|
19
19
|
|
|
20
20
|
function MyChart() {
|
|
21
21
|
return (
|
|
22
22
|
<TVChartContainer
|
|
23
23
|
symbol="BTCUSDT"
|
|
24
24
|
interval="1h"
|
|
25
|
-
exchange="binance"
|
|
25
|
+
exchange="binance" // or "kucoin"
|
|
26
26
|
supportedExchanges={["binance", "kucoin"]}
|
|
27
27
|
/>
|
|
28
28
|
);
|
|
@@ -45,14 +45,13 @@ function MyChart() {
|
|
|
45
45
|
You can also import and use the exchange configurations directly:
|
|
46
46
|
|
|
47
47
|
```jsx
|
|
48
|
-
import { SUPPORTED_EXCHANGES, ExchangeConfig } from
|
|
48
|
+
import { SUPPORTED_EXCHANGES, ExchangeConfig } from "binbot-charts";
|
|
49
49
|
|
|
50
50
|
// Access exchange configuration
|
|
51
51
|
const binanceConfig = SUPPORTED_EXCHANGES.binance;
|
|
52
52
|
console.log(binanceConfig.restApiUrl); // "https://api.binance.com"
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
56
55
|
## How to start
|
|
57
56
|
|
|
58
57
|
1. Run `npm install && npm start`. It will build the project and open a default browser with the Charting Library.
|
|
@@ -64,4 +63,5 @@ console.log(binanceConfig.restApiUrl); // "https://api.binance.com"
|
|
|
64
63
|
This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
|
|
65
64
|
|
|
66
65
|
## Notes
|
|
66
|
+
|
|
67
67
|
The earliest supported version of Node of the charting library for these examples is `v20`.
|
package/dist/main.cjs
CHANGED
|
@@ -360,6 +360,7 @@ var import_react = require("react");
|
|
|
360
360
|
function resolveApiBase(apiHost) {
|
|
361
361
|
if (typeof window !== "undefined") {
|
|
362
362
|
if (apiHost.includes("api.binance.com")) return "/binance";
|
|
363
|
+
if (apiHost.includes("api-futures.kucoin.com")) return "/kucoin-futures";
|
|
363
364
|
if (apiHost.includes("api.kucoin.com")) return "/kucoin";
|
|
364
365
|
}
|
|
365
366
|
return apiHost.replace(/\/$/, "");
|
|
@@ -383,7 +384,10 @@ function getAllSymbols(symbol, apiHost = "https://api.binance.com", exchange = "
|
|
|
383
384
|
return __async(this, null, function* () {
|
|
384
385
|
let newSymbols = [];
|
|
385
386
|
try {
|
|
386
|
-
const data = yield makeApiRequest(
|
|
387
|
+
const data = yield makeApiRequest(
|
|
388
|
+
`api/v3/exchangeInfo?symbol=${symbol.toUpperCase()}`,
|
|
389
|
+
apiHost
|
|
390
|
+
);
|
|
387
391
|
data.symbols.forEach((item) => {
|
|
388
392
|
if (item.status === "TRADING") {
|
|
389
393
|
newSymbols.push({
|
|
@@ -404,12 +408,34 @@ function getAllSymbols(symbol, apiHost = "https://api.binance.com", exchange = "
|
|
|
404
408
|
}
|
|
405
409
|
|
|
406
410
|
// src/exchangeAdapters.ts
|
|
411
|
+
var KUCOIN_FUTURES_API = "https://api-futures.kucoin.com";
|
|
412
|
+
function isKucoinFutures(symbol) {
|
|
413
|
+
return symbol.endsWith("M");
|
|
414
|
+
}
|
|
407
415
|
function mapKuCoinInterval(interval) {
|
|
408
416
|
if (interval.endsWith("m")) return interval.replace("m", "min");
|
|
409
417
|
if (interval.endsWith("h")) return interval.replace("h", "hour");
|
|
410
418
|
if (interval.endsWith("d")) return interval.replace("d", "day");
|
|
411
419
|
return interval;
|
|
412
420
|
}
|
|
421
|
+
function mapKuCoinFuturesGranularity(interval) {
|
|
422
|
+
const match = interval.match(/^(\d+)([mhdw])$/);
|
|
423
|
+
if (!match) return 60;
|
|
424
|
+
const value = parseInt(match[1]);
|
|
425
|
+
const unit = match[2];
|
|
426
|
+
switch (unit) {
|
|
427
|
+
case "m":
|
|
428
|
+
return value;
|
|
429
|
+
case "h":
|
|
430
|
+
return value * 60;
|
|
431
|
+
case "d":
|
|
432
|
+
return value * 1440;
|
|
433
|
+
case "w":
|
|
434
|
+
return value * 10080;
|
|
435
|
+
default:
|
|
436
|
+
return 60;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
413
439
|
var binanceAdapter = {
|
|
414
440
|
fetchSymbolMeta(symbol, apiHost) {
|
|
415
441
|
return __async(this, null, function* () {
|
|
@@ -457,6 +483,20 @@ var binanceAdapter = {
|
|
|
457
483
|
var kucoinAdapter = {
|
|
458
484
|
fetchSymbolMeta(symbol, apiHost) {
|
|
459
485
|
return __async(this, null, function* () {
|
|
486
|
+
var _a;
|
|
487
|
+
if (isKucoinFutures(symbol)) {
|
|
488
|
+
const info2 = yield makeApiRequest(
|
|
489
|
+
`api/v1/contracts/${symbol}`,
|
|
490
|
+
KUCOIN_FUTURES_API
|
|
491
|
+
);
|
|
492
|
+
let priceScale2 = 8;
|
|
493
|
+
if ((_a = info2 == null ? void 0 : info2.data) == null ? void 0 : _a.tickSize) {
|
|
494
|
+
const tickStr = String(info2.data.tickSize);
|
|
495
|
+
const dot = tickStr.indexOf(".");
|
|
496
|
+
priceScale2 = dot >= 0 ? Math.max(0, tickStr.length - dot - 1) : 0;
|
|
497
|
+
}
|
|
498
|
+
return { priceScale: priceScale2 };
|
|
499
|
+
}
|
|
460
500
|
const info = yield makeApiRequest(
|
|
461
501
|
`api/v1/symbols?symbol=${symbol}`,
|
|
462
502
|
apiHost
|
|
@@ -473,6 +513,37 @@ var kucoinAdapter = {
|
|
|
473
513
|
},
|
|
474
514
|
fetchBars(_0, _1) {
|
|
475
515
|
return __async(this, arguments, function* ({ symbol, interval, from, to }, apiHost) {
|
|
516
|
+
if (isKucoinFutures(symbol)) {
|
|
517
|
+
const granularity = mapKuCoinFuturesGranularity(interval);
|
|
518
|
+
const params2 = new URLSearchParams({
|
|
519
|
+
symbol,
|
|
520
|
+
granularity: String(granularity),
|
|
521
|
+
from: String(from * 1e3),
|
|
522
|
+
// futures expects milliseconds
|
|
523
|
+
to: String(to * 1e3)
|
|
524
|
+
});
|
|
525
|
+
const resp2 = yield makeApiRequest(
|
|
526
|
+
`api/v1/kline/query?${params2.toString()}`,
|
|
527
|
+
KUCOIN_FUTURES_API
|
|
528
|
+
);
|
|
529
|
+
const raw2 = Array.isArray(resp2 == null ? void 0 : resp2.data) ? resp2.data : [];
|
|
530
|
+
const candles2 = raw2.map((bar) => [
|
|
531
|
+
Number(bar[0]),
|
|
532
|
+
// time already in ms
|
|
533
|
+
Number(bar[1]),
|
|
534
|
+
// open
|
|
535
|
+
Number(bar[2]),
|
|
536
|
+
// high
|
|
537
|
+
Number(bar[3]),
|
|
538
|
+
// low
|
|
539
|
+
Number(bar[4]),
|
|
540
|
+
// close
|
|
541
|
+
Number(bar[5])
|
|
542
|
+
// volume
|
|
543
|
+
]);
|
|
544
|
+
candles2.sort((a, b) => a[0] - b[0]);
|
|
545
|
+
return candles2;
|
|
546
|
+
}
|
|
476
547
|
const type = mapKuCoinInterval(interval);
|
|
477
548
|
const params = new URLSearchParams({
|
|
478
549
|
symbol,
|
|
@@ -535,21 +606,23 @@ var SUPPORTED_EXCHANGES = {
|
|
|
535
606
|
restApiUrl: "https://api.kucoin.com",
|
|
536
607
|
wsUrl: "",
|
|
537
608
|
// Will be fetched dynamically
|
|
538
|
-
getWsUrl: () => __async(void 0, null, function* () {
|
|
609
|
+
getWsUrl: (symbol) => __async(void 0, null, function* () {
|
|
539
610
|
var _a, _b;
|
|
611
|
+
const isFutures = symbol == null ? void 0 : symbol.endsWith("M");
|
|
612
|
+
const apiBase = isFutures ? "https://api-futures.kucoin.com" : "https://api.kucoin.com";
|
|
540
613
|
try {
|
|
541
|
-
const data = yield makeApiRequest(
|
|
542
|
-
"
|
|
543
|
-
|
|
544
|
-
{ method: "POST" }
|
|
545
|
-
);
|
|
614
|
+
const data = yield makeApiRequest("api/v1/bullet-public", apiBase, {
|
|
615
|
+
method: "POST"
|
|
616
|
+
});
|
|
546
617
|
if (data.code === "200000" && ((_b = (_a = data.data) == null ? void 0 : _a.instanceServers) == null ? void 0 : _b.length) > 0) {
|
|
547
618
|
const server = data.data.instanceServers[0];
|
|
548
619
|
return `${server.endpoint}?token=${data.data.token}`;
|
|
549
620
|
}
|
|
550
621
|
throw new Error("Failed to get KuCoin WebSocket URL: Invalid response");
|
|
551
622
|
} catch (error) {
|
|
552
|
-
throw new Error(
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Failed to get KuCoin WebSocket URL: ${(error == null ? void 0 : error.message) || String(error)}`
|
|
625
|
+
);
|
|
553
626
|
}
|
|
554
627
|
})
|
|
555
628
|
}
|
|
@@ -673,7 +746,15 @@ function parseKuCoinMessage(data) {
|
|
|
673
746
|
subscriptionItem.handlers.forEach((handler) => handler.callback(bar));
|
|
674
747
|
}
|
|
675
748
|
function subscribeOnStream(symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback, interval, wsUrl, exchange = EXCHANGE_BINANCE) {
|
|
676
|
-
|
|
749
|
+
let channelString;
|
|
750
|
+
if (exchange === EXCHANGE_BINANCE) {
|
|
751
|
+
channelString = `${symbolInfo.name.toLowerCase()}@kline_${interval}`;
|
|
752
|
+
} else if (isKucoinFutures(symbolInfo.name)) {
|
|
753
|
+
const granularity = mapKuCoinFuturesGranularity(interval);
|
|
754
|
+
channelString = `/contractMarket/candle:${symbolInfo.name}_${granularity}`;
|
|
755
|
+
} else {
|
|
756
|
+
channelString = `/market/candles:${symbolInfo.name}_${interval}`;
|
|
757
|
+
}
|
|
677
758
|
const handler = {
|
|
678
759
|
id: subscribeUID,
|
|
679
760
|
callback: onRealtimeCallback
|
|
@@ -793,7 +874,9 @@ var Datafeed = class {
|
|
|
793
874
|
constructor(timescaleMarks = [], interval = "1h", exchangeConfig, supportedExchanges = []) {
|
|
794
875
|
this.configurationData = null;
|
|
795
876
|
this.onReady = (callback) => __async(this, null, function* () {
|
|
796
|
-
this.configurationData = yield getConfigurationData(
|
|
877
|
+
this.configurationData = yield getConfigurationData(
|
|
878
|
+
this.supportedExchanges
|
|
879
|
+
);
|
|
797
880
|
callback(this.configurationData);
|
|
798
881
|
});
|
|
799
882
|
this.searchSymbols = (userInput, exchange, symbolType, onResultReadyCallback) => __async(this, null, function* () {
|
|
@@ -811,7 +894,10 @@ var Datafeed = class {
|
|
|
811
894
|
}
|
|
812
895
|
const symbolInfo = () => __async(this, null, function* () {
|
|
813
896
|
var _a;
|
|
814
|
-
const meta = yield this.adapter.fetchSymbolMeta(
|
|
897
|
+
const meta = yield this.adapter.fetchSymbolMeta(
|
|
898
|
+
symbolName,
|
|
899
|
+
this.exchangeConfig.restApiUrl
|
|
900
|
+
);
|
|
815
901
|
const priceScale = (_a = meta.priceScale) != null ? _a : 8;
|
|
816
902
|
return {
|
|
817
903
|
name: symbolName,
|
|
@@ -885,7 +971,7 @@ var Datafeed = class {
|
|
|
885
971
|
let wsUrl = this.exchangeConfig.wsUrl;
|
|
886
972
|
if (this.exchangeConfig.getWsUrl) {
|
|
887
973
|
try {
|
|
888
|
-
wsUrl = yield this.exchangeConfig.getWsUrl();
|
|
974
|
+
wsUrl = yield this.exchangeConfig.getWsUrl(symbolInfo.name);
|
|
889
975
|
} catch (error) {
|
|
890
976
|
console.error("Failed to get WebSocket URL:", error);
|
|
891
977
|
return;
|
|
@@ -922,7 +1008,9 @@ var Datafeed = class {
|
|
|
922
1008
|
}
|
|
923
1009
|
getServerTime(onServertimeCallback) {
|
|
924
1010
|
return __async(this, null, function* () {
|
|
925
|
-
const serverTime = yield this.adapter.fetchServerTime(
|
|
1011
|
+
const serverTime = yield this.adapter.fetchServerTime(
|
|
1012
|
+
this.exchangeConfig.restApiUrl
|
|
1013
|
+
);
|
|
926
1014
|
onServertimeCallback(serverTime);
|
|
927
1015
|
});
|
|
928
1016
|
}
|
|
@@ -945,74 +1033,32 @@ var TVChartContainer = ({
|
|
|
945
1033
|
const containerRef = (0, import_react2.useRef)(null);
|
|
946
1034
|
const [chartOrderLines, setChartOrderLines] = (0, import_use_immer.useImmer)([]);
|
|
947
1035
|
const [widgetState, setWidgetState] = (0, import_use_immer.useImmer)(null);
|
|
948
|
-
const [symbolState, setSymbolState] = (0, import_react2.useState)(symbol);
|
|
949
|
-
const [transformedSymbol, setTransformedSymbol] = (0, import_react2.useState)(null);
|
|
950
|
-
const [isLoadingSymbol, setIsLoadingSymbol] = (0, import_react2.useState)(false);
|
|
951
1036
|
const prevTimescaleMarks = (0, import_react2.useRef)(timescaleMarks);
|
|
952
1037
|
const prevExchange = (0, import_react2.useRef)(exchange);
|
|
1038
|
+
const prevSymbol = (0, import_react2.useRef)(symbol);
|
|
953
1039
|
(0, import_react2.useEffect)(() => {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const response = yield fetch(`https://api.terminal.binbot.in/symbol/${cleanSymbol}`);
|
|
961
|
-
if (!response.ok) {
|
|
962
|
-
console.warn(`Failed to fetch symbol metadata for ${cleanSymbol}, using original symbol`);
|
|
963
|
-
setTransformedSymbol(symbol);
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
966
|
-
const result = yield response.json();
|
|
967
|
-
if (((_a = result.data) == null ? void 0 : _a.base_asset) && ((_b = result.data) == null ? void 0 : _b.quote_asset)) {
|
|
968
|
-
const { base_asset, quote_asset } = result.data;
|
|
969
|
-
const formatted = exchange.toLowerCase() === "kucoin" ? `${base_asset}-${quote_asset}` : `${base_asset}${quote_asset}`;
|
|
970
|
-
setTransformedSymbol(formatted);
|
|
971
|
-
} else {
|
|
972
|
-
console.warn("Invalid symbol metadata response, using original symbol");
|
|
973
|
-
setTransformedSymbol(symbol);
|
|
974
|
-
}
|
|
975
|
-
} catch (error) {
|
|
976
|
-
console.error("Error fetching symbol metadata:", error);
|
|
977
|
-
setTransformedSymbol(symbol);
|
|
978
|
-
} finally {
|
|
979
|
-
setIsLoadingSymbol(false);
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
fetchSymbolMetadata();
|
|
983
|
-
}, [symbol, exchange]);
|
|
984
|
-
(0, import_react2.useEffect)(() => {
|
|
985
|
-
if (isLoadingSymbol || !transformedSymbol) {
|
|
1040
|
+
if (!symbol) return;
|
|
1041
|
+
if (widgetState && (symbol !== prevSymbol.current || exchange !== prevExchange.current)) {
|
|
1042
|
+
widgetState.remove();
|
|
1043
|
+
setWidgetState(null);
|
|
1044
|
+
prevSymbol.current = symbol;
|
|
1045
|
+
prevExchange.current = exchange;
|
|
986
1046
|
return;
|
|
987
1047
|
}
|
|
988
1048
|
if (!widgetState) {
|
|
989
1049
|
initializeChart(interval);
|
|
990
|
-
|
|
991
|
-
setSymbolState(transformedSymbol);
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
if (widgetState && exchange !== prevExchange.current) {
|
|
995
|
-
widgetState.remove();
|
|
996
|
-
setWidgetState(null);
|
|
1050
|
+
prevSymbol.current = symbol;
|
|
997
1051
|
prevExchange.current = exchange;
|
|
998
1052
|
return;
|
|
999
1053
|
}
|
|
1000
1054
|
if (orderLines && orderLines.length > 0) {
|
|
1001
1055
|
updateOrderLines(orderLines);
|
|
1002
1056
|
}
|
|
1003
|
-
if (widgetState && transformedSymbol !== symbolState) {
|
|
1004
|
-
try {
|
|
1005
|
-
widgetState.setSymbol(transformedSymbol, interval);
|
|
1006
|
-
setSymbolState(transformedSymbol);
|
|
1007
|
-
} catch (error) {
|
|
1008
|
-
console.error("Failed to set symbol:", error);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
1057
|
if (widgetState && prevTimescaleMarks.current && timescaleMarks !== prevTimescaleMarks.current) {
|
|
1012
1058
|
widgetState._options.datafeed.timescaleMarks = timescaleMarks;
|
|
1013
1059
|
prevTimescaleMarks.current = timescaleMarks;
|
|
1014
1060
|
}
|
|
1015
|
-
}, [orderLines, timescaleMarks, exchange,
|
|
1061
|
+
}, [symbol, orderLines, timescaleMarks, exchange, widgetState, interval]);
|
|
1016
1062
|
const initializeChart = (interval2) => {
|
|
1017
1063
|
const exchangeConfig = SUPPORTED_EXCHANGES[exchange.toLowerCase()];
|
|
1018
1064
|
if (!exchangeConfig) {
|
|
@@ -1021,7 +1067,7 @@ var TVChartContainer = ({
|
|
|
1021
1067
|
}
|
|
1022
1068
|
const supportedExchangeConfigs = supportedExchanges.map((ex) => SUPPORTED_EXCHANGES[ex.toLowerCase()]).filter(Boolean);
|
|
1023
1069
|
const widgetOptions = {
|
|
1024
|
-
symbol
|
|
1070
|
+
symbol,
|
|
1025
1071
|
datafeed: new Datafeed(
|
|
1026
1072
|
timescaleMarks,
|
|
1027
1073
|
interval2,
|