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 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
@@ -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(`api/v3/exchangeInfo?symbol=${symbol.toUpperCase()}`, apiHost);
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
- "api/v1/bullet-public",
543
- "https://api.kucoin.com",
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(`Failed to get KuCoin WebSocket URL: ${(error == null ? void 0 : error.message) || String(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
- const channelString = exchange === EXCHANGE_BINANCE ? `${symbolInfo.name.toLowerCase()}@kline_${interval}` : `/market/candles:${symbolInfo.name}_${interval}`;
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(this.supportedExchanges);
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(symbolName, this.exchangeConfig.restApiUrl);
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(this.exchangeConfig.restApiUrl);
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
- const fetchSymbolMetadata = () => __async(void 0, null, function* () {
955
- var _a, _b;
956
- if (!symbol) return;
957
- setIsLoadingSymbol(true);
958
- try {
959
- const cleanSymbol = symbol.replace(/-/g, "");
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
- prevExchange.current = exchange;
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, isLoadingSymbol, transformedSymbol, widgetState, symbolState, interval]);
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: transformedSymbol || symbol,
1070
+ symbol,
1025
1071
  datafeed: new Datafeed(
1026
1072
  timescaleMarks,
1027
1073
  interval2,