binbot-charts 0.12.12 → 0.12.14

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
@@ -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 'binbot-charts';
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" // or "kucoin"
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 'binbot-charts';
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
@@ -384,7 +384,10 @@ function getAllSymbols(symbol, apiHost = "https://api.binance.com", exchange = "
384
384
  return __async(this, null, function* () {
385
385
  let newSymbols = [];
386
386
  try {
387
- const data = yield makeApiRequest(`api/v3/exchangeInfo?symbol=${symbol.toUpperCase()}`, apiHost);
387
+ const data = yield makeApiRequest(
388
+ `api/v3/exchangeInfo?symbol=${symbol.toUpperCase()}`,
389
+ apiHost
390
+ );
388
391
  data.symbols.forEach((item) => {
389
392
  if (item.status === "TRADING") {
390
393
  newSymbols.push({
@@ -477,6 +480,7 @@ var binanceAdapter = {
477
480
  });
478
481
  }
479
482
  };
483
+ var kucoinFuturesBarsAbort = null;
480
484
  var kucoinAdapter = {
481
485
  fetchSymbolMeta(symbol, apiHost) {
482
486
  return __async(this, null, function* () {
@@ -511,35 +515,50 @@ var kucoinAdapter = {
511
515
  fetchBars(_0, _1) {
512
516
  return __async(this, arguments, function* ({ symbol, interval, from, to }, apiHost) {
513
517
  if (isKucoinFutures(symbol)) {
518
+ if (kucoinFuturesBarsAbort) {
519
+ kucoinFuturesBarsAbort.abort();
520
+ }
521
+ kucoinFuturesBarsAbort = new AbortController();
522
+ const { signal } = kucoinFuturesBarsAbort;
514
523
  const granularity = mapKuCoinFuturesGranularity(interval);
515
- const params2 = new URLSearchParams({
516
- symbol,
517
- granularity: String(granularity),
518
- from: String(from * 1e3),
519
- // futures expects milliseconds
520
- to: String(to * 1e3)
524
+ const fromMs = from * 1e3;
525
+ let currentTo = to * 1e3;
526
+ let allCandles = [];
527
+ const MAX_PAGES = 10;
528
+ for (let page = 0; page < MAX_PAGES; page++) {
529
+ const params2 = new URLSearchParams({
530
+ symbol,
531
+ granularity: String(granularity),
532
+ to: String(currentTo)
533
+ });
534
+ const resp2 = yield makeApiRequest(
535
+ `api/v1/kline/query?${params2.toString()}`,
536
+ KUCOIN_FUTURES_API,
537
+ { signal }
538
+ );
539
+ const raw2 = Array.isArray(resp2 == null ? void 0 : resp2.data) ? resp2.data : [];
540
+ if (raw2.length === 0) break;
541
+ const candles2 = raw2.map((bar) => [
542
+ Number(bar[0]),
543
+ Number(bar[1]),
544
+ Number(bar[2]),
545
+ Number(bar[3]),
546
+ Number(bar[4]),
547
+ Number(bar[5])
548
+ ]);
549
+ allCandles = allCandles.concat(candles2);
550
+ const earliest = Math.min(...candles2.map((c) => c[0]));
551
+ if (earliest <= fromMs) break;
552
+ currentTo = earliest;
553
+ }
554
+ const seen = /* @__PURE__ */ new Set();
555
+ const unique = allCandles.filter((c) => {
556
+ if (seen.has(c[0])) return false;
557
+ seen.add(c[0]);
558
+ return true;
521
559
  });
522
- const resp2 = yield makeApiRequest(
523
- `api/v1/kline/query?${params2.toString()}`,
524
- KUCOIN_FUTURES_API
525
- );
526
- const raw2 = Array.isArray(resp2 == null ? void 0 : resp2.data) ? resp2.data : [];
527
- const candles2 = raw2.map((bar) => [
528
- Number(bar[0]),
529
- // time already in ms
530
- Number(bar[1]),
531
- // open
532
- Number(bar[2]),
533
- // high
534
- Number(bar[3]),
535
- // low
536
- Number(bar[4]),
537
- // close
538
- Number(bar[5])
539
- // volume
540
- ]);
541
- candles2.sort((a, b) => a[0] - b[0]);
542
- return candles2;
560
+ unique.sort((a, b) => a[0] - b[0]);
561
+ return unique;
543
562
  }
544
563
  const type = mapKuCoinInterval(interval);
545
564
  const params = new URLSearchParams({
@@ -608,18 +627,18 @@ var SUPPORTED_EXCHANGES = {
608
627
  const isFutures = symbol == null ? void 0 : symbol.endsWith("M");
609
628
  const apiBase = isFutures ? "https://api-futures.kucoin.com" : "https://api.kucoin.com";
610
629
  try {
611
- const data = yield makeApiRequest(
612
- "api/v1/bullet-public",
613
- apiBase,
614
- { method: "POST" }
615
- );
630
+ const data = yield makeApiRequest("api/v1/bullet-public", apiBase, {
631
+ method: "POST"
632
+ });
616
633
  if (data.code === "200000" && ((_b = (_a = data.data) == null ? void 0 : _a.instanceServers) == null ? void 0 : _b.length) > 0) {
617
634
  const server = data.data.instanceServers[0];
618
635
  return `${server.endpoint}?token=${data.data.token}`;
619
636
  }
620
637
  throw new Error("Failed to get KuCoin WebSocket URL: Invalid response");
621
638
  } catch (error) {
622
- throw new Error(`Failed to get KuCoin WebSocket URL: ${(error == null ? void 0 : error.message) || String(error)}`);
639
+ throw new Error(
640
+ `Failed to get KuCoin WebSocket URL: ${(error == null ? void 0 : error.message) || String(error)}`
641
+ );
623
642
  }
624
643
  })
625
644
  }
@@ -871,7 +890,9 @@ var Datafeed = class {
871
890
  constructor(timescaleMarks = [], interval = "1h", exchangeConfig, supportedExchanges = []) {
872
891
  this.configurationData = null;
873
892
  this.onReady = (callback) => __async(this, null, function* () {
874
- this.configurationData = yield getConfigurationData(this.supportedExchanges);
893
+ this.configurationData = yield getConfigurationData(
894
+ this.supportedExchanges
895
+ );
875
896
  callback(this.configurationData);
876
897
  });
877
898
  this.searchSymbols = (userInput, exchange, symbolType, onResultReadyCallback) => __async(this, null, function* () {
@@ -889,7 +910,10 @@ var Datafeed = class {
889
910
  }
890
911
  const symbolInfo = () => __async(this, null, function* () {
891
912
  var _a;
892
- const meta = yield this.adapter.fetchSymbolMeta(symbolName, this.exchangeConfig.restApiUrl);
913
+ const meta = yield this.adapter.fetchSymbolMeta(
914
+ symbolName,
915
+ this.exchangeConfig.restApiUrl
916
+ );
893
917
  const priceScale = (_a = meta.priceScale) != null ? _a : 8;
894
918
  return {
895
919
  name: symbolName,
@@ -917,6 +941,10 @@ var Datafeed = class {
917
941
  });
918
942
  this.getBars = (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => __async(this, null, function* () {
919
943
  const { from, to, firstDataRequest } = periodParams;
944
+ if (!firstDataRequest) {
945
+ onHistoryCallback([], { noData: true });
946
+ return;
947
+ }
920
948
  let interval = "60";
921
949
  if (!/[a-zA-Z]$/.test(resolution)) {
922
950
  if (parseInt(resolution) >= 60) {
@@ -952,7 +980,7 @@ var Datafeed = class {
952
980
  ];
953
981
  }
954
982
  });
955
- onHistoryCallback(bars, { noData: false });
983
+ onHistoryCallback(bars, { noData: bars.length === 0 });
956
984
  } catch (error) {
957
985
  console.log("[getBars]: Get error", error);
958
986
  onErrorCallback(error);
@@ -1000,7 +1028,9 @@ var Datafeed = class {
1000
1028
  }
1001
1029
  getServerTime(onServertimeCallback) {
1002
1030
  return __async(this, null, function* () {
1003
- const serverTime = yield this.adapter.fetchServerTime(this.exchangeConfig.restApiUrl);
1031
+ const serverTime = yield this.adapter.fetchServerTime(
1032
+ this.exchangeConfig.restApiUrl
1033
+ );
1004
1034
  onServertimeCallback(serverTime);
1005
1035
  });
1006
1036
  }
@@ -1023,74 +1053,32 @@ var TVChartContainer = ({
1023
1053
  const containerRef = (0, import_react2.useRef)(null);
1024
1054
  const [chartOrderLines, setChartOrderLines] = (0, import_use_immer.useImmer)([]);
1025
1055
  const [widgetState, setWidgetState] = (0, import_use_immer.useImmer)(null);
1026
- const [symbolState, setSymbolState] = (0, import_react2.useState)(symbol);
1027
- const [transformedSymbol, setTransformedSymbol] = (0, import_react2.useState)(null);
1028
- const [isLoadingSymbol, setIsLoadingSymbol] = (0, import_react2.useState)(false);
1029
1056
  const prevTimescaleMarks = (0, import_react2.useRef)(timescaleMarks);
1030
1057
  const prevExchange = (0, import_react2.useRef)(exchange);
1058
+ const prevSymbol = (0, import_react2.useRef)(symbol);
1031
1059
  (0, import_react2.useEffect)(() => {
1032
- const fetchSymbolMetadata = () => __async(void 0, null, function* () {
1033
- var _a, _b;
1034
- if (!symbol) return;
1035
- setIsLoadingSymbol(true);
1036
- try {
1037
- const cleanSymbol = symbol.replace(/-/g, "");
1038
- const response = yield fetch(`https://api.terminal.binbot.in/symbol/${cleanSymbol}`);
1039
- if (!response.ok) {
1040
- console.warn(`Failed to fetch symbol metadata for ${cleanSymbol}, using original symbol`);
1041
- setTransformedSymbol(symbol);
1042
- return;
1043
- }
1044
- const result = yield response.json();
1045
- if (((_a = result.data) == null ? void 0 : _a.base_asset) && ((_b = result.data) == null ? void 0 : _b.quote_asset)) {
1046
- const { base_asset, quote_asset } = result.data;
1047
- const formatted = exchange.toLowerCase() === "kucoin" ? `${base_asset}-${quote_asset}` : `${base_asset}${quote_asset}`;
1048
- setTransformedSymbol(formatted);
1049
- } else {
1050
- console.warn("Invalid symbol metadata response, using original symbol");
1051
- setTransformedSymbol(symbol);
1052
- }
1053
- } catch (error) {
1054
- console.error("Error fetching symbol metadata:", error);
1055
- setTransformedSymbol(symbol);
1056
- } finally {
1057
- setIsLoadingSymbol(false);
1058
- }
1059
- });
1060
- fetchSymbolMetadata();
1061
- }, [symbol, exchange]);
1062
- (0, import_react2.useEffect)(() => {
1063
- if (isLoadingSymbol || !transformedSymbol) {
1060
+ if (!symbol) return;
1061
+ if (widgetState && (symbol !== prevSymbol.current || exchange !== prevExchange.current)) {
1062
+ widgetState.remove();
1063
+ setWidgetState(null);
1064
+ prevSymbol.current = symbol;
1065
+ prevExchange.current = exchange;
1064
1066
  return;
1065
1067
  }
1066
1068
  if (!widgetState) {
1067
1069
  initializeChart(interval);
1068
- prevExchange.current = exchange;
1069
- setSymbolState(transformedSymbol);
1070
- return;
1071
- }
1072
- if (widgetState && exchange !== prevExchange.current) {
1073
- widgetState.remove();
1074
- setWidgetState(null);
1070
+ prevSymbol.current = symbol;
1075
1071
  prevExchange.current = exchange;
1076
1072
  return;
1077
1073
  }
1078
1074
  if (orderLines && orderLines.length > 0) {
1079
1075
  updateOrderLines(orderLines);
1080
1076
  }
1081
- if (widgetState && transformedSymbol !== symbolState) {
1082
- try {
1083
- widgetState.setSymbol(transformedSymbol, interval);
1084
- setSymbolState(transformedSymbol);
1085
- } catch (error) {
1086
- console.error("Failed to set symbol:", error);
1087
- }
1088
- }
1089
1077
  if (widgetState && prevTimescaleMarks.current && timescaleMarks !== prevTimescaleMarks.current) {
1090
1078
  widgetState._options.datafeed.timescaleMarks = timescaleMarks;
1091
1079
  prevTimescaleMarks.current = timescaleMarks;
1092
1080
  }
1093
- }, [orderLines, timescaleMarks, exchange, isLoadingSymbol, transformedSymbol, widgetState, symbolState, interval]);
1081
+ }, [symbol, orderLines, timescaleMarks, exchange, widgetState, interval]);
1094
1082
  const initializeChart = (interval2) => {
1095
1083
  const exchangeConfig = SUPPORTED_EXCHANGES[exchange.toLowerCase()];
1096
1084
  if (!exchangeConfig) {
@@ -1099,7 +1087,7 @@ var TVChartContainer = ({
1099
1087
  }
1100
1088
  const supportedExchangeConfigs = supportedExchanges.map((ex) => SUPPORTED_EXCHANGES[ex.toLowerCase()]).filter(Boolean);
1101
1089
  const widgetOptions = {
1102
- symbol: transformedSymbol || symbol,
1090
+ symbol,
1103
1091
  datafeed: new Datafeed(
1104
1092
  timescaleMarks,
1105
1093
  interval2,