market-data-tradingview-ws 0.0.1
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/.prettierignore +0 -0
- package/.prettierrc +12 -0
- package/index.js +6 -0
- package/lib/client.js +156 -0
- package/lib/tradingView.js +217 -0
- package/lib/utils.js +39 -0
- package/old.js +141 -0
- package/package.json +15 -0
- package/server.js +91 -0
package/.prettierignore
ADDED
|
File without changes
|
package/.prettierrc
ADDED
package/index.js
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { tradingView } from './tradingView.js';
|
|
3
|
+
|
|
4
|
+
const tv = new tradingView();
|
|
5
|
+
|
|
6
|
+
const sessions = {
|
|
7
|
+
values: new Map(),
|
|
8
|
+
|
|
9
|
+
set({ key, data }) {
|
|
10
|
+
if (data) {
|
|
11
|
+
this.values.set(key, data);
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
get({ key }) {
|
|
15
|
+
return this.values.get(key);
|
|
16
|
+
},
|
|
17
|
+
getKeys() {
|
|
18
|
+
return this.values.keys();
|
|
19
|
+
},
|
|
20
|
+
getKeyByType({ type }) {
|
|
21
|
+
let session = '';
|
|
22
|
+
this.values.forEach((value, key) => {
|
|
23
|
+
if (type === value.type) session = key;
|
|
24
|
+
});
|
|
25
|
+
return session;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class Client {
|
|
30
|
+
#ws;
|
|
31
|
+
#sessions; // = new Map();
|
|
32
|
+
#token = null;
|
|
33
|
+
constructor() {
|
|
34
|
+
this.#sessions = sessions;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
close() {
|
|
38
|
+
this.#ws.close();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getSession({ key }) {
|
|
42
|
+
return this.#sessions.get({ key });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getSessionKeys() {
|
|
46
|
+
return this.#sessions.keys();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getToken({ login, pass }) {
|
|
50
|
+
return tv.getToken({ login, pass });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
auth(token = ['unauthorized_user_token']) {
|
|
54
|
+
return tv.formatWSPacket({
|
|
55
|
+
packet: {
|
|
56
|
+
m: 'set_auth_token',
|
|
57
|
+
p: [token],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async connect({ url, options, login, pass, result }) {
|
|
63
|
+
this.#token = await this.getToken({ login, pass });
|
|
64
|
+
if (this.#token.length > 10) {
|
|
65
|
+
return new Promise((resolv, reject) => {
|
|
66
|
+
this.#ws = new WebSocket(url, options);
|
|
67
|
+
|
|
68
|
+
this.#ws.onclose = () => console.log('disconnected');
|
|
69
|
+
|
|
70
|
+
this.#ws.onerror = (error) => reject(console.error('error: ', error));
|
|
71
|
+
this.#ws.onopen = () => {
|
|
72
|
+
this.#ws.send(this.auth(this.#token));
|
|
73
|
+
this.messageListner({ result });
|
|
74
|
+
resolv('open');
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
createSession({ type }) {
|
|
81
|
+
const startsWith = type === 'chart' ? 'cs_' : 'qs_';
|
|
82
|
+
const key = tv.createSession({ startsWith });
|
|
83
|
+
const session = sessions.set({ key, data: { type, symbols: [] } });
|
|
84
|
+
// console.log(key, session);
|
|
85
|
+
if (type === 'chart') {
|
|
86
|
+
this.#ws.send(tv.wsCreateChartSession({ chartSession: key }));
|
|
87
|
+
this.#ws.send(tv.switchTimezone({ chartSession: key, tz: 'Etc/UTC' }));
|
|
88
|
+
} else {
|
|
89
|
+
this.#ws.send(tv.wsQuoteCreateSession({ session: key }));
|
|
90
|
+
this.#ws.send(tv.setFieldsChart({ session: key }));
|
|
91
|
+
}
|
|
92
|
+
return session;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
addQuoteSymbols({ symbolIds }) {
|
|
96
|
+
const key = this.#sessions.getKeyByType({ type: 'quote' });
|
|
97
|
+
this.#ws.send(tv.quoteAddSymbols({ session: key, symbolIds }));
|
|
98
|
+
|
|
99
|
+
const data = this.#sessions.get({ key });
|
|
100
|
+
// console.log(key, data);
|
|
101
|
+
symbolIds.forEach((each) => data.symbols.push(each));
|
|
102
|
+
this.#sessions.set({ key, data });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
messageListner({ result }) {
|
|
106
|
+
this.#ws.on('message', (raw) => {
|
|
107
|
+
if (this.#ws.readyState !== this.#ws.OPEN) return;
|
|
108
|
+
|
|
109
|
+
let session = '';
|
|
110
|
+
tv.parsePacket({ str: raw }).forEach((packet) => {
|
|
111
|
+
if (packet === undefined) {
|
|
112
|
+
console.log('undefined: ', raw.toString());
|
|
113
|
+
} else {
|
|
114
|
+
if (packet.type === 'ping') {
|
|
115
|
+
// console.info('pong', packet);
|
|
116
|
+
this.#ws.send(tv.createPong({ value: packet.data }));
|
|
117
|
+
} else if (packet.type === 'protocol_error') {
|
|
118
|
+
console.warn('protocol_error', packet);
|
|
119
|
+
this.close();
|
|
120
|
+
} else if (packet.type === 'qsd') {
|
|
121
|
+
session = sessions.get({ key: packet.data[0] });
|
|
122
|
+
// console.log('qsd', packet.data, session);
|
|
123
|
+
const send = packet.data[1].v;
|
|
124
|
+
send.symbol = packet.data[1].n;
|
|
125
|
+
// console.log(JSON.stringify([packet.type, send]));
|
|
126
|
+
result({ packet: send });
|
|
127
|
+
} else if (['du', 'timescale_update'].includes(packet.type)) {
|
|
128
|
+
session = sessions.get({ key: packet.data[0] });
|
|
129
|
+
// console.log('du', packet.data, session);
|
|
130
|
+
result({ packet });
|
|
131
|
+
// const arr = [];
|
|
132
|
+
// for (var key in packet.data[1].s1.s) {
|
|
133
|
+
// const send = {
|
|
134
|
+
// symbol: session.symbolId,
|
|
135
|
+
// end: packet.data[1].s1.s[key + 1] !== undefined ? packet.data[1].s1.s[key + 1].v[0] : packet.data[1].s1.lbs.bar_close_time,
|
|
136
|
+
// };
|
|
137
|
+
// console.log(key, packet.data[1].s1.s);
|
|
138
|
+
// [send.start, send.open, send.low, send.high, send.close, send.passed] = packet.data[1].s1.s[key].v;
|
|
139
|
+
// console.log(send);
|
|
140
|
+
// arr.push(send);
|
|
141
|
+
// }
|
|
142
|
+
// [send.start, send.open, send.low, send.high, send.close, send.passed] = packet.data[1].s1.s.v;
|
|
143
|
+
// console.log(JSON.stringify([packet.type, arr]));
|
|
144
|
+
} else if (['series', 'symbol', 'quote'].includes(packet.type)) {
|
|
145
|
+
console.info(packet);
|
|
146
|
+
} else {
|
|
147
|
+
console.warn('error', raw.toString());
|
|
148
|
+
}
|
|
149
|
+
// else {
|
|
150
|
+
// console.log('received: %s', raw);
|
|
151
|
+
// }
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Utils } from './utils.js';
|
|
2
|
+
const u = new Utils();
|
|
3
|
+
|
|
4
|
+
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
5
|
+
const cleanerRgx = /~h~/g;
|
|
6
|
+
const splitterRgx = /~m~[0-9]{1,}~m~/g;
|
|
7
|
+
const urlSymbolSearch = 'https://symbol-search.tradingview.com/symbol_search';
|
|
8
|
+
|
|
9
|
+
export class tradingView {
|
|
10
|
+
async getToken({ login, pass }) {
|
|
11
|
+
const url = 'https://www.tradingview.com/accounts/signin/';
|
|
12
|
+
const headers = {
|
|
13
|
+
Referer: 'https://www.tradingview.com',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const form = new FormData();
|
|
17
|
+
form.append('username', login);
|
|
18
|
+
form.append('password', pass);
|
|
19
|
+
form.append('remember', 'on');
|
|
20
|
+
|
|
21
|
+
// const res = await fetch(url, {
|
|
22
|
+
// method: 'POST',
|
|
23
|
+
// headers,
|
|
24
|
+
// body: form,
|
|
25
|
+
// });
|
|
26
|
+
|
|
27
|
+
// if (res.status === 200) {
|
|
28
|
+
// const data = await res.json();
|
|
29
|
+
// if (data.user.auth_token === undefined) throw new Error('Error get auth_token', data);
|
|
30
|
+
// console.log(data.user.auth_token);
|
|
31
|
+
// return data.user.auth_token;
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// await u.pause();
|
|
35
|
+
return 'eyJhbGciOiJSUzUxMiIsImtpZCI6IkdaeFUiLCJ0eXAiOiJKV1QifQ.eyJ1c2VyX2lkIjoxMjI1ODcxMiwiZXhwIjoxNjkwNzkzMjIwLCJpYXQiOjE2OTA3Nzg4MjAsInBsYW4iOiJwcm9fcHJlbWl1bSIsImV4dF9ob3VycyI6MSwicGVybSI6IiIsInN0dWR5X3Blcm0iOiJ0di1jaGFydF9wYXR0ZXJucyx0di1jaGFydHBhdHRlcm5zLHR2LXByb3N0dWRpZXMsdHYtdm9sdW1lYnlwcmljZSIsIm1heF9zdHVkaWVzIjoyNSwibWF4X2Z1bmRhbWVudGFscyI6MCwibWF4X2NoYXJ0cyI6OCwibWF4X2FjdGl2ZV9hbGVydHMiOjQwMCwibWF4X3N0dWR5X29uX3N0dWR5IjoyNCwibWF4X2FjdGl2ZV9wcmltaXRpdmVfYWxlcnRzIjo0MDAsIm1heF9hY3RpdmVfY29tcGxleF9hbGVydHMiOjQwMCwibWF4X2Nvbm5lY3Rpb25zIjo1MH0.pZQgM_oTZMnm1Qb4qBf6CfM9dHKltp2T3O2g_VVoK9l66UngEL8mLdeH7-ZpUZ8Y50rV5yr0ND8WRPsRH2w2izk_SNMD63p6GJzXXGPvILgirXW3SFa7HmaHv8Xp6SPhQTWb-ahyv_2GUIueKTvHWBNF5-eBnqYeoXQeWkRFsLA';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getSymbol({ symbol, type }) {
|
|
39
|
+
// type = 'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'
|
|
40
|
+
// query = what you want to search!
|
|
41
|
+
// it returns first matching item
|
|
42
|
+
const res = await fetch(urlSymbolSearch + '?text=' + symbol + '&type=' + type);
|
|
43
|
+
if (res.status === 200) {
|
|
44
|
+
const data = await res.json();
|
|
45
|
+
// console.log(data);
|
|
46
|
+
return data[0];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return 'Network Error!';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getSymbolId({ data }) {
|
|
53
|
+
const name = data.type === 'futures' ? data.contracts[0].symbol : data.symbol;
|
|
54
|
+
return data.exchange.toUpperCase() + ':' + name.toUpperCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
createSession({ startsWith }) {
|
|
58
|
+
let session = startsWith;
|
|
59
|
+
while (session.length < 12) {
|
|
60
|
+
session += letters[Math.floor(Math.random() * letters.length)];
|
|
61
|
+
}
|
|
62
|
+
return session;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
formatWSPacket({ packet }) {
|
|
66
|
+
const msg = typeof packet === 'object' ? JSON.stringify(packet) : packet;
|
|
67
|
+
return '~m~' + msg.length + '~m~' + msg;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
parsePacket({ str }) {
|
|
71
|
+
const messages = [];
|
|
72
|
+
this.parseWSPacket({ str }).forEach((packet) => {
|
|
73
|
+
messages.push(this.eachPacket(packet));
|
|
74
|
+
});
|
|
75
|
+
return messages;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
parseWSPacket({ str }) {
|
|
79
|
+
return str
|
|
80
|
+
.toString()
|
|
81
|
+
.replace(cleanerRgx, '')
|
|
82
|
+
.split(splitterRgx)
|
|
83
|
+
.map((p) => {
|
|
84
|
+
if (!p) return false;
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(p);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn('Cant parse', p);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.filter((p) => p);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
eachPacket(packet) {
|
|
96
|
+
if (typeof packet === 'number') {
|
|
97
|
+
return { type: 'ping', data: packet }; // Ping
|
|
98
|
+
} else if (packet.m && packet.p) {
|
|
99
|
+
// Normal packet
|
|
100
|
+
if (packet.m.startsWith('du')) return { type: packet.m, data: packet.p };
|
|
101
|
+
if (packet.m.startsWith('timescale') || packet.m.startsWith('qsd')) return { type: packet.m, data: packet.p };
|
|
102
|
+
if (packet.m.startsWith('series') || packet.m.startsWith('symbol') || packet.m.startsWith('quote')) {
|
|
103
|
+
const type = packet.m.split('_');
|
|
104
|
+
return { type: type[0], data: { sub: type[1], detail: packet.p, time: packet.t_ms ? packet.t_ms : Date.now() } };
|
|
105
|
+
} else {
|
|
106
|
+
return { type: 'msg', data: parsed.data };
|
|
107
|
+
}
|
|
108
|
+
// const session = packet.p[0];
|
|
109
|
+
|
|
110
|
+
// if (session && this.#sessions[session]) {
|
|
111
|
+
// this.#sessions[session].onData(parsed);
|
|
112
|
+
//
|
|
113
|
+
// }
|
|
114
|
+
} else if (packet.session_id) {
|
|
115
|
+
return { type: 'session', data: packet };
|
|
116
|
+
} else if (packet.m === 'protocol_error') {
|
|
117
|
+
return { type: packet.m, data: packet.p }; // Error
|
|
118
|
+
} else {
|
|
119
|
+
return { type: 'msgError', data: packet }; // Error
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
wsQuoteCreateSession({ session }) {
|
|
124
|
+
return this.formatWSPacket({ packet: { m: 'quote_create_session', p: [session] } });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
wsCreateChartSession({ chartSession }) {
|
|
128
|
+
return this.formatWSPacket({ packet: { m: 'chart_create_session', p: [chartSession, ''] } });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setFieldsQuote({ session }) {
|
|
132
|
+
return this.formatWSPacket({ packet: { m: 'quote_set_fields', p: [session, 'lp'] } });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setFieldsChart({ session }) {
|
|
136
|
+
return this.formatWSPacket({
|
|
137
|
+
packet: {
|
|
138
|
+
m: 'quote_set_fields',
|
|
139
|
+
p: [
|
|
140
|
+
session,
|
|
141
|
+
'ch',
|
|
142
|
+
'chp',
|
|
143
|
+
'current_session',
|
|
144
|
+
'description',
|
|
145
|
+
// 'local_description',
|
|
146
|
+
// 'language',
|
|
147
|
+
'exchange',
|
|
148
|
+
// 'format',
|
|
149
|
+
// 'fractional',
|
|
150
|
+
'is_tradable',
|
|
151
|
+
'lp',
|
|
152
|
+
'lp_time',
|
|
153
|
+
// 'minmov',
|
|
154
|
+
// 'minmove2',
|
|
155
|
+
'original_name',
|
|
156
|
+
// 'pricescale',
|
|
157
|
+
'pro_name',
|
|
158
|
+
'short_name',
|
|
159
|
+
'type',
|
|
160
|
+
// 'update_mode',
|
|
161
|
+
'volume',
|
|
162
|
+
'ask',
|
|
163
|
+
'bid',
|
|
164
|
+
'high_price',
|
|
165
|
+
'low_price',
|
|
166
|
+
'open_price',
|
|
167
|
+
'prev_close_price',
|
|
168
|
+
'currency_code',
|
|
169
|
+
// 'rch',
|
|
170
|
+
// 'rchp',
|
|
171
|
+
// 'rtc',
|
|
172
|
+
'status',
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
quoteAddSymbols({ session, symbolIds }) {
|
|
179
|
+
return this.formatWSPacket({ packet: { m: 'quote_add_symbols', p: [session, ...symbolIds] } });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
quoteRemoveSymbols({ session, symbolId }) {
|
|
183
|
+
return this.formatWSPacket({ packet: { m: 'quote_remove_symbols', p: [session, symbolId] } });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
switchTimezone({ chartSession, tz = 'Etc/UTC' }) {
|
|
187
|
+
return this.formatWSPacket({ packet: { m: 'switch_timezone', p: [chartSession, tz] } });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
resolveSymbol({ chartSession, rSymbolId, symbolId }) {
|
|
191
|
+
return this.formatWSPacket({
|
|
192
|
+
packet: {
|
|
193
|
+
m: 'resolve_symbol',
|
|
194
|
+
p: [chartSession, rSymbolId, '={"symbol":"' + symbolId + '","adjustment":"splits","session":"extended"}'],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
createSeries({ chartSession, id = 1, interval = '60', total_candle = 1000 }) {
|
|
200
|
+
return this.formatWSPacket({
|
|
201
|
+
packet: { m: 'create_series', p: [chartSession, 's' + id, 's' + id, 'symbol_1', interval, total_candle] },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
modifySeries({ chartSession, rSymbolId, symbolId }) {
|
|
206
|
+
return this.formatWSPacket({
|
|
207
|
+
packet: {
|
|
208
|
+
m: 'resolve_symbol',
|
|
209
|
+
p: [chartSession, 'symbol_' + id, '={"symbol":"' + symbolId + '","adjustment":"splits","session":"extended"}'],
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
createPong({ value }) {
|
|
215
|
+
return this.formatWSPacket({ packet: '~h~' + value });
|
|
216
|
+
}
|
|
217
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import https from 'https';
|
|
2
|
+
|
|
3
|
+
export class Utils {
|
|
4
|
+
pause(time = 1000) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
setTimeout(resolve, time);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
request(options = {}, raw = false, content = '') {
|
|
11
|
+
return new Promise((cb, err) => {
|
|
12
|
+
const req = https.request(options, (res) => {
|
|
13
|
+
let data = '';
|
|
14
|
+
res.on('data', (c) => {
|
|
15
|
+
data += c;
|
|
16
|
+
});
|
|
17
|
+
res.on('end', () => {
|
|
18
|
+
if (raw) {
|
|
19
|
+
cb({ data, cookies: res.headers['set-cookie'] });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
data = JSON.parse(data);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.log(data);
|
|
27
|
+
err(new Error("Can't parse server response"));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
cb({ data, cookies: res.headers['set-cookie'] });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
req.on('error', err);
|
|
36
|
+
req.end(content);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
package/old.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
process.title = 'socket_client';
|
|
4
|
+
|
|
5
|
+
const urlWS = 'wss://data.tradingview.com/socket.io/websocket';
|
|
6
|
+
|
|
7
|
+
const login = 'sulimenko@ptfin.kz';
|
|
8
|
+
const pass = '7n8zGZ8v27pH';
|
|
9
|
+
const token = '';
|
|
10
|
+
const interval = '5';
|
|
11
|
+
const total_candle = 100;
|
|
12
|
+
|
|
13
|
+
const stop = async () => {
|
|
14
|
+
const closed = new Promise((resolve) => {
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
resolve(console.log('exit'));
|
|
17
|
+
}, 1000);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
for (const sessionKey of sessions.values.keys()) {
|
|
21
|
+
sessions.values.delete(sessionKey);
|
|
22
|
+
console.log('session clear: ', sessionKey);
|
|
23
|
+
}
|
|
24
|
+
socket.close();
|
|
25
|
+
await closed;
|
|
26
|
+
process.exit(1);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function changeWSSymbol({ key, chartkey, symbolId }) {
|
|
30
|
+
const s = sessions.get({ key });
|
|
31
|
+
const c = sessions.get({ key: chartkey });
|
|
32
|
+
const queue = [];
|
|
33
|
+
|
|
34
|
+
// console.log(s, symbolId, id, c, chartkey, sessions);
|
|
35
|
+
if (s.symbolId) queue.push(tv.removeSymbols({ session: s.main, symbolId: s.symbolId }));
|
|
36
|
+
queue.push(tv.addSymbols({ session: s.main, symbolId }));
|
|
37
|
+
queue.push(tv.resolveSymbol({ chartSession: c.main, id, symbolId }));
|
|
38
|
+
|
|
39
|
+
queue.forEach((each) => {
|
|
40
|
+
console.log(each);
|
|
41
|
+
socket.send(each);
|
|
42
|
+
});
|
|
43
|
+
id++;
|
|
44
|
+
sessions.set({ key, val: { main: key, symbolId } });
|
|
45
|
+
sessions.set({ key: chartkey, val: { main: chartkey, symbolId } });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
(async () => {
|
|
49
|
+
|
|
50
|
+
// const data = await tv.getSymbol({ symbol: 'TSLA', type: 'stock' }); // NASDAQ:TSLA
|
|
51
|
+
// const data = await tv.getSymbol({ symbol: 'MRNA', type: 'stock' }); // NASDAQ:MRNA
|
|
52
|
+
const data = await tv.getSymbol({ symbol: 'BTCUSDT', type: 'crypto' });
|
|
53
|
+
// console.log('symbol: ', data);
|
|
54
|
+
|
|
55
|
+
const symbolId = tv.getSymbolId({ data });
|
|
56
|
+
console.log(symbolId);
|
|
57
|
+
|
|
58
|
+
socket = new WebSocket(urlWS, { origin: 'https://data.tradingview.com' });
|
|
59
|
+
// // const socket = new WebSocket('wss://data.tradingview.com/socket.io/websocket?&type=chart', { origin: 'https://s.tradingview.com' });
|
|
60
|
+
|
|
61
|
+
const sessionKey = tv.createSession({ startsWith: 'qs_' });
|
|
62
|
+
const chartSessionKey = tv.createSession({ startsWith: 'cs_' });
|
|
63
|
+
sessions.set({ key: sessionKey, val: { main: sessionKey } });
|
|
64
|
+
sessions.set({ key: chartSessionKey, val: { main: chartSessionKey } });
|
|
65
|
+
let session = sessions.get({ key: sessionKey });
|
|
66
|
+
let chartSession = sessions.get({ key: chartSessionKey });
|
|
67
|
+
// console.log(session);
|
|
68
|
+
|
|
69
|
+
socket.on('open', (e) => {
|
|
70
|
+
console.log('open');
|
|
71
|
+
socket.send(auth());
|
|
72
|
+
|
|
73
|
+
socket.send(tv.wsCreateChartSession({ chartSession: chartSession.main }));
|
|
74
|
+
socket.send(tv.switchTimezone({ chartSession: chartSession.main, tz: 'Etc/UTC' }));
|
|
75
|
+
socket.send(tv.wsCreateSession({ session: session.main }));
|
|
76
|
+
// socket.send(tv.resolveSymbol({ chartSession, id: 1, symbolId }));
|
|
77
|
+
changeWSSymbol({ key: session.main, chartkey: chartSession.main, symbolId });
|
|
78
|
+
|
|
79
|
+
socket.send(tv.setFieldsChart({ session: session.main }));
|
|
80
|
+
socket.send(tv.createSeries({ chartSession: chartSession.main, id: 1, interval, total_candle }));
|
|
81
|
+
|
|
82
|
+
// setTimeout(() => {
|
|
83
|
+
// addWSSymbol({ key: session.main, symbolId: 'NASDAQ:TSLA' });
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
changeWSSymbol({ key: session.main, chartkey: chartSession.main, symbolId: 'NASDAQ:TSLA' });
|
|
86
|
+
}, 10000);
|
|
87
|
+
// }, 10000);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
socket.on('message', (raw) => {
|
|
91
|
+
const response = tv.parsePacket({ readyState: socket.readyState, str: raw });
|
|
92
|
+
// console.log(response, response === undefined);
|
|
93
|
+
response.forEach((packet) => {
|
|
94
|
+
// console.log(packet);
|
|
95
|
+
if (packet === undefined) {
|
|
96
|
+
console.log('undefined: ', raw.toString());
|
|
97
|
+
} else {
|
|
98
|
+
if (packet.type === 'ping') {
|
|
99
|
+
// console.log('pong', packet);
|
|
100
|
+
socket.send(tv.createPong({ value: packet.data }));
|
|
101
|
+
} else if (packet.type === 'protocol_error') {
|
|
102
|
+
console.log('protocol_error', packet);
|
|
103
|
+
socket.close();
|
|
104
|
+
} else if (packet.type === 'qsd') {
|
|
105
|
+
session = sessions.get({ key: packet.data[0] });
|
|
106
|
+
// console.log(packet.data, session, sessions);
|
|
107
|
+
const send = packet.data[1].v;
|
|
108
|
+
send.symbol = session.symbolId;
|
|
109
|
+
console.log(JSON.stringify([packet.type, send]));
|
|
110
|
+
} else if (['du', 'timescale_update'].includes(packet.type)) {
|
|
111
|
+
session = sessions.get({ key: packet.data[0] });
|
|
112
|
+
// console.log(packet.data, session, sessions);
|
|
113
|
+
const arr = [];
|
|
114
|
+
for (var key in packet.data[1].s1.s) {
|
|
115
|
+
const send = {
|
|
116
|
+
symbol: session.symbolId,
|
|
117
|
+
end: packet.data[1].s1.s[key + 1] !== undefined ? packet.data[1].s1.s[key + 1].v[0] : packet.data[1].s1.lbs.bar_close_time,
|
|
118
|
+
};
|
|
119
|
+
// console.log(key, packet.data[1].s1.s);
|
|
120
|
+
[send.start, send.open, send.low, send.high, send.close, send.passed] = packet.data[1].s1.s[key].v;
|
|
121
|
+
// console.log(send);
|
|
122
|
+
arr.push(send);
|
|
123
|
+
}
|
|
124
|
+
// [send.start, send.open, send.low, send.high, send.close, send.passed] = packet.data[1].s1.s.v;
|
|
125
|
+
console.log(JSON.stringify([packet.type, arr]));
|
|
126
|
+
} else {
|
|
127
|
+
console.log('error', raw.toString());
|
|
128
|
+
}
|
|
129
|
+
// else {
|
|
130
|
+
// console.log('received: %s', raw);
|
|
131
|
+
// }
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
// // setTimeout(async () => {
|
|
136
|
+
// // console.log('exit');
|
|
137
|
+
// // }, 3000);
|
|
138
|
+
|
|
139
|
+
process.on('SIGINT', stop);
|
|
140
|
+
process.on('SIGTERM', stop);
|
|
141
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "market-data-tradingview-ws",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "marketData from Tradingview ws",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"author": "sulimenkoas@gmail.com",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"ws": "^8.13.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
process.title = 'marketDataClient';
|
|
4
|
+
|
|
5
|
+
import { Client } from './lib/client.js';
|
|
6
|
+
import { Utils } from './lib/utils.js';
|
|
7
|
+
const u = new Utils();
|
|
8
|
+
|
|
9
|
+
const url = 'wss://data.tradingview.com/socket.io/websocket';
|
|
10
|
+
const options = { origin: 'https://data.tradingview.com' };
|
|
11
|
+
|
|
12
|
+
const login = 'sulimenko@ptfin.kz';
|
|
13
|
+
const pass = '7n8zGZ8v27pH';
|
|
14
|
+
|
|
15
|
+
const interval = '5';
|
|
16
|
+
const total_candle = 100;
|
|
17
|
+
|
|
18
|
+
const marketData = new Client();
|
|
19
|
+
|
|
20
|
+
const clients = new Map();
|
|
21
|
+
const symbolQuotes = new Map();
|
|
22
|
+
|
|
23
|
+
const stop = async () => {
|
|
24
|
+
const closed = new Promise((resolve) => {
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
resolve(console.log('exit'));
|
|
27
|
+
}, 1000);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// for (const sessionKey of marketData.getSessionKeys()) {
|
|
31
|
+
// let data = marketData.getSession({ key: sessionKey });
|
|
32
|
+
// console.log('session clear: ', sessionKey, data);
|
|
33
|
+
// }
|
|
34
|
+
marketData.close();
|
|
35
|
+
await closed;
|
|
36
|
+
console.log(clients, symbolQuotes);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = ({ packet }) => {
|
|
41
|
+
let data = symbolQuotes.get(packet.symbol);
|
|
42
|
+
if (!data) data = {};
|
|
43
|
+
Object.keys(packet).forEach((key) => {
|
|
44
|
+
// console.log(key, data, packet);
|
|
45
|
+
data[key] = packet[key];
|
|
46
|
+
});
|
|
47
|
+
symbolQuotes.set(packet.symbol, data);
|
|
48
|
+
|
|
49
|
+
clients.forEach((each) => {
|
|
50
|
+
if (each.quote.includes(packet.symbol)) {
|
|
51
|
+
each.client({ acc: each.acc, packet });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// function changeWSSymbol({ key, chartkey, symbolId }) {
|
|
57
|
+
// const s = sessions.get({ key });
|
|
58
|
+
// const c = sessions.get({ key: chartkey });
|
|
59
|
+
// const queue = [];
|
|
60
|
+
|
|
61
|
+
// // console.log(s, symbolId, id, c, chartkey, sessions);
|
|
62
|
+
// if (s.symbolId) queue.push(tv.removeSymbols({ session: s.main, symbolId: s.symbolId }));
|
|
63
|
+
// queue.push(tv.addSymbols({ session: s.main, symbolId }));
|
|
64
|
+
// queue.push(tv.resolveSymbol({ chartSession: c.main, id, symbolId }));
|
|
65
|
+
|
|
66
|
+
// queue.forEach((each) => {
|
|
67
|
+
// console.log(each);
|
|
68
|
+
// socket.send(each);
|
|
69
|
+
// });
|
|
70
|
+
// id++;
|
|
71
|
+
// sessions.set({ key, val: { main: key, symbolId } });
|
|
72
|
+
// sessions.set({ key: chartkey, val: { main: chartkey, symbolId } });
|
|
73
|
+
// }
|
|
74
|
+
|
|
75
|
+
(async () => {
|
|
76
|
+
clients.set('123', { acc: '123', quote: ['NASDAQ:TSLA', 'NASDAQ:META'], client: ({ acc, packet }) => console.log(acc, packet) });
|
|
77
|
+
clients.set('U444', { acc: 'U444', quote: ['NASDAQ:MRNA', 'NASDAQ:LI'], client: ({ acc, packet }) => console.log(acc, packet) });
|
|
78
|
+
|
|
79
|
+
const status = await marketData.connect({ url, options, login, pass, result });
|
|
80
|
+
console.log(status);
|
|
81
|
+
// await u.pause(2000);
|
|
82
|
+
marketData.createSession({ type: 'chart' });
|
|
83
|
+
marketData.createSession({ type: 'quote' });
|
|
84
|
+
|
|
85
|
+
clients.forEach((each) => {
|
|
86
|
+
marketData.addQuoteSymbols({ symbolIds: each.quote });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
process.on('SIGINT', stop);
|
|
90
|
+
process.on('SIGTERM', stop);
|
|
91
|
+
})();
|