binbot-charts 0.7.3 → 0.7.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,13 +1,11 @@
1
1
  {
2
- "version": "0.7.3",
2
+ "version": "0.7.4",
3
3
  "name": "binbot-charts",
4
4
  "dependencies": {
5
+ "react": "^18.3.1",
6
+ "react-dom": "^18.3.1",
5
7
  "use-immer": "^0.10.0"
6
8
  },
7
- "peerDependencies": {
8
- "react": ">=18",
9
- "react-dom": ">=18"
10
- },
11
9
  "devDependencies": {
12
10
  "@babel/cli": "^7.18.10",
13
11
  "@babel/core": "^7.18.13",
@@ -24,7 +22,7 @@
24
22
  "scripts": {
25
23
  "start": "react-scripts start",
26
24
  "build": "rm -rf dist && tsup --config tsup.config.ts --env.NODE_ENV production",
27
- "release": "npm run build && npm run publish"
25
+ "release": "yarn build && yarn publish"
28
26
  },
29
27
  "description": "Binbot charts is the default candlestick bars chart used in terminal.binbot.in to render bots graphically.",
30
28
  "repository": {
@@ -34,20 +32,6 @@
34
32
  "main": "./dist/main.js",
35
33
  "module": "./dist/main.mjs",
36
34
  "types": "./dist/main.d.ts",
37
- "exports": {
38
- ".": {
39
- "types": "./dist/main.d.ts",
40
- "import": "./dist/main.mjs",
41
- "require": "./dist/main.js",
42
- "default": "./dist/main.mjs"
43
- }
44
- },
45
- "files": [
46
- "dist",
47
- "public",
48
- "README.md",
49
- "LICENSE"
50
- ],
51
35
  "keywords": [
52
36
  "binbot",
53
37
  "charts"
package/src/App.css ADDED
@@ -0,0 +1,26 @@
1
+ .App {
2
+ text-align: center;
3
+ }
4
+
5
+ .App-header {
6
+ display: flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ padding: 10px 0;
10
+ background-color: #222;
11
+ color: #fff;
12
+ }
13
+
14
+ .App-tv-logo {
15
+ height: 45px;
16
+ }
17
+
18
+ .App-react-logo {
19
+ display: block;
20
+ height: 62px;
21
+ }
22
+
23
+ .App-title {
24
+ display: block;
25
+ font-size: 1.5em;
26
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,89 @@
1
+ import { type FC, useEffect, useState } from "react";
2
+ import { useImmer } from "use-immer";
3
+ import { Immutable } from "immer";
4
+ import "./App.css";
5
+ import TVChartContainer, { OrderLine } from "./main";
6
+ import { ResolutionString } from "./charting_library/charting_library";
7
+ import { ITimescaleMarks } from "./charting-library-interfaces";
8
+ import { roundTime } from "./helpers";
9
+
10
+ type TimeMarks = Immutable<ITimescaleMarks>;
11
+
12
+ export const App: FC<{}> = (): JSX.Element => {
13
+ const [currentPrice, setCurrentPrice] = useState(null);
14
+ const [orderLines, setOrderLines] = useImmer<OrderLine[]>([]);
15
+ const [symbolState, setSymbolState] = useState("QTUMBTC");
16
+ const [testTimeMarks, setTestTimeMarks] = useState<Array<TimeMarks>>([]);
17
+
18
+ useEffect(() => {
19
+ if (currentPrice) {
20
+ updateOrderLines();
21
+ }
22
+ }, [currentPrice]);
23
+
24
+ const updateOrderLines = () => {
25
+ if (currentPrice) {
26
+ setOrderLines((draft) => {
27
+ draft.push({
28
+ id: "base_order",
29
+ text: "Base",
30
+ tooltip: ["Inactive"],
31
+ quantity: `XX USDT`,
32
+ price: currentPrice,
33
+ color: "#1f77d0",
34
+ });
35
+ return draft;
36
+ });
37
+ setOrderLines((draft) => {
38
+ draft.push({
39
+ id: "take_profit",
40
+ text: "Take profit",
41
+ tooltip: ["Inactive"],
42
+ quantity: `XX USDT`,
43
+ price: parseFloat((currentPrice * 1.03).toFixed(6)),
44
+ color: "#1f77d0",
45
+ });
46
+ return draft;
47
+ });
48
+ }
49
+ };
50
+
51
+ const handleTick = (ohlc) => {
52
+ setCurrentPrice(ohlc.close);
53
+ };
54
+ const getLatestBar = (bar) => {
55
+ const purchaseTs = new Date(2023, 0, 14, 13, 0).getTime();
56
+ setCurrentPrice(bar[3]);
57
+ setTestTimeMarks([
58
+ {
59
+ id: "tsm4",
60
+ time: roundTime(purchaseTs),
61
+ color: "blue",
62
+ label: "B",
63
+ tooltip: ["Safety Order 4"],
64
+ },
65
+ ]);
66
+ };
67
+ const handleChange = (e) => {
68
+ if (e.target.name === "symbol") {
69
+ setSymbolState(e.target.value);
70
+ }
71
+ };
72
+ return (
73
+ <>
74
+ <h1 style={{ textAlign: "center" }}>Test chart</h1>
75
+ <label htmlFor="symbol">Type symbol</label>
76
+ <input name="symbol" type="text" onChange={handleChange} />
77
+ <TVChartContainer
78
+ symbol={symbolState}
79
+ interval={"1h" as ResolutionString}
80
+ timescaleMarks={testTimeMarks}
81
+ orderLines={orderLines}
82
+ onTick={handleTick}
83
+ getLatestBar={getLatestBar}
84
+ />
85
+ </>
86
+ );
87
+ };
88
+
89
+ export default App;
@@ -0,0 +1,36 @@
1
+ import Datafeed from "./datafeed";
2
+
3
+ export interface IWidgetOptions {
4
+ symbol: string;
5
+ // BEWARE: no trailing slash is expected in feed URL
6
+ datafeed: Datafeed;
7
+ interval: string;
8
+ container: HTMLInputElement;
9
+ library_path: string; // node_modules path of charting_library
10
+ locale?: string;
11
+ disabled_features?: Array<string>;
12
+ enabled_features?: Array<string>;
13
+ charts_storage_url?: string;
14
+ charts_storage_api_version?: string;
15
+ client_id?: string;
16
+ user_id?: string;
17
+ fullscreen?: boolean;
18
+ autosize?: boolean;
19
+ studies_overrides?: object;
20
+ }
21
+
22
+ export interface IOrderLine {
23
+ text: string;
24
+ tooltip?: Array<string>;
25
+ quantity?: string;
26
+ price: Number;
27
+ color?: string;
28
+ }
29
+
30
+ export interface ITimescaleMarks {
31
+ id: string;
32
+ time: Number;
33
+ color?: string;
34
+ label: string;
35
+ tooltip?: Array<string>;
36
+ }
@@ -0,0 +1,295 @@
1
+ import { getAllSymbols, makeApiRequest } from "./helpers";
2
+ import { subscribeOnStream, unsubscribeFromStream } from "./streaming";
3
+
4
+ enum BinanceResolutions {
5
+ one_second = "1s",
6
+ one_minute = "1m",
7
+ three_minute = "3m",
8
+ five_minute = "5m",
9
+ fifteen_minute = "15m",
10
+ thirty_minute = "30m",
11
+ one_hour = "1h",
12
+ two_hour = "2h",
13
+ four_hour = "4h",
14
+ six_hour = "6h",
15
+ eight_hour = "8h",
16
+ twelve_hour = "12h",
17
+ one_day = "1d",
18
+ three_day = "3d",
19
+ one_week = "1w",
20
+ one_month = "1M",
21
+ }
22
+
23
+ interface ConfigurationData {
24
+ supports_marks: boolean;
25
+ supports_timescale_marks: boolean;
26
+ supports_time: boolean;
27
+ supported_resolutions: string[];
28
+ exchanges: { value: string; name: string; desc: string }[];
29
+ symbols_types: { name: string; value: string }[];
30
+ }
31
+
32
+ const getConfigurationData = async (): Promise<ConfigurationData> => {
33
+ return {
34
+ supports_marks: true,
35
+ supports_timescale_marks: true,
36
+ supports_time: true,
37
+ supported_resolutions: [
38
+ "1S",
39
+ "1",
40
+ "3",
41
+ "5",
42
+ "15",
43
+ "30",
44
+ "60",
45
+ "120",
46
+ "240",
47
+ "360",
48
+ "480",
49
+ "720",
50
+ "1D",
51
+ "3D",
52
+ "1W",
53
+ "12M",
54
+ ],
55
+ exchanges: [
56
+ {
57
+ value: "Binance",
58
+ name: "Binance",
59
+ desc: "Binance",
60
+ },
61
+ ],
62
+ symbols_types: [
63
+ {
64
+ name: "crypto",
65
+ value: "crypto",
66
+ },
67
+ ],
68
+ };
69
+ };
70
+
71
+ interface TimescaleMark {
72
+ id: string;
73
+ time: number;
74
+ color: string;
75
+ label: string;
76
+ tooltip: string;
77
+ }
78
+
79
+ interface SymbolInfo {
80
+ name: string;
81
+ ticker: string;
82
+ description: string;
83
+ type: string;
84
+ session: string;
85
+ timezone: string;
86
+ exchange: string;
87
+ minmov: number;
88
+ pricescale: number;
89
+ has_daily: boolean;
90
+ has_intraday: boolean;
91
+ has_no_volume: boolean;
92
+ has_seconds: boolean;
93
+ seconds_multipliers: number[];
94
+ volume: string;
95
+ volume_precision: number;
96
+ data_status: string;
97
+ resolution: string;
98
+ }
99
+
100
+ interface PeriodParams {
101
+ from: number;
102
+ to: number;
103
+ firstDataRequest: boolean;
104
+ }
105
+
106
+ interface Bar {
107
+ time: number;
108
+ low: number;
109
+ high: number;
110
+ open: number;
111
+ close: number;
112
+ volume: number;
113
+ }
114
+
115
+ export default class Datafeed {
116
+ private streaming: any;
117
+ private timescaleMarks: TimescaleMark[];
118
+ private interval: string;
119
+ private configurationData: ConfigurationData | null = null;
120
+
121
+ constructor(timescaleMarks: TimescaleMark[] = [], interval: string = "1h") {
122
+ this.streaming = null;
123
+ this.timescaleMarks = timescaleMarks;
124
+ this.interval = interval;
125
+ }
126
+
127
+ onReady = async (callback: (data: ConfigurationData) => void): Promise<void> => {
128
+ this.configurationData = await getConfigurationData();
129
+ callback(this.configurationData);
130
+ };
131
+
132
+ searchSymbols = async (
133
+ userInput: string,
134
+ exchange: string,
135
+ symbolType: string,
136
+ onResultReadyCallback: (symbols: any[]) => void
137
+ ): Promise<void> => {
138
+ const symbols = await getAllSymbols(userInput);
139
+ onResultReadyCallback(symbols);
140
+ };
141
+
142
+ resolveSymbol = async (
143
+ symbolName: string,
144
+ onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void,
145
+ onResolveErrorCallback: (error: string) => void
146
+ ): Promise<void> => {
147
+ if (!symbolName) {
148
+ await onResolveErrorCallback("cannot resolve symbol");
149
+ return;
150
+ }
151
+
152
+ const symbolInfo = async (): Promise<SymbolInfo> => {
153
+
154
+ const symbolData = await makeApiRequest(`api/v3/exchangeInfo?symbol=${symbolName}`);
155
+ const priceScale = symbolData.symbols[0].baseAssetPrecision;
156
+
157
+ return {
158
+ name: symbolName,
159
+ ticker: symbolName,
160
+ description: symbolName,
161
+ type: "crypto",
162
+ session: "24x7",
163
+ timezone: "Etc/UTC",
164
+ exchange: "Binance",
165
+ minmov: 1,
166
+ pricescale: 10 ** parseFloat(priceScale),
167
+ has_daily: true,
168
+ has_intraday: true,
169
+ has_no_volume: false,
170
+ has_seconds: true,
171
+ seconds_multipliers: [1],
172
+ volume: "hundreds",
173
+ volume_precision: 9,
174
+ data_status: "streaming",
175
+ resolution: "1h",
176
+ };
177
+ };
178
+ const symbol = await symbolInfo();
179
+ console.log("Resolve requested for:", symbol);
180
+
181
+ onSymbolResolvedCallback(symbol);
182
+ };
183
+
184
+ getBars = async (
185
+ symbolInfo: SymbolInfo,
186
+ resolution: string,
187
+ periodParams: PeriodParams,
188
+ onHistoryCallback: (bars: Bar[], meta: { noData: boolean }) => void,
189
+ onErrorCallback: (error: any) => void
190
+ ): Promise<void> => {
191
+ const { from, to, firstDataRequest } = periodParams;
192
+ let interval = "60"; // 1 hour
193
+ // Calculate interval using resolution data
194
+ if (!/[a-zA-Z]$/.test(resolution)) {
195
+ if (parseInt(resolution) >= 60) {
196
+ interval = parseInt(resolution) / 60 + "h";
197
+ } else {
198
+ interval = resolution + "m";
199
+ }
200
+ } else {
201
+ interval = resolution.toLowerCase().replace(/[a-z]\b/g, (c) => c.toLowerCase());
202
+ }
203
+
204
+ let urlParameters = {
205
+ symbol: symbolInfo.name,
206
+ interval: interval,
207
+ startTime: Math.abs(from * 1000),
208
+ endTime: Math.abs(to * 1000),
209
+ limit: 600,
210
+ };
211
+
212
+ const query = Object.keys(urlParameters)
213
+ .map((name) => `${name}=${encodeURIComponent(urlParameters[name])}`)
214
+ .join("&");
215
+
216
+ try {
217
+ const data = await makeApiRequest(`api/v3/uiKlines?${query}`);
218
+ if ((data.Response && data.Response === "Error") || data.length === 0) {
219
+ // "noData" should be set if there is no data in the requested period.
220
+ onHistoryCallback([], {
221
+ noData: true,
222
+ });
223
+ return;
224
+ }
225
+ let bars: Bar[] = [];
226
+ data.forEach((bar: any) => {
227
+ if (bar[0] >= from * 1000 && bar[0] < to * 1000) {
228
+ // Binance returns string values; convert to numbers so the library can compute volume
229
+ const open = parseFloat(bar[1]);
230
+ const high = parseFloat(bar[2]);
231
+ const low = parseFloat(bar[3]);
232
+ const close = parseFloat(bar[4]);
233
+ const volume = parseFloat(bar[5]);
234
+ bars = [
235
+ ...bars,
236
+ {
237
+ time: bar[0],
238
+ low,
239
+ high,
240
+ open,
241
+ close,
242
+ volume,
243
+ },
244
+ ];
245
+ }
246
+ });
247
+ onHistoryCallback(bars, {
248
+ noData: false,
249
+ });
250
+ } catch (error) {
251
+ console.log("[getBars]: Get error", error);
252
+ onErrorCallback(error);
253
+ }
254
+ };
255
+
256
+ getTimescaleMarks(
257
+ symbolInfo: SymbolInfo,
258
+ from: number,
259
+ to: number,
260
+ onDataCallback: (marks: TimescaleMark[]) => void,
261
+ resolution: string
262
+ ): void {
263
+ if (this.timescaleMarks.length > 0) {
264
+ let timescaleMarks = Object.assign([], this.timescaleMarks);
265
+ onDataCallback(timescaleMarks);
266
+ }
267
+ }
268
+
269
+ async getServerTime(onServertimeCallback: (time: number) => void): Promise<void> {
270
+ const data = await makeApiRequest(`api/v3/time`);
271
+ const serverTime = data.serverTime / 1000;
272
+ onServertimeCallback(serverTime);
273
+ }
274
+
275
+ subscribeBars = (
276
+ symbolInfo: SymbolInfo,
277
+ resolution: string,
278
+ onRealtimeCallback: (bar: Bar) => void,
279
+ subscribeUID: string,
280
+ onResetCacheNeededCallback: () => void
281
+ ): void => {
282
+ subscribeOnStream(
283
+ symbolInfo,
284
+ resolution,
285
+ onRealtimeCallback,
286
+ subscribeUID,
287
+ onResetCacheNeededCallback,
288
+ this.interval
289
+ );
290
+ };
291
+
292
+ unsubscribeBars = (subscriberUID: string): void => {
293
+ unsubscribeFromStream(subscriberUID);
294
+ };
295
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+
4
+ export async function makeApiRequest(path) {
5
+ try {
6
+ const response = await fetch(`https://api.binance.com/${path}`);
7
+ return response.json();
8
+ } catch (error) {
9
+ throw new Error(`Binance request error: ${error.status}`);
10
+ }
11
+ }
12
+
13
+ export async function getAllSymbols(symbol) {
14
+ let newSymbols = [];
15
+ try {
16
+ const data = await makeApiRequest(`api/v3/exchangeInfo?symbol=${symbol.toUpperCase()}`);
17
+ data.symbols.forEach(item => {
18
+ if (item.status === "TRADING") {
19
+ newSymbols.push({
20
+ symbol: item.symbol,
21
+ full_name: `${item.baseAsset}/${item.quoteAsset}`,
22
+ description: `Precision: ${item.quoteAssetPrecision}`,
23
+ exchange: "Binance",
24
+ ticker: item.symbol,
25
+ type: "crypto",
26
+ });
27
+ }
28
+ });
29
+ } catch (e) {
30
+ return newSymbols
31
+ }
32
+ return newSymbols;
33
+ }
34
+
35
+ export function usePrevious(value) {
36
+ const ref = useRef();
37
+ useEffect(() => {
38
+ ref.current = value;
39
+ });
40
+ return ref.current;
41
+ }
42
+
43
+ export function roundTime(ts: number): number {
44
+ /**
45
+ * @param ts a JavaScript new Date().getTime() timestamp
46
+ */
47
+ let time = new Date(ts);
48
+ time.setMinutes(0);
49
+ time.setSeconds(0)
50
+ time.setMilliseconds(0);
51
+ const roundFloor = time.getTime();
52
+ return roundFloor / 1000
53
+ }
package/src/index.css ADDED
@@ -0,0 +1,5 @@
1
+ body {
2
+ margin: 0;
3
+ padding: 0;
4
+ font-family: sans-serif;
5
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import App from "./App"; // Ensure App is a default export from "./App"
3
+ import "./index.css";
4
+
5
+ const rootElement = document.getElementById("root");
6
+
7
+ if (rootElement) {
8
+ const root = createRoot(rootElement);
9
+ root.render(<App />);
10
+ }