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 @@ +MSFT Historical Prices | Microsoft Corporation Stock - Yahoo Finance
U.S. Markets open in 25 mins.

Microsoft Corporation (MSFT)


NasdaqGS - NasdaqGS Real Time Price. Currency in USD
Add to watchlist
69.41+0.98 (+1.43%)
At close: 4:00PM EDT

68.80 -0.61 (-0.88%)
Pre-Market: 9:05AM EDT

People also watch
INTCCSCOAAPLIBMGOOG
DateOpenHighLowCloseAdj Close*Volume
May 16, 201768.2369.4468.1669.4169.4133,670,800
May 16, 20170.39 Dividend
May 15, 201768.1468.4867.5768.4368.4331,530,300
May 12, 201768.6168.6168.0468.3868.3818,714,100
May 11, 201768.3668.7368.1268.4668.4628,789,400
May 10, 201768.9969.5668.9269.3169.3117,977,800
May 09, 201768.8669.2868.6869.0469.0422,858,400
May 08, 201768.9769.0568.4268.9468.9418,566,100
May 05, 201768.9069.0368.4969.0069.0019,128,800
May 04, 201769.0369.0868.6468.8168.8121,749,400
May 03, 201769.3869.3868.7169.0869.0828,928,000
May 02, 201769.7169.7169.1369.3069.3023,906,100
May 01, 201768.6869.5568.5069.4169.4131,954,400
Apr 28, 201768.9169.1467.6968.4668.4639,548,800
Apr 27, 201768.1568.3867.5868.2768.2734,971,000
Apr 26, 201768.0868.3167.6267.8367.8326,190,800
Apr 25, 201767.9068.0467.6067.9267.9230,242,700
Apr 24, 201767.4867.6667.1067.5367.5329,770,000
Apr 21, 201765.6766.7065.4566.4066.4032,522,600
Apr 20, 201765.4665.7565.1465.5065.5022,299,500
Apr 19, 201765.6565.7564.8965.0465.0426,992,800
Apr 18, 201765.3365.7165.1665.3965.3915,155,600
Apr 17, 201765.0465.4965.0165.4865.4816,689,300
Apr 13, 201765.2965.8664.9564.9564.9517,896,500
Apr 12, 201765.4265.5165.1165.2365.2317,108,500
Apr 11, 201765.6065.6164.8565.4865.4818,791,500
Apr 10, 201765.6165.8265.3665.5365.5317,952,700
Apr 07, 201765.8565.9665.4465.6865.6814,108,500
Apr 06, 201765.6066.0665.4865.7365.7318,103,500
Apr 05, 201766.3066.3565.4465.5665.5621,448,600
Apr 04, 201765.3965.8165.2865.7365.7312,997,400
Apr 03, 201765.8165.9465.1965.5565.5520,400,900
Mar 31, 201765.6566.1965.4565.8665.8621,040,300
Mar 30, 201765.4265.9865.3665.7165.7115,122,800
Mar 29, 201765.1265.5064.9565.4765.4713,618,400
Mar 28, 201764.9665.4764.6565.2965.2920,080,400
Mar 27, 201764.6365.2264.3565.1065.1018,614,700
Mar 24, 201765.3665.4564.7664.9864.9822,617,100
Mar 23, 201764.9465.2464.7764.8764.8719,269,200
Mar 22, 201764.1265.1464.1265.0365.0320,680,000
Mar 21, 201765.1965.5064.1364.2164.2126,640,500
Mar 20, 201764.9165.1864.7264.9364.9314,598,100
Mar 17, 201764.9165.2464.6864.8764.8749,219,700
Mar 16, 201764.7564.7664.3064.6464.6420,674,300
Mar 15, 201764.5564.9264.2564.7564.7524,833,800
Mar 14, 201764.5364.5564.1564.4164.4114,280,200
Mar 13, 201765.0165.1964.5764.7164.7120,100,000
Mar 10, 201765.1165.2664.7564.9364.9319,538,200
Mar 09, 201765.1965.2064.4864.7364.7319,846,800
Mar 08, 201764.2665.0864.2564.9964.9921,510,900
Mar 07, 201764.1964.7864.1964.4064.4018,521,000
Mar 06, 201763.9764.5663.8164.2764.2718,750,300
Mar 03, 201763.9964.2863.6264.2564.2518,135,900
Mar 02, 201764.6964.7563.8864.0164.0124,539,600
Mar 01, 201764.1364.9964.0264.9464.9426,937,500
Feb 28, 201764.0864.2063.7663.9863.9823,239,800
Feb 27, 201764.5464.5464.0564.2364.2315,871,500
Feb 24, 201764.5364.8064.1464.6264.6221,796,800
Feb 23, 201764.4264.7364.1964.6264.6220,273,100
Feb 22, 201764.3364.3964.0564.3664.3619,292,700
Feb 21, 201764.6164.9564.4564.4964.4920,655,900
Feb 17, 201764.4764.6964.3064.6264.6221,248,800
Feb 16, 201764.7465.2464.4464.5264.5220,546,300
Feb 15, 201764.5064.5764.1664.5364.5317,005,200
Feb 14, 201764.4164.7264.0264.5764.5723,108,400
Feb 14, 20170.39 Dividend
Feb 13, 201764.2464.8664.1364.7264.7222,920,100
Feb 10, 201764.2564.3063.9864.0064.0018,170,700
Feb 09, 201763.5264.4463.3264.0664.0622,644,400
Feb 08, 201763.5763.8163.2263.3463.3418,096,400
Feb 07, 201763.7463.7863.2363.4363.4320,277,200
Feb 06, 201763.5063.6563.1463.6463.6419,796,400
Feb 03, 201763.5063.7063.0763.6863.6830,301,800
Feb 02, 201763.2563.4162.7563.1763.1745,827,000
Feb 01, 201764.3664.6263.4763.5863.5839,671,500
Jan 31, 201764.8665.1564.2664.6564.6525,270,500
Jan 30, 201765.6965.7964.8065.1365.1331,651,400
Jan 27, 201765.3965.9164.8965.7865.7844,818,000
Jan 26, 201764.1264.5463.5564.2764.2743,554,600
Jan 25, 201763.9564.1063.4563.6863.6823,672,700
Jan 24, 201763.2063.7462.9463.5263.5224,672,900
Jan 23, 201762.7063.1262.5762.9662.9623,097,600
Jan 20, 201762.6762.8262.3762.7462.7430,213,500
Jan 19, 201762.2462.9862.2062.3062.3018,451,700
Jan 18, 201762.6762.7062.1262.5062.5019,670,100
Jan 17, 201762.6862.7062.0362.5362.5320,664,000
Jan 13, 201762.6262.8762.3562.7062.7019,422,300
Jan 12, 201763.0663.4061.9562.6162.6120,968,200
Jan 11, 201762.6163.2362.4363.1963.1921,517,300
Jan 10, 201762.7363.0762.2862.6262.6218,593,000
Jan 09, 201762.7663.0862.5462.6462.6420,256,600
Jan 06, 201762.3063.1562.0462.8462.8419,922,900
Jan 05, 201762.1962.6662.0362.3062.3024,876,000
Jan 04, 201762.4862.7562.1262.3062.3021,340,000
Jan 03, 201762.7962.8462.1362.5862.5820,694,100
Dec 30, 201662.9662.9962.0362.1462.1425,579,900
Dec 29, 201662.8663.2062.7362.9062.9010,250,600
Dec 28, 201663.4063.4062.8362.9962.9914,653,300
Dec 27, 201663.2164.0763.2163.2863.2811,763,200
Dec 23, 201663.4563.5462.8063.2463.2412,403,800
*Close price adjusted for dividends and splits.
Loading more data...
+ + + + +
+ \ No newline at end of file diff --git a/tests/fixtures/history2.html b/tests/fixtures/history2.html new file mode 100644 index 0000000..b654082 --- /dev/null +++ b/tests/fixtures/history2.html @@ -0,0 +1,49 @@ + + +GOOGL Historical Prices | Alphabet Inc. Stock - Yahoo Finance - https://finance.yahoo.com/
US Markets close in 5 hrs and 13 mins

