binbot-charts 0.0.3 → 0.0.4

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.3",
2
+ "version": "0.0.4",
3
3
  "name": "binbot-charts",
4
4
  "dependencies": {
5
5
  "@babel/cli": "^7.18.10",
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "scripts": {
14
14
  "start": "react-scripts start",
15
- "build": "rm -rf dist && NODE_ENV=production babel src/components src/charting_library --out-dir dist --copy-files"
15
+ "build": "rm -rf dist && NODE_ENV=production babel src/components src/charting_library --out-dir dist --copy-files",
16
+ "publish": "yarn build && npm publish"
16
17
  },
17
18
  "description": "Binbot charts is the default candlestick bars chart used in terminal.binbot.com to render bots graphically.",
18
19
  "repository": {
package/src/App.jsx CHANGED
@@ -1,13 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import './App.css';
3
3
  import { TVChartContainer } from './components/TVChartContainer';
4
- import { version } from './charting_library';
5
4
 
6
5
  class App extends React.Component {
6
+
7
7
  render() {
8
8
  return (
9
9
  <div className={ 'App' }>
10
- <TVChartContainer />
10
+ <TVChartContainer symbol="symbol" interval="D"/>
11
11
 
12
12
  </div>
13
13
  );
@@ -1,17 +1,8 @@
1
- import * as React from "react";
1
+ import React, { Component } from "react";
2
2
  import { widget } from "../charting_library";
3
+ import Datafeed from "./datafeed";
3
4
 
4
- export class TVChartContainer extends React.PureComponent {
5
- static defaultProps = {
6
- symbol: "AAPL",
7
- interval: "D",
8
- datafeedUrl: "https://demo_feed.tradingview.com",
9
- libraryPath: "/charting_library/",
10
- chartsStorageApiVersion: "1.1",
11
- fullscreen: false,
12
- autosize: true,
13
- studiesOverrides: {},
14
- };
5
+ export default class TVChartContainer extends Component {
15
6
 
16
7
  tvWidget = null;
17
8
 
@@ -22,26 +13,25 @@ export class TVChartContainer extends React.PureComponent {
22
13
  }
23
14
 
24
15
  componentDidMount() {
16
+
17
+
25
18
  const widgetOptions = {
26
19
  symbol: "AAPL",
27
20
  // BEWARE: no trailing slash is expected in feed URL
28
- datafeed: new window.Datafeeds.UDFCompatibleDatafeed(
29
- this.props.datafeedUrl
30
- ),
31
- interval: this.props.interval,
21
+ datafeed: Datafeed,
22
+ // interval: this.props.interval,
32
23
  container: this.ref.current,
33
24
  library_path: this.props.libraryPath,
34
-
35
25
  locale: "en",
36
- disabled_features: ["use_localstorage_for_settings"],
37
- enabled_features: ["study_templates"],
38
- charts_storage_url: this.props.chartsStorageUrl,
39
- charts_storage_api_version: this.props.chartsStorageApiVersion,
40
- client_id: this.props.clientId,
41
- user_id: this.props.userId,
42
- fullscreen: this.props.fullscreen,
43
- autosize: this.props.autosize,
44
- studies_overrides: this.props.studiesOverrides,
26
+ // disabled_features: ["use_localstorage_for_settings"],
27
+ // enabled_features: ["study_templates"],
28
+ // charts_storage_url: this.props.chartsStorageUrl,
29
+ // charts_storage_api_version: this.props.chartsStorageApiVersion,
30
+ // client_id: this.props.clientId,
31
+ // user_id: this.props.userId,
32
+ fullscreen: false,
33
+ autosize: false,
34
+ studies_overrides: {},
45
35
  };
46
36
 
47
37
  const tvWidget = new widget(widgetOptions);
@@ -0,0 +1,201 @@
1
+ import { makeApiRequest, generateSymbol, parseFullSymbol } from "./helpers.js";
2
+ import { subscribeOnStream, unsubscribeFromStream } from "./streaming.js";
3
+
4
+ const lastBarsCache = new Map();
5
+ const exchange = "Binance";
6
+ const configurationData = {
7
+ supported_resolutions: ["1D", "1W", "1M"],
8
+ exchanges: [
9
+ {
10
+ value: "Binance",
11
+ name: "Binance",
12
+ desc: "Binance",
13
+ },
14
+ ],
15
+ symbols_types: [
16
+ {
17
+ name: "crypto",
18
+
19
+ // `symbolType` argument for the `searchSymbols` method, if a user selects this symbol type
20
+ value: "crypto",
21
+ },
22
+ // ...
23
+ ],
24
+ };
25
+
26
+ async function getAllSymbols() {
27
+ const data = await makeApiRequest("data/v3/all/exchanges");
28
+ let allSymbols = [];
29
+
30
+ const pairs = data.Data[exchange].pairs;
31
+ for (const leftPairPart of Object.keys(pairs)) {
32
+ const symbols = pairs[leftPairPart].map((rightPairPart) => {
33
+ const symbol = generateSymbol(
34
+ exchange,
35
+ leftPairPart,
36
+ rightPairPart
37
+ );
38
+ return {
39
+ symbol: symbol.short,
40
+ full_name: symbol.full,
41
+ description: symbol.short,
42
+ exchange: exchange,
43
+ type: "crypto",
44
+ };
45
+ });
46
+ allSymbols = [...allSymbols, ...symbols];
47
+ }
48
+ return allSymbols;
49
+ }
50
+
51
+ export default class Datafeed {
52
+ static onReady = (callback) => {
53
+ console.log("[onReady]: Method call");
54
+ setTimeout(() => callback(configurationData));
55
+ };
56
+
57
+ static searchSymbols = async (
58
+ userInput,
59
+ exchange,
60
+ symbolType,
61
+ onResultReadyCallback
62
+ ) => {
63
+ console.log("[searchSymbols]: Method call");
64
+ const symbols = await getAllSymbols();
65
+ const newSymbols = symbols.filter((symbol) => {
66
+ const isExchangeValid = exchange === "" || symbol.exchange === exchange;
67
+ const isFullSymbolContainsInput =
68
+ symbol.full_name.toLowerCase().indexOf(userInput.toLowerCase()) !== -1;
69
+ return isExchangeValid && isFullSymbolContainsInput;
70
+ });
71
+ onResultReadyCallback(newSymbols);
72
+ };
73
+
74
+ static resolveSymbol = async (
75
+ symbolName,
76
+ onSymbolResolvedCallback,
77
+ onResolveErrorCallback
78
+ ) => {
79
+ console.log("[resolveSymbol]: Method call", symbolName);
80
+ const symbols = await getAllSymbols();
81
+ const symbolItem = symbols.find(
82
+ ({ full_name }) => full_name === symbolName
83
+ );
84
+ if (!symbolItem) {
85
+ console.log("[resolveSymbol]: Cannot resolve symbol", symbolName);
86
+ onResolveErrorCallback("cannot resolve symbol");
87
+ return;
88
+ }
89
+ const symbolInfo = {
90
+ ticker: symbolItem.full_name,
91
+ name: symbolItem.symbol,
92
+ description: symbolItem.description,
93
+ type: symbolItem.type,
94
+ session: "24x7",
95
+ timezone: "Etc/UTC",
96
+ exchange: symbolItem.exchange,
97
+ minmov: 1,
98
+ pricescale: 100,
99
+ has_intraday: false,
100
+ has_no_volume: true,
101
+ has_weekly_and_monthly: false,
102
+ supported_resolutions: ["1D", "1W", "1M"],
103
+ volume_precision: 2,
104
+ data_status: "streaming",
105
+ };
106
+
107
+ console.log("[resolveSymbol]: Symbol resolved", symbolName);
108
+ onSymbolResolvedCallback(symbolInfo);
109
+ };
110
+
111
+ static getBars = async (
112
+ symbolInfo,
113
+ resolution,
114
+ periodParams,
115
+ onHistoryCallback,
116
+ onErrorCallback
117
+ ) => {
118
+ const { from, to, firstDataRequest } = periodParams;
119
+ console.log("[getBars]: Method call", symbolInfo, resolution, from, to);
120
+ const parsedSymbol = parseFullSymbol(symbolInfo.full_name);
121
+ const urlParameters = {
122
+ e: parsedSymbol.exchange,
123
+ fsym: parsedSymbol.fromSymbol,
124
+ tsym: parsedSymbol.toSymbol,
125
+ toTs: to,
126
+ limit: 2000,
127
+ };
128
+ const query = Object.keys(urlParameters)
129
+ .map((name) => `${name}=${encodeURIComponent(urlParameters[name])}`)
130
+ .join("&");
131
+ try {
132
+ const data = await makeApiRequest(`data/histoday?${query}`);
133
+ if (
134
+ (data.Response && data.Response === "Error") ||
135
+ data.Data.length === 0
136
+ ) {
137
+ // "noData" should be set if there is no data in the requested period.
138
+ onHistoryCallback([], {
139
+ noData: true,
140
+ });
141
+ return;
142
+ }
143
+ let bars = [];
144
+ data.Data.forEach((bar) => {
145
+ if (bar.time >= from && bar.time < to) {
146
+ bars = [
147
+ ...bars,
148
+ {
149
+ time: bar.time * 1000,
150
+ low: bar.low,
151
+ high: bar.high,
152
+ open: bar.open,
153
+ close: bar.close,
154
+ },
155
+ ];
156
+ }
157
+ });
158
+ if (firstDataRequest) {
159
+ lastBarsCache.set(symbolInfo.full_name, {
160
+ ...bars[bars.length - 1],
161
+ });
162
+ }
163
+ console.log(`[getBars]: returned ${bars.length} bar(s)`);
164
+ onHistoryCallback(bars, {
165
+ noData: false,
166
+ });
167
+ } catch (error) {
168
+ console.log("[getBars]: Get error", error);
169
+ onErrorCallback(error);
170
+ }
171
+ };
172
+
173
+ static subscribeBars = (
174
+ symbolInfo,
175
+ resolution,
176
+ onRealtimeCallback,
177
+ subscribeUID,
178
+ onResetCacheNeededCallback
179
+ ) => {
180
+ console.log(
181
+ "[subscribeBars]: Method call with subscribeUID:",
182
+ subscribeUID
183
+ );
184
+ subscribeOnStream(
185
+ symbolInfo,
186
+ resolution,
187
+ onRealtimeCallback,
188
+ subscribeUID,
189
+ onResetCacheNeededCallback,
190
+ lastBarsCache.get(symbolInfo.full_name)
191
+ );
192
+ };
193
+
194
+ static unsubscribeBars = (subscriberUID) => {
195
+ console.log(
196
+ "[unsubscribeBars]: Method call with subscriberUID:",
197
+ subscriberUID
198
+ );
199
+ unsubscribeFromStream(subscriberUID);
200
+ };
201
+ }
@@ -0,0 +1,32 @@
1
+ // Make requests to CryptoCompare API
2
+ export async function makeApiRequest(path) {
3
+ try {
4
+ const response = await fetch(`https://min-api.cryptocompare.com/${path}`);
5
+ return response.json();
6
+ } catch (error) {
7
+ throw new Error(`CryptoCompare request error: ${error.status}`);
8
+ }
9
+ }
10
+
11
+ // Generate a symbol ID from a pair of the coins
12
+ export function generateSymbol(exchange, fromSymbol, toSymbol) {
13
+ const short = `${fromSymbol}/${toSymbol}`;
14
+ return {
15
+ short,
16
+ full: `${exchange}:${short}`,
17
+ };
18
+ }
19
+
20
+ export function parseFullSymbol(fullSymbol) {
21
+ const match = fullSymbol.match(/^(\w+):(\w+)\/(\w+)$/);
22
+ if (!match) {
23
+ return null;
24
+ }
25
+
26
+ return {
27
+ exchange: match[1],
28
+ fromSymbol: match[2],
29
+ toSymbol: match[3],
30
+ };
31
+ }
32
+
@@ -0,0 +1,136 @@
1
+ import { parseFullSymbol } from "./helpers.js";
2
+ import io from 'socket.io-client';
3
+
4
+ const socket = io("wss://streamer.cryptocompare.com");
5
+ const channelToSubscription = new Map();
6
+
7
+ socket.on("connect", () => {
8
+ console.log("[socket] Connected");
9
+ });
10
+
11
+ socket.on("disconnect", (reason) => {
12
+ console.log("[socket] Disconnected:", reason);
13
+ });
14
+
15
+ socket.on("error", (error) => {
16
+ console.log("[socket] Error:", error);
17
+ });
18
+
19
+ socket.on("m", (data) => {
20
+ console.log("[socket] Message:", data);
21
+ const [
22
+ eventTypeStr,
23
+ exchange,
24
+ fromSymbol,
25
+ toSymbol,
26
+ ,
27
+ ,
28
+ tradeTimeStr,
29
+ ,
30
+ tradePriceStr,
31
+ ] = data.split("~");
32
+
33
+ if (parseInt(eventTypeStr) !== 0) {
34
+ // skip all non-TRADE events
35
+ return;
36
+ }
37
+ const tradePrice = parseFloat(tradePriceStr);
38
+ const tradeTime = parseInt(tradeTimeStr);
39
+ const channelString = `0~${exchange}~${fromSymbol}~${toSymbol}`;
40
+ const subscriptionItem = channelToSubscription.get(channelString);
41
+ if (subscriptionItem === undefined) {
42
+ return;
43
+ }
44
+ const lastDailyBar = subscriptionItem.lastDailyBar;
45
+ const nextDailyBarTime = getNextDailyBarTime(lastDailyBar.time);
46
+
47
+ let bar;
48
+ if (tradeTime >= nextDailyBarTime) {
49
+ bar = {
50
+ time: nextDailyBarTime,
51
+ open: tradePrice,
52
+ high: tradePrice,
53
+ low: tradePrice,
54
+ close: tradePrice,
55
+ };
56
+ console.log("[socket] Generate new bar", bar);
57
+ } else {
58
+ bar = {
59
+ ...lastDailyBar,
60
+ high: Math.max(lastDailyBar.high, tradePrice),
61
+ low: Math.min(lastDailyBar.low, tradePrice),
62
+ close: tradePrice,
63
+ };
64
+ console.log("[socket] Update the latest bar by price", tradePrice);
65
+ }
66
+ subscriptionItem.lastDailyBar = bar;
67
+
68
+ // send data to every subscriber of that symbol
69
+ subscriptionItem.handlers.forEach((handler) => handler.callback(bar));
70
+ });
71
+
72
+ function getNextDailyBarTime(barTime) {
73
+ const date = new Date(barTime * 1000);
74
+ date.setDate(date.getDate() + 1);
75
+ return date.getTime() / 1000;
76
+ }
77
+
78
+ export function subscribeOnStream(
79
+ symbolInfo,
80
+ resolution,
81
+ onRealtimeCallback,
82
+ subscribeUID,
83
+ onResetCacheNeededCallback,
84
+ lastDailyBar
85
+ ) {
86
+ const parsedSymbol = parseFullSymbol(symbolInfo.full_name);
87
+ const channelString = `0~${parsedSymbol.exchange}~${parsedSymbol.fromSymbol}~${parsedSymbol.toSymbol}`;
88
+ const handler = {
89
+ id: subscribeUID,
90
+ callback: onRealtimeCallback,
91
+ };
92
+ let subscriptionItem = channelToSubscription.get(channelString);
93
+ if (subscriptionItem) {
94
+ // already subscribed to the channel, use the existing subscription
95
+ subscriptionItem.handlers.push(handler);
96
+ return;
97
+ }
98
+ subscriptionItem = {
99
+ subscribeUID,
100
+ resolution,
101
+ lastDailyBar,
102
+ handlers: [handler],
103
+ };
104
+ channelToSubscription.set(channelString, subscriptionItem);
105
+ console.log(
106
+ "[subscribeBars]: Subscribe to streaming. Channel:",
107
+ channelString
108
+ );
109
+ socket.emit("SubAdd", { subs: [channelString] });
110
+ }
111
+
112
+ export function unsubscribeFromStream(subscriberUID) {
113
+ // find a subscription with id === subscriberUID
114
+ for (const channelString of channelToSubscription.keys()) {
115
+ const subscriptionItem = channelToSubscription.get(channelString);
116
+ const handlerIndex = subscriptionItem.handlers.findIndex(
117
+ (handler) => handler.id === subscriberUID
118
+ );
119
+
120
+ if (handlerIndex !== -1) {
121
+ // remove from handlers
122
+ subscriptionItem.handlers.splice(handlerIndex, 1);
123
+
124
+ if (subscriptionItem.handlers.length === 0) {
125
+ // unsubscribe from the channel, if it was the last handler
126
+ console.log(
127
+ "[unsubscribeBars]: Unsubscribe from streaming. Channel:",
128
+ channelString
129
+ );
130
+ socket.emit("SubRemove", { subs: [channelString] });
131
+ channelToSubscription.delete(channelString);
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
package/dist/yarn.lock DELETED
@@ -1,4 +0,0 @@
1
- # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
- # yarn lockfile v1
3
-
4
-
@@ -1,4 +0,0 @@
1
- # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
- # yarn lockfile v1
3
-
4
-
@@ -1,4 +0,0 @@
1
- # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
- # yarn lockfile v1
3
-
4
-