UNPKG

7.27 kBJavaScriptView Raw
1const LibraryError = require('../globals/LibraryError');
2const News = require('./News');
3const Quote = require('../globals/Quote');
4const EventEmitter = require('events');
5const request = require('request');
6const socket = require('socket.io-client');
7
8/**
9 * An extension of the Node.js EventEmitter that sends Quote and News objects as they arrive.
10 * @event Stream#quote
11 * @type {Quote}
12 * @event Stream#news
13 * @type {News}
14 * @event Stream#iex
15 * @type {Object}
16 */
17class Stream extends EventEmitter {
18
19 /**
20 * Creates a new Stream class.
21 * @author Torrey Leonard <https://github.com/Ladinn>
22 * @constructor
23 * @param {Array} symbols
24 * @param {Object|Null} options
25 * @property {Boolean} iex - Whether to include real time IEX data in stream
26 * @property {String} iexType - Which endpoint to use for IEX stream (tops, last, hist, deep, book, etc. See: https://iextrading.com/developer/docs/#iex-market-data)
27 * @property {Boolean} news - Whether to include news headlines in the stream.
28 * @property {Boolean} allHeadlines - If true, all U.S. business headlines will be sent in the stream. If false, only news pertaining to the given symbols will be outputted.
29 * @property {String} newsApiKey - If 'includeNews' is yes, this should be your API key from https://newsapi.org/register.
30 */
31 constructor(symbols, options) {
32 super();
33 this.symbols = symbols;
34 if (options) {
35 this.iex = Boolean(options.iex) || false;
36 this.iexType = String(options.iexType) || "tops";
37 this.news = Boolean(options.news) || false;
38 this.allHeadlines = Boolean(options.allHeadlines) || false;
39 this.newsApiKey = String(options.newsApiKey) || false;
40 this.newsArray = [];
41 }
42 }
43
44 /**
45 * Start the streaming class.
46 * @author Torrey Leonard <https://github.com/Ladinn>
47 */
48 start() {
49 const _this = this;
50 this.request = request.get({
51 uri: "https://streamerapi.finance.yahoo.com/streamer/1.0",
52 qs: {
53 s: _this.symbols.join(','),
54 k: _this._getYahooKeys().join(','),
55 callback: 'parent.yfs_u1f',
56 mktmcb: 'parent.yfs_mktmcb',
57 gencallback: 'parent.yfs_gencb',
58 mu: '1',
59 lang: 'en-US',
60 region: 'US',
61 localize: '0'
62 },
63 qsStringifyOptions: {
64 encode: false
65 },
66 forever: true
67 }).on('error', error => {
68 _this.emit('error', error)
69 }).on('response', response => {
70 if (response.statusCode === 404) _this.emit('error', new LibraryError("Yahoo Finance streamer responded with status code: 404.\n\tThis is typically a result of an invalid equity symbol in the query."));
71 else if (response.statusCode !== 200) _this.emit('error', new LibraryError("Yahoo Finance streamer responded with status code: " + response.statusCode));
72 else _this.emit('response', response);
73 }).on('data', data => {
74 try {
75 let input = data.toString().split('yfs_u1f(');
76 input.shift();
77 input.forEach(i => {
78 const quote = _this._createQuote(i);
79 _this.emit('quote', quote);
80 if (_this.news) {
81 let options = {
82 country: "us",
83 pageSize: 100,
84 };
85 if (!_this.allHeadlines) options.q = quote.getSymbol();
86 else options.category = "business";
87 News.getHeadlines(_this.newsApiKey, options).then(array => {
88 array.forEach(news => {
89 if (_this.newsArray.indexOf(news) === -1) {
90 _this.emit('news', news);
91 _this.newsArray.push(news);
92 }
93 })
94 }).catch(error => _this.emit('error', new LibraryError(error)));
95 }
96 });
97 } catch (error) {
98 _this.emit('error', error);
99 }
100 });
101 if (_this.iex) {
102 _this.socket = socket("https://ws-api.iextrading.com/1.0/" + _this.iexType);
103 _this.socket.on('connect', () => {
104 _this.socket.emit('subscribe', _this.symbols.join(','));
105 });
106 _this.socket.on('message', data => {
107 _this.emit('iex', data);
108 });
109 }
110 }
111
112 /**
113 * Stop the streaming class.
114 * @author Torrey Leonard <https://github.com/Ladinn>
115 * @author Wyatt Calandro <https://github.com/wclandro>
116 */
117 stop() {
118 this.request.abort();
119 if(this.iex){
120 this.socket.disconnect();
121 }
122 }
123
124 /**
125 * @author Torrey Leonard <https://github.com/Ladinn>
126 * @param input
127 * @returns {Quote}
128 * @private
129 */
130 _createQuote(input) {
131 const yahooKeys = {
132 a00: 'ask',
133 a50: 'askSize',
134 b00: 'bid',
135 b60: 'bidSize',
136 c10: 'change',
137 c63: 'changeRealTime',
138 c64: 'disputedChangeRealTimeAfterHours',
139 c85: 'changeRealTimeAfterHours',
140 c86: 'percentChangeRealTimeAfterHours',
141 g53: 'dayLow',
142 h53: 'dayHigh',
143 j10: 'marketCap',
144 l84: 'priceRealTime',
145 l86: 'priceRealTimeAfterHours',
146 p20: 'percentChange',
147 p43: 'percentChangeRealTime',
148 p44: 'percentChangeRealTimeAfterHours',
149 t53: 'disputedTimestampForCommodities',
150 t54: 'disputedTimestampForStocks',
151 v53: 'volume',
152 v00: 'volume2',
153 l10: 'lastSalePrice',
154 t10: 'lastSaleTime',
155 l90: 'ecnQuoteLastValue'
156 };
157 for (const key in yahooKeys) {
158 input = input.replace(key, '"' + yahooKeys[key] + '"');
159 }
160 const json = JSON.parse(input.split(');')[0]);
161
162 const symbol = Object.keys(json)[0];
163 const object = json[symbol];
164
165 let quote = {
166 symbol: symbol,
167 date: new Date(),
168 source: "Yahoo Finance",
169 price: {
170 last: Number(object.priceRealTimeAfterHours) || Number(object.priceRealTime) || Number(object.lastSalePrice),
171 high: Number(object.dayHigh),
172 low: Number(object.dayLow)
173 },
174 dom: {
175 bid: {
176 price: Number(object.bid),
177 size: Number(object.bidSize)
178 },
179 ask: {
180 price: Number(object.ask),
181 size: Number(object.askSize)
182 }
183 },
184 meta: {
185 change: object.change || object.changeRealTime,
186 percentChange: object.percentChange || object.percentChangeRealTime,
187 marketCap: object.marketCap
188 },
189 original: json
190 };
191
192 if (object.volume) quote.price.volume = Number(object.volume.replace(/,/g, ''));
193 if (object.volume2) quote.price.volume = Number(object.volume2.replace(/,/g, ''));
194
195 return new Quote(quote);
196
197 }
198
199 /**
200 * @author Torrey Leonard <https://github.com/Ladinn>
201 * @returns {string[]}
202 * @private
203 */
204 _getYahooKeys() {
205 const keys = {
206 Ask: 'a00',
207 AskSize: 'a50',
208 Bid: 'b00',
209 BidSize: 'b60',
210 Change: 'c10',
211 ChangeRealTime: 'c63',
212 DisputedChangeRealTimeAfterHours: 'c64',
213 ChangeRealTimeAfterHours: 'c85',
214 PercentChangeRealTimeAfterHours: 'p44',
215 DayLow: 'g53',
216 DayHigh: 'h53',
217 MarketCap: 'j10',
218 PriceRealTime: 'l84',
219 PriceRealTimeAfterHours: 'l86',
220 PercentChange: 'p20',
221 PercentChangeRealTime: 'p43',
222 DisputedTimestampForCommodities: 't53',
223 DisputedTimestampForStocks: 't54',
224 Volume: 'v53',
225 Volume2: 'v00',
226 LastSalePrice: 'l10',
227 LastSaleTime: 't10',
228 EcnQuoteLastValue: 'l90',
229 };
230 // return [
231 // keys.Ask,
232 // keys.AskSize,
233 // keys.Bid,
234 // keys.BidSize,
235 // keys.Change,
236 // keys.Volume,
237 // keys.Volume2,
238 // keys.PriceRealTime,
239 // keys.PriceRealTimeAfterHours,
240 // keys.LastSalePrice,
241 // keys.EcnQuoteLastValue,
242 // keys.LastSaleTime,
243 // keys.DisputedTimestampForStocks,
244 // keys.MarketCap
245 // ]
246 return [
247 'a00', 'a50', 'b00', 'b60', 'c10', 'c63', 'c64', 'c85', 'p44', 'g53', 'h53', 'j10', 'l84', 'l86', 'p20', 'p43', 't53', 't54', 'v53', 'v00', 'l10', 't10', 'l90'
248 ]
249 }
250
251}
252
253module.exports = Stream;
\No newline at end of file