diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..aec1179 --- /dev/null +++ b/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": [ + ["env", { + "targets": { + "node": "current" + } + }] + ] +} diff --git a/README.md b/README.md index 435aa8b..d5cf71c 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,20 @@ The library handles fetching, parsing, and cleaning of CSV data and returns JSON Also check out [google-finance](https://github.com/pilwon/node-google-finance). +## NB: Yahoo API + +Yahoo's API has completely changed recently. This interim release is intended to get you up and running again quickly, but we're still working on it. Note also that Yahoo have stopped supporting their API for developers, so no guarantees can be made about service availability. This package relies entirely on open source contributions. Regarding this package's API: + +* `historical()` - should work as expected - please check the output and report any inconsistencies. + +* `snapshot()` - disabled in this release. It will be enabled again in the next release with a compatibility layer for SOME options. Since Yahoo's new API does not contain all the same data as the old version, 100% compatibility is impossible. As such, snapshot() is DEPRECATED and we suggest you switch to the new `quote()` API. However, the upcoming release should provide an easier upgrade for the most common use cases. + +* `quote()` - NEW API more faithful to Yahoo's new API. See below. This replaces `snapshot()` and we suggest you use it instead. + + ## Installation - $ npm install yahoo-finance + $ npm install --save yahoo-finance ## Usage @@ -26,16 +37,23 @@ yahooFinance.historical({ //... }); -yahooFinance.snapshot({ +// This replaces the deprecated snapshot() API +yahooFinance.quote({ symbol: 'AAPL', - fields: ['s', 'n', 'd1', 'l1', 'y', 'r'], -}, function (err, snapshot) { - //... + modules: [ 'price', 'summaryDetail' ] // see the docs for the full list +}, function (err, quotes) { + // ... }); + ``` * [See more comprehensive examples here.](https://github.com/pilwon/node-yahoo-finance/tree/master/examples) +* See the [full quote() docs](docs/quote.md) for the list of all possible +modules and the data they return. + +* See also the [deprecated snapshot() API](docs/snapshot.md) docs, for +reference. ## API @@ -137,201 +155,6 @@ yahooFinance.historical({ }); ``` -### Download Snapshot Data (single symbol) - -```js -yahooFinance.snapshot({ - symbol: SYMBOL, - fields: FIELDS // ex: ['s', 'n', 'd1', 'l1', 'y', 'r'] -}, function (err, snapshot) { - /* - { - symbol: 'AAPL', - name: 'Apple Inc.', - lastTradeDate: '11/15/2013', - lastTradePriceOnly: '524.88', - dividendYield: '2.23', - peRatio: '13.29' - } - */ -}); -``` - -### Download Snapshot Data (multiple symbols) - -```js -yahooFinance.snapshot({ - symbols: [SYMBOL1, SYMBOL2], - fields: FIELDS // ex: ['s', 'n', 'd1', 'l1', 'y', 'r'] -}, function (err, snapshot) { - /* - { - AAPL: { - symbol: 'AAPL', - name: 'Apple Inc.', - lastTradeDate: '11/15/2013', - lastTradePriceOnly: '524.88', - dividendYield: '2.23', - peRatio: '13.29' - }, - GOOGL: { - symbol: 'GOOGL', - name: 'Google Inc.', - lastTradeDate: '11/15/2013', - lastTradePriceOnly: '1034.23', - dividendYield: 'N/A', - peRatio: '28.17' - } - } - */ -}); -``` - - - -### Fields - -``` -s: Symbol -``` - -#### Pricing - -``` -a: Ask -b: Bid -b2: Ask (Realtime) -b3: Bid (Realtime) -p: Previous Close -o: Open -``` - -#### Dividends - -``` -y: Dividend Yield -d: Dividend Per Share -r1: Dividend Pay Date -q: Ex-Dividend Date -``` - -#### Date - -``` -c1: Change -c: Change And Percent Change -c6: Change (Realtime) -k2: Change Percent (Realtime) -p2: Change in Percent -d1: Last Trade Date -d2: Trade Date -t1: Last Trade Time -``` - -#### Averages - -``` -c8: After Hours Change (Realtime) -c3: Commission -g: Day’s Low -h: Day’s High -k1: Last Trade (Realtime) With Time -l: Last Trade (With Time) -l1: Last Trade (Price Only) -t8: 1 yr Target Price -m5: Change From 200-day Moving Average -m6: Percent Change From 200-day Moving Average -m7: Change From 50-day Moving Average -m8: Percent Change From 50-day Moving Average -m3: 50-day Moving Average -m4: 200-day Moving Average -``` - -#### Misc - -``` -w1: Day’s Value Change -w4: Day’s Value Change (Realtime) -p1: Price Paid -m: Day’s Range -m2: Day’s Range (Realtime) -g1: Holdings Gain Percent -g3: Annualized Gain -g4: Holdings Gain -g5: Holdings Gain Percent (Realtime) -g6: Holdings Gain (Realtime) -``` - -#### 52 Week Pricing - -``` -k: 52-week High -j: 52-week Low -j5: Change From 52-week Low -k4: Change From 52-week High -j6: Percent Change From 52-week Low -k5: Percebt Change From 52-week High -w: 52-week Range -``` - -#### System Info - -``` -i: More Info -j1: Market Capitalization -j3: Market Cap (Realtime) -f6: Float Shares -n: Name -n4: Notes -s1: Shares Owned -x: Stock Exchange -j2: Shares Outstanding -``` - -#### Volume - -``` -v: Volume -a5: Ask Size -b6: Bid Size -k3: Last Trade Size -a2: Average Daily Volume -``` - -#### Ratio - -``` -e: Earnings Per Share -e7: EPS Estimate Current Year -e8: EPS Estimate Next Year -e9: EPS Estimate Next Quarter -b4: Book Value -j4: EBITDA -p5: Price per Sales -p6: Price per Book -r: PE Ratio -r2: PE Ratio (Realtime) -r5: PEG Ratio -r6: Price Per EPS Estimate Current Year -r7: Price Per EPS Estimate Next Year -s7: Short Ratio -``` - -#### Misc - -``` -t7: Ticker Trend -t6: Trade Links -i5: Order Book (Realtime) -l2: High Limit -l3: Low Limit -v1: Holdings Value -v7: Holdings Value (Realtime) -s6: Revenue -e1: Error Indication (returned for symbol changed or invalid) -``` - - ### Specifying request options Optionally request options (such as a proxy) can be specified by inserting an @@ -353,9 +176,9 @@ yahooFinance.historical({ }); -yahooFinance.snapshot({ +yahooFinance.quote({ symbol: SYMBOL, - fields: FIELDS // ex: ['s', 'n', 'd1', 'l1', 'y', 'r'] + modules: MODULES // ex: ['price', 'summaryDetail'] }, httpRequestOptions, function (err, snapshot) { // Result }); diff --git a/docs/quote.md b/docs/quote.md new file mode 100644 index 0000000..3247bd5 --- /dev/null +++ b/docs/quote.md @@ -0,0 +1,391 @@ +# quote + +## Usage: + +```js +var yahooFinance = require('yahoo-finance'); + +yahooFinance.quote({ + symbol: 'TSLA', + modules: ['price', 'summaryDetail'] // optional; default modules. +}, function(err, quote) { + console.log(quote); + { + price: { + // output from price module (see below) + }, + summaryDetail: { + // output from summaryDetail module (see below) + } + } +}); +``` + +Without a callback, `quote()` returns a **promise**. There is also a +'shortcut' alternative API, as follows (shown using ES7 async-wait syntax). + +```js +const result = await quote('TSLA'); // implies default modules below +const result = await quote('TSLA', ['summaryDetail', 'recommendationTrend']); +``` + +## Modules and sample output + +**recommendationTrend**: + +```js +{ + "recommendationTrend": { + "trend": [{ + "period": "0w", + "strongBuy": 0, + "buy": 0, + "hold": 0, + "sell": 0, + "strongSell": 0 + }, { + "period": "-1w", + "strongBuy": 0, + "buy": 0, + "hold": 0, + "sell": 0, + "strongSell": 0 + }, { + "period": "0m", + "strongBuy": 3, + "buy": 5, + "hold": 8, + "sell": 6, + "strongSell": 0 + }, { + "period": "-1m", + "strongBuy": 3, + "buy": 3, + "hold": 8, + "sell": 5, + "strongSell": 0 + }, { + "period": "-2m", + "strongBuy": 3, + "buy": 2, + "hold": 8, + "sell": 6, + "strongSell": 0 + }, { + "period": "-3m", + "strongBuy": 3, + "buy": 3, + "hold": 8, + "sell": 6, + "strongSell": 0 + }], + "maxAge": 86400 + } +} +``` + +**summaryDetail** + +```js +{ + "summaryDetail": { + "maxAge": 1, + "priceHint": 2, + "previousClose": 313.06, + "open": 315.5, + "dayLow": 310.2, + "dayHigh": 316.5, + "regularMarketPreviousClose": 313.06, + "regularMarketOpen": 315.5, + "regularMarketDayLow": 310.2, + "regularMarketDayHigh": 316.5, + "dividendRate": 1.56, + "dividendYield": 0.023, + "exDividendDate": 2017-02-09T00:00:00.000Z, + "payoutRatio": 0.66080004, + "fiveYearAvgDividendYield": 2.59, + "beta": 1.15078, + "trailingPE": -65.177185, + "forwardPE": -330.6702, + "volume": 4628544, + "regularMarketVolume": 4628544, + "averageVolume": 6076252, + "averageVolume10days": 5380128, + "averageDailyVolume10Day": 5380128, + "bid": 310.3, + "ask": 310.72, + "bidSize": 400, + "askSize": 200, + "marketCap": 51056934912, + "fiftyTwoWeekLow": 178.19, + "fiftyTwoWeekHigh": 327.66, + "priceToSalesTrailing12Months": 5.972023, + "fiftyDayAverage": 309.1597, + "twoHundredDayAverage": 249.92029, + "trailingAnnualDividendRate": 1.53, + "trailingAnnualDividendYield": 0.022603042 + } +} +``` + +**earnings** + +```js +{ + "earnings": { + "maxAge": 86400, + "earningsChart": { + "quarterly": [{ + "date": "2Q2016", + "actual": -1.61, + "estimate": -0.51 + }, { + "date": "3Q2016", + "actual": 0.71, + "estimate": -0.54 + }, { + "date": "4Q2016", + "actual": -0.69, + "estimate": -0.43 + }, { + "date": "1Q2017", + "actual": -1.33, + "estimate": -0.81 + }], + "currentQuarterEstimate": -1.64, + "currentQuarterEstimateDate": "2Q", + "currentQuarterEstimateYear": 2017 + }, + "financialsChart": { + "yearly": [{ + "date": 2014, + "revenue": 3198356000, + "earnings": -294040000 + }, { + "date": 2015, + "revenue": 4046025000, + "earnings": -888663000 + }, { + "date": 2016, + "revenue": 7000132000, + "earnings": -674914000 + }], + "quarterly": [{ + "date": "2Q2016", + "revenue": 1270017000, + "earnings": -293188000 + }, { + "date": "3Q2016", + "revenue": 2298436000, + "earnings": 21878000 + }, { + "date": "4Q2016", + "revenue": 2284631000, + "earnings": -121337000 + }, { + "date": "1Q2017", + "revenue": 2696270000, + "earnings": -330277000 + }] + } + } +} +``` + +**calendarEvents** + +```js +{ + "calendarEvents": { + "maxAge": 1, + "earnings": { + "earningsDate": [1501545600, 1502064000], + "earningsAverage": -1.64, + "earningsLow": -2.98, + "earningsHigh": -0.5, + "revenueAverage": 2636630000, + "revenueLow": 2457200000, + "revenueHigh": 2887970000 + }, + "exDividendDate": 2017-02-09T00:00:00.000Z, + "dividendDate": 2017-05-18T00:00:00.000Z + } +} +``` + +**upgradeDowngradeHistory** + +```js +{ + "upgradeDowngradeHistory": { + "history": [{ + "epochGradeDate": 2017-01-24T00:00:00.000Z, + "firm": "Piper Jaffray", + "toGrade": "Overweight", + "fromGrade": "Neutral", + "action": "up" + }, { + "epochGradeDate": 2016-10-27T00:00:00.000Z, + "firm": "Bernstein", + "toGrade": "Mkt Perform", + "fromGrade": "", + "action": "init" + }, + // ... + { + "epochGradeDate": 2007-01-18T00:00:00.000Z, + "firm": "Dougherty & Company", + "toGrade": "Buy", + "fromGrade": "", + "action": "init" + }, { + "epochGradeDate": 2007-01-18T00:00:00.000Z, + "firm": "Deutsche Bank", + "toGrade": "Hold", + "fromGrade": "", + "action": "init" + }], + "maxAge": 86400 + } +} +``` + +**price** + +```js +{ + "price": { + "maxAge": 1, + "preMarketChangePercent": 7.3050486E-4, + "preMarketChange": 0.05000305, + "preMarketTime": 2017-05-24T13:29:46.000Z, + "preMarketPrice": 68.5, + "preMarketSource": "FREE_REALTIME", + "postMarketChangePercent": -0.0014798812, + "postMarketChange": -0.45999146, + "postMarketTime": 2017-05-23T23:59:01.000Z, + "postMarketPrice": 310.37, + "postMarketSource": "FREE_REALTIME", + "regularMarketChangePercent": -0.0071232705, + "regularMarketChange": -2.230011, + "regularMarketTime": 2017-05-24T15:20:15.000Z, + "priceHint": 2, + "regularMarketPrice": 310.83, + "regularMarketDayHigh": 316.5, + "regularMarketDayLow": 310.2, + "regularMarketVolume": 4628544, + "averageDailyVolume10Day": 5380128, + "averageDailyVolume3Month": 6076252, + "regularMarketPreviousClose": 313.06, + "regularMarketSource": "FREE_REALTIME", + "regularMarketOpen": 315.5, + "exchange": "NMS", + "exchangeName": "NasdaqGS", + "marketState": "CLOSED", + "quoteType": "EQUITY", + "symbol": "TSLA", + "underlyingSymbol": null, + "shortName": "Tesla, Inc.", + "longName": "Tesla, Inc.", + "currency": "USD", + "quoteSourceName": "Nasdaq Real Time Price", + "currencySymbol": "$" + } +} +``` + +**defaultKeyStatics** + +```js +{ + "defaultKeyStatistics": { + "maxAge": 1, + "forwardPE": -357.27585, + "profitMargins": -0.08456001, + "floatShares": 121545634, + "sharesOutstanding": 164260000, + "sharesShort": 31068700, + "sharesShortPriorMonth": 31013300, + "heldPercentInsiders": 0.22283001, + "heldPercentInstitutions": 0.62, + "shortRatio": 5.27, + "shortPercentOfFloat": 0.383402, + "beta": 1.15078, + "category": null, + "bookValue": 30.764, + "priceToBook": 10.103692, + "fundFamily": null, + "legalType": null, + "lastFiscalYearEnd": 2016-09-24T00:00:00.000Z, + "nextFiscalYearEnd": 2018-09-24T00:00:00.000Z, + "mostRecentQuarter": 2017-04-01T00:00:00.000Z, + "netIncomeToCommon": -722924032, + "trailingEps": -4.769, + "forwardEps": -0.94, + "pegRatio": -1.59, + "lastSplitFactor": "2/1", + "lastSplitDate": 2014-06-09T00:00:00.000Z, + "52WeekChange": 0.44787717, + "SandP52WeekChange": 0.15511417 + } +} +``` + +**summaryProfile** + +```js +{ + "summaryProfile": { + "address1": "3500 Deer Creek Road", + "city": "Palo Alto", + "state": "CA", + "zip": "94304", + "country": "United States", + "phone": "650-681-5000", + "website": "http://www.tesla.com", + "industry": "Auto Manufacturers - Major", + "sector": "Consumer Goods", + "longBusinessSummary": "Tesla, Inc. designs, develops, manufactures, and sells electric vehicles and energy storage products in the United States, China, Norway, and internationally. The company operates in two segments, Automotive, and Energy Generation and Storage. It primarily offers sedans and sport utility vehicles. The company also provides electric vehicle powertrain components and systems to other manufacturers; and services for electric vehicles through its 135 company-owned service centers and Service Plus locations, as well as through Tesla Ranger mobile technicians. It sells its products through a network of company-owned stores and galleries, as well as through Internet. In addition, the company offers energy storage products, such as rechargeable lithium-ion battery systems for use in homes, commercial facilities, and utility sites. Further, the company designs, manufactures, installs, maintains, leases, and sells solar energy systems to residential and commercial customers through a sales organization that include specialized internal call centers, outside sales force, a channel partner network, and a customer referral program, as well as through selected Tesla stores. Additionally, it sells renewable electricity generated by solar energy systems to customers. The company was formerly known as Tesla Motors, Inc. and changed its name to Tesla, Inc. in February 2017. Tesla, Inc. was founded in 2003 and is headquartered in Palo Alto, California.", + "fullTimeEmployees": 17782, + "companyOfficers": [], + "maxAge": 86400 + } +} +``` + +**financialData** + +```js +{ + "financialData": { + "maxAge": 86400, + "currentPrice": 310.83, + "targetHighPrice": 380.0, + "targetLowPrice": 155.0, + "targetMeanPrice": 275.2, + "targetMedianPrice": 305.0, + "recommendationMean": 2.9, + "recommendationKey": "hold", + "numberOfAnalystOpinions": 15, + "totalCash": 4006593024, + "totalCashPerShare": 24.392, + "ebitda": 530736032, + "totalDebt": 9667725312, + "quickRatio": 0.711, + "currentRatio": 1.124, + "totalRevenue": 8549353472, + "debtToEquity": 156.916, + "revenuePerShare": 56.403, + "returnOnAssets": -0.02317, + "returnOnEquity": -0.24903, + "grossProfits": 1599257000, + "freeCashflow": 673406976, + "operatingCashflow": 55965000, + "revenueGrowth": 1.351, + "grossMargins": 0.23566, + "ebitdaMargins": 0.062080003, + "operatingMargins": -0.074250005, + "profitMargins": -0.08456001 + } +} +``` diff --git a/docs/snapshot.md b/docs/snapshot.md new file mode 100644 index 0000000..35b89ac --- /dev/null +++ b/docs/snapshot.md @@ -0,0 +1,233 @@ +# snapshot + +## Important Notice + +**`snapshot()` has been deprecated**. Yahoo no longer provides all the info +required for it to function properly. Use [quote()](./quote.md) instead. + +*The documentation below is for reference purposes only.* + +### Quick Start: + +```js +yahooFinance.snapshot({ + symbol: 'AAPL', + fields: ['s', 'n', 'd1', 'l1', 'y', 'r'], +}, function (err, snapshot) { + //... +}); +``` + +### Download Snapshot Data (single symbol) + +```js +yahooFinance.snapshot({ + symbol: SYMBOL, + fields: FIELDS // ex: ['s', 'n', 'd1', 'l1', 'y', 'r'] +}, function (err, snapshot) { + /* + { + symbol: 'AAPL', + name: 'Apple Inc.', + lastTradeDate: '11/15/2013', + lastTradePriceOnly: '524.88', + dividendYield: '2.23', + peRatio: '13.29' + } + */ +}); +``` + +### Download Snapshot Data (multiple symbols) + +```js +yahooFinance.snapshot({ + symbols: [SYMBOL1, SYMBOL2], + fields: FIELDS // ex: ['s', 'n', 'd1', 'l1', 'y', 'r'] +}, function (err, snapshot) { + /* + { + AAPL: { + symbol: 'AAPL', + name: 'Apple Inc.', + lastTradeDate: '11/15/2013', + lastTradePriceOnly: '524.88', + dividendYield: '2.23', + peRatio: '13.29' + }, + GOOGL: { + symbol: 'GOOGL', + name: 'Google Inc.', + lastTradeDate: '11/15/2013', + lastTradePriceOnly: '1034.23', + dividendYield: 'N/A', + peRatio: '28.17' + } + } + */ +}); +``` + + + +### Fields + +``` +s: Symbol +``` + +#### Pricing + +``` +a: Ask +b: Bid +b2: Ask (Realtime) +b3: Bid (Realtime) +p: Previous Close +o: Open +``` + +#### Dividends + +``` +y: Dividend Yield +d: Dividend Per Share +r1: Dividend Pay Date +q: Ex-Dividend Date +``` + +#### Date + +``` +c1: Change +c: Change And Percent Change +c6: Change (Realtime) +k2: Change Percent (Realtime) +p2: Change in Percent +d1: Last Trade Date +d2: Trade Date +t1: Last Trade Time +``` + +#### Averages + +``` +c8: After Hours Change (Realtime) +c3: Commission +g: Day’s Low +h: Day’s High +k1: Last Trade (Realtime) With Time +l: Last Trade (With Time) +l1: Last Trade (Price Only) +t8: 1 yr Target Price +m5: Change From 200-day Moving Average +m6: Percent Change From 200-day Moving Average +m7: Change From 50-day Moving Average +m8: Percent Change From 50-day Moving Average +m3: 50-day Moving Average +m4: 200-day Moving Average +``` + +#### Misc + +``` +w1: Day’s Value Change +w4: Day’s Value Change (Realtime) +p1: Price Paid +m: Day’s Range +m2: Day’s Range (Realtime) +g1: Holdings Gain Percent +g3: Annualized Gain +g4: Holdings Gain +g5: Holdings Gain Percent (Realtime) +g6: Holdings Gain (Realtime) +``` + +#### 52 Week Pricing + +``` +k: 52-week High +j: 52-week Low +j5: Change From 52-week Low +k4: Change From 52-week High +j6: Percent Change From 52-week Low +k5: Percebt Change From 52-week High +w: 52-week Range +``` + +#### System Info + +``` +i: More Info +j1: Market Capitalization +j3: Market Cap (Realtime) +f6: Float Shares +n: Name +n4: Notes +s1: Shares Owned +x: Stock Exchange +j2: Shares Outstanding +``` + +#### Volume + +``` +v: Volume +a5: Ask Size +b6: Bid Size +k3: Last Trade Size +a2: Average Daily Volume +``` + +#### Ratio + +``` +e: Earnings Per Share +e7: EPS Estimate Current Year +e8: EPS Estimate Next Year +e9: EPS Estimate Next Quarter +b4: Book Value +j4: EBITDA +p5: Price per Sales +p6: Price per Book +r: PE Ratio +r2: PE Ratio (Realtime) +r5: PEG Ratio +r6: Price Per EPS Estimate Current Year +r7: Price Per EPS Estimate Next Year +s7: Short Ratio +``` + +#### Misc + +``` +t7: Ticker Trend +t6: Trade Links +i5: Order Book (Realtime) +l2: High Limit +l3: Low Limit +v1: Holdings Value +v7: Holdings Value (Realtime) +s6: Revenue +e1: Error Indication (returned for symbol changed or invalid) +``` + +### Specifying request options + +Optionally request options (such as a proxy) can be specified by inserting an +extra parameter just before the callback: + + +```js +var httpRequestOptions = { + proxy: 'http://localproxy.com' +}; + + +yahooFinance.snapshot({ + symbol: SYMBOL, + fields: FIELDS // ex: ['s', 'n', 'd1', 'l1', 'y', 'r'] +}, httpRequestOptions, function (err, snapshot) { + // Result +}); +``` diff --git a/lib/constants.js b/lib/constants.js index 11b205b..fbef2c7 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,2 +1,3 @@ -exports.HISTORICAL_URL = 'http://chart.finance.yahoo.com/table.csv'; -exports.SNAPSHOT_URL = 'http://download.finance.yahoo.com/d/quotes.csv'; +exports.HISTORICAL_CRUMB_URL = 'https://finance.yahoo.com/quote/$SYMBOL/history'; +exports.HISTORICAL_DOWNLOAD_URL = 'https://query1.finance.yahoo.com/v7/finance/download/$SYMBOL'; +exports.SNAPSHOT_URL = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/$SYMBOL'; diff --git a/lib/fields.js b/lib/fields.js index ca42499..de001e8 100644 --- a/lib/fields.js +++ b/lib/fields.js @@ -105,5 +105,115 @@ module.exports = { v1: 'Holdings Value', v7: 'Holdings Value (Realtime)', s6: 'Revenue', - e1: 'Error Indication (returned for symbol changed or invalid)' + e1: 'Error Indication (returned for symbol changed or invalid)', + + // Map to v10 API + _map: { + // Pricing + a: 'summaryDetail.ask', // 'Ask' + b: 'summaryDetail.bid', // 'Bid' + b2: 'summaryDetail.ask', // 'Ask (Realtime)' + b3: 'summaryDetail.bid', // 'Bid (Realtime)' + p: 'summaryDetail.previousClose', // 'Previous Close' + o: 'summaryDetail.open', // 'Open' + + // Dividends + y: 'summaryDetail.dividendYield', // 'Dividend Yield' + d: null, // 'Dividend Per Share' + r1: null, // 'Dividend Pay Date' + q: 'summaryDetail.exDividendDate', // 'Ex-Dividend Date' + + // Date + c1: 'price.regularMarketChange', // 'Change' + c: 'price.regularMarketChange,price.regularMarketChangePercent', // 'Change And Percent Change' + c6: 'price.postMarketChange', // 'Change (Realtime)', + k2: 'price.postMarketChange', // 'Change Percent (Realtime)', + p2: 'price.regularMarketChangePercent', // 'Change in Percent', + d1: null, // 'Last Trade Date' + d2: null, // 'Trade Date' + t1: null, // 'Last Trade Time' + + // Averages + c8: null, // 'After Hours Change (Realtime)', + c3: null, // 'Commission', + g: 'summaryDetail.dayLow', // 'Day’s Low', + h: 'summaryDetail.dayHigh', // 'Day’s High', + k1: null, // 'Last Trade (Realtime) With Time', + l: null, // 'Last Trade (With Time)', + l1: null, // 'Last Trade (Price Only)', + t8: null, // '1 yr Target Price', + m5: null, // 'Change From 200-day Moving Average', + m6: null, // 'Percent Change From 200-day Moving Average', + m7: null, // 'Change From 50-day Moving Average', + m8: null, // 'Percent Change From 50-day Moving Average', + m3: 'summaryDetail.fiftyDayAverage', // '50-day Moving Average' + m4: 'summaryDetail.twoHundredDayAverage', // '200-day Moving Average' + + // Misc + w1: null, // 'Day’s Value Change', + w4: null, // 'Day’s Value Change (Realtime)', + p1: null, // 'Price Paid', + m: null, // 'Day’s Range', + m2: null, // 'Day’s Range (Realtime)', + g1: null, // 'Holdings Gain Percent', + g3: null, // 'Annualized Gain', + g4: null, // 'Holdings Gain', + g5: null, // 'Holdings Gain Percent (Realtime)', + g6: null, // 'Holdings Gain (Realtime)', + + // 52 Week Pricing + k: 'summaryDetail.fiftyTwoWeekHigh', // '52-week High', + j: 'summaryDetail.fiftyTwoWeekLow', // '52-week Low', + j5: null, // 'Change From 52-week Low', + k4: null, // 'Change From 52-week High', + j6: null, // 'Percent Change From 52-week Low', + k5: null, // 'Percebt Change From 52-week High', + w: null, // '52-week Range', + + // System Info + i: null, // 'More Info', + j1: null, // 'Market Capitalization', + j3: null, // 'Market Cap (Realtime)', + f6: null, // 'Float Shares', + n: null, // 'Name', + n4: null, // 'Notes', + s1: null, // 'Shares Owned', + x: null, // 'Stock Exchange', + j2: null, // 'Shares Outstanding', + + // Volume + v: 'summaryDetail.volume', // 'Volume', + a5: 'summaryDetail.askSize', // 'Ask Size', + b6: 'summaryDetail.bidSize', // 'Bid Size', + k3: null, // 'Last Trade Size', + a2: 'summaryDetail.averageDailyVolume10Day', // 'Average Daily Volume', + + // Ratio + e: 'defaultKeyStatistics.forwardEps', // 'Earnings Per Share', + e7: null, // 'EPS Estimate Current Year', + e8: null, // 'EPS Estimate Next Year', + e9: null, // 'EPS Estimate Next Quarter', + b4: 'defaultKeyStatistics.bookValue', // 'Book Value', + j4: 'financialData.ebitda', // 'EBITDA', + p5: null, // 'Price per Sales', + p6: null, // 'Price per Book', + r: 'price.trailingPE', // 'PE Ratio', + r2: 'price.forwardPE', // 'PE Ratio (Realtime)', + r5: 'defaultKeyStatistics.pegRatio', // 'PEG Ratio', + r6: null, // 'Price Per EPS Estimate Current Year', + r7: null, // 'Price Per EPS Estimate Next Year', + s7: 'defaultKeyStatistics.shortRatio', // 'Short Ratio', + + // Misc + t7: null, // 'Ticker Trend', + t6: null, // 'Trade Links', + i5: null, // 'Order Book (Realtime)', + l2: null, // 'High Limit', + l3: null, // 'Low Limit', + v1: null, // 'Holdings Value', + v7: null, // 'Holdings Value (Realtime)', + s6: null, // 'Revenue', + e1: null, // 'Error Indication (returned for symbol changed or invalid)' + + } }; diff --git a/lib/index.js b/lib/index.js index 49ab8dd..af173f7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,6 +9,10 @@ var Promise = require('bluebird'); var _constants = require('./constants'); var _fields = require('./fields'); var _utils = require('./utils'); +var getCrumb = require('./yahooCrumb').getCrumb; + +// For re-export +var quote = require('./quote'); function _sanitizeHistoricalOptions(options) { if (!_.isPlainObject(options)) { @@ -80,7 +84,18 @@ function _sanitizeHistoricalOptions(options) { } if (!options.period) { - options.period = 'd'; + options.period = '1d'; + } + + options.events = 'history'; + + // Convert to yahoo v7 API + switch (options.period) { + case 'd': options.period = '1d'; break; + case 'w': options.period = '1wk'; break; + case 'm': options.period = '1mo'; break; + case 'v': options.period = '1d'; options.events = 'div'; break; + // No default case needed, options are sanitized above. } if ((options.from || options.to) && options.from.isAfter(options.to)) { @@ -215,80 +230,127 @@ function historical(options, optionalHttpRequestOptions, cb) { optionalHttpRequestOptions = undefined; } - return Promise.map(symbols, function (symbol) { - return _utils.download(_constants.HISTORICAL_URL, { - s: symbol, - a: options.from.format('MM') - 1, - b: options.from.format('DD'), - c: options.from.format('YYYY'), - d: options.to.format('MM') - 1, - e: options.to.format('DD'), - f: options.to.format('YYYY'), - g: options.period, - ignore: '.csv' - }, optionalHttpRequestOptions) - .then(_utils.parseCSV) - .then(function (data) { - return _transformHistorical(symbol, data); - }) - .catch(function (err) { - if (options.error) { - throw err; - } else { - return []; - } + console.warn("[yahoo-finance] Warning, Yahoo completely changed their API " + + "recently. We've done our best to maintain backwards compatibility in " + + "historical(), but please keep an eye out to ensure results are what you " + + "expect and report any issues. This warning will be removed in our next " + + "release."); + + return getCrumb(symbols[0]) + .then(function(crumb) { + return Promise.map(symbols, function (symbol) { + var url = _constants.HISTORICAL_DOWNLOAD_URL.replace(/\$SYMBOL/, symbol); + return _utils.download(url, { + period1: options.from.format('X'), + period2: options.to.format('X'), + interval: options.period, + events: options.events, + crumb: crumb + }, optionalHttpRequestOptions) + .then(_utils.parseCSV) + .then(function (data) { + return _transformHistorical(symbol, data); + }) + .catch(function (err) { + if (options.error) { + throw err; + } else { + return []; + } + }); + }, {concurrency: options.maxConcurrentSymbols || os.cpus().length}) + .then(function (result) { + if (options.symbols) { + return _.zipObject(symbols, result); + } else { + return result[0]; + } + }) + .catch(function (err) { + throw new Error(util.format('Failed to download data (%s)', err.message)); + }) + .nodeify(cb); }); - }, {concurrency: options.maxConcurrentSymbols || os.cpus().length}) - .then(function (result) { - if (options.symbols) { - return _.zipObject(symbols, result); - } else { - return result[0]; - } - }) - .catch(function (err) { - throw new Error(util.format('Failed to download data (%s)', err.message)); - }) - .nodeify(cb); } function snapshot(options, optionalHttpRequestOptions, cb) { + throw new Error("[yahoo-finance] snapshot() is not available in this " + + "release. Yahoo have completely changed their API recently. In our " + + "next release, we will have a compatibility-layer in place so that " + + "snapshot() works as expected for SOME options. Unfortunately, Yahoo's " + + "new API does not provide all the same data, so 100% compatibility is " + + "impossible. For this reason, please be advised that snapshot() is " + + "DEPRECATED. See our in-progress quote() API for an alternative."); + var symbols = options.symbols || _.flatten([options.symbol]); options = _.clone(options); _sanitizeSnapshotOptions(options); if(optionalHttpRequestOptions && typeof optionalHttpRequestOptions == 'function') { cb = optionalHttpRequestOptions; - optionalHttpRequestOptions = undefined; + optionalHttpRequestOptions = {}; + } else if (!optionalHttpRequestOptions) { + optionalHttpRequestOptions = {}; } - return _utils.download(_constants.SNAPSHOT_URL, { - s: symbols.join(','), - f: options.fields.join('') - }, optionalHttpRequestOptions) - .then(_utils.parseCSV) - .then(function (data) { - return _transformSnapshot(options.fields, symbols, data); - }) - .then(function (results) { - if (options.symbols) { - return _(symbols) - .zipObject(results) - .filter(function (result) { - return _.isPlainObject(result); - }) - .value(); - } else if (_.isPlainObject(results[0])) { - return results[0]; - } else { - throw new Error(results[0]); - } - }) - .catch(function (err) { - throw new Error(util.format('Failed to download data (%s)', err.message)); - }) - .nodeify(cb); + optionalHttpRequestOptions.json = true; + + if (symbols.length > 1) + throw new Error("TODO multi symbol support, requires multiple requests in new API"); + + return getCrumb(symbols[0]) + .then(function(crumb) { + var url = _constants.SNAPSHOT_URL.replace(/\$SYMBOL/, symbols[0]); + return _utils.download(url, { + // f: options.fields.join('') TODO + formatted: 'false', + crumb: crumb, + // summaryProfile,m financialData, recommendationTrend, upgradeDowngradeHistory + // earnings, price, summaryDetail, defaultKeyStatistics, calendarEvents + modules: 'price,summaryDetail', + corsDomain: 'finance.yahoo.com' + }, optionalHttpRequestOptions) + .then(function(result) { + var quoteSummary = result.quoteSummary; + if (!quoteSummary || quoteSummary.error) + throw new Error(quoteSummary.error); + + var result = quoteSummary.result; + if (!_.isArray(result) || result.length > 1) + throw new Error("quoteSummary format has changed, please report " + + "this."); + + return result[0]; + }) + /* + .then(_utils.parseCSV) + .then(function (data) { + return _transformSnapshot(options.fields, symbols, data); + }) + */ + .then(function (results) { + if (options.symbols) { + // TODO + return _(symbols) + .zipObject(results) + .filter(function (result) { + return _.isPlainObject(result); + }) + .value(); + } else if (_.isPlainObject(results)) { + // TODO, return old format? + return results; + } else { + throw new Error(results); + } + }) + .catch(function (err) { + throw new Error(util.format('Failed to download data (%s)', err.message)); + }) + .nodeify(cb); + }); } exports.historical = historical; exports.snapshot = snapshot; +exports.quote = quote; diff --git a/lib/index.spec.js b/lib/index.spec.js new file mode 100644 index 0000000..c38a734 --- /dev/null +++ b/lib/index.spec.js @@ -0,0 +1,108 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import proxyquire from 'proxyquire'; + +import { parseAndGetCrumb } from './yahooCrumb'; +import { stubbedFor } from '../tests/testUtils'; + +const should = chai.should(); +chai.use(chaiAsPromised); + +function assertValidHistoricalResult(result) { + /* + [ + { + date: Thu Nov 07 2013 00:00:00 GMT-0500 (EST), + open: 45.1, + high: 50.09, + low: 44, + close: 44.9, + volume: 117701700, + adjClose: 44.9, + symbol: 'TWTR' + }, + ... + { + date: Thu Nov 14 2013 00:00:00 GMT-0500 (EST), + open: 42.34, + high: 45.67, + low: 42.24, + close: 44.69, + volume: 11090800, + adjClose: 44.69, + symbol: 'TWTR' + } + ] + */ + + result.should.be.an('array'); + result.should.have.length.above(0); + + const row = result[0]; + row.should.include.keys('date', 'open', 'high', 'low', 'close', + 'volume', 'adjClose', 'symbol'); + row.should.be.an('object'); + row.date.should.be.an.instanceOf(Date); + row.open.should.be.a('number'); + row.high.should.be.a('number'); + row.low.should.be.a('number'); + row.close.should.be.a('number'); + row.volume.should.be.a('number'); + row.adjClose.should.be.a('number'); + row.symbol.should.be.a('string'); +} + +function assertValidSnapshotResult(result) { + /* + { + symbol: 'AAPL', + name: 'Apple Inc.', + lastTradeDate: '11/15/2013', + lastTradePriceOnly: '524.88', + dividendYield: '2.23', + peRatio: '13.29' + } + */ + result.should.be.an('object'); + result.should.include.keys('symbol', 'name'); +} + +describe('index', () => { + + describe('historical', () => { + + it('correctly transforms current Yahoo response (2017-05-21)', async () => { + + const historical = proxyquire('./index', stubbedFor('historyCsv')).historical; + const result = await historical({ + // Note, these aren't actually used in this test - data from a fixture + symbol: 'AAPL', + from: '2012-01-01' + }); + + assertValidHistoricalResult(result); + + }); + + }); + + describe('snapshot', () => { + + it('correctly transforms current Yahoo response (2017-05-21)', async () => { + + const snapshot = proxyquire('./index', stubbedFor('quoteJson')).snapshot; + const result = await snapshot({ + // Note, these aren't actually used in this test - data from a fixture + symbol: 'TSLA', + fields: ['s', 'n', 'd1', 'l1', 'y', 'r'] + }); + + assertValidSnapshotResult(result); + + }); + + }); + +}); + +export { assertValidSnapshotResult, assertValidHistoricalResult }; diff --git a/lib/quote.js b/lib/quote.js new file mode 100644 index 0000000..8a7a484 --- /dev/null +++ b/lib/quote.js @@ -0,0 +1,133 @@ +var util = require('util'); + +var _ = require('lodash'); + +var _constants = require('./constants'); +var _utils = require('./utils'); +var getCrumb = require('./yahooCrumb').getCrumb; + +var dateFields = { + summaryDetail: [ 'exDividendDate' ], + calendarEvents: [ 'exDividendDate', 'dividendDate' ], + upgradeDowngradeHistory: [ 'history.epochGradeDate' ], + price: [ 'preMarketTime', 'postMarketTime', 'regularMarketTime' ], + defaultKeyStatistics: [ + 'lastFiscalYearEnd', 'nextFiscalYearEnd', + 'mostRecentQuarter', 'lastSplitDate' + ] +}; + +function transformDates(result) { + _.each(_.keys(result), function(module) { + _.each(dateFields[module], function(field) { + + if (field.indexOf('.') === -1) { + + if (result[module][field]) + result[module][field] = new Date(result[module][field] * 1000); + + } else { + + var parts = field.split('.'); + var arrayName = parts[0]; + var subField = parts[1]; + + if (result[module][arrayName]) + _.each(result[module][arrayName], function(row) { + if (row[subField]) + row[subField] = new Date(row[subField] * 1000); + }); + + } + }); + }); + return result; +} + +function quote(options, optionalHttpRequestOptions, cb) { + console.warn("[yahoo-finance] Warning, Yahoo completely changed their API " + + "recently. quote() replaces the deprecated snapshot(), but is brand " + + "new. Please report any issues. This notice will be removed in the " + + "next release."); + + if (_.isString(options)) { + options = { symbol: options }; + if (_.isArray(optionalHttpRequestOptions)) { + options.modules = optionalHttpRequestOptions; + optionalHttpRequestOptions = undefined; + } + } + + var symbols = options.symbols || _.flatten([options.symbol]); + options = _.clone(options); + // _sanitizeSnapshotOptions(options); + if (!options.modules) + options.modules = ['price', 'summaryDetail'] + + if(optionalHttpRequestOptions && typeof optionalHttpRequestOptions == 'function') { + cb = optionalHttpRequestOptions; + optionalHttpRequestOptions = {}; + } else if (!optionalHttpRequestOptions) { + optionalHttpRequestOptions = {}; + } + + optionalHttpRequestOptions.json = true; + + if (symbols.length > 1) + throw new Error("TODO multi symbol support, requires multiple requests in new API"); + + return getCrumb(symbols[0]) + .then(function(crumb) { + var url = _constants.SNAPSHOT_URL.replace(/\$SYMBOL/, symbols[0]); + return _utils.download(url, { + // f: options.fields.join('') TODO + formatted: 'false', + crumb: crumb, + // summaryProfile, financialData, recommendationTrend, upgradeDowngradeHistory + // earnings, price, summaryDetail, defaultKeyStatistics, calendarEvents + modules: options.modules.join(','), + corsDomain: 'finance.yahoo.com' + }, optionalHttpRequestOptions) + .then(function(result) { + var quoteSummary = result.quoteSummary; + if (!quoteSummary || quoteSummary.error) + throw new Error(quoteSummary.error); + + var result = quoteSummary.result; + if (!_.isArray(result) || result.length > 1) + throw new Error("quoteSummary format has changed, please report " + + "this."); + + return result[0]; + }) + /* + .then(_utils.parseCSV) + .then(function (data) { + return _transformSnapshot(options.fields, symbols, data); + }) + */ + .then(transformDates) + .then(function (results) { + if (options.symbols) { + // TODO + return _(symbols) + .zipObject(results) + .filter(function (result) { + return _.isPlainObject(result); + }) + .value(); + } else if (_.isPlainObject(results)) { + // TODO, return old format? + return results; + } else { + throw new Error(results); + } + }) + .catch(function (err) { + throw new Error(util.format('Failed to download data (%s)', err.message)); + }) + .nodeify(cb); + }); +} + +module.exports = quote; diff --git a/lib/quote.spec.js b/lib/quote.spec.js new file mode 100644 index 0000000..86b1b46 --- /dev/null +++ b/lib/quote.spec.js @@ -0,0 +1,38 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import proxyquire from 'proxyquire'; + +import { parseAndGetCrumb } from './yahooCrumb'; +import { stubbedFor } from '../tests/testUtils'; + +const should = chai.should(); +chai.use(chaiAsPromised); + +describe('quote', () => { + + it('correctly transforms current Yahoo response (2017-05-21)', async () => { + + const fetchQuote = proxyquire('./quote', stubbedFor('quoteJson')); + const result = await fetchQuote({ + // Note, these aren't actually used in this test - data from a fixture + symbol: 'MSFT' + }); + + result.price.symbol.should.equal('MSFT'); + + // check dates + result.summaryDetail.exDividendDate.should.be.a('date'); + result.calendarEvents.exDividendDate.should.be.a('date'); + result.calendarEvents.dividendDate.should.be.a('date'); + result.upgradeDowngradeHistory.history[0].epochGradeDate.should.be.a('date'); + result.upgradeDowngradeHistory.history[1].epochGradeDate.should.be.a('date'); + result.price.preMarketTime.should.be.a('date'); + result.price.postMarketTime.should.be.a('date'); + result.price.regularMarketTime.should.be.a('date'); + result.defaultKeyStatistics.lastFiscalYearEnd.should.be.a('date'); + result.defaultKeyStatistics.nextFiscalYearEnd.should.be.a('date'); + result.defaultKeyStatistics.mostRecentQuarter.should.be.a('date'); + result.defaultKeyStatistics.lastSplitDate.should.be.a('date'); + }); + +}); diff --git a/lib/utils.js b/lib/utils.js index 3849c2b..a1d22bc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,7 +6,11 @@ var S = require('string'); var debug = require('debug')('yahoo-finance:utils'); var request = require('request-promise'); var moment = require('moment-timezone'); +var tough = require('tough-cookie'); + +var Cookie = tough.Cookie; var dateFormats = ['YYYY-MM-DD', 'MM/DD/YYYY']; +var cookiejar = new request.jar(); function camelize(text) { return S(text) @@ -15,9 +19,45 @@ function camelize(text) { .s; } +function augmentHttpRequestOptions(optionalOptions) { + if (optionalOptions && optionalOptions.jar) + throw new Error("node-yahoo-finance does not support 'jar' key in " + + "optionalHttpRequestOptions, since we need to use our own cookiejar."); + + return _.assign({}, optionalOptions, { + resolveWithFullResponse: true, + jar: cookiejar + }); +} + +function storeCookiesInJar(setCookieHeader, url, cookiejar) { + var cookies; + + if (typeof setCookieHeader === 'undefined') { + // no-op + } else if (setCookieHeader instanceof Array) { + cookies = setCookieHeader.map(Cookie.parse); + } else if (typeof setCookieHeader === 'string') { + cookies = [ Cookie.parse(setCookieHeader) ]; + } + + if (cookies) + for (var i=0; i < cookies.length; i++) { + // note: async, possible timing issues? TODO + cookiejar.setCookie(cookies[i], url); + } +} + function download(uri, qs, optionalHttpRequestOptions) { + var finalHttpRequestOptions = augmentHttpRequestOptions(optionalHttpRequestOptions); + debug(url.format({pathname: uri, query: qs})); - return request(_.extend({uri: uri, qs: qs}, optionalHttpRequestOptions)); + return request(_.extend({uri: uri, qs: qs}, finalHttpRequestOptions)) + .then(function(res) { + storeCookiesInJar(res.headers['set-cookie'], uri, cookiejar); + return (optionalHttpRequestOptions && + optionalHttpRequestOptions.resolveWithFullResponse) ? res : res.body; + }); } function parseCSV(text) { @@ -66,6 +106,7 @@ function toInt(value, valueForNaN) { } } +exports.cookiejar = cookiejar; exports.camelize = camelize; exports.download = download; exports.parseCSV = parseCSV; diff --git a/lib/yahooCrumb.js b/lib/yahooCrumb.js new file mode 100644 index 0000000..652158b --- /dev/null +++ b/lib/yahooCrumb.js @@ -0,0 +1,101 @@ +var util = require('util'); + +var Promise = require('bluebird'); +var _ = require('lodash'); +var debug = require('debug')('yahoo-finance:yahooCrumb'); + +var _constants = require('./constants'); +var _utils = require('./utils'); + +// Faster but probably more brittle option: +// var crumbRE = /"CrumbStore":\{"crumb":"(.+?)"\}/; + +var dataRE = /^root.App.main = (\{.*\});$/m; +function parseAndGetCrumb(body) { + var match = dataRE.exec(body); + if (!match) { + throw new Error("Could not match root.App.main line. If this happens " + + "consistently, Yahoo output has changed and you should open a bug " + + "report."); + } + + var data; + try { + data = JSON.parse(match[1]); + } catch (err) { + console.error(err); + throw new Error("root.App.main line (or regexp) did not capture valid " + + "JSON. If this happens consistently, please open a bug report."); + } + + var crumb; + if (!data.context) + throw new Error("root.Api.main JSON structure has changed. If this " + + "happens consistently, please open a bug report."); + + var dispatcher = data.context.dispatcher; + crumb = dispatcher && + dispatcher.stores && + dispatcher.stores.CrumbStore && + dispatcher.stores.CrumbStore.crumb; + + if (!crumb) { + console.warn('root.Api.main context.dispatcher.stores.CrumbStore.crumb ' + + 'structure no longer exists, please open an issue.'); + + var plugins = data.context.plugins; + crumb = plugins && + plugins.ServicePlugin && + plugins.ServicePlugin.xhrContent && + plugins.ServicePlugin.xhrContext.crumb; + + if (!crumb) + throw new Error('root.Api.main ' + + 'context.plugins.ServicePlugin.xhrContext.crumb' + + 'structure no longer exists, please open an issue.') + } + + return crumb; +} + +var crumb = null; +var rpOpts = { resolveWithFullResponse: true }; + +function fetch(symbol) { + var url = _constants.HISTORICAL_CRUMB_URL.replace(/\$SYMBOL/, symbol); + return _utils.download(url, '', rpOpts) + .then(function (res) { + crumb = parseAndGetCrumb(res.body); + return crumb; + }) + .catch(function(err){ + throw new Error(util.format('Failed to get crumb (%s)', err.message)); + }); +} + +function getCrumb(symbol) { + // Invalidate the crumb if the cookie is expired. + if (crumb) { + // Note: getCookies() won't return expired cookies and we rely on this. + var cookies = _utils.cookiejar.getCookies(_constants.HISTORICAL_CRUMB_URL); + var bCookie = _.find(cookies, { key: 'B' }); + if (!bCookie) { + debug('No valid cookies, invalidating crumb'); + crumb = null; + } + } + + if (crumb) { + debug('Returning cached crumb'); + return Promise.resolve(crumb); + } else { + debug('Fetching a new cookie & crumb'); + return fetch(symbol).then(function(crumb) { return crumb; }) + } +} + +// API +exports.getCrumb = getCrumb; + +// for testing +exports.parseAndGetCrumb = parseAndGetCrumb; diff --git a/lib/yahooCrumb.spec.js b/lib/yahooCrumb.spec.js new file mode 100644 index 0000000..e034e79 --- /dev/null +++ b/lib/yahooCrumb.spec.js @@ -0,0 +1,68 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import proxyquire from 'proxyquire'; + +// for nodeify to work with stubbed out functions +import BluebirdPromise from 'bluebird'; + +import tough from 'tough-cookie'; +import requestPromise from 'request-promise'; + +import _constants from './constants'; +import { parseAndGetCrumb } from './yahooCrumb'; +import { getFile, STATIC_CRUMB } from '../tests/testUtils'; + +const Cookie = tough.Cookie; + +const should = chai.should(); +chai.use(chaiAsPromised); + +describe('yahooCrumb', () => { + + describe('getCrumb', () => { + + it('works with current Yahoo response (2017-05-21)', () => { + const body = getFile('historyHtml'); + const crumb = parseAndGetCrumb(body); + crumb.should.equal(STATIC_CRUMB); + }); + + it('gets a new crumb if an existing cookie is expired', async () => { + const cookiejar = new requestPromise.jar(); + + let fileToGet; + const getCrumb = proxyquire('./yahooCrumb', { + './utils': { + cookiejar, + download: () => BluebirdPromise.resolve({ body: getFile(fileToGet) }), + } + }).getCrumb; + + // create an un-expired cookie + const cookie = new Cookie({ + key: 'B', + value: 'notImportant', + expires: new Date(Date.now() + 10000), + domain: 'yahoo.com', + path: '/', + }); + // async method, but works async for the default memoryStore + cookiejar.setCookie(cookie, _constants.HISTORICAL_CRUMB_URL); + + fileToGet = 'historyHtml'; + const initialCrumb = await getCrumb('IGNORED'); + + // expire the cookie + cookie.expires = new Date(Date.now() - 5000); + // async method, but works async for the default memoryStore + cookiejar.setCookie(cookie, _constants.HISTORICAL_CRUMB_URL); + + fileToGet = 'historyHtml2'; + const nextCrumb = await getCrumb('IGNORED'); + + initialCrumb.should.not.equal(nextCrumb); + }); + + }); + +}); diff --git a/package.json b/package.json index bbf3825..ecb82cb 100644 --- a/package.json +++ b/package.json @@ -33,18 +33,30 @@ "moment": "^2.17.1", "moment-timezone": "^0.5.10", "request": "^2.79.0", - "request-promise": "4.1.1", - "string": "^3.3.3" + "request-promise": "^4.2.1", + "string": "^3.3.3", + "tough-cookie": "^2.3.2" }, "devDependencies": { + "babel-cli": "^6.24.1", + "babel-core": "^6.24.1", + "babel-polyfill": "^6.23.0", + "babel-preset-env": "^1.5.0", + "chai": "^3.5.0", + "chai-as-promised": "^6.0.0", "colors": "1.1.2", + "coveralls": "^2.13.1", "grunt": "1.0.1", "grunt-concurrent": "2.3.1", "grunt-contrib-jshint": "^1.1.0", "grunt-contrib-watch": "1.0.0", - "load-grunt-tasks": "3.5.2" + "load-grunt-tasks": "3.5.2", + "mocha": "^3.4.1", + "proxyquire": "^1.8.0" }, "scripts": { - "prepublish": "npm prune" + "prepublish": "npm prune", + "test": "mocha --require babel-polyfill --compilers js:babel-core/register \"lib/**/*.spec.js\" tests", + "coverage": "istanbul cover _mocha --require babel-polyfill --compilers js:babel-core/register \"lib/**/*.spec.js\" tests -R spec" } } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..8240057 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,21 @@ +# yahoo-finance tests + +## Directory structure + +### Unit tests + +Live adjacent to the file they're testing with a `.spec.js` suffix. + +### Integration tests + +Live in this directory. + +### Fixtures + +Sample data used by both the unit and integration tests, live in +`tests/fixtures`. + +## ES version + +Tests are written in ES7, and transpiled for your current node version with +babel. diff --git a/tests/fixtures/history.html b/tests/fixtures/history.html new file mode 100644 index 0000000..e0f18e9 --- /dev/null +++ b/tests/fixtures/history.html @@ -0,0 +1,46 @@ +
| Date | Open | High | Low | Close | Adj Close* | Volume |
|---|---|---|---|---|---|---|
| May 16, 2017 | 68.23 | 69.44 | 68.16 | 69.41 | 69.41 | 33,670,800 |
| May 16, 2017 | 0.39 Dividend | |||||
| May 15, 2017 | 68.14 | 68.48 | 67.57 | 68.43 | 68.43 | 31,530,300 |
| May 12, 2017 | 68.61 | 68.61 | 68.04 | 68.38 | 68.38 | 18,714,100 |
| May 11, 2017 | 68.36 | 68.73 | 68.12 | 68.46 | 68.46 | 28,789,400 |
| May 10, 2017 | 68.99 | 69.56 | 68.92 | 69.31 | 69.31 | 17,977,800 |
| May 09, 2017 | 68.86 | 69.28 | 68.68 | 69.04 | 69.04 | 22,858,400 |
| May 08, 2017 | 68.97 | 69.05 | 68.42 | 68.94 | 68.94 | 18,566,100 |
| May 05, 2017 | 68.90 | 69.03 | 68.49 | 69.00 | 69.00 | 19,128,800 |
| May 04, 2017 | 69.03 | 69.08 | 68.64 | 68.81 | 68.81 | 21,749,400 |
| May 03, 2017 | 69.38 | 69.38 | 68.71 | 69.08 | 69.08 | 28,928,000 |
| May 02, 2017 | 69.71 | 69.71 | 69.13 | 69.30 | 69.30 | 23,906,100 |
| May 01, 2017 | 68.68 | 69.55 | 68.50 | 69.41 | 69.41 | 31,954,400 |
| Apr 28, 2017 | 68.91 | 69.14 | 67.69 | 68.46 | 68.46 | 39,548,800 |
| Apr 27, 2017 | 68.15 | 68.38 | 67.58 | 68.27 | 68.27 | 34,971,000 |
| Apr 26, 2017 | 68.08 | 68.31 | 67.62 | 67.83 | 67.83 | 26,190,800 |
| Apr 25, 2017 | 67.90 | 68.04 | 67.60 | 67.92 | 67.92 | 30,242,700 |
| Apr 24, 2017 | 67.48 | 67.66 | 67.10 | 67.53 | 67.53 | 29,770,000 |
| Apr 21, 2017 | 65.67 | 66.70 | 65.45 | 66.40 | 66.40 | 32,522,600 |
| Apr 20, 2017 | 65.46 | 65.75 | 65.14 | 65.50 | 65.50 | 22,299,500 |
| Apr 19, 2017 | 65.65 | 65.75 | 64.89 | 65.04 | 65.04 | 26,992,800 |
| Apr 18, 2017 | 65.33 | 65.71 | 65.16 | 65.39 | 65.39 | 15,155,600 |
| Apr 17, 2017 | 65.04 | 65.49 | 65.01 | 65.48 | 65.48 | 16,689,300 |
| Apr 13, 2017 | 65.29 | 65.86 | 64.95 | 64.95 | 64.95 | 17,896,500 |
| Apr 12, 2017 | 65.42 | 65.51 | 65.11 | 65.23 | 65.23 | 17,108,500 |
| Apr 11, 2017 | 65.60 | 65.61 | 64.85 | 65.48 | 65.48 | 18,791,500 |
| Apr 10, 2017 | 65.61 | 65.82 | 65.36 | 65.53 | 65.53 | 17,952,700 |
| Apr 07, 2017 | 65.85 | 65.96 | 65.44 | 65.68 | 65.68 | 14,108,500 |
| Apr 06, 2017 | 65.60 | 66.06 | 65.48 | 65.73 | 65.73 | 18,103,500 |
| Apr 05, 2017 | 66.30 | 66.35 | 65.44 | 65.56 | 65.56 | 21,448,600 |
| Apr 04, 2017 | 65.39 | 65.81 | 65.28 | 65.73 | 65.73 | 12,997,400 |
| Apr 03, 2017 | 65.81 | 65.94 | 65.19 | 65.55 | 65.55 | 20,400,900 |
| Mar 31, 2017 | 65.65 | 66.19 | 65.45 | 65.86 | 65.86 | 21,040,300 |
| Mar 30, 2017 | 65.42 | 65.98 | 65.36 | 65.71 | 65.71 | 15,122,800 |
| Mar 29, 2017 | 65.12 | 65.50 | 64.95 | 65.47 | 65.47 | 13,618,400 |
| Mar 28, 2017 | 64.96 | 65.47 | 64.65 | 65.29 | 65.29 | 20,080,400 |
| Mar 27, 2017 | 64.63 | 65.22 | 64.35 | 65.10 | 65.10 | 18,614,700 |
| Mar 24, 2017 | 65.36 | 65.45 | 64.76 | 64.98 | 64.98 | 22,617,100 |
| Mar 23, 2017 | 64.94 | 65.24 | 64.77 | 64.87 | 64.87 | 19,269,200 |
| Mar 22, 2017 | 64.12 | 65.14 | 64.12 | 65.03 | 65.03 | 20,680,000 |
| Mar 21, 2017 | 65.19 | 65.50 | 64.13 | 64.21 | 64.21 | 26,640,500 |
| Mar 20, 2017 | 64.91 | 65.18 | 64.72 | 64.93 | 64.93 | 14,598,100 |
| Mar 17, 2017 | 64.91 | 65.24 | 64.68 | 64.87 | 64.87 | 49,219,700 |
| Mar 16, 2017 | 64.75 | 64.76 | 64.30 | 64.64 | 64.64 | 20,674,300 |
| Mar 15, 2017 | 64.55 | 64.92 | 64.25 | 64.75 | 64.75 | 24,833,800 |
| Mar 14, 2017 | 64.53 | 64.55 | 64.15 | 64.41 | 64.41 | 14,280,200 |
| Mar 13, 2017 | 65.01 | 65.19 | 64.57 | 64.71 | 64.71 | 20,100,000 |
| Mar 10, 2017 | 65.11 | 65.26 | 64.75 | 64.93 | 64.93 | 19,538,200 |
| Mar 09, 2017 | 65.19 | 65.20 | 64.48 | 64.73 | 64.73 | 19,846,800 |
| Mar 08, 2017 | 64.26 | 65.08 | 64.25 | 64.99 | 64.99 | 21,510,900 |
| Mar 07, 2017 | 64.19 | 64.78 | 64.19 | 64.40 | 64.40 | 18,521,000 |
| Mar 06, 2017 | 63.97 | 64.56 | 63.81 | 64.27 | 64.27 | 18,750,300 |
| Mar 03, 2017 | 63.99 | 64.28 | 63.62 | 64.25 | 64.25 | 18,135,900 |
| Mar 02, 2017 | 64.69 | 64.75 | 63.88 | 64.01 | 64.01 | 24,539,600 |
| Mar 01, 2017 | 64.13 | 64.99 | 64.02 | 64.94 | 64.94 | 26,937,500 |
| Feb 28, 2017 | 64.08 | 64.20 | 63.76 | 63.98 | 63.98 | 23,239,800 |
| Feb 27, 2017 | 64.54 | 64.54 | 64.05 | 64.23 | 64.23 | 15,871,500 |
| Feb 24, 2017 | 64.53 | 64.80 | 64.14 | 64.62 | 64.62 | 21,796,800 |
| Feb 23, 2017 | 64.42 | 64.73 | 64.19 | 64.62 | 64.62 | 20,273,100 |
| Feb 22, 2017 | 64.33 | 64.39 | 64.05 | 64.36 | 64.36 | 19,292,700 |
| Feb 21, 2017 | 64.61 | 64.95 | 64.45 | 64.49 | 64.49 | 20,655,900 |
| Feb 17, 2017 | 64.47 | 64.69 | 64.30 | 64.62 | 64.62 | 21,248,800 |
| Feb 16, 2017 | 64.74 | 65.24 | 64.44 | 64.52 | 64.52 | 20,546,300 |
| Feb 15, 2017 | 64.50 | 64.57 | 64.16 | 64.53 | 64.53 | 17,005,200 |
| Feb 14, 2017 | 64.41 | 64.72 | 64.02 | 64.57 | 64.57 | 23,108,400 |
| Feb 14, 2017 | 0.39 Dividend | |||||
| Feb 13, 2017 | 64.24 | 64.86 | 64.13 | 64.72 | 64.72 | 22,920,100 |
| Feb 10, 2017 | 64.25 | 64.30 | 63.98 | 64.00 | 64.00 | 18,170,700 |
| Feb 09, 2017 | 63.52 | 64.44 | 63.32 | 64.06 | 64.06 | 22,644,400 |
| Feb 08, 2017 | 63.57 | 63.81 | 63.22 | 63.34 | 63.34 | 18,096,400 |
| Feb 07, 2017 | 63.74 | 63.78 | 63.23 | 63.43 | 63.43 | 20,277,200 |
| Feb 06, 2017 | 63.50 | 63.65 | 63.14 | 63.64 | 63.64 | 19,796,400 |
| Feb 03, 2017 | 63.50 | 63.70 | 63.07 | 63.68 | 63.68 | 30,301,800 |
| Feb 02, 2017 | 63.25 | 63.41 | 62.75 | 63.17 | 63.17 | 45,827,000 |
| Feb 01, 2017 | 64.36 | 64.62 | 63.47 | 63.58 | 63.58 | 39,671,500 |
| Jan 31, 2017 | 64.86 | 65.15 | 64.26 | 64.65 | 64.65 | 25,270,500 |
| Jan 30, 2017 | 65.69 | 65.79 | 64.80 | 65.13 | 65.13 | 31,651,400 |
| Jan 27, 2017 | 65.39 | 65.91 | 64.89 | 65.78 | 65.78 | 44,818,000 |
| Jan 26, 2017 | 64.12 | 64.54 | 63.55 | 64.27 | 64.27 | 43,554,600 |
| Jan 25, 2017 | 63.95 | 64.10 | 63.45 | 63.68 | 63.68 | 23,672,700 |
| Jan 24, 2017 | 63.20 | 63.74 | 62.94 | 63.52 | 63.52 | 24,672,900 |
| Jan 23, 2017 | 62.70 | 63.12 | 62.57 | 62.96 | 62.96 | 23,097,600 |
| Jan 20, 2017 | 62.67 | 62.82 | 62.37 | 62.74 | 62.74 | 30,213,500 |
| Jan 19, 2017 | 62.24 | 62.98 | 62.20 | 62.30 | 62.30 | 18,451,700 |
| Jan 18, 2017 | 62.67 | 62.70 | 62.12 | 62.50 | 62.50 | 19,670,100 |
| Jan 17, 2017 | 62.68 | 62.70 | 62.03 | 62.53 | 62.53 | 20,664,000 |
| Jan 13, 2017 | 62.62 | 62.87 | 62.35 | 62.70 | 62.70 | 19,422,300 |
| Jan 12, 2017 | 63.06 | 63.40 | 61.95 | 62.61 | 62.61 | 20,968,200 |
| Jan 11, 2017 | 62.61 | 63.23 | 62.43 | 63.19 | 63.19 | 21,517,300 |
| Jan 10, 2017 | 62.73 | 63.07 | 62.28 | 62.62 | 62.62 | 18,593,000 |
| Jan 09, 2017 | 62.76 | 63.08 | 62.54 | 62.64 | 62.64 | 20,256,600 |
| Jan 06, 2017 | 62.30 | 63.15 | 62.04 | 62.84 | 62.84 | 19,922,900 |
| Jan 05, 2017 | 62.19 | 62.66 | 62.03 | 62.30 | 62.30 | 24,876,000 |
| Jan 04, 2017 | 62.48 | 62.75 | 62.12 | 62.30 | 62.30 | 21,340,000 |
| Jan 03, 2017 | 62.79 | 62.84 | 62.13 | 62.58 | 62.58 | 20,694,100 |
| Dec 30, 2016 | 62.96 | 62.99 | 62.03 | 62.14 | 62.14 | 25,579,900 |
| Dec 29, 2016 | 62.86 | 63.20 | 62.73 | 62.90 | 62.90 | 10,250,600 |
| Dec 28, 2016 | 63.40 | 63.40 | 62.83 | 62.99 | 62.99 | 14,653,300 |
| Dec 27, 2016 | 63.21 | 64.07 | 63.21 | 63.28 | 63.28 | 11,763,200 |
| Dec 23, 2016 | 63.45 | 63.54 | 62.80 | 63.24 | 63.24 | 12,403,800 |
| *Close price adjusted for dividends and splits. | ||||||
| Date | Open | High | Low | Close | Adj Close* | Volume |
|---|---|---|---|---|---|---|
| May 22, 2017 | 958.00 | 964.38 | 956.56 | 960.82 | 960.82 | 421,435 |
| May 19, 2017 | 952.82 | 959.56 | 952.00 | 954.65 | 954.65 | 1,344,200 |
| May 18, 2017 | 943.20 | 954.18 | 941.27 | 950.50 | 950.50 | 1,800,500 |
| May 17, 2017 | 959.70 | 960.99 | 940.06 | 942.17 | 942.17 | 2,449,100 |
| May 16, 2017 | 963.55 | 965.90 | 960.35 | 964.61 | 964.61 | 1,101,500 |
| May 15, 2017 | 955.29 | 962.70 | 952.82 | 959.22 | 959.22 | 1,337,700 |
| May 12, 2017 | 957.85 | 957.98 | 952.06 | 955.14 | 955.14 | 1,214,900 |
| May 11, 2017 | 951.29 | 957.99 | 948.61 | 955.89 | 955.89 | 1,031,100 |
| May 10, 2017 | 956.22 | 956.71 | 949.84 | 954.84 | 954.84 | 1,146,000 |
| May 09, 2017 | 961.33 | 962.20 | 954.40 | 956.71 | 956.71 | 1,687,900 |
| May 08, 2017 | 947.45 | 960.99 | 947.40 | 958.69 | 958.69 | 1,876,700 |
| May 05, 2017 | 956.72 | 958.44 | 948.10 | 950.28 | 950.28 | 1,615,500 |
| May 04, 2017 | 950.29 | 959.14 | 947.37 | 954.72 | 954.72 | 1,937,600 |
| May 03, 2017 | 936.05 | 950.20 | 935.21 | 948.45 | 948.45 | 1,824,800 |
| May 02, 2017 | 933.27 | 942.99 | 931.00 | 937.09 | 937.09 | 1,751,300 |
| May 01, 2017 | 924.15 | 935.82 | 920.80 | 932.82 | 932.82 | 2,327,800 |
| Apr 28, 2017 | 929.00 | 935.90 | 923.22 | 924.52 | 924.52 | 3,845,900 |
| Apr 27, 2017 | 890.00 | 893.38 | 887.18 | 891.44 | 891.44 | 2,342,300 |
| Apr 26, 2017 | 891.39 | 892.99 | 885.15 | 889.14 | 889.14 | 1,323,300 |
| Apr 25, 2017 | 882.26 | 892.25 | 879.28 | 888.84 | 888.84 | 2,038,000 |
| Apr 24, 2017 | 868.44 | 879.96 | 866.11 | 878.93 | 878.93 | 1,696,500 |
| Apr 21, 2017 | 860.62 | 862.44 | 857.73 | 858.95 | 858.95 | 1,172,900 |
| Apr 20, 2017 | 859.74 | 863.93 | 857.50 | 860.08 | 860.08 | 1,188,800 |
| Apr 19, 2017 | 857.39 | 860.20 | 853.53 | 856.51 | 856.51 | 1,080,300 |
| Apr 18, 2017 | 852.54 | 857.39 | 851.25 | 853.99 | 853.99 | 936,200 |
| Apr 17, 2017 | 841.38 | 855.64 | 841.03 | 855.13 | 855.13 | 1,049,100 |
| Apr 13, 2017 | 841.04 | 843.73 | 837.85 | 840.18 | 840.18 | 1,073,700 |
| Apr 12, 2017 | 838.46 | 843.72 | 837.59 | 841.46 | 841.46 | 1,135,800 |
| Apr 11, 2017 | 841.70 | 844.63 | 834.60 | 839.88 | 839.88 | 974,300 |
| Apr 10, 2017 | 841.54 | 846.74 | 840.79 | 841.70 | 841.70 | 1,046,200 |
| Apr 07, 2017 | 845.00 | 845.88 | 837.30 | 842.10 | 842.10 | 1,111,600 |
| Apr 06, 2017 | 849.50 | 853.59 | 844.00 | 845.10 | 845.10 | 1,533,600 |
| Apr 05, 2017 | 854.71 | 860.59 | 847.52 | 848.91 | 848.91 | 1,855,200 |
| Apr 04, 2017 | 848.00 | 853.00 | 847.51 | 852.57 | 852.57 | 1,348,500 |
| Apr 03, 2017 | 848.75 | 859.00 | 847.53 | 856.75 | 856.75 | 1,969,400 |
| Mar 31, 2017 | 846.83 | 849.56 | 845.24 | 847.80 | 847.80 | 1,441,000 |
| Mar 30, 2017 | 851.98 | 852.00 | 846.77 | 849.48 | 849.48 | 949,400 |
| Mar 29, 2017 | 842.75 | 851.59 | 841.38 | 849.87 | 849.87 | 1,457,300 |
| Mar 28, 2017 | 839.69 | 845.40 | 832.27 | 840.63 | 840.63 | 1,519,200 |
| Mar 27, 2017 | 828.09 | 841.38 | 824.30 | 838.51 | 838.51 | 1,934,600 |
| Mar 24, 2017 | 842.00 | 844.00 | 829.10 | 835.14 | 835.14 | 2,105,700 |
| Mar 23, 2017 | 841.39 | 841.69 | 833.00 | 839.65 | 839.65 | 3,287,700 |
| Mar 22, 2017 | 849.48 | 855.35 | 847.00 | 849.80 | 849.80 | 1,366,700 |
| Mar 21, 2017 | 870.06 | 873.47 | 847.69 | 850.14 | 850.14 | 2,538,000 |
| Mar 20, 2017 | 869.48 | 870.34 | 864.67 | 867.91 | 867.91 | 1,542,200 |
| Mar 17, 2017 | 873.68 | 874.42 | 868.37 | 872.37 | 872.37 | 1,868,300 |
| Mar 16, 2017 | 870.53 | 872.71 | 867.52 | 870.00 | 870.00 | 1,104,500 |
| Mar 15, 2017 | 867.94 | 869.88 | 861.30 | 868.39 | 868.39 | 1,332,900 |
| Mar 14, 2017 | 863.75 | 867.58 | 860.13 | 865.91 | 865.91 | 1,061,700 |
| Mar 13, 2017 | 860.83 | 867.13 | 860.82 | 864.58 | 864.58 | 1,166,600 |
| Mar 10, 2017 | 862.70 | 864.23 | 857.61 | 861.41 | 861.41 | 1,336,600 |
| Mar 09, 2017 | 853.69 | 860.71 | 852.67 | 857.84 | 857.84 | 1,347,700 |
| Mar 08, 2017 | 853.12 | 856.93 | 851.25 | 853.64 | 853.64 | 1,028,800 |
| Mar 07, 2017 | 847.26 | 853.33 | 845.52 | 851.15 | 851.15 | 1,038,700 |
| Mar 06, 2017 | 846.86 | 848.94 | 841.17 | 847.27 | 847.27 | 1,047,900 |
| Mar 03, 2017 | 848.94 | 850.82 | 844.71 | 849.08 | 849.08 | 1,005,000 |
| Mar 02, 2017 | 856.31 | 856.49 | 848.72 | 849.85 | 849.85 | 1,250,900 |
| Mar 01, 2017 | 851.38 | 858.00 | 849.02 | 856.75 | 856.75 | 1,818,700 |
| Feb 28, 2017 | 847.35 | 848.83 | 841.44 | 844.93 | 844.93 | 1,383,100 |
| Feb 27, 2017 | 844.95 | 850.67 | 843.01 | 849.67 | 849.67 | 1,010,300 |
| Feb 24, 2017 | 847.65 | 848.36 | 842.96 | 847.81 | 847.81 | 1,346,200 |
| Feb 23, 2017 | 851.08 | 852.62 | 842.50 | 851.00 | 851.00 | 1,386,700 |
| Feb 22, 2017 | 848.00 | 853.79 | 846.71 | 851.36 | 851.36 | 1,224,400 |
| Feb 21, 2017 | 847.99 | 852.20 | 846.55 | 849.27 | 849.27 | 1,260,300 |
| Feb 17, 2017 | 841.31 | 846.94 | 839.78 | 846.55 | 846.55 | 1,461,200 |
| Feb 16, 2017 | 838.50 | 842.69 | 837.26 | 842.17 | 842.17 | 1,005,100 |
| Feb 15, 2017 | 838.81 | 841.77 | 836.22 | 837.32 | 837.32 | 1,357,200 |
| Feb 14, 2017 | 839.77 | 842.00 | 835.83 | 840.03 | 840.03 | 1,363,300 |
| Feb 13, 2017 | 837.70 | 841.74 | 836.25 | 838.96 | 838.96 | 1,295,700 |
| Feb 10, 2017 | 832.95 | 837.15 | 830.51 | 834.85 | 834.85 | 1,415,100 |
| Feb 09, 2017 | 831.73 | 831.98 | 826.50 | 830.06 | 830.06 | 1,194,200 |
| Feb 08, 2017 | 830.53 | 834.25 | 825.11 | 829.88 | 829.88 | 1,302,200 |
| Feb 07, 2017 | 825.50 | 831.92 | 823.29 | 829.23 | 829.23 | 1,666,600 |
| Feb 06, 2017 | 820.92 | 822.39 | 814.29 | 821.62 | 821.62 | 1,350,900 |
| Feb 03, 2017 | 823.13 | 826.13 | 819.35 | 820.13 | 820.13 | 1,528,100 |
| Feb 02, 2017 | 815.00 | 824.56 | 812.05 | 818.26 | 818.26 | 1,689,200 |
| Feb 01, 2017 | 824.00 | 824.00 | 812.25 | 815.24 | 815.24 | 2,251,000 |
| Jan 31, 2017 | 819.50 | 823.07 | 813.40 | 820.19 | 820.19 | 2,020,200 |
| Jan 30, 2017 | 837.06 | 837.23 | 821.03 | 823.83 | 823.83 | 3,516,900 |
| Jan 27, 2017 | 859.00 | 867.00 | 841.90 | 845.03 | 845.03 | 3,752,500 |
| Jan 26, 2017 | 859.05 | 861.00 | 850.52 | 856.98 | 856.98 | 3,493,300 |
| Jan 25, 2017 | 853.55 | 858.79 | 849.74 | 858.45 | 858.45 | 1,655,400 |
| Jan 24, 2017 | 846.98 | 851.52 | 842.28 | 849.53 | 849.53 | 1,688,400 |
| Jan 23, 2017 | 831.61 | 845.54 | 828.70 | 844.43 | 844.43 | 2,457,400 |
| Jan 20, 2017 | 829.09 | 829.24 | 824.60 | 828.17 | 828.17 | 1,306,200 |
| Jan 19, 2017 | 829.00 | 833.00 | 823.96 | 824.37 | 824.37 | 1,070,500 |
| Jan 18, 2017 | 829.80 | 829.81 | 824.08 | 829.02 | 829.02 | 1,027,700 |
| Jan 17, 2017 | 830.00 | 830.18 | 823.20 | 827.46 | 827.46 | 1,440,900 |
| Jan 13, 2017 | 831.00 | 834.65 | 829.52 | 830.94 | 830.94 | 1,290,200 |
| Jan 12, 2017 | 828.38 | 830.38 | 821.01 | 829.53 | 829.53 | 1,349,500 |
| Jan 11, 2017 | 826.62 | 829.90 | 821.47 | 829.86 | 829.86 | 1,325,400 |
| Jan 10, 2017 | 827.07 | 829.41 | 823.14 | 826.01 | 826.01 | 1,197,400 |
| Jan 09, 2017 | 826.37 | 830.43 | 821.62 | 827.18 | 827.18 | 1,406,800 |
| Jan 06, 2017 | 814.99 | 828.96 | 811.50 | 825.21 | 825.21 | 2,017,100 |
| Jan 05, 2017 | 807.50 | 813.74 | 805.92 | 813.02 | 813.02 | 1,340,500 |
| Jan 04, 2017 | 809.89 | 813.43 | 804.11 | 807.77 | 807.77 | 1,515,300 |
| Jan 03, 2017 | 800.62 | 811.44 | 796.89 | 808.01 | 808.01 | 1,959,000 |
| Dec 30, 2016 | 803.21 | 803.29 | 789.62 | 792.45 | 792.45 | 1,735,900 |
| Dec 29, 2016 | 802.33 | 805.75 | 798.14 | 802.88 | 802.88 | 1,057,400 |
| Dec 28, 2016 | 813.33 | 813.33 | 802.44 | 804.57 | 804.57 | 1,214,800 |
| *Close price adjusted for dividends and splits. | ||||||
Estimate
Actual
Earnings
Revenue
| Strong Buy | |
| Buy | |
| Hold | |
| Underperform | |
| Sell |
| Initiated | Barclays: to Overweight | 3/29/2017 | |
| Downgrade | Pivotal Research Group: Buy to Hold | 3/20/2017 | |
| Initiated | Aegis Capital: to Buy | 12/21/2016 | |
| Upgrade | Pivotal Research Group: Hold to Buy | 4/11/2016 | |
| Upgrade | Goldman: Neutral to Buy | 8/26/2015 | |
| Upgrade | Atlantic Equities: Neutral to Overweight | 8/19/2015 |
1600 Amphitheatre Parkway
Mountain View, CA 94043
United States
650-253-0000
http://abc.xyz
Sector: Technology
Industry: Internet Information Providers
Full Time Employees: 73,992
Alphabet Inc., through its subsidiaries, provides online advertising services in the United States, the United Kingdom, and rest of the world. The company offers performance and brand advertising services. It operates through Google and Other Bets segments. The Google segment includes principal Internet products, such as Search, Ads, Commerce, Maps, YouTube, Google Cloud, Android, Chrome, and Google Play, as well as technical infrastructure and newer efforts, including Virtual Reality. This segment also sells digital contents, apps and cloud offerings, and hardware products. The Other Bets segment includes businesses, such as Access, Calico, CapitalG, GV, Nest, Verily, Waymo, X, and Google Fiber. Alphabet Inc. was founded in 1998 and is headquartered in Mountain View, California.