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/babel.config.json +13 -0
- package/browserslist +31 -0
- package/dist/main.js +17 -11
- package/dist/main.js.map +1 -1
- package/dist/main.mjs +17 -11
- package/dist/main.mjs.map +1 -1
- package/package-lock.json +18327 -0
- package/package.json +4 -20
- package/src/App.css +26 -0
- package/src/App.tsx +89 -0
- package/src/charting-library-interfaces.d.ts +36 -0
- package/src/datafeed.ts +295 -0
- package/src/helpers.ts +53 -0
- package/src/index.css +5 -0
- package/src/index.tsx +10 -0
- package/src/main.tsx +151 -0
- package/src/streaming.ts +125 -0
- package/tsconfig.json +97 -0
- package/tsup.config.ts +10 -0
- package/public/charting_library/yarn.lock +0 -4
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
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": "
|
|
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
|
+
}
|
package/src/datafeed.ts
ADDED
|
@@ -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
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
|
+
}
|