Alphabet Inc. (GOOGL)


NasdaqGS - NasdaqGS Real Time Price. Currency in USD
Add to watchlist
960.88+6.23 (+0.65%)
As of 10:47AM EDT. Market open.
People also watch
FBTSLABABANFLXTWTR
DateOpenHighLowCloseAdj Close*Volume
May 22, 2017958.00964.38956.56960.82960.82421,435
May 19, 2017952.82959.56952.00954.65954.651,344,200
May 18, 2017943.20954.18941.27950.50950.501,800,500
May 17, 2017959.70960.99940.06942.17942.172,449,100
May 16, 2017963.55965.90960.35964.61964.611,101,500
May 15, 2017955.29962.70952.82959.22959.221,337,700
May 12, 2017957.85957.98952.06955.14955.141,214,900
May 11, 2017951.29957.99948.61955.89955.891,031,100
May 10, 2017956.22956.71949.84954.84954.841,146,000
May 09, 2017961.33962.20954.40956.71956.711,687,900
May 08, 2017947.45960.99947.40958.69958.691,876,700
May 05, 2017956.72958.44948.10950.28950.281,615,500
May 04, 2017950.29959.14947.37954.72954.721,937,600
May 03, 2017936.05950.20935.21948.45948.451,824,800
May 02, 2017933.27942.99931.00937.09937.091,751,300
May 01, 2017924.15935.82920.80932.82932.822,327,800
Apr 28, 2017929.00935.90923.22924.52924.523,845,900
Apr 27, 2017890.00893.38887.18891.44891.442,342,300
Apr 26, 2017891.39892.99885.15889.14889.141,323,300
Apr 25, 2017882.26892.25879.28888.84888.842,038,000
Apr 24, 2017868.44879.96866.11878.93878.931,696,500
Apr 21, 2017860.62862.44857.73858.95858.951,172,900
Apr 20, 2017859.74863.93857.50860.08860.081,188,800
Apr 19, 2017857.39860.20853.53856.51856.511,080,300
Apr 18, 2017852.54857.39851.25853.99853.99936,200
Apr 17, 2017841.38855.64841.03855.13855.131,049,100
Apr 13, 2017841.04843.73837.85840.18840.181,073,700
Apr 12, 2017838.46843.72837.59841.46841.461,135,800
Apr 11, 2017841.70844.63834.60839.88839.88974,300
Apr 10, 2017841.54846.74840.79841.70841.701,046,200
Apr 07, 2017845.00845.88837.30842.10842.101,111,600
Apr 06, 2017849.50853.59844.00845.10845.101,533,600
Apr 05, 2017854.71860.59847.52848.91848.911,855,200
Apr 04, 2017848.00853.00847.51852.57852.571,348,500
Apr 03, 2017848.75859.00847.53856.75856.751,969,400
Mar 31, 2017846.83849.56845.24847.80847.801,441,000
Mar 30, 2017851.98852.00846.77849.48849.48949,400
Mar 29, 2017842.75851.59841.38849.87849.871,457,300
Mar 28, 2017839.69845.40832.27840.63840.631,519,200
Mar 27, 2017828.09841.38824.30838.51838.511,934,600
Mar 24, 2017842.00844.00829.10835.14835.142,105,700
Mar 23, 2017841.39841.69833.00839.65839.653,287,700
Mar 22, 2017849.48855.35847.00849.80849.801,366,700
Mar 21, 2017870.06873.47847.69850.14850.142,538,000
Mar 20, 2017869.48870.34864.67867.91867.911,542,200
Mar 17, 2017873.68874.42868.37872.37872.371,868,300
Mar 16, 2017870.53872.71867.52870.00870.001,104,500
Mar 15, 2017867.94869.88861.30868.39868.391,332,900
Mar 14, 2017863.75867.58860.13865.91865.911,061,700
Mar 13, 2017860.83867.13860.82864.58864.581,166,600
Mar 10, 2017862.70864.23857.61861.41861.411,336,600
Mar 09, 2017853.69860.71852.67857.84857.841,347,700
Mar 08, 2017853.12856.93851.25853.64853.641,028,800
Mar 07, 2017847.26853.33845.52851.15851.151,038,700
Mar 06, 2017846.86848.94841.17847.27847.271,047,900
Mar 03, 2017848.94850.82844.71849.08849.081,005,000
Mar 02, 2017856.31856.49848.72849.85849.851,250,900
Mar 01, 2017851.38858.00849.02856.75856.751,818,700
Feb 28, 2017847.35848.83841.44844.93844.931,383,100
Feb 27, 2017844.95850.67843.01849.67849.671,010,300
Feb 24, 2017847.65848.36842.96847.81847.811,346,200
Feb 23, 2017851.08852.62842.50851.00851.001,386,700
Feb 22, 2017848.00853.79846.71851.36851.361,224,400
Feb 21, 2017847.99852.20846.55849.27849.271,260,300
Feb 17, 2017841.31846.94839.78846.55846.551,461,200
Feb 16, 2017838.50842.69837.26842.17842.171,005,100
Feb 15, 2017838.81841.77836.22837.32837.321,357,200
Feb 14, 2017839.77842.00835.83840.03840.031,363,300
Feb 13, 2017837.70841.74836.25838.96838.961,295,700
Feb 10, 2017832.95837.15830.51834.85834.851,415,100
Feb 09, 2017831.73831.98826.50830.06830.061,194,200
Feb 08, 2017830.53834.25825.11829.88829.881,302,200
Feb 07, 2017825.50831.92823.29829.23829.231,666,600
Feb 06, 2017820.92822.39814.29821.62821.621,350,900
Feb 03, 2017823.13826.13819.35820.13820.131,528,100
Feb 02, 2017815.00824.56812.05818.26818.261,689,200
Feb 01, 2017824.00824.00812.25815.24815.242,251,000
Jan 31, 2017819.50823.07813.40820.19820.192,020,200
Jan 30, 2017837.06837.23821.03823.83823.833,516,900
Jan 27, 2017859.00867.00841.90845.03845.033,752,500
Jan 26, 2017859.05861.00850.52856.98856.983,493,300
Jan 25, 2017853.55858.79849.74858.45858.451,655,400
Jan 24, 2017846.98851.52842.28849.53849.531,688,400
Jan 23, 2017831.61845.54828.70844.43844.432,457,400
Jan 20, 2017829.09829.24824.60828.17828.171,306,200
Jan 19, 2017829.00833.00823.96824.37824.371,070,500
Jan 18, 2017829.80829.81824.08829.02829.021,027,700
Jan 17, 2017830.00830.18823.20827.46827.461,440,900
Jan 13, 2017831.00834.65829.52830.94830.941,290,200
Jan 12, 2017828.38830.38821.01829.53829.531,349,500
Jan 11, 2017826.62829.90821.47829.86829.861,325,400
Jan 10, 2017827.07829.41823.14826.01826.011,197,400
Jan 09, 2017826.37830.43821.62827.18827.181,406,800
Jan 06, 2017814.99828.96811.50825.21825.212,017,100
Jan 05, 2017807.50813.74805.92813.02813.021,340,500
Jan 04, 2017809.89813.43804.11807.77807.771,515,300
Jan 03, 2017800.62811.44796.89808.01808.011,959,000
Dec 30, 2016803.21803.29789.62792.45792.451,735,900
Dec 29, 2016802.33805.75798.14802.88802.881,057,400
Dec 28, 2016813.33813.33802.44804.57804.571,214,800
*Close price adjusted for dividends and splits.
Loading more data...
+ + + + +
+ + \ No newline at end of file diff --git a/tests/fixtures/history_cookies b/tests/fixtures/history_cookies new file mode 100644 index 0000000..23c2fcb --- /dev/null +++ b/tests/fixtures/history_cookies @@ -0,0 +1,5 @@ +# HTTP cookie file. +# Generated by Wget on 2017-05-17 15:02:49. +# Edit at your own risk. + +.yahoo.com TRUE / FALSE 1526562167 B di2g869choifn&b=3&s=hd diff --git a/tests/fixtures/history_download_TSLA.csv b/tests/fixtures/history_download_TSLA.csv new file mode 100644 index 0000000..c88b576 --- /dev/null +++ b/tests/fixtures/history_download_TSLA.csv @@ -0,0 +1,24 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2017-04-18,299.700012,300.839996,297.899994,300.250000,300.250000,3035700 +2017-04-19,302.459991,306.619995,302.109985,305.519989,305.519989,3898000 +2017-04-20,306.510010,309.149994,300.230011,302.510010,302.510010,6149400 +2017-04-21,302.000000,306.399994,300.420013,305.600006,305.600006,4509800 +2017-04-24,309.220001,310.549988,306.019989,308.029999,308.029999,5083500 +2017-04-25,308.000000,313.980011,305.859985,313.790009,313.790009,6737700 +2017-04-26,312.369995,314.500000,309.000000,310.170013,310.170013,4695000 +2017-04-27,311.690002,313.089996,307.500000,308.630005,308.630005,3468600 +2017-04-28,309.829987,314.799988,308.000000,314.070007,314.070007,4505500 +2017-05-01,314.880005,327.250000,314.809998,322.829987,322.829987,8829600 +2017-05-02,324.000000,327.660004,316.559998,318.890015,318.890015,5382800 +2017-05-03,317.670013,321.529999,310.450012,311.019989,311.019989,7133400 +2017-05-04,307.440002,307.769989,290.760010,295.459991,295.459991,14152000 +2017-05-05,298.000000,308.549988,296.799988,308.350006,308.350006,8177300 +2017-05-08,310.899994,313.790009,305.820007,307.190002,307.190002,7006500 +2017-05-09,309.380005,321.989990,309.100006,321.260010,321.260010,9676500 +2017-05-10,321.559998,325.500000,318.119995,325.220001,325.220001,5741600 +2017-05-11,323.399994,326.000000,319.600006,323.100006,323.100006,4753800 +2017-05-12,325.480011,327.000000,321.529999,324.809998,324.809998,4121600 +2017-05-15,318.380005,320.200012,312.529999,315.880005,315.880005,7622000 +2017-05-16,317.589996,320.059998,315.140015,317.010010,317.010010,4152500 +2017-05-17,314.390015,314.630005,305.500000,306.109985,306.109985,6711900 +2017-05-18,307.000000,313.940002,305.309998,313.059998,313.059998,5653800 diff --git a/tests/fixtures/quote b/tests/fixtures/quote new file mode 100644 index 0000000..d6c48af --- /dev/null +++ b/tests/fixtures/quote @@ -0,0 +1,53 @@ +curl 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/ES=F?formatted=true&crumb=sxCZygzUaUK&lang=en-US®ion=US&modules=price%2CsummaryDetail&corsDomain=finance.yahoo.com' -H 'pragma: no-cache' -H 'origin: https://finance.yahoo.com' -H 'accept-encoding: gzip, deflate, sdch, br' -H 'accept-language: en-US,en;q=0.8,he;q=0.6' -H 'user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36' -H 'accept: */*' -H 'cache-control: no-cache' -H 'authority: query2.finance.yahoo.com' -H 'cookie: B=1jmfastbret12&b=3&s=89; PRF=t%3DXIV%252BMSFT%252B0700.HK' -H 'referer: https://finance.yahoo.com/quote/XIV?p=XIV' --compressed + +GENERAL + +Request URL:https://query2.finance.yahoo.com/v10/finance/quoteSummary/ES=F?formatted=true&crumb=sxCZygzUaUK&lang=en-US®ion=US&modules=price%2CsummaryDetail&corsDomain=finance.yahoo.com +Request Method:GET +Status Code:200 +Remote Address:87.248.116.12:443 +Referrer Policy:no-referrer-when-downgrade + +RESPONSE HEADERS + +access-control-allow-credentials:true +access-control-allow-origin:https://finance.yahoo.com +age:1 +cache-control:max-age=1, stale-while-revalidate=15 +content-encoding:gzip +content-length:891 +content-type:application/json; charset=utf-8 +date:Thu, 18 May 2017 08:30:38 GMT +server:ATS +status:200 +strict-transport-security:max-age=0 +vary:Origin +via:http/1.1 media-ncache-api15.prod.media.ir2.yahoo.com (ApacheTrafficServer [cMsSfW]), http/1.1 media-ncache-api16.prod.media.ir2.yahoo.com (ApacheTrafficServer [cMsSf ]), http/1.1 media-router-api4.prod.media.ir2.yahoo.com (ApacheTrafficServer [cMsSfW]), https/1.1 e12.ycpi.amb.yahoo.com (ApacheTrafficServer [cMsSfW]) +x-content-type-options:nosniff +x-yahoo-request-id:cf4gfslchqmte +x-yql-host:pprd3-node7519-lh2.manhattan.ir2.yahoo.com + +REQUEST HEADERS + +:authority:query2.finance.yahoo.com +:method:GET +:path:/v10/finance/quoteSummary/ES=F?formatted=true&crumb=sxCZygzUaUK&lang=en-US®ion=US&modules=price%2CsummaryDetail&corsDomain=finance.yahoo.com +:scheme:https +accept:*/* +accept-encoding:gzip, deflate, sdch, br +accept-language:en-US,en;q=0.8,he;q=0.6 +cache-control:no-cache +cookie:B=1jmfastbret12&b=3&s=89; PRF=t%3DXIV%252BMSFT%252B0700.HK +origin:https://finance.yahoo.com +pragma:no-cache +referer:https://finance.yahoo.com/quote/XIV?p=XIV +user-agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36 + +QUERY STRING PARAMETERS + +formatted:true +crumb:sxCZygzUaUK +lang:en-US +region:US +modules:price,summaryDetail +corsDomain:finance.yahoo.com diff --git a/tests/fixtures/quote_MSFT.json b/tests/fixtures/quote_MSFT.json new file mode 100644 index 0000000..6d1ae9e --- /dev/null +++ b/tests/fixtures/quote_MSFT.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"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":10,"buy":14,"hold":9,"sell":0,"strongSell":1},{"period":"-1m","strongBuy":10,"buy":14,"hold":9,"sell":0,"strongSell":1},{"period":"-2m","strongBuy":10,"buy":16,"hold":9,"sell":0,"strongSell":1},{"period":"-3m","strongBuy":10,"buy":15,"hold":8,"sell":0,"strongSell":2}],"maxAge":86400},"summaryDetail":{"maxAge":1,"priceHint":2,"previousClose":67.69,"open":67.89,"dayLow":67.5,"dayHigh":68.5,"regularMarketPreviousClose":67.69,"regularMarketOpen":67.89,"regularMarketDayLow":67.5,"regularMarketDayHigh":68.5,"dividendRate":1.56,"dividendYield":0.023,"exDividendDate":1494892800,"payoutRatio":0.66080004,"fiveYearAvgDividendYield":2.59,"beta":1.38096,"trailingPE":30.207413,"forwardPE":20.61747,"volume":16238036,"regularMarketVolume":16238036,"averageVolume":22599262,"averageVolume10days":27506616,"averageDailyVolume10Day":27506616,"bid":68.5,"ask":68.59,"bidSize":1600,"askSize":1000,"marketCap":528468869120,"fiftyTwoWeekLow":48.04,"fiftyTwoWeekHigh":69.71,"priceToSalesTrailing12Months":6.057158,"fiftyDayAverage":67.318855,"twoHundredDayAverage":64.07338,"trailingAnnualDividendRate":1.53,"trailingAnnualDividendYield":0.022603042},"earnings":{"maxAge":86400,"earningsChart":{"quarterly":[{"date":"2Q2016","actual":0.69,"estimate":0.58},{"date":"3Q2016","actual":0.76,"estimate":0.68},{"date":"4Q2016","actual":0.83,"estimate":0.79},{"date":"1Q2017","actual":0.73,"estimate":0.7}],"currentQuarterEstimate":0.71,"currentQuarterEstimateDate":"2Q","currentQuarterEstimateYear":2017},"financialsChart":{"yearly":[{"date":2014,"revenue":86833000000,"earnings":22074000000},{"date":2015,"revenue":93580000000,"earnings":12193000000},{"date":2016,"revenue":85320000000,"earnings":16798000000}],"quarterly":[{"date":"2Q2016","revenue":20614000000,"earnings":3122000000},{"date":"3Q2016","revenue":20453000000,"earnings":4690000000},{"date":"4Q2016","revenue":24090000000,"earnings":5200000000},{"date":"1Q2017","revenue":22090000000,"earnings":4801000000}]}},"calendarEvents":{"maxAge":1,"earnings":{"earningsDate":[1500336000],"earningsAverage":0.71,"earningsLow":0.67,"earningsHigh":0.78,"revenueAverage":24257700000,"revenueLow":24081600000,"revenueHigh":24503000000},"exDividendDate":1494892800,"dividendDate":1496880000},"upgradeDowngradeHistory":{"history":[{"epochGradeDate":1493251200,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1485475200,"firm":"Citigroup","toGrade":"Neutral","fromGrade":"Sell","action":"up"},{"epochGradeDate":1484092800,"firm":"Wells Fargo","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1482364800,"firm":"Piper Jaffray","toGrade":"Overweight","fromGrade":"","action":"init"},{"epochGradeDate":1479340800,"firm":"Goldman","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1478736000,"firm":"Atlantic Equities","toGrade":"Neutral","fromGrade":"Underweight","action":"up"},{"epochGradeDate":1477008000,"firm":"Wunderlich","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1457049600,"firm":"Macquarie","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1452643200,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Equal-Weight","action":"up"},{"epochGradeDate":1452211200,"firm":"BMO Capital Markets","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1450396800,"firm":"Goldman","toGrade":"Neutral","fromGrade":"Sell","action":"up"},{"epochGradeDate":1445558400,"firm":"BofA/Merrill","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1443657600,"firm":"BofA/Merrill","toGrade":"Neutral","fromGrade":"Underperform","action":"up"},{"epochGradeDate":1439424000,"firm":"Stifel","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1433808000,"firm":"Wunderlich","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1429833600,"firm":"Nomura","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1422316800,"firm":"MKM Partners","toGrade":"Neutral","fromGrade":"Buy","action":"down"},{"epochGradeDate":1412812800,"firm":"BMO Capital Markets","toGrade":"Market Perform","fromGrade":"","action":"init"},{"epochGradeDate":1401926400,"firm":"FBR Capital","toGrade":"Outperform","fromGrade":"Mkt Perform","action":"up"},{"epochGradeDate":1390435200,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1389312000,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Equal Weight","action":"up"},{"epochGradeDate":1376352000,"firm":"Stifel","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1367280000,"firm":"Standpoint Research","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1357257600,"firm":"Argus","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1355443200,"firm":"BMO Capital Markets","toGrade":"Market Perform","fromGrade":"","action":"init"},{"epochGradeDate":1355270400,"firm":"Standpoint Research","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1345420800,"firm":"Longbow","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1342742400,"firm":"Griffin Securities","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1334016000,"firm":"MKM Partners","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1331856000,"firm":"Argus","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1323129600,"firm":"Barclays Capital","toGrade":"Equal Weight","fromGrade":"","action":"init"},{"epochGradeDate":1315526400,"firm":"Standpoint Research","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1297900800,"firm":"Collins Stewart","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1294358400,"firm":"Standpoint Research","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1287964800,"firm":"FBR Capital","toGrade":"Mkt Perform","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1284336000,"firm":"Standpoint Research","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1279843200,"firm":"The Benchmark Company","toGrade":"Hold","fromGrade":"Sell","action":"up"},{"epochGradeDate":1274918400,"firm":"FBR Capital","toGrade":"Outperform","fromGrade":"Mkt Perform","action":"up"},{"epochGradeDate":1271376000,"firm":"Janney Mntgmy Scott","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1270684800,"firm":"Soleil","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1266364800,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1258070400,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1256601600,"firm":"Argus","toGrade":"Hold","fromGrade":"Sell","action":"up"},{"epochGradeDate":1252886400,"firm":"Auriga U.S.A","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1250553600,"firm":"AmTech Research","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1248393600,"firm":"FBR Capital","toGrade":"Mkt Perform","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1239580800,"firm":"AmTech Research","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1239062400,"firm":"RBC Capital Mkts","toGrade":"Outperform","fromGrade":"Sector Perform","action":"up"},{"epochGradeDate":1238457600,"firm":"Davenport","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1238112000,"firm":"Auriga U.S.A","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1232668800,"firm":"Davenport","toGrade":"Neutral","fromGrade":"Buy","action":"down"},{"epochGradeDate":1227225600,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Perform","action":"up"},{"epochGradeDate":1226966400,"firm":"Caris & Company","toGrade":"Average","fromGrade":"","action":"init"},{"epochGradeDate":1224115200,"firm":"Canaccord Adams","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1217203200,"firm":"Edward Jones","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1217203200,"firm":"Collins Stewart","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1215734400,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1204761600,"firm":"Cross Research","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1204675200,"firm":"Jefferies & Co","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1204588800,"firm":"AmTech Research","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1202688000,"firm":"RBC Capital Mkts","toGrade":"Sector Perform","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1201737600,"firm":"Stanford Research","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1200960000,"firm":"DA Davidson","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1189036800,"firm":"Deutsche Securities","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1178496000,"firm":"Davenport","toGrade":"Neutral","fromGrade":"Buy","action":"down"},{"epochGradeDate":1177632000,"firm":"CIBC Wrld Mkts","toGrade":"Sector Perform","fromGrade":"Sector Underperform","action":"up"},{"epochGradeDate":1170374400,"firm":"Banc of America Sec","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1169596800,"firm":"DA Davidson","toGrade":"Neutral","fromGrade":"Buy","action":"down"},{"epochGradeDate":1163980800,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1154390400,"firm":"Prudential","toGrade":"Overweight","fromGrade":"","action":"init"},{"epochGradeDate":1154044800,"firm":"Canaccord Adams","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1150761600,"firm":"DA Davidson","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1148947200,"firm":"Caris & Company","toGrade":"Average","fromGrade":"Above Average","action":"down"},{"epochGradeDate":1146182400,"firm":"Caris & Company","toGrade":"Above Average","fromGrade":"Buy","action":"down"},{"epochGradeDate":1146182400,"firm":"Lehman Brothers","toGrade":"Equal-weight","fromGrade":"Overweight","action":"down"},{"epochGradeDate":1146182400,"firm":"Canaccord Adams","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1146182400,"firm":"Morgan Stanley","toGrade":"Equal-weight","fromGrade":"Overweight","action":"down"},{"epochGradeDate":1146182400,"firm":"CIBC Wrld Mkts","toGrade":"Sector Underperform","fromGrade":"Sector Outperform","action":"down"},{"epochGradeDate":1145923200,"firm":"Citigroup","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1142380800,"firm":"RBC Capital Mkts","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1136505600,"firm":"CSFB","toGrade":"Neutral","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1135296000,"firm":"Soleil","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1130457600,"firm":"CSFB","toGrade":"Outperform","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1115683200,"firm":"Banc of America Sec","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1115164800,"firm":"CSFB","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1094774400,"firm":"AG Edwards","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1091491200,"firm":"Caris & Company","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1088035200,"firm":"Banc of America Sec","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1086825600,"firm":"Schwab Soundview","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1078790400,"firm":"Banc of America Sec","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1073260800,"firm":"SG Cowen","toGrade":"Strong Buy","fromGrade":"Outperform","action":"up"},{"epochGradeDate":1071792000,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"","action":"init"},{"epochGradeDate":1070841600,"firm":"Soundview Technology","toGrade":"Outperform","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1067558400,"firm":"Oppenheimer","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1066953600,"firm":"RBC Capital Mkts","toGrade":"Sector Perform","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1064534400,"firm":"Prudential","toGrade":"Overweight","fromGrade":"","action":"init"},{"epochGradeDate":1063756800,"firm":"Wachovia","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1061769600,"firm":"Smith Barney Citigroup","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1057104000,"firm":"Merrill Lynch","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1053475200,"firm":"Fulcrum","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1051660800,"firm":"CSFB","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1050364800,"firm":"Fulcrum","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1050019200,"firm":"First Albany","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":1049846400,"firm":"SG Cowen","toGrade":"Outperform","fromGrade":"Mkt Perform","action":"up"},{"epochGradeDate":1049155200,"firm":"UBS Warburg","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1046217600,"firm":"Deutsche Securities","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1045180800,"firm":"Bear Stearns","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1043193600,"firm":"USB Piper Jaffray","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1039651200,"firm":"Wachovia Sec","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1037923200,"firm":"Investec","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1037664000,"firm":"Raymond James","toGrade":"Mkt Perform","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1034294400,"firm":"Wells Fargo Sec","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1030665600,"firm":"Raymond James","toGrade":"","fromGrade":"","action":"init"},{"epochGradeDate":1029974400,"firm":"Salomon Smth Brny","toGrade":"Outperform","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1027468800,"firm":"RBC Capital Mkts","toGrade":"Outperform","fromGrade":"Sector Perform","action":"up"},{"epochGradeDate":1020211200,"firm":"Banc of America Sec","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1019174400,"firm":"First Albany","toGrade":"Strong Buy","fromGrade":"Buy","action":"up"},{"epochGradeDate":1019174400,"firm":"Merrill Lynch","toGrade":"NT Buy","fromGrade":"NT Neutral","action":"up"},{"epochGradeDate":1017878400,"firm":"AG Edwards","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1011312000,"firm":"Pacific Crest","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":1010361600,"firm":"ABN AMRO","toGrade":"Buy","fromGrade":"Add","action":"up"},{"epochGradeDate":1007596800,"firm":"FAC/Eqts First Albany","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1006300800,"firm":"Salomon Smth Brny","toGrade":"Neutral","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1003276800,"firm":"AG Edwards","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1002672000,"firm":"SoundView Technology","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":1002153600,"firm":"AG Edwards","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":1001376000,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1001376000,"firm":"Deutsche Bc Alex. Br","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1001289600,"firm":"Thomas Weisel","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":1000944000,"firm":"Dain Rauscher Wessels","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":996796800,"firm":"McDonald Invst","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":995587200,"firm":"Salomon Smth Brny","toGrade":"Outperform","fromGrade":"Buy","action":"down"},{"epochGradeDate":994896000,"firm":"CSFB","toGrade":"Strong Buy","fromGrade":"Buy","action":"up"},{"epochGradeDate":994896000,"firm":"Pacific Crest","toGrade":"Strong Buy","fromGrade":"Buy","action":"up"},{"epochGradeDate":993686400,"firm":"JP Morgan","toGrade":"Buy","fromGrade":"LT Buy","action":"up"},{"epochGradeDate":992476800,"firm":"Prudential","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":987724800,"firm":"Goldman Sachs","toGrade":"Recomm List","fromGrade":"Mkt Outperform","action":"up"},{"epochGradeDate":987724800,"firm":"Thomas Weisel","toGrade":"Strong Buy","fromGrade":"Mkt Perform","action":"up"},{"epochGradeDate":987724800,"firm":"Salomon Smth Brny","toGrade":"Buy","fromGrade":"Outperform","action":"up"},{"epochGradeDate":985132800,"firm":"Banc of America Sec","toGrade":"Mkt Perform","fromGrade":"","action":"init"},{"epochGradeDate":985132800,"firm":"Banc of America Sec","toGrade":"Mkt Perform","fromGrade":"","action":"init"},{"epochGradeDate":984096000,"firm":"WF Van Kasper","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":981590400,"firm":"Merrill Lynch","toGrade":"LT Accum","fromGrade":"LT Buy","action":"down"},{"epochGradeDate":981072000,"firm":"CSFB","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":980812800,"firm":"Pacific Crest","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":976838400,"firm":"Robertson Stephens","toGrade":"LT Attractive","fromGrade":"Buy","action":"down"},{"epochGradeDate":976838400,"firm":"Dresdner Klnwrt Bnsn","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":976665600,"firm":"Prudential","toGrade":"Hold","fromGrade":"Accumulate","action":"down"},{"epochGradeDate":976233600,"firm":"ING Barings","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":974764800,"firm":"Dresdner Klnwrt Bnsn","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":972345600,"firm":"Salomon Smth Brny","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":970617600,"firm":"Tucker Anthny Clry","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":970531200,"firm":"AG Edwards","toGrade":"Buy","fromGrade":"Accumulate","action":"up"},{"epochGradeDate":963964800,"firm":"SG Cowen","toGrade":"Neutral","fromGrade":"Buy","action":"down"},{"epochGradeDate":963964800,"firm":"Prudential","toGrade":"Accumulate","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":961545600,"firm":"CIBC Wrld Mkts","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":956534400,"firm":"SG Cowen","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":956534400,"firm":"Goldman Sachs","toGrade":"Market Outperform","fromGrade":"Recommended List","action":"down"},{"epochGradeDate":956534400,"firm":"Thomas Weisel","toGrade":"Market Perform","fromGrade":"Buy","action":"down"},{"epochGradeDate":954720000,"firm":"CIBC Wrld Mkts","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":952473600,"firm":"Deutsche Bc Alex. Br","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":948931200,"firm":"ING Barings","toGrade":"Strong Buy","fromGrade":"","action":"init"},{"epochGradeDate":947030400,"firm":"Prudential","toGrade":"Strong Buy","fromGrade":"Accumulate","action":"up"},{"epochGradeDate":942019200,"firm":"Volpe Brown Whelan","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":941155200,"firm":"CIBC Wrld Mkts","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":940377600,"firm":"SG Cowen","toGrade":"Strong Buy","fromGrade":"Buy","action":"up"},{"epochGradeDate":940377600,"firm":"AG Edwards","toGrade":"Accumulate","fromGrade":"Maintain Position","action":"up"},{"epochGradeDate":932428800,"firm":"Prudential","toGrade":"Accumulate","fromGrade":"Hold","action":"up"},{"epochGradeDate":927676800,"firm":"Merrill Lynch","toGrade":"NT Acc/LT Buy","fromGrade":"","action":"init"},{"epochGradeDate":927676800,"firm":"Raymond James","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":924652800,"firm":"Prudential","toGrade":"Hold","fromGrade":"Accumulate","action":"down"},{"epochGradeDate":923270400,"firm":"Advest Inc","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":920937600,"firm":"Cowen & Co","toGrade":"Buy","fromGrade":"Strong Buy","action":"down"},{"epochGradeDate":916272000,"firm":"Prudential","toGrade":"Accumulate","fromGrade":"","action":"init"},{"epochGradeDate":913334400,"firm":"JP Morgan","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":898646400,"firm":"AG Edwards","toGrade":"Maintain","fromGrade":"Reduce","action":"up"},{"epochGradeDate":897609600,"firm":"LaSalle Street Sec","toGrade":"Buy/Core","fromGrade":"","action":"init"},{"epochGradeDate":895536000,"firm":"DLJ","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":893289600,"firm":"EVEREN Sec.","toGrade":"Mkt Outperform","fromGrade":"Mkt Perform","action":"up"}],"maxAge":86400},"price":{"maxAge":1,"preMarketChangePercent":7.3050486E-4,"preMarketChange":0.05000305,"preMarketTime":1495533970,"preMarketPrice":68.5,"preMarketSource":"FREE_REALTIME","postMarketChangePercent":-0.0018991564,"postMarketChange":-0.12999725,"postMarketTime":1495497260,"postMarketPrice":68.32,"postMarketSource":"FREE_REALTIME","regularMarketChangePercent":0.011227574,"regularMarketChange":0.7599945,"regularMarketTime":1495483205,"priceHint":2,"regularMarketPrice":68.45,"regularMarketDayHigh":68.5,"regularMarketDayLow":67.5,"regularMarketVolume":16238036,"averageDailyVolume10Day":27506616,"averageDailyVolume3Month":22599262,"regularMarketPreviousClose":67.69,"regularMarketSource":"DELAYED","regularMarketOpen":67.89,"exchange":"NMS","exchangeName":"NasdaqGS","marketState":"PRE","quoteType":"EQUITY","symbol":"MSFT","underlyingSymbol":null,"shortName":"Microsoft Corporation","longName":"Microsoft Corporation","currency":"USD","quoteSourceName":"Nasdaq Real Time Price","currencySymbol":"$"},"defaultKeyStatistics":{"maxAge":1,"forwardPE":20.61747,"profitMargins":0.20417,"floatShares":7549350839,"sharesOutstanding":7720509952,"sharesShort":39444700,"sharesShortPriorMonth":39671300,"heldPercentInsiders":0.0378,"heldPercentInstitutions":0.726,"shortRatio":1.94,"shortPercentOfFloat":0.005227,"beta":1.38096,"category":null,"bookValue":9.029,"priceToBook":7.5811267,"fundFamily":null,"legalType":null,"lastFiscalYearEnd":1467244800,"nextFiscalYearEnd":1530316800,"mostRecentQuarter":1490918400,"earningsQuarterlyGrowth":0.278,"netIncomeToCommon":17813000192,"trailingEps":2.266,"forwardEps":3.32,"pegRatio":2.39,"lastSplitFactor":"2/1","lastSplitDate":1045526400,"52WeekChange":0.31207597,"SandP52WeekChange":0.14723563},"summaryProfile":{"address1":"One Microsoft Way","city":"Redmond","state":"WA","zip":"98052","country":"United States","phone":"425-882-8080","fax":"425-706-7329","website":"http://www.microsoft.com","industry":"Business Software & Services","sector":"Technology","longBusinessSummary":"Microsoft Corporation, a technology company, develops, licenses, and supports software products, services, and devices worldwide. The company’s Productivity and Business Processes segment offers Office 365 commercial products and services for businesses, including Office, Exchange, SharePoint, and Skype, as well as related Client Access Licenses (CALs); Office 365 consumer services, such as Skype, Outlook.com, and OneDrive; Dynamics business solutions, such as financial management, customer relationship management, supply chain management, and analytics applications for small and mid-size businesses, large organizations, and divisions of enterprises; and LinkedIn online professional network. Its Intelligent Cloud segment licenses server products and cloud services, such as SQL Server, Windows Server, Visual Studio, System Center, and related CALs, as well as Azure, a cloud platform with computing, networking, storage, database, and management services; and enterprise services, such as Premier Support and Microsoft Consulting that assist in developing, deploying, and managing Microsoft server and desktop solutions, as well as provide training and certification to developers and IT professionals on Microsoft products. The company’s More Personal Computing segment comprises Windows OEM, volume, and other non-volume licensing of the Windows operating system, as well as patent licensing, Windows Embedded, MSN display advertising, and Windows Phone licensing system; devices, including Microsoft Surface, phones, and PC accessories; and search advertising, including Bing and Bing Ads. This segment also provides gaming platforms, including Xbox hardware, Xbox Live, video games, and third-party video games. The company markets and distributes its products through original equipment manufacturers (OEM), distributors, and resellers, as well as through online and Microsoft retail stores. Microsoft Corporation was founded in 1975 and is headquartered in Redmond, Washington.","fullTimeEmployees":114000,"companyOfficers":[],"maxAge":86400},"financialData":{"maxAge":86400,"currentPrice":68.45,"targetHighPrice":90.0,"targetLowPrice":45.0,"targetMeanPrice":74.13,"targetMedianPrice":75.0,"recommendationMean":2.0,"recommendationKey":"buy","numberOfAnalystOpinions":32,"totalCash":121575997440,"totalCashPerShare":15.747,"ebitda":28800000000,"totalDebt":85995003904,"quickRatio":2.585,"currentRatio":2.813,"totalRevenue":87247003648,"debtToEquity":123.327,"revenuePerShare":11.217,"returnOnAssets":0.06509,"returnOnEquity":0.24649,"grossProfits":52540000000,"freeCashflow":18027249664,"operatingCashflow":36965998592,"earningsGrowth":0.298,"revenueGrowth":0.076,"grossMargins":0.61278,"ebitdaMargins":0.33009997,"operatingMargins":0.24283001,"profitMargins":0.20417}}],"error":null}} \ No newline at end of file diff --git a/tests/fixtures/quote_TSLA.json b/tests/fixtures/quote_TSLA.json new file mode 100644 index 0000000..c50f50b --- /dev/null +++ b/tests/fixtures/quote_TSLA.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"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":{"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,"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},"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":{"maxAge":1,"earnings":{"earningsDate":[1501545600,1502064000],"earningsAverage":-1.64,"earningsLow":-2.98,"earningsHigh":-0.5,"revenueAverage":2636630000,"revenueLow":2457200000,"revenueHigh":2887970000}},"upgradeDowngradeHistory":{"history":[{"epochGradeDate":1491782400,"firm":"Piper Jaffray","toGrade":"Overweight","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1489017600,"firm":"Bernstein","toGrade":"Mkt Perform","fromGrade":"","action":"init"},{"epochGradeDate":1488153600,"firm":"Goldman","toGrade":"Sell","fromGrade":"Neutral","action":"down"},{"epochGradeDate":1484784000,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Equal-Weight","action":"up"},{"epochGradeDate":1483488000,"firm":"Guggenheim","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1475712000,"firm":"Goldman","toGrade":"Neutral","fromGrade":"Buy","action":"down"},{"epochGradeDate":1467072000,"firm":"Argus","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1466726400,"firm":"Standpoint Research","toGrade":"Hold","fromGrade":"Sell","action":"up"},{"epochGradeDate":1466640000,"firm":"Morgan Stanley","toGrade":"Equal-Weight","fromGrade":"Overweight","action":"down"},{"epochGradeDate":1466553600,"firm":"Oppenheimer","toGrade":"Perform","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1465516800,"firm":"Piper Jaffray","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1464912000,"firm":"Sterne Agee CRT","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1463529600,"firm":"Goldman","toGrade":"Buy","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1459987200,"firm":"Standpoint Research","toGrade":"Sell","fromGrade":"Hold","action":"down"},{"epochGradeDate":1458518400,"firm":"Argus","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1457913600,"firm":"Robert W. Baird","toGrade":"Outperform","fromGrade":"Neutral","action":"up"},{"epochGradeDate":1444176000,"firm":"Robert W. Baird","toGrade":"Neutral","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1444176000,"firm":"RBC Capital Mkts","toGrade":"Sector Perform","fromGrade":"","action":"init"},{"epochGradeDate":1441756800,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1437436800,"firm":"UBS","toGrade":"Sell","fromGrade":"Neutral","action":"down"},{"epochGradeDate":1430784000,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1427414400,"firm":"Argus","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1413504000,"firm":"MLV & Co","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1412726400,"firm":"Tigress Financial","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1409616000,"firm":"Stifel","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1407715200,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1395792000,"firm":"UBS","toGrade":"Neutral","fromGrade":"","action":"init"},{"epochGradeDate":1392854400,"firm":"Deutsche Bank","toGrade":"Hold","fromGrade":"Buy","action":"down"},{"epochGradeDate":1392768000,"firm":"FBR Capital","toGrade":"Mkt Perform","fromGrade":"","action":"init"},{"epochGradeDate":1383782400,"firm":"Standpoint Research","toGrade":"Hold","fromGrade":"Sell","action":"up"},{"epochGradeDate":1380672000,"firm":"Robert W. Baird","toGrade":"Neutral","fromGrade":"Outperform","action":"down"},{"epochGradeDate":1377129600,"firm":"Stifel","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1375920000,"firm":"Barclays","toGrade":"Equal Weight","fromGrade":"Overweight","action":"down"},{"epochGradeDate":1374796800,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1362096000,"firm":"Northland Capital","toGrade":"Outperform","fromGrade":"","action":"init"},{"epochGradeDate":1342569600,"firm":"Wunderlich","toGrade":"Sell","fromGrade":"Buy","action":"down"},{"epochGradeDate":1337644800,"firm":"Maxim Group","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1332720000,"firm":"Wunderlich","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1326758400,"firm":"Wunderlich","toGrade":"Buy","fromGrade":"Hold","action":"up"},{"epochGradeDate":1321920000,"firm":"Wunderlich","toGrade":"Hold","fromGrade":"","action":"init"},{"epochGradeDate":1320969600,"firm":"Barclays Capital","toGrade":"Overweight","fromGrade":"","action":"init"},{"epochGradeDate":1284595200,"firm":"Dougherty & Company","toGrade":"Buy","fromGrade":"","action":"init"},{"epochGradeDate":1281312000,"firm":"Deutsche Bank","toGrade":"Hold","fromGrade":"","action":"init"}],"maxAge":86400},"price":{"maxAge":1,"preMarketSource":"FREE_REALTIME","postMarketChangePercent":-0.0014798812,"postMarketChange":-0.45999146,"postMarketTime":1495238377,"postMarketPrice":310.37,"postMarketSource":"FREE_REALTIME","regularMarketChangePercent":-0.0071232705,"regularMarketChange":-2.230011,"regularMarketTime":1495224000,"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":"$"},"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":1483142400,"nextFiscalYearEnd":1546214400,"mostRecentQuarter":1490918400,"netIncomeToCommon":-722924032,"trailingEps":-4.769,"forwardEps":-0.94,"pegRatio":-1.59,"lastSplitFactor":null,"52WeekChange":0.44787717,"SandP52WeekChange":0.15511417},"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":{"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}}],"error":null}} \ No newline at end of file diff --git a/tests/integration.js b/tests/integration.js new file mode 100644 index 0000000..7712fe7 --- /dev/null +++ b/tests/integration.js @@ -0,0 +1,39 @@ +/* + * In the spec unit tests, we see if all our functions work as expected, given + * the data they're expecting. Here, we check if everything is working, which + * is also testing that Yahoo is operating the same way we expect. + */ + +import fs from 'fs'; +import path from 'path'; + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import proxyquire from 'proxyquire'; + +import moment from 'moment'; + +import { assertValidHistoricalResult, assertValidSnapshotResult } from '../lib/index.spec.js'; + +import { historical } from '../lib/index'; + +const should = chai.should(); +chai.use(chaiAsPromised); + +const NETWORK_TIMEOUT = 10 * 1000; + +const lastWeekStr = moment().subtract(7, 'days').format('YYYY-MM-DD'); + +if (process.env.INTEGRATION_TESTS) +describe('integration tests', () => { + + describe('historical', () => { + + it('works', async () => { + const result = await historical({ symbol: 'TSLA', from: lastWeekStr }); + assertValidHistoricalResult(result); + }).timeout(NETWORK_TIMEOUT); + + }); + +}); diff --git a/tests/testUtils.js b/tests/testUtils.js new file mode 100644 index 0000000..88e8604 --- /dev/null +++ b/tests/testUtils.js @@ -0,0 +1,55 @@ +import fs from 'fs'; +import path from 'path'; + +// for nodeify to work with stubbed out functions +import BluebirdPromise from 'bluebird'; + +const fixturePath = (...args) => path.join('tests', 'fixtures', ...args); + +const fixtures = { + historyHtml: fixturePath('history.html'), // crumb: STATIC_CRUMB + historyHtml2: fixturePath('history2.html'), // crumb: sxCZygzUaUK + historyCsv: fixturePath('history_download_TSLA.csv'), + quoteJson: fixturePath('quote_MSFT.json'), // crumb: sxCZygzUaUK +}; + +const STATIC_CRUMB = 'zhqGa4p9aDu'; + +// Since the same files are used by multiple tests, but we won't already run +// all tests, this helper ensures we only load (and cache) what we need. +const fileCache = {}; +function getFile(name) { + if (fileCache[name]) + return fileCache[name]; + + if (!fixtures[name]) + throw new Error('No fixture file: ' + name); + + fileCache[name] = fs.readFileSync(fixtures[name]).toString(); + + // Since we download with httpRequestOptions = { json: true }; + if (name.endsWith('Json')) + fileCache[name] = JSON.parse(fileCache[name]); + + return fileCache[name]; +} + +const yahooCrumbStub = { + async getCrumb() { return STATIC_CRUMB } +}; + +function utilsDownloadFixture(name) { + return { + // async download() { return getFile(name); } + download: () => BluebirdPromise.resolve(getFile(name)) + } +} + +function stubbedFor(name) { + return { + './yahooCrumb': yahooCrumbStub, + './utils': utilsDownloadFixture(name) + } +} + +export { getFile, STATIC_CRUMB, utilsDownloadFixture, stubbedFor };