Finance-Quote-1.65/0000775000175000017500000000000015003302667013735 5ustar bschuckbschuckFinance-Quote-1.65/Changes0000644000175000017500000016173315003302667015241 0ustar bschuckbschuck1.65 2025-04-26 18:17:38-07:00 America/Los_Angeles * Added EXCHANGE feature to Sinvestor.pm and some fixes - PR #481 * Added EXCHANGE feature to OnVista.pm - PR #480 * Added EXCHANGE feature to Consorsbank.pm and some fixes - PR #479 * Enabled usage count in Quote.pm. Removed DWS.pm. Improved check that divident is a number in YahooJSON.pm by adding regex anchors. * Added ssl_opts to user agent in Union.pm to account for bad certificate chain - Issue #482 * Added ETFs search, more labels and EXCHANGE feature to Comdirect.pm - PR #478 * GoogleWeb - Add support for Hong Kong Exchange - PR #476 * YahooJSON - Adjust other currency fields when data is returned in GBp, ZAc, or ILA. * Added label wkn, ìsin, close, ask, bid, p_change, time to OnVista.pm * Modified OnVista.pm to enable search by ISIN and WKN and search for ETFs * Added methodinfo hash to Tradegate.pm - PR #465 * Added label isin, open, ask, bid, time to Tradegate.pm - PR #465 * Fixed Tradegate.pm - Search for ETFs - PR #465 * Removed MorningstarAU - Issue #405 * Fixed malformed character in YahooWeb.pm - Issue #468 * Changed useragent in YahooJSON.pm - Issue #467 * Added methodinfo hash to AEX.pm - PR #466 * Added label exchange, close, time to AEX.pm - PR #466 * Fixed XETRA.pm - Issue #460 * Added methodinfo hash to Fool.pm * Fixed MorningstarJP.pm - Issue #443 - PR #451 1.64 2024-11-27 18:11:09-08:00 America/Los_Angeles * Update AlphaVantage.pm - Issue #447 * Fix to Stooq.pm - Issue #445 * Updated ASX.pm - Issue #404 * Added more fields to CSE.pm and exposed all labels. * Changed parsing in Comdirect.pm - Issue #413 * Complete rewrite of OnVista.pm - Issue #414 * Minor fix to FinanceAPI decoding JSON - Issue #434 * Modified YahooJSON to deal with "nan" as dividend yield in JSON. * New CurrencyRates module, CurrencyRates/FinanceAPI - Issue #427 * Fixed Bourso.pm - Issue #417 * Allowed Currency Rates modules Fixer.pm and OpenExchange.pm to read their API keys from environment variables - Issue #426 1.63 2024-09-21 12:47:39-07:00 America/Los_Angeles * Fixed TesouroDireto.pm - Using different source URL - PR #424 * Added FinanceAPI.pm - Requires API key from https://financeapi.net/. US and other exchange data available. * Fixed BVB.pm - Issue #409 * Fixed BSEIndia.pm - Issue #410 and removed Unzip as quotes file is now a CSV file * Fixed NSEIndia.pm - Issue #410 * Fixed NZX.pm - Issue #401 1.62 2024-05-16 18:19:12-07:00 America/Los_Angeles * Fixed AEX.pm * Removed throttling from AlphaVantage.pm - Issue #363 * Added CurrencyFreaks.pm - new currency module * YahooJSON.pm - added more error handling - Issue #390 * Fixed MarketWatch.pm module - Issue #389 * Fool.pm - Rewrote Fool.pm and added back to F::Q. Issue #379. * New Module! StockData.pm. Methods stockdata, nyse, nasdaq. * Modified yahooJSON.pm module in order handle EU consent redirects better. * TwelveData.pm - Added "last" to data being returned * BorsaItaliana.pm - New module for Borsa Italiana, Italian traded bonds using ISIN * YahooWeb.pm - Issue #377. Modified YahooWeb to account for changes from Yahoo. 1.61 2024-04-18 21:34:24-07:00 America/Los_Angeles * SIX.pm - Changed lookup for currency, added lookups for symbol and last. Issue #380 * YahooJSON.pm - URLs to retrieve required cookies and crumbs were changed to allow EU based users to use the module. Issue #373 1.60 2024-04-15 17:34:46-07:00 America/Los_Angeles * Removed not working modules. Issues #346, #366, and #368. Fidelity.pm, Cdbfundlibrary.com, Fundata.pm, and Fool.pm. * YahooJSON.pm - Added code to retrieve cookies and a "crumb" required to continue to utilize the v11 API. Issue #369. The YahooJSON.pm currency module was changed to use the v8 API. * Added initial version of CONTRIBUTING.pod that metacpan.org utilizes. It will completely replace the Hacker's Guide in the future. * Bloomberg.pm - Changed module to extract data from JSON structure embedded within the HTML - Issue #360 * NSEIndia.pm - Eliminated need to use temp folders by storing file data from URL into a variable. 1.59 2023-12-31 14:52:12-08:00 America/Los_Angeles * Fixed XETRA, Tradegate and SInvestor after webpage was restructured - Issue #350 * Fidelity.pm temporarily disabled - Issue #346 * Finanzpartner.pm - Fix scraper, did not work if quote was higher than the previous day's quote. * GoogleWeb.pm - Updated to retrieve mutual fund and BATS prices - Issue #355 * BSEIndia.pm - Updated to use standardized data file at URL https://www.bseindia.com/download/BhavCopy/Equity/BSE_EQ_BHAVCOPY_{DDMMYYYY}.ZIP Eliminated need to use temp folders by storing standardized file data from URL into a variable. Updated names of source fields to conform to those in the standardized data file * IndiaMutual.pm - Eliminated need to use temp folders by storing nav file data from URL into a variable. * TMX.pm - Correct a self-reference in documentation - PR #345 * Stooq.pm - Added new currencies and a fix for commodities' prices * YahooWeb.pm - Skip rows in the price table where the prices are "-". This seems to happen sometimes with TIAA (and perhaps other) securities including TILIX and QCILIX * TSP.pm - Was not returning hash when the HTTP GET failed completely or the content did not contain the expected CSV file. - Issue #338 * BSEIndia.pm - Removed print when symbol not found - Issue #335 1.58 2023-08-12 10:59:05-07:00 America/Los_Angeles * Consorsbank.pm - New module - PR #329 * Stooq.pm - New module - Issue #203 * Bloomberg.pm - Changed modules to utilize cookie jar - PR #331 - Issue #324 * AlphaVantage.pm - Apply currency scaling (GBp -> GBP) when symbol had additional ".X" suffix - Issue #281 Fixed check for "Information" JSON usually returned when daily API limit has been reached. * YahooWeb.pm - Fixed incorrect pricing for single character symbols and changed URL to get trade date - Issues #314 #319 * Another fix to the URL in YahooJSON and CurrencyRates/YahooJSON - Issue #318 1.57 2023-07-01 12:37:07-07:00 America/Los_Angeles * Correct set exchange in YahooJSON.pm - Issue #306 * Added close, change and p_change to Tradegate, XETRA and Sinvestor Added optional parameter INST_ID to specify the institute id. Fixed bug in Tradegate, XETRA and Sinvestor for numbers equal or higher than 1.000 - PR #304 * Added GoogleWeb Module * YahooWeb module added - PR #296 * Added MarketWatch Module * Replaced cached file with IO::String object in IndiaMutual.pm * Fixed missing date in AEX.pm - Issue #298 * Fixed Examples in POD Documentation in a few modules - PR #295 * move use strict to be the first statement in TreasuryDirect.pm and TwelveData.pm #290 * remove old perl version requirement statements from TreasuryDirect.pm and TwelveData.pm #290 * removed Data::Dumper that caused another test to fail from TreasuryDirect.pm #290 * Fixed Fool.pm and fool.t - PR #289 1.56 2023-05-29 14:56:23-07:00 America/Los_Angeles * Replaced Tradeville.pm with BVB.pm - Issue #269 * Added new TwelveData module * Updated YahooJSON.pm and CurrencyRates/YahooJSON.pm to use https://query2.finance.yahoo.com/v11 - PR #284 * Bourso.pm - Squash anything but numbers and period in quote values. * Renamed MStarUK.pm to MorningstarUK.pm * Added get_features method - PR #260 1.55 2023-05-13 12:22:00-07:00 America/Los_Angeles * Added YahooJSON currency rate module PR #270 * Added TRV => CAD in AlphaVantage.pm. Issue #265 - PR #267 * Quick fix for YahooJSON.pm API * URL Change for MorningstarJP. Issue #261 * Regex fix in FTfunds.pm and changed test cases ftfunds.t. PR #262 1.54 2022-12-26 15:25:02-08:00 America/Los_Angeles * Fix to AEX.pm - Issue #235 - PR #244 * New modules Sinvestor.pm Tradegate.pm and XETRA.pm PR #243 * Updates to TMX.pm (Toronto Stock Exchange) PR #248 and #253 * Reverted API change (PR #230) in CurrencyRates/AlphaVantage.pm PR #249 * Fix to Fondsweb.pm PR #250 1.53 2022-10-08 18:11:07-07:00 America/Los_Angeles * dist.ini - changed bugtracker.web to https://github.com/finance-quote/finance-quote/issues * DWS.pm - Set $info{$symbol, 'symbol'} to $symbol. * Union.pm - #231 - reworked for a different CSV file. * CurrencyRates/AlphaVantage.pm - API CURRENCY_EXCHANGE_RATE no longer accepts free API keys. Changed to use FX_DAILY API. Issue #229 PR #230 * Set minimum version for LWP::UserAgent in dist.ini to honor redirects. * CurrencyRates/AlphaVantage.pm - Added logic to account for empty JSON returned from currency exchange fetch. * Bourso.pm - Added Europe and France back as failover methods. These were removed some time ago in commit e26484b. * Tradeville.pm - Changed hostname in URL to tradeville.ro. Added logic to better account for the symbol not being found. * YahooJSON.pm - #202 - Account for symbols with '&'. * Minor change to isoTime function in Quote.pm. * Updated TSP.pm - PR #227 - update URL and handling of dates. 1.52 2022-07-03 15:15:38-07:00 America/Los_Angeles * Quote.pm - Fixed logic for FQ_LOAD_QUOTELET starting with "-defaults" reported in issue #197. PR #199. * AlphaVantage currency module: Don't recurse infinitely when exchange rate is less than .001 (PR 193) * Bourso.pm - Fixed data bug reported in issue #174 with PR #194. * TSP.pm - Minor fix for URL used to retrieve data. PR #195. Note: URL was changed after the PR was merged. Module remains in a non-working status. * TesouroDireto.pm - New module for Brazilian's National Treasury public bounds. PR #198. * Bloomberg.pm - Update Bloomberg class names #205. Correct html parsing errors. * MorningstarCH.pm - Re-enabled and fixed in #207. * ZA.pm - Change to return price from sharenet in major denomination. PR #208. * Changes to SourceForge project website HTML files. * Add [Prereqs] to dist.ini. #215 1.51 2021-07-04 14:41:59-07:00 America/Los_Angeles * Fix bugs in t/fq-object-methods.t * Add code to hide warning in t/currency_lookup.t 1.50 2021-06-26 20:52:16-07:00 America/Los_Angeles * New modules: CurrencyRates * Updated modules: ASX, TIAA-CREF, Fool, Currencies * Corrected some POD issues (thanks to the Debian Perl Group) 1.49 2019-06-30 12:20:58+02:00 Europe/Brussels * Alphavantage: Removed Time::HiRes dependency due to mswin32 not supporting clock_gettime calls. 1.48 2019-06-30 01:19:14+02:00 Europe/Brussels * Alphavantage: Add a waiting mechanism to comply to alphavantage use terms * Alphavantage: Added several stock exchange support and currency * Updated modules: Union, Deka, Indiamutual, ASX, Yahoojson, TSP, AEX, Fool * New modules: IEXTrading, MorningstarAU, MorningstarCH, IEXCloud * Yahoo: removed modules referring to yahoo API, which yahoo stopped * BUGFIX: 'use of uninitialized value' returned by perl could make gnucash fail when more than 15 quotes where requested * BUGFIX: MS Windows does not support %T in strftime call * Added new documentation files: Release.txt, Hackers-Guide, Modules-README.yml * We started moving known failing tests into TODO blocks * This release is the result of hard work by Bruce Schuck, Vincent Lucarelli, Pieter-Jan Vandormael, Manuel Friedli, Jalon Avens, Chris Good, Mark J. Cox, Eelco Dolstra, Henrik Ahlgren, Vinay Shastry, Mike Alexander, Erik Colson. * Special thanks go to Bruce Schuck and Vincent Lucarelli which joined the maintainers team! 1.47 2017-11-12 17:19:42+01:00 Europe/Brussels * Use AlphaVantage for currency quotes instead of Yahoo (Mike Alexander) 1.46 2017-11-12 17:13:15+01:00 Europe/Brussels * Drop long-obsolete debian directory (Florian Schlichting) * AlphaVantage: - added support for .IL => USD currency and division (Adriano Baldi) - graceful error catchup (Mike Alexander) * Yahoojson: - module adapted to new URL and returned json (Rafael Casali) 1.45 2017-11-08 21:35:51+01:00 Europe/Brussels * alphavantage * more suffix - currency pairs added * GBP and GBX divided by 100 1.44 2017-11-07 21:57:57+01:00 Europe/Brussels * Added currencies for .SA (Brazil) and .TO (Canada/Toronto) markets * Setup a pause of .7s between queries in AlphaVantage.pm to limit queries 1.43 2017-11-06 23:41:47+01:00 Europe/Brussels * Added currency for .DE market * BUGFIX in currency determination regex 1.42 2017-11-06 19:04:25+01:00 Europe/Brussels * more tests in alphavantage.t * BUG resolved: removed time from $last_refresh when markets are open 1.41 2017-11-06 16:06:41+01:00 Europe/Brussels * return symbol for AlphaVantage data 1.39 * added AlphaVantage module (Matthew Patterson) * some other module changes: yahoojson, Morningstar, Bourso, TSX (not working) 1.38 2015-08-22 13:22:56+02:00 Europe/Brussels * module updates: tiaacref, yahooJSON, FTfunds, MStaruk, USFedBonds, GoldMoney * new modules: fidelityfixed (Peter Ratzlaff), yahooYQL * removed modules: MTGox * more tests: yahoo_speed.t, tiaacref.t 1.37 2015-02-01 20:24:32+01:00 Europe/Brussels * modified 00-use.t to show more info * Remove Crypt::SSLeay dependency in favor of LWP::Protocol::https (Geert Janssens) * Updated HU.pm and test file to current website (Kristof Marussy) 1.36 2015-01-31 * MorningstarJP : changed dependency from Date::Calc to DateTime 1.35 2014-06-17 08:06:14+02:00 Europe/Brussels * BUGFIX: VWD - currency returned. 1.34 2014-06-15 21:30:03+02:00 Europe/Brussels * VWD adapted to recent website change. 1.33 2014-06-01 11:24:24+02:00 Europe/Brussels * BUGFIX: yahoo_json never returns currency. so don't set a default. yahoo_json returned current timestamp instead of quote date. 1.32 2014-05-18 21:55:12+02:00 Europe/Brussels * return "symbol" for yahoo_json module * allow to retrieve ISIN codes with VWD module 1.31 2014-05-04 22:56:45+02:00 Europe/Brussels * Case mismatch in ZA_UnitTrusts pod 1.30 2014-05-04 21:56:34+02:00 Europe/Brussels * Bourso.pm works on current website changes. Thanks to Guillaume * New module CSE.pm by Hiranya Samarasekera. Covers Colombo Stock Exchange (CSE) in Sri Lanka. 1.29 2014-04-08 08:28:25+02:00 Europe/Brussels * New module za_unittrusts. kudos to Rolf Endres. * Indiamutual patched to current website. kudos to Vinay S Shastry. 1.28 2014-03-16 12:05:19+01:00 Europe/Brussels * Removed some unnecessary dependencies 1.27 2014-03-05 15:04:22+01:00 Europe/Brussels * New module YahooJSON added (Abhijit Kshirsagar) * mtgox.t should not create errors when ONLINE_TEST is not defined 1.26 2014-03-03 22:24:58+01:00 Europe/Brussels * VWD adapted to current website (skaringa) * Boursorama module mostly fixed (Arnaud Gardelein) 1.25 2014-03-02 23:18:23+01:00 Europe/Brussels * AEX.pm : Some checking added. Not working yet * Quote.pm : readded sub parse_csv_semicolon. 1.24 2014-03-02 21:39:00+01:00 Europe/Brussels * Added module Citywire.pm written by Martin Sadler * Added module FTfunds.pm written by Martin Sadler * Added module MStaruk.pm written by Martin Sadler * Added module TNetuk.pm written by Martin Sadler * Data::Dumper is no more required to build 1.23_02 2014-03-02 14:46:41+01:00 Europe/Brussels (TRIAL RELEASE) TEST RELEASE 1.22 2014-03-02 10:38:18+01:00 Europe/Brussels (TRIAL RELEASE) TEST RELEASE 1.21 2014-03-02 09:56:38+01:00 Europe/Brussels (TRIAL RELEASE) * ASX.pm now supports querying more than 10 symbols (goodvibes2) * Travis config added for automated test build * BUILD: Updated to use Dist::Zilla 2014-02-17 Erik Colson * INSTALLATION FIX: Install Date::Calc as dependency 2014-02-16 Erik Colson * MtGox support added by Sam Morris * MorningstarJP support added by Christopher Hill * Yahoo modules changed due to site change 2010-02-16 Erik Colson * TSP.pm updated. Patch from Kevin Ryde. * YAHOO/Base.pm added conversion from 'B' billions to numbers. Patch from Kevin Ryde. * Billions support moved to Quote.pm 2009-10-05 Erik Colson * Lots of tests added 2009-10-04 Erik Colson * IndiaMutual.t tests added * Yahoo_europe: Bug 44245 solved. Wrong fields returned. * Documentation: Bug 48818 corrected. 2009-10-03 Erik Colson * yahoo_europe.t tests corrected 2009-09-30 Erik Colson * Bourso.t tests corrected 2009-09-29 Erik Colson * AEX.pm removed code for options and futures. (didn't work) 2009-07-19 Erik Colson * Bug in Yahoo::Base corrected. Sometimes year range is wrongly returned. 2009-07-19 Giles Robertson * Bug in Yahoo::Base corrected. GBp wrongly interpreted. 2009-07-19 Stephan Ebelt * Goldmoney.pm patched. support for platinum added. 2009-07-18 Zoltan Levardy * New module HU.pm (Hungarian stocks) 2009-06-14 Divakar Ramachandran * Bug RT 46155 solved by modifying link in IndiaMutual.pm 2009-06-14 Stephan Ebelt * Cominvest URL modified 2009-06-14 Erik Colson * currencies adapted to yahoo denomination 2009-06-13 Sattvik * currency retrieval updated (yahoo website changed) 2009-04-27 Bradley Dean * ASX.pm updated due to website change 2009-04-13 Erik Colson * Release 1.16 2009-04-12 Erik Colson * BUGFIX: Bourso.pm allmost completely rewritten due to website change. * BUGFIX: Morningstar.pm patched by Fredrik Persson. * BUGFIX: AEX.pm patched by Herman van Rink. 2009-03-19 Erik Colson * BUGFIX: 12:XXpm formatted time handling. 2009-03-04 Erik Colson * RENAMED ITE is now RZR 2009-03-02 Erik Colson * BUGFIX: ZA.pm patched. 2009-03-01 Erik Colson * BUGFIX: Stephen Ebelt patch applied for goldmoney.pm * BUGFIX: Encoding problem solved by Ashwin 2009-02-16 Bradley Dean * NEW: Function fetch_live_currencies. 2009-02-15 Bradley Dean * NEW: Module Finance::Quote::Currencies created for use in currency_lookup. 2008-12-05 Erik Colson * Finanzpartner module adapted to site updated. By Jan Wilamowius. * Morningstar patched by Fredrik Persson. 2008-11-09 Erik Colson * Bourso.pm updated due to website update. By Bernard Fuentes 2008-10-26 Erik Colson * BUG correction : IndiaMutual.pm: symbol not set it module. * Release 1.15 2008-10-21 Erik Colson * BUG correction : function isoTime - make sure $hours and $mins are treated as numbers * use sprintf in isoTime 2008-10-15 Erik Colson * Release 1.14 2008-10-13 Erik Colson * Finanzpartner.pm added by Jan Willamowius 2008-10-12 Erik Colson * isoTime function added * added quote.t * yahoo time is now format using isoTime 2008-10-11 Erik Colson * added prerequisite for HTML::TreeBuilder * union.t from todo 2008-10-10 Paul Fenwick * TEST: Ensure Data::Dumper is not accidently left in F::Q code. 2008-10-07 Erik Colson * Root README added * BUGFIX: asx.t 2008-10-05 Erik Colson * Added Cominvest module from Stephan Ebelt 2008-10-04 Erik Colson * Failing tests moved to todo-state for trustnet, usfedbonds, bourso, deka, union * BUGFIX: currency.t * BUGFIX: lerevenu.t - index ID corrected 2008-10-02 Erik Colson * Failing tests moved to todo-state for ftportfolios, aiahk, nzx, maninvestments. 2008-09-30 Paul Fenwick * BUGFIX: Applied patch from Bill Carlson to fix Tiaacref.pm. Thanks Bill, you rock! 2008-09-28 Erik Colson * StockHouseCanada.pm updated to new site layout * DWS.pm updated 2008-09-27 Erik Colson * VWD.pm updated to new site layout * TSX.pm added 2008-09-27 Paul Fenwick * DOCUMENTATION: Fixed malformed formatting in authors email addresses in Fiannce/Quote.pm. 2008-09-26 Erik Colson * AEX.pm updated to new site layout (comma used in numbers) 2008-09-22 Paul Fenwick * BUILD: Module::Install 0.77 is now used for building and installation. (PJF) * TESTING: Finance::Quote's test system has been restructured. Tests are now run during installation, but online and author tests are skipped by default. (PJF) 2008-09-21 Erik Colson * lib/Finance/Quote/Yahoo/Brasil.pm: Enable semicolon. 2007-05-13 David Hampton * lib/Finance/Quote/SEB.pm: Patch from Henrik Riomar to fix a problem finding quotes for funds with the Swedish chars åäö in the name. * lib/Finance/Quote/IndiaMutual.pm: Patch from Devendra Gera to remove white space when splitting the results in the AMFI data file. 2007-01-07 David Hampton * CVSTAG: finance_quote_1_13 * lib/Finance/Quote.pm: Update version to 1.13. * lib/Finance/Quote.pm: Update the store_date() function for the case when the year isn't explicitly stated. If the specified month would put the quote in the future, then consider this a quite from last year. manly intended to handle the rollover from December to January on web sites that don't specify the year. Suggestion from Christian Lupien. * lib/Finance/Quote/BMONesbittBurns.pm: Enhance module to support mutual fund quotes (which have less data than stock quotes). Correct date format parsing. Fix from Christian Lupien . * lib/Finance/Quote/StockHouseCanada.pm: The fund name seems to be a moving target. Search all tables of depth one looking for it. 2007-01-01 David Hampton * lib/Finance/Quote/FTPortfolios.pm: Rewrite this module for the new web site design. * lib/Finance/Quote/TSP.pm: Strip spaces around the dollar values. * lib/Finance/Quote/FinanceCanada.pm: Rewrite this module for the new web site design. * test/*.t: Some restructuring of tests. Add a couple more tests on dates. Replace a couple of test stocks that are no longer valid. * test/*.t: Accept last year as a valid date. Comes in hand when testing modules at the start of the new year. * lib/Finance/Quote/ZA.pm: Update for changes in the display of the web site. 2006-12-31 David Hampton * lib/Finance/Quote/StockHouseCanada.pm: * test/stockhousecanada.t: New module for getting Canadian Mutual fund quotes from Chris Carton . * lib/Finance/Quote/Deka.pm: Update for changes in the display of the web site. Now uses an https url. * lib/Finance/Quote/LeRevenu.pm: Updates from Dominique Corbex for changes in the display of the web site. * lib/Finance/Quote/Trustnet.pm: Update for changes in the display of the web site. 2006-09-11 David Hampton * Move AIA.pm to AIAHK.pm since AIA has sites in multiple countries. 2006-09-10 David Hampton * lib/Finance/Quote/AIA.pm: * test/aia.pm: New module to access American International Assurance fund information. Based on perl script from Wouter van Marle . * lib/Finance/Quote/Bourso.pm: Update the URL to track changes on the web site. 2006-07-10 David Hampton * lib/Finance/Quote.pm: Update version to 1.12. * CVSTAG: finance_quote_1_12 2006-06-27 David Hampton * Makefile.PL: State the dependency on Crypt::SSLeay that several modules now have. * lib/Finance/Quote/Yahoo/Base.pm: Don't set fields that are defined but are empty.. * test/yahoo_brasil.t: Update the test module to have more cases and use better stocks. * lib/Finance/Quote/Yahoo/Brasil.pm: Update for the change of the data separator from a semicolon to a comma. * test/financecanada.t: * test/hex.t: Add new test cases to cover the last untested modules. All modules are now tested. * lib/Finance/Quote/FinanceCanada.pm: Don't set the success flag if the lookup failed. * lib/Finance/Quote/Platinum.pm: Use the new url of the pricing information. * lib/Finance/Quote/ManInvestments.pm: Update for the new table format on the web site. * test/indiamutual.t: * test/aex.t: Update the test cases to use currently listed funds. * lib/Finance/Quote/ManInvestments.pm: The url for updating quotes has changed, as has the table header. * lib/Finance/Quote/Bourso.pm: * lib/Finance/Quote/LeRevenu.pm: * test/bourso.t: * test/lerevenu.t: Updated modules from Dominique Corbex . The name field now returns the real name and not ticker symbols, and other small fixes. 2006-04-08 David Hampton * lib/Finance/Quote/Trustnet.pm: Work around the fact that gnucash escapes the ampersand character when passing stock names to F::Q. * lib/Finance/Quote/DWS.pm: Updated module from Klaus Dahlke to retrieve quotes from the new DWS web page. * lib/Finance/Quote/Tiaacref.pm: Explicitly state in the code that this module requires ssl support (it uses an https:// url). This prevents perl from trying to run the code when ssl support isn't present. * lib/Finance/Quote.pm: * lib/Finance/Quote/HEX.pm: * test/hex.t: New module from Mika Laari to fetch quote information from the Helsinki stock exchange. * Documentation/Hackers-Guide: Add a section on the q->store_date() function. All modules should use this function to set the 'date' and 'isodate' fields based on the retrieved textual date (or lack thereof). * test/lerevenu.t: Test the right module. 2006-04-07 David Hampton * lib/Finance/Quote.pm: * lib/Finance/Quote/LeRevenu.pm: * test/lefrevenu.t: New module from Dominique Corbex to fetch information from the LeRevenu.com site in France. * t/yahoo_europe.t: New test cases for non-GBP London exchange stocks. Use new stock for test of the XETRA exchange. (Can't find a non-Euro stock there, so remove those tests.) * lib/Finance/Quote/Yahoo/Base.pm: Patch from p1n0@sourceforge.net to only divide London exchange values by 100 if they are denominated in pence. 2006-04-06 David Hampton * lib/Finance/Quote.pm: * lib/Finance/Quote/Bourso.pm: * test/bourso.t: New module from Dominique Corbex to fetch information from the "Paris Stock Exchange", http://www.boursorama.com. * lib/Finance/Quote/VWD.pm: Updated module from Jörg Sommer that is more tolerant of the advertising added to the web page. 2006-01-11 David Hampton * CVSTAG: finance_quote_1_11 * lib/Finance/Quote.pm: Updated $VERSION to 1.01 2006-01-10 David Hampton * test/dws.t: * test/maninvestments.t: * test/yahoo_brasil.t: Accept dates in both the previous and current year as valid responses. * test/aex.t: Change test currency to one still on the exchange. * lib/Finance/Quote/VWD.pm: Jörg Sommer's patch to work better with invalid WKNs and to extract the exchange information from its new location. 2005-11-18 David Hampton * lib/Finance/Quote/AEX.pm: Use the store_date function. * lib/Finance/Quote/ASX.pm: * lib/Finance/Quote/Cdnfundlibrary.pm: * lib/Finance/Quote/Platinum.pm: * lib/Finance/Quote/Trustnet.pm: * lib/Finance/Quote/Yahoo/Base.pm: Add code to protect against empty tables, data fields, etc. * test/aex.t: Correct the number of test cases. Changed test stock to one that is still on the exchange. Site no longer provides time (date only) so remove tests for time of quote. Futures quotes don't always have bid/ask values so comment out those tests. * test/indiamutual.t: * test/trustnet.t: * test/union.t: * test/yahoo.t: Changed test stock to one that is still on the exchange. * test/yahoo_brasil.t: All returned prices (for all stocks I tried) return a price of zero, so comment out the test for non-zero. 2005-10-23 David Hampton * lib/Finance/Quote/VWD.pm: Put back the call to the HTML::TableExtract first_table_state_found() function for now. At some point this should be removed and the 2.0 version of HTML::TableExtract required. * lib/Finance/Quote.pm: Make note of an alternate yahoo URL that can be used to obtain currency quotes. From Gerry Barksdale . 2005-10-22 David Hampton * lib/Finance/Quote/Tiaacref.pm: * test/tiaacref.t: Add Support for TIAA-CREF mutual funds from Brandon . * lib/Finance/Quote/VWD.pm: Remove the thousands separator character from quote values. * lib/Finance/Quote/TSP.pm: * test/tsp.t: Frank Mori Hess's change to add support for the TSP lifecycle L funds. * ChangeLog: Archive pre-2005 data into a separate file. * lib/Finance/Quote/Deka.pm: Add a new module to retrieve German investment fund prices from Deka. Module from Knut Franke * lib/Finance/Quote/USFedBonds.pm: * test/usfedbonds.t: Add a new US Federal Bonds stock quote module from Stephen Langenhoven . 2005-10-21 David Hampton * lib/Finance/Quote.pm: Updated to work with new Yahoo currency conversion pages. * lib/Finance/Quote/Tiaacref.pm: New URL from Kevin Foss. Uses https, so the Crypt::SSLeay module is now required for TIAA-CREF quotes. * lib/Finance/Quote/ZI.pm: Zürich Invest has been purchased by Deutsche Bank and integrated into DWS. The DWS.pm module should now be used in place of the ZI.pm module. * lib/Finance/Quote/VWD.pm: Patch from Rainer Dorsch to return the current price as 'last'. The HTML::TableExtract first_table_state_found() function has been deprecated. Accept both the old and new values as correct answers. * lib/Finance/Quote/ZA.pm: * test/za.t: Add a new South African stock quote module from Stephen Langenhoven . 2005-08-10 David Hampton * lib/Finance/Quote/VWD.pm: Handle invalid ISINs better. VWD delivers a 404 error instead of a blank page. Fix suggested by Uwe Simon 2005-07-04 Paul Fenwick * lib/Finance/Quote.pm: Bumped $VERSION to 1.10, primarily to work around a problem with CPAN distributions. * CVSTAG: finance_quote_1_10 2005-06-29 David Hampton * lib/Finance/Quote/VWD.pm: Updated module from Jörg Sommer . * lib/Finance/Quote/cdnfundlibrary.pm: Changes inspired by kalaleq@users.sourceforge.net allow retrieval of more data. * lib/Finance/Quote.pm: * lib/Finance/Quote/Yahoo/NZ.pm: New module from Stephen Judd . * lib/Finance/Quote/NZX.pm: Tweak to allow both NZX and Yahoo:NZ to get quotes for New Zealand Stocks (use method nz). * CVSTAG: finance_quote_1_09 2005-05-30 Paul Fenwick * MANIFEST: Updated with files intended for distribution. * lib/Finance/Quote.pm: Updated $VERSION to 1.09 * CVSTAG: Updated finance_quote_1_09 tag on MANIFEST and lib/Finace/Quote.pm * Released updated version 1.09 with new MANIFEST and Quote.pm to Sourceforge. * lib/Finance/Quote/VWD.pm: Re-enabled $VERSION and bumped to 1.01 to allow correct indexing on CPAN, however the older version currently remains in the 1.09 release. 2005-05-04 David Hampton * lib/Finance/Quote/FTPortfolios.pm: Update for changes in the website. * t/ftportfolios.t: New test module. * TSP.pm: Replace core parsing with tighter code. Support the symbols used by both Frank Mori Hess' and Trent Piepho's TSP modules. Make the symbols case insensitive. * lib/Finance/Quote/Cdnfundlibrary.pm: Find table by headers instead of by index. * test/asegr.t: New module * lib/Finance/Quote/ASEGR.pm: New module * lib/Finance/Quote/ASX.pm: Skip any blank lines in the table. Pass an extra parameter to TableExtract to keep it from doing unnecessary work that produces warnings. 2005-03-19 David Hampton * lib/Finance/Quote/Trustnet.pm: Encode the '&' character before calling the user agent GET function. Fixes bug 747080. 2005-03-19 David Hampton * almost all files: Collapsed all date parsing code into a single function. This function handles the date formats provided by all current quote sources and converts them all into the F::Q standard of a US date format. It also adds an ISO format date to all quotes in the new isodate field. Added lots of test functions to check date formats. 2005-03-19 David Hampton * lib/Finance/Quote/AEX.pm: Spelling correcting from Frank Mori Hess. * lib/Finance/Quote.pm: Documentation correction from Trent Piepho. * lib/Finance/Quote/Yahoo/Base.pm (yahoo_request): Fix an undefined reference when Yahoo ocassionally returns an empty field. 2005-03-01 David Hampton * lib/Finance/Quote.pm: New modules. New function to parse files separated by semicolons instead of commas. * lib/Finance/Quote/AEX.pm: Worked over modules from Johan van Oostrum. Most of the old AEX data has migrated elsewhere. * lib/Finance/Quote/ASX.pm: * lib/Finance/Quote/Trustnet.pm: Got the modules working again. * lib/Finance/Quote/ManInvestments.pm: * lib/Finance/Quote/Platinum.pm: New modules for Australian investment price sources from Ian Dall . * lib/Finance/Quote/NZX.pm: New modules for fetching quotes the from the New Zealand stock exchange. Provided by Michael Curtis. * lib/Finance/Quote/SEB.pm: New modules for fetching quotes from the Swedish Bank. Submitted by Tomas Carlsson. * lib/Finance/Quote/TSP.pm: New modules for fetching quotes from the US Govt. Thrift Service Plan. Submitted by Frank Mori Hess. * lib/Finance/Quote/Yahoo/Base.pm: Corrected currency tags for Vienna and Valence. * lib/Finance/Quote/Yahoo/Brasil.pm: Add new Yahoo Brasil module from Ismael Orenstein . * t/*: Various new test modules. 2005-02-09 David Hampton * lib/Finance/Quote/Fidelity.pm: * lib/Finance/Quote/Union.pm: Got the modules working again. * lib/Finance/Quote/Yahoo/Base.pm: Extract the currency directly from Yahoo, instead of looking it up in an exchange/currency mapping table. * t/*: Various new test and updated modules. 2005-02-06 David Hampton * lib/Finance/Quote/BMONesbittBurns.pm: Got the modules working again. 2005-01-14 David Hampton * lib/Finance/Quote/Cdnfundlibrary.pm: Got the module working again. 2004-07-02 Paul Fenwick * Added Finance/Quote/Tdefunds.pm thanks to David Grant. * Added t/tdefunds.t basic regression test (3 tests). * Fixed bug #916966, TASE prices were 100 times their true price. Thanks to Eldad Zack for the patch. 2004-02-08 David Hampton * Yahoo/Base.pm: Added suffixes for the Brussels and Dublin exchanges. 2003-09-20 Pawel Konieczny * AEX module: major update: fetching of futures quotes implemented. POD and test script updated as well. 2003-09-15 Pawel Konieczny * AEX module: major update: additional labels for options available: volume, oi, and other. Subframes cache implemented, resulting in substantial speedup for repeating (intraday) requests of individual options. 2003-09-12 David Hampton * Yahoo/Base.pm: Added suffixes for 1) the US Options, 2) US exchanges when using a non-US Yahoo site, and 3) the Zurich exchange. 2003-08-31 David Hampton * Yahoo/Base.pm: Added entry for the Lisbon Portugal stock exchange. 2003-08-31 Pawel Konieczny * AEX module: major update: fetching of stock and index options implemented. POD and test script updated as well. 2003-08-27 Pawel Konieczny * AEX module: following a suffestion of Paul Fenwick, value 'undef' is returned if no valid data could be dowloaded (previously it was returning empty strings in such cases). 2003-07-07 Pawel Konieczny * AEX module: added label 'symbol' 2003-07-06 Pawel Konieczny * Update of F::Q::AEX module: added a remap functionality which translates the official stock ticker to AEX CGI symbol * Update of F::Q::AEX module: Distiguishing indices and stocks: indices will have "currency" label undefined, stocks will have value "EUR". This fixes the currency conversion problem for indices. * Update of F::Q::AEX module: Cleanup of labels: "offer" renamed to "ask" ("ask" is more common, besides, it can be currency-converted). Label "offer" stays for backward compatibility. * Update of F::Q::AEX module: Cleanup of garbage in some fields. (Ocassionally, the http fetch & parse will return garbage for some values). 2003-07-04 Paul Fenwick * Fixed currency conversion problems. (#232075) * Fixed ASX problems whereby quotes were not being obtained. Thanks to Rik Harris for bringing this issue to light, and providing a correct URL. (#653025) * Fixed problem where no symbol tag was being defined in ASX. Thanks again to Rik Harris. (#653035) * Fixed problem where undefined currencies would be returned as zero, and not undefined as occured in previous versions. * Fixed tests in currency.t which assumed that conversion could be done between EUR and former European currencies. These are no longer supported due to a change in the lookups provided by Yahoo. * Updated Fidelity.pm to indicate its current non-working status. * Added T. Rowe Price patches by David Hampton, which provide a troweprice_direct method. (#666351) * As above, for the Fidelity module. (#666353) * Accepted David Hampton's patch to Yahoo currencies. The correct currency will now be flagged on stocks regardless of the module used to grab them. (#666361) * Added Keith Refson's module to obtain quotes from First Trust Portfolios L.P. Thanks to David Hampton (again!) for supplying this. (#670202) * Added Ganesan Rajagopal's excellent IndiaMutal module. (#720896) * Fixed Cdnfundlibrary to correctly set the success flag when successful. Thanks to Robert Clark for finding this bug. (#752395) * Updated Tdwaterhouse.pm with Robert Clark's changes to work with the new TD Waterhouse website. Many thanks to Robert Clark again. (#750843) * Added the BM Nesbitt Burns module provided by Robert Clark, including test cases. (#752423). * Updated BMONesbittBurns module to provide better date handling, screening out of high-ascii characters from fields, and removal of spurious debug output. * CVSTAG: finance_quote_1_08 2003-02-04 Paul Fenwick * Fixed VWD problem whereby no symbol was being returned. Many thanks to Joachim Breitner for this fix. (#600698) * Updated TrustNet documentation, thanks to David Hampton. (#666349) 2002-06-25 Paul Fenwick * Added ZI.pm and Union.pm modules. Thanks to Rainer Dorsch for providing these. 2002-04-18 Paul Fenwick * Patched Quote.pm after currency conversion started to fail. Many thanks to Sean Wenzel for the fix. * Updated documentation in ASX.pm and Trustnet.pm to note their current functional unhappiness. * CVSTAG: finance_quote_1_07 2001-12-19 Paul Fenwick * Added Finance::Quote::Yahoo::Asia to provide lookup of Asian stock quotes (not including Japan). Many thanks to M.R.Muthu Kumar for this patch. 2001-07-23 Paul Fenwick * Updated Yahoo::Europe.pm so that stocks fetched from the Stockholm exhcnage (.ST) are correctly listed as being in Swedish Krona (SEK). 2001-07-04 Paul Fenwick * Confirmed debian packages release with Ross Peachey. * Moved cvs tags in debian/* so that finance_quote_1_06 point to the files used in building 1.06, not those building 1.05. 2001-06-26 Paul Fenwick * Added AEX module courtesy of Rob Sessink. * Updated ASX module to use the new (again!) ASX website. * Updated ASX module to use HTML::TableExtract. Code is much simplier now. * Updated Fidelity module to make use of the new fidelity website. * Updated the Fidelity test to always test the fidelity_direct method. * Bumped version number on F::Q to 1.06. * Updated Trustnet module to assume GBP if no currency explicitly shown. * Updated Trustnet test because one of the funds we were looking for had changed its name, causing the test to fail. * Added AEX test suite to repository. * Removed failover into Fool.pm support, as I'd like to do more testing before it enters the failover system. * Updated the FAQ. * Updated the INSTALL file. * Moved regression tests from /t to /test. Updated MANIFEST file accordingly. This means that autoamtic installs from CPAN won't run the regression tests, which was causing installation to fail on some systems. * Updated lists of copyright holders, SEE ALSO sections in man-pages. * CVSTAG: finance_quote_1_06 2001-06-25 Linas Vepstas * Added Tdwaterhouse module from James A. Treacy. 2001-06-04 Brent Neal * Fixed Tiaacref.pm to reflect new CGIs at www.tiaa-cref.org New symbols available for the module - check the POD documentation for more info. 2001-05-11 Paul Fenwick * Tweaked VWD.pm to strip whitespace from currency and remove asterisks from names. 2001-05-10 Volker Stuerzl * Updated VWD.pm to account for changes in VWD website. 2001-05-09 Paul Fenwick * Fixed strange behaviour which could occur when using FQ_LOAD_QUOTELET environment variable. This now works as intened. * Added Jasmin Bertovic's Cdnfundlibrary module. 2001-05-08 Paul Fenwick * Applied Leigh Wedding's patch to ASX.pm, after ASX changed their website yet again! All is working happily once more. Thanks Leigh. * Added Tobias Vancura's F::Q::Fool module. * Added Fool as a automatically loaded loaded module from F::Quote.pm. 2001-04-05 Paul Fenwick * Updated ASX.pm module to reflect changed location of information on the ASX's website. (Still doesn't help when the ASX site is totally broken, as is too often the case.) * Updated Trustnet.pm module to reflect changes to the Trustnet site. 2001-02-16 Paul Fenwick * Updated to repsect formatting changes in data fed to the currency function. * Updated Quote.pm to include updated information on FQ_LOAD_QUOTELET * CVSTAG: finance_quote_1_05 2001-01-22 Paul Fenwick * Updated to respect the FQ_LOAD_QUOTELET environment variable to auto-load custom Quotelet. 2001-12-05 Paul Fenwick * Updated the Yahoo::USA source to finance.yahoo.com as the quote.yahoo.com may become depreciated in the future. Thanks to Iain Lea for spotting this. 2000-11-29 Paul Fenwick * Updated the URL we obtain currency information to http://uk.finance.yahoo.com/m5?" * Updated docs in Yahoo/Europe.pm to note the Xtera exchange moving from FX to DE. * Thanks to Jan Willamowius for the above two changes. 2000-11-21 Paul Fenwick * Extra code to ensure that currency-fields returned by a Quotelet are unique. This prevents the potential bug of a field undergoing currency conversion multiple times and hence being quite off-track. 2000-11-05 Paul Fenwick * BUG 121557: Fixed bug where the 40th symbol in a Yahoo lookup would fail. * F::Q::UserAgent is now ready for release, but is still considered experimental. Users must explicitly turn it on by setting $Finance::Quote::USE_EXPERIMENTAL_UA = 1; * Updated the FAQ. * Added MANIFEST file. * CVSTAG: finance_quote_1_04 2000-10-29 Paul Fenwick * F::Q now makes use of a custom F::Q::UserAgent to fetch information. This is capable of doing proxy authentication and other arbitary http-headers. 2000-10-27 Paul Fenwick * Updated yahoo_europe test suite as one of the symbols we were using for testing has since dissapeared. (Bankrupt? Merged?) 2000-10-20 Paul Fenwick * Much better discovery of non-existant stocks in ASX.pm. * Checks for possible divide-by-zero problems in ASX.pm. Thanks to Stephen Stebbing for catching this. * Updated ASX testing. * Updated all test scripts to remove spurious warnings under Perl 5.6. * Updated ASX module to deal with stocks when they have market announcements. Previously this would result in garbage being returned for that stock. * Updated VWD module so it can parse information from the new VWD site. * Updated F::Q version to 1.03. * CVSTAG: finance_quote_1_03 2000-09-27 Paul Fenwick * Updated Trustnet module with patch from Keith Refson. 2000-09-16 Paul Fenwick * Added Volker's VWD module and testing script. * Updated Makefile to check for HTML::TableExtract. * Updated INSTALL file to provide infomation on how to install modules that F::Q depends upon. * Updated Quote.pm to load VWD, DWS and Trustnet by default. * Updated README file to mention the webpage. * Added Trustnet regression testing program. * CVSTAG: finance_quote_1_02 2000-09-12 Paul Fenwick * Mention of Bill Bell's java library in the FAQ. 2000-09-04 Paul Fenwick * Keith Refson's patch to Trustnet to avoid premature returns in case of a bad symbol. 2000-09-01 Paul Fenwick * Tweaked ASX.pm to avoid divide-by-zero errors and dodgy bogus-looking label values. 2000-08-31 Paul Fenwick * Added Keith Refson's Trustnet module. * Added .cvsignore file to reduce spam for developers using CVS. * Updated Yahoo/USA.pm to provide more compatible returns when called as a fidelity failover. * Tweaked DWS.t testing script so that it loads the module correctly. 2000-08-29 Paul Fenwick * Rejiggered ASX module to try and make it work again after an ASX site rewrite. * Updated Quote.pm so that if a method was called directly (old-style) not through fetch, then it would do the right thing if called via an object. This means that things like $q->asx(@stocks) work correctly again. * Updated fetch() method so that it returns the empty list rather than undef when called in an array context. * fetch() now returns a hashref if called in a scalar context. 2000-08-22 Volker Stuerzl * Added DWS test script. 2000-08-21 Paul Fenwick * Improved documentation in the yahoo_europe test script. 2000-08-16 Paul Fenwick * Added DWS.pm module to the CVS repository, courtesy of Volker Stuerzl. This module fetches information from the Deutsche Bank Gruppe. * Updated asx.t script because it really hurts the entire "make test" thing when ASX is unhappy. Now it still hurts (because the ASX module sucks), but less. 2000-08-14 Paul Fenwick * Updated chkshares script so that it can deal with any market, not just the ASX. 2000-08-06 Paul Fenwick * Updated regression testing scripts to make sure that spurious percentage signs are no longer returned. 2000-08-04 Paul Fenwick * Patched Yahoo::Base to no longer return spurious percentage signs. 2000-07-31 Paul Fenwick * The currency function no longer makes an expensive HTTP request if both the to and from currencies are identical. 2000-07-25 Paul Fenwick * Finance::Quote::Yahoo::Base now removes more HTML-ish guff that Yahoo tries to place in CSVs. * Updated yahoo_europe.t to check that stocks from London are in GBP. * Finance::Quote::Yahoo::Europe now returns London stocks in GBP. Previously it was incorrectly returning them in pence and calling it Euros. * Finance::Quote has a new scale_field() function that is used in currency conversion and by some sub-modules (Yahoo::Europe). This may be useful for future module writers. * CVSTAG: finance_quote_1_01 2000-07-16 Paul Fenwick * Documented the list of possible markets in Yahoo::Europe. * Many many small syntax fixes in documentation. * Expanded and improved webpage. * Updated revision to 1.00 * Updated INSTALL documentation. * CVSTAG: finance_quote_1_00 2000-07-15 Paul Fenwick * Wrote documentation for Yahoo::Europe (incomplete) and Yahoo::USA. Changed modules to require perl 5.005 because we make use of some of its features (like hash slices). * Improved labels documentation in Finance::Quote. * Added exchange and method information (where possible) to the various sub-modules. * Improved chkshares example script to check for errors. * Removed bad test in currency.t and replaced it with a better one. 2000-07-13 Paul Fenwick * Improved fidelity module such that it doesn't return information about stocks we did not request. 2000-07-08 Paul Fenwick * Added POD for TIAA-CREF and T. Rowe Price sub-modules. 2000-07-02 Paul Fenwick * Many more documentation improvements in both the Finance::Quote POD and the sub-modules. 2000-06-25 Paul Fenwick * Documented many of the new 0.19 functions in the Finance::Quote POD. 2000-06-24 Paul Fenwick * Updated currency regression testing script. * Updated currency fetching routines to handle different date formats returned by Yahoo! * Updated automatic currency conversion routines to avoid spurious warnings. 2000-06-23 Paul Fenwick * Cleaned up the hacker's guide. * Updated Examples/stockdump.pl to allow currency to be specified. * Automatic currency conversion now works. * Updated ASX and Yahoo::USA to not tag indexes with currency labels. * Updated Yahoo::Base to automatically accomodate suffixes for when we wish to add them automatically. * Added Yahoo::Australia to look up Australian stocks. * Tested failover of Yahoo::Australia to Yahoo::ASX. 2000-06-22 Paul Fenwick * Added sections on currency conversion to the hacker's guide. Now I just need to write the code. :) # Added currency conversion code, but haven't tested it yet. 2000-06-21 Paul Fenwick * Rolled all the Yahoo functions into a base pseudo-class. * Fixed bug whereby large lookups in the yahoo functions would overflow the maximum URL length of some proxies/servers. * Expanded the number of fields available via Yahoo::Europe. 2000-06-17 Paul Fenwick * Re-added currency lookups to Quote.pm * Added regression testing script for currency. * Added automatic currency conversion stub. * Removed TODO file as we now keep track of outstanding jobs in SourceForge. 2000-06-16 Paul Fenwick * Added webpage to CVS repository. 2000-06-03 Jacinta Richardson * Changed modules so they return undef in scalar context, empty list in list context, on failure. * Changed modules so they return a hashref when in scalar context, and a hash in list context. * Reviewed/corrected hackers guide. * Added currency tags to existing modules to signal currency type (AUD, EUR, USD) * Added tests to check currency tags. 2000-06-03 Paul Fenwick * Revived dead vanguard method by rolling it through Yahoo. * Added labels method to everything. * Added price labels to everything. * Updated Quote.pm to query new labels methods. * Provided a failover method for fidelity via Yahoo. * Added failover functionality. 2000-05-31 Paul Fenwick * Added Documentation/TODO. 2000-05-27 Paul Fenwick * Added Documentation/Hackers-Guide. * Tweaked Quote.pm to provide an AUTOLOAD method for those people who don't want to go through the fetch() methods. 2000-05-14 Paul Fenwick * Huge re-write and change of everything so that it should be easy to plug in new modules without changing any existing code. 2000-05-13 Paul Fenwick * Added Documentation/FAQ file. 2000-04-30 Paul Fenwick * Updated POD. * Improved returned error messages. * Updated the README file. * Updated the INSTALL file. * Tagged files as finance_quote_0_18 for release. 2000-04-25 Paul Fenwick * Rolled changes together when CVS got a little out-of-whack. :) * Return many new fields from yahoo() which we previously fetched but did not use. These include avg_vol, day_range, year_range, div_date, div, and div_yield. * Fixed typo in docs s/yeild/yield/; * Added ex_div for Ex-Divident Date in yahoo(). 2000-04-24 Brent Neal * Finished updating error-checking for tiaacref. The tiaacref function now returns a success/failure flag for every symbol passed to it. It also checks that the data is valid. Returns meaningful error messages for these failures. 2000-04-24 Paul Fenwick * Updated in-line code regarding checking for Yahoo! successes. * Added meaningful error-messages to Yahoo! when stock lookups fail. 2000-04-23 Paul Fenwick * Added success/fail tests to asx, fidelity, troweprice, yahoo and yahoo_europe. * Updated appropriate testing functions. * Updated documentation to include fetch and list of known bugs. * Updated yahoo_europe to return undef's instead of N/As. * Removed HTML from returns from yahoo_europe. * Fixed logic bug in process yahoo_* N/As into undefs. 2000-04-21 Brent Neal * Added checking for bogus symbols in tiaacref * Added checking of the LWP::UserAgent->is_success method * tiaacref() now supports $stocks{$sym,'success'} notation. * Updated t/tiaacref.t and Examples/Quote_example.pl for the changes 2000-04-21 Paul Fenwick * Added extra methods to fetch (nasdaq, nyse) which act as aliases to yahoo. * Added stockdump.pl example script, which is handy in debugging. * fetch is now an exportable function. * yahoo() function no longer returns entries for fields that used to be returned as 'N/A'. * yahoo() now supports the $stocks{$sym,'success'} notation. 2000-04-20 Paul Fenwick * Removed misleading comments from Finance::Quote.pm 2000-04-18 Paul Fenwick * Added fetch() function to provide a cleaner interface to fetching quotes from a variety of sources. * Added tests for fetch() to the asx.t test script. 2000-04-16 Paul Fenwick * Added yahoo_europe test script. * Removed depreciated vanguard function. 2000-04-14 Paul Fenwick * Added TIAA-CREF testing script. * Added troweprice testing script. 2000-04-13 Paul Fenwick * Fidelity private functions renamed to indicate they are private. * Small optimisations in fidelity functions to avoid spurious warnings and un-needed processing of non-useful lines. * Functions now quickly return undef when not passed a list of stocks. Previously they would waste time looking up nothing. * Documentation additions and corrections. * Added test files (Use.t, asx.t, yahoo.t, fidelity.t) * Added experimental function (currency) to look-up conversion rates between currencies. * Added an example script (currency-lookup.pl) to test said currency conversion. 2000-04-10 Paul Fenwick * Changed Examples/chkshares.pl to print a pretty table. * Incorporated Cooper Vertz's patch to add high, low and net change to quotes obtained from Yahoo! 2000-04-08 Paul Fenwick * Integrated TIAA-CREF changes from Brent Neal. * Changes to Makefile.PL to check dependancies, etc. * Updated Examples/Quote_example.pl to include TIAA-CREF examples. * CVSTAG: finance_quote_0_17 2000-04-06 Paul Fenwick * CVSTAG: finance_quote_0_16 * Initial public release. Finance-Quote-1.65/Examples/0000775000175000017500000000000015003302667015513 5ustar bschuckbschuckFinance-Quote-1.65/Examples/stockdump.pl0000755000175000017500000000076415003302667020071 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use lib '../lib'; use Finance::Quote; use Data::Dumper; use Getopt::Std; # A very very simple script. Takes a source and a symbol, looks it up, # and dumps it to STDOUT. Useful for debugging. my %options = ('c' => ''); getopts('c:',\%options); die "Usage: $0 [-c currency] source symbol\n" unless (defined $ARGV[1]); my $q = Finance::Quote->new; if ($options{'c'}) { $q->set_currency($options{'c'}); } my %quotes = $q->fetch(@ARGV); print Dumper(\%quotes); Finance-Quote-1.65/Examples/currency-lookup.pl0000755000175000017500000000076315003302667021220 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use lib '../lib'; use Finance::Quote; # This script demonstrates how currencies can be converted using # Finance::Quote. # Example usage: currency-lookup.pl USD AUD # (Converts from US Dollars to Australian Dollars) die "Usage: $0 FROM TO\n" unless defined($ARGV[1]); my $q = Finance::Quote->new(); my $exchange_rate = $q->currency($ARGV[0],$ARGV[1]); die "Urgh! Nothing back\n" unless $exchange_rate; print $ARGV[0]."->".$ARGV[1]." = ".$exchange_rate."\n"; Finance-Quote-1.65/Examples/Quote_example.pl0000755000175000017500000000477015003302667020671 0ustar bschuckbschuck#!/usr/bin/perl -w # # example script showing how to use the Quote perl module. # gets prices for some stocks, for some mutual funds # # This script was originally part of GnuCash. use lib '../lib'; use Finance::Quote; my $q = Finance::Quote->new(); # ----------------------------------- # get quotes for two stocks ... %quotes = $q->yahoo ("IBM", "SGI"); # print some selected values print "NYSE by Yahoo: ", $quotes {"IBM", "name"}, " last price: ", $quotes {"IBM", "last"}, "\n"; print "NYSE by Yahoo: ", $quotes {"SGI", "name"}, " last price: ", $quotes {"SGI", "last"}, "\n"; # loop over and print all values. # Notes that values are stored ion a multi-dimensional associative array foreach $k (sort (keys %quotes)) { ($sym, $attr) = split ($;, $k, 2); $val = $quotes {$sym, $attr}; # $val = $quotes {$k}; # this also works, if desired ... print "\t$sym $attr =\t $val\n"; } print "\n\n"; # ----------------------------------- # get quotes from Fidelity Investments @funds = ("FGRIX", "FNMIX", "FASGX", "FCONX"); %quotes = $q->fidelity (@funds); foreach $f (@funds) { $name = $quotes {$f, "name"}; $nav = $quotes {$f, "nav"}; print "Fidelity Fund $f $name \tNAV = $nav\n"; } print "\n\n"; # ----------------------------------- @funds = ("FGRXX"); %quotes = $q->fidelity (@funds); print "Not all funds have a NAV; some have Yeilds:\n"; foreach $f (@funds) { $name = $quotes {$f, "name"}; $yield = $quotes {$f, "yield"}; print "\tFidelity $f $name 30-day Yield = $yield percent\n"; } print "\n\n"; # ----------------------------------- # demo T. Rowe Price -- same as above @funds = ("PRFDX", "PRIDX"); %quotes = $q->troweprice (@funds); foreach $f (@funds) { $nav = $quotes {$f, "nav"}; $dayte = $quotes {$f, "date"}; print "T. Rowe Price $f NAV = $nav as of $dayte\n"; } print "\n\n"; # ----------------------------------- # demo for ASX. Grab the price of Coles-Myer and Telstra @funds = ("CML","TLS"); %quotes = $q->asx(@funds); foreach $f (@funds) { print "ASX Price of $f is ".$quotes{$f,"last"}." at ". $quotes{$f,"date"}."\n"; } print "\n\n"; # Demo for TIAA-CREF. @funds = qw/CREFstok BOGOname TIAAreal CREFmony/; %quotes = $q->tiaacref(@funds); foreach $f (@funds) { if ($quotes{$f,"success"} == 1) { print "TIAA-CREF Price of ".$quotes{$f,"name"}." is ".$quotes{$f,"nav"}. " at ".$quotes{$f,"date"}."\n"; } else { print "Error: ".$quotes{$f,"errormsg"}." for ".$f."\n"; } } print "\n\n"; Finance-Quote-1.65/Examples/chkshares.pl0000755000175000017500000000247415003302667020033 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use lib '../lib'; use Finance::Quote qw/asx/; =head1 NAME chkshares.pl - Check share information. =head1 USAGE chkshares.pl australia TLS CML ITE =head1 NOTES Example program. Demonstrates how to use one of the interface to Finance::Quote. The first argument must be the market. =cut my ($name, $date, $last, $p_change, $high, $low, $volume, $close); format STDOUT_TOP = STOCK REPORT TICKER DATE LAST %CHANGE HIGH LOW VOLUME CLOSE ---------------------------------------------------------------------------------- . format STDOUT = @<<<<<< @>>>>>>>>>> @####.### @###.### @####.### @####.### @>>>>>>>> @####.### $name, $date, $last, $p_change, $high, $low, $volume, $close . my $quoter = Finance::Quote->new(); my $market = shift || die "Usage: $0 market stocks\n"; my %quote = $quoter->fetch($market,@ARGV); foreach my $code (@ARGV) { unless ($quote{$code,"success"}) { warn "Lookup of $code failed - ".$quote{$code,"errormsg"}."\n"; next; } $name = $code; $date = $quote{$code,'date'}; $last = $quote{$code,'last'}; $p_change = $quote{$code,'p_change'}; $high = $quote{$code,'high'}; $low = $quote{$code,'low'}; $volume = $quote{$code,'volume'}; $close = $quote{$code,'close'}; write; } Finance-Quote-1.65/META.yml0000644000175000017500000000656215003302667015215 0ustar bschuckbschuck--- abstract: 'Get stock and mutual fund quotes from various exchanges' author: - 'Erik Colson ' - 'Bruce Schuck ' - 'Paul Fenwick ' - 'Vincent Lucarelli ' - 'David Hampton ' - 'moenny <>' - 'jvolkening ' - 'Pawel Konieczny ' - 'Mike Alexander ' - 'AndreJ ' - 'Vinay S Shastry ' - 'Bradley Dean ' - 'Brent Neal ' - 'Jacinta Richardson ' - 'e-dorigatti ' - 'goodvibes2 ' - 'Paul Howarth ' - 'Sam Morris ' - 'Linas Vepstas ' - 'Rajan Vaswani <111571283+bgr22112@users.noreply.github.com>' - 'Hiranya Samarasekera ' - 'Manuel Friedli ' - 'Przemysław Kryger ' - 'Achim Winkler ' - 'Diego Marcolungo ' - 'John Haiducek ' - 'alex314159 ' - 'gregor herrmann ' - 'Gustavo R. Montesino ' - 'Jalon Avens ' - 'Sigve Indregard ' - 'bgr22112 <111571283+bgr22112@users.noreply.github.com>' - 'goodvibes2 ' build_requires: Date::Manip: '0' Date::Range: '0' Date::Simple: '0' DateTime: '0' DateTime::Duration: '0' DateTime::Format::ISO8601: '0' File::Spec: '0' Module::CPANTS::Analyse: '0' String::Util: '0' Test::Kwalitee: '0' Test::More: '0' Test::Perl::Critic: '0' feature: '0' open: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010' license: gpl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Finance-Quote requires: Carp: '0' Compress::Zlib: '0' Date::Parse: '0' DateTime::Format::Strptime: '0' Encode: '0' Exporter: '0' HTML::Entities: '0' HTML::TableExtract: '0' HTML::TokeParser: '0' HTML::TreeBuilder: '0' HTML::TreeBuilder::XPath: '0' HTTP::CookieJar::LWP: '0.014' HTTP::Cookies: '0' HTTP::Headers: '0' HTTP::Request: '0' HTTP::Request::Common: '0' HTTP::Status: '0' IO::Handle: '0' IO::String: '0' IO::Uncompress::Unzip: '0' JSON: '0' LWP::Protocol::http: '0' LWP::Protocol::https: '0' LWP::Simple: '0' LWP::UserAgent: '6.48' Module::Load: '0.36' Mozilla::CA: '0' POSIX: '0' Readonly: '0' Scalar::Util: '0' Spreadsheet::XLSX: '0' String::Util: '0' Test2: '1.302167' Text::Template: '0' Time::Piece: '0' Time::Seconds: '0' Try::Tiny: '0' URI::Escape: '3.31' Web::Scraper: '0' XML::LibXML: '0' base: '0' constant: '0' if: '0' perl: v5.10.1 strict: '0' utf8: '0' vars: '0' warnings: '0' resources: bugtracker: https://github.com/finance-quote/finance-quote/issues homepage: http://finance-quote.sourceforge.net/ repository: git://github.com/finance-quote/finance-quote version: '1.65' x_generated_by_perl: v5.34.0 x_serialization_backend: 'YAML::Tiny version 1.73' x_spdx_expression: GPL-2.0-only Finance-Quote-1.65/lib/0000775000175000017500000000000015003302667014503 5ustar bschuckbschuckFinance-Quote-1.65/lib/Finance/0000775000175000017500000000000015003302667016046 5ustar bschuckbschuckFinance-Quote-1.65/lib/Finance/Quote.pm0000644000175000017500000015440215003302667017505 0ustar bschuckbschuck#!/usr/bin/perl -w # # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 1998, Dj Padzensky # Copyright (C) 1998, 1999 Linas Vepstas # Copyright (C) 2000, Yannick LE NY # Copyright (C) 2000, Paul Fenwick # Copyright (C) 2000, Brent Neal # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code derived from Padzensky's work on package Finance::YahooQuote, # but extends its capabilites to encompas a greater number of data sources. # # This code was developed as part of GnuCash package Finance::Quote; use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; use Module::Load; use Exporter (); use Carp; use Finance::Quote::UserAgent; use HTTP::Request::Common; use Encode; use JSON qw( decode_json ); use vars qw/@ISA @EXPORT @EXPORT_OK @EXPORT_TAGS $TIMEOUT @MODULES %MODULES %METHODS $AUTOLOAD @CURRENCY_RATES_MODULES $USE_EXPERIMENTAL_UA/; our $VERSION = '1.65'; # VERSION @CURRENCY_RATES_MODULES = qw/ AlphaVantage CurrencyFreaks ECB FinanceAPI Fixer OpenExchange YahooJSON /; @MODULES = qw/ AEX ASEGR ASX AlphaVantage BSEIndia Bloomberg BorsaItaliana Bourso BVB CSE Comdirect Consorsbank Currencies Deka FinanceAPI Finanzpartner Fondsweb Fool FTfunds GoldMoney GoogleWeb HU IndiaMutual MarketWatch MorningstarCH MorningstarJP MorningstarUK NSEIndia NZX OnVista Oslobors SEB SIX Sinvestor StockData Stooq TesouroDireto Tiaacref TMX Tradegate TreasuryDirect Troweprice TSP TwelveData Union XETRA YahooJSON YahooWeb ZA /; @ISA = qw/Exporter/; @EXPORT = (); @EXPORT_OK = qw/troweprice asx tiaacref currency_lookup/; @EXPORT_TAGS = ( all => [@EXPORT_OK]); $USE_EXPERIMENTAL_UA = 0; ################################################################################ # # Private Class Methods # ################################################################################ # Autoload method for obsolete methods. This also allows people to # call methods that objects export without having to go through fetch. sub AUTOLOAD { my $method = $AUTOLOAD; (my $name = $method) =~ s/.*:://; # Force the dummy object (and hence default methods) to be loaded. _dummy(); if (exists($METHODS{$name})) { no strict 'refs'; ## no critic *$method = sub { my $this = ref($_[0]) ? shift : _dummy(); $this->fetch($name, @_); }; return &$method; } carp "$AUTOLOAD does not refer to a known method."; } # Dummy destroy function to avoid AUTOLOAD catching it. sub DESTROY { return; } # _convert (private object method) # # This function converts between one currency and another. It expects # to receive a hashref to the information, a reference to a list # of the stocks to be converted, and a reference to a list of fields # that conversion should apply to. { my %conversion; # Conversion lookup table. sub _convert { my $this = shift; my $info = shift; my $stocks = shift; my $convert_fields = shift; my $new_currency = $this->{"currency"}; # Skip all this unless they actually want conversion. return unless $new_currency; foreach my $stock (@$stocks) { my $currency; # Skip stocks that don't have a currency. next unless ($currency = $info->{$stock,"currency"}); # Skip if it's already in the same currency. next if ($currency eq $new_currency); # Lookup the currency conversion if we haven't # already. unless (exists $conversion{$currency,$new_currency}) { $conversion{$currency,$new_currency} = $this->currency($currency,$new_currency); } # Make sure we have a reasonable currency conversion. # If we don't, mark the stock as bad. unless ($conversion{$currency,$new_currency}) { $info->{$stock,"success"} = 0; $info->{$stock,"errormsg"} = "Currency conversion failed."; next; } # Okay, we have clean data. Convert it. Ideally # we'd like to just *= entire fields, but # unfortunately some things (like ranges, # capitalisation, etc) don't take well to that. # Hence we pull out any numbers we see, convert # them, and stick them back in. That's pretty # yucky, but it works. foreach my $field (@$convert_fields) { next unless (defined $info->{$stock,$field}); $info->{$stock,$field} = $this->scale_field($info->{$stock,$field},$conversion{$currency,$new_currency}); } # Set the new currency. $info->{$stock,"currency"} = $new_currency; } } } # ======================================================================= # _dummy (private function) # # _dummy returns a Finance::Quote object. I'd really rather not have # this, but to maintain backwards compatibility we hold on to it. { my $dummy_obj; sub _dummy { return $dummy_obj ||= Finance::Quote->new; } } # _load_module (private class method) # _load_module loads a module(s) and registers its various methods for # use. sub _load_modules { my $class = shift; my $baseclass = ref $class || $class; my @modules = @_; # Go to each module and use them. Also record what methods # they support and enter them into the %METHODS hash. foreach my $module (@modules) { my $modpath = "${baseclass}::${module}"; unless (defined($MODULES{$modpath})) { eval { load $modpath; $MODULES{$modpath} = 1; my %methodhash = $modpath->methods; my %labelhash = $modpath->labels; my $curr_fields_func = $modpath->can("currency_fields") || \&default_currency_fields; my @currency_fields = &$curr_fields_func; my %seen; @currency_fields = grep {!$seen{$_}++} @currency_fields; foreach my $method (keys %methodhash) { push (@{$METHODS{$method}}, { name => $module, modpath => $modpath, function => $methodhash{$method}, labels => $labelhash{$method}, currency_fields => \@currency_fields}); } }; carp $@ if $@; } } } # _smart_compare (private method function) # # This function compares values where the method depends on the # type of the parameters. # val1, val2 # scalar,scaler - test for substring match # scalar,regex - test val1 against val2 regex # array,scalar - return true if any element of array substring matches scalar # array,regex - return true if any element of array matches regex sub _smart_compare { my ($val1, $val2) = @_; if ( ref $val1 eq 'ARRAY' ) { if ( ref $val2 eq 'Regexp' ) { my @r = grep {$_ =~ $val2} @$val1; return @r > 0; } else { my @r = grep {$_ =~ /$val2/} @$val1; return @r > 0; } } else { if ( ref $val2 eq 'Regexp' ) { return $val1 =~ $val2; } else { return index($val1, $val2) > -1 } } } # This is a list of fields that will be automatically converted during # currency conversion. If a module provides a currency_fields() # function then that list will be used instead. sub get_default_currency_fields { return qw/last high low net bid ask close open day_range year_range eps div cap nav price/; } sub get_default_timeout { return $TIMEOUT; } # get_methods returns a list of sources which can be passed to fetch to # obtain information. sub get_methods { # Create a dummy object to ensure METHODS is populated my $t = Finance::Quote->new(); return(wantarray ? keys %METHODS : [keys %METHODS]); } # return hash: # # quote_methods => hash of # method_name => array of module names # quote_modules => hash of # module_name => array of parameters # currency_modules => hash of # module_name => array of parameters # # { # 'quote_methods' => {'group' => ['module', 'module'], ...}, # 'quote_modules' => {'abc' => ['API_KEY'], ...}, # 'currency_modules' => {'xyz' => [], 'lmn' => ['USER_NAME', 'API_KEY']}, # } sub get_features { # Create a dummy object to ensure METHODS is populated my $t = Finance::Quote->new(currency_rates => {order => \@CURRENCY_RATES_MODULES}); my $baseclass = ref $t; my %feature = ( 'quote_methods' => {map {$_, [map {$_->{name}} @{$METHODS{$_}}]} keys %METHODS}, 'quote_modules' => {map {$_, []} @MODULES}, 'currency_modules' => {map {$_, []} @CURRENCY_RATES_MODULES}, ); my %mods = ('quote_modules' => $baseclass, 'currency_modules' => "${baseclass}::CurrencyRates"); while (my ($field, $base) = each %mods) { foreach my $name (keys %{$feature{$field}}) { my $modpath = "${base}::${name}"; if ($modpath->can("parameters")) { push (@{$feature{$field}->{$name}}, $modpath->parameters()); } } } return %feature; } # ======================================================================= # new (public class method) # # Returns a new Finance::Quote object. # # Arguments :: # - zero or more module names from the Finance::Quote::get_sources list # - zero or more named parameters, passes as name => value # # Named Parameters :: # - timeout # timeout in seconds for web requests # - failover # boolean value indicating if failover is acceptable # - fetch_currency # currency code for fetch results # - required_labels # array of required labels in fetch results # - # hash specific to various Finance::Quote modules # # new() # default constructor # new('a', 'b') # load only modules a and b # new(timeout => 30) # load all default modules, set timeout # new('a', fetch_currency => 'X') # load only module a, use currency X for results # new('z' => {API_KEY => 'K'}) # load all modules, pass hash to module z constructor # new('z', 'z' => {API_KEY => 'K'}) # load only module z and pass hash to its constructor # # Enivornment Variables :: # - FQ_LOAD_QUOTELET # if no modules named in argument list, use ones in this variable # # Return Value :: # - Finanace::Quote object sub new { # Create and bless object my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; # To add a named parameter: # 0. Document it in the POD for new # 1. Add a default value for $this->{object-name} # 2. Add the 'user-visible-name' => [type, object-name] to %named_parameter # Check for FQ_CURRENCY - preferred currency module # Set to AlphaVantage if not set or not in @CURRENCY_RATES_MODULES my $CURRENCY_MODULE; if (!$ENV{FQ_CURRENCY}) { $CURRENCY_MODULE='AlphaVantage'; } else { if ( grep( /^$ENV{FQ_CURRENCY}$/, @CURRENCY_RATES_MODULES ) ) { $CURRENCY_MODULE=$ENV{FQ_CURRENCY} } else { $CURRENCY_MODULE='AlphaVantage'; } } # Default values $this->{FAILOVER} = 1; $this->{REQUIRED} = []; $this->{TIMEOUT} = $TIMEOUT if defined($TIMEOUT); $this->{currency_rates} = {order => [$CURRENCY_MODULE]}; # Sort out arguments my %named_parameter = (timeout => ['', 'TIMEOUT'], failover => ['', 'FAILOVER'], fetch_currency => ['', 'currency'], required_labels => ['ARRAY', 'REQUIRED'], currency_rates => ['HASH', 'currency_rates']); $this->{module_specific_data} = {}; my @load_modules = (); for (my $i = 0; $i < @_; $i++) { if (exists $named_parameter{$_[$i]}) { die "missing value for named parameter $_[$i]" if $i + 1 == @_; die "unexpect type for value of named parameter $_[$i]" if ref $_[$i+1] ne $named_parameter{$_[$i]}[0]; $this->{$named_parameter{$_[$i]}[1]} = $_[$i+1]; $i += 1; } elsif ($i + 1 < @_ and ref $_[$i+1] eq 'HASH') { $this->{module_specific_data}->{$_[$i]} = $_[$i+1]; $i += 1; } elsif ($_[$i] eq '-defaults') { push (@load_modules, @MODULES); } else { push (@load_modules, $_[$i]); } } # Honor FQ_LOAD_QUOTELET if @load_modules is empty if ($ENV{FQ_LOAD_QUOTELET} and !@load_modules) { @load_modules = split(' ',$ENV{FQ_LOAD_QUOTELET}); if ($load_modules[0] eq '-defaults') { shift @load_modules; push(@load_modules, @MODULES); } } elsif (@load_modules == 0) { push(@load_modules, @MODULES); } $this->_load_modules(@load_modules); # Load the currency rate methods my %currency_check = map { $_ => 1 } @CURRENCY_RATES_MODULES; $this->{currency_rate_method} = []; foreach my $method (@{$this->{currency_rates}->{order}}) { unless (defined($currency_check{$method})) { carp "Unknown curreny rates method: $method"; return; } my $method_path = "${class}::CurrencyRates::${method}"; eval { autoload $method_path; my $args = exists $this->{currency_rates}->{lc($method)} ? $this->{currency_rates}->{lc($method)} : {}; my $rate = $method_path->new($args); die unless defined $rate; push(@{$this->{currency_rate_method}}, $rate); }; if ($@) { next; } } return $this; } sub set_default_timeout { $TIMEOUT = shift; } ################################################################################ # # Private Object Methods # ################################################################################ # _require_test (private object method) # # This function takes an array. It returns true if all required # labels appear in the arrayref. It returns false otherwise. # # This function could probably be made more efficient. sub _require_test { my $this = shift; my %available; @available{@_} = (); # Ooooh, hash-slice. :) my @required = @{$this->{REQUIRED}}; return 1 unless @required; for (my $i = 0; $i < @required; $i++) { return 0 unless exists $available{$required[$i]}; } return 1; } ################################################################################ # # Public Object Methods # ################################################################################ # If $str ends with a B like "20B" or "1.6B" then expand it as billions like # "20000000000" or "1600000000". # # This is done with string manipulations so floating-point rounding doesn't # produce spurious digits for values like "1.6" which aren't exactly # representable in binary. # # Is "B" for billions the only abbreviation from Yahoo? # Could extend and rename this if there's also millions or thousands. # # For reference, if the value was just for use within perl then simply # substituting to exponential "1.5e9" might work. But expanding to full # digits seems a better idea as the value is likely to be printed directly # as a string. sub B_to_billions { my ($self,$str) = @_; # B_to_billions() $str if ($str =~ s/B$//i) { $str = $self->decimal_shiftup ($str, 9); } return $str; } # $str is a number like "123" or "123.45" # return it with the decimal point moved $shift places to the right # must have $shift>=1 # eg. decimal_shiftup("123",3) -> "123000" # decimal_shiftup("123.45",1) -> "1234.5" # decimal_shiftup("0.25",1) -> "2.5" # sub decimal_shiftup { my ($self, $str, $shift) = @_; # delete decimal point and set $after to count of chars after decimal. # Leading "0" as in "0.25" is deleted too giving "25" so as not to end up # with something that might look like leading 0 for octal. my $after = ($str =~ s/(?:^0)?\.(.*)/$1/ ? length($1) : 0); $shift -= $after; # now $str is an integer and $shift is relative to the end of $str if ($shift >= 0) { # moving right, eg. "1234" becomes "12334000" return $str . ('0' x $shift); # extra zeros appended } else { # negative means left, eg. "12345" becomes "12.345" # no need to prepend zeros since demanding initial $shift>=1 substr ($str, $shift,0, '.'); # new '.' at shifted spot from end return $str; } } # ======================================================================= # fetch (public object method) # # Fetch is a wonderful generic fetcher. It takes a method and stuff to # fetch. It's a nicer interface for when you have a list of stocks with # different sources which you wish to deal with. sub fetch { my $this = ref($_[0]) ? shift : _dummy(); my $method = lc(shift); my @stocks = @_; unless (exists $METHODS{$method}) { carp "Undefined fetch-method $method passed to ". "Finance::Quote::fetch"; return; } { no strict 'vars'; our $VERSION = '0.00' unless defined $VERSION; unless ($ENV{"FQ_NOCOUNT"}) { # Temporary Counting - not concerned about return code my $COUNT_URL = 'http://www.panix.com/~hd-fxsts/finance-quote.html?' . $VERSION . '&' . $method; my $count_ua = LWP::UserAgent->new(timeout => 10); my $count_response = $count_ua->head($COUNT_URL); ### COUNT_URL: $COUNT_URL ### Code: $count_response->code } } # Failover code. This steps through all available methods while # we still have failed stocks to look-up. This loop only # runs a single time unless FAILOVER is defined. my %returnhash = (); foreach my $methodinfo (@{$METHODS{$method}}) { my $funcref = $methodinfo->{"function"}; next unless $this->_require_test(@{$methodinfo->{"labels"}}); my @failed_stocks = (); %returnhash = (%returnhash,&$funcref($this,@stocks)); foreach my $stock (@stocks) { push(@failed_stocks,$stock) unless ($returnhash{$stock,"success"}); } $this->_convert(\%returnhash,\@stocks, $methodinfo->{"currency_fields"}); last unless $this->{FAILOVER}; last unless @failed_stocks; @stocks = @failed_stocks; } return wantarray() ? %returnhash : \%returnhash; } sub get_failover { my $self = shift; return $self->{FAILOVER}; } sub get_fetch_currency { my $self = shift; return $self->{currency}; } sub get_required_labels { my $self = shift; return $self->{REQUIRED}; } sub get_timeout { my $self = shift; return $self->{TIMEOUT}; } sub get_user_agent { my $this = shift; return $this->{UserAgent} if $this->{UserAgent}; my $ua; if ($USE_EXPERIMENTAL_UA) { $ua = Finance::Quote::UserAgent->new; } else { $ua = LWP::UserAgent->new; } $ua->timeout($this->{TIMEOUT}) if defined($this->{TIMEOUT}); $ua->env_proxy; $this->{UserAgent} = $ua; return $ua; } sub isoTime { my ($self,$timeString) = @_ ; $timeString =~ tr/ //d ; $timeString = uc $timeString ; my $retTime = "00:00"; # return zero time if unparsable input if ($timeString=~m/^(\d+)[\.:UH](\d+) *(AM|am|PM|pm)?/) { my ($hours,$mins)= ($1-0,$2-0) ; $hours-=12 if ($hours==12 && $3 && ($3 =~ /AM/i)); $hours+=12 if ($3 && ($3 =~ /PM/i) && ($hours != 12)); if ($hours>=0 && $hours<=23 && $mins>=0 && $mins<=59 ) { $retTime = sprintf ("%02d:%02d", $hours, $mins) ; } } return $retTime; } sub set_failover { my $self = shift; $self->{FAILOVER} = shift; } sub set_fetch_currency { my $self = shift; $self->{currency} = shift; } sub set_required_labels { my $self = shift; $self->{REQUIRED} = shift; } sub set_timeout { my $self = shift; $self->{TIMEOUT} = shift; } # ======================================================================= # store_date (public object method) # # Given the various pieces of a date, this functions figure out how to # store them in both the pre-existing US date format (mm/dd/yyyy), and # also in the ISO date format (yyyy-mm-dd). This function expects to # be called with the arguments: # # (inforef, symbol_name, data_hash) # # The components of date hash can be any of: # # usdate - A date in mm/dd/yy or mm/dd/yyyy # eurodate - A date in dd/mm/yy or dd/mm/yyyy # isodate - A date in yy-mm-dd or yyyy-mm-dd, yyyy/mm/dd, yyyy.mm.dd, or yyyymmdd # year - The year in yyyy # month - The month in mm or mmm format (i.e. 07 or Jul) # day - The day # today - A flag to indicate todays date should be used. # # The separator for the *date forms is ignored. It can be any # non-alphanumeric character. Any combination of year, month, and day # values can be provided. Missing fields are filled in based upon # today's date. # sub store_date { my $this = shift; my $inforef = shift; my $symbol = shift; my $piecesref = shift; my ($year, $month, $day, $this_month, $year_specified); my %mnames = (jan => 1, feb => 2, mar => 3, apr => 4, may => 5, jun => 6, jul => 7, aug => 8, sep => 9, oct =>10, nov =>11, dec =>12); ### store_date symbol: $symbol ### store_date pieces: $piecesref # Default to today's date. ($month, $day, $year) = (localtime())[4,3,5]; $month++; $year += 1900; $this_month = $month; $year_specified = 0; # Process the inputs if ((defined $piecesref->{isodate}) && ($piecesref->{isodate})) { if ($piecesref->{isodate} =~ /^([0-9]{4})([0-9]{2})([0-9]{2})$/) { ($year, $month, $day) = ($1, $2, $3); } else { ($year, $month, $day) = ($piecesref->{isodate} =~ m|([0-9]{4})\W+(\w+)\W+(\w+)|); } $year += 2000 if $year < 100; $year_specified = 1; ### format: printf "isodate %s -> Day %d, Month %s, Year %d\n", $piecesref->{isodate}, $day, $month, $year } if ((defined $piecesref->{usdate}) && ($piecesref->{usdate})) { ($month, $day, $year) = ($piecesref->{usdate} =~ /(\w+)\W+(\d+)\W+(\d+)/); $year += 2000 if $year < 100; $year_specified = 1; ### format: printf STDERR "usdate %s -> Day %d, Month %s, Year %d\n", $piecesref->{usdate}, $day, $month, $year } if ((defined $piecesref->{eurodate}) && ($piecesref->{eurodate})) { ($day, $month, $year) = ($piecesref->{eurodate} =~ /(\d+)\W+(\w+)\W+(\d+)/); $year += 2000 if $year < 100; $year_specified = 1; ### format: printf STDERR "eurodate %s -> Day %d, Month %s, Year %d\n", $piecesref->{eurodate}, $day, $month, $year } if (defined ($piecesref->{year})) { $year = $piecesref->{year}; $year += 2000 if $year < 100; $year_specified = 1; ### format: printf "year %s -> Year %d\n", $piecesref->{year}, $year } if (defined ($piecesref->{month})) { $month = $piecesref->{month}; ### format: printf "month %s -> Month %s\n", $piecesref->{month}, $month } if (defined ($piecesref->{day})) { $day = $piecesref->{day}; ### format: printf "day %s -> Day %d\n", $piecesref->{day}, $day } $month = $mnames{lc(substr($month,0,3))} if ($month =~ /\D/); $year-- if (($year_specified == 0) && ($this_month < $month)); ### format: printf STDERR "Final Year-Month-Day -> %04d-%02d-%02d\n", $year, $month, $day $inforef->{$symbol, "date"} = sprintf "%02d/%02d/%04d", $month, $day, $year; $inforef->{$symbol, "isodate"} = sprintf "%04d-%02d-%02d", $year, $month, $day; } ################################################################################ # # Public Class or Object Methods # ################################################################################ # ======================================================================= # Helper function that can scale a field. This is useful because it # handles things like ranges "105.4 - 108.3", and not just straight fields. # # The function takes a string or number to scale, and the factor to scale # it by. For example, scale_field("1023","0.01") would return "10.23". sub scale_field { shift if ref $_[0]; # Shift off the object, if there is one. my ($field, $scale) = @_; my @chunks = split(/([^0-9.])/,$field); for (my $i=0; $i < @chunks; $i++) { next unless $chunks[$i] =~ /\d/; $chunks[$i] *= $scale; } return join("",@chunks); } # ======================================================================= # currency (public object method) # # currency allows the conversion of one currency to another. # # Usage: $quoter->currency("USD","AUD"); # $quoter->currency("15.95 USD","AUD"); # # undef is returned upon error. sub currency { my $this = ref($_[0]) ? shift : _dummy(); my ($from_code, $to_code) = @_; return unless ($from_code and $to_code); $from_code =~ s/^\s*(\d*\.?\d*)\s*//; my $amount = $1 || 1; $to_code = uc($to_code); $from_code = uc($from_code); return $amount if ($from_code eq $to_code); # Trivial case. my $ua = $this->get_user_agent; foreach my $rate (@{$this->{currency_rate_method}}) { ### rate: ref($rate) my $final = eval { my ($from, $to) = $rate->multipliers($ua, $from_code, $to_code); die("Failed to find currency rates for $from_code or $to_code") unless defined $from and defined $to; ### to weight : $to ### from weight: $from ### amount : $amount # Is from closest to (amount, to, amount * to)? # (amount * to) / from my $delta = abs($amount - $from); my $result = ($amount/$from) * $to; ### amount/from -> delta/result : ($delta, $result) if ($delta > abs($to - $from)) { $delta = abs($to - $from); $result = ($to/$from) * $amount; ### to/from -> delta/result : ($delta, $result) } if ($delta > abs($amount*$to - $from)) { $delta = abs($amount*$to - $from); $result = ($amount * $to)/$from; ### (amount * to)/from -> delta/result : ($delta, $result) } return $result; }; if ($@) { ### Rate Error: chomp($@), $@ next; } return $final; } return; } # ======================================================================= # currency_lookup (public object method) # # search for available currency codes # # Usage: # $currency = $quoter->currency_lookup(); # $currency = $quoter->currency_lookup( name => "Dollar"); # $currency = $quoter->currency_loopup( country => qw/denmark/i ); # $currency = $q->currency_lookup(country => qr/united states/i, number => 840); # # If more than one lookup parameter is given all must match for # a currency to match. # # undef is returned upon error. sub currency_lookup { my $this = ref $_[0] ? shift : _dummy(); my %params = @_; my $currencies = Finance::Quote::Currencies::known_currencies(); my %attributes = map {$_ => 1} map {keys %$_} values %$currencies; for my $key (keys %params ) { if ( ! exists $attributes{$key}) { warn "Invalid parameter: $key"; return; } } while (my ($tag, $check) = each(%params)) { $currencies = {map {$_ => $currencies->{$_}} grep {_smart_compare($currencies->{$_}->{$tag}, $check)} keys %$currencies}; } return $currencies; } # ======================================================================= # parse_csv (public object method) # # Grabbed from the Perl Cookbook. Parsing csv isn't as simple as you thought! # sub parse_csv { shift if (ref $_[0]); # Shift off the object if we have one. my $text = shift; # record containing comma-separated values my @new = (); push(@new, $+) while $text =~ m{ # the first part groups the phrase inside the quotes. # see explanation of this pattern in MRE "([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx; push(@new, undef) if substr($text, -1,1) eq ','; return @new; # list of values that were comma-separated } # ======================================================================= # parse_csv_semicolon (public object method) # # Grabbed from the Perl Cookbook. Parsing csv isn't as simple as you thought! # sub parse_csv_semicolon { shift if (ref $_[0]); # Shift off the object if we have one. my $text = shift; # record containing comma-separated values my @new = (); push(@new, $+) while $text =~ m{ # the first part groups the phrase inside the quotes. # see explanation of this pattern in MRE "([^\"\\]*(?:\\.[^\"\\]*)*)";? | ([^;]+);? | ; }gx; push(@new, undef) if substr($text, -1,1) eq ';'; return @new; # list of values that were comma-separated } ############################################################################### # # Legacy Class Methods # ############################################################################### sub sources { return get_methods(); } sub default_currency_fields { return get_default_currency_fields(); } ############################################################################### # # Legacy Class or Object Methods # ############################################################################### # ======================================================================= # set_currency (public object method) # # set_currency allows information to be requested in the specified # currency. If called with no arguments then information is returned # in the default currency. # # Requesting stocks in a particular currency increases the time taken, # and the likelyhood of failure, as additional operations are required # to fetch the currency conversion information. # # This method should only be called from the quote object unless you # know what you are doing. sub set_currency { if (@_ == 1 or !ref($_[0])) { # Direct or class call - there is no class default currency return; } my $this = shift; if (defined($_[0])) { $this->set_fetch_currency($_[0]); } return $this->get_fetch_currency(); } # ======================================================================= # Timeout code. If called on a particular object, then it sets # the timout for that object only. If called as a class method # (or as Finance::Quote::timeout) then it sets the default timeout # for all new objects that will be created. sub timeout { if (@_ == 1 or !ref($_[0])) { # Direct or class call Finance::Quote::set_default_timeout(shift); return Finance::Quote::get_default_timeout(); } # Otherwise we were called through an object. Yay. # Set the timeout in this object only. my $this = shift; $this->set_timeout(shift); return $this->get_timeout(); } ############################################################################### # # Legacy Object Methods # ############################################################################### # ======================================================================= # failover (public object method) # # This sets/gets whether or not it's acceptable to use failover techniques. sub failover { my $this = shift; my $value = shift; $this->set_failover($value) if defined $value; return $this->get_failover(); } # ======================================================================= # require_labels (public object method) # # Require_labels indicates which labels are required for lookups. Only methods # that have registered all the labels specified in the list passed to # require_labels() will be called. # # require_labels takes a list of required labels. When called with no # arguments, the require list is cleared. # # This method always succeeds. sub require_labels { my $this = shift; my @labels = @_; $this->set_required_labels(\@labels); return; } # ======================================================================= # user_agent (public object method) # # Returns a LWP::UserAgent which conforms to the relevant timeouts, # proxies, and other settings on the particular Finance::Quote object. # # This function is mainly intended to be used by the modules that we load, # but it can be used by the application to directly play with the # user-agent settings. sub user_agent { my $this = shift; return $this->get_user_agent(); } 1; __END__ =for comment README.md generated from lib/Finance/Quote.pm =head1 NAME Finance::Quote - Get stock and mutual fund quotes from various exchanges =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %quotes = $q->fetch("nasdaq", @stocks); =head1 DESCRIPTION This module gets stock quotes from various internet sources all over the world. Quotes are obtained by constructing a quoter object and using the fetch method to gather data, which is returned as a two-dimensional hash (or a reference to such a hash, if called in a scalar context). For example: $q = Finance::Quote->new; %info = $q->fetch("australia", "CML"); print "The price of CML is ".$info{"CML", "price"}; The first part of the hash (eg, "CML") is referred to as the stock. The second part (in this case, "price") is referred to as the label. =head2 LABELS When information about a stock is returned, the following standard labels may be used. Some custom-written modules may use labels not mentioned here. If you wish to be certain that you obtain a certain set of labels for a given stock, you can specify that using require_labels(). ask Ask avg_vol Average Daily Vol bid Bid cap Market Capitalization close Previous Close currency Currency code for the returned data date Last Trade Date (MM/DD/YY format) day_range Day's Range div Dividend per Share div_date Dividend Pay Date div_yield Dividend Yield eps Earnings per Share errormsg If success is false, this field may contain the reason why. ex_div Ex-Dividend Date. exchange The exchange the information was obtained from. high Highest trade today isin International Securities Identification Number isodate ISO 8601 formatted date last Last Price low Lowest trade today method The module (as could be passed to fetch) which found this information. name Company or Mutual Fund Name nav Net Asset Value net Net Change open Today's Open p_change Percent Change from previous day's close pe P/E Ratio success Did the stock successfully return information? (true/false) time Last Trade Time type The type of equity returned volume Volume year_range 52-Week Range yield Yield (usually 30 day avg) If all stock lookups fail (possibly because of a failed connection) then the empty list may be returned, or undef in a scalar context. =head1 INSTALLATION Please note that the Github repository is not meant for general users of Finance::Quote for installation. If you downloaded the Finance-Quote-N.NN.tar.gz tarball from CPAN (N.NN is the version number, ex: Finance-Quote-1.50.tar.gz), run the following commands: tar xzf Finance-Quote-1.50.tar.gz cd Finance-Quote-1.50.tar.gz perl Makefile.PL make make test make install If you have the CPAN module installed: Using cpanm (Requires App::cpanminus) cpanm Finance::Quote or Using CPAN shell perl -MCPAN -e shell install Finance::Quote =head1 SUPPORT AND DOCUMENTATION After installing, you can find documentation for this module with the perldoc command. perldoc Finance::Quote You can also look for information at: =over =item Finance::Quote GitHub project https://github.com/finance-quote/finance-quote =item Search CPAN http://search.cpan.org/dist/Finance-Quote =item The Finance::Quote home page http://finance-quote.sourceforge.net/ =item The Finance::YahooQuote home page http://www.padz.net/~djpadz/YahooQuote/ =item The GnuCash home page http://www.gnucash.org/ =back =head1 PUBLIC CLASS METHODS Finance::Quote implements public class methods for constructing a quoter object, getting or setting default class values, and for listing available methods. =head2 new my $q = Finance::Quote->new() my $q = Finance::Quote->new('-defaults') my $q = Finance::Quote->new(timeout => 30) my $q = Finance::Quote->new('YahooJSON', fetch_currency => 'EUR') my $q = Finance::Quote->new('alphavantage' => {API_KEY => '...'}) my $q = Finance::Quote->new(currency_rates => {order => ['ECB', 'Fixer'], 'fixer' => {API_KEY => '...'}}); Finance::Quote modules access a wide range of sources to provide quotes. A module provides one or more methods to fetch quotes. One method is usually the name of the module in lower case. Other methods, if provided, are descriptive names, such as 'canada', 'nasdaq', or 'nyse'. A Finance::Quote object uses one or more methods to fetch quotes for securities. C constructs a Finance::Quote object and enables the caller to load only specific modules, set parameters that control the behavior of the fetch method, and pass method specific parameters. =over =item C T> sets the web request timeout to C seconds =item C B> where C is a boolean value indicating if failover in fetch is permitted =item C C> sets the desired currency code to C for fetch results =item C H> configures the order currency rate modules are consulted for exchange rates and currency rate module options =item C A> sets the required labels for fetch results to array C =item C<> as a string is the name of a specific Finance::Quote::Module to load =item C< => H> passes hash C to methodname during fetch to configure the method =back With no arguments, C creates a Finance::Quote object with the default methods. If the environment variable FQ_LOAD_QUOTELET is set, then the contents of FQ_LOAD_QUOTELET (split on whitespace) will be used as the argument list. This allows users to load their own custom modules without having to change existing code. If any method names are passed to C or the flag '-defaults' is included in the argument list, then FQ_LOAD_QUOTELET is ignored. When new() is passed one or more class name arguments, an object is created with only the specified modules loaded. If the first argument is '-defaults', then the default modules will be loaded first, followed by any other specified modules. Note that the FQ_LOAD_QUOTELET environment variable must begin with '-defaults' if you wish the default modules to be loaded. Method names correspond to the Perl module in the Finance::Quote module space. For example, Cnew('ASX')> will load the module Finance::Quote::ASX, which provides the method "asx". Some methods require API keys or have unique options. Passing 'method => HASH' to new() enables the caller to provide a configuration HASH to the corresponding method. The key 'currency_rates' configures the Finanace::Quote currency rate conversion. By default, to maintain backward compatibility, Finance::Quote::CurrencyRates::AlphaVantage is used for currency conversion. This end point requires an API key, which can either be set in the environment or included in the configuration hash. To specify a different primary currency conversion method or configure fallback methods, include the 'order' key, which points to an array of Finance::Quote::CurrencyRates module names. Setting the environment variable FQ_CURRENCY will change the default endpoint used for currency conversion. See the documentation for the individual Finance::Quote::CurrencyRates modules to learn more. =head2 get_default_currency_fields my @fields = Finance::Quote::get_default_currency_fields(); C returns the standard list of fields in a quote that are automatically converted during currency conversion. Individual modules may override this list. =head2 get_default_timeout my $value = Finance::Quote::get_default_timeout(); C returns the current Finance::Quote default timeout in seconds for web requests. Finance::Quote does not specify a default timeout, deferring to the underlying user agent for web requests. So this function will return undef unless C was previously called. =head2 set_default_timeout Finance::Quote::set_default_timeout(45); C sets the Finance::Quote default timeout to a new value. =head2 get_methods my @methods = Finance::Quote::get_methods(); C returns the list of methods that can be passed to C when creating a quoter object and as the first argument to C. =head2 get_features my %features = Finance::Quote::get_features(); C returns a hash with three keys: quote_methods, quote_modules, and currency_modules. $features{quote_methods} is a hash with key/value pairs of method_name => [array of module names] $features{quote_modules} is a hash with key/value pairs of module_name => [array of parameter names] $features{currency_modules} is a hash with key/value pairs of currency_module_name => [array of paramater names] Parameter names are values that the module needs to function, such as API_KEY. Most modules will have an empty list. Modules with a parameter are configured when creating the Finance::Quote by passing the argument 'module_name_in_lower_case' => {paramter => value} to Finance::Quote->new(). The keys of the $features{currency_modules} hash are the names of currency modules that can be used for currency conversion and the order in which the modules are used is controlled by the argument currency_rates => {order => [subset of $features{currency_modules}]} to Finance::Quote->new(). By default, AlphaVantage in used for currency conversion, "order" can be set to use other currency modules. The currency module used can also be changed by setting the FQ_CURRENCY environment variable. Please note that some of the alternate currency modules require API keys. =head1 PUBLIC OBJECT METHODS =head2 B_to_billions my $value = $q->B_to_billions("20B"); C is a utility function that expands a numeric string with a "B" suffix to the corresponding multiple of 1000000000. =head2 decimal_shiftup my $value = $q->decimal_shiftup("123.45", 1); # returns 1234.5 my $value = $q->decimal_shiftup("0.25", 1); # returns 2.5 C moves a the decimal point in a numeric string the specified number of places to the right. =head2 fetch my %stocks = $q->fetch("alphavantage", "IBM", "MSFT", "LNUX"); my $hashref = $q->fetch("nasdaq", "IBM", "MSFT", "LNUX"); C takes a method as its first argument and the remaining arguments are treated as securities. If the quoter C<$q> was constructed with a specific method or methods, then only those methods are available. When called in an array context, a hash is returned. In a scalar context, a reference to a hash will be returned. The keys for the returned hash are C<{SECURITY,LABEL}>. For the above example call, C<$stocks{"IBM","high"}> is the high value for IBM. $q->get_methods() returns the list of valid methods for quoter object $q. Some methods specify a specific Finance::Quote module, such as 'alphavantage'. Other methods are available from multiple Finance::Quote modules, such as 'nasdaq'. The quoter failover over option determines if multiple modules are consulted for methods such as 'nasdaq' that more than one implementation. =head2 get_failover my $failover = $q->get_failover(); Failover is when the C method attempts to retrieve quote information for a security from alternate sources when the requested method fails. C returns a boolean value indicating if the quoter object will use failover or not. =head2 set_failover $q->set_failover(False); C sets the failover flag on the quoter object. =head2 get_fetch_currency my $currency = $q->get_fetch_currency(); C returns either the desired currency code for the quoter object or undef if no target currency was set during construction or with the C function. =head2 set_fetch_currency $q->set_fetch_currency("FRF"); # Get results in French Francs. C method is used to request that all information be returned in the specified currency. Note that this increases the chance stock-lookup failure, as remote requests must be made to fetch both the stock information and the currency rates. In order to improve reliability and speed performance, currency conversion rates are cached and are assumed not to change for the duration of the Finance::Quote object. See the introduction to this page for information on how to configure the source of currency conversion rates. =head2 get_required_labels my @labels = $q->get_required_labels(); C returns the list of labels that must be populated for a security quote to be considered valid and returned by C. =head2 set_required_labels my $labels = ['close', 'isodate', 'last']; $q->set_required_labels($labels); C updates the list of required labels for the quoter object. =head2 get_timeout my $timeout = $q->get_timeout(); C returns the timeout in seconds the quoter object is using for web requests. =head2 set_timeout $q->set_timeout(45); C updated the timeout in seconds for the quoter object. =head2 store_date $quoter->store_date(\%info, $stocks, {eurodate => '06/11/2020'}); C is used by modules to consistent store date information about securities. Given the various pieces of a date, this function figures out how to construct a ISO date (yyyy-mm-dd) and US date (mm/dd/yyyy) and stores those values in C<%info> for security C<$stock>. =head2 get_user_agent my $ua = $q->get_user_agent(); C returns the LWP::UserAgent the quoter object is using for web requests. =head2 isoTime $q->isoTime("11:39PM"); # returns "23:39" $q->isoTime("9:10 AM"); # returns "09:10" C returns an ISO formatted time. =head1 PUBLIC CLASS OR OBJECT METHODS The following methods are available as class methods, but can also be called from Finance::Quote objects. =head2 scale_field my $value = Finance::Quote->scale_field('1023', '0.01') C is a utility function that scales the first argument by the second argument. In the above example, C is C<'10.23'>. =head2 currency my $value = $q->currency('15.95 USD', 'AUD'); my $value = Finance::Quote->currency('23.45 EUR', 'RUB'); C converts a value with a currency code suffix to another currency using the current exchange rate as determined by the Finance::Quote::CurrencyRates method or methods configured for the quoter $q. When called as a class method, Finance::Quote::AlphaVantage is the default, which requires an API key. See the introduction for information on configuring currency rate conversions and see Finance::Quote::CurrencyRates::AlphaVantage for information about the API key. =head2 currency_lookup my $currency = $quoter->currency_lookup(); my $currency = $quoter->currency_lookup( name => "Caribbean"); my $currency = $quoter->currency_loopup( country => qw/denmark/i ); my $currency = $q->currency_lookup(country => qr/united states/i, number => 840); C takes zero or more constraints and filters the list of currencies known to Finance::Quote. It returns a hash reference where the keys are ISO currency codes and the values are hash references containing metadata about the currency. A constraint is a key name and either a scalar or regular expression. A currency satisfies the constraint if its metadata hash contains the constraint key and the value of that metadata field matches the regular expression or contains the constraint value as a substring. If the metadata field is an array, then it satisfies the constraint if any value in the array satisfies the constraint. =head2 parse_csv my @list = Finance::Quote::parse_csv($string); C is a utility function for splitting a comma separated value string into a list of terms, treating double-quoted strings that contain commas as a single value. =head2 parse_csv_semicolon my @list = Finance::Quote::parse_csv_semicolon($string); C is a utility function for splitting a semicolon separated value string into a list of terms, treating double-quoted strings that contain semicolons as a single value. =head1 LEGACY METHODS =head2 default_currency_fields Replaced with get_default_currency_fields(). =head2 sources Replaced with get_methods(). =head2 failover Replaced with get_failover() and set_failover(). =head2 require_labels Replaced with get_required_labels() and set_required_labels(). =head2 user_agent Replaced with get_user_agent(). =head2 set_currency Replaced with get_fetch_currency() and set_fetch_currency(). =head1 ENVIRONMENT Finance::Quote respects all environment that your installed version of LWP::UserAgent respects. Most importantly, it respects the http_proxy environment variable. =head1 USAGE COUNTER A simple usage counter has been enabled. Other than the user's IP address, Finance::Quote version, and which method was used, no other data is collected. To opt out of your usage being counted, set the environment variable "FQ_NOCOUNT". =head1 BUGS The caller cannot control the fetch failover order. The two-dimensional hash is a somewhat unwieldly method of passing around information when compared to references =head1 COPYRIGHT & LICENSE Copyright 1998, Dj Padzensky Copyright 1998, 1999 Linas Vepstas Copyright 2000, Yannick LE NY (update for Yahoo Europe and YahooQuote) Copyright 2000-2001, Paul Fenwick (updates for ASX, maintenance and release) Copyright 2000-2001, Brent Neal (update for TIAA-CREF) Copyright 2000 Volker Stuerzl (DWS) Copyright 2001 Rob Sessink (AEX support) Copyright 2001 Leigh Wedding (ASX updates) Copyright 2001 James Treacy (TD Waterhouse support) Copyright 2008 Erik Colson (isoTime) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Currency information fetched through this module is bound by the terms and conditons of the data source. Other copyrights and conditions may apply to data fetched through this module. Please refer to the sub-modules for further information. =head1 AUTHORS The following list are those contributors with 5 or more commits in the GitHub repository. Please see the Git log for all the authors who have helped with Finance::Quote. Erik Colson Bruce Schuck Paul Fenwick Vincent Lucarelli David Hampton jvolkening Pawel Konieczny Mike Alexander AndreJ Vinay S Shastry Bradley Dean Brent Neal Jacinta Richardson e-dorigatti goodvibes2 Paul Howarth Sam Morris Linas Vepstas Rajan Vaswani <111571283+bgr22112@users.noreply.github.com> Hiranya Samarasekera Manuel Friedli The Finance::Quote home page can be found at http://finance-quote.sourceforge.net/ The Finance::YahooQuote home page can be found at http://www.padz.net/~djpadz/YahooQuote/ The GnuCash home page can be found at http://www.gnucash.org/ =head1 SEE ALSO Finance::Quote::CurrencyRates::AlphaVantage, Finance::Quote::CurrencyRates::CurrencyFreaks, Finance::Quote::CurrencyRates::ECB, Finance::Quote::CurrencyRates::FinanceAPI, Finance::Quote::CurrencyRates::Fixer, Finance::Quote::CurrencyRates::OpenExchange, Finance::Quote::CurrencyRates::YahooJSON, Finance::Quote::AEX, Finance::Quote::ASEGR, Finance::Quote::ASX, Finance::Quote::Bloomberg, Finance::Quote::BorsaItaliana, Finance::Quote::BSEIndia, Finance::Quote::Bourso, Finance::Quote::BVB, Finance::Quote::CSE, Finance::Quote::Comdirect, Finance::Quote::Consorsbank, Finance::Quote::Currencies, Finance::Quote::Deka, Finance::Quote::FinanceAPI, Finance::Quote::Finanzpartner, Finance::Quote::Fondsweb, Finance::Quote::Fool, Finance::Quote::FTfunds, Finance::Quote::GoldMoney, Finance::Quote::GoogleWeb, Finance::Quote::HU, Finance::Quote::IndiaMutual, Finance::Quote::MarketWatch, Finance::Quote::MorningstarCH, Finance::Quote::MorningstarJP, Finance::Quote::MorningstarUK, Finance::Quote::NSEIndia, Finance::Quote::NZX, Finance::Quote::OnVista, Finance::Quote::Oslobors, Finance::Quote::SEB, Finance::Quote::SIX, Finance::Quote::StockData, Finance::Quote::Stooq, Finance::Quote::TSP, Finance::Quote::TMX, Finance::Quote::Tiaacref, Finance::Quote::TesouroDireto, Finance::Quote::TreasuryDirect, Finance::Quote::Troweprice, Finance::Quote::TwelveData, Finance::Quote::Union, Finance::Quote::YahooJSON, Finance::Quote::YahooWeb, Finance::Quote::ZA You should have received the Finance::Quote hacker's guide with this package. Please read it if you are interested in adding extra methods to this package. The latest hacker's guide can also be found on GitHub at https://github.com/finance-quote/finance-quote/blob/master/Documentation/Hackers-Guide =head1 DONATIONS While the contributors to Finance::Quote are happy to volunteer their time and resources on this project, donations are helpful and great way to say thank you. So far the only real cost to the team is domain registration for finance-quote.org. However, some operating systems and hardware upgrades do have a cost. Access to the latest or current environments is needed for development and testing. Upgrades to the latest MS Windows often includes a cost, and recently Apple's move to ARM/M1/M2 CPUs has stifled some contributors' ability to assist. With that in mind, you can send donations to the email address donations@finance-quote.org through PayPal. If it's for a particular contributor, please indicate in a note while submitting the donation through PayPal. Additionally, Bitcoin donations can be received at 37Bg4EMAp575j4iL9jHv1ZhDgiropU38jY. =cut Finance-Quote-1.65/lib/Finance/Quote/0000775000175000017500000000000015003302667017143 5ustar bschuckbschuckFinance-Quote-1.65/lib/Finance/Quote/ASEGR.pm0000644000175000017500000001243415003302667020344 0ustar bschuckbschuck#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::ASEGR; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use Web::Scraper; use Spreadsheet::XLSX; use String::Util qw(trim); our $VERSION = '1.65'; # VERSION our @labels = qw/symbol date isodate close volume high low isin/; our %labels = (symbol => ['symbol', 'trading symbol'], date => ['date'], close => ['price', 'current nominal value', 'closing price'], volume => ['volume'], high => ['max'], low => ['min'], isin => ['isin']); sub methods { return ( greece => \&asegr, asegr => \&asegr, europe => \&asegr); } sub labels { return ( greece => \@labels, asegr => \@labels, europe => \@labels); } our @sources = qw/statistics-end-of-day-securities statistics-end-of-day-etfs statistics-end-of-day-bonds statistics-end-of-day-warrants statistics-end-of-day-derivatives statistics-end-of-day-lending statistics-end-of-day-indices/; sub load_source { my $ua = shift; my $table = shift; my $source = shift; eval { my $url = "https://www.athexgroup.gr/web/guest/$source"; my $reply = $ua->get($url); ### Fetched : $url, $reply->code my $data = scraper { process 'div.portlet-content-container div.portlet-body table ~ p:last-child > a:first-child', 'link[]' => '@href'; }; my $result = $data->scrape($reply); foreach my $link (@{$result->{link}}) { $reply = $ua->get($link); ### Fetched : $link, $reply->code my $xlsx = $reply->decoded_content(); my $io; open($io, '<', \$xlsx); my $workbook = Spreadsheet::XLSX->new($io); for my $worksheet ($workbook->worksheets()) { my ($row_min, $row_max) = $worksheet->row_range(); my ($col_min, $col_max) = $worksheet->col_range(); my %head = map {$_ => trim(lc($worksheet->get_cell($row_min, $_)->value()))} ($col_min .. $col_max); for my $row (($row_min+1) .. $row_max) { my $this = {}; for my $col ($col_min .. $col_max) { my $cell = $worksheet->get_cell($row, $col); next unless $cell; $this->{$head{$col}} = trim($cell->value()); } $table->{$this->{'symbol'}} = $this if exists $this->{'symbol'}; $table->{$this->{'trading symbol'}} = $this if exists $this->{'trading symbol'}; } } } }; if ($@) { ### Error: $@ } } sub asegr { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my @found; my %info; my %table; my $index = 0; while (@symbols and $index < @sources) { # Load the next source load_source($ua, \%table, $sources[$index++]); # Sift through @symbols push(@found, grep {exists $table{$_}} @symbols); @symbols = grep {not exists $table{$_}} @symbols; } ### Found : @found ### Not found : @symbols foreach my $symbol (@found) { foreach my $label (@labels) { next if $label eq 'isodate'; foreach my $key (@{$labels{$label}}) { $info{$symbol,$label} = $table{$symbol}->{$key} if exists $table{$symbol}->{$key}; } } $quoter->store_date(\%info, $symbol, {eurodate => $info{$symbol,'date'}}); } # Anything left in @symbols is a failure foreach my $symbol (@symbols) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Not found'; } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::ASEGR - Obtain quotes from Athens Exchange Group =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = Finance::Quote->fetch("asegr","minoa"); # Only query ASEGR %info = Finance::Quote->fetch("greece","aaak"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from https://www.athexgroup.gr. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'asegr' in the argument list to Finance::Quote->new(). This module provides both the 'asegr' and 'greece' fetch methods. =head1 LABELS RETURNED The following labels may be returned: symbol date isodate close volume high low isin. =head1 Terms & Conditions Use of www.athexgroup.gr is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut Finance-Quote-1.65/lib/Finance/Quote/FinanceAPI.pm0000644000175000017500000002105715003302667021401 0ustar bschuckbschuck#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2024, Bruce Schuck # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # Changes: # Initial Version: 2024-08-31, Bruce Schuck package Finance::Quote::FinanceAPI; use strict; use warnings; use Encode qw(decode); use HTTP::Request::Common; use JSON qw( decode_json ); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION my $FINANCEAPI_URL = 'https://yfapi.net/v6/finance/quote?region=US&lang=en&symbols='; # Change DISPLAY and method values in code below # Modify LABELS to those returned by the method our $DISPLAY = 'FinanceAPI'; our $FEATURES = {'API_KEY' => 'registered user API key'}; our @LABELS = qw/symbol name open high low last price date volume currency method/; our $METHODHASH = {subroutine => \&financeapi, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub methodinfo { return ( financeapi => $METHODHASH, nyse => $METHODHASH, nasdaq => $METHODHASH, usa => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub financeapi { my $quoter = shift; my @stocks = @_; my (%info, $url, $reply); my $ua = $quoter->user_agent(); # Set token. If not passed in as argument, get from environment. my $token = exists $quoter->{module_specific_data}->{stockdata}->{API_KEY} ? $quoter->{module_specific_data}->{stockdata}->{API_KEY} : $ENV{"FINANCEAPI_API_KEY"}; # Set headers. API key is sent as a header. my @ua_headers = ( 'Accept' => 'application/json', 'X-API-KEY' => $token, ); foreach my $stock (@stocks) { if ( !defined $token ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = 'FINANCEAPI_API_KEY not defined. Get an API key at https://financeapi.net/'; next; } $url = $FINANCEAPI_URL . $stock; $reply = $ua->get( $url, @ua_headers ); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = $reply->content; ### Body: $body my ($quote, $name, $bid, $ask, $last, $open, $high, $low, $volume, $currency, $date); my ($month, $day, $year); $info{ $stock, "symbol" } = $stock; if ( $code == 200 ) { eval {$quote = JSON::decode_json $body}; if ($@) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $@; next; } ### [] JSON quote: $quote if (!exists $quote->{'quoteResponse'}) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $@; next; } # JSON Result Array is sometimes empty my $json_data_count = scalar @{ $quote->{'quoteResponse'}{'result'} }; if ( $json_data_count < 1 ) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock - no listing for this name found. Please check symbol and the two letter extension (if any)"; next; } $name = $quote->{'quoteResponse'}{'result'}[0]{'longName'}; $open = $quote->{'quoteResponse'}{'result'}[0]{'regularMarketOpen'}; $high = $quote->{'quoteResponse'}{'result'}[0]{'regularMarketDayHigh'}; $low = $quote->{'quoteResponse'}{'result'}[0]{'regularMarketDayLow'}; $last = $quote->{'quoteResponse'}{'result'}[0]{'regularMarketPrice'}; $volume = $quote->{'quoteResponse'}{'result'}[0]{'regularMarketVolume'}; #$currency = $quote->{'quoteResponse'}{'result'}[0]{'financialCurrency'}; $currency = $quote->{'quoteResponse'}{'result'}[0]{'currency'}; $date = $quote->{'quoteResponse'}{'result'}[0]{'regularMarketTime'}; ($month, $day, $year) = (localtime($date))[4,3,5]; $month++; $year += 1900; $info{$stock, 'name'} = $name; $info{$stock, 'open'} = $open; $info{$stock, 'high'} = $high; $info{$stock, 'low'} = $low; $info{$stock, 'last'} = $last; $info{$stock, 'price'} = $last; $info{$stock, 'volume'} = $volume; $info{$stock, 'currency'} = $currency; $info{ $stock, 'method' } = 'financeapi'; $quoter->store_date(\%info, $stock, {month => $month, day => $day, year => $year}); # Check for stocks traded in pence instead of pounds # Convert GBp or GBX to GBP (divide price by 100). # GBP.L - pence # GBPG.L - pounds if ( ($info{$stock,"currency"} eq "GBp") || ($info{$stock,"currency"} eq "GBX")) { foreach my $field ( $quoter->default_currency_fields ) { next unless ( $info{ $stock, $field } ); $info{ $stock, $field } = $quoter->scale_field( $info{ $stock, $field }, 0.01 ); } $info{ $stock, "currency"} = "GBP"; } # Apply the same hack for Johannesburg Stock Exchange # (JSE) prices as they are returned in ZAc (cents) # instead of ZAR (rands). JSE symbols are suffixed # with ".JO" when querying Yahoo e.g. ANG.JO if ($info{$stock,"currency"} eq "ZAc") { foreach my $field ( $quoter->default_currency_fields ) { next unless ( $info{ $stock, $field } ); $info{ $stock, $field } = $quoter->scale_field( $info{ $stock, $field }, 0.01 ); } $info{ $stock, "currency"} = "ZAR"; } # Apply the same hack for Tel Aviv Stock Exchange # (TASE) prices as they are returned in ILA (Agorot) # instead of ILS (Shekels). TASE symbols are suffixed # with ".TA" when querying Yahoo e.g. POLI.TA if ($info{$stock,"currency"} eq "ILA") { foreach my $field ( $quoter->default_currency_fields ) { next unless ( $info{ $stock, $field } ); $info{ $stock, $field } = $quoter->scale_field( $info{ $stock, $field }, 0.01 ); } $info{ $stock, "currency"} = "ILS"; } $info{ $stock, 'success' } = 1; } else { # HTTP Request failed (code != 200) $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; __END__ =head1 NAME Finance::Quote::FinanceAPI - Obtain quotes from financeapi.net. =head1 SYNOPSIS use Finance::Quote; # API Key passed during object creation $q = Finance::Quote->new('FinanceAPI', financeapi => {API_KEY => 'your-financeapi-api-key'}); # FINANCEAPI_API_KEY environment variable set $q = Finance::Quote->new; %info = $q->fetch("financeapi", "AAPL"); # Only query financeapi =head1 DESCRIPTION This module fetches information from L. The API URL is https://yfapi.net/. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "financeapi" in the argument list to Finance::Quote->new(). This module provides the "financeapi" fetch method. In addition it advertises "nyse", "usa", and "nasdaq". =head1 API_KEY L requires users to register for an API Key (token). The free "Basic" API Key allows 100 queries per day and a 300 per minute rate. The API key may be set by either providing a module specific hash to Finance::Quote->new as in the above example, or by setting the environment variable FINANCEAPI_API_KEY. =head1 LABELS RETURNED The following labels are returned: =over =item name =item symbol =item open =item high =item low =item price =item date =item volume =item currency =back =cut Finance-Quote-1.65/lib/Finance/Quote/TreasuryDirect.pm0000644000175000017500000001410215003302667022446 0ustar bschuckbschuck#!/usr/bin/perl -w # # TreasuryDirect.pm # =begin comment perl -MData::Dumper -MFinance::Quote -le '$q = Finance::Quote->new(); print Dumper { $q->fetch("treasurydirect", @ARGV) };' 912810QT8 912810QY7 =end comment =cut package Finance::Quote::TreasuryDirect; use strict; use warnings; # # Modification of Rolf Endres' Finance::Quote::ZA # # Peter Ratzlaff # April, 2018 # our $VERSION = '1.65'; # VERSION use vars qw /$VERSION/ ; use LWP::UserAgent; use HTTP::Request::Common; use HTML::TableExtract; use HTTP::Request; my $TREASURY_DIRECT_URL = 'https://www.treasurydirect.gov/GA-FI/FedInvest/todaySecurityPriceDate.htm'; sub methods { return treasurydirect => \&treasurydirect; } sub labels { my @labels = qw/ method source symbol rate bid ask price date isodate /; return treasurydirect => \@labels; } sub treasurydirect { # check for quotes for today, as well as the last three days my $time = time(); my @times = map { $time-86400*$_ } 0..3; for my $t (@times) { my ($d, $m, $y) = (localtime($t))[3,4,5]; $y += 1900; $m += 1; my @quotes = treasurydirect_ymd($y, $m, $d, @_); return @quotes if @quotes; } } sub treasurydirect_ymd { my ($y, $m, $d, $quoter, @symbols) = @_; return unless @symbols; my %info; $info{$_, 'success'} = 0 for @symbols; my $ua = $quoter->user_agent; $ua->timeout(10); $ua->ssl_opts( verify_hostname => 0 ); my $content; my $url = $TREASURY_DIRECT_URL; #print "[debug]: ", $url, "\n"; if (0) { my $response = $ua->request(GET $url); #print "[debug]: ", $response->content, "\n"; if (!$response->is_success) { $info{$_, 'errormsg'} = 'Error contacting URL' for @symbols; return wantarray() ? %info : \%info; } $content = $response->content; } # this is no longer working, for some reason elsif (0) { my $url = 'https://www.treasurydirect.gov/GA-FI/FedInvest/selectSecurityPriceDate'; # my $post_data = [ "priceDate.month" => "4", "priceDate.day" => "13", "priceDate.year" => "2018", "submit" => "Show+Prices" ]; my $post_data = [ 'priceDate.month' => $m, 'priceDate.day' => $d, 'priceDate.year' => $y, 'submit' => 'Show Prices', ]; my $request = POST( $url, $post_data); my $resp = $ua->request($request); if ($resp->is_success) { $content = $resp->decoded_content; # print "[debug]: ", $content, "\n"; } else { $info{$_, 'errormsg'} = 'Error contacting URL' for @symbols; return wantarray() ? %info : \%info; } } else { my $url = 'https://www.treasurydirect.gov/GA-FI/FedInvest/selectSecurityPriceDate'; #my $data= 'priceDate.month=1&priceDate.day=4&priceDate.year=2021&submit=Show+Prices'; my $data = 'priceDate.month=' . $m . '&priceDate.day=' . $d . '&priceDate.year=' . $y . '&submit=Show+Prices'; $content = `wget --no-check-certificate --post-data='$data' $url -O - 2>/dev/null`; } # submitted a future date return if $content =~ /Submitted date must be equal to/; # weekends, holidays (doesn't work like this any more) return if $content =~ /No data for selected date range/; my ($date, $isodate); if ($content =~ /Prices For:\s+(\w+)\s+(\d+),\s+(\d+)/) { my @months = qw/ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec /; my %months; @months{@months} = 1..12; my ($year, $month, $day) = ($3, $months{$1}, $2); $date = sprintf "%02d/%02d/%04d", $month, $day, $year; $isodate = sprintf "%04d-%02d-%02d", $year, $month, $day; } my $te = new HTML::TableExtract(); $te->parse($content); # print "[debug]: (parsed HTML)",$te, "\n"; unless ($te->first_table_found()) { #print STDERR "no tables on this page\n"; $info{$_, 'errormsg'} = 'Parse error' for @symbols; return wantarray() ? %info : \%info; } # Debug to dump all tables in HTML... =begin comment print "\n \n \n \n[debug]: ++++ ==== ++++ ==== ++++ ==== ++++ ==== START OF TABLE DUMP ++++ ==== ++++ ==== ++++ ==== ++++ ==== \n \n \n \n"; for my $ts ($te->table_states) { printf "\n \n \n \n[debug]: //// \\\\ //// \\\\ //// \\\\ //// \\\\ START OF TABLE %d,%d //// \\\\ //// \\\\ //// \\\\ //// \\\\ \n \n \n \n", $ts->depth, $ts->count; for my $row ($ts->rows) { print '[debug]: ', join('|', map { defined $_ ? $_ : 'undef' } @$row), "\n"; } } print "\n \n \n \n[debug]: ++++ ==== ++++ ==== ++++ ==== ++++ ==== END OF TABLE DUMP ++++ ==== ++++ ==== ++++ ==== ++++ ==== \n \n \n \n"; =end comment =cut my %bonds; for my $ts ($te->table_states) { for my $row ($ts->rows) { $bonds{$row->[0]} = { rate => $row->[2], maturity => $row->[3], bid => $row->[5], ask => $row->[6], }; } } # no bonds were returned, probably due to being a weekend or holiday return unless keys(%bonds) > 1; for my $symbol (@symbols) { # GENERAL FIELDS $info{$symbol, 'method'} = 'treasurydirect'; $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'source'} = $TREASURY_DIRECT_URL; # OTHER INFORMATION if (exists $bonds{$symbol}) { $info{$symbol, 'success'} = 1; $info{$symbol, 'currency'} = 'USD'; $info{$symbol, $_} = $bonds{$symbol}{$_} for keys %{$bonds{$symbol}}; $info{$symbol, 'price'} = sprintf("%.2f", 0.5*($info{$symbol, 'bid'} + $info{$symbol, 'ask'})); $info{$symbol, 'date'} = $date if defined $date; $info{$symbol, 'isodate'} = $isodate if defined $isodate; } else { $info{$symbol, 'errormsg'} = 'no match'; } } return wantarray() ? %info : \%info; } 1; __END__ =head1 NAME Finance::Quote::TreasuryDirect - Obtain bond quotes from Treasury Direct =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch('treasurydirect', '912810QT8'); =head1 DESCRIPTION This module obtains individual bond quotes by CUSIP number from treasurydirect.gov =head1 LABELS RETURNED Information available from Treasury Direct may include the following labels: method source symbol rate bid ask price date isodate =head1 SEE ALSO treasurydirect.gov Finance::Quote =cut Finance-Quote-1.65/lib/Finance/Quote/Oslobors.pm0000644000175000017500000000445615003302667021312 0ustar bschuckbschuckpackage Finance::Quote::Oslobors; use strict; use JSON qw( decode_json ); use HTTP::Request::Common; our $VERSION = '1.65'; # VERSION use vars qw( $OSLOBORS_COMPONENTS_URL ); $OSLOBORS_COMPONENTS_URL = "https://www.oslobors.no/ob/servlets/components?type=table&source=feed.omff.FUNDS&view=REALTIME&columns=ITEM%2C+PRICECHANGEPCT%2C+PRICE%2C+DATE%2C+QUOTATIONCURRENCY&filter=ITEM_SECTOR%3D%3Ds"; sub methods { return (oslobors => \&oslobors); } { my @labels = qw/date isodate method source currency price p_change/; sub labels { return (oslobors => \@labels); } } sub oslobors { my $quoter = shift; my @symbols = @_; my %funds; my $ua = $quoter->user_agent; my ($url, $reply, $data); foreach my $symbol (@symbols) { $url = $OSLOBORS_COMPONENTS_URL . $symbol; $reply = $ua->request(GET $url); unless($reply->is_success) { $funds{$symbol, "success"} = 0; $funds{$symbol, "errormsg"} = "HTTP request failed"; } else { $data = JSON::decode_json($reply->content)->{"rows"}[0]{"values"}; $quoter->store_date(\%funds, $symbol, { isodate => sprintf("%s-%s-%s", $data->{"DATE"} =~ /(\d\d\d\d)(\d\d)(\d\d)/)}); $funds{$symbol, 'method'} = 'oslobors'; $funds{$symbol, 'currency'} = $data->{"QUOTATIONCURRENCY"}; $funds{$symbol, 'success' } = 1; $funds{$symbol, 'price' } = $data->{"PRICE"}; $funds{$symbol, 'source' } = 'Finance::Quote::Oslobors'; $funds{$symbol, 'symbol' } = $symbol; $funds{$symbol, 'p_change'} = $data->{"PRICECHANGEPCT"}; } } return wantarray() ? %funds : \%funds; } 1; =head1 NAME Finance::Quote::Oslobors - Obtain fund quotes from Oslo stock exchange =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %fundinfo = $q->fetch("oslobors","FUND-TICKER.OSE"); =head1 DESCRIPTION This module obtains information about mutual fund prices from www.oslobors.no. =head1 FUND TICKER SYMBOLS The fund ticker symbols can be found by searching for the fund, and visit its page. The symbol will be visible in the URL, for instance OD-HORIA.OSE. The .OSE part is necessary. The package does not understand Oslo stock symbols. =head1 LABELS RETURNED The module returns date, method, source, currency, price and p_change. The prices are updated on bank days. =head1 SEE ALSO Finance::Quote =cut Finance-Quote-1.65/lib/Finance/Quote/Tiaacref.pm0000644000175000017500000003354315003302667021225 0ustar bschuckbschuck#!/usr/bin/perl -w # # Copyright (C) 1998, Dj Padzensky # Copyright (C) 1998, 1999 Linas Vepstas # Copyright (C) 2000, Yannick LE NY # Copyright (C) 2000, Paul Fenwick # Copyright (C) 2000, Brent Neal # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code derived from Padzensky's work on package Finance::YahooQuote, # but extends its capabilites to encompas a greater number of data sources. # # This code was developed as part of GnuCash package Finance::Quote::Tiaacref; require 5.005; use strict; use Encode qw/decode/; use Time::Piece; use Time::Seconds; use Try::Tiny; our $VERSION = '1.65'; # VERSION # URLs of where to obtain information. my $TIAA_MAIN_URL = 'https://www.tiaa.org/public/investment-performance'; my $TIAA_DATA_URL = 'https://www.tiaa.markitondemand.com/Research/Public/Export/Details'; sub methods { return (tiaacref=>\&tiaacref); } sub labels { return (tiaacref => [qw/ method symbol exchange name date isodate nav price currency /]); } # ======================================================================= # TIAA-CREF Annuities are not listed on any exchange, unlike their mutual funds # TIAA-CREF provides unit values via a cgi on their website. The cgi returns # a csv file in the format # description,price1,date1 # description,price2,date2 # ..etc. # As of 11-Oct-2020, the following securities are found in their lookup # service. Data for some of these are available elsewhere and some are not: # QCBMIX QCBMPX QCBMRX QCEQIX QCEQPX QCEQRX QCGLIX QCGLPX QCGLRX # QCGRIX QCGRPX QCGRRX QCILIX QCILPX QCILRX QCMMIX QCMMPX QCMMRX # QCSCIX QCSCPX QCSCRX QCSTIX QCSTPX QCSTRX QREARX TAISX TAIWX # TBBWX TBIAX TBIIX TBILX TBIPX TBIRX TBIWX TBPPX TCBHX # TCBPX TCBRX TCBWX TCCHX TCEPX TCFPX TCHHX TCHPX TCIEX # TCIHX TCIIX TCILX TCIWX TCIXX TCLCX TCLEX TCLFX TCLHX # TCLIX TCLNX TCLOX TCLPX TCLRX TCLTX TCMGX TCMHX TCMVX # TCNHX TCNIX TCOIX TCQHX TCQPX TCREX TCRIX TCSEX TCSIX # TCTHX TCTIX TCTPX TCTRX TCTWX TCWHX TCWIX TCWPX TCYHX # TCYIX TCYPX TCZHX TCZPX TECGX TECWX TEDHX TEDLX TEDNX # TEDPX TEDTX TEDVX TEIEX TEIHX TEIWX TELCX TELWX TEMHX # TEMLX TEMPX TEMRX TEMSX TEMVX TENWX TEQHX TEQKX TEQLX # TEQPX TEQSX TEQWX TESHX TEVIX TEWCX TFIHX TFIIX TFIPX # TFIRX TFITX TFTHX TFTIX TGIHX TGIWX TGRKX TGRLX TGRMX # TGRNX TGROX THCVX THCWX TIBDX TIBEX TIBFX TIBHX TIBLX # TIBNX TIBUX TIBVX TIBWX TICHX TICRX TIDPX TIDRX TIEHX # TIEIX TIERX TIEWX TIEXX TIGRX TIHHX TIHPX TIHRX TIHWX # TIHYX TIIEX TIIHX TIILX TIIRX TIISX TIIWX TIKPX TIKRX # TILGX TILHX TILIX TILPX TILRX TILVX TILWX TIMIX TIMRX # TIMVX TINRX TIOHX TIOIX TIOPX TIORX TIOSX TIOTX TIOVX # TIQRX TIREX TIRHX TIRTX TIRXX TISAX TISBX TISCX TISEX # TISIX TISPX TISRX TISWX TITIX TITRX TIXHX TIXRX TIYRX # TLFAX TLFIX TLFPX TLFRX TLGRX TLHHX TLHIX TLHPX TLHRX # TLIHX TLIIX TLIPX TLIRX TLISX TLLHX TLLIX TLLPX TLLRX # TLMHX TLMPX TLMRX TLPRX TLQHX TLQIX TLQRX TLRHX TLRIX # TLRRX TLSHX TLSPX TLSRX TLTHX TLTIX TLTPX TLTRX TLVPX # TLWCX TLWHX TLWIX TLWPX TLWRX TLXHX TLXIX TLXNX TLXPX # TLXRX TLYHX TLYIX TLYPX TLYRX TLZHX TLZIX TLZRX TMHXX # TNSHX TNWCX TPILX TPISX TPPXX TPSHX TPWCX TRBIX TRCIX # TRCPX TRCVX TREPX TRERX TRGIX TRGMX TRGPX TRHBX TRIEX # TRIHX TRILX TRIPX TRIRX TRIWX TRLCX TRLHX TRLIX TRLWX # TRPGX TRPSX TRPWX TRRPX TRRSX TRSCX TRSEX TRSHX TRSPX # TRVHX TRVPX TRVRX TSAHX TSAIX TSALX TSAPX TSARX TSBBX # TSBHX TSBIX TSBPX TSBRX TSCHX TSCLX TSCTX TSCWX TSDBX # TSDDX TSDFX TSDHX TSDJX TSFHX TSFPX TSFRX TSFTX TSGGX # TSGHX TSGLX TSGPX TSGRX TSIHX TSILX TSIMX TSIPX TSITX # TSMEX TSMHX TSMLX TSMMX TSMNX TSMOX TSMPX TSMTX TSMUX # TSMWX TSOEX TSOHX TSONX TSOPX TSORX TSRPX TSTPX TTBHX # TTBWX TTFHX TTFIX TTFPX TTFRX TTIHX TTIIX TTIPX TTIRX # TTISX TTRHX TTRIX TTRLX TTRPX TVIHX TVIIX TVIPX TVITX # W111# W113# W114# W115# W116# W117# W118# W119# W120# # W121# W122# W123# W128# W130# W131# W132# W133# W134# # W135# W136# W137# W138# W139# W140# W141# W142# W143# # W144# W145# W146# W147# W148# W149# W150# W151# W152# # W153# W154# W155# W156# W157# W158# W159# W160# W161# # W162# W163# W164# W165# W166# W167# W168# W169# W170# # W171# W172# W173# W174# W175# W176# W177# W178# W179# # W180# W211# W213# W214# W215# W216# W217# W218# W219# # W220# W221# W222# W223# W228# W230# W231# W232# W233# # W234# W235# W236# W237# W238# W239# W240# W241# W242# # W243# W244# W245# W246# W247# W248# W249# W250# W251# # W252# W253# W254# W255# W256# W257# W258# W259# W260# # W261# W262# W263# W264# W265# W266# W267# W268# W269# # W270# W271# W272# W273# W274# W275# W276# W277# W278# # W279# W280# W311# W313# W314# W315# W316# W317# W318# # W319# W320# W321# W322# W323# W328# W330# W331# W332# # W333# W334# W335# W336# W337# W338# W339# W340# W341# # W342# W343# W344# W345# W346# W347# W348# W349# W350# # W351# W352# W353# W354# W355# W356# W357# W358# W359# # W360# W361# W362# W363# W364# W365# W366# W367# W368# # W369# W370# W371# W372# W373# W374# W375# W376# W377# # W378# W379# W380# W411# W413# W414# W415# W416# W417# # W418# W419# W420# W421# W422# W423# W428# W430# W431# # W432# W433# W434# W435# W436# W437# W438# W439# W440# # W441# W442# W443# W444# W445# W446# W447# W448# W449# # W450# W451# W452# W453# W454# W455# W456# W457# W458# # W459# W460# W461# W462# W463# W464# W465# W466# W467# # W468# W469# W470# W471# W472# W473# W474# W475# W476# # W477# W478# W479# W480# W511# W512# W514# W515# W516# # W517# W518# W519# W520# W521# W522# W523# W524# W525# # W526# W527# W528# W529# W530# W531# W532# W533# W534# # W535# W536# W537# W538# W539# W540# W541# W543# W544# # W545# W546# W547# W548# W549# W550# W611# W612# W614# # W615# W616# W617# W618# W619# W620# W621# W622# W623# # W624# W625# W626# W627# W628# W629# W630# W631# W632# # W633# W634# W635# W636# W637# W638# W639# W640# W641# # W643# W644# W645# W646# W647# W648# W649# W650# W711# # W712# W714# W715# W716# W717# W718# W719# W720# W721# # W722# W723# W724# W725# W726# W727# W728# W729# W730# # W731# W732# W733# W734# W735# W736# W737# W738# W739# # W740# W741# W743# W744# W745# W746# W747# W748# W749# # W750# W811# W812# W814# W815# W816# W817# W818# W819# # W820# W821# W822# W823# W824# W825# W826# W827# W828# # W829# W830# W831# W832# W833# W834# W835# W836# W837# # W838# W839# W840# W841# W843# W844# W845# W846# W847# # W848# W849# W850# # # This subroutine was written by Brent Neal # Modified to support new TIAA-CREF webpages by Kevin Foss and Brent Neal # Modified to support new 2012 TIAA-CREF webpages by Carl LaCombe # Modified to support new 2020 TIAA webpages by Jeremy Volkening # # TODO: # # The TIAA-CREF cgi allows you to specify the exact dates for which to retrieve # price data. That functionality could be worked into this subroutine. # Currently, we only grab the most recent price data. # sub tiaacref { my $quoter = shift; my @symbols = @_; return unless scalar @symbols; my %info; my $ua = $quoter->user_agent; # The TIAA data service wants a start and end date. To guarantee data, # ask for 7 days of quotes, and only take the first (most recent) one. my $end = localtime; my $start = $end - ONE_WEEK; #Need to fetch a session key first my $session_key; my $fail_msg; my $res = $ua->get( $TIAA_MAIN_URL ); if (! $res->is_success) { $fail_msg = "Failed to fetch TIAA page from $TIAA_MAIN_URL. It may be" . " that the link has changed. HTTP status returned: " . $res->status_line; } else { if ($res->content =~ /\bMODKey=\'([^']+)'/) { $session_key = $1; } else { $fail_msg = "Failed to fetch session key from TIAA site. Please" . " contact the developers for further assistance." } } if (defined $fail_msg) { for my $symbol (@symbols) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = $fail_msg; } return %info if wantarray; return \%info; } SYMBOL: for my $symbol (@symbols) { my $payload = { xids => [$symbol], exportType => 'CSV', startDate => $start->mdy, endDate => $end->mdy, selectedDetails => '', }; my $url = join '?', $TIAA_DATA_URL, $session_key, ; my $res = $ua->post($url, $payload); if (! $res->is_success) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "There was an error fetching data" . " for $symbol. HTTP status returned: " . $res->status_line; next SYMBOL; } # Data returned is in UTF-16-encoded CSV. As we asked for a week of # data, successful queries will likely return multiple lines, but they # are sorted in descending chronological order so we can just take # the first one. my $csv = decode( 'UTF-16LE', $res->content ); open my $stream, '<', \$csv; while (my $line = <$stream>) { chomp $line; my ($description, $price, $date) = split ',', $line; # if no data is found for the given symbol, no error is thrown # but the content returned contains a textual error message. In # this case, the latter fields will not be defined. if (! defined $date) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "Error retrieving quote for $symbol - no listing for this" . " name found. Please check symbol and the two letter" . " extension (if any)"; next SYMBOL; } try { $date = Time::Piece->strptime($date, "%m/%d/%Y"); } catch { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "Error parsing date ($date) for $symbol. Please" . " contact the developers for further assistance."; next SYMBOL; }; $info{ $symbol, "success" } = 1; $info{ $symbol, "symbol" } = $symbol; $info{ $symbol, "exchange" } = "TIAA"; $info{ $symbol, "name" } = $description; $info{ $symbol, "nav" } = $price; $info{ $symbol, "price" } = $info{$symbol, "nav"}; $info{ $symbol, "currency" } = "USD"; $info{ $symbol, "method" } = "tiaacref"; $info{ $symbol, "isodate" } = $date->ymd; $info{ $symbol, "date" } = $date->mdy('/'); $quoter->store_date( \%info, $symbol, {isodate => $date->ymd} ); last; # IMPORTANT: don't parse older data! } } return %info if wantarray; return \%info; } 1; =head1 NAME Finance::Quote::Tiaacref - Obtain quote from TIAA (formerly TIAA-CREF) =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("tiaacref","TIAAreal"); =head1 DESCRIPTION This module obtains information about TIAA-CREF managed funds. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by passing "Tiaacref" in to the argument list of Finance::Quote->new(). Information returned by this module is governed by TIAA's terms and conditions. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::Tiaacref: symbol, exchange, name, date, nav, price. =head1 SEE ALSO TIAA, L =cut Finance-Quote-1.65/lib/Finance/Quote/MorningstarCH.pm0000644000175000017500000002104515003302667022217 0ustar bschuckbschuck#!/usr/bin/perl -w # MorningstarCH.pm # # Obtains quotes for CH Unit Trusts from http://morningstar.ch/ - please # refer to the end of this file for further information. # # author: Manuel Friedli (manuel@fritteli.ch) # # version: 0.1 Initial version - 02 March 2019 # # This file is heavily based on MStaruk.pm by Martin Sadler # (martinsadler@users.sourceforge.net), version 0.1, 01 April 2013 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::MorningstarCH; require 5.006; use strict; use warnings; # URLs use vars qw($VERSION $MSTARCH_NEXT_URL $MSTARCH_LOOK_UP $MSTARCH_MAIN_URL); use LWP::Simple; use LWP::UserAgent; use HTTP::Request::Common; use HTTP::Cookies; $MSTARCH_MAIN_URL = "https://www.morningstar.ch"; $MSTARCH_LOOK_UP = "https://www.morningstar.ch/ch/funds/SecuritySearchResults.aspx?search="; $MSTARCH_NEXT_URL = "https://www.morningstar.ch/ch/funds/snapshot/snapshot.aspx?id="; # FIXME - our $VERSION = '1.65'; # VERSION sub methods { return (morningstarch => \&morningstarch_fund); } { my @labels = qw/name currency last date time price nav source iso_date method net p_change success errormsg/; sub labels { return (morningstarch => \@labels); } } # # ======================================================================= sub morningstarch_fund { my $quoter = shift; my @symbols = @_; return unless @symbols; my %fundquote; my $ua = $quoter->user_agent; my $cj = HTTP::Cookies->new(); $ua->cookie_jar( $cj ); foreach (@symbols) { my $code = $_; my $code_type = "** Invalid **"; if ($code =~ m/^[a-zA-Z]{2}[a-zA-Z0-9]{9}\d(.*)/ && !$1) { $code_type = "ISIN"; } elsif ($code =~ m/^[a-zA-Z0-9]{6}\d(.*)/ && !$1 ) { $code_type = "SEDOL"; } elsif ($code =~ m/^[a-zA-Z]{4,6}(.*)/ && !$1) { $code_type = "MEXID"; } # current version can only use ISIN - report an error and exit if any other type if ($code_type ne "ISIN") { $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - invalid symbol"; next; } $fundquote {$code,"success"} = 1; # ever the optimist.... $fundquote {$code,"errormsg"} = "Success"; # perform the look-up - if not found, return with error my $webdoc = get($MSTARCH_LOOK_UP.$code); if (!$webdoc) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data"; next; } $fundquote {$code, "symbol"} = $code; $fundquote {$code, "source"} = $MSTARCH_MAIN_URL; # Find name by regexp my ($name, $nexturl, $isin); if ($webdoc =~ m[(.*?)[a-zA-Z]{2}[a-zA-Z0-9]{9}\d(.*)] ) { $nexturl = $1; $name = $2; $isin = $3; } if (!defined($name)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find fund name"; $name = "*** UNKNOWN ***"; # ... and continue } $fundquote {$code, "name"} = $name; # set name if (!defined($nexturl)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data"; next; } # modify $nexturl to remove html escape encoding for the Ampersand (&) character $nexturl =~ s/&/&/; # Now need to look-up next page using $next_url $webdoc = get($MSTARCH_MAIN_URL.$nexturl); if (!$webdoc) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data"; next; } # Find date, currency and price all in one table row my ($currency, $date, $price, $pchange); if ($webdoc =~ m[NAV
([0-9]{2}\.[0-9]{2}\.[0-9]{4})
.*([A-Z]{3}).([0-9\,\.]+).*>([0-9\,\.\-]+)%?] ) { $date = $1; $currency = $2; $price = $3; $pchange = $4; } if (!defined($pchange)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find net or %-age change"; # set to (minus)zero $pchange = -0.00; # ... and continue } $pchange =~ s/,/./; $fundquote {$code, "p_change"} = $pchange; # set %-change if (!defined($date)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find a date"; # use today's date $quoter->store_date(\%fundquote, $code, {today => 1}); # ... and continue } else { $quoter->store_date(\%fundquote, $code, {eurodate => $date}); } if (!defined($price)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to find a price"; next; } $price =~ s/,/./; if (!defined($currency)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to find a currency"; next; } # defer setting currency and price until we've dealt with possible GBX currency... # Calculate net change - it's not included in the morningstar factsheets my $net = ($price * $pchange) / 100 ; # now set prices and currency $fundquote {$code, "price"} = $price; $fundquote {$code, "last"} = $price; $fundquote {$code, "nav"} = $price; $fundquote {$code, "net"} = $net; $fundquote {$code, "currency"} = $currency; # Set a dummy time as gnucash insists on having a valid format my $time = "12:00"; # set to Midday if no time supplied ??? # gnucash insists on having a valid-format $fundquote {$code, "time"} = $time; # set time $fundquote {$code, "method"} = "morningstarch"; # set method } return wantarray ? %fundquote : \%fundquote; } 1; =head1 NAME Finance::Quote::morningstarch - Obtain CH Unit Trust quotes from morningstar.ch. =head1 SYNOPSIS $q = Finance::Quote->new; %info = Finance::Quote->fetch("morningstarch"," ..."); # Only query morningstar.ch using ISINs =head1 DESCRIPTION This module fetches information from the MorningStar Funds service, https://morningstar.com/ch/. Funds are identified by their ISIN code. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "morningstarch" in the argument list to Finance::Quote->new(). Information obtained by this module may be covered by Morningstar terms and conditions See https://morningstar.ch/ for details. =head2 Stocks And Indices This module provides the "morningstarch" fetch method for fetching CH Unit Trusts and OEICs prices and other information from morningstar.ch. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::morningstarch : name, currency, last, date, time, price, nav, source, method, iso_date, net, p_change, success, errormsg. =head1 SEE ALSO Morning Star websites, https://morningstar.ch =head1 AUTHOR Manuel Friedli, Emanuel@fritteli.chE Based heavily on the work of Martin Sadler Emartinsadler@users.sourceforge.netE, many thanks! =head1 COPYRIGHT AND LICENSE Copyright (C) 2019 by Manuel Friedli This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available. =cut __END__ Finance-Quote-1.65/lib/Finance/Quote/FTfunds.pm0000644000175000017500000002706515003302667021062 0ustar bschuckbschuck#!/usr/bin/perl -w # ftfunds.pm # # Obtains quotes for UK Unit Trusts from http://funds.ft.com/ - please # refer to the end of this file for further information. # # author: Martin Sadler (martinsadler@users.sourceforge.net) # # Version 0.1 Initial version - 06 Sep 2010 # # Version 0.2 Better look-up - 19 Sep 2010 # # Version 0.3 name changed to "ftfunds" # (all lower case) and tidy-up - 31 Jan 2011 # # Version 0.4 Allows alphanumerics MEXIDs # and back to "FTfunds" - 28 Feb 2011 # # Version 1.0 Changed to work with the new # format of funds.ft.com - 14 Sep 2011 # # Version 2.0 Changed to work with the latest # format of funds.ft.com - 31 Mar 2013 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::FTfunds; require 5.005; use strict; use warnings; # Set DEBUG => 0 for no debug messages, => 1 for first level, => 2 for 2nd level, etc. use constant DEBUG => 0; # URLs use vars qw($VERSION $FTFUNDS_LOOK_UD $FTFUNDS_LOOK_LD $FTFUNDS_MAIN_URL); use LWP::UserAgent; use HTTP::Request::Common; use HTTP::Cookies; use HTML::TokeParser; # use Data::Dumper; our $VERSION = '1.65'; # VERSION $FTFUNDS_MAIN_URL = "https://markets.ft.com"; $FTFUNDS_LOOK_LD = "https://markets.ft.com/data/funds/tearsheet/summary?s="; $FTFUNDS_LOOK_UD = "http://funds.ft.com/UnlistedTearsheet/Summary?s="; # this will work with ISIN codes only. # FIXME - sub methods { return (ftfunds => \&ftfunds_fund, ukfunds => \&ftfunds_fund); } { my @labels = qw/name currency last date time price nav source iso_date method net p_change success errormsg/; sub labels { return (ftfunds => \@labels, ukfunds => \@labels); } } # # ======================================================================= sub ftfunds_fund { my $quoter = shift; my @symbols = @_; return unless @symbols; my %fundquote; my $ua = $quoter->user_agent; my $cj = HTTP::Cookies->new(); $ua->cookie_jar( $cj ); foreach (@symbols) { my $code = $_; my $code_type = "** Invalid **"; if ($code =~ m/^[a-zA-Z]{2}[a-zA-Z0-9]{9}\d(.*)/ && !$1) { $code_type = "ISIN"; } elsif ($code =~ m/^[a-zA-Z0-9]{6}\d(.*)/ && !$1 ) { $code_type = "SEDOL"; } elsif ($code =~ m/^[a-zA-Z0-9]{4,6}(.*)/ && !$1) { $code_type = "MEXID"; } # current version can only use ISIN - report an error and exit if any other type if ($code_type ne "ISIN") { $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - invalid symbol"; next; } $fundquote {$code,"success"} = 1; # ever the optimist.... $fundquote {$code,"errormsg"} = "Success"; # perform the look-up - if not found, return with error # try listed funds first... my $webdoc = $ua->get($FTFUNDS_LOOK_LD.$code); if (!$webdoc->is_success) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data : HTTP status = ".$webdoc->status_line; next; } DEBUG and print "\nTitle = ",$webdoc->title,"\n"; DEBUG and print "\nStatus = ",$webdoc->status_line, "\n"; DEBUG > 1 and print "\nCookie Jar = : \n",Dumper($cj),"\n\n"; $fundquote {$code, "source"} = $FTFUNDS_LOOK_LD.$code; # if page contains "

0 results

" it's not found... # ... try unlisted funds... if ($webdoc->content =~ m[

(0 results)

] ) { DEBUG and print "\nTrying unlisted funds for ",$code," : ",$1,"\n"; $webdoc = $ua->get($FTFUNDS_LOOK_UD.$code); if (!$webdoc->is_success) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data : HTTP status = ".$webdoc->status_line; next; } DEBUG and print "\nTitle = ",$webdoc->title,"\n"; DEBUG and print "\nStatus = ",$webdoc->status_line, "\n"; DEBUG > 1 and print "\nCookie Jar = : \n",Dumper($cj),"\n\n"; $fundquote {$code, "source"} = $FTFUNDS_LOOK_UD.$code; } $fundquote {$code, "symbol"} = $code; # Find name by simple regexp my $name; if ($webdoc->content =~ m[(.*) [Ss]ummary - FT.com] ) { $name = $1 ; } if (!defined($name)) { # not a serious error - don't report it .... # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find fund name"; $name = "*** UNKNOWN ***"; # ... and continue } $fundquote {$code, "name"} = $name; # set name # Find price and currency my $currency; my $price; if ($webdoc->content =~ m[<span class="mod-ui-data-list__label"[^>]*>Price [(]([A-Z]{3})[)]</span><span class="mod-ui-data-list__value">([\.\,0-9]*)</span>] ) { $currency = $1; $price = $2; } if (!defined($currency)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to find a currency"; next; } if (!defined($price)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to find a price"; next; } if ($price =~ m[([0-9]*),([\.0-9]*)]) { $price = $1 * 1000 + $2; } # Find net and percent-age change my $net; my $pchange; if ($webdoc->content =~ m[<span class="mod-ui-data-list__label">Today's Change</span><span class="mod-ui-data-list__value"><span [^>]*><i [^>]*></i>(-?[\.0-9]*) / (-?[\.0-9]*)%</span>] ) { $net = $1 ; $pchange = $2; } if (!defined($net)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find a net change."; $net = "-0.00"; # ???? is this OK ???? # ... and continue } if (!defined($pchange)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find a %-change"; $pchange = "-0.00"; # ???? is this OK ???? # ... and continue } if ($net =~ m[([0-9]*),([\.0-9]*)]) { $net = $1 * 1000 + $2; } if ($pchange =~ m[([0-9]*),([\.0-9]*)]) { $pchange = $1 * 1000 + $2; } # deal with GBX pricing of UK unit trusts if ($currency eq "GBX") { $currency = "GBP" ; $price = $price / 100 ; $net = $net / 100 ; } # now set prices, net change and currency $fundquote {$code, "price"} = $price; $fundquote {$code, "last"} = $price; $fundquote {$code, "nav"} = $price; $fundquote {$code, "net"} = $net; $fundquote {$code, "currency"} = $currency; $fundquote {$code, "p_change"} = $pchange; # set %-change # Find time # NB. version 2.0 - there is no time quoted on the current (31/3/2013) factsheet page for unit trusts # ... this code left in in case it is available in later revisions of the page my $time; if ($webdoc->content =~ m[......... some string that will identify the time ............] ) { if ($1 =~ m[(\d\d:\d\d)] ) # strip any trailing text (Timezone, etc.) { $time = $1; } } if (!defined($time)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find a time"; $time = "17:00"; # set to 17:00 if no time supplied ??? # gnucash insists on having a valid-format # ... and continue } $fundquote {$code, "time"} = $time; # set time # Find date my $date; if ($webdoc->content =~ m[([A-Za-z]{3}) ([0-9]{2}) ([0-9]{4})] ) { $date = "$2/$1/$3" ; } if (!defined($date)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find a date"; # use today's date $quoter->store_date(\%fundquote, $code, {today => 1}); # ... and continue } else { $quoter->store_date(\%fundquote, $code, {eurodate => $date}); } $fundquote {$code, "method"} = "ftfunds"; # set method sleep 1; # go to sleep for a while to give the web-site a breather } # end of "foreach (@symbols)" return wantarray ? %fundquote : \%fundquote; } 1; =head1 NAME Finance::Quote::FTfunds - Obtain UK Unit Trust quotes from FT.com (Financial Times). =head1 SYNOPSIS $q = Finance::Quote->new; %info = Finance::Quote->fetch("ftfunds","<isin> ..."); # Only query FT.com using ISINs =head1 DESCRIPTION This module fetches information from the Financial Times Funds service, http://funds.ft.com. There are over 47,000 UK Unit Trusts and OEICs quoted, as well as many Offshore Funds and Exhange Traded Funds (ETFs). It converts any funds quoted in GBX (pence) to GBP, dividing the price by 100 in the process. Funds are identified by their ISIN code, a 12 character alphanumeric code. Although the web site also allows searching by fund name, this version of Finance::Quote::FTfunds only implements ISIN lookup. To determine the ISIN for funds of interest to you, visit the funds.ft.com site and use the flexible search facilities to identify the funds of interest. The factsheet display for any given fund displays the ISIN along with other useful information. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "ftfunds" in the argument list to Finance::Quote->new(). Information obtained by this module may be covered by funds.ft.com terms and conditions See http://funds.ft.com/ and http://ft.com for details. =head2 Stocks And Indices This module provides both the "ftfunds" and "ukfunds" fetch methods for fetching UK and Offshore Unit Trusts and OEICs prices and other information from funds.ft.com. Please use the "ukfunds" fetch method if you wish to have failover with future sources for UK and Offshore Unit Trusts and OEICs - the author has plans to develop Finance::Quote modules for the London Stock Exchange and Morningstar unit trust services. Using the "ftfunds" method will guarantee that your information only comes from the funds.ft.com website. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::ftfunds : name, currency, last, date, time, price, nav, source, method, iso_date, net, p_change, success, errormsg. =head1 SEE ALSO Financial Times websites, http://ft.com and http://funds.ft.com =head1 AUTHOR Martin Sadler, E<lt>martinsadler@users.sourceforge.netE<gt> =head1 COPYRIGHT AND LICENSE Copyright (C) 2010 by Martin Sadler This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available. =cut __END__ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/BorsaItaliana.pm�����������������������������������������������0000644�0001750�0001750�00000013237�15003302667�022216� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This module is based on the Finance::Quote::BSERO module # It was first called BOMSE but has been renamed to yahooJSON # since it gets a lot of quotes besides Indian # # The code has been modified by Abhijit K to # retrieve stock information from Yahoo Finance through json calls # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::BorsaItaliana; require 5.005; use strict; use vars qw($VERSION $YIND_URL_HEAD $YIND_URL_TAIL); use LWP::UserAgent; use Web::Scraper; our $VERSION = '1.65'; # VERSION # URL example = https://www.borsaitaliana.it/borsa/search/scheda.html?code=IT0001086567&lang=it my $YIND_URL_HEAD = 'https://www.borsaitaliana.it/borsa/search/scheda.html?code='; my $YIND_URL_TAIL = '&lang=it'; sub methods { return ( borsa_italiana => \&borsa_italiana, ); } { my @labels = qw/name last date isodate volume currency method exchange type div_yield eps pe year_range open high low close/; sub labels { return ( borsa_italiana => \@labels, ); } } sub borsa_italiana { my $quoter = shift; my @bonds = @_; my ( %info, $reply, $url, $te, $ts, $row, @cells, $ce ); my ( $my_date ); my $ua = $quoter->user_agent(); foreach my $bond (@bonds) { $url = $YIND_URL_HEAD . $bond . $YIND_URL_TAIL; $reply = $ua->get($url); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = $reply->content; #Response variables available: #Response code: $code #Response description: $desc #HTTP Headers: $headers #Response body $body $info{ $bond, "symbol" } = $bond; if ( $code == 200 ) { my $widget = scraper { process 'div.summary-value span.t-text', 'val' => 'TEXT'; }; my $result = $widget->scrape($reply); # check if found unless (exists $result->{val}) { $info{$bond, 'success'} = 0; $info{$bond, 'errormsg'} = 'Failed to find ISIN'; next; } my $value = $result->{val}; $value =~ s/[^0123456789,]//g; $value =~ s/,/./g; $widget = scraper { process 'title', 'name' => 'TEXT'; }; $result = $widget->scrape($reply); # check if found unless (exists $result->{name}) { $info{$bond, 'success'} = 0; $info{$bond, 'errormsg'} = 'Failed to find ISIN'; next; } my $name = $result->{name}; $name =~ s/quotazioni in tempo reale .* Borsa Italiana//g; $widget = scraper { process 'div.summary-fase span.t-text', 'dt[]' => 'TEXT'; }; $result = $widget->scrape($reply); # check if found unless (exists $result->{dt}) { $info{$bond, 'success'} = 0; $info{$bond, 'errormsg'} = 'Failed to find ISIN'; next; } my $date = $result->{dt}[1]; $date =~ s/.*Contratto:\ //g; $date =~ s/[^0123456789]//g; my ($dd,$mm,$yy,$hh,$mi,$ss) = $date =~ /^([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{1,})([0-9]{2})([0-9]{2})\z/ or die; my $my_date= $dd.".".$mm.".".$yy." ".$hh.":".$mi.":".$ss; $info{ $bond, "success" } = 1; $info{ $bond, "exchange" } = "Borsa Italiana"; $info{ $bond, "method" } = "borsa_italiana"; $info{ $bond, "name" } = $name; $info{ $bond, "symbol" } = $bond; $info{ $bond, "price" } = $value; $info{ $bond, "last" } = $value; $info{ $bond, "currency" } = "EUR"; $quoter->store_date( \%info, $bond, { eurodate => $my_date } ); } #HTTP request fail else { $info{ $bond, "success" } = 0; $info{ $bond, "errormsg" } = "Error retrieving quote for $bond. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; =head1 NAME Finance::Quote::BorsaItaliana - Obtain quotes from Borsa Italiana site =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = Finance::Quote->fetch("borsa_italiana","{ISIN_CODE}"); =head1 DESCRIPTION This module fetches information from Borsa Italiana site This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "BorsaItaliana" in the argument list to Finance::Quote->new(). This module provides the "borsa_italiana" fetch method. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::BorsaItaliana : name, symbol, price, last, isodate, currency, method, exchange. =head1 SEE ALSO =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Comdirect.pm���������������������������������������������������0000644�0001750�0001750�00000020244�15003302667�021412� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# vi: set ts=2 sw=2 noai ic showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::Comdirect; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use HTML::TableExtract; use HTML::TreeBuilder; use LWP::UserAgent; use String::Util qw(trim); use Encode qw(encode_utf8); our $VERSION = '1.65'; # VERSION our $DISPLAY = 'Comdirect - Frankfurt and other exchanges'; our $FEATURES = { 'EXCHANGE' => 'select market place (i.e. "gettex", "Xetra", "Tradegate")' }; our @LABELS = qw/symbol name open high low last date time p_change ask bid currency isin wkn method exchange exchanges/; our $METHODHASH = {subroutine => \&comdirect, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( comdirect => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub comdirect { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my (%info, %pricetable, %infotable); foreach my $symbol (@_) { my $try = 0; my $url = 'https://www.comdirect.de/inf/search/all.html?SEARCH_VALUE=' . $symbol; RETRY: ++$try; my $reply = $ua->get($url); unless ($reply->is_success) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = join ' ', $reply->code, $reply->message; next; } my $body = $reply->decoded_content; ### [<now>] Body: $body my $tree = HTML::TreeBuilder->new; unless ($tree->parse($body)) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Parse body failed'; next; } my $exchange = exists $quoter->{module_specific_data}->{comdirect}->{EXCHANGE} ? $quoter->{module_specific_data}->{comdirect}->{EXCHANGE} : undef; my $select = $tree->look_down(_tag => 'select', name=> 'ID_NOTATION', id=> "marketSelect"); unless($select) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Parse marketplaces failed.'; next; } my %exchange2nid = map { encode_utf8($_->as_text) => $_->attr('value') } grep { ref eq 'HTML::Element' and $_->tag eq 'option' } $select->content_list; my $option = $select->look_down(_tag => 'option', selected => 'selected'); unless($option) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Parse selected marketplace failed.'; next; } if ($exchange and $exchange ne encode_utf8($option->as_text)) { unless (exists($exchange2nid{$exchange}) and $try < 2) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Marketplace not found: ' . "'".$exchange."'"; next; } my $u = $reply->request->url; $u->query_param("ID_NOTATION" => $exchange2nid{$exchange}); $url = $u->as_string; goto RETRY; } $info{$symbol, 'exchanges'} = [ sort keys %exchange2nid ]; $info{$symbol, 'exchange'} = encode_utf8($option->as_text); $info{$symbol, 'notation_id'} = $option->attr('value'); my $h1 = $tree->look_down(_tag => 'h1'); if ($h1) { $info{$symbol, 'name'} = trim(encode_utf8($h1->as_text)); } my $div = $tree->look_down(_tag => 'div', class => "realtime-indicator"); if ($div) { my @span = $div->look_down(_tag => 'span'); if (scalar(@span) >= 2) { $info{$symbol, 'last'} = $1 if trim($span[-2]->as_text) =~ /^([0-9.]+)/; $info{$symbol, 'currency'} = $1 if trim($span[-1]->as_text) =~ /^([A-Z]+)/; } } my $te = HTML::TableExtract->new( count => 4, attribs => { class => 'simple-table' } ); ### [<now>] TE: $te unless ( $te->parse($body) and $te->first_table_found) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'No price data found'; next; } foreach my $row ($te->rows) { ### [<now>] Row: $row if (defined($row->[0])) { $infotable{$row->[0]} = $row->[1]; } } $te = HTML::TableExtract->new( count => 2, attribs => { class => 'simple-table' } ); ### [<now>] TE: $te unless ( $te->parse($body) and $te->first_table_found) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'No price data found'; next; } foreach my $row ($te->rows) { ### [<now>] Row: $row if ($row->[0] eq 'Zeit' && $pricetable{'Zeit'}) {next} $pricetable{$row->[0]} = trim($row->[1]); } ### [<now>] Pricetable hash: %pricetable unless (exists $pricetable{Zeit}) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Parse failed.'; next; } my %mapping = ( 'high' => 'Hoch', 'low' => 'Tief', 'bid' => 'Geld', 'ask' => 'Brief', 'open' => "Er\x{f6}ffnung", 'close' => "Schluss Vortag" ); while ((my $fqkey, my $cbkey) = each (%mapping)) { $info{$symbol, $fqkey} = $1.'.'.$2 if (exists($pricetable{$cbkey}) and defined($pricetable{$cbkey}) and $pricetable{$cbkey} =~ /^(\d+),(\d+)/); } $info{$symbol, 'p_change'} = $1 if $pricetable{"Diff. Vortag"} =~ /([+-][0-9]+\.[0-9]+)\x{a0}%/; $info{$symbol, 'isin'} = $infotable{ISIN}; $info{$symbol, 'wkn'} = $infotable{WKN}; $info{$symbol, 'symbol'} = $infotable{Symbol}; if ($pricetable{Zeit} =~ /([0-9]{2}[.][0-9]{2}[.][0-9]{2}) ([ 0-9][0-9]:[0-9][0-9])/) { $quoter->store_date(\%info, $symbol, {eurodate => $1}); $info{$symbol, 'time'} = $2; } $info{$symbol, 'method'} = 'comdirect'; $info{$symbol, 'success'} = 1; } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::Comdirect - Obtain quotes from https://www.comdirect.de =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; or $q = Finance::Quote->new("Comdirect", "comdirect" => { "EXCHANGE" => "Xetra" }); %info = Finance::Quote->fetch('comdirect', 'DE0007664039'); %info = Finance::Quote->fetch('comdirect', 'Volkswagen'); %info = Finance::Quote->fetch('comdirect', 'VWAGY'); @exchanges = @{ $info{ "VWAGY", "exchanges" } }; # List of available marketplaces =head1 DESCRIPTION This module fetches information from https://www.comdirect.de. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'Comdirect' in the argument list to Finance::Quote->new(). =head1 EXCHANGE https://www.comdirect.de/ supports different marketplaces: "gettex" "Xetra" "Frankfurt" "Tradegate" ... any many more ... The EXCHANGE may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::Comdirect: isodate, time, last, currency, open, high, low, name, isin, wkn, p_change, ask, bid, method, exchange, success, exchanges =head1 TERMS & CONDITIONS Use of www.comdirect.de is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/YahooWeb.pm����������������������������������������������������0000644�0001750�0001750�00000016330�15003302667�021217� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# vi: set noai ic ts=4 sw=4 showmode showmatch: # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. package Finance::Quote::YahooWeb; use warnings; use strict; use Encode qw( encode_utf8 ); use HTTP::Request::Common; use HTML::TreeBuilder::XPath; use JSON qw( decode_json ); use LWP::Protocol::http; use Text::Template; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use constant URLTAG => "data-url"; use constant JSONBODY => "body"; our $VERSION = '1.65'; # VERSION # Fix for 500 Header line too long message push(@LWP::Protocol::http::EXTRA_SOCK_OPTS, MaxLineLength => 0); my $URL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://finance.yahoo.com/quote/{$symbol}/history?p={$symbol}'); my $AGENT = 'Mozilla/5.0'; my $XPATH = Text::Template->new(TYPE => 'STRING', SOURCE => '//*[@data-symbol=~"^{$symbol}$"][@data-field=~"regularMarketPrice"]'); our $DISPLAY = '<Module Name + Brief Info>'; our @LABELS = qw/symbol name exchange currency isodate last open high low volume/; our $METHODHASH = {subroutine => \&yahooweb, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( yahooweb => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub yahooweb { my $quoter = shift; my @stocks = @_; my ( %info, $url, $reply, $script_tag ); my $ua = $quoter->user_agent(); my $agent = $ua->agent(); $ua->agent($AGENT); foreach my $symbol (@stocks) { $url = $URL->fill_in(HASH => {symbol => $symbol}); ### [<now>] YahooWeb: $url $reply = $ua->request(GET $url); ### [<now>] Reply: $reply unless ($reply->is_success) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = join ' ', $reply->code, $reply->message; next; } my $tree = HTML::TreeBuilder::XPath->new(); $tree->ignore_unknown(0); $tree->parse($reply->decoded_content); $script_tag = $tree->look_down(_tag => 'script', type => 'application/json', URLTAG, qr!https://query1.finance.yahoo.com/v7/finance/quote\?fields=fiftyTwoWeekHigh.*! ); ### [<now>] script_tag: $script_tag unless($script_tag) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Error - Symbol not found'; next; } my @numfound = $script_tag->content_list(); ### [<now>] numfound: @numfound my $json_data; eval {$json_data = decode_json encode_utf8( $numfound[0] )}; if($@) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $@; next; } ### [<now>] json_data: $json_data my $json_body = encode_utf8($json_data->{'body'}); ### [<now>] json_body: $json_body; eval {$json_data = decode_json $json_body}; if($@) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $@; next; } ### [<now>] json_data 2: $json_data my $yahoo_symbol = $json_data->{'quoteResponse'}{'result'}[0]{'symbol'}; my $name = $json_data->{'quoteResponse'}{'result'}[0]{'shortName'}; if (uc($symbol) ne uc($yahoo_symbol)) { ### Error: $symbol, $yahoo_symbol $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = 'Unexpected response from Yahoo site'; next; } $info{ $symbol, 'name' } = $name if $name; my $currency = $json_data->{'quoteResponse'}{'result'}[0]{'currency'}; $info{ $symbol, 'currency' } = $currency if $currency; $info{ $symbol, 'exchange' } = $json_data->{'quoteResponse'}{'result'}[0]{'fullExchangeName'}; if ($currency =~ /^GBp/) { $info{ $symbol, 'currency' } = 'GBP'; } else { $info{ $symbol, 'currency' } = $currency; } my $last = $json_data->{'quoteResponse'}{'result'}[0]{'regularMarketPrice'}{'fmt'}; $last =~ s/,//g; if ($currency =~ /^GBp/) { $last = $last / 100; } my $open = $json_data->{'quoteResponse'}{'result'}[0]{'regularMarketOpen'}{'fmt'}; if ($open) { $open =~ s/,//g; if ($currency =~ /^GBp/) { $open = $open / 100; } } my $high = $json_data->{'quoteResponse'}{'result'}[0]{'regularMarketDayHigh'}{'fmt'}; if ($high) { $high =~ s/,//g; if ($currency =~ /^GBp/) { $high = $high / 100; } } my $low = $json_data->{'quoteResponse'}{'result'}[0]{'regularMarketDayLow'}{'fmt'}; if ($low) { $low =~ s/,//g; if ($currency =~ /^GBp/) { $low = $low / 100; } } my $volume = $json_data->{'quoteResponse'}{'result'}[0]{'regularMarketVolume'}{'raw'}; $volume =~ s/,//g if $volume; # regularMarketTime in JSON is seconds since epoch my $tradedate = $json_data->{'quoteResponse'}{'result'}[0]{'regularMarketTime'}{'raw'}; my (undef,undef,undef,$day,$month,$year,undef,undef,undef) = localtime($tradedate); $month += 1; $year += 1900; ### YahooWeb Result: $last $info{ $symbol, 'last'} = $last; $info{ $symbol, 'open'} = $open; $info{ $symbol, 'high'} = $high; $info{ $symbol, 'low'} = $low; $info{ $symbol, 'volume'} = $volume unless $volume eq "-"; $quoter->store_date(\%info, $symbol, {month => $month, day => $day, year => $year}); $info{ $symbol, 'symbol' } = $symbol; $info{ $symbol, 'method' } = 'yahooweb'; $info{ $symbol, 'success' } = 1; } $ua->agent($agent); return wantarray ? %info : \%info; } 1; =head1 NAME Finance::Quote::YahooWeb - Obtain quotes from https://finance.yahoo.com/quote =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new('YahooWeb'); %info = $q->fetch('yahooweb', "IBM", "AAPL"); =head1 DESCRIPTION This module fetches information from https://finance.yahoo.com/quote. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "YahooWeb" in the argument list to Finance::Quote->new(). This module provides the "yahooweb" fetch method. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::YahooWeb : symbol name exchange currency isodate last open high low volume =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/TesouroDireto.pm�����������������������������������������������0000644�0001750�0001750�00000006743�15003302667�022320� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # $Id: $ package Finance::Quote::TesouroDireto; require 5.10.1; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use HTTP::Request::Common; use JSON; our $VERSION = '1.65'; # VERSION sub methods { return (tesouro_direto => \&tesouro); } sub labels { return (tesouro_direto => [qw/exchange date isodate symbol name price last method currency/]); } sub tesouro { my $quoter = shift; my @funds = @_; return unless @funds; my $ua = $quoter->user_agent; my (%fundsymbol, %fundhash, @q, %info); # create hash of all funds requested foreach my $fund (@funds) { $fundhash{$fund} = 0; } my $url = "https://raw.githubusercontent.com/ghostnetrn/bot-tesouro-direto/main/tesouro.json"; my $response = $ua->request(GET $url); if ($response->is_success) { my $data = decode_json($response->content)->{'response'}; my $quote_date = substr($data->{'TrsrBondMkt'}{'qtnDtTm'},0,10); my @bounds_list = @{$data->{'TrsrBdTradgList'}}; foreach(@bounds_list) { my $quote_name = $_->{'TrsrBd'}{'nm'}; if (exists $fundhash{$quote_name}) { $fundhash{$quote_name} = 1; $info{$quote_name, "exchange"} = "Tesouro Direto"; $info{$quote_name, "name"} = $quote_name; $info{$quote_name, "symbol"} = $quote_name; $info{$quote_name, "price"} = $_->{'TrsrBd'}{'untrRedVal'}; $info{$quote_name, "last"} = $_->{'TrsrBd'}{'untrRedVal'}; $quoter->store_date(\%info, $quote_name, {isodate => $quote_date}); $info{$quote_name, "method"} = "tesouro_direto"; $info{$quote_name, "currency"} = "BRL"; $info{$quote_name, "success"} = 1; } } # check to make sure a value was returned for every fund requested foreach my $fund (keys %fundhash) { if ($fundhash{$fund} == 0) { $info{$fund, "success"} = 0; $info{$fund, "errormsg"} = "No data returned"; } } } else { foreach my $fund (@funds) { $info{$fund, "success"} = 0; $info{$fund, "errormsg"} = "HTTP error"; } } ### result: %info return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::TesouroDireto - Obtain quotes for Brazilian government bounds =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("tesouro_direto", "Tesouro IPCA+ 2045"); =head1 DESCRIPTION This module obtains quotes for Brazilian government bounds, obtained from https://www.tesourodireto.com.br/titulos/precos-e-taxas.htm =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::TesouroDireto: exchange, name, symbol, date, price, last, method, currency. =head1 SEE ALSO =cut �����������������������������Finance-Quote-1.65/lib/Finance/Quote/IndiaMutual.pm�������������������������������������������������0000644�0001750�0001750�00000013561�15003302667�021721� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # Version 0.1 preliminary version using Cdnfundlibrary.pm v0.4 as an example package Finance::Quote::IndiaMutual; use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use vars qw( $AMFI_URL $AMFI_NAV_LIST $AMFI_MAIN_URL); use LWP::UserAgent; use HTTP::Request::Common; use HTTP::Status; use IO::String; our $VERSION = '1.65'; # VERSION # URLs of where to obtain information. $AMFI_MAIN_URL = ("http://www.amfiindia.com/"); $AMFI_URL = ("https://www.amfiindia.com/spages/NAVAll.txt"); sub methods { return (indiamutual => \&amfiindia, amfiindia => \&amfiindia); } { my @labels = qw/method source link name currency date isodate nav/; sub labels { return (indiamutual => \@labels, amfiindia => \@labels); } } # # ======================================================================= sub amfiindia { my $quoter = shift; my @symbols = @_; # Make sure symbols are requested return unless @symbols; # Local Variables my %fundquote; my($ua, $url, $reply); my($req, $output); # Added for retrieving file contents to variable $url = "$AMFI_URL"; # Code to read file into variable $ua = $quoter->get_user_agent(); $ua->agent(""); # make user_agent as empty for a conducive server response $req = HTTP::Request->new(GET => $url); #done to avoid downloading file $reply = $ua->request($req); # Make sure something is returned unless ($reply->is_success or $reply->code == RC_NOT_MODIFIED) { foreach my $symbol (@symbols) { $fundquote{$symbol,"success"} = 0; $fundquote{$symbol,"errormsg"} = "HTTP failure"; } return wantarray ? %fundquote : \%fundquote; } # Attach body of response to IO::String object for file-like processing # my $nav_fh = IO::String->new($reply->content) ; # Create a hash of all stocks requested my %symbolhash; foreach my $symbol (@symbols) { $symbolhash{$symbol} = 0; } # Read whole file from variable into array $output = $reply->content; my @array = split("\n", $output); my @words; #Scheme Code;ISIN Div Payout/ ISIN Growth;ISIN Div Reinvestment;Scheme Name;Net Asset Value;Date # Note it is best to use Scheme Code as not all rows have ISINs in the source file foreach (@array) { next if !/\;/; chomp; s/\r//; @words = split(";", $_); #the delimiter is ; not a , my ($symbol1, $symbol2, $symbol3, $name, $nav, $date); $symbol1 = $words[0]; $symbol2 = $words[1]; $symbol3 = $words[2]; my $symbol; if (exists $symbolhash{$symbol1}) { $symbol = $symbol1; } elsif(exists $symbolhash{$symbol2}) { $symbol = $symbol2; } elsif(exists $symbolhash{$symbol3}) { $symbol = $symbol3; } else { next; } $fundquote{$symbol, "symbol"} = $symbol; $fundquote{$symbol, "currency"} = "INR"; $fundquote{$symbol, "source"} = $AMFI_MAIN_URL; $fundquote{$symbol, "link"} = $url; $fundquote{$symbol, "method"} = "amfiindia"; $fundquote{$symbol, "name"} = $words[3]; $fundquote{$symbol, "nav"} = $words[4]; $quoter->store_date(\%fundquote, $symbol, {eurodate => $words[5]}); $fundquote{$symbol, "success"} = 1; } foreach my $symbol (@symbols) { unless (exists $fundquote{$symbol, 'success'}) { $fundquote{$symbol, 'success'} = 0; $fundquote{$symbol, 'errormsg'} = 'Fund not found.'; } } return wantarray ? %fundquote : \%fundquote; } 1; =head1 NAME Finance::Quote::IndiaMutual - Obtain Indian mutual fund prices from amfiindia.com =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; # Can failover to other methods %stockinfo = $q->fetch("indiamutual", "amfiindia-code"); # Use this module only %stockinfo = $q->fetch("amfiindia", "amfiindia-code"); # NOTE: currently no failover methods exist for indiamutual =head1 DESCRIPTION This module obtains information about Indian Mutual Fund prices from the Association of Mutual Funds India website amfiindia.com. The information source "indiamutual" can be used if the source of prices is irrelevant, and "amfiindia" if you specifically want to use information downloaded from amfiindia.com. =head1 AMFIINDIA-CODE/ISIN In India, not all funds have an ISIN. However, they do have a scheme code. You can use those if you can't find the ISIN. See AMFI site for details. http://www.amfiindia.com/nav-history-download =head1 LABELS RETURNED Information available from amfiindia may include the following labels: =over =item method =item link =item source =item name =item currency =item nav =back The link label will be a url location for the NAV list table for all funds. =head1 NOTES AMFI provides a link to download a text file containing all the L<NAVs|https://www.amfiindia.com/spages/NAVAll.txt>. It is processed in memory using the L<IO::String> Perl module. =head1 SEE ALSO AMFI india website - http://www.amfiindia.com/ Finance::Quote =cut �����������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Consorsbank.pm�������������������������������������������������0000644�0001750�0001750�00000033106�15003302667�021764� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # Copyright (C) 2023, Stephan Gambke <s7eph4n@gmail.com> # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA require 5.005; use strict; use warnings; package Finance::Quote::Consorsbank; use LWP::UserAgent; use JSON qw( decode_json ); use Date::Parse qw(str2time); use POSIX qw(strftime); use Encode qw(encode_utf8); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use if DEBUG, 'Data::Dumper'; our $VERSION = '1.65'; # VERSION my $CONSORS_URL = 'https://www.consorsbank.de/web-financialinfo-service/api/marketdata/stocks?'; my $CONSORS_SOURCE_BASE_URL = 'https://www.consorsbank.de/web/Wertpapier/'; our $DISPLAY = 'Consorsbank - Consorsbank API'; our $FEATURES = { 'EXCHANGE' => 'select market place (i.e. "gettex", "Xetra", "Tradegate")' }; # Correspondence of FQ labels to Consorsbank API fields # success Did the stock successfully return information? (true/false) # errormsg Info.Errors.ERROR_MESSAGE If success is false, this field may contain the reason why. # symbol Info.ID.SYMBOL ticker symbol # wkn Info.ID.WKN WKN # symbol Info.ID.ISIN ISIN # name BasicV1.NAME_SECURITY Company or Mutual Fund Name # method 'consorsbank' The module (as could be passed to fetch) which found this information. # source Source URL, either general website or direct human-readable deep link # exchange CONSORS_EXCHANGE_NAME The exchange the information was obtained from. # currency ISO_CURRENCY ISO currency code # ask ASK Ask # avg_vol Average Daily Vol # bid BID Bid # cap Market Capitalization # close PREVIOUS_LAST Previous Close # date DATETIME_PRICE Last Trade Date (MM/DD/YY format) # day_range HIGH, LOW Day's Range # div Dividend per Share # div_date Dividend Pay Date # div_yield Dividend Yield # eps Earnings per Share # ex_div Ex-Dividend Date. # high HIGH Highest trade today # last PRICE Last Price # low LOW Lowest trade today # nav Net Asset Value # net PERFORMANCE Net Change # open FIRST Today's Open # p_change PERFORMANCE_PCT Percent Change from previous day's close # pe P/E Ratio # time DATETIME_PRICE Last Trade Time # type The type of equity returned # volume TOTAL_VOLUME Volume # year_range HIGH_PRICE_1_YEAR - LOW_PRICE_1_YEAR 52-Week Range # yield Yield (usually 30 day avg) our @LABELS = qw/ symbol wkn isin name method source exchange exchanges currency ask bid date day_range high last low net open close p_change volume year_range /; our $METHODHASH = {subroutine => \&consorsbank, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub methodinfo { return ( consorsbank => $METHODHASH, europe => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub consorsbank { # a Finance::Quote object my Finance::Quote $quoter = shift; # a list of zero or more symbol names my @symbols = @_ or return; # user_agent() provides a ready-to-use LWP::UserAgent my $ua = $quoter->user_agent; my %info; my %mapping = (# in QuotesV1, PriceV2, ExchangesV2 'exchange' => 'CONSORS_EXCHANGE_NAME', 'currency' => 'ISO_CURRENCY', # also 'UNIT_PRICE' exists 'p_change' => 'PERFORMANCE_PCT'); my %map_price_v2 = (# QuotesV1 & PriceV2 keys 'ask' => 'ASK', 'bid' => 'BID', 'close' => 'PREVIOUS_LAST', 'open' => 'FIRST', 'high' => 'HIGH', 'last' => 'PRICE', 'low' => 'LOW', 'net' => 'PERFORMANCE', 'volume' => 'TOTAL_VOLUME'); my %map_quotes_v1 = ( %map_price_v2, # QuotesV1 keys 'year_low' => 'LOW_PRICE_1_YEAR', 'year_high' => 'HIGH_PRICE_1_YEAR'); my %map_exchanges_v2 = (# ExchangesV2 keys 'last' => 'PRICE', 'ask' => 'PRICE_ASK', 'bid' => 'PRICE_BID'); my $labels = $quoter->get_required_labels; $labels = \@LABELS unless(scalar(@$labels)); my $require_quotes_v1 = grep { exists($map_quotes_v1{$_}) and not exists($map_exchanges_v2{$_}) } @$labels; my $require_exchanges = grep { $_ eq 'exchanges' } @$labels; my $exchange = exists $quoter->{module_specific_data}->{consorsbank}->{EXCHANGE} ? $quoter->{module_specific_data}->{consorsbank}->{EXCHANGE} : undef; if ($exchange and not $require_quotes_v1) { %mapping = (%mapping, %map_exchanges_v2); } else { %mapping = (%mapping, %map_quotes_v1); } my %map_basic_v1_id = (# BasicV1.ID keys 'symbol' => 'SYMBOL', 'wkn' => 'WKN', 'isin' => 'ISIN', 'notation_id' => 'ID_NOTATION'); for my $symbol (@symbols) { ### $symbol $info{ $symbol, 'symbol' } = $symbol; $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = ''; my $get_json = sub { my $query = shift; my $response = $ua->get($query); unless ($response->is_success) { $info{ $symbol, 'errormsg' } = "Unable to fetch data from the Consorsbank server for $symbol. Error: " . $response->status_line; return; } unless ($response->header('content-type') =~ m|application/json|i) { $info{ $symbol, 'errormsg' } = "Invalid content-type from Consorsbank server for $symbol. Expected: application/json, received: " . $response->header('content-type'); return; } my $json = encode_utf8($response->content); ### [<here>] $json: ### $json my $data; eval { $data = JSON::decode_json($json) }; if ($@) { $info{ $symbol, 'errormsg' } = "Failed to parse JSON data for $symbol. Error: $@."; ### $@ return; } ### [<here>] $data: ### $data if ( defined $data->[0]{'Info'}{'Errors'} ){ ### API Error: $data->[0]{'Info'}{'Errors'} if ( $data->[0]{'Info'}{'Errors'}[0]{'ERROR_CODE'} eq 'IDMS' ){ $info{ $symbol, 'errormsg' } = "Invalid symbol: $symbol"; } else { $info{ $symbol, 'errormsg' } = $data->[0]{'Info'}{'Errors'}[0]{'ERROR_MESSAGE'} } return; } return $data; }; my ($data, $quote); if ($exchange) { $data = &$get_json($CONSORS_URL . "id=$symbol&field=ExchangesV2&field=BasicV1") or next; $info{ $symbol, 'exchanges' } = [ map { $_->{'CONSORS_EXCHANGE_NAME'} } @{$data->[0]{'ExchangesV2'}} ]; ($quote) = grep { $_->{'CONSORS_EXCHANGE_NAME'} eq $exchange or $_->{'CONSORS_EXCHANGE_CODE'} eq '_' .$exchange } @{$data->[0]{'ExchangesV2'}}; unless($quote) { $info{ $symbol, 'errormsg' } = "Marketplace not found: $exchange"; next; } if ($require_quotes_v1) { my ($id, $code) = ($quote->{'CONSORS_ID'}, $quote->{'CONSORS_EXCHANGE_CODE'}); $data = &$get_json($CONSORS_URL . "id=$id&field=QuotesV1&field=BasicV1&rtExchangeCode=$code") or next; $quote = $data->[0]{'QuotesV1'}[0]; } } else { if ($require_exchanges) { $data = &$get_json($CONSORS_URL . "id=$symbol&field=QuotesV1&field=BasicV1&field=ExchangesV2") or next; $info{ $symbol, 'exchanges' } = [ map { $_->{'CONSORS_EXCHANGE_NAME'} } @{$data->[0]{'ExchangesV2'}} ]; } else { $data = &$get_json($CONSORS_URL . "id=$symbol&field=QuotesV1&field=BasicV1") or next; } $quote = $data->[0]{'QuotesV1'}[0]; #$quote = $data->[0]{'PriceV2'}; } ### [<here>] $symbol: ### $symbol while ((my $fqkey, my $cbkey) = each (%map_basic_v1_id)) { $info{ $symbol, $fqkey } = $data->[0]{'BasicV1'}{'ID'}{$cbkey} if (defined $data->[0]{'BasicV1'}{'ID'}{$cbkey}); } $info{ $symbol, 'name' } = $data->[0]{'BasicV1'}{'NAME_SECURITY'} if (defined $data->[0]{'BasicV1'}{'NAME_SECURITY'}); $info{ $symbol, 'method' } = 'consorsbank'; $info{ $symbol, 'source' } = $CONSORS_SOURCE_BASE_URL . $data->[0]{'Info'}{'ID'}; while ((my $fqkey, my $cbkey) = each (%mapping)) { $info{ $symbol, $fqkey } = $quote->{$cbkey} if (exists $quote->{ $cbkey } and defined $quote->{ $cbkey } ); } # in QuotesV1, PriceV2, ExchangesV2 if (defined($quote->{'DATETIME_PRICE'})) { my $utc_timestamp = str2time($quote->{'DATETIME_PRICE'}); $info{ $symbol, 'time' } = strftime("%H:%M", localtime($utc_timestamp)); # local time zone $quoter->store_date(\%info, $symbol, {isodate => strftime("%Y-%m-%d", localtime($utc_timestamp))}); } $info{ $symbol, 'day_range' } = $info{ $symbol, 'low' } . ' - ' . $info{ $symbol, 'high' } if (exists $info{ $symbol, 'low' } && exists $info{ $symbol, 'high' }); $info{ $symbol, 'year_range' } = $info{ $symbol, 'year_low' } . ' - ' . $info{ $symbol, 'year_high' } if (exists $info{ $symbol, 'year_low' } && exists $info{ $symbol, 'year_high' }); unless (defined $info{ $symbol, 'last'} ) { $info{ $symbol, 'errormsg' } = "The server did not return a price for $symbol."; next; } $info{ $symbol, 'success' } = 1; } ### [<here>] %info: ### %info return wantarray() ? %info : \%info; } 1; __END__ =head1 NAME Finance::Quote::Consorsbank - Obtain quotes from Consorsbank. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; or $q = Finance::Quote->new("Consorsbank", "consorsbank" => { "EXCHANGE" => "Xetra" }); %stockinfo = $q->fetch("consorsbank","DE0007664005"); # Only query consorsbank using ISIN. %stockinfo = $q->fetch("consorsbank","766400"); # Only query consorsbank using WKN. %stockinfo = $q->fetch("europe","DE0007664005"); # Failover to other sources OK. @exchanges = @{ $info{ "DE0007664005", "exchanges" } }; # List of available marketplaces =head1 DESCRIPTION This module obtains information from Consorsbank (https://www.consorsbank.de). It accepts ISIN or German WKN as requested symbol. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "Consorsbank" in the argument list to Finance::Quote->new(). This module provides both the "consorsbank" and "europe" fetch methods. Please use the "europe" fetch method if you wish to have failover with other sources for European stock exchanges. Using the "consorsbank" method will guarantee that your information only comes from the Consorsbank service. =head1 EXCHANGE https://www.consorsbank.de/ supports different market places. A default is not specified. "Xetra" alias "GER" "Tradegate" alias "GAT" "gettex" alias "TRO" "Berlin" alias "BER" ... any many more ... The EXCHANGE may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::Consorsbank: symbol, wkn, isin, name, method, source, exchange, exchanges, currency, ask, bid, date, day_range, high, last, low, net, open, close, p_change, volume, year_range =cut ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/SEB.pm���������������������������������������������������������0000644�0001750�0001750�00000007733�15003302667�020122� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2000, Keith Refson <Keith.Refson@earth.ox.ac.uk> # Copyright (C) 2003, Tomas Carlsson <tc@tompa.nu> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code was derived from the work on the packages Finance::Yahoo::* # package Finance::Quote::SEB; require 5.004; use strict; use vars qw( $SEB_FUNDS_URL); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use HTTP::Request::Common; use utf8; our $VERSION = '1.65'; # VERSION $SEB_FUNDS_URL = 'https://seb.se/pow/fmk/2100/Senaste_fondkurserna.TXT'; sub methods { return (seb_funds => \&seb_funds); } { my @labels = qw/date isodate method source name currency price/; sub labels { return (seb_funds => \@labels); } } sub seb_funds { my $quoter = shift; my @symbols = @_; return unless @symbols; my ($ua, $reply, $url, %funds); $url = $SEB_FUNDS_URL; $ua = $quoter->user_agent; $reply = $ua->request(GET $url); ### url : $url ### reply : $reply unless ($reply->is_success) { foreach my $symbol (@symbols) { $funds{$symbol, "success"} = 0; $funds{$symbol, "errormsg"} = "HTTP failure"; } return wantarray ? %funds : \%funds; } foreach my $line (split /\n/, $reply->content) { chomp($line); # Format: # 2003-08-11;SEB Aktiesparfond;5,605;387 my ($date, $name, $price, $hmm) = split ';', $line; utf8::encode($name); if (grep {$_ eq $name} @symbols) { $price =~ s/,/\./; # change decimal point from , to . $funds{$name, 'symbol'} = $name; $quoter->store_date(\%funds, $name, {isodate => $date}); $funds{$name, 'method'} = 'seb_funds'; $funds{$name, 'source'} = 'Finance::Quote::SEB'; $funds{$name, 'name'} = $name; $funds{$name, 'currency'} = 'SEK'; $funds{$name, 'price'} = $price; $funds{$name, 'success'} = 1; } } # Check for undefined symbols foreach my $symbol (@symbols) { unless ($funds{$symbol, 'success'}) { $funds{$symbol, "success"} = 0; $funds{$symbol, "errormsg"} = "Fund name not found"; } } return %funds if wantarray; return \%funds; } 1; =head1 NAME Finance::Quote::SEB - Obtain fund prices from www.seb.se =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %fundinfo = $q->fetch("seb_funds","fund name"); =head1 DESCRIPTION This module obtains information about SEB fund prices from www.seb.se. The only available information source is "seb_funds" and it will use www.seb.se. =head1 FUND NAMES Unfortunately there is no unique identifier for the fund names. Therefore the complete fund name must be given, including spaces, case is important. Consult https://seb.se/bors-och-finans/fonder/fondkurslista for all available funds. Example "SEB Aktiesparfond" =head1 LABELS RETURNED Information available from SEB may include the following labels: date method source name currency price. The prices are updated at the end of each bank day. =head1 SEE ALSO SEB website - http://www.seb.se/ =cut �������������������������������������Finance-Quote-1.65/lib/Finance/Quote/OnVista.pm�����������������������������������������������������0000644�0001750�0001750�00000023135�15003302667�021066� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2024, Bruce Schuck <bschuck@asgard-systems.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # 2024-10-06 Complete rewrite of module for F::Q issue #414 package Finance::Quote::OnVista; use strict; use warnings; use Encode qw(encode_utf8); use HTML::TreeBuilder; use HTTP::Request::Common; use JSON qw( decode_json ); use Date::Parse qw(str2time); use POSIX qw(strftime); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION my $ONVISTA_URL = 'https://www.onvista.de/suche/'; # Change DISPLAY and method values in code below # Modify LABELS to those returned by the method our $DISPLAY = 'OnVista - Germany'; our $FEATURES = { 'EXCHANGE' => 'select market place (i.e. "GER", "Xetra", "Tradegate")' }; our @LABELS = qw/symbol isin wkn name open close high low last date volume currency exchange method ask bid change p_change time exchanges source/; our $METHODHASH = {subroutine => \&onvista, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub methodinfo { return ( onvista => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub onvista { my $quoter = shift; my @stocks = @_; my (%info, $tree, $url, $reply, $json, $json_decoded); my $ua = $quoter->user_agent(); foreach my $stock (@stocks) { $url = $ONVISTA_URL . $stock; $reply = $ua->request( GET $url); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = $reply->decoded_content; ### Body: $body if ( $code == 200 ) { # Use HTML::TreeBuilder to parse HTML in $body $tree = HTML::TreeBuilder->new; if ($tree->parse($body)) { $tree->eof; unless ( $json = encode_utf8 (($tree->look_down(_tag => 'script', id => '__NEXT_DATA__', type => 'application/json')->content_list())[0]) ) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. No data returned"; next; } ### [<now>] JSON: $json $json_decoded = decode_json $json; ### [<now>] JSON Decoded: $json_decoded my $result_array; if ($json_decoded->{'props'}{'pageProps'}{'data'}{'snapshot'}{'instrument'}) { $result_array = [ $json_decoded->{'props'}{'pageProps'}{'data'}{'snapshot'}{'instrument'} ]; } else { $result_array = $json_decoded->{'props'}{'pageProps'}{'facets'}[0]{'results'}; } ### [<now>] Result Array: $result_array my $item; foreach $item ( @$result_array ) { ### [<now>] Item: $item if ( ($item->{'symbol'} && $item->{'symbol'} eq $stock) or ($item->{'wkn'} && $item->{'wkn'} eq $stock) or ($item->{'isin'} && $item->{'isin'} eq $stock) ) { last; } } # By default set URL to first in array # For US stocks, the symbol may not match stock $item ||= $result_array->[0]; map { $info{ $stock, $_ } = $item->{$_} } qw(symbol wkn isin); $url = $item->{'urls'}{'WEBSITE'}; unless ( $url ) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "No data found for $stock."; next; } ### [<now>] New URL: $url $reply = $ua->request( GET $url); $code = $reply->code; $desc = HTTP::Status::status_message($code); $headers = $reply->headers_as_string; $body = $reply->decoded_content; unless ( $code == 200 ) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error accessing $url ($desc)."; next; } # Create HTML::TreeBuilder object from 2nd URL's body $tree = HTML::TreeBuilder->new; unless ($tree->parse($body)) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error parsing HTML from $url."; next; } $tree->eof; unless ( $json = encode_utf8(($tree->look_down(_tag => 'script', id => '__NEXT_DATA__', type => 'application/json')->content_list())[0]) ) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. No data returned"; next; } ### [<now>] 2nd JSON: $json eval {$json_decoded = decode_json encode_utf8 $json}; if($@) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $@; next; } my $exchange = exists $quoter->{module_specific_data}->{onvista}->{EXCHANGE} ? $quoter->{module_specific_data}->{onvista}->{EXCHANGE} : undef; my $markets = $json_decoded->{'props'}{'pageProps'}{'data'}{'snapshot'}{'quoteList'}{'list'}; $info{ $stock, 'exchanges' } = [ map { $_->{'market'}{'name'} } @$markets ]; my $json_quote; if ($exchange) { ($json_quote) = grep { $_->{'market'}{'name'} eq $exchange or $_->{'market'}{'codeExchange'} eq $exchange } @$markets; unless($json_quote) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. No data returned for $exchange"; next; } } else { $json_quote = $json_decoded->{'props'}{'pageProps'}{'data'}{'snapshot'}{'quote'}; } ### [<now>] 2nd JSON Decoded: $json_decoded $info{ $stock, "success" } = 1; $info{ $stock, 'method' } = 'onvista'; $info{ $stock, 'source' } = $url; $info{ $stock, 'name' } = $json_decoded->{'props'}{'pageProps'}{'data'}{'snapshot'}{'instrument'}{'name'}; map { $info{ $stock, $_ } = $json_quote->{$_} } qw(open high low last volume ask bid); $info{ $stock, 'price' } = $json_quote->{'last'}; $info{ $stock, 'currency' } = $json_quote->{'isoCurrency'}; $info{ $stock, 'exchange' } = $json_quote->{'market'}{'name'}; $info{ $stock, 'close' } = $json_quote->{'previousLast'}; $info{ $stock, 'change' } = $json_quote->{'performance'}; $info{ $stock, 'p_change' } = $json_quote->{'performancePct'}; $quoter->store_date(\%info, $stock, {isodate => substr $json_quote->{'datetimeLast'}, 0, 10}); #$info{ $stock, 'time' } = substr $date, 11, 5; # UTC my $utc_timestamp = str2time($json_quote->{'datetimeLast'}); $info{ $stock, 'time' } = strftime("%H:%M", localtime($utc_timestamp)); # local time zone } else { $tree->eof; $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Could not parse HTML returned from $url."; } } else { # HTTP Request failed (code != 200) $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } # end foreach stock return wantarray() ? %info : \%info; return \%info; } # end onvista subroutine 1; __END__ =head1 NAME Finance::Quote::OnVista - Obtain quotes from Frankfurt Stock Exchange. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; or $q = Finance::Quote->new("OnVista", "onvista" => { "EXCHANGE" => "Xetra" }); %info = $q->fetch("onvista", "sap"); # Only query onvista @exchanges = @{ $info{ "sap", "exchanges" } }; # List of available marketplaces =head1 DESCRIPTION This module fetches information from L<https://onvista.de/>. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "onvista" in the argument list to Finance::Quote->new(). This module provides "onvista" fetch methods. It was written to replace a non-working Tradeville.pm module. Information obtained by this module may be covered by Frankfurt Stock Exchange terms and conditions. =head1 EXCHANGE https://onvista.de/ supports different market places. A default is not specified. "Xetra" alias "GER" "Tradegate" alias "GAT" "gettex" alias "TRO" "London Stock Exchange" alias "LSE" ... any many more ... The EXCHANGE may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 LABELS RETURNED The following labels are returned: =over =item name =item symbol =item isin =item wkn =item open =item close =item high =item low =item price =item bid =item ask =item date =item time =item currency =item change =item p_change =item source =back �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/ASX.pm���������������������������������������������������������0000644�0001750�0001750�00000024267�15003302667�020145� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2001, Leigh Wedding <leigh.wedding@telstra.com> # Copyright (C) 2000-2004, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2014, Chris Good <chris.good@@ozemail.com.au> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code derived from Padzensky's work on package Finance::YahooQuote, # but extends its capabilites to encompas a greater number of data sources. # # This code was developed as part of GnuCash <http://www.gnucash.org/> use strict; use warnings; package Finance::Quote::ASX; use LWP::UserAgent; use JSON qw/decode_json/; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use vars qw/$ASX_URL/; our $VERSION = '1.65'; # VERSION our $DISPLAY = 'ASX - Australian Securities Exchange'; our @LABELS = qw/symbol ask bid p_change net type name last price volume currency method/; our $METHODHASH = {subroutine => \&asx, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( asx => $METHODHASH, australia => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } $ASX_URL = 'https://asx.api.markitdigital.com/asx-research/1.0/companies/'; # Australian Stock Exchange (ASX) # The ASX provides free delayed quotes through their webpage: # https://www2.asx.com.au/markets/company/NAB # # Maintainer of this section is Paul Fenwick <pjf@cpan.org> # 5-May-2001 Updated by Leigh Wedding <leigh.wedding@telstra.com> # 24-Feb-2014 Updated by Chris Good <chris.good@@ozemail.com.au> # 12-Oct-2020 Updated by Jeremy Volkening # # Jan-2021 Updated by Geoff <cleanoutmyshed@gmail.com> # October 2020 the ASX revamped their website with dynamic content for quotes # which prevented the previous HTML screen scraping from working, but exposed # a number of JSON data sources, two of which are used here. # The primary source returns data elements for almost all securities, but # does not return prices for certain security types (some bonds and exchange # traded products, options, and warrants), and returns an error for indices. # The alternate source returns less data elements, but provides usable quote # data for all the known exceptions, including indices. # This version will always call the primary source, and call the alternate if # a price is not returned by the primary. # # 2024-10-21 Updated by Bruce Schuck <bschuck@asgard-systems.com> # The primary URL (https://www.asx.com.au/asx/1/share/) stopped retrieving # data. The website is utilizing the Imperva Incapsula, an anti-webscraping # service. A caveat of using the alternate URL is that only a few # data points are returned and no trade date. # # Smart::Comments implemented to conform with the Hackers Guide: # https://github.com/finance-quote/finance-quote/blob/master/Documentation/Hackers-Guide # # Main function to fetch quotes from the Australian Securities Exchange (ASX) sub asx { my $quoter = shift; my @symbols = @_ or return; my($error, %info, $status, $ua, $url); $ua = $quoter->user_agent; for my $symbol (@symbols) { ### ASX.pm Processing symbol: $symbol $info{ $symbol, 'symbol' } = $symbol; $info{ $symbol, 'method' } = 'asx'; $info{ $symbol, 'currency' } = 'AUD'; $symbol =~ s/\s+$//; if ($symbol !~ m/^[A-Za-z0-9]{1,6}$/) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = 'Invalid symbol. ASX symbols must be alpha numeric maximum length 6 characters.'; ### ASX.pm: $info{ $symbol, 'errormsg' } next; } ($status, $error) = asx_fetch($symbol, $ua, \%info); if ($status != 1) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = "$error"; ### ASX.pm Unsuccessful call to ASX URL - symbol cannot be processed: $symbol next; } ### ASX.pm We have valid data, apply various clean ups and add remaining data for symbol: $symbol # Remove trailing percentage sign from p_change $info{ $symbol, 'p_change' } =~ s/\%$//; $info{ $symbol, 'price' } = $info{ $symbol, 'last' }; if ((exists $info{ $symbol, 'date' }) && ($info{ $symbol, 'date' } =~ m/([0-9]{4}-[0-9]{2}-[0-9]{2})T/)) { $quoter->store_date(\%info, $symbol, {isodate => $1}); ### ASX.pm Converted Last Trade Date to ISO format: "$info{ $symbol, 'date' } --> $1" } # Technically indices don't have a currency, but it is not possible to distinguish them $info{ $symbol, 'currency' } = 'AUD'; $info{ $symbol, 'success' } = 1; $info{ $symbol, 'errormsg' } = ''; } ### ASX.pm Returning data for all symbols to Finance-Quote and exiting <file>[<line>] return %info if wantarray; return \%info; } # end main asx method # Internal function to handle ASX data source sub asx_fetch { my ($symbol, $ua, $info) = @_; my($data, $error, %label_map, $status, $url); $url = $ASX_URL . $symbol . '/header'; ($status, $error, $data) = get_asx_data($url, $ua); return $status, $error unless $status == 1; if (exists $data->{error}) { $status = 0; $error = "Error returned by ASX server '$url'. Code: " . $data->{error}{code} . ' Message: ' . $data->{error}{message}; ### ASX.pm Error: $error return $status, $error; } if (! exists $data->{data}) { $status = 0; $error = "Cannot parse content from ASX server '$url'. Expected a top level JSON element named data."; ### ASX.pm Error: $error return $status, $error; } # Map the Finance::Quote labels (left) to the corresponding ASX labels (right) %label_map = ( 'name' => 'displayName', 'ask' => 'priceAsk', 'bid' => 'priceBid', 'net' => 'priceChange', 'p_change' => 'priceChangePercent', 'last' => 'priceLast', 'type' => 'securityType', 'volume' => 'volume', ); process_asx_data($symbol, $data->{data}, \%label_map, $info); return 1, ''; } # end asx_fetch # Internal function to fetch, validate, and decode data from an ASX URL using LWP User Agent # Handle any errors sub get_asx_data { my ($url, $ua) = @_; my($data, $error, $json, $response, $status); ### ASX.pm Retrieving data from ASX URL: $url $response = $ua->get($url); if (! $response->is_success) { $status = 0; $error = "Unable to fetch data from the ASX server '$url'. Status: " . $response->status_line; ### ASX.pm Error: $error return $status, $error, undef; } if ($response->header('content-type') !~ m|application/json|i) { $status = 0; $error = "Invalid content-type from ASX server '$url'. Expected: application/json, received: " . $response->header('content-type'); ### ASX.pm Error: $error return $status, $error, undef; } $json = $response->content; # The JSON module will croak on errors, so use eval to trap this. $data = eval{ decode_json($json) }; if ($@) { $status = 0; $error = "Failed to parse JSON data from ASX server '$url'. Error: '$@'."; ### ASX.pm Error: $error return $status, $error, undef; } # Return valid, decoded data $status = 1; return $status, $error, $data; } # end get_asx_data # Internal function to push the ASX data elements into the Finance::Quote structure (%info) sub process_asx_data { my ($symbol, $data, $label_map, $info) = @_; foreach my $label (sort(keys %{$label_map})) { if ((exists $data->{$label_map->{$label}}) && (defined $data->{$label_map->{$label}})) { # Concatenate Primary and Alternate Names if (($label eq 'name') && (exists $info->{$symbol, $label}) && (uc($info->{$symbol, $label}) ne uc($data->{$label_map->{$label}}))) { $info->{$symbol, $label} = $data->{$label_map->{$label}} . ' ' . $info->{$symbol, $label}; } # Overwrite all other labels else { $info->{$symbol, $label} = $data->{$label_map->{$label}}; } ### ASX.pm Mapped ASX data element to Finance-Quote: sprintf("%-22s%-15s%-s", $label_map->{$label}, $label, $data->{$label_map->{$label}}) } else { $info->{$symbol,$label} = ''; } } return; } # end process_asx_data 1; __END__ =head1 NAME Finance::Quote::ASX - Obtain quotes from the Australian Stock Exchange. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("asx","BHP"); # Only query ASX. %stockinfo = $q->fetch("australia","BHP"); # Failover to other sources OK. =head1 DESCRIPTION This module obtains information from the Australian Stock Exchange http://www.asx.com.au/. Data for all Australian listed securities and indices is available. Indexes start with the letter 'X'. For example, the All Ordinaries is "XAO". But some securities also start with the letter 'X'. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "ASX" in the argument list to Finance::Quote->new(). This module provides both the "asx" and "australia" fetch methods. Please use the "australia" fetch method if you wish to have failover with other sources for Australian stocks (such as Yahoo). Using the "asx" method will guarantee that your information only comes from the Australian Stock Exchange. Information returned by this module is governed by the Australian Stock Exchange's terms and conditions. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::ASX: bid, offer, open, high, low, last, net, p_change, volume, and price. =head1 SEE ALSO Australian Stock Exchange, http://www.asx.com.au/ Finance::Quote::Yahoo::Australia. =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/BVB.pm���������������������������������������������������������0000644�0001750�0001750�00000012630�15003302667�020112� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # Written as a replacement for Tradeville.pm. package Finance::Quote::BVB; use strict; use warnings; use POSIX qw(strftime); use LWP::UserAgent; use LWP::Simple; use HTTP::Status; use IO::String; use HTTP::Request::Common; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION use vars qw($BVB_URL); $BVB_URL = "https://bvb.ro/TradingAndStatistics/Trading/HistoricalTradingInfo.ashx?day="; sub methods { return (bvb => \&bvb, romania => \&bvb, tradeville => \&bvb, europe => \&bvb); } sub labels { my @labels = qw/symbol name market trades volume value open low high avg close refprice var date/; return (bvb => \@labels, romania => \@labels, tradeville => \@labels, europe => \@labels); } sub bvb { my $quoter = shift; my @symbols = @_; return unless @symbols; my (%info, $errormsg, $ua, $req, $date, $reply); my @array; my $meuradate; $ua = $quoter->user_agent; # Set the ua to be blank. Server blocks default useragent. $ua->agent(''); # Try to fetch last 10 days historical data file for (my ($days, $now) = (0, time()); $days < 10; $days++) { # Ex: https://bvb.ro/TradingAndStatistics/Trading/HistoricalTradingInfo.ashx?day=20240809 my @lt = localtime($now - $days*24*60*60); my ($url, $output); # added $req, $output for fileless $date = strftime "%Y%m%d", @lt; $url = sprintf("https://bvb.ro/TradingAndStatistics/Trading/HistoricalTradingInfo.ashx?day=%s", $date); $req = get($url); #added for fileless die "Couldn't get data!" unless defined $req; if ($req ne "") { last; } } #Set the date to the date of the last available historical file date $meuradate = $date; #Remove quotation marks around every field in the file. $req =~ s/"//g; #If the file is empty, die with error if ($req eq "") { die "Can't GET data"; } @array = split("\n", $req); if ($errormsg) { foreach my $symbol (@symbols) { $info{$symbol, "success"} = 0; $info{$symbol, "errormsg"} = $errormsg; } return wantarray() ? %info : \%info; } # Create a hash of all stocks requested my %symbolhash; foreach my $symbol (@symbols) { $symbolhash{$symbol} = 0; } my $csvhead; my @headhash; # "Symbol","Name","Market","Trades","Volume","Value","Open","Low","High","Avg.","Close","Ref. price","Var (%)" $csvhead = $array[0]; @headhash = split(/\s*,s*/, $csvhead); foreach (@array) { my @data = split(",", $_); my %datahash; my $symbol; @datahash{@headhash} = @data; if (exists $symbolhash{$datahash{"Symbol"}}) { $symbol = $datahash{"Symbol"}; } else { next; } $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'close'} = $datahash{"Close"}; $info{$symbol, 'last'} = $datahash{"Close"}; $info{$symbol, 'high'} = $datahash{"High"}; $info{$symbol, 'low'} = $datahash{"Low"}; $info{$symbol, 'open'} = $datahash{"Open"}; $info{$symbol, 'name'} = $datahash{"Name"}; $quoter->store_date(\%info, $symbol, {isodate => $meuradate}); $info{$symbol, 'method'} = 'bvb'; $info{$symbol, 'currency'} = 'RON'; $info{$symbol, 'exchange'} = 'BVB'; $info{$symbol, 'success'} = 1; } foreach my $symbol (@symbols) { unless (exists $info{$symbol, 'success'}) { ### Not Found: $symbol $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Stock not found on BVB.'; } } return wantarray() ? %info : \%info; return \%info; } 1; __END__ =head1 NAME Finance::Quote::BVB - Obtain quotes from Bucharest Stock Exchange. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch("bvb", "tlv"); # Only query bvb %info = $q->fetch("romania", "brd"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from L<https://bvb.ro/>. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "bvb" in the argument list to Finance::Quote->new(). This module provides "bvb", "tradeville", "romania", and "europe" fetch methods. It was written use historical trade data file posted by BVB on their site. Information obtained by this module may be covered by Bucharest Stock Exchange terms and conditions. =head1 LABELS RETURNED The following labels are returned: =over =item * name =item * symbol =item * open =item * high =item * low =item * price =item * bid =item * ask =item * date =item * currency (always RON) =back ��������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Finanzpartner.pm�����������������������������������������������0000644�0001750�0001750�00000010543�15003302667�022323� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Finance::Quote Perl module to retrieve quotes from Finanzpartner.de # Copyright (C) 2007 Jan Willamowius <jan@willamowius.de> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA package Finance::Quote::Finanzpartner; use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Web::Scraper; use Encode; our $VERSION = '1.65'; # VERSION my $FINANZPARTNER_URL = "https://www.finanzpartner.de/fi/"; sub methods {return (finanzpartner => \&finanzpartner);} sub labels { return (finanzpartner=>[qw/name date price last method/]); } # TODO sub finanzpartner { my $quoter = shift; # The Finance::Quote object. my @stocks = @_; my $ua = $quoter->user_agent(); my %info; foreach my $stock (@stocks) { eval { my @headers = ( "authority" => "www.finanzpartner.de", "sec-ch-ua" => '"Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"', "sec-ch-ua-mobile" => "?0", "upgrade-insecure-requests" => "1", "user-agent" => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-language" => "en-US,en;q=0.9", "sec-ch-ua" => "\"Google Chrome\";v=\"87\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"87\"", "sec-fetch-dest" => "document", "sec-fetch-mode" => "navigate", "sec-fetch-site" => "none", ); my $url = $FINANZPARTNER_URL . $stock . '/'; ### url : $url my $reply = $ua->get($url, @headers); my $processor = scraper { process 'span.kurs-m.pull-left', 'price[]' => 'TEXT'; process 'span.kurs.pull-left', 'price_alternative[]' => 'TEXT'; process 'h1 > small', 'isin[]' => 'TEXT'; process 'div.col-md-2', 'date[]' => 'TEXT'; process 'h1 > span', 'name[]' => 'TEXT'; }; my $data = $processor->scrape(decode_utf8 $reply->content); # If price does not exists, then price_alternative should exist. In that case, put price_alternative into price. if(exists $data->{price_alternative}) { $data->{price} = $data->{price_alternative}; } ### data: $data die "Unexpected price format" unless exists $data->{price} and $data->{price}->[0] =~ /^([0-9.]+) ([A-Z]+)$/; $info{$stock, "last"} = $1; $info{$stock, "currency"} = $2; die "Unexpected date format" unless exists $data->{date} and $data->{date}->[0] =~ /([0-9]{2}[.][0-9]{2}[.][0-9]{4})$/; $quoter->store_date(\%info, $stock, {eurodate => $1}); $info{$stock,"method"} = "finanzpartner"; $info{$stock,"symbol"} = $stock; $info{$stock,"success"} = 1; }; if ($@) { $info{$stock,"errormsg"} = $@; $info{$stock,"success"} = 0; } } return wantarray ? %info : \%info; } 1; =head1 NAME Finance::Quote::Finanzpartner - Obtain quotes from Finanzpartner.de. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new("Finanzpartner"); %info = $q->fetch("finanzpartner","LU0055732977"); =head1 DESCRIPTION This module obtains quotes from Finanzpartner.de (http://www.finanzpartner.de) by WKN or ISIN. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::Finanzpartner: name, date, price, last, method. =head1 SEE ALSO Finanzpartner, http://www.finanzpartner.de/ Finance::Quote; =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/BSEIndia.pm����������������������������������������������������0000644�0001750�0001750�00000012644�15003302667�021064� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::BSEIndia; use strict; use POSIX qw(strftime); #use IO::Uncompress::Unzip qw(unzip $UnzipError); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION use vars qw($BSE_URL); $BSE_URL = "https://www.bseindia.com"; sub methods { return ( 'india' => \&bseindia, 'bseindia' => \&bseindia ); } sub labels { my @labels = qw/close last high low open prevclose exchange name/; return ( india => \@labels, bseindia => \@labels ); } sub bseindia { my $quoter = shift; my @symbols = @_; return unless @symbols; my (%info, $errormsg, $fh, $ua, $url, $reply); my $output; my @array; my $meuradate; $ua = $quoter->user_agent; # Set the ua to be blank. Server blocks default useragent. $ua->agent(''); # Try to fetch last 10 days for (my ($days, $now) = (0, time()); $days < 10; $days++) { # Ex: https://www.bseindia.com/download/BhavCopy/Equity/BhavCopy_BSE_CM_0_0_0_20240718_F_0000.CSV my @lt = localtime($now - $days*24*60*60); my ($date, $url, $req, $output); # added $req, $output for fileless $date = strftime "%Y%m%d", @lt; $url = sprintf("https://www.bseindia.com/download/BhavCopy/Equity/BhavCopy_BSE_CM_0_0_0_%s_F_0000.CSV", $date); $req = HTTP::Request->new(GET => $url); #added for fileless $reply = $ua->request($req); #print "$url", $reply->is_success, $reply->status_line, "\n"; #DEBUG if ($reply->is_success or $reply->code == 304) { last; } } if (!$reply->is_success and $reply->code != 304) { $errormsg = "HTTP failure : " . $reply->status_line; } if (!$errormsg) { #Does not use temp files. Fileless into variable $output #There is no zip file anymore #@array = split("\n", $output); @array = split("\n", $reply->content); } if ($errormsg) { foreach my $symbol (@symbols) { $info{$symbol, "success"} = 0; $info{$symbol, "errormsg"} = $errormsg; } return wantarray() ? %info : \%info; } # Create a hash of all stocks requested my %symbolhash; foreach my $symbol (@symbols) { $symbolhash{$symbol} = 0; } my $csvhead; my @headhash; # TradDt,BizDt,Sgmt,Src,FinInstrmTp,FinInstrmId,ISIN,TckrSymb,SctySrs,XpryDt,FininstrmActlXpryDt,StrkPric,OptnTp,FinInstrmNm,OpnPric,HghPric,LwPric,ClsPric,LastPric,PrvsClsgPric,UndrlygPric,SttlmPric,OpnIntrst,ChngInOpnIntrst,TtlTradgVol,TtlTrfVal,TtlNbOfTxsExctd,SsnId,NewBrdLotQty,Rmks,Rsvd1,Rsvd2,Rsvd3,Rsvd4 $csvhead = $array[0]; @headhash = split /\s*,s*/, $csvhead; foreach (@array) { my @data = split(",", $_); my %datahash; my $symbol; @datahash{@headhash} = @data; if (exists $symbolhash{$datahash{"FinInstrmId"}}) { $symbol = $datahash{"FinInstrmId"}; } elsif(exists $symbolhash{$datahash{"ISIN"}}) { $symbol = $datahash{"ISIN"}; } else { next; } $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'close'} = $datahash{"ClsPric"}; $info{$symbol, 'last'} = $datahash{"LastPric"}; $info{$symbol, 'high'} = $datahash{"HghPric"}; $info{$symbol, 'low'} = $datahash{"LwPric"}; $info{$symbol, 'open'} = $datahash{"OpnPric"}; $info{$symbol, 'prevclose'} = $datahash{"PrvsClsgPric"}; $info{$symbol, 'name'} = $datahash{"FinInstrmNm"}; $quoter->store_date(\%info, $symbol, {isodate => $datahash{"TradDt"}}); $info{$symbol, 'method'} = 'bseindia'; $info{$symbol, 'currency'} = 'INR'; $info{$symbol, 'exchange'} = 'BSE'; $info{$symbol, 'success'} = 1; } foreach my $symbol (@symbols) { unless (exists $info{$symbol, 'success'}) { ### Not Found: $symbol $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Stock not found on BSE.'; } } return wantarray ? %info : \%info; } 1; =head1 NAME Finance::Quote::BSEIndia - Obtain quotes from BSE (India). =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(); %info = $q->fetch('bseindia', 'INE001A01036'); # Only query BSE. %info = $q->fetch('india', 'INE001A01036'); # Failover to other sources OK. =head1 DESCRIPTION This module obtains information about shares listed on the BSE (India). Source is the daily bhav copy (zipped CSV). This module provides both the "bseindia" and "india" fetch methods. Please use the "india" fetch method if you wish to have failover with other sources for Indian stocks (such as NSE). =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::BSEIndia: close, last, high, low, open, prevclose, exchange, name =head1 SEE ALSO BSE (formerly known as Bombay Stock Exchange Ltd.), http://www.bseindia.com/ =cut ��������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Stooq.pm�������������������������������������������������������0000644�0001750�0001750�00000020412�15003302667�020603� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::Stooq; use strict; use warnings; use Encode qw(decode encode); use LWP::UserAgent; use HTTP::Request::Common; use HTTP::CookieJar::LWP (); use HTML::TableExtract; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION our $DISPLAY = 'Stooq - Warsaw Stock Exchange'; our @LABELS = qw/symbol name open high low last bid ask date currency method/; our $METHODHASH = {subroutine => \&stooq, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( stooq => $METHODHASH, europe => $METHODHASH, poland => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } my $STOOQ_URL = 'https://stooq.com/q/?s='; my %currencies_by_link = ( '?i=21' => "EUR", # Europe (€) '?i=23' => "GBP", # United Kingdom (£) '?i=25' => "HKD", # Hong Kong (HK$) '?i=30' => "HUF", # Hungary (Ft) '?i=39' => "JPY", # Japan (¥) '?i=60' => "PLN", # Poland (zł) '?i=77' => "USD", # United States ($) ); my %currencies_by_symbol = ( '£' => "GBP", # United Kingdom (£) 'p.' => "GBX", # United Kingdom (penny) '€' => "EUR", # Europe (€) "z\x{142}" => "PLN", # Poland (zł) '$' => "USD", # United States ($) '¢' => "USX", # United States (¢) 'HK$' => "HKD", # Hong Kong (HK$) '¥' => "JPY", # Japan (¥) 'Ft' => "HUF", # Hungary (Ft) ); ### [<now>] Currencies by Symbol: %currencies_by_symbol sub stooq { my $quoter = shift; my @stocks = @_; my (%info, $tree, $table, $pricetable, $url, $reply); my $cj = HTTP::CookieJar::LWP->new; # my $ua = LWP::UserAgent->new(cookie_jar => $cj); my $ua = $quoter->user_agent(); $ua->cookie_jar($cj); $ua->default_header('Accept-Encoding' => 'deflate'); $ua->default_header('Accept-Language' => 'en-US,en;q=0.5'); foreach my $stock (@stocks) { $url = $STOOQ_URL . $stock; $reply = $ua->request( GET $url ); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = $reply->decoded_content; ### Body: $body my ($name, $bid, $ask, $last, $open, $high, $low, $date, $currency); my ($te, $table); $info{ $stock, "symbol" } = $stock; if ( $code == 200 ) { # Use HTML::TableExtract to parse HTML in $body # The table with the security name is the only table # with bgcolor=e9e9e9 style=z-index:1 $te = HTML::TableExtract->new( attribs => { bgcolor => 'e9e9e9', style => 'z-index:1' } ); if (($te->parse($body)) && ($table = $te->first_table_found)) { ### NameTable Rows: $table->rows() ($name) = $table->cell(0,1) =~ m|^.*?(\w.*)$|; $te->eof; } # The table with the price data is the only table with # attribute id='t1' $te = HTML::TableExtract->new( keep_html => 1, attribs => { id => 't1' } ); if (($te->parse($body)) && ($table = $te->first_table_found)) { (my $last) = $table->cell(0,0) =~ m|^.+>([\d\.]+)<|; # usually currency is embedded in an A tag # curency default: td > b[> span_with_price] + " " + _a_linking_to_currency # curency USD/HUF: td > b > _a_linking_to_currency + " " + span_with_price # except for commodities there's no A tag: # commodities: td > b[> span_with_price] + " _currency_without_link_" ### [<now>] Cell 0,0: $table->cell(0,0) (my $currlink) = $table->cell(0,0) =~ m|<a href=t/(\?i=\d+)>|; if ( ($currlink) && ($currencies_by_link{$currlink}) ) { $currency = $currencies_by_link{$currlink}; } else { (my $currsymbol) = $table->cell(0,0) =~ m#[\d\.]+</span></b> (.+)/(ozt|lb|t|gal|bbl|bu|mmBtu)#; ### [<now>] CurrSymbol: $currsymbol if ( ($currsymbol) && ($currencies_by_symbol{$currsymbol}) ) { $currency = $currencies_by_symbol{$currsymbol}; } } ### [<now>] Currency: $currency (my $date) = $table->cell(0,1) =~ m|Date.+>(\d{4}-\d{2}-\d{2})<|; (my $high, my $low) = $table->cell(1,1) =~ m|.+>([\d\.]+)<.+>([\d\.]+)<|; (my $open) = $table->cell(3,0) =~ m|Open.+>([\d\.]+)<|; (my $bid) = $table->cell(4,0) =~ m|Bid.+>([\d\.]+)<|; (my $ask) = $table->cell(4,1) =~ m|Ask.+>([\d\.]+)<|; # If last and date are defined, save values in hash if ( ($last) && ($date) && ($currency) ) { $info{ $stock, 'success' } = 1; $info{ $stock, 'method' } = 'stooq'; $info{ $stock, 'name' } = $name; $info{ $stock, 'last' } = $last; $info{ $stock, 'currency' } = $currency; $info{ $stock, 'open' } = $open; $info{ $stock, 'high' } = $high; $info{ $stock, 'low' } = $low; $info{ $stock, 'bid' } = $bid if ($bid); $info{ $stock, 'ask' } = $ask if ($ask); $quoter->store_date(\%info, $stock, { isodate => $date }); # Adjust/scale price data if currency is GBX (GBp) or USX (USc) if ( ( $currency eq 'GBX' ) || ( $currency eq 'USX' ) ) { foreach my $field ( $quoter->default_currency_fields ) { next unless ( $info{ $stock, $field } ); $info{ $stock, $field } = $quoter->scale_field( $info{ $stock, $field }, 0.01 ); } if ( $info{ $stock, 'currency' } eq 'GBX' ) { $info{ $stock, 'currency' } = 'GBP'; } else { $info{ $stock, 'currency' } = 'USD'; } } } else { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Could not parse HTML returned from $url."; } } else { $te->eof; $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Could not parse HTML returned from $url."; } } else { # HTTP Request failed (code != 200) $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; __END__ =head1 NAME Finance::Quote::stooq - Obtain quotes from stooq Stock Exchange. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch("stooq", "ISLN.UK"); # Only query stooq %info = $q->fetch("poland", "LRQ"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from L<https://stooq.com/>. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "stooq" in the argument list to Finance::Quote->new(). This module provides "stooq", "poland", and "europe" fetch methods. Information obtained by this module may be covered by Warsaw Stock Exchange terms and conditions. =head1 LABELS RETURNED The following labels are returned: =over =item name =item symbol =item open =item high =item low =item last =item bid =item ask =item date =item currency =back ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Fool.pm��������������������������������������������������������0000644�0001750�0001750�00000022412�15003302667�020377� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set noai ts=2 sw=2 ic showmode showmatch: # This module was rewritten in June 2019 based on the # Finance::Quote::IEXCloud.pm module and prior versions of Fool.pm # that carried the following copyrights: # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2001, Tobias Vancura <tvancura@altavista.net> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::Fool; use strict; use HTTP::Request::Common; use JSON qw( decode_json ); use Text::Template; use Encode qw(decode); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; our $VERSION = '1.65'; # VERSION my $SEARCHURL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://api.fool.com/quotes/v4/instruments/search/?maxResults=10&apikey=public&domain=fool.com&query={$symbol}'); # my $QUOTEURL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://www.fool.com/quote/{$lcexchange}/{$lcsymbol}/'); my $QUOTEURL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://api.fool.com/quotes/v4/historical/charts/{$instrumentID}?timeFrame=OneWeek&precision=Day&apikey=6cbf5f34-ba40-4108-a1ab-d951c608955e'); # Fool returns JSON with a Currency stanza # '1' -> USD my %currencies_by_id = ( '1' => 'USD', ); our $DISPLAY = 'Fool - Motley Fool Website Scrape'; our @LABELS = qw/date isodate open high low close volume last currency method/; our $METHODHASH = {subroutine => \&fool, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( fool => $METHODHASH, nyse => $METHODHASH, nasdaq => $METHODHASH, usa => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub fool { my $quoter = shift; my @stocks = @_; my (%info, $symbol, $url, $reply, $code, $desc, $body); my ($json_data, $instrumentID, $exchange, $tree ); my %mnames = (jan => 1, feb => 2, mar => 3, apr => 4, may => 5, jun => 6, jul => 7, aug => 8, sep => 9, oct =>10, nov =>11, dec =>12); my $ua = $quoter->user_agent(); $ua->agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'); my $quantity = @stocks; ### Stocks: @stocks foreach my $symbol (@stocks) { # Get the JSON with possible matches $url = $SEARCHURL->fill_in(HASH => {symbol => $symbol}); ### url: $url $reply = $ua->request( GET $url); $code = $reply->code; $desc = HTTP::Status::status_message($code); $body = decode('UTF-8', $reply->content); ### Reply: $reply if ($code != 200) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $desc; next; } ### Body: $body # Parse the JSON eval {$json_data = JSON::decode_json $body}; if ($@) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $@; next; } # The JSON returned may return information for multiple # securities # { # "ResultsFound": true, # "SearchResults": [ # { # "IndexedDate": "2023-01-14T02:44:40+00:00", # "InstrumentId": 203983, # "Symbol": "IBM", # "Exchange": "NYSE", # "Name": "International Business Machines", # "AssetClass": "stock", # "Popularity": 0, # "Country": "US", # "Sector": "Information Technology", # "Industry": "IT Services", # "IgnoreInSearch": false, # "Relevance": 86.37764 # }, # { # "IndexedDate": "2023-01-14T02:44:45+00:00", # "InstrumentId": 270916, # "Symbol": "IBM", # "Exchange": "LSE", # "Name": "International Business Machines", # "AssetClass": "stock", # "Popularity": 0, # "Country": "US", # "Sector": "Information Technology", # "Industry": "IT Services", # "IgnoreInSearch": false, # "Relevance": 80.79494 # } # ] # } my $searchresults = $json_data->{'SearchResults'}; # Loop through the array looking for a match for Symbol and # US Exchanges # In the future, symbols supplied can be "LSE:IBM". Will match # Symbol and Exchange. foreach my $item( @$searchresults ) { if ( $item->{'Symbol'} eq $symbol && $item->{'Exchange'} =~ /NYSE|NASDAQ|OTC/ ) { $instrumentID = $item->{'InstrumentId'}; $exchange = $item->{'Exchange'}; last; } } # If instrumentID is not set return error unless ($instrumentID) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "Stock symbol not found"; next; } # Create QUOTE URL $url = $QUOTEURL->fill_in(HASH => {instrumentID => $instrumentID}); ### [<now>] Quote URL: $url $reply = $ua->request( GET $url); $code = $reply->code; $desc = HTTP::Status::status_message($code); $body = decode('UTF-8', $reply->content); ### [<now>] Reply: $reply if ($code != 200) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $desc; next; } ### [<now>] Body: $body # Parse the JSON eval {$json_data = JSON::decode_json $body}; if ($@) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $@; next; } my $numChartBars = scalar @{$json_data->{'ChartBars'}}; ### [<now>] Number of ChartBars: $numChartBars my $cb = $numChartBars - 1; if ( $json_data->{'Symbol'} ne $symbol ) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = "Unexpect Data in JSON"; next; } else { $info{ $symbol, 'symbol' } = $symbol; } my $name = $json_data->{'Name'}; my $currencyid = $json_data->{'Currency'}{'Id'}; if ( $currencies_by_id{$currencyid} ) { $info{ $symbol, 'currency' } = $currencies_by_id{$currencyid}; } my $date = $json_data->{'ChartBars'}[$cb]{'PricingDate'}; my $open = $json_data->{'ChartBars'}[$cb]{'Open'}{'Amount'}; my $last = $json_data->{'ChartBars'}[$cb]{'Close'}{'Amount'}; my $high = $json_data->{'ChartBars'}[$cb]{'High'}{'Amount'}; my $low = $json_data->{'ChartBars'}[$cb]{'Low'}{'Amount'}; my $volume = $json_data->{'ChartBars'}[$cb]{'Volume'}; # my $currency = $json_data->{'ChartBars'}[$cb]{'Close'}{'CurrencyCode'}; $info{ $symbol, 'success' } = 1; $info{ $symbol, 'name' } = $name; # $info{ $symbol, 'currency' } = $currency; $info{ $symbol, 'open' } = $open; $info{ $symbol, 'last' } = $last; $info{ $symbol, 'high' } = $high; $info{ $symbol, 'low' } = $low; $info{ $symbol, 'volume' } = $volume; $info{ $symbol, 'method' } = 'fool'; $quoter->store_date(\%info, $symbol, {isodate => $date}); sleep 1; } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::Fool - Obtain quotes from the Motley Fool web site. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch('fool','GE', 'INTC'); =head1 DESCRIPTION This module obtains information from the Motley Fool website (http://caps.fool.com). The site provides date from NASDAQ, NYSE and AMEX. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "Fool" in the argument list to Finance::Quote->new(). Information returned by this module is governed by the Motley Fool's terms and conditions. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::Fool: symbol, open, high, low, volume, last, currency, method. =head1 SEE ALSO Motley Fool, http://www.fool.com Finance::Quote. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Bourso.pm������������������������������������������������������0000644�0001750�0001750�00000016671�15003302667�020763� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2001, Rob Sessink <rob_ses@users.sourceforge.net> # Copyright (C) 2005, Morten Cools <morten@cools.no> # Copyright (C) 2006, Dominique Corbex <domcox@sourceforge.net> # Copyright (C) 2008, Bernard Fuentes <bernard.fuentes@gmail.com> # Copyright (C) 2009, Erik Colson <eco@ecocode.net> # Copyright (C) 2018, Jean-Marie Pacquet <jmpacquet@sourceforge.net> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code derived from Padzensky's work on package Finance::YahooQuote, # but extends its capabilites to encompas a greater number of data sources. # # # Changelog # # 2018-04-08 Jean-Marie Pacquet # # * (1.49) Major site change (html 5) # # 2014-01-12 Arnaud Gardelein # # * changes on website # # 2009-04-12 Erik Colson # # * Major site change. # # 2008-11-09 Bernard Fuentes # # * changes on website # # 2006-12-26 Dominique Corbex <domcox@sourceforge.net> # # * (1.4) changes on web site # # 2006-09-02 Dominique Corbex <domcox@sourceforge.net> # # * (1.3) changes on web site # # 2006-06-28 Dominique Corbex <domcox@sourceforge.net> # # * (1.2) changes on web site # # 2006-02-22 Dominique Corbex <domcox@sourceforge.net> # # * (1.0) iniial release # require 5.005; use strict; package Finance::Quote::Bourso; use vars qw( $Bourso_URL ); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use HTTP::Request::Common; use HTML::TreeBuilder; use Encode qw(decode); use JSON qw( decode_json ); use utf8; our $VERSION = '1.65'; # VERSION my $Bourso_URL = 'https://www.boursorama.com/cours/'; our $DISPLAY = 'Bourso'; our @LABELS = qw/name last date isodate p_change open high low close volume currency method exchange/; our $METHODHASH = {subroutine => \&bourso, display => $DISPLAY, labels => \@LABELS, }; sub methodinfo { return ( bourso => $METHODHASH, europe => $METHODHASH, france => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub bourso_to_number { my $x = shift(@_); $x =~ s/\s//g; # remove spaces etc in number return $x; } sub bourso { my $quoter = shift; my @stocks = @_; my $ua = $quoter->user_agent(); my %info; ### UA max_redirect: $ua->max_redirect foreach my $stock (@stocks) { eval { my $query = $Bourso_URL . $stock; my $reply = $ua->request(GET $query); ### Search: $query, $reply->code my $body = decode('UTF-8', $reply->content); my $root = HTML::TreeBuilder->new_from_content($body); my $div = $root->look_down(_tag => 'div', class => qr/^c-faceplate/); my $name = $div->look_down(_tag => 'a', class => qr/^c-faceplate__company-link/)->as_text(); $name =~ s/^\s+|\s+$//g; utf8::encode($name); my $currency = $div->look_down(_tag => 'span', class => qr/^c-faceplate__price-currency/)->as_text(); $currency =~ s/^\s+|\s+$//g; my ($date, $last, $symbol, $low, $high, $close, $exchange, $volume, $net); if ($div->attr('data-ist-init')) { my $json = JSON::decode_json($div->attr('data-ist-init')); $date = $json->{'tradeDate'}; $last = $json->{'last'}; $symbol = $json->{'symbol'}; $low = $json->{'low'}; $high = $json->{'high'}; $close = $json->{'previousClose'}; $exchange = $json->{'exchangeCode'}; $volume = $json->{'totalVolume'}; $net = $json->{'variation'}; } else { # date captures more than the date, but the regular expression below extracts just the date $date = $div->look_down(_tag => 'div', class => qr/^c-faceplate__real-time/)->as_text(); $last = $div->look_down(_tag => 'span', class => qr/^c-instrument c-instrument--last/)->as_text(); $symbol = $stock; # last here will contain a comma (,) instead of a decimal point $last =~ s/,/./; } $info{$stock, 'symbol'} = $symbol; $info{$stock, 'name'} = $name; $info{$stock, 'currency'} = $currency; ($info{$stock, 'last'} = $last) =~ s/[^0-9.]//g; ($info{$stock, 'high'} = $high) =~ s/[^0-9.]//g if $high; ($info{$stock, 'low'} = $low) =~ s/[^0-9.]//g if $low; ($info{$stock, 'close'} = $close) =~ s/[^0-9.]//g if $close; $info{$stock, 'exchange'} = $exchange if $exchange; ($info{$stock, 'volume'} = $volume) =~ s/[^0-9.]//g if $volume; ($info{$stock, 'net'} = $net) =~ s/[^0-9.]//g if $net; # 2020-07-17 17:03:45 $quoter->store_date(\%info, $stock, {isodate => $1}) if $date =~ m|([0-9]{4}-[0-9]{2}-[0-9]{2})|; # dd/mm/yyyy $quoter->store_date(\%info, $stock, {eurodate => $1}) if $date =~ m|([0-9]{2}/[0-9]{2}/[0-9]{4})|; $info{$stock, 'method' } = 'bourso'; $info{$stock, 'success'} = 1; }; if ($@) { $info{$stock, 'success'} = 0; $info{$stock, 'errormsg'} = 'Failed to retrieve quote'; } } return wantarray() ? %info : \%info; return \%info; } 1; =head1 NAME Finance::Quote::Bourso - Obtain quotes from Boursorama. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = Finance::Quote->fetch("bourso","ml"); # Only query Bourso =head1 DESCRIPTION This module fetches information from the "Paris Stock Exchange", https://www.boursorama.com. All stocks are available. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "bourso" in the argument list to Finance::Quote->new(). Information obtained by this module may be covered by www.boursorama.com terms and conditions See https://www.boursorama.com/ for details. =head1 LABELS RETURNED The following labels will be returned by Finance::Quote::Bourso : name, last, symbol, date, isodate, method, currency. For some symbols, additional information is available: exchange, high, low, close, net, volume. =head1 SEE ALSO Boursorama (french web site), https://www.boursorama.com =cut �����������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CSE.pm���������������������������������������������������������0000644�0001750�0001750�00000007631�15003302667�020120� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::CSE; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use JSON qw( decode_json ); use String::Util qw(trim); our $VERSION = '1.65'; # VERSION our $DISPLAY = 'CSE - Colombo Stock Exchange'; our @LABELS = qw/isin close last high low cap change p_change name symbol currency method symbol date isodate/; our $METHODHASH = {subroutine => \&cse, display => \$DISPLAY, labels => \@LABELS}; sub methodinfo { return ( cse => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub cse { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my %info; foreach my $symbol (@_) { eval { my $url = 'https://www.cse.lk/api/companyInfoSummery'; my $form = { 'symbol' => $symbol, 'MIME Type' => 'application/x-www-form-urlencoded; charset=UTF-8', }; my $reply = $ua->post($url, $form); my $search = JSON::decode_json $reply->content; ### Search : $url, $form, $reply->code ### Search : $search my $data = $search->{reqSymbolInfo} or die('query did not return expected data'); $info{$symbol, 'isin'} = $data->{isin}; $info{$symbol, 'close'} = $data->{closingPrice}; $info{$symbol, 'last'} = $data->{lastTradedPrice}; $info{$symbol, 'high'} = $data->{hiTrade}; $info{$symbol, 'low'} = $data->{lowTrade}; $info{$symbol, 'cap'} = $data->{marketCap}; $info{$symbol, 'change'} = $data->{change}; $info{$symbol, 'p_change'} = $data->{changePercentage}; $info{$symbol, 'name'} = $data->{name}; $info{$symbol, 'symbol'} = $data->{symbol}; $info{$symbol, 'currency'} = 'LKR'; $info{$symbol, 'method'} = 'cse'; $quoter->store_date(\%info, $symbol, {today => 1}); $info{$symbol, 'success'} = 1; }; if ($@) { my $error = "CSE failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); } } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::CSE - Obtain quotes from Colombo Stock Exchange in Sri Lanka =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = Finance::Quote->fetch('cse', 'YORK.N0000'); =head1 DESCRIPTION This module fetches information from the Colombo Stock Exchange (CSE) in Sri Lanka http://www.cse.lk. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'CSE' in the argument list to Finance::Quote->new(). =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::CSE : isin close last high low cap change p_change name symbol currency method symbol date isodate =head1 TERMS & CONDITIONS Use of www.cse.lk is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/XETRA.pm�������������������������������������������������������0000644�0001750�0001750�00000020043�15003302667�020361� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::XETRA; use strict; use warnings; use HTML::Entities; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use Web::Scraper; our $VERSION = '1.65'; # VERSION my $xetra_URL = 'https://web.s-investor.de/app/detail.htm?boerse=GER&isin='; our $DISPLAY = 'XETRA - German Sparkasse banking group'; our $FEATURES = {'INST_ID' => 'Required Institution ID'}; our @LABELS = qw/symbol last close exchange volume open price change p_change/; our $METHODHASH = {subroutine => \&xetra, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub methodinfo { return ( xetra => $METHODHASH, europe => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub parameters { return ('INST_ID'); } sub xetra { my $quoter = shift; my $inst_id = exists $quoter->{module_specific_data}->{xetra}->{INST_ID} ? $quoter->{module_specific_data}->{xetra}->{INST_ID} : '0000057'; my $ua = $quoter->user_agent(); my $agent = $ua->agent; $ua->agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'); my %info; my $url; my $reply; foreach my $symbol (@_) { eval { my $url = $xetra_URL . $symbol . '&INST_ID=' . $inst_id; my $symlen = length($symbol); my $tree = HTML::TreeBuilder->new_from_url($url); my $lastvalue = $tree->look_down('class'=>'si_seitenbezeichnung'); if (defined($lastvalue)) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = 'Invalid institute id. Get a valid institute id from https://web.s-investor.de/app/webauswahl.jsp'; } else { $lastvalue = $tree->look_down('id'=>'kursdaten'); my $td1 = ($lastvalue->look_down('_tag'=>'td'))[1]; my @child = $td1->content_list; my $isin = $child[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[3]; @child = $td1->content_list; my $sharename = $child[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[5]; @child = $td1->content_list; my $exchange = $child[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[7]; @child = $td1->content_list; my $date = substr($child[0], 0, 8); $td1 = ($lastvalue->look_down('_tag'=>'td'))[9]; @child = $td1->content_list; my $price = $child[0]; $price =~ s/\.//g; $price =~ s/,/\./; my $encprice = encode_entities($price); my @splitprice= split ('&',$encprice); $price = $splitprice[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[11]; @child = $td1->content_list; my $currency = $child[0]; $currency =~ s/Euro/EUR/; $td1 = ($lastvalue->look_down('_tag'=>'td'))[13]; @child = $td1->content_list; my $volume = $child[0]; my @searchvalue = $tree->look_down('class'=>'contentBox oneColum'); my $isFound = 0; foreach (@searchvalue) { # Get number of child elements. # Skip this tree member my $num_elements = $_->content_list(); next unless $num_elements > 1; if (($_->content_list)[0]{'_content'}[0]{'_content'}[0] eq 'Aktuelle Vergleichszahlen') { $isFound = 1; #-- change (absolute change) $td1 = ($_->look_down('_tag'=>'td'))[13]; @child = $td1->content_list; my $change = $child[0]; $change =~ s/\.//g; $change =~ s/,/\./; my $encchange = encode_entities($change); my @splitcchange= split ('&',$encchange); $change = $splitcchange[0]; #-- p_change (relative change) $td1 = ($_->look_down('_tag'=>'td'))[16]; @child = $td1->content_list; my $p_change =$child[0]; $p_change =~ s/[\.|%]//g; $p_change =~ s/,/\./; #-- close $td1 = ($_->look_down('_tag'=>'td'))[34]; @child = $td1->content_list; my $close = $child[0]; $close =~ s/\.//g; $close =~ s/,/\./; my $encclose = encode_entities($close); my @splitclose= split ('&',$encclose); $close = $splitclose[0]; $info{$symbol, 'success'} = 1; $info{$symbol, 'method'} = 'xetra'; $info{$symbol, 'symbol'} = $isin; $info{$symbol, 'name'} = $sharename; $info{$symbol, 'exchange'} = $exchange; $info{$symbol, 'last'} = $price; $info{$symbol, 'price'} = $price; $info{$symbol, 'close'} = $close; $info{$symbol, 'change'} = $change; $info{$symbol, 'p_change'} = $p_change; $info{$symbol, 'volume'} = $volume; $info{$symbol, 'currency'} = $currency; #$info{$symbol, 'date'} = $date; $quoter->store_date(\%info, $symbol, {eurodate => $date}); # leave foreach loop since we have the data last; } } if (!$isFound) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = "Error retreiving $symbol: $@"; } } }; if ($@) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = "Error retreiving $symbol: $@"; } } $ua->agent($agent); return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::xetra - Obtain quotes from S-Investor platform. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; or $q = Finance::Quote->new('XETRA', 'xetra' => {INST_ID => 'your institute id'}); %info = Finance::Quote->fetch("xetra", "DE000ENAG999"); # Only query xetra %info = Finance::Quote->fetch("europe", "brd"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from https://s-investor.de/, the investment platform of the German Sparkasse banking group. It fetches share prices from XETRA, a major German trading platform. The prices on XETRA serve as the basis for calculating the DAX and other stock market indices. Suitable for shares and ETFs that are traded in Germany. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "XETRA" in the argument list to Finance::Quote->new(). This module provides "xetra" and "europe" fetch methods. Information obtained by this module may be covered by s-investor.de terms and conditions. =head1 INST_ID https://s-investor.de/ supports different institute IDs. The default value "0000057" is used (Krefeld) if no institute ID is provided. A list of institute IDs is provided here: https://web.s-investor.de/app/webauswahl.jsp The INST_ID may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 LABELS RETURNED The following labels are returned: currency exchange last method success symbol volume price close change p_change ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Fondsweb.pm����������������������������������������������������0000644�0001750�0001750�00000012671�15003302667�021255� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Copyright (C) 2018, Diego Marcolungo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. package Finance::Quote::Fondsweb; use warnings; use strict; use HTTP::Request::Common; use HTML::TreeBuilder::XPath; our $VERSION = '1.65'; # VERSION our $FONDSWEB_URL = "https://www.fondsweb.com/de/"; sub methods { return ( fondsweb => \&fondsweb ); } { my @labels = qw/name symbol isin date isodate year_range nav last price currency source method type/; sub labels { return (fondsweb => \@labels); } } # 123.456.789,00 -> 123456789.00 sub decimalPeriod { my $value = shift; $value =~ s/[.,]([0-9]+)$/#$1/; # temporarily replace final seperator with # $value =~ s/[.,]//g; # remove all other seperators $value =~ s/#/./; # replace # with . return $value; } sub fondsweb { my $quoter = shift; my @symbols = @_; my $te = HTML::TableExtract->new( depth => 0, count => 6 ); my %info; # Iterate over each symbol foreach my $symbol (@symbols) { my $tree = HTML::TreeBuilder::XPath->new; my $url = $FONDSWEB_URL . $symbol; #~ debug_ua( $quoter->user_agent ); # The site check the user agent $quoter->user_agent->agent("Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0"); my $reply = $quoter->user_agent->request(GET $url); # Check response unless ($reply->is_success) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errmsg" } = join ' ', $reply->code, $reply->message; } else { # Parse the HTML tree $tree->parse( $reply->decoded_content ); # Find data using xpath # name my $name = $tree->findvalue( '//h1[@class="fw--h1 fw--fondsModule-head-content-headline"]'); $info{ $symbol, 'name' } = $name; # isin my $isin_raw = $tree->findvalue( '//span[@class="text_bold"]'); my @isin = $isin_raw =~ m/^(\w\w\d+)\w./; my $sym=$isin[0]; my $symlen = length($sym); if($symlen>12) { $sym = substr($isin[0], 0, 12); } $info{ $symbol, 'isin' } = $sym; $info{ $symbol, 'symbol' } = $sym; # date, isodate my $raw_date = $tree->findvalue( '//i[@data-key="nav"]/..' ); my @date = $raw_date =~ m/.(\d\d)\.(\d\d)\.(\d\d\d\d)./; $quoter->store_date(\%info, $symbol, {eurodate => "$date[0]/$date[1]/$date[2]"} ); # year_range, in this case use table extract $te->parse($reply->decoded_content); # the 6th table my $details = $te->table(0, 6); my $lastRowIndex = @{$details->rows} - 1; # extract data with re my $highest = decimalPeriod($details->cell($lastRowIndex - 1, 1)); my $lowest = decimalPeriod($details->cell($lastRowIndex, 1)); $info{ $symbol, "year_range" } = $lowest . ' - ' . $highest; # nav, last, currency my $raw_nav_currency = $tree->findvalue( '//div[@class="fw--fondDetail-price"]' ); my @nav_currency = $raw_nav_currency =~ m/^([0-9,.]+)\s(\w+)/; $info{ $symbol, 'nav' } = decimalPeriod($nav_currency[0]); $info{ $symbol, 'last' } = $info{ $symbol, 'nav' }; $info{ $symbol, 'currency' } = $nav_currency[-1]; # Other metadata $info{ $symbol, 'method' } = 'fondsweb'; $info{ $symbol, "type" } = "fund"; $info{ $symbol, "success" } = 1; } } return wantarray ? %info : \%info; } __END__ =head1 NAME Finance::Quote::Fondsweb - Obtain price data from Fondsweb (Germany) =head1 VERSION This documentation describes version 1.00 of Fondsweb.pm, December 28, 2018. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch("fondsweb", "LU0804734787"); =head1 DESCRIPTION This module obtains information from Fondsweb (Germany), L<https://www.fondsweb.com/>. Information returned by this module is governed by Fondsweb (Germany)'s terms and conditions. =head1 FUND SYMBOLS Use the ISIN number e.g. For L<https://www.fondsweb.com/de/LU0804734787>, one would supply LU0804734787 as the symbol argument on the fetch API call. =head1 LABELS RETURNED The following labels are returned by Finance::Quote::Fondsweb: - currency - date - isin - isodate - last - method - name - nav - type =head1 REQUIREMENTS Perl 5.012 HTML::TableExtract HTML::TreeBuilder::XPath =head1 ACKNOWLEDGEMENTS Inspired by other modules already present with Finance::Quote =head1 AUTHOR Diego Marcolungo =head1 LICENSE AND COPYRIGHT Copyright (C) 2018, Diego Marcolungo. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. =head1 DISCLAIMER This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. =head1 SEE ALSO Fondsweb (Germany), L<https://www.fondsweb.com/> =cut �����������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/�������������������������������������������������0000775�0001750�0001750�00000000000�15003302667�021734� 5����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/FinanceAPI.pm������������������������������������0000644�0001750�0001750�00000007651�15003302667�024176� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 ic noai showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # Copyright (C) 2024, Bruce Schuck <bschuck@asgard-systems.com> # Changes: # 2024-09-22 - Initial version. Base code opied from # CurrencyRates/YahooJSON.pm package Finance::Quote::CurrencyRates::FinanceAPI; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use JSON; our $VERSION = '1.65'; # VERSION my $YIND_URL_HEAD = 'https://yfapi.net/v8/finance/chart/'; my $YIND_URL_TAIL = '?metrics=high&interval=1d&range=1d'; sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; my $args = shift; ### FinanceAPI->new args : $args # FinanceAPI is permitted to use an environment variable for API key # (for backwards compatibility). # New modules should use the API_KEY from args. $this->{API_KEY} = $ENV{'FINANCEAPI_API_KEY'}; $this->{API_KEY} = $args->{API_KEY} if (ref $args eq 'HASH') and (exists $args->{API_KEY}); return $this; } sub multipliers { my ($this, $ua, $from, $to) = @_; my $json_data; my $rate; # Set headers. API key is sent as a header. my @ua_headers = ( 'Accept' => 'application/json', 'X-API-KEY' => $this->{API_KEY}, ); my $reply = $ua->get($YIND_URL_HEAD . ${from} . ${to} . '%3DX' . $YIND_URL_TAIL, @ua_headers); ### HTTP Status: $reply->code return unless ($reply->code == 200); my $body = $reply->content; $json_data = JSON::decode_json $body; ### JSON: $json_data if ( !$json_data || !$json_data->{'chart'}->{'result'}->[0]->{'meta'}->{'regularMarketPrice'} ) { return; } $rate = $json_data->{'chart'}->{'result'}->[0]->{'meta'}->{'regularMarketPrice'}; ### Rate from JSON: $rate return unless $rate + 0; # For small rates, request the inverse if ($rate < 0.001) { ### Rate is too small, requesting inverse : $rate my ($a, $b) = $this->multipliers($ua, $to, $from); return ($b, $a); } return (1.0, $rate); } 1; =head1 NAME Finance::Quote::CurrencyRates::FinanceAPI - Obtain currency rates from https://yfapi.net/v8/finance/chart/. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['FinanceAPI'], financeapi => {API_KEY => ...} }); $value = $q->currency('18.99 EUR', 'USD'); =head1 DESCRIPTION This module fetches currency rates from https://yfapi.net/v8/finance/chart/ provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. This module is not the default currency conversion module for a Finance::Quote object. =head1 API_KEY https://financeapi.net requires users to register and obtain an API key. The API key can be set by setting the Environment variable "FINANCEAPI_API_KEY" or providing a 'financeapi' hash inside the 'currency_rates' hash to Finance::Quote->new as in the above example. =head1 Terms & Conditions Use of https://yfapi.net/v8/finance/chart/ is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ���������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/OpenExchange.pm����������������������������������0000644�0001750�0001750�00000007565�15003302667�024651� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::CurrencyRates::OpenExchange; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use JSON; our $VERSION = '1.65'; # VERSION sub parameters { return ('API_KEY'); } sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; my $args = shift; ### OpenExchange->new args : $args $this->{API_KEY} = $ENV{'OPENEXCHANGE_API_KEY'}; $this->{API_KEY} = $args->{API_KEY} if (ref $args eq 'HASH') and (exists $args->{API_KEY}); $this->{refresh} = 0; $this->{refresh} = not $args->{cache} if exists $args->{cache}; # Return nothing if API_KEY not set return unless ($this->{API_KEY}); ### API_KEY: $this->{API_KEY} return $this; } # end new sub multipliers { my ($this, $ua, $from, $to) = @_; if ($this->{refresh} or not exists $this->{cache}) { my $url = "https://openexchangerates.org/api/latest.json?app_id=$this->{API_KEY}"; my $reply = $ua->get($url); return unless ($reply->code == 200); my $body = $reply->content; my $json_data = JSON::decode_json $body; if ( !$json_data || $json_data->{error} || not exists $json_data->{rates}) { ### OpenExchange error : $json_data->{description} return; } $this->{cache} = $json_data->{rates}; ### OpenExchange rates: $this->{cache} } if (exists $this->{cache}->{$from} and exists $this->{cache}->{$to}) { ### from : $from, $this->{cache}->{$from} ### to : $to, $this->{cache}->{$to} return ($this->{cache}->{$from}, $this->{cache}->{$to}); } ### At least one code not found: $from, $to return; } # end multipliers 1; =head1 NAME Finance::Quote::CurrencyRates::OpenExchange - Obtain currency rates from https://openexchangerates.org =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['OpenExchange'], openexchange => {API_KEY => ...}} ); $value = $q->currency('18.99 EUR', 'USD'); =head1 DESCRIPTION This module fetches currency rates from https://openexchangerates.org and provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. This module caches the currency rates for the lifetime of the quoter object, unless 'cache => 0' is included in the 'openexchange' options hash. =head1 Currency Module Selection The Finance::Quote currency method to be used can also be selected by setting the environment variable FQ_CURRENCY. export FQ_CURRENCY=OpenExchange =head1 API_KEY https://openexchangerates.org requires users to register and obtain an API key. Their free plan allows 1000 queries per month. The API key can be set by setting the environment variable OPENEXCHANGE_API_KEY or by providing a 'openexchange' hash inside the 'currency_rates' hash to Finance::Quote->new as in the above example. =head1 Terms & Conditions Use of https://openexchangerates.org is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/CurrencyFreaks.pm��������������������������������0000755�0001750�0001750�00000010343�15003302667�025222� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 ic noai showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> package Finance::Quote::CurrencyRates::CurrencyFreaks; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use constant TESTING => $ENV{TESTING}; use if DEBUG, 'Smart::Comments'; use JSON; our $VERSION = '1.65'; # VERSION sub parameters { return ('API_KEY'); } sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; my $args = shift; ### CurrencyFreaks->new args : $args # CurrencyFreaks is permitted to use an environment variable for API key # (for backwards compatibility). # New modules should use the API_KEY from args. $this->{API_KEY} = $ENV{'CURRENCYFREAKS_API_KEY'}; $this->{API_KEY} = $args->{API_KEY} if (ref $args eq 'HASH') and (exists $args->{API_KEY}); return $this; } sub multipliers { my ($this, $ua, $from, $to) = @_; my $reply = $ua->get('https://api.currencyfreaks.com/v2.0/rates/latest' . '?apikey=' . $this->{API_KEY} . '&symbols=' . ${from} . ',' . ${to} ); my $body = $reply->content; my $reply_code = $reply->code; if (TESTING) { $body = '{ "date": "2023-03-21 13:26:00+00", "base": "USD", "rates": { "EUR": "0.9278605451274349", "GBP": "0.8172754173817152", "PKR": "281.6212943333344", "USD": "1.0", "TST": "3000.0" } }'; $reply_code = 200; } ### HTTP body: $body return unless ($reply_code == 200); my $json_data = decode_json ($body); if ( !$json_data->{'rates'}->{$from} || !$json_data->{'rates'}->{$to} ) { return; } # We really don't care what the base is as long as it is same. ### rates base: $json_data->{"base"} ### from: $to ### to: $json_data->{"base"} ### rate: ($json_data->{'rates'}->{${to}}) ### from: $json_data->{"base"} ### to: $from ### rate: ($json_data->{'rates'}->{${from}}) my $rate = $json_data->{'rates'}->{${to}} / $json_data->{'rates'}->{${from}}; return unless $rate + 0; # For small rates, request the inverse if ($rate < 0.001) { ### Rate is too small, requesting inverse : $rate my ($a, $b) = $this->multipliers($ua, $to, $from); return ($b, $a); } return (1.0, $rate); } 1; __END__ =head1 NAME Finance::Quote::CurrencyRates::CurrencyFreaks - Obtain currency rates from CurrencyFreaks. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['CurrencyFreaks'], currencyfreaks => {API_KEY => ...} } ); $value = $q->currency('18.99 EUR', 'USD'); =head1 DESCRIPTION This module fetches currency rates from https://currencyfreaks.com/ provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. This module is not the default currency conversion module for a Finance::Quote object. =head1 API_KEY https://currencyfreaks.com/ requires users to register and obtain an API key, which is also called a token. The API key may be set by either providing a currencyfreaks hash inside the currency_rates hash to Finance::Quote->new as in the above example, or by setting the environment variable CURRENCYFREAKS_API_KEY. =head1 Terms & Conditions Use of https://currencyfreaks.com/ is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/AlphaVantage.pm����������������������������������0000644�0001750�0001750�00000007564�15003302667�024637� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::CurrencyRates::AlphaVantage; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use JSON; our $VERSION = '1.65'; # VERSION sub parameters { return ('API_KEY'); } sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; my $args = shift; ### AlphaVantage->new args : $args # AlphaVantage is the ONLY module permitted to use an environment variable # for API key (for backwards compatibility). New modules MUST use the # API_KEY from args. $this->{API_KEY} = $ENV{'ALPHAVANTAGE_API_KEY'}; $this->{API_KEY} = $args->{API_KEY} if (ref $args eq 'HASH') and (exists $args->{API_KEY}); return $this; } sub multipliers { my ($this, $ua, $from, $to) = @_; my $url = 'https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE'; my $try_cnt = 0; my $json_data; my $rate; do { $try_cnt += 1; my $reply = $ua->get($url . '&from_currency=' . ${from} . '&to_currency=' . ${to} . '&apikey=' . $this->{API_KEY}); return unless ($reply->code == 200); my $body = $reply->content; $json_data = JSON::decode_json $body; if ( !$json_data || $json_data->{'Error Message'} ) { return; } ### JSON: $json_data sleep (20) if (($try_cnt < 5) && ($json_data->{'Note'})); } while (($try_cnt < 5) && ($json_data->{'Note'})); if( !$json_data->{'Realtime Currency Exchange Rate'} ) { ### No data in JSON $rate = 0.0; } else { $rate = $json_data->{'Realtime Currency Exchange Rate'}->{'5. Exchange Rate'}; } ### Rate from JSON: $rate return unless $rate + 0; # For small rates, request the inverse if ($rate < 0.001) { ### Rate is too small, requesting inverse : $rate my ($a, $b) = $this->multipliers($ua, $to, $from); return ($b, $a); } return (1.0, $rate); } 1; =head1 NAME Finance::Quote::CurrencyRates::AlphaVantage - Obtain currency rates from https://www.alphavantage.co =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['AlphaVantage'], alphavantage => {API_KEY => ...}}); $value = $q->currency('18.99 EUR', 'USD'); =head1 DESCRIPTION This module fetches currency rates from https://www.alphavantage.co and provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. This module is the default currency conversion module for a Finance::Quote object. =head1 API_KEY https://www.alphavantage.co requires users to register and obtain an API key, which is also called a token. The API key may be set by either providing a alphavantage hash inside the currency_rates hash to Finance::Quote->new as in the above example, or by setting the environment variable ALPHAVANTAGE_API_KEY. =head1 Terms & Conditions Use of https://www.alphavantage.co is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ��������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/YahooJSON.pm�������������������������������������0000644�0001750�0001750�00000006404�15003302667�024045� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 ic noai showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> # Changes: # 2024-04-13 - Changed to use # https://query1.finance.yahoo.com/v8/finance/chart/ package Finance::Quote::CurrencyRates::YahooJSON; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use JSON; our $VERSION = '1.65'; # VERSION my $YIND_URL_HEAD = 'https://query1.finance.yahoo.com/v8/finance/chart/'; my $YIND_URL_TAIL = '?metrics=high&interval=1d&range=1d'; sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; my $args = shift; ### YahooJSON->new args : $args return $this; } sub multipliers { my ($this, $ua, $from, $to) = @_; my $json_data; my $rate; my $reply = $ua->get($YIND_URL_HEAD . ${from} . ${to} . '%3DX' . $YIND_URL_TAIL); ### HTTP Status: $reply->code return unless ($reply->code == 200); my $body = $reply->content; $json_data = JSON::decode_json $body; ### JSON: $json_data if ( !$json_data || !$json_data->{'chart'}->{'result'}->[0]->{'meta'}->{'regularMarketPrice'} ) { return; } $rate = $json_data->{'chart'}->{'result'}->[0]->{'meta'}->{'regularMarketPrice'}; ### Rate from JSON: $rate return unless $rate + 0; # For small rates, request the inverse if ($rate < 0.001) { ### Rate is too small, requesting inverse : $rate my ($a, $b) = $this->multipliers($ua, $to, $from); return ($b, $a); } return (1.0, $rate); } 1; =head1 NAME Finance::Quote::CurrencyRates::YahooJSON - Obtain currency rates from https://query2.finance.yahoo.com/v11/finance/quoteSummary/...?modules=price =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['YahooJSON']}); $value = $q->currency('18.99 EUR', 'USD'); =head1 DESCRIPTION This module fetches currency rates from https://query2.finance.yahoo.com/v11/finance/quoteSummary/...?modules=price provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. This module is not the default currency conversion module for a Finance::Quote object. =head1 Terms & Conditions Use of https://query2.finance.yahoo.com/v11/finance/quoteSummary/...?modules=price is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/Fixer.pm�����������������������������������������0000644�0001750�0001750�00000006773�15003302667�023362� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::CurrencyRates::Fixer; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use JSON; our $VERSION = '1.65'; # VERSION sub parameters { return ('API_KEY'); } sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; my $args = shift; ### Fixer->new args : $args $this->{API_KEY} = $ENV{'FIXER_API_KEY'}; $this->{API_KEY} = $args->{API_KEY} if (ref $args eq 'HASH') and (exists $args->{API_KEY}); $this->{refresh} = 0; $this->{refresh} = not $args->{cache} if exists $args->{cache}; # Return nothing if API_KEY not set return unless ($this->{API_KEY}); ### API_KEY: $this->{API_KEY} return $this; } sub multipliers { my ($this, $ua, $from, $to) = @_; if ($this->{refresh} or not exists $this->{cache}) { my $url = "http://data.fixer.io/api/latest?access_key=$this->{API_KEY}"; my $reply = $ua->get($url); return unless ($reply->code == 200); my $body = $reply->content; my $json_data = JSON::decode_json $body; if ( !$json_data || $json_data->{error} || not exists $json_data->{rates}) { ### fixer error : $json_data->{error}->{info} return; } $this->{cache} = $json_data->{rates}; ### Fixer rates: $this->{cache} } if (exists $this->{cache}->{$from} and exists $this->{cache}->{$to}) { ### from : $from, $this->{cache}->{$from} ### to : $to, $this->{cache}->{$to} return ($this->{cache}->{$from}, $this->{cache}->{$to}); } ### At least one code not found: $from, $to return; } 1; =head1 NAME Finance::Quote::CurrencyRates::Fixer - Obtain currency rates from https://fixer.io =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['Fixer'], fixer => {API_KEY => ...}}); $value = $q->currency('18.99 EUR', 'USD'); =head1 DESCRIPTION This module fetches currency rates from https://fixer.io and provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. This module caches the currency rates for the lifetime of the quoter object, unless 'cache => 0' is included in the 'fixer' options hash. =head1 API_KEY https://fixer.io requires users to register and obtain an API key. The API key can be set by setting the Environment variable "FIXER_API_KEY" or providing a 'fixer' hash inside the 'currency_rates' hash to Finance::Quote->new as in the above example. =head1 Terms & Conditions Use of https://fixer.io is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �����Finance-Quote-1.65/lib/Finance/Quote/CurrencyRates/ECB.pm�������������������������������������������0000644�0001750�0001750�00000005420�15003302667�022662� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::CurrencyRates::ECB; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use XML::LibXML; our $VERSION = '1.65'; # VERSION sub new { my $self = shift; my $class = ref($self) || $self; my $this = {}; bless $this, $class; return $this; } sub multipliers { my ($this, $ua, $from, $to) = @_; unless (exists $this->{cache}) { my $url = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'; my $reply = $ua->get($url); return unless ($reply->code == 200); my $xml = XML::LibXML->load_xml(string => $reply->content); $this->{cache} = {map {$_->getAttribute('currency'), $_->getAttribute('rate')} $xml->findnodes('//*[@currency]')}; $this->{cache}->{EUR} = 1.0; ### cache : $this->{cache} } if (exists $this->{cache}->{$from} and exists $this->{cache}->{$to}) { ### from : $from, $this->{cache}->{$from} ### to : $to, $this->{cache}->{$to} return ($this->{cache}->{$from}, $this->{cache}->{$to}); } ### At least one code not found: $from, $to return; } 1; =head1 NAME Finance::Quote::CurrencyRates::ECB - Obtain currency rates from https://www.ecb.europa.eu =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(currency_rates => {order => ['ECB']}); $value = $q->currency('18.99 EUR', 'CAD'); =head1 DESCRIPTION This module fetches currency rates from https://www.ecb.europa.eu and provides data to Finance::Quote to convert the first argument to the equivalent value in the currency indicated by the second argument. The European Central Bank provides a small list of currencies, quoted against the Euro. This module caches the table of rates for the lifetime of the Finance::Quote object after the first currency conversion. =head1 Terms & Conditions Use of https://www.ecb.europa.eu is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/UserAgent.pm���������������������������������������������������0000644�0001750�0001750�00000006405�15003302667�021401� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # This module defines our own LWP::UserAgent, in particular it allows # user-defined headers to be set which will be automatically added to # new HTTP requests. This is particularly important if you wish to get # through authenticated proxies and the like. package Finance::Quote::UserAgent; require 5.005; use strict; use LWP::UserAgent; use HTTP::Headers; use vars qw/@ISA /; our $VERSION = '1.65'; # VERSION @ISA = qw/LWP::UserAgent/; # A very simple extension. When we generate a LWP::UserAgent object, # we add an extra field called finance_quote_headers which stores an # HTTP::Headers object. sub new { my $ua = LWP::UserAgent::new(@_); $ua->{finance_quote_headers} = HTTP::Headers->new(); return $ua; } # This returns the HTTP::Headers object, so the user can play with it. sub default_headers { my $this = shift; return $this->{finance_quote_headers}; } # Over-ride for the simple_request method. This sets the user-supplied # template headers if they have not already been set in the request. sub simple_request { my ($this, $request, @args) = @_; my $new_request = $this->_add_custom_headers($request); return $this->SUPER::simple_request($new_request,@args); } # Over-ride for the request method. This also sets the user-supplied # template headers if they have not already been set in the request. sub request { my ($this, $request, @args) = @_; my $new_request = $this->_add_custom_headers($request); return $this->SUPER::request($new_request,@args); } # _add_custom_headers is a private method which does the dirty work # of copying across headers and other fun things. # # We take the user-defined template, and then overlay the request over the # top of it. This should get us by in most situations. sub _add_custom_headers { my ($this, $request) = @_; my $header_template = $this->default_headers; my $new_request = $request->clone; # Modifying the original is rude. # Copy things that are in the template that we don't have # defined in the request. $header_template->scan(sub { $new_request->header($_[0],$_[1]) unless defined ($new_request->header($_[0])); }); return $new_request; } # If users wish to place their username and proxy password(!) into # the "http_proxy_auth_clear" environment variable, then we'll # read it out and automatically use it for proxy requests. sub env_proxy { my ($this, @args) = @_; if ($ENV{http_proxy_auth_clear}) { $this->default_headers->proxy_authorization_basic( split(/:/,$ENV{http_proxy_auth_clear})); } $this->SUPER::env_proxy(@_); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/NZX.pm���������������������������������������������������������0000644�0001750�0001750�00000010243�15003302667�020156� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=4 sw=4 noai ic showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::NZX; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Encode; use JSON qw( decode_json ); use LWP::UserAgent; use Web::Scraper; use String::Util qw(trim); our $VERSION = '1.65'; # VERSION our $DISPLAY = 'NZX - New Zealand Exchange'; our @LABELS = qw/last isin name currency date isodate/; our $METHODHASH = {subroutine => \&nzx, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( nzx => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub nzx { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my %info; foreach my $symbol (@_) { eval { my $url = "https://www.nzx.com/instruments/$symbol"; my $reply = $ua->get($url); # JSON inside script id="__NEXT_DATA__" type="application/json" crossorigin=""> my $widget = scraper { process '//script[contains(@id, "__NEXT_DATA__")]/text()', "script" => 'TEXT'; }; my $result = $widget->scrape($reply->decoded_content); #my $result = $widget->scrape($reply->content); ### RESULT : $result ### [<now>] Result->script: $result->{script} my $json = encode_utf8($result->{script}); my $json_data; eval {$json_data = JSON::decode_json($json)}; if ($@) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $@; } ### [<now>] JSON Data: $json_data unless ($json_data->{'props'}{'pageProps'}{'overview'}{'code'} eq $symbol) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Symbol not found'; next; } $info{$symbol, 'success'} = 1; $info{$symbol, 'currency'} = 'NZD'; $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'last'} = $json_data->{'props'}{'pageProps'}{'overview'}{'priceAmount'}; $info{$symbol, 'isin'} = $json_data->{'props'}{'pageProps'}{'overview'}{'ISIN'}; $info{$symbol, 'name'} = $json_data->{'props'}{'pageProps'}{'overview'}{'name'}; my $closePriceDate = $json_data->{'props'}{'pageProps'}{'overview'}{'closePriceDate'}; $quoter->store_date(\%info, $symbol, {isodate => $closePriceDate}); }; if ($@) { my $error = "Search failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); } } ### info : %info return wantarray() ? %info : \%info; } 1; __END__ =head1 NAME Finance::Quote::NZX - Obtain quotes from New Zealand's Exchange www.nzx.com =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch('nzx','MNW'); =head1 DESCRIPTION This module obtains information from L<NZX|https://www.nzx.com/>. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::NZX: last, isin, name, currency, date, isodate =head1 Terms & Conditions Use of nzx.com is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/GoogleWeb.pm���������������������������������������������������0000644�0001750�0001750�00000015225�15003302667�021356� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::GoogleWeb; use strict; use warnings; use Encode qw(decode); use HTML::TreeBuilder; use HTTP::Request::Common; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION my $GOOGLE_URL = 'https://www.google.com/finance/'; our $DISPLAY = 'GoogleWeb - Scrapes www.google.com/finance/'; our @LABELS = qw/symbol name last date currency method/; our $METHODHASH = {subroutine => \&googleweb, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( googleweb => $METHODHASH, bats => $METHODHASH, nyse => $METHODHASH, nasdaq => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub googleweb { my $quoter = shift; my @stocks = @_; my (%info, $tree, $url, $reply); my $ua = $quoter->user_agent(); foreach my $stock (@stocks) { my $ucstock = uc($stock); $url = $GOOGLE_URL . "quote/" . $ucstock; $reply = $ua->request( GET $url); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = decode('UTF-8', $reply->content); ### Body: $body my ($name, $last, $date, $currency, $time, $taglink, $link, $exchange); $info{ $stock, "symbol" } = $stock; if ( $code == 200 ) { # Use HTML::TreeBuilder to parse HTML in $body # Without the exchange Google returns a list of possible matches # For example AAPL will give you a list of links that will # include AAPL:NASDAQ $tree = HTML::TreeBuilder->new; if ($tree->parse_content($body)) { # # Get link with exchange appended (MUTF|NYSE|NASDAQ|NYSEAMERICAN|BATS|HKG) $taglink = $tree->look_down(_tag => 'a', href => qr!^./quote/$ucstock:(MUTF|NYSE|NASDAQ|NYSEAMERICAN|BATS|HKG)!); if ($taglink) { $link = $taglink->attr('href'); $link =~ s|\./quote|quote|; ($exchange = $link) =~ s/.*${ucstock}://; } else { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "$stock not found on Google Finance"; next; } } else { # Could not parse body into tree $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Could not parse HTML returned from $url."; next; } # Found a link that looks like STOCK:EXCHANGE # Fetch that link and parse $url = $GOOGLE_URL . $link; $reply = $ua->get($url); if ($reply->code ne "200") { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock from $url"; next; } # Parse returned HTML $body = decode('UTF-8', $reply->content); unless ($tree->parse_content($body)) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Cannot parse HTML from $url"; next; } ### Tree: $tree # Look for div tag with data-last-price attribute $taglink = $tree->look_down(_tag => 'div', 'data-last-price' => qr|[0-9.]+|); unless ($taglink) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Cannot find price data in $url"; next; } $last = $taglink->attr('data-last-price'); # Google does not include .00 if the price is a whole dollar amount unless ( $last =~ /\./ ) { $last = $last . '.00'; } # Also fix missing cents (15.30 will be 15.3 in the HTML) if ( $last =~ /\d+\.\d$/ ) { $last = $last . '0'; } $time = $taglink->attr('data-last-normal-market-timestamp'); $currency = $taglink->attr('data-currency-code'); my ( undef, undef, undef, $mday, $mon, $year, undef, undef, undef ) = localtime($time); $date = sprintf("%d/%02d/%02d", $year + 1900, $mon + 1, $mday); $info{ $stock, 'method' } = 'googleweb'; $info{ $stock, 'last' } = $last; $info{ $stock, 'currency' } = $currency; $info{ $stock, 'exchange' } = $exchange; $quoter->store_date(\%info, $stock, { isodate => $date}); $info{ $stock, 'success' } = 1; } else { # HTTP Request failed (code != 200) $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; __END__ =head1 NAME Finance::Quote::GoogleWeb - Obtain quotes from Google Finance Web Pages =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch("googleweb", "aapl"); # Only query googleweb %info = $q->fetch("nyse", "ge"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from L<https://www.google.com/finance/>. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "googleweb" in the argument list to Finance::Quote->new(). This module provides "googleweb", "bats", "nyse", and "nasdaq" fetch methods. =head1 LABELS RETURNED The following labels are returned: =over =item name =item symbol =item last =item date =item currency =item method =back =head1 AVAILABLE EXCHANGES While the Google Finance web pages contain price information from other stock exchanges, this module currently retrieves last trade prices for securities listed on the NYSE, American, BATS, and NASDAQ stock exchanges. U.S. Mutual Funds quotes can also be retrieved with this module. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/GoldMoney.pm���������������������������������������������������0000644�0001750�0001750�00000017250�15003302667�021401� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2000, Volker Stuerzl <volker.stuerzl@gmx.de> # Copyright (C) 2006, Klaus Dahlke <klaus.dahlke@gmx.de> # Copyright (C) 2008, Stephan Ebelt <stephan.ebelt@gmx.de> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # $Id: $ package Finance::Quote::GoldMoney; require 5.005; use HTTP::Request::Common; use JSON; use strict; use warnings; our $VERSION = '1.65'; # VERSION sub methods { return ( goldmoney => \&goldmoney ); } sub labels { return ( goldmoney => [qw/exchange name date isodate price method/] ); } # goldmoney($quoter, @symbols) # # - get 'gold' and 'silver' spot rates from goldmoney.com # - error out properly (that is: ignore) all other symbols # sub goldmoney { my $quoter = shift; my @symbols = @_; return unless @symbols; my $ua = $quoter->user_agent; # Set the ua to be blank. GoldMOney are using CloudFlare who block # the default useragent. $ua->agent(''); my ( %symbolhash, @q, %info ); my ( $html_string, $te, $table_gold, $table_silver, $table_platinum, $gold_gg, $gold_oz, $silver_oz, $platinum_oz, $platinum_pg, $currency ); my $_want_gold = 0; my $_want_silver = 0; my $_want_platinum = 0; # - feed all requested symbols into %info (to be returned later) # - set error state to false by default # - see if a gold or silver rate is requested foreach my $s (@symbols) { $info{ $s, 'success' } = 0; $info{ $s, 'exchange' } = 'goldmoney.com'; $info{ $s, 'method' } = 'goldmoney'; $info{ $s, 'symbol' } = $s; if ( $s eq 'gold' ) { $_want_gold = 1; } elsif ( $s eq 'silver' ) { $_want_silver = 1; } elsif ( $s eq 'platinum' ) { $_want_platinum = 1; } else { $info{ $s, 'errormsg' } = "No data returned (note: this module only works for 'gold' and 'silver')"; } } # get the JSON of the prices. Currently getting sell price, if ( $_want_gold or $_want_silver or $_want_platinum ) { my $currency = $quoter->{"currency"} || 'EUR'; my $GOLDMONEY_URL = "http://www.goldmoney.com/metal/prices/currentSpotPrices?currency=" . lc($currency) . "&units=grams&price=bid"; my $response = $ua->request( GET $GOLDMONEY_URL); if ( $response->is_success ) { $html_string = $response->content; my $json = from_json($html_string); $table_gold = $json->{spotPrices}[0]; $table_silver = $json->{spotPrices}[1]; $table_platinum = $json->{spotPrices}[2]; } else { # retrieval error - flag an error and return right away foreach my $s (@symbols) { %info = _goldmoney_error( @symbols, 'HTTP error: ' . $response->status_line ); return wantarray() ? %info : \%info; } return wantarray() ? %info : \%info; } # get gold rate # if ($_want_gold) { # assemble final dataset # - take "now" as date/time as the site is always current and does # not provide this explicitly - so there is a time-slip $quoter->store_date( \%info, 'gold', { isodate => _goldmoney_time('isodate') } ); $info{ 'gold', 'time' } = _goldmoney_time('time'); $info{ 'gold', 'name' } = 'Gold Spot'; $info{ 'gold', 'last' } = $table_gold->{spotPrice}; $info{ 'gold', 'price' } = $table_gold->{spotPrice}; $info{ 'gold', 'currency' } = $currency; $info{ 'gold', 'success' } = 1; } # get silver rate # if ($_want_silver) { $quoter->store_date( \%info, 'silver', { isodate => _goldmoney_time('isodate') } ); $info{ 'silver', 'time' } = _goldmoney_time('time'); $info{ 'silver', 'name' } = 'Silver Spot'; $info{ 'silver', 'last' } = $table_silver->{spotPrice}; $info{ 'silver', 'price' } = $table_silver->{spotPrice}; $info{ 'silver', 'currency' } = $currency; $info{ 'silver', 'success' } = 1; } # get platinum rate # if ($_want_platinum) { # assemble final dataset # - take "now" as date/time as the site is always current and does # not provide this explicitly - so there is a time-slip $quoter->store_date( \%info, 'platinum', { isodate => _goldmoney_time('isodate') } ); $info{ 'platinum', 'time' } = _goldmoney_time('time'); $info{ 'platinum', 'name' } = 'Platinum Spot'; $info{ 'platinum', 'last' } = $table_platinum->{spotPrice}; $info{ 'platinum', 'price' } = $table_platinum->{spotPrice}; $info{ 'platinum', 'currency' } = $currency; $info{ 'platinum', 'success' } = 1; } } return wantarray() ? %info : \%info; } # - populate %info with errormsg and status code set for all requested symbols # - return a hash ready to pass back to fetch() sub _goldmoney_error { my @symbols = shift; my $msg = shift; my %info; foreach my $s (@symbols) { $info{ $s, "success" } = 0; $info{ $s, "errormsg" } = $msg; } return (%info); } # - return current 'isodate' and 'time' string sub _goldmoney_time { my $want = shift; my @now = localtime(); my $str; if ( $want eq 'isodate' ) { $str = sprintf( '%4d-%02d-%02d', $now[5] + 1900, $now[4] + 1, $now[3] ); } elsif ( $want eq 'time' ) { $str = sprintf( '%02d:%02d:%02d', $now[2], $now[1], $now[0] ); } return ($str); } 1; =head1 NAME Finance::Quote::GoldMoney - obtain spot rates from GoldMoney. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %rates = $q->fetch('goldmoeny','gold', 'silver', 'platinum'); =head1 DESCRIPTION This module obtains current spot rates for 'gold', 'silver' and 'platinum' from Goldmoney (http://www.goldmoney.com). All other symbols are ignored. Information returned by this module is governed by Net Transactions Ltd.'s terms and conditions. This module is *not* affiliated with the company in any way. Use at your own risk. =head1 LABELS RETURNED The following labels are returned by Finance::Quote::GoldMoney: - exchange - name - date, time - price (per gram), - currency =head1 SEE ALSO GoldMoney (Net Transactions Ltd.), http://www.goldmoney.com/ =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Union.pm�������������������������������������������������������0000644�0001750�0001750�00000012550�15003302667�020572� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2000, Volker Stuerzl <volker.stuerzl@gmx.de> # Copyright (C) 2002, Rainer Dorsch <rainer.dorsch@informatik.uni-stuttgart.de> # Copyright (C) 2022, Andre Joost <andrejoost@gmx.de> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code derived from Padzensky's work on package Finance::YahooQuote, # but extends its capabilites to encompas a greater number of data sources. # # This code was developed as part of GnuCash <http://www.gnucash.org/> # # $Id: Union.pm,v 1.3 2005/03/20 01:44:13 hampton Exp $ package Finance::Quote::Union; use strict; use LWP::UserAgent; use HTTP::Request::Common; use POSIX qw(strftime); our $VERSION = '1.65'; # VERSION our $UNION_URL1 = "https://legacy-apps.union-investment.de/handle?generate=true&action=doDownloadSearch&start_time="; # Date format 27.07.2022&end_time=01.08.2022 our $UNION_URL2 ="&csvformat=us&choose_indi_fondsnames="; our $DISPLAY = 'Union - German Funds'; our @LABELS = qw/exchange name date isodate price method currency/; our $METHODHASH = {subroutine => \&unionfunds, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( unionfunds => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } # ======================================================================= # The unionfunds routine gets quotes of UNION funds (Union Invest) # On their website UNION provides a csv file in the format # label1,label2,... # name1,symbol1,buy1,bid1,... # name2,symbol2,buy2,bid2,... # ... # # This subroutine was written by Volker Stuerzl <volker.stuerzl@gmx.de> sub unionfunds { my $quoter = shift; my @funds = @_; return unless @funds; my $ua = $quoter->user_agent; my (%fundhash, @q, %info, $tempdate); # create hash of all funds requested foreach my $fund (@funds) { $fundhash{$fund} = 0; my $endtime = POSIX::strftime ("%d.%m.%Y" , localtime()); my $epoc = time(); $epoc = $epoc - 7 * 24 * 60 * 60; # one week before of current date. my $starttime = POSIX::strftime ("%d.%m.%Y" , localtime($epoc)); my $url = $UNION_URL1 . $starttime."&end_time=" . $endtime . $UNION_URL2 . $fund; # Website not supplying intermediate certificate causing # GET to fail $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00); # get csv data my $response = $ua->request(GET $url); if ($response->is_success) { # process csv data foreach (split('\015?\012',$response->content)) { @q = split(/,/) or next; next unless (defined $q[1]); if (exists $fundhash{$q[1]}) { $fundhash{$q[1]} = 1; $info{$q[1], "exchange"} = "UNION"; $info{$q[1], "name"} = $q[0]; $info{$q[1], "symbol"} = $q[1]; $info{$q[1], "price"} = $q[4]; $info{$q[1], "last"} = $q[4]; $quoter->store_date(\%info, $q[1], {eurodate => $q[6]}); $info{$q[1], "method"} = "unionfunds"; $info{$q[1], "currency"} = $q[2]; $info{$q[1], "success"} = 1; } } } } # check to make sure a value was returned for every fund requested foreach my $fund (keys %fundhash) { if ($fundhash{$fund} == 0) { $info{$fund, "success"} = 0; $info{$fund, "errormsg"} = "No data returned"; } } return wantarray() ? %info : \%info; } 1; # UNION provides a csv file named historische-preise.csv on # <https://www.union-investment.de/fonds_depot/fonds-finden/preise-berechnen#HistorischeTagespreise> # containing the prices of a selction of all their funds for a selected period. __END__ =head1 NAME Finance::Quote::Union - Obtain quotes from UNION (Union Investment). =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("unionfunds","DE0008491002"); =head1 DESCRIPTION This module obtains information about UNION managed funds. Information returned by this module is governed by UNION's terms and conditions. Note that previous versions of the module required the WKN, now the ISIN is needed as symbol value. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::UNION: exchange, name, date, price, last. =head1 SEE ALSO UNION (Union Investment), https://www.union-investment.de/ =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Bloomberg.pm���������������������������������������������������0000644�0001750�0001750�00000006004�15003302667�021407� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Finance::Quote::Bloomberg; use strict; use LWP::UserAgent; use HTTP::Request::Common; use HTTP::CookieJar::LWP (); use HTML::TreeBuilder; use Encode; use JSON; our $VERSION = '1.65'; # VERSION use vars qw($BLOOMBERG_URL); $BLOOMBERG_URL = 'https://www.bloomberg.com/quote/'; sub methods { return (bloomberg => \&bloomberg); } { my @labels = qw/method name last currency symbol isodate/; sub labels { return (bloomberg => \@labels); } } sub bloomberg { my $quoter = shift; my @symbols = @_; return unless @symbols; my ($ua, $cj, $reply, $url, %funds, $te, $table, $row, @value_currency, $name); foreach my $symbol (@symbols) { $name = $symbol; $url = $BLOOMBERG_URL; $url = $url . $name; $cj = HTTP::CookieJar::LWP->new; $ua = LWP::UserAgent->new(cookie_jar => $cj); my @ns_headers = ( 'User-Agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0', 'Referer' => 'https://www.bloomberg.com/', 'Accept' => '*/*', 'Accept-Language' => 'en-US,en;q=0.5', 'Pragma' => 'no-cache', ); $reply = $ua->get($url, @ns_headers); unless ($reply->is_success) { $funds{$symbol, "success"} = 0; $funds{$symbol, "errormsg"} = "HTTP failure"; next; } eval { my $tree = HTML::TreeBuilder->new_from_content(decode_utf8 $reply->content); my $json = encode_utf8 (($tree->look_down(_tag=>'script', 'id'=>'__NEXT_DATA__')->content_list())[0]); my $json_decoded = decode_json $json; my $json_quote = $json_decoded->{'props'}{'pageProps'}{'quote'}; my $desc = $json_quote->{'longName'}; my $price = $json_quote->{'price'}; my $curr = $json_quote->{'issuedCurrency'}; my $date = $json_quote->{'lastUpdate'}; $curr =~ s/.*[(](.*)[)].*/$1/; $price =~ s/,//g; if ($curr eq "GBp") { $curr = "GBP"; $price = $price / 100; } $date = $1 if $date =~ m|([0-9]{4}-[0-9]{2}-[0-9]{2})T.*|; $funds{$name, 'method'} = 'bloomberg'; $funds{$name, 'name'} = $desc; $funds{$name, 'last'} = $price; $funds{$name, 'currency'} = $curr; $funds{$name, 'symbol'} = $name; $quoter->store_date(\%funds, $name, {isodate => $date}); $funds{$name, 'success'} = 1; }; if ($@) { $funds{$symbol, "success"} = 0; $funds{$symbol, "errormsg"} = "parse error"; } } return %funds if wantarray; return \%funds; } 1; =head1 NAME Finance::Quote::Bloomberg - Obtain fund prices from Bloomberg.com =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %fundinfo = $q->fetch("bloomberg", "security"); =head1 DESCRIPTION This module obtains information about fund prices from www.bloomberg.com. =head1 SECURITY NAME The security string must match the format expected by the site, such as 'AAPL:US' not 'AAPL'. =head1 LABELS RETURNED Labels returned by this module include: name, last, currency, symbol, isodate =head1 SEE ALSO Finance::Quote =cut ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/AlphaVantage.pm������������������������������������������������0000644�0001750�0001750�00000035363�15003302667�022044� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This module is based on the Finance::Quote::yahooJSON module # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # 2024-04-07: Commented call to sleep_before_query in get_content # subroutine. AlphaVantage is not throttling API calls, # but limits usage with free API key to 25 per day. # 2019-12-01: Added additional labels for net and p_change. Set # close to previous close as returned in the JSON. # Bruce Schuck (bschuck at asgard hyphen systems dot com) package Finance::Quote::AlphaVantage; use strict; use JSON qw( decode_json ); use HTTP::Request::Common; our $VERSION = '1.65'; # VERSION # Alpha Vantage recommends that API call frequency does not extend far # beyond ~1 call per second so that they can continue to deliver # optimal server-side performance: # https://www.alphavantage.co/support/#api-key our @alphaqueries=(); my $maxQueries = { quantity =>5 , seconds => 60}; # no more than x # queries per y # seconds, based on # https://www.alphavantage.co/support/#support my $ALPHAVANTAGE_URL = 'https://www.alphavantage.co/query?function=GLOBAL_QUOTE&datatype=json'; my %currencies_by_suffix = ( # Country City/Exchange Name '.US' => "USD", # USA AMEX, Nasdaq, NYSE '.A' => "USD", # USA American Stock Exchange (ASE) '.B' => "USD", # USA Boston Stock Exchange (BOS) '.N' => "USD", # USA Nasdaq Stock Exchange (NAS) '.O' => "USD", # USA NYSE Stock Exchange (NYS) '.OB' => "USD", # USA OTC Bulletin Board '.PK' => "USD", # USA Pink Sheets '.X' => "USD", # USA US Options '.BA' => "ARS", # Argentina Buenos Aires '.VI' => "EUR", # Austria Vienna '.AX' => "AUD", # Australia '.SA' => "BRL", # Brazil Sao Paolo '.BR' => "EUR", # Belgium Brussels '.TO' => "CAD", # Canada Toronto '.TRV' => "CAD", # Canada Toronto Venture '.V' => "CAD", # Canada Toronto Venture '.TRT' => "CAD", # Canada Toronto '.SN' => "CLP", # Chile Santiago '.SS' => "CNY", # China Shanghai '.SZ' => "CNY", # Shenzhen '.CO' => "DKK", # Denmark Copenhagen '.PA' => "EUR", # France Paris '.BE' => "EUR", # Germany Berlin '.BM' => "EUR", # Bremen '.D' => "EUR", # Dusseldorf '.F' => "EUR", # Frankfurt '.FRK' => "EUR", # Frankfurt '.H' => "EUR", # Hamburg '.HA' => "EUR", # Hanover '.MU' => "EUR", # Munich '.DEX' => "EUR", # Xetra '.ME' => "RUB", # Russia Moscow '.SG' => "EUR", # Stuttgart '.DE' => "EUR", # XETRA '.HK' => "HKD", # Hong Kong '.BO' => "INR", # India Bombay '.CL' => "INR", # Calcutta '.NS' => "INR", # National Stock Exchange '.JK' => "IDR", # Indonesia Jakarta '.I' => "EUR", # Ireland Dublin '.TA' => "ILS", # Israel Tel Aviv '.MI' => "EUR", # Italy Milan '.KS' => "KRW", # Korea Stock Exchange '.KQ' => "KRW", # KOSDAQ '.KL' => "MYR", # Malaysia Kuala Lampur '.MX' => "MXP", # Mexico '.NZ' => "NZD", # New Zealand '.AS' => "EUR", # Netherlands Amsterdam '.AMS' => "EUR", # Netherlands Amsterdam '.OL' => "NOK", # Norway Oslo '.LM' => "PEN", # Peru Lima '.IN' => "EUR", # Portugal Lisbon '.SI' => "SGD", # Singapore '.BC' => "EUR", # Spain Barcelona '.BI' => "EUR", # Bilbao '.MF' => "EUR", # Madrid Fixed Income '.MC' => "EUR", # Madrid SE CATS '.MA' => "EUR", # Madrid '.VA' => "EUR", # Valence '.ST' => "SEK", # Sweden Stockholm '.STO' => "SEK", # Sweden Stockholm '.HE' => "EUR", # Finland Helsinki '.S' => "CHF", # Switzerland Zurich '.TW' => "TWD", # Taiwan Taiwan Stock Exchange '.TWO' => "TWD", # OTC '.BK' => "THB", # Thialand Thailand Stock Exchange '.TH' => "THB", # ??? From Asia.pm, (in Thai Baht) '.L' => "GBP", # United Kingdom London '.IL' => "USD", # United Kingdom London USD*100 '.VX' => "CHF", # Switzerland '.SW' => "CHF", # Switzerland ); our $DISPLAY = 'AlphaVantage'; our $FEATURES = {'API_KEY' => 'registered user API key'}; our @LABELS = qw/date isodate open high low close volume last net p_change/; our $METHODHASH = {subroutine => \&alphavantage, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methodinfo { return ( alphavantage => $METHODHASH, canada => $METHODHASH, usa => $METHODHASH, nyse => $METHODHASH, nasdaq => $METHODHASH, ); } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub sleep_before_query { # wait till we can query again my $q = $maxQueries->{quantity}-1; if ( $#alphaqueries >= $q ) { my $time_since_x_queries = time()-$alphaqueries[$q]; # print STDERR "LAST QUERY $time_since_x_queries\n"; if ($time_since_x_queries < $maxQueries->{seconds}) { my $sleeptime = ($maxQueries->{seconds} - $time_since_x_queries) ; # print STDERR "SLEEP $sleeptime\n"; sleep( $sleeptime ); # print STDERR "CONTINUE\n"; } } unshift @alphaqueries, time(); pop @alphaqueries while $#alphaqueries>$q; # remove unnecessary data # print STDERR join(",",@alphaqueries)."\n"; } sub alphavantage { my $quoter = shift; my @stocks = @_; my $quantity = @stocks; my ( %info, $reply, $url, $code, $desc, $body, $ticker, $adjust ); my $ua = $quoter->user_agent(); my $launch_time = time(); # Since the JSON returned by the GLOBAL_QUOTE API does not specify # the currency of the price data, there is no way to determine the # correct currency without an additional call to the SYMBOL_SEARCH # API. To avoid even slower throttling, this module expects the # user to know which securties from certain countries may be traded # in the non-ISO4217 currency. # Example is LSE traded GBP.L and GBPG.L. GBP.L is traded in GBX, # which is also known as GBp, and GBPG.L is traded in the iso-4217 # currency GBP (Great Britain Pounds). # The user will add ".X" to symbols to return GBX priced securities # as GBP. my $token = exists $quoter->{module_specific_data}->{alphavantage}->{API_KEY} ? $quoter->{module_specific_data}->{alphavantage}->{API_KEY} : $ENV{"ALPHAVANTAGE_API_KEY"}; foreach my $stock (@stocks) { if ($stock =~ /\.X$/) { $adjust = 1; ($ticker = $stock) =~ s/\.X$//; } else { $adjust = 0; $ticker = $stock } if ( !defined $token ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = 'An AlphaVantage API is required. Get an API key at https://www.alphavantage.co'; next; } $url = $ALPHAVANTAGE_URL . '&apikey=' . $token . '&symbol=' . $ticker; my $get_content = sub { # sleep_before_query(); my $time=int(time()-$launch_time); # print STDERR "Query at:".$time."\n"; $reply = $ua->request( GET $url); $code = $reply->code; $desc = HTTP::Status::status_message($code); $body = $reply->content; # print STDERR "AlphaVantage returned: $body\n"; }; &$get_content(); if ($code != 200) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $desc; next; } my $json_data; eval {$json_data = JSON::decode_json $body}; if ($@) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $@; } my $try_cnt = 0; while (($try_cnt < 5) && ($json_data->{'Note'})) { # print STDERR "NOTE:".$json_data->{'Note'}."\n"; # print STDERR "ADDITIONAL SLEEPING HERE !"; sleep (20); &$get_content(); eval {$json_data = JSON::decode_json $body}; $try_cnt += 1; } if ( !$json_data ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = 'Query returned no JSON'; next; } elsif ( $json_data->{'Error Message'} ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $json_data->{'Error Message'}; next; } elsif ( $json_data->{'Information'} ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $json_data->{'Information'}; next; } my $quote = $json_data->{'Global Quote'}; if ( ! $quote ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = "json_data does not contain Global Quote"; next; } # %ts holds data as # { # "Global Quote": { # "01. symbol": "SOLB.BR", # "02. open": "104.2000", # "03. high": "104.9500", # "04. low": "103.4000", # "05. price": "104.0000", # "06. volume": "203059", # "07. latest trading day": "2019-11-29", # "08. previous close": "105.1500", # "09. change": "-1.1500", # "10. change percent": "-1.0937%" # } # } # remove trailing percent sign, if present $quote->{'10. change percent'} =~ s/\%$//; $info{ $stock, 'success' } = 1; $info{ $stock, 'success' } = 1; $info{ $stock, 'symbol' } = $quote->{'01. symbol'}; $info{ $stock, 'open' } = $quote->{'02. open'}; $info{ $stock, 'high' } = $quote->{'03. high'}; $info{ $stock, 'low' } = $quote->{'04. low'}; $info{ $stock, 'last' } = $quote->{'05. price'}; $info{ $stock, 'volume' } = $quote->{'06. volume'}; $info{ $stock, 'close' } = $quote->{'08. previous close'}; $info{ $stock, 'net' } = $quote->{'09. change'}; $info{ $stock, 'p_change' } = $quote->{'10. change percent'}; $info{ $stock, 'method' } = 'alphavantage'; $quoter->store_date( \%info, $stock, { isodate => $quote->{'07. latest trading day'} } ); # deduce currency if ( $ticker =~ /(\..*)/ ) { my $suffix = uc $1; if ( $currencies_by_suffix{$suffix} ) { $info{ $stock, 'currency' } = $currencies_by_suffix{$suffix}; # divide .X quotes by 100 if ( $adjust == 1 ) { foreach my $field ( $quoter->default_currency_fields ) { next unless ( $info{ $stock, $field } ); $info{ $stock, $field } = $quoter->scale_field( $info{ $stock, $field }, 0.01 ); } } # divide USD quotes by 100 if suffix is '.IL' if ( ($suffix eq '.IL') && ($info{$stock,'currency'} eq 'USD') ) { foreach my $field ( $quoter->default_currency_fields ) { next unless ( $info{ $stock, $field } ); $info{ $stock, $field } = $quoter->scale_field( $info{ $stock, $field }, 0.01 ); } } } } else { $info{ $stock, 'currency' } = 'USD'; } $info{ $stock, "currency_set_by_fq" } = 1; } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::AlphaVantage - Obtain quotes from https://alphavantage.co =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new('AlphaVantage', alphavantage => {API_KEY => 'your-alphavantage-api-key'}); %info = $q->fetch('alphavantage', 'IBM', 'AAPL'); =head1 DESCRIPTION This module fetches information from https://www.alphavantage.co. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "AlphaVantage" in the argument list to Finance::Quote->new(). This module provides the "alphavantage" fetch method. =head1 API_KEY https://www.alphavantage.co requires users to register and obtain an API key, which is also called a token. The token is a sequence of random characters. The API key may be set by either providing a module specific hash to Finance::Quote->new as in the above example, or by setting the environment variable ALPHAVANTAGE_API_KEY. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::AlphaVantage : symbol, open, close, high, low, last, volume, method, isodate, currency. =head1 CAVEATs Since the JSON returned by the GLOBAL_QUOTE API does not specify the currency of the price data, there is no way to determine the correct currency without an additional call to the SYMBOL_SEARCH API. To avoid even slower throttling, this module expects the user to know which securties from certain countries may be traded in the non-ISO4217 currency. An example are London Stock Exchange traded GBP.L (Global Petroleum Limited) and GBPG.L (Goldman Sachs Access UK Gilts 1-10 Years UCITS ETF CLASS GBP (Dist)). GBP.L is traded in GBX, which is also known as GBp (Great Britain Pence), and GBPG.L is traded in the iso-4217 currency GBP (Great Britain Pounds). The user will need to add ".X" to symbols to return GBX priced securities as GBP. For the example above the user would use the symbol GBP.L.X in the call to the alphavantage method for the prices to be output as GBP. =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/MarketWatch.pm�������������������������������������������������0000644�0001750�0001750�00000012126�15003302667�021713� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::MarketWatch; use strict; use warnings; use Encode qw(decode); use HTTP::Request::Common; use HTML::TreeBuilder; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION my $MW_URL = 'https://www.marketwatch.com/investing/stock/'; sub methods { return (marketwatch => \&marketwatch, nyse => \&marketwatch, nasdaq => \&marketwatch); } our @labels = qw/symbol name last date currency method/; sub labels { return (marketwatch => \@labels, nyse => \@labels, nasdaq => \@labels); } sub marketwatch { my $quoter = shift; my @stocks = @_; my (%info, $tree, $url, $reply); my $ua = $quoter->user_agent(); my @ua_headers = ( 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36', 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Encoding' => 'gzip, deflate, sdch', 'Accept-Language' => 'en-US,en;q=0.8', ); foreach my $stock (@stocks) { $url = $MW_URL . $stock; $reply = $ua->get($url, @ua_headers); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = $reply->decoded_content; ### Body: $body my ($name, $last, $open, $date, $currency, $metatag); my (%datehash, @timearray, $timestring); $info{ $stock, "symbol" } = $stock; if ( $code == 200 ) { # Use HTML::TreeBuilder to parse HTML in $body $tree = HTML::TreeBuilder->new; if ($tree->parse($body)) { $tree->eof; if ($metatag = $tree->look_down(_tag => 'meta', name => 'name')) { $info{ $stock, 'name' } = $metatag->attr('content'); } else { $info{ $stock, 'success'} = 0; $info{ $stock, 'errormsg' } = 'Error retrieving quote for $stock.'; next; } $metatag = $tree->look_down(_tag => 'meta', name => 'tickerSymbol'); $info{ $stock, 'symbol' } = $metatag->attr('content'); $metatag = $tree->look_down(_tag => 'meta', name => 'price'); ($info{ $stock, 'last' } = $metatag->attr('content')) =~ s/[^0-9.]//g; $metatag = $tree->look_down(_tag => 'meta', name => 'priceCurrency'); $info{ $stock, 'currency' } = $metatag->attr('content'); $metatag = $tree->look_down(_tag => 'meta', name => 'quoteTime'); $date = $metatag->attr('content'); @timearray = split / /, $date; $timearray[1] =~ s/[^0-9]//g; %datehash = ( year => $timearray[2], month => $timearray[0], day => $timearray[1] ); ($timestring = $timearray[4]) =~ s|\.||g; $quoter->store_date(\%info, $stock, \%datehash); $info{ $stock, 'success' } = 1; $info{ $stock, 'method' } = 'marketwatch'; } else { $tree->eof; $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Could not parse HTML returned from $url."; } } else { # HTTP Request failed (code != 200) $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; __END__ =head1 NAME Finance::Quote::MarketWatch - Obtain quotes from MarketWatch Website =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch("marketwatch", "aapl"); # Only query marketwatch %info = $q->fetch("nyse", "f"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from L<https://www.marketwatch.com/>. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "marketwatch" in the argument list to Finance::Quote->new(). This module provides "marketwatch", "nyse", and "nasdaq" fetch methods. =head1 LABELS RETURNED The following labels are returned: =over =item name =item symbol =item last =item date =item currency =back ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/HU.pm����������������������������������������������������������0000644�0001750�0001750�00000016103�15003302667�020014� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # HU.pm # # Version 0.3 - Fixed BAMOSZ website scraping and download stocks # directly from www.BET.hu # This version based on ZA.pm module # # Zoltan Levardy <zoltan at levardy dot org> 2008, 2009 # Kristof Marussy <kris7topher at gmail dot com> 2014 package Finance::Quote::HU; use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use HTTP::Request::Common; use HTML::TableExtract; use Encode; use JSON; use Web::Scraper; our $VERSION = '1.65'; # VERSION my $BAMOSZ_MAINURL = "http://www.bamosz.hu/"; my $BAMOSZ_URL = $BAMOSZ_MAINURL . "alapoldal?isin="; my $BSE_MAINURL = "http://www.bet.hu/"; my $BSE_URL = $BSE_MAINURL . '/oldalak/ceg_adatlap/$security/'; sub methods { return ( hufund => \&bamosz, bamosz => \&bamosz, hustock => \&bse, bse => \&bse, bet => \&bse, hu => \&hu, hungary => \&hu ); } sub labels { my @fundlabels = qw/symbol method source name currency isin date isodate price last/; my @stocklabels = qw/symbol method source currency isin date isodate price open close high low p_change last/; my @alllabels = ( @stocklabels, "name" ); return ( hufund => \@fundlabels, bamosz => \@fundlabels, hustock => \@stocklabels, bse => \@stocklabels, bet => \@stocklabels, hu => \@alllabels, hungary => \@alllabels ); } sub hu { my $quoter = shift; my @symbols = @_; my %info; for my $symbol (@symbols) { my %bse_info = bse( $quoter, $symbol ); if ( $bse_info{ $symbol, "success" } ) { %info = ( %info, %bse_info ); next; } my %bamosz_info = bamosz( $quoter, $symbol ); if ( $bamosz_info{ $symbol, "success" } ) { %info = ( %info, %bamosz_info ); next; } $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "Fetch from bse or bamosz failed"; } return wantarray() ? %info : \%info; } sub bse { my $quoter = shift; my @symbols = @_; my %info; my $ua = $quoter->user_agent; for my $symbol (@symbols) { eval { my $url = $BSE_URL . $symbol; my $response = $ua->request(GET $url); ### bse response : $response->content die "Request error" unless $response->is_success; die "Failed to find JSON data" unless $response->content =~ m|window[.]dataSourceResults=([{].+[}])</script>|; my $json = decode_json $1; ### json : $json ### keys : keys %{$json} my @profile_key = grep {/CompanyProfileDataSource;table=left/} keys %{$json}; die "Failed to process JSON" unless @profile_key == 1; my $profile = $json->{$profile_key[0]}; ### profile : $profile foreach my $term (@{$profile}) { $info{$symbol, "close"} = hu_decimal($term->{value}) if $term->{title} eq "El\x{151}z\x{151} z\x{e1}r\x{f3}\x{e1}r"; $info{$symbol, "high"} = hu_decimal($term->{value}) if $term->{title} eq "Napi maximum"; $info{$symbol, "low"} = hu_decimal($term->{value}) if $term->{title} eq "Napi minimum"; } my @trade_key = grep {/CompanyProfileDataSource;table=trades/} keys %{$json}; die "Failed to process JSON" unless @trade_key == 1; my $trade = $json->{$trade_key[0]}; $info{$symbol, "last"} = hu_decimal($trade->[0]->{price}); my $processor = scraper { process '//*[@id="cp_tab_content_2"]/div[3]/div[3]/table/tbody/tr[1]/td[2]/span', 'ticker' => 'TEXT'; process '//*[@id="cp_tab_content_2"]/div[3]/div[3]/table/tbody/tr[2]/td[2]/span', 'isin' => 'TEXT'; process '//*[@id="cp_tab_content_2"]/div[3]/div[3]/table/tbody/tr[4]/td[2]/span', 'currency' => 'TEXT'; process '//*[@id="cp_tab_content_2"]/div[1]/div/div/div[2]/div/div[2]/span[2]', 'date' => 'TEXT'; }; my $data = $processor->scrape($response); ### data : $data $info{ $symbol, "symbol" } = $data->{ticker}; $info{ $symbol, "isin" } = $data->{isin}; $info{ $symbol, "currency" } = $data->{currency}; $quoter->store_date(\%info, $symbol, {isodate => $data->{date}}); $info{ $symbol, "method" } = "bse"; $info{ $symbol, "source" } = $BSE_MAINURL; $info{ $symbol, "success" } = 1; }; if ($@) { ### bse error : $@ $info{ $symbol, "method"} = "bse"; $info{ $symbol, "errormsg"} = $@; $info{ $symbol, "success"} = 0; } } return wantarray() ? %info : \%info; } sub bamosz { my $quoter = shift; my @symbols = @_; my %info; my $ua = $quoter->user_agent; for my $symbol (@symbols) { $info{ $symbol, "method" } = "bamosz"; $info{ $symbol, "source" } = $BAMOSZ_MAINURL; $info{ $symbol, "success" } = 0; my $url = $BAMOSZ_URL . $symbol; my $response = $ua->request( GET $url); ### bamosz response : $response unless ( $response->is_success ) { $info{ $symbol, "errormsg" } = "Request error"; next; } my $te = HTML::TableExtract->new( attribs => { class => "dataTable" } ); $te->parse( decode_utf8( $response->content ) ); unless ( $te->first_table_found ) { $info{ $symbol, "errormsg" } = "No dataTable found"; next; } my $ts = $te->table( 0, 0 ); $info{ $symbol, "name" } = $ts->cell( 0, 1 ); my $isin = $ts->cell( 2, 1 ); $info{ $symbol, "symbol" } = $isin; $info{ $symbol, "isin" } = $isin; $info{ $symbol, "currency" } = $ts->cell( 3, 1 ); my $price = hu_decimal( $ts->cell( 5, 1 ) ); $info{ $symbol, "price" } = $price; $info{ $symbol, "last" } = $price; my $date = $ts->cell( 6, 1 ); $quoter->store_date( \%info, $symbol, { isodate => $date } ); $info{ $symbol, "success" } = 1; } return wantarray() ? %info : \%info; } sub trim { my $s = shift; if ($s) { $s =~ s/^\s+//; $s =~ s/\s+$//; return $s; } else { return ''; } } sub hu_decimal { my $s = shift; if ($s) { $s =~ s/[^\d,-]//g; $s =~ s/,/./; return $s; } else { return ''; } } 1; =head1 NAME Finance::Quote::HU - Obtain Hungarian Securities from www.bet.hu and www.bamosz.hu =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; # Don't know anything about failover yet... =head1 DESCRIPTION This module obtains information about Hungarian Securities. Share fetched from www.bet.hu, while mutual funds retrieved from www.bamosz.hu. Stocks are searched by ticker while mutual funds may only searched by ISIN. =head1 LABELS RETURNED Information available may include the following labels: method source name symbol currency date last price low high open close p_change =head1 SEE ALSO Budapest Stock Exchange (BET) website - http://www.bet.hu BAMOSZ website - http://www.bamosz.hu/ Finance::Quote =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/MorningstarUK.pm�����������������������������������������������0000644�0001750�0001750�00000022411�15003302667�022242� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # MorningstarUK.pm # # Obtains quotes for UK Unit Trusts from http://morningstar.co.uk/ - please # refer to the end of this file for further information. # # author: Martin Sadler (martinsadler@users.sourceforge.net) # # version: 0.1 Initial version - 01 April 2013 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::MorningstarUK; require 5.005; use strict; use warnings; # URLs use vars qw($VERSION $MSTARUK_NEXT_URL $MSTARUK_LOOK_UP $MSTARUK_MAIN_URL); use LWP::Simple; use LWP::UserAgent; use HTTP::Request::Common; use HTTP::Cookies; our $VERSION = '1.65'; # VERSION $MSTARUK_MAIN_URL = "http://www.morningstar.co.uk"; $MSTARUK_LOOK_UP = "http://www.morningstar.co.uk/uk/funds/SecuritySearchResults.aspx?search="; $MSTARUK_NEXT_URL = "http://www.morningstar.co.uk/uk/funds/snapshot/snapshot.aspx?id="; # FIXME - sub methods { return (morningstaruk => \&mstaruk_fund, mstaruk => \&mstaruk_fund, ukfunds => \&mstaruk_fund); } { my @labels = qw/name currency last date time price nav source iso_date method net p_change success errormsg/; sub labels { return (morningstaruk => \@labels, mstaruk => \@labels, ukfunds => \@labels); } } # # ======================================================================= sub mstaruk_fund { my $quoter = shift; my @symbols = @_; return unless @symbols; my %fundquote; my $ua = $quoter->user_agent; my $cj = HTTP::Cookies->new(); $ua->cookie_jar( $cj ); foreach (@symbols) { my $code = $_; my $code_type = "** Invalid **"; if ($code =~ m/^[a-zA-Z]{2}[a-zA-Z0-9]{9}\d(.*)/ && !$1) { $code_type = "ISIN"; } elsif ($code =~ m/^[a-zA-Z0-9]{6}\d(.*)/ && !$1 ) { $code_type = "SEDOL"; } elsif ($code =~ m/^[a-zA-Z]{4,6}(.*)/ && !$1) { $code_type = "MEXID"; } # current version can only use ISIN - report an error and exit if any other type if ($code_type ne "ISIN") { $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - invalid symbol"; next; } $fundquote {$code,"success"} = 1; # ever the optimist.... $fundquote {$code,"errormsg"} = "Success"; # perform the look-up - if not found, return with error my $webdoc = get($MSTARUK_LOOK_UP.$code); if (!$webdoc) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data"; next; } $fundquote {$code, "symbol"} = $code; $fundquote {$code, "source"} = $MSTARUK_MAIN_URL; # Find name by regexp my ($name, $nexturl, $isin); if ($webdoc =~ m[<td class="msDataText searchLink"><a href="(.*?)">(.*?)</a></td><td class="msDataText searchIsin"><span>[a-zA-Z]{2}[a-zA-Z0-9]{9}\d(.*)</span></td>] ) { $nexturl = $1; $name = $2; $isin = $3; } if (!defined($name)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find fund name"; $name = "*** UNKNOWN ***"; # ... and continue } $fundquote {$code, "name"} = $name; # set name if (!defined($nexturl)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data"; next; } # modify $nexturl to remove html escape encoding for the Ampersand (&) character $nexturl =~ s/&/&/; # Now need to look-up next page using $next_url $webdoc = get($MSTARUK_MAIN_URL.$nexturl); if (!$webdoc) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to retrieve fund data"; next; } # Find date, currency and price all in one table row my ($currency, $date, $price, $pchange); if ($webdoc =~ m[<td class="line heading">NAV<span class="heading"><br />([0-9]{2}/[0-9]{2}/[0-9]{4})</span>.*([A-Z]{3}).([0-9\.]+).*Day Change[^%]*>([0-9\.\-]+)] ) { $date = $1; $currency = $2; $price = $3; $pchange = $4; } if (!defined($pchange)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find net or %-age change"; # set to (minus)zero $pchange = -0.00; # ... and continue } $fundquote {$code, "p_change"} = $pchange; # set %-change if (!defined($date)) { # not a serious error - don't report it .... # $fundquote {$code,"success"} = 0; # ... but set a useful message .... $fundquote {$code,"errormsg"} = "Warning - failed to find a date"; # use today's date $quoter->store_date(\%fundquote, $code, {today => 1}); # ... and continue } else { $quoter->store_date(\%fundquote, $code, {eurodate => $date}); } if (!defined($price)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to find a price"; next; } if (!defined($currency)) { # serious error, report it and give up $fundquote {$code,"success"} = 0; $fundquote {$code,"errormsg"} = "Error - failed to find a currency"; next; } # defer setting currency and price until we've dealt with possible GBX currency... # Calculate net change - it's not included in the morningstar factsheets my $net = ($price * $pchange) / 100 ; # deal with GBX pricing of UK unit trusts if ($currency eq "GBX") { $currency = "GBP" ; $price = $price / 100 ; $net = $net / 100 ; } # now set prices and currency $fundquote {$code, "price"} = $price; $fundquote {$code, "last"} = $price; $fundquote {$code, "nav"} = $price; $fundquote {$code, "net"} = $net; $fundquote {$code, "currency"} = $currency; # Set a dummy time as gnucash insists on having a valid format my $time = "12:00"; # set to Midday if no time supplied ??? # gnucash insists on having a valid-format $fundquote {$code, "time"} = $time; # set time $fundquote {$code, "method"} = "mstaruk"; # set method } return wantarray ? %fundquote : \%fundquote; } 1; =head1 NAME Finance::Quote::MorningstarUK - Obtain UK Unit Trust quotes from morningstar.co.uk. =head1 SYNOPSIS $q = Finance::Quote->new; %info = $q->fetch("mstaruk","<isin> ..."); # Only query morningstar.co.uk using ISINs %info = $q->fetch("ukfunds","<isin> ..."); # Failover to other sources =head1 DESCRIPTION This module fetches information from the MorningStar Funds service, http://morningstar.com/uk/. There are many UK Unit Trusts and OEICs quoted, as well as many Offshore Funds and Exhange Traded Funds (ETFs). It converts any funds quoted in GBX (pence) to GBP, dividing the price by 100 in the process. Funds are identified by their ISIN code. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "mstaruk" in the argument list to Finance::Quote->new(). Information obtained by this module may be covered by funds.ft.com terms and conditions See http://morningstar.co.ukfor details. =head2 Stocks And Indices This module provides both the "mstaruk" and "ukfunds" fetch methods for fetching UK and Offshore Unit Trusts and OEICs prices and other information from funds.ft.com. Please use the "ukfunds" fetch method if you wish to have failover with future sources for UK and Offshore Unit Trusts and OEICs - the author has plans to develop Finance::Quote modules for other source providing unit trust prices. Using the "mstaruk" method will guarantee that your information only comes from the morningstar.co.uk website. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::MorningstarUK : name, currency, last, date, time, price, nav, source, method, iso_date, net, p_change, success, errormsg. =head1 SEE ALSO Morning Star websites, http://morningstar.co.uk =head1 AUTHOR Martin Sadler, E<lt>martinsadler@users.sourceforge.netE<gt> =head1 COPYRIGHT AND LICENSE Copyright (C) 2010 by Martin Sadler This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available. =cut __END__ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/YahooJSON.pm���������������������������������������������������0000644�0001750�0001750�00000033137�15003302667�021257� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=4 sw=4 noai ic showmode showmatch: # This module is based on the Finance::Quote::BSERO module # It was first called BOMSE but has been renamed to yahooJSON # since it gets a lot of quotes besides Indian # # The code has been modified by Abhijit K to # retrieve stock information from Yahoo Finance through json calls # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::YahooJSON; use strict; use JSON qw( decode_json ); use vars qw($VERSION $YIND_URL_HEAD $YIND_URL_TAIL); use HTTP::Request::Common; use Time::Piece; use HTTP::Cookies; use URI::Escape; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; our $VERSION = '1.65'; # VERSION # Required to successfully read extra long headers returned from yahoo my %OPTS = @LWP::Protocol::http::EXTRA_SOCK_OPTS; $OPTS{MaxLineLength} = 16384; @LWP::Protocol::http::EXTRA_SOCK_OPTS = %OPTS; my $YIND_URL_HEAD = 'https://query2.finance.yahoo.com/v11/finance/quoteSummary/?symbols='; my $YIND_URL_TAIL = '&modules=price,summaryDetail,defaultKeyStatistics'; #my $browser = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'; my $browser = 'Mozilla/5.0'; our $DISPLAY = 'YahooJSON'; our @LABELS = qw/name last date isodate volume currency method exchange type div_yield eps pe year_range open high low close/; our $METHODHASH = {subroutine => \&yahoo_json, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( yahoo_json => $METHODHASH, yahoojson => $METHODHASH, nyse => $METHODHASH, nasdaq => $METHODHASH, usa => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub yahoo_json { my $quoter = shift; my @stocks = @_; my ( %info, $reply, $url); my ( $my_date, $amp_stocks, $symbol ); my $ua = $quoter->user_agent(); my $cookie_jar = HTTP::Cookies->new; # Redirect handler deals with cookie consent workflow applicable to EU countries # credit to John Weber from Germany for injecting redirect handler my $gcrumb = ""; $ua->add_handler("response_redirect", sub { my($response, $ua, $h) = @_; # Check where we've been redirected and act accordingly my $redirect_uri = URI->new($response->header("Location")); if ($redirect_uri->path eq "/consent") { # Remember gcrumb value for collectConsent request later my %params = $redirect_uri->query_form; $gcrumb = $params{'gcrumb'}; } elsif ($redirect_uri->path eq "/v2/collectConsent") { my %params = $redirect_uri->query_form; my $sessionId = $params{'sessionId'}; # Turn this request into a POST with form data to confoo accept cookies my $request = POST($redirect_uri, [ 'csrfToken' => $gcrumb, 'sessionId' => $sessionId, 'originalDoneUrl' => 'https://www.yahoo.com/?guccounter=1', 'namespace' => 'yahoo', # For the EU consent, either can : # 'agree' => 'agree' # to it or 'reject' => 'reject' ]); return $request; } return; }); $ua->cookie_jar($cookie_jar); $ua->agent($browser); # Tell user agent to redirect POSTs in additional to GET AND HEAD $ua->requests_redirectable(['GET', 'HEAD', 'POST']); # get necessary cookies $reply = $ua->get('https://www.yahoo.com/', "Accept" => "text/html"); if ($reply->code != 200) { foreach my $symbol (@stocks) { $info{$symbol, "success"} = 0; $info{$symbol, "errormsg"} = "Error accessing www.yahoo.com: $@"; } return wantarray() ? %info : \%info; } # get the crumb that corrosponds to cookies retrieved $reply = $ua->request(GET 'https://query2.finance.yahoo.com/v1/test/getcrumb'); if ($reply->code != 200) { foreach my $symbol (@stocks) { $info{$symbol, "success"} = 0; $info{$symbol, "errormsg"} = "Error accessing query2.finance.yahoo.com/v1/test/getcrumb: $@"; } return wantarray() ? %info : \%info; } my $crumb = uri_escape($reply->content); ### [<now>] cookie_jar : $cookie_jar ### [<now>] crumb : $crumb foreach my $stocks (@stocks) { # Issue 202 - Fix symbols with Ampersand # Can also be written as # $amp_stocks = $stocks =~ s/&/%26/gr; ($amp_stocks = $stocks) =~ s/&/%26/g; $url = $YIND_URL_HEAD . $amp_stocks . '&crumb=' . $crumb . $YIND_URL_TAIL; $reply = $ua->request(GET $url); ### [<now>] url : $url ### [<now>] reply : $reply my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = $reply->content; #Response variables available: #Response code: $code #Response description: $desc #HTTP Headers: $headers #Response body $body $info{ $stocks, "symbol" } = $stocks; if ( $code == 200 ) { #HTTP_Response succeeded - parse the data my $json_data = JSON::decode_json $body; # Requests for invalid symbols sometimes return 200 with an empty # JSON result array my $json_data_count = scalar @{ $json_data->{'quoteSummary'}{'result'} }; if ( $json_data_count < 1 ) { $info{ $stocks, "success" } = 0; $info{ $stocks, "errormsg" } = "Error retrieving quote for $stocks - no listing for this name found. Please check symbol and the two letter extension (if any)"; } else { my $json_resources_price = $json_data->{'quoteSummary'}{'result'}[0]{'price'}; my $json_resources_summaryDetail = $json_data->{'quoteSummary'}{'result'}[0]{'summaryDetail'}; my $json_resources_defaultKeyStatistics = $json_data->{'quoteSummary'}{'result'}[0]{'defaultKeyStatistics'}; # TODO: Check if $json_response_type is "Quote" # before attempting anything else my $json_symbol = $json_resources_price->{'symbol'}; # || $json_resources->{'resource'}{'fields'}{'symbol'}; my $json_volume = $json_resources_price->{'regularMarketVolume'}{'raw'}; my $json_timestamp = $json_resources_price->{'regularMarketTime'}; my $json_name = $json_resources_price->{'shortName'}; my $json_type = $json_resources_price->{'quoteType'}; my $json_price = $json_resources_price->{'regularMarketPrice'}{'raw'}; $info{ $stocks, "success" } = 1; $info{ $stocks, "exchange" } = $json_resources_price->{'exchangeName'}; $info{ $stocks, "method" } = "yahoo_json"; $info{ $stocks, "name" } = $stocks . ' (' . $json_name . ')'; $info{ $stocks, "type" } = $json_type; $info{ $stocks, "last" } = $json_price; $info{ $stocks, "currency"} = $json_resources_price->{'currency'}; $info{ $stocks, "volume" } = $json_volume; # Add extra fields using names as per yahoo to make it easier # to switch from yahoo to yahooJSON # Code added by goodvibes { # turn off warnings in this block to fix bogus # 'Use of uninitialized value in multiplication' warning # in Strawberry perl 5.18.2 in Windows local $^W = 0; $info{ $stocks, "div_yield" } = $json_resources_summaryDetail->{'trailingAnnualDividendYield'}{'raw'} =~ m/^[\d,\.]+$/ ? $json_resources_summaryDetail->{'trailingAnnualDividendYield'}{'raw'} * 100 : 0.0; } $info{ $stocks, "eps"} = $json_resources_defaultKeyStatistics->{'trailingEps'}{'raw'}; # $json_resources_summaryDetail->{'epsTrailingTwelveMonths'}; $info{ $stocks, "pe"} = $json_resources_summaryDetail->{'trailingPE'}{'raw'}; $info{ $stocks, "year_range"} = sprintf("%12s - %s", $json_resources_summaryDetail->{"fiftyTwoWeekLow"}{'raw'}, $json_resources_summaryDetail->{'fiftyTwoWeekHigh'}{'raw'}); $info{ $stocks, "open"} = $json_resources_price->{'regularMarketOpen'}{'raw'}; $info{ $stocks, "high"} = $json_resources_price->{'regularMarketDayHigh'}{'raw'}; $info{ $stocks, "low"} = $json_resources_price->{'regularMarketDayLow'}{'raw'}; $info{ $stocks, "close"} = $json_resources_summaryDetail->{'regularMarketPreviousClose'}{'raw'}; # The Yahoo JSON interface can London prices in # GBp (pence) instead of GBP (pounds) and the Yahoo Base # had a hack to convert them to GBP. In theory all the # callers would correctly handle GBp as not the same as GBP, # but they don't, and since we had the hack before, # let's add it back now. # Convert GBp or GBX to GBP (divide price by 100). if ( ($info{$stocks,"currency"} eq "GBp") || ($info{$stocks,"currency"} eq "GBX")) { foreach my $field ($quoter->get_default_currency_fields) { next unless ( $info{ $stocks, $field } ); $info{$stocks, $field} = $quoter->scale_field($info{ $stocks, $field }, 0.01); } $info{ $stocks, "currency"} = "GBP"; } # Apply the same hack for Johannesburg Stock Exchange # (JSE) prices as they are returned in ZAc (cents) # instead of ZAR (rands). JSE symbols are suffixed # with ".JO" when querying Yahoo e.g. ANG.JO if ( $info{$stocks,"currency"} eq "ZAc") { foreach my $field ($quoter->get_default_currency_fields) { next unless ( $info{ $stocks, $field } ); $info{$stocks, $field} = $quoter->scale_field($info{ $stocks, $field }, 0.01); } $info{ $stocks, "currency"} = "ZAR"; } # Apply the same hack for Tel Aviv Stock Exchange # (TASE) prices as they are returned in ILA (Agorot) # instead of ILS (Shekels). TASE symbols are suffixed # with ".TA" when querying Yahoo e.g. POLI.TA if ( $info{$stocks,"currency"} eq "ILA") { foreach my $field ($quoter->get_default_currency_fields) { next unless ( $info{ $stocks, $field } ); $info{$stocks, $field} = $quoter->scale_field($info{ $stocks, $field }, 0.01); } $info{ $stocks, "currency"} = "ILS"; } # MS Windows strftime() does not support %T so use %H:%M:%S # instead. $my_date = localtime($json_timestamp)->strftime('%d.%m.%Y %H:%M:%S'); $quoter->store_date( \%info, $stocks, { eurodate => $my_date } ); } } #HTTP request fail else { $info{ $stocks, "success" } = 0; $info{ $stocks, "errormsg" } = "Error retrieving quote for $stocks. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; =head1 NAME Finance::Quote::YahooJSON - Obtain quotes from Yahoo Finance through JSON call =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch('yahoo_json','SBIN.NS'); =head1 DESCRIPTION This module fetches information from Yahoo as JSON This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "YahooJSON" in the argument list to Finance::Quote->new(). This module provides the "yahoo_json" fetch method. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::YahooJSON : name, last, isodate, volume, currency, method, exchange, type, div_yield eps pe year_range open high low close. =head1 SEE ALSO =cut ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Tradegate.pm���������������������������������������������������0000644�0001750�0001750�00000021373�15003302667�021405� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::Tradegate; use strict; use warnings; use HTML::Entities; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use Web::Scraper; our $VERSION = '1.65'; # VERSION my $TRADEGATE_URL = 'https://web.s-investor.de/app/detail.htm?boerse=TDG&isin='; our $DISPLAY = 'Tradegate'; # see https://web.s-investor.de/app/webauswahl.jsp for "Institutsliste" our $FEATURES = {'INST_ID' => 'Institut Id (default: 0000057 for "Sparkasse Krefeld")' }; our @LABELS = qw/symbol isin last close exchange volume open price change p_change date time low high/; our $METHODHASH = {subroutine => \&tradegate, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methodinfo { return ( tradegate => $METHODHASH, europe => $METHODHASH, ); } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub tradegate { my $quoter = shift; my $inst_id = exists $quoter->{module_specific_data}->{tradegate}->{INST_ID} ? $quoter->{module_specific_data}->{tradegate}->{INST_ID} : '0000057'; my $ua = $quoter->user_agent(); my $agent = $ua->agent; $ua->agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'); my %info; my $url; my $reply; foreach my $symbol (@_) { eval { my $url = $TRADEGATE_URL . $symbol . '&INST_ID=' . $inst_id; my $symlen = length($symbol); my $tree = HTML::TreeBuilder->new_from_url($url); my $lastvalue = $tree->look_down('class'=>'si_seitenbezeichnung'); if (defined($lastvalue)) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = 'Invalid institute id. Get a valid institute id from https://web.s-investor.de/app/webauswahl.jsp'; } else { $lastvalue = $tree->look_down('id'=>'kursdaten'); my $td1 = ($lastvalue->look_down('_tag'=>'td'))[1]; my @child = $td1->content_list; my $isin = $child[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[3]; @child = $td1->content_list; my $sharename = $child[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[5]; @child = $td1->content_list; my $exchange = $child[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[7]; @child = $td1->content_list; my $date = substr($child[0], 0, 8); my $time = substr($child[0], 9, 5); # CE(S)T $td1 = ($lastvalue->look_down('_tag'=>'td'))[9]; @child = $td1->content_list; my $price = $child[0]; $price =~ s/\.//g; $price =~ s/,/\./; my $encprice = encode_entities($price); my @splitprice= split ('&',$encprice); $price = $splitprice[0]; $td1 = ($lastvalue->look_down('_tag'=>'td'))[11]; @child = $td1->content_list; my $currency = $child[0]; $currency =~ s/Euro/EUR/; $td1 = ($lastvalue->look_down('_tag'=>'td'))[13]; @child = $td1->content_list; my $volume = $child[0]; my $table = ($lastvalue->look_down('_tag'=>'table'))[1]; $td1 = ($table->look_down('_tag'=>'td'))[1]; @child = $td1->content_list; my $low = $child[0]; $low =~ s/\.//g; $low =~ s/,/\./; $td1 = ($table->look_down('_tag'=>'td'))[2]; @child = $td1->content_list; my $high = $child[0]; $high =~ s/\.//g; $high =~ s/,/\./; my @searchvalue = $tree->look_down('class'=>'contentBox oneColum'); my $isFound = 0; foreach (@searchvalue) { if (ref(($_->content_list)[0]) eq "HTML::Element" and ($_->content_list)[0]{'_content'}[0]{'_content'}[0] eq 'Aktuelle Vergleichszahlen') { $isFound = 1; #-- open $td1 = ($_->look_down('_tag'=>'td'))[4]; @child = $td1->content_list; my $open = $child[0]; $open =~ s/\.//g; $open =~ s/,/\./; #-- change (absolute change) $td1 = ($_->look_down('_tag'=>'td'))[13]; @child = $td1->content_list; my $change = $child[0]; $change =~ s/\.//g; $change =~ s/,/\./; my $encchange = encode_entities($change); my @splitcchange= split ('&',$encchange); $change = $splitcchange[0]; #-- p_change (relative change) $td1 = ($_->look_down('_tag'=>'td'))[16]; @child = $td1->content_list; my $p_change =$child[0]; $p_change =~ s/[\.|%]//g; $p_change =~ s/,/\./; #-- close $td1 = ($_->look_down('_tag'=>'td'))[34]; @child = $td1->content_list; my $close = $child[0]; $close =~ s/\.//g; $close =~ s/,/\./; my $encclose = encode_entities($close); my @splitclose= split ('&',$encclose); $close = $splitclose[0]; $info{$symbol, 'success'} = 1; $info{$symbol, 'method'} = 'Tradegate'; $info{$symbol, 'symbol'} = $isin; $info{$symbol, 'isin'} = $isin; $info{$symbol, 'name'} = $sharename; $info{$symbol, 'exchange'} = $exchange; $info{$symbol, 'last'} = $price; $info{$symbol, 'price'} = $price; $info{$symbol, 'close'} = $close; $info{$symbol, 'change'} = $change; $info{$symbol, 'p_change'} = $p_change; $info{$symbol, 'volume'} = $volume; $info{$symbol, 'currency'} = $currency; #$info{$symbol, 'date'} = $date; $quoter->store_date(\%info, $symbol, {eurodate => $date}); $info{$symbol, 'time'} = $time; $info{$symbol, 'open'} = $open; $info{$symbol, 'low'} = $low; $info{$symbol, 'high'} = $high; } } if (!$isFound) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = "Error retreiving $symbol: $@"; } } }; if ($@) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = "Error retreiving $symbol: $@"; } } $ua->agent($agent); return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::Tradegate - Obtain quotes from S-Investor platform. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; or $q = Finance::Quote->new('Tradegate', 'tradegate' => {INST_ID => 'your institute id'}); %info = Finance::Quote->fetch("Tradegate", "DE000ENAG999"); # Only query Tradegate %info = Finance::Quote->fetch("europe", "brd"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from https://s-investor.de/, the investment platform of the German Sparkasse banking group. It fetches share prices from tradegate, a major German trading platform. Suitable for shares and ETFs that are traded in Germany. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "Tradegate" in the argument list to Finance::Quote->new(). This module provides "Tradegate" and "europe" fetch methods. Information obtained by this module may be covered by s-investor.de terms and conditions. =head1 INST_ID https://s-investor.de/ supports different institute IDs. The default value "0000057" is used (Krefeld) if no institute ID is provided. A list of institute IDs is provided here: https://web.s-investor.de/app/webauswahl.jsp The INST_ID may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 LABELS RETURNED The following labels are returned: currency exchange last method success symbol isin date time volume price close open low high change p_change ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/TSP.pm���������������������������������������������������������0000644�0001750�0001750�00000011734�15003302667�020153� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # Copyright (C) 2001, Rob Sessink <rob_ses@users.sourceforge.net> # Copyright (C) 2004, Frank Mori Hess <fmhess@users.sourceforge.net> # Trent Piepho <xyzzy@spekeasy.org> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code is derived from version 0.9 of the AEX.pm module. use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; package Finance::Quote::TSP; use vars qw( $TSP_URL $TSP_MAIN_URL @HEADERS ); use LWP::UserAgent; use HTTP::Request::Common; use POSIX; our $VERSION = '1.65'; # VERSION # URLs of where to obtain information $TSP_URL = 'https://www.tsp.gov/data/fund-price-history.csv'; $TSP_MAIN_URL = 'http://www.tsp.gov'; @HEADERS = ('user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36'); sub methods { return (tsp => \&tsp) } { my @labels = qw/name date isodate currency close/; sub labels { return (tsp => \@labels); } } sub format_name { my $name = shift; $name =~ s/ //g; $name = lc($name); return $1 if $name =~ /^(.)fund$/; return $name; } # ============================================================================== sub tsp { my $quoter = shift; my @symbols = @_; return unless @symbols; my %info; my @line; # Ask for the last 7 days my $startdate = strftime("%Y-%m-%d", localtime (time - 7*24*3600)); my $enddate = strftime("%Y-%m-%d", localtime time); my $ua = $quoter->user_agent; my $url = "$TSP_URL?startdate=$startdate&enddate=$enddate&Lfunds=1&InvFunds=1&download=1"; my $reply = $ua->get($url, @HEADERS); ### [<now>] url : $url ### [<now>] reply: $reply unless (($reply->is_success) && (@line = split(/\n/, $reply->content)) && (@line > 1)) { foreach my $symbol (@symbols) { $info{$symbol, "success"} = 0; $info{$symbol, "errormsg"} = "TSP fetch failed. No data for $symbol."; } ### Failure: %info return wantarray ? %info : \%info; } my @header = split(/,/, $line[0]); my %column = map { format_name($header[$_]) => $_ } 0 .. $#header; my @latest = split(/,/, $line[1]); ### [<now>] header: @header ### [<now>] column: %column ### [<now>] latest: @latest foreach (@symbols) { my $symbol = lc $_; if(exists $column{$symbol}) { $info{$_, 'success'} = 1; $quoter->store_date(\%info, $_, {isodate => $latest[$column{'date'}]}); ($info{$_, 'last'} = $latest[$column{$symbol}]) =~ s/[^0-9]*([0-9.,]+).*/$1/s; $info{$_, 'currency'} = 'USD'; $info{$_, 'method'} = 'tsp'; $info{$_, 'source'} = $TSP_MAIN_URL; $info{$_, 'symbol'} = $_; } else { $info{$_, 'success'} = 0; $info{$_, 'errormsg'} = "Fund not found"; } } return %info if wantarray; return \%info; } 1; =head1 NAME Finance::Quote::TSP - Obtain fund prices for US Federal Government Thrift Savings Plan =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch('tsp','c'); #get value of C - Common Stock Index Investment Fund %info = $q->fetch('tsp','l2040'); #get value of the L2040 Lifecycle Fund %info = $q->fetch('tsp','lincome'); #get value of the LINCOME Lifecycle Fund =head1 DESCRIPTION This module fetches fund information from the "Thrift Savings Plan" http://www.tsp.gov The quote symbols are C common stock fund F fixed income fund G government securities fund I international stock fund S small cap stock fund LX lifecycle fund X (eg 2050 or INCOME) =head1 LABELS RETURNED The following labels are returned by Finance::Quote::TSP : date latest date, eg. "21/02/10" isodate latest date, eg. "2010-02-21" last latest available price, eg. "16.1053" currency "USD" method "tsp" source TSP URL =head1 SEE ALSO Thrift Savings Plan, http://www.tsp.gov =cut ������������������������������������Finance-Quote-1.65/lib/Finance/Quote/NSEIndia.pm����������������������������������������������������0000644�0001750�0001750�00000013071�15003302667�021073� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::NSEIndia; require 5.010; use strict; use POSIX qw(strftime); use IO::Uncompress::Unzip qw(unzip $UnzipError); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION use vars qw($NSE_MAIN_URL $NSE_URL); $NSE_MAIN_URL = "https://www.nseindia.com"; $NSE_URL = "https://nsearchives.nseindia.com"; sub methods { return ( 'india' => \&nseindia, 'nseindia' => \&nseindia ); } sub labels { my @labels = qw/close last high low open prevclose exchange name/; return ( india => \@labels, nseindia => \@labels ); } sub nseindia { my $quoter = shift; my @symbols = @_; return unless @symbols; my (%info, $errormsg, $fh, $ua, $url, $reply); my $output; my @array; $ua = $quoter->user_agent; # Disable redirects - server redirects instead of 404. $ua->requests_redirectable([]); # Set the ua to be blank. Server blocks default useragent. $ua->agent(''); # Try to fetch last 10 days for (my ($days, $now) = (0, time()); $days < 10; $days++) { # Ex: https://nsearchives.nseindia.com/content/cm/BhavCopy_NSE_CM_0_0_0_20240718_F_0000.csv.zip my @lt = localtime($now - $days*24*60*60); my ($date, $day, $month, $year, $url, $req, $output); $date = strftime "%Y%m%d", @lt; $url = sprintf("https://nsearchives.nseindia.com/content/cm/BhavCopy_NSE_CM_0_0_0_%s_F_0000.csv.zip", $date); $req = HTTP::Request->new(GET => $url); #added for fileless $reply = $ua->request($req); # print "$url", $reply->is_success, $reply->status_line, "\n"; #DEBUG if ($reply->is_success or $reply->code == 304) { last; } } if (!$reply->is_success and $reply->code != 304) { $errormsg = "HTTP failure : " . $reply->status_line; } if (!$errormsg) { #Does not use temp files. Fileless into variable $output if (! unzip \$reply->content => \$output) { $errormsg = "Unzip error : $UnzipError"; } else { @array = split("\n", $output); } } if ($errormsg) { foreach my $symbol (@symbols) { $info{$symbol, "success"} = 0; $info{$symbol, "errormsg"} = $errormsg; } return wantarray() ? %info : \%info; } # Create a hash of all stocks requested my %symbolhash; foreach my $symbol (@symbols) { $symbolhash{$symbol} = 0; } my $csvhead; my @headhash; # TradDt,BizDt,Sgmt,Src,FinInstrmTp,FinInstrmId,ISIN,TckrSymb,SctySrs,XpryDt,FininstrmActlXpryDt,StrkPric,OptnTp,FinInstrmNm,OpnPric,HghPric,LwPric,ClsPric,LastPric,PrvsClsgPric,UndrlygPric,SttlmPric,OpnIntrst,ChngInOpnIntrst,TtlTradgVol,TtlTrfVal,TtlNbOfTxsExctd,SsnId,NewBrdLotQty,Rmks,Rsvd1,Rsvd2,Rsvd3,Rsvd4 $csvhead = $array[0]; @headhash = split /\s*,s*/, $csvhead; foreach (@array) { my @data = split /\s*,s*/; my %datahash; my $symbol; @datahash{@headhash} = @data; if (exists $symbolhash{$datahash{"TckrSymb"}}) { $symbol = $datahash{"TckrSymb"}; } elsif(exists $symbolhash{$datahash{"ISIN"}}) { $symbol = $datahash{"ISIN"}; } else { next; } $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'close'} = $datahash{"ClsPric"}; $info{$symbol, 'last'} = $datahash{"LastPric"}; $info{$symbol, 'high'} = $datahash{"HghPric"}; $info{$symbol, 'low'} = $datahash{"LwPric"}; $info{$symbol, 'open'} = $datahash{"OpnPric"}; $info{$symbol, 'prevclose'} = $datahash{"PrvsClsgPric"}; $info{$symbol, 'name'} = $datahash{"FinInstrmNm"}; $quoter->store_date(\%info, $symbol, {isodate => $datahash{"TradDt"}}); $info{$symbol, 'method'} = 'nseindia'; $info{$symbol, 'currency'} = 'INR'; $info{$symbol, 'exchange'} = 'NSE'; $info{$symbol, 'success'} = 1; } foreach my $symbol (@symbols) { unless (exists $info{$symbol, 'success'}) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Stock not found on NSE.'; } } return wantarray ? %info : \%info; } 1; =head1 NAME Finance::Quote::NSEIndia - Obtain quotes from NSE India. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new(); %info = $q->fetch('nseindia', 'TCS'); # Only query NSE. %info = $q->fetch('india', 'TCS'); # Failover to other sources OK. =head1 DESCRIPTION This module obtains information about shares listed on the National Stock Exchange of India Ltd. Source is the daily bhav copy (zipped CSV). This module provides both the "nseindia" and "india" fetch methods. Please use the "india" fetch method if you wish to have failover with other sources for Indian stocks (such as BSE). =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::NSEIndia: close, last, high, low, open, prevclose, exchange, name =head1 SEE ALSO National Stock Exchange of India Ltd., http://www.nseindia.com/ =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Sinvestor.pm���������������������������������������������������0000644�0001750�0001750�00000030034�15003302667�021473� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::Sinvestor; use strict; use warnings; use HTML::Entities; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use Web::Scraper; use Encode qw(encode_utf8); our $VERSION = '1.65'; # VERSION my $SINVESTOR_URL = 'https://web.s-investor.de/app/detail.htm?isin='; our $DISPLAY = 'Sinvestor'; # see https://web.s-investor.de/app/webauswahl.jsp for "Institutsliste" our $FEATURES = {'INST_ID' => 'Institut Id (default: 0000057 for "Sparkasse Krefeld")', 'EXCHANGE' => 'select market place (i.e. "gettex", "Xetra", "Tradegate")'}; our @LABELS = qw/symbol isin last close exchange exchanges volume open price change p_change date time low high ask bid source/; our $METHODHASH = {subroutine => \&sinvestor, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methodinfo { return ( sinvestor => $METHODHASH, europe => $METHODHASH, ); } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub strip_exchange_name { my $exchange_name = shift; $exchange_name =~ s/^(Zürich) - SWX$/$1/g; return $exchange_name; } sub get_utf8_string { return encode_utf8(shift); } sub get_de_number { my $number = shift; $number =~ s/\.//g; $number =~ s/,/\./; return ($number =~ /^([-+]?[0-9]+(\.[0-9]+)?)/) ? $1 : undef; } sub get_iso_currency { my ($currency) = @_; $currency =~ s/Euro/EUR/ and return $currency; $currency =~ s/Schweizer Franken/CHF/ and return $currency; $currency =~ s/US Dollar/USD/ and return $currency; $currency =~ s/^Taiwan-Dollar.*/TWD/ and return $currency; # TODO: add more currencies .. } sub td_search { my ($tree, $text) = @_; my $td = $tree->look_down('_tag'=>'td', sub { $_[0]->as_text eq $text }) or return; my @tds = $td->parent->look_down('_tag'=>'td'); return map { $tds[$_]->as_text } (1..$#tds); } sub sinvestor { my $quoter = shift; my $inst_id = exists $quoter->{module_specific_data}->{sinvestor}->{INST_ID} ? $quoter->{module_specific_data}->{sinvestor}->{INST_ID} : '0000057'; my $exchange_code = exists $quoter->{module_specific_data}->{sinvestor}->{EXCHANGE} ? $quoter->{module_specific_data}->{sinvestor}->{EXCHANGE} : undef; my %exchange2code = ( 'Gettex' => 'GTX', 'Tradegate' => 'TDG', 'Stuttgart' => 'STU', 'Frankfurt' => 'FRA', 'Xetra' => 'GER', 'Paris' => 'PAR', "Düsseldorf" => 'DUS', 'Berlin' => 'BER', 'Hamburg' => 'HAM', 'Hannover' => 'HAN', "München" => 'MUN', 'Refinitiv CT' => 'RCT', 'Zürich' => 'SWX', 'Zürich - SWX' => 'SWX', 'NASDAQ - Pink Sheets' => 'PNK', # undef means: this exchange is not selectable by name 'Quotrix' => undef, # '0QT', 'QTX', 'KVG Fondskurse' => undef, # 'LIP', 'Lipper Fondsdaten' => undef, # 'LIP' ); my %exchange2code_uc = ( map { uc($_) => $exchange2code{$_} } keys %exchange2code ); if (defined($exchange_code) and $exchange_code !~ /^[A-Z0-9]{3}$/) { # we need the exchange_code for the querry if (exists $exchange2code_uc{uc($exchange_code)} and defined $exchange2code_uc{uc($exchange_code)}) { $exchange_code = $exchange2code_uc{uc($exchange_code)}; } else { die("unsupported exchange(-code): $exchange_code"); } } my $ua = $quoter->user_agent(); my $agent = $ua->agent; $ua->agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'); my %info; foreach my $symbol (@_) { my $url = $SINVESTOR_URL . $symbol . '&INST_ID=' . $inst_id; $url .= '&boerse=' . $exchange_code if defined($exchange_code); eval { my $tree = HTML::TreeBuilder->new_from_url($url); my $lastvalue = $tree->look_down('class'=>'si_seitenbezeichnung'); if (defined($lastvalue)) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = 'Invalid institute id. Get a valid institute id from https://web.s-investor.de/app/webauswahl.jsp'; } else { $lastvalue = $tree->look_down('id'=>'kursdaten') or die("Not found"); my $isin = get_utf8_string(td_search($lastvalue, 'ISIN')); my $sharename = get_utf8_string(td_search($lastvalue, 'Bezeichnung')); my $exchange = get_utf8_string(td_search($lastvalue, "B\xf6rse/Contributor")); my $price = get_de_number(td_search($lastvalue, 'Letzter Kurs')); my $currency = get_iso_currency(td_search($lastvalue, "W\xe4hrung")); # TODO: per trade or day? my $volume = get_de_number(td_search($lastvalue, 'Volumen/Trade')); #my $volume = get_de_number(td_search($lastvalue, 'Volumen/Tag')); my $table = ($tree->look_down('_tag'=>'table'))[1]; my ($bid, $ask) = map { get_de_number($_) } td_search($lastvalue, 'Kurs'); $table = $tree->look_down('_tag'=>'table', 'id'=>'detailHandelsplatz'); my %exchanges = (); foreach ($table->look_down('_tag'=>'tr', 'class'=>'si_click_nav rowLink')) { my $a = $_->attr('data-dest'); if ($a and $a =~ /boerse=([^&"]+)/) { my $code = $1; my $name = encode_utf8(($_->look_down('_tag'=>'td'))[0]->as_text); $exchanges{ $name } = $code; $name = strip_exchange_name($name); $exchanges{ $name } = $code if not exists $exchanges{ $name }; } } if (defined($exchange_code)) { my $key = strip_exchange_name($exchange); unless(exists($exchanges{ $key }) and $exchanges{ $key } eq $exchange_code) { die("$symbol not found on marketplace: $exchange_code"); } } my @searchvalue = $tree->look_down('class'=>'contentBox oneColum'); my $isFound = 0; foreach my $t (@searchvalue) { if (ref(($t->content_list)[0]) eq "HTML::Element" and ($t->content_list)[0]{'_content'}[0]{'_content'}[0] eq 'Aktuelle Vergleichszahlen') { $isFound = 1; $info{$symbol, 'success'} = 1; $info{$symbol, 'method'} = 'Sinvestor'; $info{$symbol, 'source'} = $url; $info{$symbol, 'symbol'} = $isin; $info{$symbol, 'isin'} = $isin; $info{$symbol, 'name'} = $sharename; $info{$symbol, 'volume'} = $volume; $info{$symbol, 'currency'} = $currency; $info{$symbol, 'ask'} = $ask; $info{$symbol, 'bid'} = $bid; $info{$symbol, 'exchange'} = $exchange; $info{$symbol, 'exchanges'} = [ grep { exists $exchange2code_uc{uc($_)} and defined $exchange2code_uc{uc($_)} } sort keys %exchanges ]; $info{$symbol, 'last'} = $price; $info{$symbol, 'price'} = $price; # TODO: useless label (and not specified in README.md) $info{$symbol, 'close'} = get_de_number(td_search($t, 'VT-Schluss')); $info{$symbol, 'change'} = get_de_number(td_search($t, 'Diff.')); $info{$symbol, 'p_change'} = get_de_number(td_search($t, 'Diff.%')); $info{$symbol, 'open'} = get_de_number(td_search($t, "Er\xf6ffnung")); $info{$symbol, 'low'} = get_de_number(td_search($t, 'Tief')); $info{$symbol, 'high'} = get_de_number(td_search($t, 'Hoch')); my ($child) = td_search($t, 'Datum/Zeit'); my $date = substr($child, 0, 8); my $time = substr($child, 9, 5); # CE(S)T $quoter->store_date(\%info, $symbol, {eurodate => $date}); $info{$symbol, 'time'} = $1 if $time =~ /^([0-9]{2}:[0-9]{2})/; if (DEBUG) { my %unknown_exchanges = map { $_ => $exchanges{$_} } grep { not exists $exchange2code_uc{uc($_)} } sort keys %exchanges; ### unknown_exchanges: %unknown_exchanges $info{$symbol, '_unknown_exchanges'} = { %unknown_exchanges }; } } } if (!$isFound) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = "Error retreiving $symbol: $@"; } } }; if ($@) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = "Error retreiving $symbol: $@"; } } $ua->agent($agent); return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::Sinvestor - Obtain quotes from S-Investor platform. =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; or $q = Finance::Quote->new('Sinvestor', 'sinvestor' => {INST_ID => 'your institute id'}); or $q = Finance::Quote->new('Sinvestor', 'sinvestor' => {EXCHANGE => 'Xetra'}); %info = Finance::Quote->fetch("Sinvestor", "DE000ENAG999"); # Only query Sinvestor %info = Finance::Quote->fetch("europe", "brd"); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from https://s-investor.de/, the investment platform of the German Sparkasse banking group. It fetches share prices from various marketplaces. The marketplace is returned in the "exchange" field. Suitable for shares, ETFs and funds that are traded in Germany. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "Sinvestor" in the argument list to Finance::Quote->new(). This module provides "Sinvestor" and "europe" fetch methods. Information obtained by this module may be covered by s-investor.de terms and conditions. =head1 EXCHANGE https://www.s-investor.de/ supports different market places. A default is not specified. "Xetra" alias "GER" "Tradegate" alias "TDG" "gettex" alias "GTX" "Berlin" alias "BER" ... any many more ... The EXCHANGE may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 INST_ID https://s-investor.de/ supports different institute IDs. The default value "0000057" is used (Krefeld) if no institute ID is provided. A list of institute IDs is provided here: https://web.s-investor.de/app/webauswahl.jsp The INST_ID may be set by providing a module specific hash to Finance::Quote->new as in the above example (optional). =head1 LABELS RETURNED The following labels are returned: currency exchange exchanges last method source success symbol isin date time volume price close open low high change p_change ask bid ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/SIX.pm���������������������������������������������������������0000644�0001750�0001750�00000013227�15003302667�020147� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::SIX; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use JSON qw( decode_json ); use String::Util qw(trim); use Scalar::Util qw(looks_like_number); our $VERSION = '1.65'; # VERSION our @labels = qw/last date isodate symbol/; sub labels { return ( six => \@labels ); } sub methods { return ( six => \&six ); } sub six { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my %info; foreach my $symbol (@_) { eval { # 1. Search for the security my $url = 'https://www.six-group.com/fqs/snap.json?select=ValorId,PortalSegment,ProductLine&where=PortalSegment=EQ|BO|FU|EP|IN&pagesize=2&match=' . $symbol; my $reply = $ua->get($url); my $search = JSON::decode_json $reply->content; ### Search : $url, $reply->code ### Search : $search # 2. Get security metadata my $valorid = $search->{rowData}->[0][0]; die "$symbol not found" unless defined $valorid; $url = 'https://www.six-group.com/fqs/ref.json?select=DividendEntitlementFlag,FirstTradingDate,LastTradingDate,ISIN,IssuerNameFull,IssuerNameShort,MarketDate,NominalCurrency,NominalValue,NumberInIssue,ProductLine,SecTypeDesc,ShortName,SmallestTradeableUnit,TitleSegment,TitleSegmentDesc,TradingBaseCurrency,ValorNumber,ValorSymbol&where=ValorId=' . $valorid; $reply = $ua->get($url); my $metadata = JSON::decode_json $reply->content; ### Metadata : $url, $reply->code ### Metadata : $metadata my @metacols = @{$metadata->{colNames}}; my %metamap = map {$metacols[$_] => $_} (0 .. $#metacols); my $metarow = $metadata->{rowData}->[0]; $info{$symbol, 'symbol'} = $metarow->[$metamap{ValorSymbol}]; $info{$symbol, 'isin'} = $metarow->[$metamap{ISIN}]; $info{$symbol, 'name'} = $metarow->[$metamap{IssuerNameFull}]; $info{$symbol, 'currency'} = $metarow->[$metamap{TradingBaseCurrency}]; $quoter->store_date(\%info, $symbol, {isodate => $metarow->[$metamap{MarketDate}]}); $url = 'https://www.six-group.com/fqs/movie.json?select=AskPrice,AskVolume,BidPrice,BidVolume,ClosingDelta,ClosingPerformance,ClosingPrice,DailyHighPrice,DailyHighTime,DailyLowPrice,DailyLowTime,LatestTradeVolume,MarketMakers,MarketTime,MidSpread,OffBookTrades,OffBookTurnover,OffBookVolume,OnMarketTrades,OnMarketTurnover,OnMarketVolume,OpeningPrice,PreviousClosingPrice,SwissAtMidTrades,SwissAtMidTurnover,SwissAtMidVolume,TotalVolume,VWAP60Price,YearAgoPerformance,YearlyHighDate,YearlyHighPrice,YearlyLowDate,YearlyLowPrice,YearToDatePerformance,YieldToWorst&where=ValorId=' . $valorid; $reply = $ua->get($url); my $data = JSON::decode_json $reply->content; ### Data : $url, $reply->code ### Data : $data my @datacols = @{$data->{colNames}}; my %datamap = map {$datacols[$_] => $_} (0 .. $#datacols); my $datarow = $data->{rowData}->[0]; $info{$symbol, 'ask'} = $datarow->[$datamap{AskPrice}] if $datarow->[$datamap{AskPrice}] and looks_like_number($datarow->[$datamap{AskPrice}]); $info{$symbol, 'close'} = $datarow->[$datamap{ClosingPrice}] if $datarow->[$datamap{ClosingPrice}]; $info{$symbol, 'high'} = $datarow->[$datamap{DailyHighPrice}] if $datarow->[$datamap{DailyHighPrice}]; $info{$symbol, 'low'} = $datarow->[$datamap{DailyLowPrice}] if $datarow->[$datamap{DailyLowPrice}]; $info{$symbol, 'open'} = $datarow->[$datamap{OpeningPrice}] if $datarow->[$datamap{OpeningPrice}]; $info{$symbol, 'last'} = $datarow->[$datamap{ClosingPrice}] if $datarow->[$datamap{ClosingPrice}]; $info{$symbol, 'volume'} = $datarow->[$datamap{TotalVolume}] if $datarow->[$datamap{TotalVolume}]; $info{$symbol, 'success'} = 1; }; if ($@) { my $error = "SIX failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); } } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::SIX - Obtain quotes from the Swiss Stock Exchange =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = Finance::Quote->fetch('six', 'NESN'); =head1 DESCRIPTION This module fetches information from the Swiss Stock Exchange, https://www.six-group.com. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'SIX' in the argument list to Finance::Quote->new(). =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::SIX : isin name currency date isodate ask close high low open volume success =head1 TERMS & CONDITIONS Use of www.six-group.com is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/TMX.pm���������������������������������������������������������0000644�0001750�0001750�00000013243�15003302667�020152� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::TMX; use strict; use warnings; use Readonly; Readonly my $DEBUG => $ENV{DEBUG}; use if $DEBUG, 'Smart::Comments'; use HTTP::Request; use LWP::UserAgent; use JSON qw( decode_json encode_json ); use String::Util qw(trim); our $VERSION = '1.65'; # VERSION our @labels = qw/currency name exchange volume open high low cap close year_range last p_change symbol isodate date/; sub labels { return ( tmx => \@labels ); } sub methods { return ( tmx => \&tmx, tsx => \&tmx, canada => \&tmx ); } sub tmx { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my %info; foreach my $symbol (@symbols) { eval { my $url = 'https://app-money.tmx.com/graphql'; my $header = ["accept" => "*/*", "accept-language" => "en-US,en;q=0.9", "authorization" => "", "content-type" => "application/json", "locale" => "en", "sec-ch-ua" => "\"Google Chrome\";v=\"87\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"87\"", "sec-ch-ua-mobile" => "?0", "sec-fetch-dest" => "empty", "sec-fetch-mode" => "cors", "sec-fetch-site" => "same-site"]; my $body = "{\"operationName\":\"getQuoteBySymbol\",\"variables\":{\"symbol\":\"$symbol\",\"locale\":\"en\"},\"query\":\"query getQuoteBySymbol(\$symbol: String, \$locale: String) {\\n getQuoteBySymbol(symbol: \$symbol, locale: \$locale) {\\n symbol\\n name\\n price\\n percentChange\\n exchangeName\\n volume\\n openPrice\\n dayHigh\\n dayLow\\n MarketCap\\n prevClose\\n weeks52high\\n weeks52low\\n }\\n}\\n\"}"; my $request = HTTP::Request->new('POST', $url, $header, $body); $request->header("referrer" => "https://money.tmx.com/", "referrerPolicy" => "strict-origin-when-cross-origin", "mode" => "cors"); my $reply = $ua->request($request); if (! $reply->is_success) { $info{$symbol, 'errormsg'} = 'Failed to connect with TMX website'; $info{$symbol, 'success'} = 0; return; } ### Search : $url, $reply->code ### reply : $reply->content my $data = decode_json $reply->content; if (exists $data->{errors}) { $info{$symbol, 'errormsg'} = $data->{errors}[0]->{message}; $info{$symbol, 'success'} = 0; return; } $data = $data->{data}->{getQuoteBySymbol}; if (lc($data->{symbol}) ne lc($symbol)) { $info{$symbol, 'errormsg'} = "returned symbol was not correct for $symbol"; $info{$symbol, 'success'} = 0; return } if ( $symbol =~ /:us/ix ) { $info{$symbol, 'currency'} = 'USD'; } else {$info{$symbol, 'currency'} = 'CAD'} $info{$symbol, 'name'} = $data->{name}; $info{$symbol, 'exchange'} = $data->{exchangeName}; $info{$symbol, 'volume'} = $data->{volume}; $info{$symbol, 'open'} = $data->{openPrice}; $info{$symbol, 'high'} = $data->{dayHigh}; $info{$symbol, 'low'} = $data->{dayLow}; $info{$symbol, 'cap'} = $data->{MarketCap}; $info{$symbol, 'close'} = $data->{prevClose}; $info{$symbol, 'year_range'} = $data->{weeks52low} . ' - ' . $data->{weeks52high}; $info{$symbol, 'last'} = $data->{price}; $info{$symbol, 'symbol'} = $data->{symbol}; $info{$symbol, 'p_change'} = $data->{percentChange}; $quoter->store_date(\%info, $symbol, {today => 1}); $info{$symbol, 'success'} = 1; }; if ($@) { my $error = "TMX failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); } } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::TMX - Obtain quotes from the Toronto Stock Exchange (https://money.tmx.com) =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch('tmx','NT-T'); # Only query TMX %stockinfo = $q->fetch('canada','NT'); # Failover to other Canadian sources =head1 DESCRIPTION This module obtains information from the Toronto Stock Exchange, https://money.tmx.com. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'TMX' in the argument list to Finance::Quote->new(). =head1 LABELS RETURNED The following labels are returned by Finance::Quote::TMX: name, exchange, volume, open, high, low, cap, close, year_range, symbol, last, p_change =head1 TERMS & CONDITIONS Use of money.tmx.com is governed by any terms & conditions of that site and its data provider quotemedia.com. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Troweprice.pm��������������������������������������������������0000644�0001750�0001750�00000011774�15003302667�021634� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # Copyright (C) 1998, Dj Padzensky <djpadz@padz.net> # Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org> # Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com> # Copyright (C) 2000, Paul Fenwick <pjf@cpan.org> # Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # # # This code derived from Padzensky's work on package Finance::YahooQuote, # but extends its capabilites to encompas a greater number of data sources. # # This code was developed as part of GnuCash <http://www.gnucash.org/> package Finance::Quote::Troweprice; require 5.005; use strict; use vars qw( $TROWEPRICE_URL); use LWP::UserAgent; use Time::Piece; use Try::Tiny; our $VERSION = '1.65'; # VERSION # URLs of where to obtain information. $TROWEPRICE_URL = ("https://www3.troweprice.com/fb2/ppfweb/downloadPrices.do"); sub methods { return (troweprice => \&troweprice, troweprice_direct => \&troweprice); } { my @labels = qw/method exchange name nav date isodate price/; sub labels { return (troweprice => \@labels, troweprice_direct => \@labels); } } # ======================================================================= sub troweprice { my $quoter = shift; my @symbols = @_; return if (! scalar @symbols); # for T Rowe Price, we get them all. my %info; my $url = $TROWEPRICE_URL; my $ua = $quoter->user_agent; my $reply = $ua->get( $url, 'Accept-Language' => 'en-US,en' ); if (! $reply->is_success) { for my $stock (@symbols) { $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response:i " . $reply->status_line; } return wantarray() ? %info : \%info; } my $quotes; my $csv = $reply->content; open my $in, '<', \$csv; RECORD: while (my $line = <$in>) { next RECORD if ($line !~ /\S/); #$line =~ s/\s+$//; my @q = $quoter->parse_csv($line); my $symbol = $q[0]; next RECORD if (! grep {$_ eq $symbol} @symbols); my $date; try { $date = Time::Piece->strptime($q[2], "%m/%d/%Y"); } catch { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "Failed to parse quote date. Please contact developers"; next RECORD; }; $quotes->{$symbol} = { price => $q[1], date => $date, } } SYMBOL: for my $symbol (@symbols) { # skip if already defined due to earlier parsing error next SYMBOL if (defined $info{ $symbol, 'success' }); if (! defined $quotes->{$symbol}) { $info{ $symbol, "success" } = 0; $info{ $symbol, "errormsg" } = "Error retrieving quote for $symbol - no listing for this" . " name found. Please check symbol."; next SYMBOL; } $info{ $symbol, "success" } = 1; $info{ $symbol, 'symbol' } = $symbol; $info{ $symbol, "exchange" } = "T. Rowe Price"; $info{ $symbol, "method" } = "troweprice"; $info{ $symbol, "name" } = $symbol; # no name supplied ... $info{ $symbol, "nav" } = $quotes->{$symbol}->{price}; $info{ $symbol, "price" } = $info{$symbol,"nav"}; $info{ $symbol, "currency" } = "USD"; $quoter->store_date( \%info, $symbol, {isodate => $quotes->{$symbol}->{date}->ymd} ); } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::Troweprice - Obtain quotes from T. Rowe Price =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("troweprice","PRFDX"); =head1 DESCRIPTION This module obtains information about managed funds from T. Rowe Price. Information about T. Rowe Price funds is available from a variety of sources. This module fetches information directly from T. Rowe Price. =head1 LABELS RETURNED Information available from T. Rowe Price may include the following labels: exchange, name, nav, date, price. =head1 SEE ALSO T. Rowe Price website - http://www.troweprice.com/ =cut ����Finance-Quote-1.65/lib/Finance/Quote/StockData.pm���������������������������������������������������0000644�0001750�0001750�00000014650�15003302667�021362� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: # # Copyright (C) 2023, Bruce Schuck <bschuck@asgard-systems.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::StockData; use strict; use warnings; use Encode qw(decode); use HTTP::Request::Common; use JSON qw(decode_json); use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments', '###'; our $VERSION = '1.65'; # VERSION my $STOCKDATA_URL = 'https://api.stockdata.org/v1/data/quote?symbols='; # Gets appended with '$stock&api_token=$token' # my $user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'; my $user_agent = 'Finance-Quote OpenSource Stock Quote Tool'; our $DISPLAY = 'StockData'; our $FEATURES = {'API_KEY' => 'registered user API key'}; our @LABELS = qw/symbol name open high low last date volume currency method/; our $METHODHASH = {subroutine => \&stockdata, display => $DISPLAY, labels => \@LABELS, features => $FEATURES}; sub methodinfo { return ( stockdata => $METHODHASH, nyse => $METHODHASH, nasdaq => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } #sub methods { # return (stockdata => \&stockdata, # nyse => \&stockdata, # nasdaq => \&stockdata); #} # our @labels = qw/symbol name open high low last date volume currency method/; #sub labels { # return (stockdata => \@labels, # nyse => \@labels, # nasdaq => \@labels); #} sub stockdata { my $quoter = shift; my @stocks = @_; my (%info, $url, $reply); my $ua = $quoter->user_agent(); $ua->agent($user_agent); my $token = exists $quoter->{module_specific_data}->{stockdata}->{API_KEY} ? $quoter->{module_specific_data}->{stockdata}->{API_KEY} : $ENV{"STOCKDATA_API_KEY"}; foreach my $stock (@stocks) { if ( !defined $token ) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = 'StockData API_KEY not defined. Get an API key at https://stockdata.org'; next; } $url = $STOCKDATA_URL . $stock . '&api_token=' . $token; $reply = $ua->request( GET $url); my $code = $reply->code; my $desc = HTTP::Status::status_message($code); my $headers = $reply->headers_as_string; my $body = decode('UTF-8', $reply->content); ### Body: $body my ($name, $last, $open, $high, $low, $date, $isodate, $volume, $currency, $quote); $info{ $stock, "symbol" } = $stock; if ( $code == 200 ) { eval {$quote = JSON::decode_json $body}; if ($@) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $@; next; } ### [<now>] JSON quote: $quote if (!exists $quote->{'meta'} || $quote->{'meta'}{'returned'} != 1) { $info{ $stock, 'success' } = 0; $info{ $stock, 'errormsg' } = $@; next; } $name = $quote->{'data'}[0]{'name'}; $last = $quote->{'data'}[0]{'price'}; $open = $quote->{'data'}[0]{'day_open'}; $low = $quote->{'data'}[0]{'day_low'}; $high = $quote->{'data'}[0]{'day_high'}; $volume = $quote->{'data'}[0]{'volume'}; $currency = $quote->{'data'}[0]{'currency'}; $date = $quote->{'data'}[0]{'last_trade_time'}; ($isodate) = $date =~ m|^([\d\-]+)T|; ### [<now>] isodate: $isodate $info{ $stock, 'name' } = $name; $info{ $stock, 'last' } = $last; $info{ $stock, 'open' } = $open; $info{ $stock, 'low' } = $low; $info{ $stock, 'high' } = $high; $info{ $stock, 'volume' } = $volume; $info{ $stock, 'currency' } = $currency; $info{ $stock, 'method' } = 'stockdata'; $quoter->store_date(\%info, $stock, {isodate => $isodate}); $info{ $stock, 'success' } = 1; } else { # HTTP Request failed (code != 200) $info{ $stock, "success" } = 0; $info{ $stock, "errormsg" } = "Error retrieving quote for $stock. Attempt to fetch the URL $url resulted in HTTP response $code ($desc)"; } } return wantarray() ? %info : \%info; return \%info; } 1; __END__ =head1 NAME Finance::Quote::StockData - Obtain quotes from StockData.org =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new('StockData', stockdata => {API_KEY => 'your-stockdata-api-token'}); %info = $q->fetch('stockdata', 'AAPL'); # Only query foobar %info = $q->fetch('nyse', 'IBM'); # Failover to other sources OK. =head1 DESCRIPTION This module fetches information from L<https://www.stockdata.org/>. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "stockdata" in the argument list to Finance::Quote->new(). This module provides "stockdata", "nyse", "nasdaq" fetch methods. Currently stock quote data is only available for securities traded on the US markets. Information obtained by this module may be covered by New York Exchange terms and conditions. =head1 API_KEY L<https://www.stockdata.org/> requires users to register and obtain an API key, which is also called a token. The token is a sequence of random characters. The API key may be set by either providing a module specific hash to Finance::Quote->new as in the above example, or by setting the environment variable STOCKDATA_API_KEY. =head1 LABELS RETURNED The following labels are returned: =over =item name =item symbol =item open =item high =item low =item last =item volume =item date =item currency =back =cut ����������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/ZA.pm����������������������������������������������������������0000644�0001750�0001750�00000007360�15003302667�020017� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::ZA; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use Web::Scraper; use String::Util qw(trim); our $VERSION = '1.65'; # VERSION our @labels = qw/method source name symbol currency last date isodate high low p_change/; sub labels { return ( sharenet => \@labels ); } sub methods { return ( za => \&sharenet ); } sub sharenet { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my %info; foreach my $symbol (@_) { eval { my $url = "https://www.sharenet.co.za/jse/$symbol"; my $reply = $ua->get($url); my $widget = scraper { process 'h1.share-chart-title', 'name' => ['TEXT', sub{trim($_)}], process 'h1.share-chart-title + h2', 'last' => ['TEXT', sub{$_ =~ /([0-9,.]+)/ ? $1 : '<unknown>';}], process 'h1.share-chart-title + h2 + div b', 'day' => ['TEXT', sub{$_ =~ /(\w{3}\s+\d+\s+\w{3}),/ ? $1 : '<unknown>';}], }; my $result = $widget->scrape($reply); die "Failed to find $symbol" unless exists $result->{name}; # Sharenet reports in minor denomination. Change this to major denomination my $price = $result->{last}; $price =~ s/,//; $price = $price / 100; ### RESULT : $result $info{$symbol, 'success'} = 1; $info{$symbol, 'currency'} = 'ZAR'; $info{$symbol, 'name'} = $result->{name}; $info{$symbol, 'price'} = $price; $info{$symbol, 'last'} = $price; # Some applications would like to see the symbol in the returned data $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'source'} = 'sharenet.co.za'; $info{$symbol, 'exchange'} = 'JSE'; if ($result->{day} =~ /(\d+)\s+(\w{3})/) { $quoter->store_date(\%info, $symbol, {day => $1, month => $2}); } }; if ($@) { my $error = "Search failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); } } ### info : %info return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::ZA - Obtain South African stock and prices from https://www.sharenet.co.za =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = Finance::Quote->fetch('za', 'AGL'); =head1 DESCRIPTION This module obtains information about South African Stocks from www.sharenet.co.za. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'za' in the argument list to Finance::Quote->new(). =head1 LABELS RETURNED The following labels will be returned: success currency name price date isodate symbol last source exchange. =head1 Terms & Conditions Use of sharenet.co.za is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/TwelveData.pm��������������������������������������������������0000644�0001750�0001750�00000013654�15003302667�021550� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::TwelveData; use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use JSON qw( decode_json ); use HTTP::Request::Common; use Text::Template; use DateTime::Format::Strptime qw( strptime strftime ); our $VERSION = '1.65'; # VERSION my $URL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://api.twelvedata.com/quote?symbol={$symbol}&apikey={$token}'); my $THROTTLE = 1.05 * 60.0/8.0; # 5% more than maximum seconds / request for free tier my $AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'; sub methods { return ( twelvedata => \&twelvedata ); } sub parameters { return ('API_KEY'); } { our @labels = qw/symbol name exchange currency isodate currency open high low close last/; sub labels { return ( twelvedata => \@labels ); } } sub twelvedata { my $quoter = shift; my @stocks = @_; my $quantity = @stocks; my ( %info, $reply, $url, $code, $desc, $body, $mark ); my $ua = $quoter->user_agent(); my $agent = $ua->agent(); $ua->agent($AGENT); my $token = exists $quoter->{module_specific_data}->{twelvedata}->{API_KEY} ? $quoter->{module_specific_data}->{twelvedata}->{API_KEY} : $ENV{"TWELVEDATA_API_KEY"}; foreach my $symbol (@stocks) { if ( !defined $token ) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = 'TwelveData API_KEY not defined. Get an API key at https://twelvedata.com'; next; } # Rate limit - first time through loop, mark is negative $mark -= time(); ### TwelveData Mark: $mark sleep($mark) if $mark > 0; $mark = time() + $THROTTLE; $url = $URL->fill_in(HASH => {symbol => $symbol, token => $token}); $reply = $ua->request(GET $url); ### url: $url ### reply: $reply $code = $reply->code; $desc = HTTP::Status::status_message($code); $body = $reply->content; if ($code != 200) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $desc; next; } my $quote; eval {$quote = JSON::decode_json $body}; if ($@) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $@; next; } if (exists $quote->{'status'} and $quote->{'status'} eq 'error') { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $quote->{'message'}; next; } if (not exists $quote->{'symbol'} or $quote->{'symbol'} ne $symbol) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = "TwevleData return and unexpected json result"; next; } $info{ $symbol, 'success' } = 1; $info{ $symbol, 'symbol' } = $symbol; $info{ $symbol, 'name'} = $quote->{'name'} if $quote->{'name'}; $info{ $symbol, 'exchange'} = $quote->{'exchange'} if $quote->{'exchange'}; $info{ $symbol, 'currency'} = $quote->{'currency'} if $quote->{'currency'}; $info{ $symbol, 'open' } = $quote->{'open'} if $quote->{'open'}; $info{ $symbol, 'high' } = $quote->{'high'} if $quote->{'high'}; $info{ $symbol, 'low' } = $quote->{'low'} if $quote->{'low'}; $info{ $symbol, 'close' } = $quote->{'close'} if $quote->{'close'}; $info{ $symbol, 'last' } = $quote->{'close'} if $quote->{'close'}; $info{ $symbol, 'volume' } = $quote->{'volume'} if $quote->{'volume'}; $info{ $symbol, 'method' } = 'twelvedata'; my $time = strptime('%s', int($quote->{'timestamp'})); my $isodate = strftime('%F', $time); $quoter->store_date( \%info, $symbol, { isodate => $isodate } ); } $ua->agent($agent); return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::TwelveData - Obtain quotes from https://twelvedata.com =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new('TwelveData', twelvedata => {API_KEY => 'your-twelvedata-api-key'}); %info = $q->fetch('twelvedata', 'IBM', 'AAPL'); =head1 DESCRIPTION This module fetches information from https://twelvedata.com. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing "TwelveData" in the argument list to Finance::Quote->new(). This module provides the "twelvedata" fetch method. =head1 API_KEY https://twelvedata.com requires users to register and obtain an API key, which is a secret value written in hexidecimal. The API key may be set by either providing a module specific hash to Finance::Quote->new as in the above example, or by setting the environment variable TWELVEDATA_API_KEY. =head2 FREE KEY LIMITS The TwelveData free key limits usage to: =over =item 800 queries per day =item 8 queries per minute =back =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::TwelveData : symbol name exchange currency isodate currency open high low close last =cut ������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/MorningstarJP.pm�����������������������������������������������0000644�0001750�0001750�00000010355�15003302667�022240� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Copyright (C) 2024, Przemyslaw Kryger # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # $Id: $ # package Finance::Quote::MorningstarJP; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use XML::LibXML; use LWP::UserAgent; use String::Util qw(trim); our $VERSION = '1.65'; # VERSION our $DISPLAY = 'Morningstar JP'; our @LABELS = qw/nav isin symbol name currency date isodate/; our $METHODHASH = {subroutine => \&morningstarjp, display => $DISPLAY, labels => \@LABELS}; sub methodinfo { return ( morningstarjp => $METHODHASH, ); } sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub morningstarjp { my $quoter = shift; my @symbols = @_; my $ua = $quoter->user_agent(); my %info; foreach my $symbol (@_) { eval { my $url = "https://apl.wealthadvisor.jp/webasp/funddataxml/basic/basic_$symbol.xml"; my $reply = $ua->get($url); my $dom; eval {$dom = XML::LibXML->load_xml(string => $reply->decoded_content)}; if ($@) { $info{$symbol, 'success' } = 0; $info{$symbol, 'errormsg' } = $@; next; } ### [<now>] DOM: $dom->toString() unless ($dom->findnodes('//Fund/@MS_FUND_CODE')->[0]->to_literal() eq $symbol) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Symbol not found'; next; } my $nav = $dom->findnodes('//Fund/Price/@KIJYUNKAGAKU')->[0]->to_literal(); $nav =~ s/,//; my $date = $dom->findnodes('//Fund/Price/@KIJYUN_YMD')->[0]->to_literal(); $date =~ s/[^0-9]/-/g; my $isin = $dom->findnodes('//Fund/@ISIN')->[0]->to_literal(); my $name = $dom->findnodes('//Fund/@FUND_NAME')->[0]->to_literal(); $info{$symbol, 'success'} = 1; $info{$symbol, 'method'} = 'MorningstarJP'; $info{$symbol, 'currency'} = 'JPY'; $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'nav'} = $nav; $info{$symbol, 'isin'} = $isin; $info{$symbol, 'name'} = $name; $quoter->store_date(\%info, $symbol, {isodate => $date}); }; if ($@) { my $error = "Search failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); } } ### info : %info return wantarray() ? %info : \%info; } 1; __END__ =head1 NAME Finance::Quote::MorninstarJP - Obtain quotes from from Morningstar (Japan) =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("morningstarjp", "2009100101"); =head1 DESCRIPTION This module obtains information from Morningstar (Japan), L<http://www.wealthadvisor.co.jp/>. Information returned by this module is governed by Morningstar (Japan)'s terms and conditions. =head1 FUND SYMBOLS Use the numeric symbol shown in the URL on the "SnapShot" page of the security of interest. e.g. For L<https://www.wealthadvisor.co.jp/snapshot/2009100101>, one would supply 2009100101 as the symbol argument on the fetch API call. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::MorningstarJP: nav, isin, symbol, name, currency. =head1 Terms & Conditions Use of apl.wealthadvisor.jp is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =head1 SEE ALSO Morningstar (Japan), L<http://www.wealthadvisor.co.jp/> =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/AEX.pm���������������������������������������������������������0000644�0001750�0001750�00000022765�15003302667�020130� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # vi: set ts=4 sw=4 noai ic showmode showmatch: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA package Finance::Quote::AEX; use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use LWP::UserAgent; use Web::Scraper; use String::Util qw(trim); our $VERSION = '1.65'; # VERSION my $EURONEXT_URL = "https://live.euronext.com/en/search_instruments/"; our $DISPLAY = 'Euronext'; our @LABELS = qw/name symbol price last date time p_change bid ask offer open high low close volume currency method exchange/; our $METHODHASH = {subroutine => \&aex, display => $DISPLAY, labels => \@LABELS}; sub labels { my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m; } sub methodinfo { return ( euronext => $METHODHASH, # for compatibility of older versions dutch => $METHODHASH, aex => $METHODHASH, ); } sub methods { my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m; } sub aex { my $quoter = shift; my $ua = $quoter->user_agent(); my $agent = $ua->agent; $ua->agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'); my %info; my $url; my $reply; foreach my $symbol (@_) { my ($isin, $mic); # eval { my $search = $EURONEXT_URL . $symbol; $reply = $ua->get($search); ### Search: $search, $reply->code if (not defined $reply->previous) { # Got a search page my $widget = scraper { process 'table#awl-lookup-instruments-directory-table a', 'link[]' => '@href'; }; my $result = $widget->scrape($reply); # die "Failed to find $symbol" unless exists $result->{link} and @{$result->{link}} > 0; unless (exists $result->{link} and @{$result->{link}} > 0) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Cannot find symbol ' . $symbol; next; } # Loop through linkarray. Skip links containing the string # "product/indices" for my $newlink (@{$result->{link}}) { ### NewLink: $newlink if ( $newlink !~ /product\/indices/i ) { $url = $newlink; ### Setting URL: $url last; } } # die "Failed to find isin" unless $url->as_string =~ m|/([A-Za-z0-9]{12}-[A-Za-z]+)/|; unless (defined($url) && $url->as_string =~ m|/([A-Za-z0-9]{12})-([A-Za-z]+\b)|) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Cannot find ISIN for ' . $symbol; next; } else { $isin = uc($1); $mic = uc($2); } } else { # Redirected my $widget = scraper { process 'a', 'redirect' => '@href'; }; my $result = $widget->scrape($reply->previous->content); # die "Failed to find $symbol in redirect" unless exists $result->{redirect}; unless (exists $result->{redirect}) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Cannot find symbol ' . $symbol . ' in redirect'; next; } my $url = $result->{redirect}; # die "Failed to find isin in redirect" unless $url =~ m|/([A-Za-z0-9]{12}-[A-Za-z]+)|; unless ($url =~ m|/([A-Za-z0-9]{12})-([A-Za-z]+\b)|) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Cannot find ISIN for ' . $symbol . ' in redirect'; next; } $isin = uc($1); $mic = uc($2); ### ISIN: $isin } # die "No isin set" unless defined $isin; unless (defined $isin) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'No ISIN set for ' . $symbol; next; } # }; # End eval if ($@) { my $error = "Search failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); next; } # eval { my $url = "https://live.euronext.com/en/ajax/getDetailedQuote/$isin-$mic"; my %form = (theme_name => 'euronext_live'); $reply = $ua->post($url, \%form); ### Header : $url, $reply->code ### Content: $reply->content my $widget = scraper { process 'h1#header-instrument-name strong', 'name' => ['TEXT', sub {trim($_)}]; process 'span#header-instrument-price', 'last' => ['TEXT', sub {trim($_)}]; # process 'div.head_detail_bottom div.col span, div.head_detail > div > div:last-child', 'date' => ['TEXT', sub {trim($_)}]; # process 'div.ml-2 last-price-date-time', 'date' => ['TEXT', sub {trim($_)}]; process 'div.ml-2.last-price-date-time', 'date' => ['TEXT', sub {trim($_)}]; }; my $header = $widget->scrape($reply); ### Header getDetailedQuote: $header $url = "https://live.euronext.com/en/intraday_chart/getDetailedQuoteAjax/$isin-$mic/full"; $reply = $ua->get($url); $widget = scraper { process 'div.table-responsive td:first-child, div.table-responsive td:first-child + td', 'data[]' => ['TEXT', sub {trim($_)}]; }; ### Body : $url, $reply->code my $body = $widget->scrape($reply); # die "Failed to find detailed quote table" unless exists $body->{data}; unless (exists $body->{data}) { $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = 'Failed to find detailed quote table'; next; } my %table = @{$body->{data}}; $info{$symbol, 'success'} = 1; $info{$symbol, 'currency'} = $table{Currency}; $info{$symbol, 'volume'} = $table{Volume}; $info{$symbol, 'volume'} =~ s/,//g; $info{$symbol, 'open'} = $table{Open}; $info{$symbol, 'close'} = $table{"Previous Close"}; $info{$symbol, 'high'} = $table{High}; $info{$symbol, 'low'} = $table{Low}; $info{$symbol, 'name'} = $header->{name}; $info{$symbol, 'isin'} = $isin; $info{$symbol, 'last'} = $header->{last}; $quoter->store_date(\%info, $symbol, {eurodate => $1}) if $header->{date} =~ m|([0-9]{2}/[0-9]{2}/[0-9]{4}) - ([0-2][0-9]:[0-5][0-9])|; $info{$symbol, 'time'} = $2 if $2; # CE(S)T # see https://www.tradinghours.com/mic/s/<MIC> my %mic2location = ( "XAMS" => "Amsterdam", # EURONEXT AMSTERDAM "XBRU" => "Brussels", # EURONEXT BRUSSELS "ALXB" => "Brussels", # EURONEXT GROWTH BRUSSELS "MLXB" => "Brussels", # EURONEXT ACCESS BRUSSELS "XMSM" => "Dublin", # EURONEXT DUBLIN "XESM" => "Dublin", # EURONEXT GROWTH DUBLIN "XLIS" => "Lisbon", # EURONEXT LISBON "ENXL" => "Lisbon", # EURONEXT ACCESS LISBON "ALXL" => "Lisbon", # EURONEXT GROWTH LISBON "MTAA" => "Milan", # EURONEXT MILAN "BGEM" => "Milan", # GLOBAL EQUITY MARKET "MTAH" => "Milan", # TRADING AFTER HOURS "ETLX" => "Milan", # EUROTLX "ETFP" => "Milan", # ELECTRONIC ETF, ETC/ETN AND OPEN-END FUNDS MARKET "XOSL" => "Oslo", # OSLO BØRS "XOAS" => "Oslo", # EURONEXT EXPAND OSLO "MERK" => "Oslo", # EURONEXT GROWTH OSLO "XPAR" => "Paris", # EURONEXT PARIS "ALXP" => "Paris", # EURONEXT GROWTH PARIS "XMLI" => "Paris", # EURONEXT ACCESS PARIS ); $info{$symbol, 'exchange'} = exists($mic2location{$mic}) ? $mic2location{$mic} : $mic; # }; # End eval if ($@) { my $error = "Fetch/Parse failed: $@"; $info{$symbol, 'success'} = 0; $info{$symbol, 'errormsg'} = trim($error); next; } } $ua->agent($agent); return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::AEX - Obtain quotes from Euronext Amsterdam/Paris/... eXchange =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %info = $q->fetch("aex", "AMG"); # Only query AEX %info = $q->fetch("dutch", "AMG"); # Failover to other sources OK =head1 DESCRIPTION This module fetches information from https://live.euronext.com. Stocks and bonds are supported. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicitly by placing 'aex' in the argument list to Finance::Quote->new(). =head1 LABELS RETURNED The following labels may be returned: currency, date, time, high, isin, isodate, last, low, name, open, close, success, symbol, volume, exchange. =head1 Terms & Conditions Use of live.euronext.com is governed by any terms & conditions of that site. Finance::Quote is released under the GNU General Public License, version 2, which explicitly carries a "No Warranty" clause. =cut �����������Finance-Quote-1.65/lib/Finance/Quote/Currencies.pm��������������������������������������������������0000644�0001750�0001750�00000101234�15003302667�021602� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA # package Finance::Quote::Currencies; use strict; use warnings; use utf8; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use if DEBUG, 'Data::Dumper::Perltidy'; use base 'Exporter'; use vars qw/@EXPORT_OK $CURRENCY_URL/; use LWP::UserAgent; use HTTP::Request::Common; use HTML::TableExtract; use Encode qw(decode); our $VERSION = '1.65'; # VERSION @EXPORT_OK = qw( known_currencies fetch_live_currencies ); $CURRENCY_URL = 'https://www.iban.com/currency-codes'; sub methods { return ( known_currencies => \&known_currencies , fetch_live_currencies => \&fetch_live_currencies ); } sub labels { return () }; my %currencies = ( 'AED' => { 'country' => ['UNITED ARAB EMIRATES (THE)'], 'name' => 'UAE Dirham', 'code' => 'AED', 'number' => '784' }, 'AFN' => { 'country' => ['AFGHANISTAN'], 'name' => 'Afghani', 'code' => 'AFN', 'number' => '971' }, 'ALL' => { 'country' => ['ALBANIA'], 'name' => 'Lek', 'code' => 'ALL', 'number' => '008' }, 'AMD' => { 'country' => ['ARMENIA'], 'name' => 'Armenian Dram', 'code' => 'AMD', 'number' => '051' }, 'ANG' => { 'country' => [ 'CURAÇAO', 'SINT MAARTEN (DUTCH PART)' ], 'name' => 'Netherlands Antillean Guilder', 'code' => 'ANG', 'number' => '532' }, 'AOA' => { 'country' => ['ANGOLA'], 'name' => 'Kwanza', 'code' => 'AOA', 'number' => '973' }, 'ARS' => { 'country' => ['ARGENTINA'], 'name' => 'Argentine Peso', 'code' => 'ARS', 'number' => '032' }, 'AUD' => { 'country' => [ 'AUSTRALIA', 'CHRISTMAS ISLAND', 'COCOS (KEELING) ISLANDS (THE)', 'HEARD ISLAND AND McDONALD ISLANDS', 'KIRIBATI', 'NAURU', 'NORFOLK ISLAND', 'TUVALU' ], 'name' => 'Australian Dollar', 'code' => 'AUD', 'number' => '036' }, 'AWG' => { 'country' => ['ARUBA'], 'name' => 'Aruban Florin', 'code' => 'AWG', 'number' => '533' }, 'AZN' => { 'country' => ['AZERBAIJAN'], 'name' => 'Azerbaijanian Manat', 'code' => 'AZN', 'number' => '944' }, 'BAM' => { 'country' => ['BOSNIA AND HERZEGOVINA'], 'name' => 'Convertible Mark', 'code' => 'BAM', 'number' => '977' }, 'BBD' => { 'country' => ['BARBADOS'], 'name' => 'Barbados Dollar', 'code' => 'BBD', 'number' => '052' }, 'BDT' => { 'country' => ['BANGLADESH'], 'name' => 'Taka', 'code' => 'BDT', 'number' => '050' }, 'BGN' => { 'country' => ['BULGARIA'], 'name' => 'Bulgarian Lev', 'code' => 'BGN', 'number' => '975' }, 'BHD' => { 'country' => ['BAHRAIN'], 'name' => 'Bahraini Dinar', 'code' => 'BHD', 'number' => '048' }, 'BIF' => { 'country' => ['BURUNDI'], 'name' => 'Burundi Franc', 'code' => 'BIF', 'number' => '108' }, 'BMD' => { 'country' => ['BERMUDA'], 'name' => 'Bermudian Dollar', 'code' => 'BMD', 'number' => '060' }, 'BND' => { 'country' => ['BRUNEI DARUSSALAM'], 'name' => 'Brunei Dollar', 'code' => 'BND', 'number' => '096' }, 'BOB' => { 'country' => ['BOLIVIA (PLURINATIONAL STATE OF)'], 'name' => 'Boliviano', 'code' => 'BOB', 'number' => '068' }, 'BOV' => { 'country' => ['BOLIVIA (PLURINATIONAL STATE OF)'], 'name' => 'Mvdol', 'code' => 'BOV', 'number' => '984' }, 'BRL' => { 'country' => ['BRAZIL'], 'name' => 'Brazilian Real', 'code' => 'BRL', 'number' => '986' }, 'BSD' => { 'country' => ['BAHAMAS (THE)'], 'name' => 'Bahamian Dollar', 'code' => 'BSD', 'number' => '044' }, 'BTN' => { 'country' => ['BHUTAN'], 'name' => 'Ngultrum', 'code' => 'BTN', 'number' => '064' }, 'BWP' => { 'country' => ['BOTSWANA'], 'name' => 'Pula', 'code' => 'BWP', 'number' => '072' }, 'BYN' => { 'country' => ['BELARUS'], 'name' => 'Belarussian Ruble', 'code' => 'BYN', 'number' => '933' }, 'BZD' => { 'country' => ['BELIZE'], 'name' => 'Belize Dollar', 'code' => 'BZD', 'number' => '084' }, 'CAD' => { 'country' => ['CANADA'], 'name' => 'Canadian Dollar', 'code' => 'CAD', 'number' => '124' }, 'CDF' => { 'country' => ['CONGO (THE DEMOCRATIC REPUBLIC OF THE)'], 'name' => 'Congolese Franc', 'code' => 'CDF', 'number' => '976' }, 'CHE' => { 'country' => ['SWITZERLAND'], 'name' => 'WIR Euro', 'code' => 'CHE', 'number' => '947' }, 'CHF' => { 'country' => [ 'LIECHTENSTEIN', 'SWITZERLAND' ], 'name' => 'Swiss Franc', 'code' => 'CHF', 'number' => '756' }, 'CHW' => { 'country' => ['SWITZERLAND'], 'name' => 'WIR Franc', 'code' => 'CHW', 'number' => '948' }, 'CLF' => { 'country' => ['CHILE'], 'name' => 'Unidad de Fomento', 'code' => 'CLF', 'number' => '990' }, 'CLP' => { 'country' => ['CHILE'], 'name' => 'Chilean Peso', 'code' => 'CLP', 'number' => '152' }, 'CNY' => { 'country' => ['CHINA'], 'name' => 'Yuan Renminbi', 'code' => 'CNY', 'number' => '156' }, 'COP' => { 'country' => ['COLOMBIA'], 'name' => 'Colombian Peso', 'code' => 'COP', 'number' => '170' }, 'COU' => { 'country' => ['COLOMBIA'], 'name' => 'Unidad de Valor Real', 'code' => 'COU', 'number' => '970' }, 'CRC' => { 'country' => ['COSTA RICA'], 'name' => 'Costa Rican Colon', 'code' => 'CRC', 'number' => '188' }, 'CUC' => { 'country' => ['CUBA'], 'name' => 'Peso Convertible', 'code' => 'CUC', 'number' => '931' }, 'CUP' => { 'country' => ['CUBA'], 'name' => 'Cuban Peso', 'code' => 'CUP', 'number' => '192' }, 'CVE' => { 'country' => ['CABO VERDE'], 'name' => 'Cabo Verde Escudo', 'code' => 'CVE', 'number' => '132' }, 'CZK' => { 'country' => ['CZECH REPUBLIC (THE)'], 'name' => 'Czech Koruna', 'code' => 'CZK', 'number' => '203' }, 'DJF' => { 'country' => ['DJIBOUTI'], 'name' => 'Djibouti Franc', 'code' => 'DJF', 'number' => '262' }, 'DKK' => { 'country' => [ 'DENMARK', 'FAROE ISLANDS (THE)', 'GREENLAND' ], 'name' => 'Danish Krone', 'code' => 'DKK', 'number' => '208' }, 'DOP' => { 'country' => ['DOMINICAN REPUBLIC (THE)'], 'name' => 'Dominican Peso', 'code' => 'DOP', 'number' => '214' }, 'DZD' => { 'country' => ['ALGERIA'], 'name' => 'Algerian Dinar', 'code' => 'DZD', 'number' => '012' }, 'EGP' => { 'country' => ['EGYPT'], 'name' => 'Egyptian Pound', 'code' => 'EGP', 'number' => '818' }, 'ERN' => { 'country' => ['ERITREA'], 'name' => 'Nakfa', 'code' => 'ERN', 'number' => '232' }, 'ETB' => { 'country' => ['ETHIOPIA'], 'name' => 'Ethiopian Birr', 'code' => 'ETB', 'number' => '230' }, 'EUR' => { 'country' => [ 'ÅLAND ISLANDS', 'ANDORRA', 'AUSTRIA', 'BELGIUM', 'CROATIA', 'CYPRUS', 'ESTONIA', 'EUROPEAN UNION', 'FINLAND', 'FRANCE', 'FRENCH GUIANA', 'FRENCH SOUTHERN TERRITORIES (THE)', 'GERMANY', 'GREECE', 'GUADELOUPE', 'HOLY SEE (THE)', 'IRELAND', 'ITALY', 'LATVIA', 'LITHUANIA', 'LUXEMBOURG', 'MALTA', 'MARTINIQUE', 'MAYOTTE', 'MONACO', 'MONTENEGRO', 'NETHERLANDS (THE)', 'PORTUGAL', 'RÉUNION', 'SAINT BARTHÉLEMY', 'SAINT MARTIN (FRENCH PART)', 'SAINT PIERRE AND MIQUELON', 'SAN MARINO', 'SLOVAKIA', 'SLOVENIA', 'SPAIN' ], 'name' => 'Euro', 'code' => 'EUR', 'number' => '978' }, 'FJD' => { 'country' => ['FIJI'], 'name' => 'Fiji Dollar', 'code' => 'FJD', 'number' => '242' }, 'FKP' => { 'country' => ['FALKLAND ISLANDS (THE) [MALVINAS]'], 'name' => 'Falkland Islands Pound', 'code' => 'FKP', 'number' => '238' }, 'GBP' => { 'country' => [ 'GUERNSEY', 'ISLE OF MAN', 'JERSEY', 'UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE)' ], 'name' => 'Pound Sterling', 'code' => 'GBP', 'number' => '826' }, 'GEL' => { 'country' => ['GEORGIA'], 'name' => 'Lari', 'code' => 'GEL', 'number' => '981' }, 'GHS' => { 'country' => ['GHANA'], 'name' => 'Ghana Cedi', 'code' => 'GHS', 'number' => '936' }, 'GIP' => { 'country' => ['GIBRALTAR'], 'name' => 'Gibraltar Pound', 'code' => 'GIP', 'number' => '292' }, 'GMD' => { 'country' => ['GAMBIA (THE)'], 'name' => 'Dalasi', 'code' => 'GMD', 'number' => '270' }, 'GNF' => { 'country' => ['GUINEA'], 'name' => 'Guinea Franc', 'code' => 'GNF', 'number' => '324' }, 'GTQ' => { 'country' => ['GUATEMALA'], 'name' => 'Quetzal', 'code' => 'GTQ', 'number' => '320' }, 'GYD' => { 'country' => ['GUYANA'], 'name' => 'Guyana Dollar', 'code' => 'GYD', 'number' => '328' }, 'HKD' => { 'country' => ['HONG KONG'], 'name' => 'Hong Kong Dollar', 'code' => 'HKD', 'number' => '344' }, 'HNL' => { 'country' => ['HONDURAS'], 'name' => 'Lempira', 'code' => 'HNL', 'number' => '340' }, 'HTG' => { 'country' => ['HAITI'], 'name' => 'Gourde', 'code' => 'HTG', 'number' => '332' }, 'HUF' => { 'country' => ['HUNGARY'], 'name' => 'Forint', 'code' => 'HUF', 'number' => '348' }, 'IDR' => { 'country' => ['INDONESIA'], 'name' => 'Rupiah', 'code' => 'IDR', 'number' => '360' }, 'ILS' => { 'country' => ['ISRAEL'], 'name' => 'New Israeli Sheqel', 'code' => 'ILS', 'number' => '376' }, 'INR' => { 'country' => [ 'BHUTAN', 'INDIA' ], 'name' => 'Indian Rupee', 'code' => 'INR', 'number' => '356' }, 'IQD' => { 'country' => ['IRAQ'], 'name' => 'Iraqi Dinar', 'code' => 'IQD', 'number' => '368' }, 'IRR' => { 'country' => ['IRAN (ISLAMIC REPUBLIC OF)'], 'name' => 'Iranian Rial', 'code' => 'IRR', 'number' => '364' }, 'ISK' => { 'country' => ['ICELAND'], 'name' => 'Iceland Krona', 'code' => 'ISK', 'number' => '352' }, 'JMD' => { 'country' => ['JAMAICA'], 'name' => 'Jamaican Dollar', 'code' => 'JMD', 'number' => '388' }, 'JOD' => { 'country' => ['JORDAN'], 'name' => 'Jordanian Dinar', 'code' => 'JOD', 'number' => '400' }, 'JPY' => { 'country' => ['JAPAN'], 'name' => 'Yen', 'code' => 'JPY', 'number' => '392' }, 'KES' => { 'country' => ['KENYA'], 'name' => 'Kenyan Shilling', 'code' => 'KES', 'number' => '404' }, 'KGS' => { 'country' => ['KYRGYZSTAN'], 'name' => 'Som', 'code' => 'KGS', 'number' => '417' }, 'KHR' => { 'country' => ['CAMBODIA'], 'name' => 'Riel', 'code' => 'KHR', 'number' => '116' }, 'KMF' => { 'country' => ['COMOROS (THE)'], 'name' => 'Comoro Franc', 'code' => 'KMF', 'number' => '174' }, 'KPW' => { 'country' => ['KOREA (THE DEMOCRATIC PEOPLE’S REPUBLIC OF)'], 'name' => 'North Korean Won', 'code' => 'KPW', 'number' => '408' }, 'KRW' => { 'country' => ['KOREA (THE REPUBLIC OF)'], 'name' => 'Won', 'code' => 'KRW', 'number' => '410' }, 'KWD' => { 'country' => ['KUWAIT'], 'name' => 'Kuwaiti Dinar', 'code' => 'KWD', 'number' => '414' }, 'KYD' => { 'country' => ['CAYMAN ISLANDS (THE)'], 'name' => 'Cayman Islands Dollar', 'code' => 'KYD', 'number' => '136' }, 'KZT' => { 'country' => ['KAZAKHSTAN'], 'name' => 'Tenge', 'code' => 'KZT', 'number' => '398' }, 'LAK' => { 'country' => ['LAO PEOPLE’S DEMOCRATIC REPUBLIC (THE)'], 'name' => 'Kip', 'code' => 'LAK', 'number' => '418' }, 'LBP' => { 'country' => ['LEBANON'], 'name' => 'Lebanese Pound', 'code' => 'LBP', 'number' => '422' }, 'LKR' => { 'country' => ['SRI LANKA'], 'name' => 'Sri Lanka Rupee', 'code' => 'LKR', 'number' => '144' }, 'LRD' => { 'country' => ['LIBERIA'], 'name' => 'Liberian Dollar', 'code' => 'LRD', 'number' => '430' }, 'LSL' => { 'country' => ['LESOTHO'], 'name' => 'Loti', 'code' => 'LSL', 'number' => '426' }, 'LYD' => { 'country' => ['LIBYA'], 'name' => 'Libyan Dinar', 'code' => 'LYD', 'number' => '434' }, 'MAD' => { 'country' => [ 'MOROCCO', 'WESTERN SAHARA' ], 'name' => 'Moroccan Dirham', 'code' => 'MAD', 'number' => '504' }, 'MDL' => { 'country' => ['MOLDOVA (THE REPUBLIC OF)'], 'name' => 'Moldovan Leu', 'code' => 'MDL', 'number' => '498' }, 'MGA' => { 'country' => ['MADAGASCAR'], 'name' => 'Malagasy Ariary', 'code' => 'MGA', 'number' => '969' }, 'MKD' => { 'country' => ['REPUBLIC OF NORTH MACEDONIA'], 'name' => 'Denar', 'code' => 'MKD', 'number' => '807' }, 'MMK' => { 'country' => ['MYANMAR'], 'name' => 'Kyat', 'code' => 'MMK', 'number' => '104' }, 'MNT' => { 'country' => ['MONGOLIA'], 'name' => 'Tugrik', 'code' => 'MNT', 'number' => '496' }, 'MOP' => { 'country' => ['MACAO'], 'name' => 'Pataca', 'code' => 'MOP', 'number' => '446' }, 'MRU' => { 'country' => ['MAURITANIA'], 'name' => 'Ouguiya', 'code' => 'MRU', 'number' => '929' }, 'MUR' => { 'country' => ['MAURITIUS'], 'name' => 'Mauritius Rupee', 'code' => 'MUR', 'number' => '480' }, 'MVR' => { 'country' => ['MALDIVES'], 'name' => 'Rufiyaa', 'code' => 'MVR', 'number' => '462' }, 'MWK' => { 'country' => ['MALAWI'], 'name' => 'Kwacha', 'code' => 'MWK', 'number' => '454' }, 'MXN' => { 'country' => ['MEXICO'], 'name' => 'Mexican Peso', 'code' => 'MXN', 'number' => '484' }, 'MXV' => { 'country' => ['MEXICO'], 'name' => 'Mexican Unidad de Inversion (UDI)', 'code' => 'MXV', 'number' => '979' }, 'MYR' => { 'country' => ['MALAYSIA'], 'name' => 'Malaysian Ringgit', 'code' => 'MYR', 'number' => '458' }, 'MZN' => { 'country' => ['MOZAMBIQUE'], 'name' => 'Mozambique Metical', 'code' => 'MZN', 'number' => '943' }, 'NAD' => { 'country' => ['NAMIBIA'], 'name' => 'Namibia Dollar', 'code' => 'NAD', 'number' => '516' }, 'NGN' => { 'country' => ['NIGERIA'], 'name' => 'Naira', 'code' => 'NGN', 'number' => '566' }, 'NIO' => { 'country' => ['NICARAGUA'], 'name' => 'Cordoba Oro', 'code' => 'NIO', 'number' => '558' }, 'NOK' => { 'country' => [ 'BOUVET ISLAND', 'NORWAY', 'SVALBARD AND JAN MAYEN' ], 'name' => 'Norwegian Krone', 'code' => 'NOK', 'number' => '578' }, 'NPR' => { 'country' => ['NEPAL'], 'name' => 'Nepalese Rupee', 'code' => 'NPR', 'number' => '524' }, 'NZD' => { 'country' => [ 'COOK ISLANDS (THE)', 'NEW ZEALAND', 'NIUE', 'PITCAIRN', 'TOKELAU' ], 'name' => 'New Zealand Dollar', 'code' => 'NZD', 'number' => '554' }, 'OMR' => { 'country' => ['OMAN'], 'name' => 'Rial Omani', 'code' => 'OMR', 'number' => '512' }, 'PAB' => { 'country' => ['PANAMA'], 'name' => 'Balboa', 'code' => 'PAB', 'number' => '590' }, 'PEN' => { 'country' => ['PERU'], 'name' => 'Nuevo Sol', 'code' => 'PEN', 'number' => '604' }, 'PGK' => { 'country' => ['PAPUA NEW GUINEA'], 'name' => 'Kina', 'code' => 'PGK', 'number' => '598' }, 'PHP' => { 'country' => ['PHILIPPINES (THE)'], 'name' => 'Philippine Peso', 'code' => 'PHP', 'number' => '608' }, 'PKR' => { 'country' => ['PAKISTAN'], 'name' => 'Pakistan Rupee', 'code' => 'PKR', 'number' => '586' }, 'PLN' => { 'country' => ['POLAND'], 'name' => 'Zloty', 'code' => 'PLN', 'number' => '985' }, 'PYG' => { 'country' => ['PARAGUAY'], 'name' => 'Guarani', 'code' => 'PYG', 'number' => '600' }, 'QAR' => { 'country' => ['QATAR'], 'name' => 'Qatari Rial', 'code' => 'QAR', 'number' => '634' }, 'RON' => { 'country' => ['ROMANIA'], 'name' => 'Romanian Leu', 'code' => 'RON', 'number' => '946' }, 'RSD' => { 'country' => ['SERBIA'], 'name' => 'Serbian Dinar', 'code' => 'RSD', 'number' => '941' }, 'RUB' => { 'country' => ['RUSSIAN FEDERATION (THE)'], 'name' => 'Russian Ruble', 'code' => 'RUB', 'number' => '643' }, 'RWF' => { 'country' => ['RWANDA'], 'name' => 'Rwanda Franc', 'code' => 'RWF', 'number' => '646' }, 'SAR' => { 'country' => ['SAUDI ARABIA'], 'name' => 'Saudi Riyal', 'code' => 'SAR', 'number' => '682' }, 'SBD' => { 'country' => ['SOLOMON ISLANDS'], 'name' => 'Solomon Islands Dollar', 'code' => 'SBD', 'number' => '090' }, 'SCR' => { 'country' => ['SEYCHELLES'], 'name' => 'Seychelles Rupee', 'code' => 'SCR', 'number' => '690' }, 'SDG' => { 'country' => ['SUDAN (THE)'], 'name' => 'Sudanese Pound', 'code' => 'SDG', 'number' => '938' }, 'SEK' => { 'country' => ['SWEDEN'], 'name' => 'Swedish Krona', 'code' => 'SEK', 'number' => '752' }, 'SGD' => { 'country' => ['SINGAPORE'], 'name' => 'Singapore Dollar', 'code' => 'SGD', 'number' => '702' }, 'SHP' => { 'country' => ['SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA'], 'name' => 'Saint Helena Pound', 'code' => 'SHP', 'number' => '654' }, 'SLE' => { 'country' => ['SIERRA LEONE'], 'name' => 'Leone', 'code' => 'SLE', 'number' => '925' }, 'SOS' => { 'country' => ['SOMALIA'], 'name' => 'Somali Shilling', 'code' => 'SOS', 'number' => '706' }, 'SRD' => { 'country' => ['SURINAME'], 'name' => 'Surinam Dollar', 'code' => 'SRD', 'number' => '968' }, 'SSP' => { 'country' => ['SOUTH SUDAN'], 'name' => 'South Sudanese Pound', 'code' => 'SSP', 'number' => '728' }, 'STN' => { 'country' => ['SAO TOME AND PRINCIPE'], 'name' => 'Dobra', 'code' => 'STN', 'number' => '930' }, 'SVC' => { 'country' => ['EL SALVADOR'], 'name' => 'El Salvador Colon', 'code' => 'SVC', 'number' => '222' }, 'SYP' => { 'country' => ['SYRIAN ARAB REPUBLIC'], 'name' => 'Syrian Pound', 'code' => 'SYP', 'number' => '760' }, 'SZL' => { 'country' => ['SWAZILAND'], 'name' => 'Lilangeni', 'code' => 'SZL', 'number' => '748' }, 'THB' => { 'country' => ['THAILAND'], 'name' => 'Baht', 'code' => 'THB', 'number' => '764' }, 'TJS' => { 'country' => ['TAJIKISTAN'], 'name' => 'Somoni', 'code' => 'TJS', 'number' => '972' }, 'TMT' => { 'country' => ['TURKMENISTAN'], 'name' => 'Turkmenistan New Manat', 'code' => 'TMT', 'number' => '934' }, 'TND' => { 'country' => ['TUNISIA'], 'name' => 'Tunisian Dinar', 'code' => 'TND', 'number' => '788' }, 'TOP' => { 'country' => ['TONGA'], 'name' => 'Pa’anga', 'code' => 'TOP', 'number' => '776' }, 'TRY' => { 'country' => ['TURKEY'], 'name' => 'Turkish Lira', 'code' => 'TRY', 'number' => '949' }, 'TTD' => { 'country' => ['TRINIDAD AND TOBAGO'], 'name' => 'Trinidad and Tobago Dollar', 'code' => 'TTD', 'number' => '780' }, 'TWD' => { 'country' => ['TAIWAN (PROVINCE OF CHINA)'], 'name' => 'New Taiwan Dollar', 'code' => 'TWD', 'number' => '901' }, 'TZS' => { 'country' => ['TANZANIA, UNITED REPUBLIC OF'], 'name' => 'Tanzanian Shilling', 'code' => 'TZS', 'number' => '834' }, 'UAH' => { 'country' => ['UKRAINE'], 'name' => 'Hryvnia', 'code' => 'UAH', 'number' => '980' }, 'UGX' => { 'country' => ['UGANDA'], 'name' => 'Uganda Shilling', 'code' => 'UGX', 'number' => '800' }, 'USD' => { 'country' => [ 'AMERICAN SAMOA', 'BONAIRE, SINT EUSTATIUS AND SABA', 'BRITISH INDIAN OCEAN TERRITORY (THE)', 'ECUADOR', 'EL SALVADOR', 'GUAM', 'HAITI', 'MARSHALL ISLANDS (THE)', 'MICRONESIA (FEDERATED STATES OF)', 'NORTHERN MARIANA ISLANDS (THE)', 'PALAU', 'PANAMA', 'PUERTO RICO', 'TIMOR-LESTE', 'TURKS AND CAICOS ISLANDS (THE)', 'UNITED STATES MINOR OUTLYING ISLANDS (THE)', 'UNITED STATES OF AMERICA (THE)', 'VIRGIN ISLANDS (BRITISH)', 'VIRGIN ISLANDS (U.S.)' ], 'name' => 'US Dollar', 'code' => 'USD', 'number' => '840' }, 'USN' => { 'country' => ['UNITED STATES OF AMERICA (THE)'], 'name' => 'US Dollar (Next day)', 'code' => 'USN', 'number' => '997' }, 'UYI' => { 'country' => ['URUGUAY'], 'name' => 'Uruguay Peso en Unidades Indexadas (URUIURUI)', 'code' => 'UYI', 'number' => '940' }, 'UYU' => { 'country' => ['URUGUAY'], 'name' => 'Peso Uruguayo', 'code' => 'UYU', 'number' => '858' }, 'UZS' => { 'country' => ['UZBEKISTAN'], 'name' => 'Uzbekistan Sum', 'code' => 'UZS', 'number' => '860' }, 'VED' => { 'country' => ['VENEZUELA (BOLIVARIAN REPUBLIC OF)'], 'name' => 'Bolivar', 'code' => 'VED', 'number' => '926' }, 'VEF' => { 'country' => ['VENEZUELA (BOLIVARIAN REPUBLIC OF)'], 'name' => 'Bolivar', 'code' => 'VEF', 'number' => '937' }, 'VND' => { 'country' => ['VIET NAM'], 'name' => 'Dong', 'code' => 'VND', 'number' => '704' }, 'VUV' => { 'country' => ['VANUATU'], 'name' => 'Vatu', 'code' => 'VUV', 'number' => '548' }, 'WST' => { 'country' => ['SAMOA'], 'name' => 'Tala', 'code' => 'WST', 'number' => '882' }, 'XAF' => { 'country' => [ 'CAMEROON', 'CENTRAL AFRICAN REPUBLIC (THE)', 'CHAD', 'CONGO (THE)', 'EQUATORIAL GUINEA', 'GABON' ], 'name' => 'CFA Franc BEAC', 'code' => 'XAF', 'number' => '950' }, 'XCD' => { 'country' => [ 'ANGUILLA', 'ANTIGUA AND BARBUDA', 'DOMINICA', 'GRENADA', 'MONTSERRAT', 'SAINT KITTS AND NEVIS', 'SAINT LUCIA', 'SAINT VINCENT AND THE GRENADINES' ], 'name' => 'East Caribbean Dollar', 'code' => 'XCD', 'number' => '951' }, 'XDR' => { 'country' => ['INTERNATIONAL MONETARY FUND (IMF) '], 'name' => 'SDR (Special Drawing Right)', 'code' => 'XDR', 'number' => '960' }, 'XOF' => { 'country' => [ 'BENIN', 'BURKINA FASO', 'CÔTE D\'IVOIRE', 'GUINEA-BISSAU', 'MALI', 'NIGER (THE)', 'SENEGAL', 'TOGO' ], 'name' => 'CFA Franc BCEAO', 'code' => 'XOF', 'number' => '952' }, 'XPF' => { 'country' => [ 'FRENCH POLYNESIA', 'NEW CALEDONIA', 'WALLIS AND FUTUNA' ], 'name' => 'CFP Franc', 'code' => 'XPF', 'number' => '953' }, 'XSU' => { 'country' => ['SISTEMA UNITARIO DE COMPENSACION REGIONAL DE PAGOS "SUCRE"'], 'name' => 'Sucre', 'code' => 'XSU', 'number' => '994' }, 'XUA' => { 'country' => ['MEMBER COUNTRIES OF THE AFRICAN DEVELOPMENT BANK GROUP'], 'name' => 'ADB Unit of Account', 'code' => 'XUA', 'number' => '965' }, 'YER' => { 'country' => ['YEMEN'], 'name' => 'Yemeni Rial', 'code' => 'YER', 'number' => '886' }, 'ZAR' => { 'country' => [ 'LESOTHO', 'NAMIBIA', 'SOUTH AFRICA' ], 'name' => 'Rand', 'code' => 'ZAR', 'number' => '710' }, 'ZMW' => { 'country' => ['ZAMBIA'], 'name' => 'Zambian Kwacha', 'code' => 'ZMW', 'number' => '967' }, 'ZWL' => { 'country' => ['ZIMBABWE'], 'name' => 'Zimbabwe Dollar', 'code' => 'ZWL', 'number' => '932' }, ); # ======================================================================= # known_currencies (public function) # # This function returns the known currency list. This is based on the # cached currency list in this module. Use fetch_live_currencies for the # live list. sub known_currencies { return \%currencies; } # ======================================================================= # fetch_live_currencies (public function) # # This function retrieved the live currency list from the Yahoo Finance # website. This function should really only be used to test if the known # currency list in this module is out of date. sub fetch_live_currencies { ### [<now>] Calling fetch_live_currencies with URL: $CURRENCY_URL my $ua = LWP::UserAgent->new(); my $reply = $ua->request(GET $CURRENCY_URL); return unless $reply->is_success; my $te = HTML::TableExtract->new( headers => ['Country', 'Currency', 'Code', 'Number']); $te->parse(decode('UTF-8', $reply->content)); my $ts = $te->first_table_found || die 'Currency table not found'; my %result = (); foreach my $row ($ts->rows) { my ($country, $currency, $code, $number) = @$row; next unless defined $code; # some country names in the HTML source have multi-space breaks $country =~ s/ +/ /g; if (exists $result{$code}) { push(@{$result{$code}->{'country'}}, $country); } else { $result{$code} = {'name' => $currency, 'country' => [$country], 'code' => $code, 'number' => $number}; } } if (DEBUG) { $Data::Dumper::Sortkeys = 1; ### result: \%result } return \%result; } 1; =head1 NAME Finance::Quote::Currencies - List of currencies from iban.com =head1 SYNOPSIS use Finance::Quote::Currencies; my $currencies = Finance::Quote::Currencies::known_currencies(); # Grab the latest list my $live_currencies = Finance::Quote::Currencies::fetchive_currencies(); =head1 DESCRIPTION This module provides a list of known currencies from iban.com. known_currencies returns a cached currency information stored in this module. fetch_live_currencies is a function that fetches the latest currency information. Both functions return a hash {CODE => {'name' => 'Currency Name', 'country' => ['List of countries known to use this currency'], 'number' => 'ISO 4217 currency code'}} =head1 CACHE DATE The currency list stored in this module was last copied from the live site Oct 2020. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Currency information fetched through this module is bound by terms and conditons available at https://www.iban.com/terms. =head1 AUTHORS Bradley Dean <bjdean@bjdean.id.au> - Original Yahoo version =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/lib/Finance/Quote/Deka.pm��������������������������������������������������������0000644�0001750�0001750�00000006255�15003302667�020353� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # # Deka import modul based on Union.pm # Version 2016-01-12 package Finance::Quote::Deka; require 5.005; use strict; use LWP::UserAgent; use HTTP::Request::Common; our $VERSION = '1.65'; # VERSION sub methods { return (deka => \&deka); } sub labels { return (deka => [qw/exchange name date isodate price method/]); } # ======================================================================= # The deka routine gets quotes of DEKA funds (Deka Investments) # On their website DEKA provides a csv file in the format # label1;label2;... # symbol1;name1;date1;date_before1;bid1;... # symbol2;name2;date2;date_before2,bid2,... # ... # # This subroutine was written by Andre Joost <andrejoost@gmx.de> # Convert number separators to US values sub convert_price { $_ = shift; tr/.,/,./ ; return $_; } sub deka { my $quoter = shift; my @funds = @_; return unless @funds; my $ua = $quoter->user_agent; my (%fundhash, @q, %info, $tempdate); # create hash of all funds requested foreach my $fund (@funds) { $fundhash{$fund} = 0; } # get csv data my $response = $ua->request(GET &dekaurl); if ($response->is_success) { # process csv data foreach (split('\015?\012',$response->content)) { # @q = $quoter->parse_csv($_) or next; @q = split(/;/) or next; next unless (defined $q[0]); if (exists $fundhash{$q[0]}) { $fundhash{$q[0]} = 1; $info{$q[0], "exchange"} = "DEKA"; $info{$q[0], "name"} = $q[1]; $info{$q[0], "symbol"} = $q[0]; $tempdate = $q[2]; $quoter->store_date(\%info, $q[0], {eurodate => $tempdate}); $info{$q[0], "price"} = convert_price($q[4]); $info{$q[0], "last"} = convert_price($q[4]); $info{$q[0], "method"} = "deka"; $info{$q[0], "currency"} = $q[8]; $info{$q[0], "success"} = 1; } } # check to make sure a value was returned for every fund requested foreach my $fund (keys %fundhash) { if ($fundhash{$fund} == 0) { $info{$fund, "success"} = 0; $info{$fund, "errormsg"} = "No data returned"; } } } else { foreach my $fund (@funds) { $info{$fund, "success"} = 0; $info{$fund, "errormsg"} = "HTTP error"; } } return wantarray() ? %info : \%info; } # DEKA provides a csv file named fondspreise.csv containing the prices of all # their funds for the most recent business day. sub dekaurl { return "https://www.deka.de/privatkunden/pflichtseiten/fondspreise?service=fondspreislisteExportController&action=exportCsv&typ=inVertrieb"; } 1; =head1 NAME Finance::Quote::Deka - Obtain quotes from DEKA (Wertpapierhaus der Sparkassen). =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; %stockinfo = $q->fetch("deka","DE0008474503"); =head1 DESCRIPTION This module obtains information about DEKA managed funds. Information returned by this module is governed by DEKA's terms and conditions. =head1 LABELS RETURNED The following labels may be returned by Finance::Quote::DEKA: exchange, name, date, price, last. =head1 SEE ALSO DEKA (Deka Investments), http://www.deka.de/ =cut ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/Makefile.PL����������������������������������������������������������������������0000644�0001750�0001750�00000012237�15003302667�015712� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.030. use strict; use warnings; use 5.010001; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Get stock and mutual fund quotes from various exchanges", "AUTHOR" => "Erik Colson <eco\@ecocode.net>, Bruce Schuck <bschuck\@asgard-systems.com>, Paul Fenwick <pjf\@perltraining.com.au>, Vincent Lucarelli <vincent.lucarelli\@gmail.com>, David Hampton <hampton-sf\@rainbolthampton.net>, moenny <>, jvolkening <jdv\@base2bio.com>, Pawel Konieczny <konieczp\@zonnet.nl>, Mike Alexander <mta\@umich.edu>, AndreJ <Andre-J\@users.noreply.github.com>, Vinay S Shastry <vinayshastry\@gmail.com>, Bradley Dean <bjdean\@bjdean.id.au>, Brent Neal <brent\@reindeergraphics.com>, Jacinta Richardson <jarich\@perltraining.com.au>, e-dorigatti <emilio.dorigatti\@gmail.com>, goodvibes2 <goodchris96\@gmail.com>, Paul Howarth <paul\@city-fan.org>, Sam Morris <sam\@robots.org.uk>, Linas Vepstas <linas\@linas.org>, Rajan Vaswani <111571283+bgr22112\@users.noreply.github.com>, Hiranya Samarasekera <hiranyas\@gmail.com>, Manuel Friedli <manuel\@fritteli.ch>, Przemys\x{142}aw Kryger <pkryger\@gmail.com>, Achim Winkler <mumpitzstuff\@gmail.com>, Diego Marcolungo <diego.marcolungo\@gmail.com>, John Haiducek <jhaiduce\@gmail.com>, alex314159 <alex314159\@users.noreply.github.com>, gregor herrmann <gregoa\@debian.org>, Gustavo R. Montesino <grmontesino\@gmail.com>, Jalon Avens <jalonavens\@snave.info>, Sigve Indregard <sigve\@indregard.no>, bgr22112 <111571283+bgr22112\@users.noreply.github.com>, goodvibes2 <chris.good\@ozemail.com.au>", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Finance-Quote", "LICENSE" => "gpl", "MIN_PERL_VERSION" => "5.010001", "NAME" => "Finance::Quote", "PREREQ_PM" => { "Carp" => 0, "Compress::Zlib" => 0, "Date::Parse" => 0, "DateTime::Format::Strptime" => 0, "Encode" => 0, "Exporter" => 0, "HTML::Entities" => 0, "HTML::TableExtract" => 0, "HTML::TokeParser" => 0, "HTML::TreeBuilder" => 0, "HTML::TreeBuilder::XPath" => 0, "HTTP::CookieJar::LWP" => "0.014", "HTTP::Cookies" => 0, "HTTP::Headers" => 0, "HTTP::Request" => 0, "HTTP::Request::Common" => 0, "HTTP::Status" => 0, "IO::Handle" => 0, "IO::String" => 0, "IO::Uncompress::Unzip" => 0, "JSON" => 0, "LWP::Protocol::http" => 0, "LWP::Protocol::https" => 0, "LWP::Simple" => 0, "LWP::UserAgent" => "6.48", "Module::Load" => "0.36", "Mozilla::CA" => 0, "POSIX" => 0, "Readonly" => 0, "Scalar::Util" => 0, "Spreadsheet::XLSX" => 0, "String::Util" => 0, "Test2" => "1.302167", "Text::Template" => 0, "Time::Piece" => 0, "Time::Seconds" => 0, "Try::Tiny" => 0, "URI::Escape" => "3.31", "Web::Scraper" => 0, "XML::LibXML" => 0, "base" => 0, "constant" => 0, "if" => 0, "strict" => 0, "utf8" => 0, "vars" => 0, "warnings" => 0 }, "TEST_REQUIRES" => { "Date::Manip" => 0, "Date::Range" => 0, "Date::Simple" => 0, "DateTime" => 0, "DateTime::Duration" => 0, "DateTime::Format::ISO8601" => 0, "File::Spec" => 0, "Module::CPANTS::Analyse" => 0, "String::Util" => 0, "Test::Kwalitee" => 0, "Test::More" => 0, "Test::Perl::Critic" => 0, "feature" => 0, "open" => 0 }, "VERSION" => "1.65", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Carp" => 0, "Compress::Zlib" => 0, "Date::Manip" => 0, "Date::Parse" => 0, "Date::Range" => 0, "Date::Simple" => 0, "DateTime" => 0, "DateTime::Duration" => 0, "DateTime::Format::ISO8601" => 0, "DateTime::Format::Strptime" => 0, "Encode" => 0, "Exporter" => 0, "File::Spec" => 0, "HTML::Entities" => 0, "HTML::TableExtract" => 0, "HTML::TokeParser" => 0, "HTML::TreeBuilder" => 0, "HTML::TreeBuilder::XPath" => 0, "HTTP::CookieJar::LWP" => "0.014", "HTTP::Cookies" => 0, "HTTP::Headers" => 0, "HTTP::Request" => 0, "HTTP::Request::Common" => 0, "HTTP::Status" => 0, "IO::Handle" => 0, "IO::String" => 0, "IO::Uncompress::Unzip" => 0, "JSON" => 0, "LWP::Protocol::http" => 0, "LWP::Protocol::https" => 0, "LWP::Simple" => 0, "LWP::UserAgent" => "6.48", "Module::CPANTS::Analyse" => 0, "Module::Load" => "0.36", "Mozilla::CA" => 0, "POSIX" => 0, "Readonly" => 0, "Scalar::Util" => 0, "Spreadsheet::XLSX" => 0, "String::Util" => 0, "Test2" => "1.302167", "Test::Kwalitee" => 0, "Test::More" => 0, "Test::Perl::Critic" => 0, "Text::Template" => 0, "Time::Piece" => 0, "Time::Seconds" => 0, "Try::Tiny" => 0, "URI::Escape" => "3.31", "Web::Scraper" => 0, "XML::LibXML" => 0, "base" => 0, "constant" => 0, "feature" => 0, "if" => 0, "open" => 0, "strict" => 0, "utf8" => 0, "vars" => 0, "warnings" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/LICENSE��������������������������������������������������������������������������0000644�0001750�0001750�00000043523�15003302667�014747� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������This software is Copyright (c) 2013 by Erik Colson <eco@ecocode.net>. This is free software, licensed under: The GNU General Public License, Version 2, June 1991 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/MANIFEST�������������������������������������������������������������������������0000644�0001750�0001750�00000006666�15003302667�015102� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.030. Changes Documentation/FAQ Documentation/Hackers-Guide Documentation/License Documentation/README Documentation/Release.md Examples/Quote_example.pl Examples/chkshares.pl Examples/currency-lookup.pl Examples/stockdump.pl LICENSE MANIFEST META.json META.yml Makefile.PL README htdocs/Images/Button1.gif htdocs/Images/Button2.gif htdocs/Images/bg.jpg htdocs/Images/bg2.jpg htdocs/Images/logo_big1.gif htdocs/Images/logo_medium1.gif htdocs/Images/logo_small1.gif htdocs/Images/logo_small2.gif htdocs/Stylesheet.css htdocs/developer.html htdocs/discussion.html htdocs/documentation.html htdocs/download.html htdocs/index.html htdocs/tpj/finance-quote-example htdocs/tpj/finance-quote-sidebar htdocs/tpj/finance-quote.txt lib/Finance/Quote.pm lib/Finance/Quote/AEX.pm lib/Finance/Quote/ASEGR.pm lib/Finance/Quote/ASX.pm lib/Finance/Quote/AlphaVantage.pm lib/Finance/Quote/BSEIndia.pm lib/Finance/Quote/BVB.pm lib/Finance/Quote/Bloomberg.pm lib/Finance/Quote/BorsaItaliana.pm lib/Finance/Quote/Bourso.pm lib/Finance/Quote/CSE.pm lib/Finance/Quote/Comdirect.pm lib/Finance/Quote/Consorsbank.pm lib/Finance/Quote/Currencies.pm lib/Finance/Quote/CurrencyRates/AlphaVantage.pm lib/Finance/Quote/CurrencyRates/CurrencyFreaks.pm lib/Finance/Quote/CurrencyRates/ECB.pm lib/Finance/Quote/CurrencyRates/FinanceAPI.pm lib/Finance/Quote/CurrencyRates/Fixer.pm lib/Finance/Quote/CurrencyRates/OpenExchange.pm lib/Finance/Quote/CurrencyRates/YahooJSON.pm lib/Finance/Quote/Deka.pm lib/Finance/Quote/FTfunds.pm lib/Finance/Quote/FinanceAPI.pm lib/Finance/Quote/Finanzpartner.pm lib/Finance/Quote/Fondsweb.pm lib/Finance/Quote/Fool.pm lib/Finance/Quote/GoldMoney.pm lib/Finance/Quote/GoogleWeb.pm lib/Finance/Quote/HU.pm lib/Finance/Quote/IndiaMutual.pm lib/Finance/Quote/MarketWatch.pm lib/Finance/Quote/MorningstarCH.pm lib/Finance/Quote/MorningstarJP.pm lib/Finance/Quote/MorningstarUK.pm lib/Finance/Quote/NSEIndia.pm lib/Finance/Quote/NZX.pm lib/Finance/Quote/OnVista.pm lib/Finance/Quote/Oslobors.pm lib/Finance/Quote/SEB.pm lib/Finance/Quote/SIX.pm lib/Finance/Quote/Sinvestor.pm lib/Finance/Quote/StockData.pm lib/Finance/Quote/Stooq.pm lib/Finance/Quote/TMX.pm lib/Finance/Quote/TSP.pm lib/Finance/Quote/TesouroDireto.pm lib/Finance/Quote/Tiaacref.pm lib/Finance/Quote/Tradegate.pm lib/Finance/Quote/TreasuryDirect.pm lib/Finance/Quote/Troweprice.pm lib/Finance/Quote/TwelveData.pm lib/Finance/Quote/Union.pm lib/Finance/Quote/UserAgent.pm lib/Finance/Quote/XETRA.pm lib/Finance/Quote/YahooJSON.pm lib/Finance/Quote/YahooWeb.pm lib/Finance/Quote/ZA.pm t/00-store-date.t t/01-pod.t t/02-pod-coverage.t t/03-kwalitee.t t/04-critic.t t/05-data-dumper.t t/aex.t t/alphavantage.t t/asegr.t t/asx.t t/author-pod-syntax.t t/bloomberg.t t/borsa_italiana.t t/bourso.t t/bseindia.t t/bvb.t t/comdirect.t t/consorsbank.t t/cse.t t/currencies.t t/currency-openexchange.t t/currency.t t/currency_lookup.t t/deka.t t/dws.t t/financeapi.t t/finanzpartner.t t/fondsweb.t t/fool.t t/fq-class-methods.t t/fq-object-methods.t t/ftfunds.t t/goldmoney.t t/googleweb.t t/hu.t t/indiamutual.t t/marketwatch.t t/morningstarCH.t t/morningstarJP.t t/morningstarUK.t t/nseindia.t t/nzx.t t/onvista.t t/oslobors.t t/quote.t t/seb.t t/sinvestor.t t/six.t t/stockdata.t t/stooq.t t/tesouro_direto.t t/tiaacref.t t/tmx.t t/tradegate.t t/treasurydirect.t t/troweprice.t t/tsp.t t/twelvedata.t t/ukfunds.t t/union.t t/usfedbonds.t t/xetra.t t/yahoojson.t t/yahooweb.t t/za.t ��������������������������������������������������������������������������Finance-Quote-1.65/META.json������������������������������������������������������������������������0000644�0001750�0001750�00000011653�15003302667�015362� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "abstract" : "Get stock and mutual fund quotes from various exchanges", "author" : [ "Erik Colson <eco@ecocode.net>", "Bruce Schuck <bschuck@asgard-systems.com>", "Paul Fenwick <pjf@perltraining.com.au>", "Vincent Lucarelli <vincent.lucarelli@gmail.com>", "David Hampton <hampton-sf@rainbolthampton.net>", "moenny <>", "jvolkening <jdv@base2bio.com>", "Pawel Konieczny <konieczp@zonnet.nl>", "Mike Alexander <mta@umich.edu>", "AndreJ <Andre-J@users.noreply.github.com>", "Vinay S Shastry <vinayshastry@gmail.com>", "Bradley Dean <bjdean@bjdean.id.au>", "Brent Neal <brent@reindeergraphics.com>", "Jacinta Richardson <jarich@perltraining.com.au>", "e-dorigatti <emilio.dorigatti@gmail.com>", "goodvibes2 <goodchris96@gmail.com>", "Paul Howarth <paul@city-fan.org>", "Sam Morris <sam@robots.org.uk>", "Linas Vepstas <linas@linas.org>", "Rajan Vaswani <111571283+bgr22112@users.noreply.github.com>", "Hiranya Samarasekera <hiranyas@gmail.com>", "Manuel Friedli <manuel@fritteli.ch>", "Przemys\u0142aw Kryger <pkryger@gmail.com>", "Achim Winkler <mumpitzstuff@gmail.com>", "Diego Marcolungo <diego.marcolungo@gmail.com>", "John Haiducek <jhaiduce@gmail.com>", "alex314159 <alex314159@users.noreply.github.com>", "gregor herrmann <gregoa@debian.org>", "Gustavo R. Montesino <grmontesino@gmail.com>", "Jalon Avens <jalonavens@snave.info>", "Sigve Indregard <sigve@indregard.no>", "bgr22112 <111571283+bgr22112@users.noreply.github.com>", "goodvibes2 <chris.good@ozemail.com.au>" ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010", "license" : [ "gpl_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Finance-Quote", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "1.41" } }, "runtime" : { "requires" : { "Carp" : "0", "Compress::Zlib" : "0", "Date::Parse" : "0", "DateTime::Format::Strptime" : "0", "Encode" : "0", "Exporter" : "0", "HTML::Entities" : "0", "HTML::TableExtract" : "0", "HTML::TokeParser" : "0", "HTML::TreeBuilder" : "0", "HTML::TreeBuilder::XPath" : "0", "HTTP::CookieJar::LWP" : "0.014", "HTTP::Cookies" : "0", "HTTP::Headers" : "0", "HTTP::Request" : "0", "HTTP::Request::Common" : "0", "HTTP::Status" : "0", "IO::Handle" : "0", "IO::String" : "0", "IO::Uncompress::Unzip" : "0", "JSON" : "0", "LWP::Protocol::http" : "0", "LWP::Protocol::https" : "0", "LWP::Simple" : "0", "LWP::UserAgent" : "6.48", "Module::Load" : "0.36", "Mozilla::CA" : "0", "POSIX" : "0", "Readonly" : "0", "Scalar::Util" : "0", "Spreadsheet::XLSX" : "0", "String::Util" : "0", "Test2" : "1.302167", "Text::Template" : "0", "Time::Piece" : "0", "Time::Seconds" : "0", "Try::Tiny" : "0", "URI::Escape" : "3.31", "Web::Scraper" : "0", "XML::LibXML" : "0", "base" : "0", "constant" : "0", "if" : "0", "perl" : "v5.10.1", "strict" : "0", "utf8" : "0", "vars" : "0", "warnings" : "0" } }, "test" : { "requires" : { "Date::Manip" : "0", "Date::Range" : "0", "Date::Simple" : "0", "DateTime" : "0", "DateTime::Duration" : "0", "DateTime::Format::ISO8601" : "0", "File::Spec" : "0", "Module::CPANTS::Analyse" : "0", "String::Util" : "0", "Test::Kwalitee" : "0", "Test::More" : "0", "Test::Perl::Critic" : "0", "feature" : "0", "open" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/finance-quote/finance-quote/issues" }, "homepage" : "http://finance-quote.sourceforge.net/", "repository" : { "type" : "git", "url" : "git://github.com/finance-quote/finance-quote", "web" : "https://github.com/finance-quote/finance-quote" } }, "version" : "1.65", "x_generated_by_perl" : "v5.34.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.27", "x_spdx_expression" : "GPL-2.0-only" } �������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/��������������������������������������������������������������������������0000775�0001750�0001750�00000000000�15003302667�015221� 5����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/�������������������������������������������������������������������0000775�0001750�0001750�00000000000�15003302667�016426� 5����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/logo_big1.gif������������������������������������������������������0000644�0001750�0001750�00000044205�15003302667�020762� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a{}�?� d'I8$ΕYdO'$ cH0+>:HHRǮwvi1,w]8/%ulhLN0hUL'-.WTYw_*'u<~yW?)͚mJ婊EǦѻNJ`;<$uSc~<XR0v[JcjP|vi:ۯ8ABgZ1K!��?�,����{}��pH,Ȥrl:ШtJZجvzxlXzn|N[̖|b) up1177  ; ;;|~gi 555 117w1 )) 1�0HxpF ڴƍ\ |ig^)2ثAƁ>�hK4P@C›8sܹCӞEhrߒ^SDYЮ.MdJ?yKY7.4vm[ʴ]QfAZjF^?yǐ#pPيz+7_|N? 28`.+۸9|9:j] xq8bk jRP|y ҫ~{O΍1_"P� A4W8݂ 6�D&�bj ~p P?ri.$ވ8ؠ6AL DGf(dba�A""*恂:)明i QΤfCYt9TR -Hzdd* Y@T6@rXbˣF(oV @"*ꨤJLpl � ƚyb:*ĺq5P Y m^|)ϴ3ĬjjD?ƶ@R-Lx -e «Nc+b8`8<Pob. {!q!4eؐsB ̥_Z,rDP@*'0*;2 }%p칀쳰h;LFL KSrwT\/R3⁄p5,v?M 'h#MkS6$� ˋ8fYpZx/bN&$6=CJ3tD!@ 6#hÙ#tlݐX8h7xISvK'Xxwԡ @Ư�&a%�^am!>;6t}h箴,9Ip  E=tDہlǸ-r*{;h%$x ބvYЁuHͮEp^(ֈ=V&&ys*״ Khe}n "WcY٘,`p wؕ8G*Znת) BFE6} h@=ij](Ăh` }m�%gעF:4I<R S". {e`Ş2"h/, 0 !JiF(pvj� i3.,^bǴ%i)K-s[Ȟ-B^j, Ĥ8SBpJU\Rة)![1>O ~{¢B7Rv R<+ :n&*RdN],5~qX+@B &(1AЙ_Px>,T�t`Ԓjj^9b`Da8 $.y�?hҟ)) FPL)FZ^Z#de8p �ZזGN(/Bt? A'u)0X.䱠='*v0Kq-HE<Q̄l^K׸QpK>(57T[Vr2@m_�%p M1E=�U0z%Y $V+`G Ms))xgjP8y_P^ x D+c@&A FM-bG@hQ7с!xŊ w,x{V�Z.m `y Ĕ?2 Qh9�&7Ζ3ɹaWBhv Z@%!œ;(w@+abR\)* ¨֫h O kIA r,WA ZA8óM] $`a@'`#k!K:.S&.e ԪV(+70<`M}4;:p30+UUh.s=dwGr #X9 HekŰt~&V_^Ss�CvPքY,qݒ\:o6w̚(`zDHLENG:.t[7x2Nn)euۀ&|M3@n^'|ixskEoN-P3|S՚w g W.Ζ2%Aa}RG9A rmʷ~>H K+Pz&Agv!ÊSTZ:uۃ)H8bpx-d ��n u`2}wn+0EaVf]&\3{aofvzTyzp9zgzpguwcWgׁdG'؁8eW!Gi&H{2'IRt&ps37.jUB[|V{L@tzp CNPD%4TwF%eYguv`c0� uPEhׇv�h7PGC�[� `P>pF2% nkdo1\Ff6ԁGãz;hwf@M9x*4rff98Qㅃ؋4`k!4}F{HHX[qc mu`b`8(!PjO'wO#( ^\&V#� Q~6p@`lxcsB`P1և�� )U6c G(gl#vY+�h밀ʐJ$@ReoSxw=z؁S B,Hw9.Ӂ8M1sC=tR0H3p�i%(dg`a &3FF))Kuv %NDW}(+avd/" %<}6 <)5 BcPfcЛ ;'�7 �q&~IpE밝E1R&@he0D4cL#GyÌ9x�T NƂj*`<x uIf2uHvMA]DIjhَO07PI(>u�o&Џsw?W GI4P0?G%V1�iU^c'PĩN Ňi‡yȓH`+4ypx fZ^ I$44IenVG'RifHG\4:M7x�yLJvV`37x~p+4ev39ؗODv P"{.Ⱥ.t5p OAGp{qj/O%9FjYWRl%:KM*QWT) y鷥6)9`JƩyYc)CBIY(QwP="_` H4Pp�,,lr{BI�xBF<˳ni8xf9W3P3"@fƴff\pz$2%PJ:<ЁB6saDP.CV{RWR"0Ot`�rl'J*>e䊰"Bx)Pj~$ !~:9D df+'d;9 c56 �.I E~Z=p.=-@-�| )=9�㤨Qnj6 f�cko3pyGb8c+z1K3[ 0L˿<ЎǵZdcLcpoL<xxXLM /Ҷ$I@OdI� 3*I�fEk'n*m5ai!**Rbt Vluw-;T"hy*~;6Ǔ ;~ 7 �H  Zv 0�K /@D̛ r;eR٨5n8[JzK�cc5Ylfivp l|uJ  -k<U 9&؋H1$ GI+i-AascXB){jI[+}َFˮf G�70pE��5�3wD;b�r Beep� %C`͛!rĞi$-%Sʜx  bS;ʿ'`%.=\cV4̴m!rE I|)'g%2 s6J$9 g|O)&[2!aQ@Xṿ'P\O�Il5RדQzPб#6Xpǘ%�`9p>h -+cF$ ݣ'Ez'8<m\\V <0w`@+@K ?-,�k݅5<�<na[=X3S OS<ե? )⛱�GG): Cئ_ZnPD!*q+VyuYʮ��4A,)֓9v7\%vpͬw��.�[۶48'݊+^i;uR<@-z+@?]<U?QGm_[|z9= Ea3MF ^ ~铞>钮ZqN^r|KO,P|2,qr EAz]`fءV\Uʮ&Q!m7P u˩;eC? Gņ^ڮ �:䯝Y�n�~lWZ^>\3@d^̍\ڍ-#UG+P acnnfckdV׳WLKma\ڶafXh n Z18n ~1W) H z #U+ŏ:H1ix*Ig]/B 'R�".Tr�Nn(,+K4(.@jVݣ]C�- #wBmk_�=7uԉ/-ToLk?(r(OƱ$1f֚DXk 0Ê(!WEERz-<®.^Uw^0hKePm{p-K0 *۫ QT4(pV+W^� h,xGgfC8oACPl<A(4x) < y -q8 <06++B3JSS5f*g-b=(xfkgf<~-j&jj8jSS3khh**Nj"2;?@U=2L"L3>0p`b&f_C �0HaE�8lj g1R| ` % `aA6^)L;\ Ki6bԩ0�+4BE@8(0#m!RXBJ�Z4h`iy m䑌藨H_,ԪD ס<:HwOhDB󰗊+`Z2PΤLgefcǞ9S,4ў[-nL1D�,Щ EL}3<cF~&%(p`a`QLx˂~)n#(LBٮ� KOSҊ[‚88)t@C ' + i&X ҂�BJ*H0 18,15ءEC1l 2MCBdP3 F4DhhEt# vY ;&Y~$8>,@feJ.kHpT' +G$4 x9 $#q(ŐaR`]e‘# LX�% Xcj/v!ŢN9Ra%@#i" F.v� *b Ɂ! #k 4@+J* lv@-Ѡ@#B02Yc  lN;Ac58!:;@2NtD6;mBv S5<QvL3^L .�M UR֛ �G SrFSŤ>[qE 3@(`#w&jC 0XeaDR aH@t%\b~}w}pwm,9r"6";k c-I>8%Wyx$HdYf� h4p%OtSwNEpEG5X 5 y`^q4MpXp9* �Ee2.ĪX{Dk GtXيeʊp " Εt&Q8Dp`1&0/$^]o;LMh WA�S˓0@XH`3!&DŽeS_!-&լf$rAF!X"~4c`5дOZiW? @�+/qp b .s>߂LgK  `(  >�9أ  XE-b\=BLڅ JDD;xLq ];=9EeA#=gK6a>6ayL fQ�@A%„Ld#[d=TtdȄLBB3*3ը<_(J'EM,_' 9⪦o6 Q3�);nd [ uok]J׼╯w,Qqd߀!(RCrF[[a<õ *NS0TnzI6v@A ^]9jZlF( 0rE̠`aKrP"Lwz Z>�-i„l|WQ&\zQXQ`!�:VU 3ƪ8,E35<cV&9 ȅ a o+? H:'D:+Xx>c� eOM Qo|('GJ8,51NQ*εl=DEM ^,b08TڅM@ 9-Cn Iun+٥iw/(�%KpAytTJ9*Z_lz~J&4`(EVp/'Yfبrt.Ҙ0 Due ǘuOѨT*kAٚ�Y"E0oA Uew`к Dm* lD#Sq*xk d*F s;#zЈ\} !!@%vm]0�x4E}Qe� P�vt7O??j'Q跿G ud!Q2xEuLH4pzIXqFb m,Pf!#l"x ~T� :݃Pe{gcr,,}r ˇz%�*<9fٳYm$Zr@`L%| "xbJ|WgF[pO 3HOчn�8 NBnn9WU~hJEN~*F뾆o rŠ.m"@`Ȁ܎>  .vt]zl'-!nV^ <D+b> F"jR$|#`BpX&B$n0 p o 0 o~`b /v6lɗz EP CNHLEf) f`<ƨ $ /�6`vl)~gX@rcH (N,*",WrB'®"4@ >,p=^,#DgM2ˆ"vaP iAl#@M<m"$&P'q6'vHϢΨF**tу2 7h2 Q @6 pb" p3D�NObQqe1?$QQQ4,qD�;1� eQz2�pNi'/)CPj6(�|A*,sу@ 8$זMa=0? b [¨R`Lz /k$ //A҆ I/"l^,޸(H'�p,l<vq_r +4 tQaE@L,0a3p0@7 �%q NR,|\:;+A;si�D (;S'l2̗ ~İŒ A"ҭ1,0=Lqt%#!%@&1P// 4a=*2`b*4,j2qplb%31c  -j_H5S35 :Dx86i'ԑ 0�|G@~1( 94%%;: (Nl`:q;gP&97Q=3!<O_mxrK~L,Q:)2E+!zj,RG2!Z2W8 q ksA]BB"'7 m/=Tۚa$`=B"珵W</8(2'3L"Sr6GAd&3/Db‚Ȑ U5\u\̡v aX)lӺ !@7udL& 0(@&93 &er0[fNt;!v"N׏&�,'qR'a|pd5H ԙ3F^dBjAף= H$TrC&_4BM&`Vbp#CxU%B"BYB2Fe*p4EYuJua(�Bn!h9eJKp{"Ĥ`erM+fN '6(bb#v;t`;t;.OO&$!LQΖ*�eT1dO1RAũCi`e .!]m"$rE=Tn4iV8REw}  1wY#p[ cl1Z"C0>:l~1^#MJIzt^7 $ 8/I#EfVa;R2i2s7%9uC;= `nƓ�%ktMnqN@mW B,G;BQ80voW$B/-äp0Rj{;1"SM JYtlHVbE@n Vwedc;DD0DpLMB`QP1g:q# 72A�6:?YÂ;"6,CfO?w:A#c d+$ +lRRhv @X w?Tِ)YHSkJsæWJ6%B0_Hk)lk[O![|97 I/T&c,U] G v^g8{3+:mbLMgMY_�Ay4*GYOq!֕StWtJ @#tl �B#y# mT, "v@$B�y 计{w@8z5" /ZG8E8eFDWUKoXȮJoa iKK-_@-q@VAlPbED@;qEM{bi6a-QN Zx}3, @w1b PY`b]E!qgr#P {=xiޑpj*Jtz+8x훠ˠk*L la(73C- !?_O- CZ�U{37O6JuqvkFS Oڅ[&W8GZڜi@:d[Xʹ[{ՙҷkgTwK_JLGfB@6QŰ{ǐ^1 7ܑג<c�2Wt;\3~^B#}A4�EzGKM}sÀ>qHNwR?KviLWVv Ƞ{g� �+ Ȯ@̅W#$Ww{Zυ_ѵdى[fTJ$$<ٝ,jY YP q%Ny@G=b_a#5]U91=~'h8)<GIwbMC25qees"- No�W险ߞ;q M G9Y-UΜOÛ%X7) ]M{lE2 NxCPbI5*%�v/ZDI'>C}]-~_QqOKOu,Z}b׀e]{2J2֫MC`x=ks^jQ)8{j[$z>EFBF[֜n;%Z[u5?} H|8W)#q/A7=7<}�<(|e2"<f<@BaݼXBbH۠h#и&Ĥ| ~~ x9> LRVZ^bR^Dddxfа I온*R:If@:V>FNjf( :ZRJdt]Bptfp|@X.yXl¨chx줯tCRZ$$pPCg/7+f8�Bfn F<VAJ4X@ r TTrJxG 0FLSF>A hDRF(Ut 6(TYRGc"dU!I W-+3#H bL7/BbO—.6 GKUAQjc-l"x^/E114k."K;rny&H \@r Nm7tԕ<{(x.,#w!͛"� ƴdI>J.!Q8TUexQhMOEPTKmpWqB#B@">gRb-A)@`gFܽÇ^b!,B0KOB哈u @5 C.b$f,TKBа 9$ &i@pEM4$�q 8@H<2@ps`g^O ;Ф@L7@$ /1ЂpA 4R(�M4  !P2 DOJUmԈJh >d[BVU}u!VZxc)O„DцqU=@W2@ߤ 7XjɥMfB bId}r #PQh�D,azi&(hv$ݧ:&T0i��s@#о O  tA$tx0@ rM]u[sB.cRH#J"K@O1Ʉ`NwOJh3Xm!2U Y@ b!cFtkI@QnVYas5 8hDAJ^@ Iˤ '0ju1!)9?h 0)��y: h<h%w);Hp (M%$ǀ}Ps<ʳkzfR-@k Z jmMÀR" pD&Ϯe=( +Z8i qܴR lEr+ +U@>G9ŵ-B�nd�,J�,[e &;%#wYX / 빁 "fQ`2 ~%PXIcD8}cctzFy=�sb�K25Np�>�0 D#�=ؚ,+5&\ l'5ݐga@5lB_AD8�7(J,-9#r,X*�#iMT;@ )X+ AQd0\h)Gt�U XdB>s�!h&�\�dn |R6j֍NL;?sL0ReG Ly'RG'5\`!E<0d@^5G~r%*a΄ZLȓXʳ |+\ys�|s[v7<+-XU:Eހ](11p':<%lhEf;Э5P&hMICODQAtܙpg,:l DzUR3妞d[& <N5K  xDT!4d� U@*JfL6Z4I-$U1Nd1d{X`֛u5C\"n.B(�O4� (ΌD;-�Un0w7e$0Zb]-{ @"P`Qzx='qġ(]ERr_O4wHP) * pT*6cѷ4�Y@8<h�]߮@G2VF%^xVP! $8HVe1s\VS|@- q2> 3�<};gp~V J &'P M^^NyFEG w;!D%tEπr:ohr>tQJɅ4)It7|9ՙ҄K@h%Z#'k%(ʁNO hϧ�0�2,Q>+*`M?F@1=H D+83ig,5[x  =s6�?c.$eKاQ a%/1wA[-+ @ ~3Q ePm9bC`YRQI-tmJ`KxFtpgz K#S1O`ulF?B](J]LW yDZUc�$��$ qA`�MDd:[� fh5-hB A�@}U\^ pAy`Vh^ HKؔЙlā%GlkGGP9"evaB9LxuLCÚȜtx@(͵| *( @@-Y }EU -qMi�şM)U@H `ڡ}@`}�|�ݹS O  d/2P]Ht u$ |][^ mImDԳт\Tb+ A51^^jXK1Z -"LoO@T�YcP٣ԣ?0~(٤_r\DYR9P@"ځ)%%2eXHHU]_�`([)f8,7db/Ȍ <%�TTVef E..^2SP2 Y] ȎFyQ`p 4̥4T@z@'|cxvΩrMMal!ha(BaG@ؔw!w7!ˑ:d&}G?rft@� B!\�ĭ9$ @&ĄFHP@Jd״��$$Nb 4@LDžJxN:SdPAGJ1BHXg /fWTFeRA 8e/H-@S\� [5} %H&eXIcPR|uH�?DI\&NYT AA ϐ\><kVI�j"2((åH�@UEh("2" (@…i'>uNv�vrV` @@EWS.ZUJef-\eR嚪^BE^�|A$(`pQ5 adƕE$X6hVg<aaǭ2xG&ؖ!CeeZ�h?W5Byu@~&+&� $@3ܛF`Bp]D) T@:kjb@#<Cr$# % DZKn( H MN5qv. n4].|@?@ PM�x,'U ~'ZX,\uaH&AMat٤Phf5ʺjALa>XလBY()>&0+5f4.hGq(0|Gr,ܐF@ άZDxRo@T@$h \@�ZԫL\$' wPFQsvIGOx'U,) Y#l!b|}4U'Qm)E)hl`` be&B ΈU (XZm`:9#eQ'��7L@ x4`jffvZh@ o tutR"@J@CrLkn) b>C|ё.% 0@ -@@M>,Rw^HL]` ,d/�Ё9I jlGVB+~Q48-@ F}(hE-1/ V& l})@P4cd\*pY܊T\ Pޢ\pnZd?tW /"H @@^T o^@TonLH < nBh  7$RqfxwDx@vJNZP,Nl-B@(VV@Q�3,R@0-.Y(>u~-kl]-j/]"B�'(. 3Z!K B 8.IGA?XG(W )G*d@|Ȧ,hҥ-;so24\GUJDHCe^Z*R Fa,,q4)5@@π TJw[T�i]�Kwngؑ5 LDBB5RY8BL 5DfmTn-r6\V zI~BIv6"VTt�AntH4JN Og@\Lo e#NQ+U ȲS2i@LY t7}݀",YHWE{5 P ĖH/p\3@|>]nɧ9vAW�q@< =?Y%9_} h×u]4$d4p8@oGBPR 7"I2ҶpRŘ�|C (4b5ʖ$t�3\:ˍ@~Rs.p�wy)1t&H$%3X 픦\x,4oO\dn�c<@5Pڇkx SzJcz^xg+¢�wKCkBt_�k 𾶙Cl#7�n+i6|$4]rkw 4@�R&Tw_�(Sv$((-Xkޔ-sj_:tG dwSXxaI:gx<Z.Ws--W_o j%/&C+T™',<s_&6mFUK\fOWykl(!oIѓ9!yxĚH~オo�Po:n319P:hr!@ /Q̀l LW9#L?]cm:/IҞƓvWR|R-{x9K֖2rQn EB9/@�Osc2PU0"dE}ի9k_fQ{/<?j=(<&7.RU]`?*>^LE[܍y- Ws<5Z ZY�LCbh̘&(x��牥2)n@ "e}1oewLnRsh0D! itر* $l̤�+̤KU5Ce%$((XmM  p8vmv~󸈙PSV{cuӘ0wabRz+}8j'@~,(8rd2PIٛ5qa LFP3'G,!,�`NGL:Ie萆CNSbPINRBG1M&Tǜ*-h03GQ)6sPa}y;Ms_Gg"o8?ٜa qJFiw*U $Ґa 0qA % `@k^+9(0$"j35&62Y]Tb>g{Y]Ɖ۬ Ԫ][\ٗn ``o! #^³)Ș ,3%\"'h("P%h(rE"}ƮMd깦*qCCQ8mK,YK*8Sn ƛ)N;K؀Ϧa m :2ʨa򎼭)A `> :3>/vqWL1:>ī7.64ሊK+B;'C:fLc(c;h3JQƔ)c^ ׹ اDFobځ) ' rY> QL (itfjd+tԱRueM7+2GcttT $R˦ $ :/ Z|N ABG{TpyxB~{Omg9.ݓBy呡+YvgfS,;+׵hQ 2zM*G+J$u MWTĂ>䫭m}q۔Qkmn +XِZ e3p(VNQ&.JrCVre)c.nPMg- Xce4I9Y{\_6<Z[C7skuwö>{ %qmh5OipXIՏ<yW%z}Fҷ_1`�i=�SQЬ][:<Ǩe'Ӫ۞<K1*RC�EX#ЄCM>NP,8׾.h!^fX??8D"(FKQgZMr(a[׵>$N CXD.vы_tB d! M6>"8G:Q XF)­BTWΎDd"Eea_ ]g\d&5IN,FU~( )b'UJVґb#'Cpt]K^җ$E@p1GDf2Ρ5-9MjVӚK"6uMnvӛ 39NrӜyNvӝg<9OzӞg>�!Optimized by Ulead SmartSaver!��;�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/logo_medium1.gif���������������������������������������������������0000644�0001750�0001750�00000037546�15003302667�021513� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89ab�� BH*|JF3!,(xkǎbc ()zh`SKfQKU27 xZs|Y$m^B);4B88'-/R\ADZVW5!|vC'ٌ~Y+;9ո£_dQrYIC>!_iJm^5026;<0GDlfPD6+T@2*)~htUgWM謋TN0ǫĘX{C_t_zڹĝA?IKTgc;`GXdfh\xk;xAp;dY{w˽ϰGpR8BTL>Vrabvmy:4լzןT=&feojrpobK4q`5# s?p՛aC@GpUYbb[hdZ3!���,����b��i1M|yMM010%i%i6i6Ăƅi˥i88'8''>>4ȒǍ/7A7|8(F)8@s>M2bu(S\ɲ˗0c$eҚxɋg'@>0ԃ FXjNӥtXE�غՄ13\QgٳhӪ]˶m4&g{x>WТH*eꔎQ^O+lY3k̹ښ6ɇ/O~]x0ӦN!Dp@#(aŠ1b/HṸȓ'[}:Bjխ'N�m:.q˟Oi62y@.ǁqD᠃`PD EcAyeW#TapW߉("|QS weqXmh6`58!e8ƌqXCuU`'GذXfeLbd!$ihIk@nfL /dA 3c &n)蠄Mԡ=֌k 餒@yiifvWGmHXvIJ#{<i 06�Â&6l `쨕9*f-rB<ՑJ!/,+j:Joe,pZRdھQ ,d)`ô�S 8u$ 4,{u�d3�I zK2O#;tLSx-p`W[cT*ٻ#Npd7-ʤ@ -MWc?h�a 9 s}6HsjܘSw琷UdM?5rbclCyP'd 7<C_BƝƼݟ'Uc7;YUس ?RܠWlM& Efdw 1Hɇe�A?ʼn˘EllO�?:̦NXOpa`35;ug*ٗQ/1h<9jj8ϔu˪ `z+yt a5(a=a*7$o(Nuy.̨2p0$ gW %1ɨ8z$LG LBa~_7Nk^3? [F�"u @reSӗ5 Wk�7 D'BO%� WAFEKh g@D|XPxf22 �^(1rfhȂ=:)BCQEUHsF͎`!}^ aNձVg}x<r�*da@�1BwVb2rcP#u'jlcϪF/6*ЀcHgƖv/pUYgy8EHZ#h/8SiMtZ+wӞ<�tiA Dn3eZ g 4)!80n "ݘPN:gCP͐ >�0.] *Sn02}?�r>T0#`dЇ`}QZO �hX-"TӚ�E0@�s@ *3D�q~ 250 \%7@RaVEgIO]cN+ etT–§i@D(&3y!mhj:eόCQ|idV`:802'66�% *EFpva8XE@Np0P`0f+� ){8C>+̛h (pv/�qe9@í jRġ [" MkF2e*>jZXh� ~‪8LQ͓"3P#(04'hU5=QIhkH;XY d w.h`:]bϘLvnP[:<1cg?c$s�L^~Pٖ̅G\?m$9TPTs8[EcM!p9`{^谠/(ڽs꣓Z <)` UCg!T}&r \My2ӑ78 RV`?zu ʃ~@]< ~^Mc_WNɢA:⃺WlLƎ?n69(n@'1"ɲ=h0UA =p`""`}e`ЇLg*`Ke8kX&X^M@ <_:hQTh<uD*ʙQy~ HB \Yy:tā�|Q0l1G� Uwu7l0*c7v`_ L_`bWpvWi3xR}cG}w`�,0P2$�4n?0`PIX,@\tV\5ÄXEUP([WExvzOp /0 b�}]{g0qJKDp8t 6O0cX7diP'�0L'KAR)�v(sIm'\$+C[Rt'hjK1|ñ:p2v5@K/} q =e=1#'V#pyq6"aWuES0 b5aX 60rp̖$Y}PwDNd03n~dw=gV:UT(CR!�r� �,PgU\Q]z\X\q,\8zV o 3|D{|L\{EtU3`)R~,RuLt0%P|W~Li~EqoskNpv(c�-xxxDy� Љ5I0'PWӐng=ec0@YRt;�6Y(9O$z�<^,o6�d3`Z{5X\�wzi\!)?@w9lƐgy7  z#�H +i}8%׆% )7w0h`U4Vu:I) ,hU8e�7}b~Z D H90tt~([yx{ebٔq8R0yWkhb]4"OFsHᗤ3xgQZ HP�7.Bv0�((�7fG:�&0SPw)=C~Oi 615Ĺ4$Ixǐٓ$= �;?zɜR30d>DQA&u�JL^g道c0=Ri0V˔;DE)pA) UTU_' .D|jtP^8ROP{"} /B>ЖPk>+D*g2z%<4PH!(kbPz0_(�z\X{=E|wSn6�~b�xej?�r#+GEf9p,dS~@C=Y4 ~@G׏{E۱?b.Kc1M�ܐi@jǟr)ـ%}EQ*c �zp@G1Vchj^y<>]pcĠ(n(j50vNG�(`k:b�Sl+xx 䈙99Jx<PokP9D~ntm0Ez w)Ewe͛'+;�3{D#yq5�m OmH4KqHKaR1Y0iW[!)%`3SʬmYrM~`ҷ�j]  Q/fu;@ C >p<1j33p[T`{ Kq1}Tʺ�\rJG%@ !f7,89,yKZ9a: v?;E�pv<i9jz�=t3JDK9#pCy5ʝ<է'EE %![a�10_#0|/ qSc! 4*MG'*Hf<pD xK ϶8|_�*3yuW`* G<{pG I@ۺOj|BR�Y\[g� e|z˲fΫK7@ثXEƫ7;#P{̼z+~ njwR01] 7>=>wu8C]8�h_ ;! #0m)xA]LW9#`(Qyqb[8j`�; bf53h5{* x'`B%w%c( DAC�TD^�R ?ɘ ك<Iy0΋ssla~*-ESu֋fZK$+/;Qj_1`r�MPU!*Iˤ`�LKE# <:JUY_YT_�)Aw%lo <L� FP8p7Q4bƁ?l6@�2j�٣*p�2�= �Tx� -1@ЙTh" 8nC=uOlPc�[yC, g0Dj?!݁QV]ZKw8q0|58ÌiiB>$I.i �֪"qJV1tMO4G�`pP*-l ցx| ,n(,)�0uoDp(. `�+. 2^fd,:.=.z-CK?C-7 ,3:?P) jT$z0*]"youO3P+1ހfZ|0_&+=RL Dk"T P袶‹TJS>P&tNV(5SPft{3HO`RK<G� ~8=uO�2�! 02 �+~�͞z@TJ{d؞]4 PydP߾=BI s܎~@s_P7nܡBE�@�P>u,UDەxuhe1 H O!/ #\\A_!R`Bi52 �JS _D.d塒rkM3& ih)OoGYpEK3u'W`> )uu)/yy}'%,G22[Abb- II^^zz"6%11  ))"u6UP"�)66~Pssݿ)0s ?Us,~s�|~ɩM3 AhjBІ d NhxÉ78bƄY!f͒"|瓟o䴹p@t+.I�/<0qWu%5ASS4AC/x 'ADŽ +&:< DIFrŌ.5�JL7"KUn\f4ld v+q7$a,e/ VBcnjH EprT6ǚUj̻a={lHϯ9U C@pЃs/PF~%iAH_TRPÉ^XWm5\)Xmp GYV<4E 3HՐUh5xP4b�HgId0[]I �2՗�|e<�u@b&,C)|�z HI/8j`8h^o"@nԡT p  \001\LvʴcHc gl/W}U4wB5݇> #(0 5XNjtF5P!6 SIYH4o lpU<P QQ$ Z1x*Yy@1$%hdT1ըvz梋%01Grҹ"K#Mj Q|~̮ D5]!�nԆCa5 %"0( q=xA$!ٯU+vigDd=# 0B U0nl -[{1T"WEU� n9,,Df<3Qz"9ğ-Isy)C%!t,|�'\tt CbCo Նʍh '<q_lps� Ky0� 2@t&Z Fc_ m`G=C,sa�F`=p F5nAXh  pN~@t) �]栐\hqtze yhІtC@Pp^mb8(xN DUa`JtY^ iL" >hV"8Jct:%:Ky>dl R}@< 0a3%DPi- Df>$QkV\ >߄8'|qGS� TȀA@$(a < Hlo1($0!H*/gsk\ahp KbEQ9`AXȌ-Ay4怅FKuH%-7>C�G" U88 ]!@ T:Ad@{f1L)j|܀&Tzǝ@gluLq0紿`\x3L;m�Mlاeg<(jJSl#κ�>|P/|~MjU6'ѼE, � fhN P@nsˀ<|P{a'5xЂGE'�λ<=Dw*`ۇ̋�J@:y8Ĥ4�\(*B@GD'gʓ$ղ}p0( pI>!bmIN. C4lMS #3dn^:o :sD 5V>d#`̤ B67 BȎ\ɇ`MQ.sˉ8AldL_h ~%7+@r bo+6F�+ Uu{Q,I%mr {7^˥ro{O^PӴ')X  {݁ӏ 50!2DE&� ͖ `v!ڔ kȒ44I;Dl :ȁ(mp$dn_ޠ 3Av&6B=A1հA |`ϼ/і6ʷ|IJqY'b̉Wi4! JZB|F@s@iheQ]E'PЮx Q?"89� n Xr> ;@]~^1&pѓ*UiCN `ሹ9{fIÒ�+*2m) 4[5po /D 3̾Q0X L}zY^?yD-)h"2YAU[ ͇p eiXx1g@@h{ P:$}X!u ;[ 7~U u\a$ awxXNI55T80Ȧ>9Jv>y PcWUFai1 �n Tvwnm%)74P0G@(�GB(\'�pd&VWO#`<r#u0cgrcC'rV~9@s5hFAQ`=~f]!i^!-Cg'ŀ�#�Fu 8uQ0.-R4UUHv1q8/~)8/�xn.�UFe�" a.hB� zJHm6ocO LPg8]'FnypJ #؄S `pvVdET iE$swry(� 0 (FPV: @~p^4g6^Ei艠?D:2s h &Њ_HU}P(1!m~J))x1x&V8HxG&~tnH3tronzWmx p,Sa{鏟 �|*ԙV qt\RU_ mT>PS0hFd! +ɈU�9 s�։/,x8?iLkɜiFa5u*V29e8S<� T"U45+3=#Ua�&Fx<pls{ƌv@xnR0VT="HȃX6 '&)<K:g!Q`F}@Q�pi} q(5wLU5 I 5 iGXnD9Hj/^UМ̹9Yʥ80UJVS>%Scp9)Pc+0+P!Rf8\/Yb1W11>Q'%bHr1cҍmlN3rcTj�X0d}(eMp5N'(ΦCb�)Б% A!�6*E!PPZuɜr`_ޠyLP _ uDEpcQdSbqpƧlj:=Cw=b�1q0x2R@nN <man3 ( @*o@X(J/0И 7˴n0Jf.`@aT3g ~S@ +  �'<c�u0\[JX�XP]V�S*k`U Pi%G1%Tp$y0=#j&3Z]12UX<tzEYتJ?{ hdy0)"S<#3(mi&;*%7d:h#+)�h>i P]StKVPj2r�L�ŝH'J|NTq#E *NN@C@8{ v1*F%ˋnBJm9�*&"w#z0`8돈ó1? '#!FK}4MT;Ij6o֍}P*!@Dt!@cǾKI;)^�{tL̹j|[ Sup`X:@j?�E y!l$G|gnO#f5>XjfJNqBbl{E̻Iʠ�}⳴e<@{3Tfe`U< [vPfru@v0�<�v@s1M|Vw VƇ�;-ɜLڀ"EMRDiF^M.�x V Іʹ4&�QW1`>9 J!2~ZI1F_d5uC9ohءfJ&G dq0f\뀙|bm%%IJ(9u@�T (ϡV2R&!9wSw uE8'7 tEщV[[=sѦL@w<$=[/`fnI{vᇈHq3$#H$YM p`ܻ˗ 1#*`53[3˪kM$p9eg}/f{p*.7@gQ0Pϲ rDͭ497]A)^ZJE`~P0ěX]ѥ S5p�s925usx,psH\ҶY L4cGr12&2,f-Z;R/@n#plmz͡Q-DW=Y-{?oU,43)Z6\xU<}kP4l8[CY%=hwv~p"�=w ! zLP;,�4^^jctKS N8%FzX"hsPJ%/_앧)>K#`m4<U,Mk)dxgn oIJ �MA@PRzLqͼm|N[$獢e}n)pT`D�^*b%DΤ@\wc7GP+~i ~Q`iCJi_0þ 1MXڲ V4n&Ho+ lPF~* {v#ubuڗ2=͘苒)H7I& Rބގa#0UX7YhN|^Ce8 _H(@D�DP7n7w }d`w5 qlrD"PcEQ_N�]];a�SO�0Hs�u)5XVVVsiu8'5mm) ut#+��)#<m)<.m)7&m4ȧ� #ttu0&)/fvX0|hF@~ &�͕WcCѠ"=bRT #QDL7x)`966 ##F01BA7lġW+נA~+ )D(RI[E~J5Ŝ0pن6+yaBׯUf2p]�kɊ# �,LAA� 9AtK8q"@$D>tdI;Xƌ:#x$8-[3Ҩx!Bh:q!v2tCmxΓ PewONSgLC8ȷJrEpB ɷ 1%EM+!CЄӈ^<PGT%lI! 46!PM`!<r 5�?Xd5\^ ^E8 (]` @^*WX%ّ 8RMrC0'%"a,t8v |z">O#l4<uژ0&uvwp $NzPC>^S}<qy 4`8ԫ#d 0F>4(?yG&a!ZbM9M N AXB0SLPTŎXa/1 =XHn5YēbX_qa8| (%^ɥ-mXd37'dQZ OcQjjhp#h@@qmȃ9AF<t-6tz9 D)d`[;Ѕ =(EGD@2s<du�ЗE<}m u`�L nPKHb PIU ER4N#CCUe[b ,^~�٥'W_}j56˒iY#7yBS k6t8JG=tdHӂ#5MC*8R Ny1�7xjQD)W1�¡vVyvu}\r(h: B0yp@t(AHbs}@CSBHJ`�0 8@TG")XT5wŽ#C h DI RхM'BfO{~^<\0b/c< Um@g$GMx!OlR9P<Xbj舡Cs[T6Xπ@&demAn J%֫D`!*H/QPO B(Y 6)((8Ҹ$.OW2�_\`<X H�l wÌFj`eVp!�؈ � 8X1m R<Xz+ɆPdRd (iLT0R[9<EUih#|pQRb4hPp; � -G�?~Uv:c F ȡ 5؀/H�W80U+<4p54 B|.3xY :�O`A90� 'VpΟN|>a[0�CCL�-$I(PyKTAD�^+iVTʣ*|tY2%g8*Y9taX*}YI\ 1^Z*X`@X0:B*x%*q3RX|Lal̈́t![8w]bQ s+` pjC #0֊kDs'KpִAZ4Zε}F<ƅ-U%P=qp ^Bq\Wo`-<@U‹…_AXK!jRST2aAzLT%ip,f0�IwO�ri~\Qd5å<L@.re#<o�. ^@Ю t9vL`MaAš@ G6&Hi^=|B0PToix],+P1~ٴ`B=|H3F "~'[:h8CV4|AФ t@VCЮn8�4 ⵁFF* e)rݎHC4+BE% 0"1]Q fL'0F D^`ͪBȻ?d4DK'eEdl` ھEb^P a}uyOLqswMj{h O�B~7YndAnn]} ]=8<Q|3B}x&Ci,Jr:\WIs>wOC.,Ama@AޏL !(u# pP�,{*CSI wt$ wQ3adxZ 3pm�b�`�RBaYHG z pn@nO�d<3g{fCbfdCqK&@xFY�+Tdp {p^C k@}Wwnv,f8s}~W~х& @@*`=XvpW3y6 �f~�~5�0paGkp){wpHnwal!h@45'4Qbw(2@ЌW)J9yp,<@g `zfY y7Rqõd3 ?t 5,Kxo05Ge&|`g`9nHupXSjM Xgh0D Pl0cB[jxЊGW`&P=5*5C`aXplu0f =cJBHm|ЌI5Hr]ӃNI v h@aYr}ph>s5+P�w(RcXaVB'PZ] raCm8a/  I.F7p7)� )%xt�b?6n5Q3=cPudXOmǓfxQ)0V((h30pjn 25`aAbbヾ(NgysuK@tH)Kx +<w>OIbA#lO" Z); 4y0m7V0GL:IhD[<P_>!CTOYĜg|` %� �4̢89QHqa'RR q|9ۡ )lDM�O- _6w=TH9Epvb5: >4d ivM@5(]6>3'>PJ ��69R)4t5h`z+45iY�5SoA 28`33p bz<s hiju7e`):!*979b`'zx"ᑴE[(3 Df@JP YP7*T VLqJ7m:tQVl.O4�4 =D GYoY yɠR~0<Mƭ)X9Zx# ő!ʁ xM +s| za]&YGƭ]4@vzal< Z44YFyj‘y9ٯS{6 ,^ TJ)i8bk;Ttlײdդ;4v5P u@ Ɗ0 ­ԸIFۧ?˅`F[P۾;aT 4H5 bGa�B0!H:F[4`-?i'K =�@KoyM@=kX4+(2<rl>V'rN9sa{D ;DD! :,FR@ܳD5w4 wyG;jƺɩ93g3]+ #zy+"\05҈\~>O(ܸƒp;ɖ|̡U[lOVڲuŕj, < 6HyPDj`\{ |w5s˘P;̓w-܏k5lp ,00 wȂ <&1,< * , :"Aj3wˬg|=M쾅Zڼ͊{Ǘ[ r?h́Z57[l<A) R{a02M\h{J):͙3=D]F ܾ,<mLowQ{XZ=7-5f}hֳԨ{üTr=t]O[ֽҊm|~-Ý ؆}؈!q#؎ؐmf]ٖ}ٜ٘ٚٞ٠ڢ=ڤ]ڦ}ڨڪi�!Optimized by Ulead SmartSaver!��;����������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/logo_small1.gif����������������������������������������������������0000644�0001750�0001750�00000026524�15003302667�021335� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a^E�� GClV6({o($ʼnqEJNbluT'DGQf58 lbN8!6'lTGZIA\H(&68?²;6S؈s())p|ZdZӶ' UY]AK[Ir_XA843))q]|~rm{O:'NHLtR>R:[G20)}`JtvA9帬ɻ͜l]4{}=8*0,TcW/-[YeY@)us~XP0|`DH0B@N*"m:6.ؽMIZv˰[W4OJ.qk?0+)(]]=c]h¦!!u?93"nai*88|l;6A<|{500dZ3* XQ^DJp/J/dyAhȠjN5dpQ{Bb>FکbeBfc~3GF|R[eh {kd֚fGN,9"E1K?B8%~zFshڽBVVd7@(dOCYcCN@/7OM\Pzq|d[x[~vRRU6βW>"eWQvsAONpdqd7+0apT;ͦddm0nNB@6LPQ-@=|sB?2)ʔbʐRM PO]|nw:/QX[zb77'D@zf6`ʹ"B"ޤC?)sdI2|tjF:F*sZJ^jNnfqز))eO7b!���,����^E���H p)ȰÇ#JHŋ3jȱǏ / (dIc̉IC !`zgϙ3(i&͘14M:`@0%- A0IիXjʵׯ`ÊKٳe Rm.+nzAe.`A_[V1ښq1N du@K˘3k9k & WnU^sAHlVb<bK.hLLȓsN\\+*z9=w]# x _.<tXΝ)~˟OT), a0 B0O.$*J sĒ=УO�Xl(,È;QT3}(1g y Q`ʍ8@;B?P0Ay! Q4|,F)TIsȵ;4h7f`Ƙ4A 7F#B XP)4)\|g.Dt^D0"@PPHe1Kǟ|W]^ {窸Z kYSM]uȈ5~ 6K̐s XlW*<HۦU304.8¼}$)ZgsY $& 4x'" .b/h|my`Y@;6Jp=, 1rm C-HVl>֨LXP+XcŰ8H6K< _lr*ь6g*?J')h߀.̀E IrA*]Ye8ך6W"qeCᔛ%Ue1 3;nWb1-F]�HZ"/<xF BWĀA0`_#)^Ц`C,kE,CqZ ;\WiB]s-UUH7a0 >j@'Bq d5ta˃R 5mn͋4T]*s2 y F}JH"1OЃP��q@0yhzϋC7>p%z`D,g@ (ț|^u~pC",xt^rp/bt�>TX øb)PFF!`׾`/`1n (1dAK "pG4ِavY mmMۦ=Mhy@@F1X8<0CSL{G4DA %?p)Ӟk;Ua({^z oA0hF"X&".a iH@Rt?+ b sX0}\{ J40UNv :*u PJ+0S|<+h` c�+jujTVZ+U:Yd &iM�i Y\ifqsXv!:^ɩ{z؂,�Pa1]P@"dE   % " GP`qJ,: TT1Iyc H #|GQр 8p#j Щ(fS*U/: TMBQPaҶa kPC lAwĭIElŽ֐6 FhCBQ�@A,rA *܂ rlq\BvKXpT Bq` \ \u~XxGtQ1/4#d\ 1أ‚ s2�|`5KSaM!ef � cx ݃v^xC4^ `E *�K$ea1%Atm 47 }D޼J6>1+^ g % ; ʍ"4c9.W=[i:pEBU:�^ H;X+7ƭ{(ߝ2q`!,a; A8n0ڄw yZ@��:),{ח֧++z˒jX*_t刓B0Q5ar.4&S{ zQ�F=I,Mq a6]P`/Z80Q+p3H A!_ԇ-G�0)p}F_tH`?ʰiHʹ2 R� E\\]iP|tfRAZta%*ZvaW:K01Pc o+ {P TUog+{t @qlP 1lOP-@ 4L@GX1<e A a+ p=tYy7B�eP^ MBT1X@}Uk tp@[G@P XtH@ ސPW$W!{X0?/B} `4e5D 5E  4`4' @YB7:e�p��P {ta� kKt{@a[$8qVa'v6yqVc vh*0xl{^�8F~w*%/Ѷ VW;P y@8wJŇ4F#0 J"ޣy8X�F#o3 @\�[ӧRoR( i}H P0H7u"3P]P ~-& ȉ0X  f< �&f.*AI-X%(+ I@b fnI|��$؁{HUpV2e9;*rmu1 yG11)yJTЏ@b ^A EJ$Mri\Qp=%!%YpmG@AR [I[Q+% H`VPS`HVsYa Г=9: 0 PxM@DYDw& jMP yI K0btğa)gIЖm j�$f@q$h_r_+Pw.20z ~! 8c0Y"@EB)�Ez7b�=` 5oHf +ȩPE У*uC3(P`^jl h^"`2ɉp�6@p Ip O0@0 I0X I`0)@2Bp6ЩP`II v@niZZ9x%A򫭁ّ&* >t=RЙ? =@;(7T:T!t;[5NE4p HZ8�Q�k1c<'Rb@ PZ+Y/Zk�^ O`Xq`CUrH`X6p s p $ zJ" @@Z&+T0 d Z,``mB+@ ɀk0g n�fIxBP5*`#[&A[`#a(^ଓq ^tx(C%,ْ3&:�_Q X"5zDK ) zF_Pj$<Vbk5.p <ٓ'`�̀pp‹ GCg) LU.0P j ӫk;2:"˽uK0cP ʩΨI*ЖĘ   ҘC9yI P#Qp$p0z ֚(Wƺ=WY'X0Xk`iDޠT+`<`cGh<r~aG~ /,88,Kpp  s p�p  @ Pƛ@ʫ PKOd; Kwd *{K[&xcP 0y K#kz׾I0U0p8@82 =bL�o#l|#GAPXE!D :#ӶSDxXCuyRED#8�P;DG>|P~�"Ri; Ö˒6ghPmӶ2h.Q|0�ۓ 'pǀmph,XGĔ DžL{LIB%S0ȃ\*K0dPP堍Z+u�'Ғ^'ܐ,,(c*ԝW ;?%TT*)kk~X!˫� ,:%q�m(@%U[aP ހIpޡ�EqPK<ֹ: Pc'  04wS�.J<v=p 7� =YSD6 m �]C7T�pr "@ {d PuPdPtU}oRM{0, Tg6]& ԞIa"`M]$(ZxYA"��p�RPOw`NCR]Dp=EÐ'k`D3CXC mXoͦ D$_-1) qRJĴ9|El}e}4a:L N5``v�p 0�2ͺ3`@�0IPplP}:M fcPP,sѐpJ^Y Y],==*Mۛɓpj#eFTFO!([hOp<1ŽsY 3ЈtOKՠ�kDH P\<XPH1E#)Kݗ]e]F@] h.'))  �I!NDM0p^t�? Ԁ^ {�KP^Yp oY@ ȥ`8 ;6�ϐ%qq תbЦmPJ4!ikQH0Ew?ߐQb h(pO\ bI}@,Nn%0/SRWH  H*mz+.7/ O [XmdkБऺC7�0v^_d@ @%K԰Y {0+0o0aunUuYg5uV}wWnD$5D8= 4p��EGČDcaڧGϪ`h0/-CLXL`��q"|٢ZL3Ձ ĄA9lҰrC8@NWጅWt9"&(_kEeYhcP#CP A<ذÜi)"|BtPE5Ȑ[272ٳ&Yވ)H1SyEѤ�yhSLƒyyP.+5젰c ecBٛLȎ�l8(7cBT#$};N, Xi:2@(Ü/q:BBa/j.r8*Bh= 5|O@H!F~9V+{D= ! :&�LA1PȪm%L7Y%JXdjs Gl󛻖XGxÑ~mv-v,G~+ZRԽ(!!9% k Ⱦ|;F4bhI(/ J< x�H4,:f^M1!#ۚvX4M]"#x:X0M Le}�9+!S%>KF3U:5vqvؘ2:D$\KN?s=#ra4`I'5 =Viq݆uT4X#,#" rف@?0%Vw! œ(De3C 9ʏZI%Ҡ5R*z>Tr'ӷ~gxzi=ϸ7 mA1LTc(3Pj,bk-=@x #, Š>RDNb /,ڂhbV=`mWwL1Eq̀bHV7@nAW o~;,�84F@('~ Quˁѕu9�fs:L%ʰ0(*1&2Ibmj Q;D[A2lw;DA@Ll o'L7oЀfЁ$deI ?+bk@X-IiH;� 48_  D`$h0�+'0Ǩ 2`"]HI[5o-\{tpaG0HIB{@L@@WG�V_h 'Luo 7.kXyd OExKy 0zcgf4, px�!4'{aRd 53xFe �DO٢bDH`}x<R]�jH,F0Eq`-\p*Q<Sz,H@) "b@�>~4iPq39,B)Tj DD`?SO(! YA-�s Yg-q u*?Q6 ݅7L/'D\QԢXB !Bі NAR(#v̧"H0S-d_>,@u㮐7 & WPQZ#WxAպ j ^ !HD#"mh5R0Ն<svx�z]A9|]f'juCx7)#fe" ;F);-: 40X`zc.wlϡOU~B(P?=:"bTd\觇CfN2]]7'?PA?+@; 늕`# S]az`Ad"/<SD6ڐ6�^ n4 EAk@a2 {aDLcp$þ]kƔ b vˡC7`HmOq !mup{=#OE �@.賌` lTD AOAEF}`@AyOGJ�+|sQp ܎p�Af#S0& O .c8FXū (<W�+.M@`\IUGGy ])jqj BN$w$YmN,˜N,*Y*-9":tpnEf++0%ג#ށhsl� nJ|]1Hk�&Hg`5\{`mt~?CO!!*Bb'c w4lFY=xR6nY�7$;@/A: MQ1<#p]Ѓ}<>��阁Ly �j 3Q3 ې+( �8ݛ.�D#aڐKhዄO8�0�fHk%t)gsH3 i^@Gr5.iG:RHA,)6B?ؓdAQ�(1²"K.8@ 8<RJ dA d fa[! ~�}(CXA=@C`b5Ї  +3!"Z3ȁ}@,P�W <m/ ,>  f/ 8V9P+ Ai'K`sH4t4CP sJ,Q1qHGLQ$�-A3$'Y61ОBi0:�w� lWS@g4 #E:LAoE=5ȼ0H:PFR"�Aj́h.(+rt.o#. ^BP8�r8�#lh9>+DHUmh[$s,qĈ,tD@@%c%`=B?M�#;N@rx(GIXd2Lѐp)E ytHTIE98g_B!(t,Z f0 b#b�/AhN8=3yȁ?n)4s(7~%+]؅\ !,P>bo $y8ch4|,�O1ƹXq4<4\hU=Y@1ՄT`ڔ?M/ee2$p)؜r2088@N HFX 5n#ZPIT`Ʌ̈@J>A}Ϩl=0+=}�d!PStAV ؇jXw(_U%,8QF>]8+>o++(L0BX%4"Ǥ4`xgІ[hNk%i6(?a@4Tz(/$?Da1G4} >؀,6)18 q0dx T!6s9܅q ��g`P31l-O@�(*Vo@�@F:pՎdIТih\\0"PEaK.(L{0 *kP9:(b0%0X ' c(І("52h�rL5f uh�M5xSp@/ "rv'=xxA/KӈTM6[nS氻Z-݁Jr 8"y(J/{ \ܭbQE <XX TCh d USջ �x (`�TT[�ohO{@%n`$04S=!H-K9H8h�n h$r;)q,@܍X`I�+=z(in<أ+bH-/e٠x}�Ɔ@Ѓ/\VV9U05 @ 1pYyӳ5]+(8m&X w[+@$s4#^�&6`؃svX4@=b<8+s2&NޅuBpG0bՐ,b؁j2h8MPz{Ꮰ P|7{iT:ڂ\0N!d=%䥇*X.m[#�.B– /k�DLJ>rE˧qH/8UafBoqb86+02#ShO.b:q>r(:p ihx3J"i6~EP2@'@@UB]njpjSPcfV،<(lJ%Nb.byI\vQ(Z2XFheMeN%)hf`;Q<oр-hT%$g#I$qn簉 �Dh/x]g�>'0op5n>hKa8zl"rS/Vvqظh<8Knm'Ѳ=jVIjoIUj0)`8x!o`+P]kTCX MCf5 EB]Z =mcyfkje^,pQi-;p-][8@2>`9E 62> ؈n9_Ian羷uPHt3nI.tVIco�~9D_ 9L/wp--uQO/'vbXK1 Dir<=Y8$%rNoHZ戸P9YVXvi%rzBfhuk]h&_kp2/ #ސcg6JY^NTfu'Xl�b2xvet er e6r2Xj(֎gg/y6 Jloy1wci2pVr-My/z7u&y #,#'r# bA9x-?zzXy-wVGZt&w{{?1gyF.ټ/y/|?kwG|ɟ|ʯ|-t#|||8`,}?}'r/wJ}}؏}ٟ}گ}ۿ}}}}}ڟ��!Optimized by Ulead SmartSaver!��;����������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/bg.jpg�������������������������������������������������������������0000644�0001750�0001750�00000011127�15003302667�017520� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������JFIF��`�`���C�    $.' ",#(7),01444'9=82<.342�C  2!!22222222222222222222222222222222222222222222222222���"�������������������������������� ���5R`i=u7#n"+(D4sç4! s[Z̊JrkXYFfF27@gfv-˼tC[?x %4 ȩ$hAhL̾~RҪlB=$a@U^zE&yi044Qw18e:DFYEAaYmxT" --233[(+=rځ2 ^ �!�����������!1A" 2B��ʮ�1 <&`VK$J`Zr/j0[0$qn*m!)PZ[LEu9fC>M)%EPƅU?^TnQßdFW>4-f Q`IM׺SMM èUB!lzqn#^._QHٕm: OZMZ*EQ~^J_m6+T\0+̴j)CE#9OSmug k-fw7e8وev$,eG1Xk%UIV)*gՠɔ<B[όWҥLc:widź\ 0sӟ_NS,dxQM28%l����������������`�?I����������������`�?I�&��������� !1aQq0A2��?.ئs (.HivFR0hh&Ix'"ZeȐbt*Z9 #bK+ɒhMl) I|RIf^?fK3QQsػ,_(h/J`XZSyzdC9>Y=ݖ$E&ْi$MSm4mBJ+ڥDIQ=SD*NS */p$IFiraC�%�������!1AQaq��?!HkU%Wa?4Gs5BS�cy^.QFqݮ$=�sf3x*#SS.Xt<| Lb`ȵ;Opz +o,t1hbjSGXsRL1ˀ?vؕI N/`XnVW#KcǹygNF߳�2 F۹SS;F1S/PLvsLQXq=ຈuT=Dj\J. -dK_re$BnL\kf%]Ea|g+ ޣ&4KFs VK ]ES5,CȕlSȗ:<|%0#P[5[]=, x:5uF"#|\Cq ]^&y'11NW0p~\`b]C*ܻ+6h]kLqG.`hu22۞2`q[JJ 7p GERp8R6Br,3< =DǨm~e:#Y(mZe!\;1]^b,p.Qh=ia^V^{<!ԱїQ?p}D0&UPeZHeҮ"䭍GexfUBY F#2!S3TTa`XŰ=5s s<j9W(ӓYoLUt?{"�*9S|ƼR~fv<x#r X4N=< :Gz$B]h/4~98 !&J1+ԲeYr걦d+2~`kĬ (f4 "ƁjU -Ľ?d (ۘftlm%T9ͽ |i~!^UP]?a<g�k7w S&hERx<QY�g?‘!axv._Եf'}ESG̪~`d``Թ_g,Jn*Ln }hk7 `Kc }&Q>kj� �����# =UX8C4li{`Eۋ+ Ǒ4S<+˙C 0)lT!����������������`�?I������������� !1A0�?\UhBhʢ 0hL}_b{}q+Q>M1ɿO�%��������!1AQaq��?) j_16\N:0^߸]Z:0Tٚ5pFt x? 3� AY, Q(�ٰJK/kiE0!̹hn^״pj W 72?j/gHrxI(apPsG#+�r<1{4ޤF"*6V�/6}t' syٙ=ݬY(.@3eSGͣ# ! =Xo`Ah߼ z<$Qw]qAM٬zw["  3TAE#qՈGo4ǂ) Zqqv ݉tyZ5¾?8ۜnU=;K cUjTY '=E%X=UR}fV>z l?[ o̓7*'((ie#Q6L)?ΩpG!pEV1CXppf GJbΙ/6њ9[M$߹6N k0TsE-yFq,�`P ֩bC6#v�b+~Q.i<TΩĶV+Ft3 0׎".XVdxݛ,F=!F ;F@4a Kk+xK`e:�++,{e )k+�@&E(Q" CXiP+1w?0_ˌ"m*kl:)vt6QN,O,J.,nʡ^ij/.m 06Q5uhD`*89spYr)p&\/gLJ}jXH0H]!V:-Qbq�Ns .0J1Ҁ3_6R/lӈao~@F\WtO+ ܡtY}}c Ҁ½S%nz5gb�TFxV:vwO [f,֥#B/Q.6cAZ=Q.[ 4`!^\BUN`j*|~ n^m5[xVOw k^-zdLLS mZ(DxK9E߈|CI|WXqu�}"YYH*-P ٕ:{A ^b1#mLq4!w_V^ۏvW&ũpZEnʷQ=�2V}ePCb.|{T"{LBjyoCR—qR fͿYUܱ-*w"p9` !j4jK"SK}.�ŹFzGrU@_C z mP)շ.Ecܮ Z}�bv#$X^WB&N �0.^}f9b9X p!0ܱn" ,"䈰eusH9[)պ@�e)5+h5Bх밄J*yYP mq A4ȴ0JIi`U{BT(..*r3(T@�!Ph樭\#4xf/%s&+I>`a-_Q`d�,L#n*QX9l8ew{.CW9%= MVA/l*J5Z)h㧬 7p5]aPn2!Et;F[)xKTGKyblZBkCATIWV<{}=˥EH4IQϒBf@'<E.%-L8@2Jf̨r`=O˱�I>{ <AVK.dGz'}6J8/{"xL4<f)!k(R7Hr_Be#,QM߹ W`W=g_>bUJ ̪i~z\e5_nW|Abi[CN}}ߴgFB*߈Z.$fʪb7QxS3b<A 4 r6 rc`K �.+v 4}'�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/Button2.gif��������������������������������������������������������0000644�0001750�0001750�00000002354�15003302667�020454� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a(�&��"FJNΒ F&g4/Ֆj˚}𾪯ils$¬nuj&6fDRPRV@~iTI~~n*%^ &&@8ҝ~ѸJA?]uy.264!̑x,+* :4l^V꺞``bĆ(~fVZG=ohRv&@,ΦƪG3ݽn<ìz> –U694#^HFxR6Awgumg622vD9.'"2Tr_ޮ]QԇG6.ү_QHсEHNndƬw_Qƺ;>G7*)!n!���,����(�&��!BMDWiH9d>HdAHA4 >999B9 /gHWE/;><.xn*]<ۘfi5--[:*<z/6[Z!�|&ԘAT?/6VpZ4[Ӡ7,"F:�̷4Y9r$` 3ɜ �bJaI%pb#F F'�0h)fPqrjc'|Iud҆Uŭsg1*by˔ b8s@'s$a 2t Z$jnH3#eV4FlGI@0pp8I{+ <AC:�)}\u[rzF 81 , , nqL` h@@FDŽ- HOCOHhasģ:, JTP= Db + رNcV<�(F�LNE k WabF =9pcjR<DDd!9@09nй? ~^Fj d .t z( m aI҉Az<VLɭ>+l :5RDb\IJ'6!j~-,yXŰ2k/WLm{@� R/Sl& 1Ym;\ASP#�4L!��!Optimized by Ulead SmartSaver!��;������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/Button1.gif��������������������������������������������������������0000644�0001750�0001750�00000002337�15003302667�020454� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a(�&��"fXʎ>(w~s &驭ʪvpdF@.)԰wcUCCCJκ*.o⾾lcU+ZSG320k䐐̼wkxgU|<T~NV70} XJ<ܼͨqqz˂Z+*0RDff?16FA8.*)ת{e[[fƶ$KLO̦\Tw"8!VK f^*@:>* ߶ܼĤLD>񯟑>Jg\Qggo2Z^zsƼvR{ux>2&::J*B"ra31!���,����(�&��DLz2ja*p4b**K2K*J92D=J#ZJG2Z�Z:H1AySS Rn@l<w<<kf28&0a"N X#,nCpA lA/^qCa;o*tECx!ev2!F P1+v zეA֒B@!G9L0@pdȺ d$F3h*^tY-ܸXDIV{aY>| e\Y8^8A'ˁ͜CxI ,|mObg16gYnVPDž}C9s|G uR E3Ď,p"aM{=,\W $i@;! (C"x^0A-OlE$|"pE]3@P,I(1FU! 5`R1LB J \!B ,LGxoߊqBuDB p0A cR]cpP@59QJ K/HXA9$jI4=�ѧ|Q,P M `T Opc *A pt'Q@n$"bdA& z*pa1 XpmnzgzkG P5'l&|4Q\ kb^@a�V葱2(�!Optimized by Ulead SmartSaver!��;�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/logo_small2.gif����������������������������������������������������0000644�0001750�0001750�00000022665�15003302667�021340� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a,;�� M8ALLJB2|)'ҿ%$,[PQ &"dFTMgqPVP0'9+!FYcD81ojF7-\Yh05 *:<pS8Ͱ=WVZA)%'`qsWLA{{H'*c70)A@Os[PF;A89)8(:.od8rfnܟB89B-)FB,WV4(18}m{q?!ON^,{sMyqwi:Zej-O'0,Y0 `n::'/+ϵuvAOOCL0|04tD^jL9E/PB.G2" *7s_\99(D2VZ<zs M[_) ZE8yg8geo\V^70  =MBaO1{>knXi}oysIҚlfYb˩Ī"JF~xl_џiZTEz^9/JZQlSC:^&+LK41;gBB>X@uiuVqiyr<[abz~hUO[Pdexrj~2L@uDzvZ:> MBc]lt|Zdc>|PʸaQwll]5׸lsvND?ŶȔĬ׺Ď{y>FNdN@}EMEPOMSa]<Q:"rP:'m{[Zž& *DC{bQ6CC6L4DAxhHEbY36OPK\Dob*6VV)85:6re~8=4}jk߫^˺ML/N6!|e]3e!���,����,;�@� x/(\Ȱ"6nŋ3jȱǏ CIɓ(3^0׏!! ALwsT ) J(Ȋ< .h ␧>['EŠV,I#c&ɛƌoJr.W.uc*sl]->ѱ\ c¤r"U.9!۷B8Zz5-O* ;RF8{W(X|X'N)9lh? >bB$|JR}Ww>1뇐/Fɧ b*)1#"`aMFY9t B!!P=1L_TBRd\LD < #T-cLȴGtQA ,@VX(cG#Yzx30W\AKQ#T@ az&R=`D` R!*\J#5uGu~\b%첆C R4|p5L }W95Jɱjdj +9f  � K9[$D@dPcL66Wi.iZ8oTQc1E8LiX" C iOc"16|B%�E o/]ЃWT]Qf?}]Q >MDDoLfl,E.ك;|,ܳ}%`* 1,?d`�b)`,V`m3�3fU_mJA)K, Ai`8DW^50ݔ]'N$]dX X!"�5PAϏ4t| dP*Cd@ Ƒi%D@41l�\/`RpM#,2d0/@xB: "  qM- 3vS 1?�Wo03& 08&$C8Vp_a d`, ȁ4Рje "DT( >zH 9v:ul {8X2 V &-�+]H�P$D P"z�@@W�JC9H5 vJ0rHNJȘKg"#ɰFT+RNI�%Y&W#4W"ZLftrjAA4[ŠjRBXІ:sI�9_?L:vjDǀ"#V0}>2i,Kw(_uRmx#8dn|0Qr.aB:XCrIdLk%-4"-pP^ o 0 @¹ЃLJ4{@e| 'Ud$4p+y�lzbAMX.�hxc!^8=( � ٠D!h!RD3/H5=Vh4,1M=xֳp?I TKǵ C 3bt?Pm@B& mm\g)E]� i&1W@Hх+vp0fǹJ�3͡2(V,kI0} (zG۹I,o4@`Q p˰/l 0cmټLB|SL4]|E9 NsƵ|=nL^ jU-& %-r?�)4}l~,ei\Ɛ'# iV]e<SB BĩӤ9L&Y<]p_nĐ AA,A.}(/5ͰdX&?_ЩJ=cXP0@v2r�q-iwmVAKqȡa r&s.)"~`3dX8}p1Bl2T-XAg �A`ZLG l1v&  ;,7! `(,`+ `Xrr,z-ʇ *D $6q$%%;&A: |F0F7\p12 HF _GIȓ}ґ|j#_󑄖Gg1c)=?^M ue\s̔Ȗi|F Ji)EI2CO~5Ce`HE` gO `h -fR2O? D��ЀVwN_紦Š@S&5z78O`PLufsy$0P "pN'qZ `|] (jD"p-TKOp vP�Jpx=P{JAQZZ dG„=IW$Xl  15ђK:nH?JcMH|KEQ]WY#$ / W)EI&I2JB @SI���<p�l@200wf` D& ΰS ,tA@rp Up�0 p#b.<X@2#`ư2Jaa0`�,xPZ8&Ovz3Yر�5�+�ARv >�,{A Ra+M p.y 291a�fZ ~"-9ctp~{P3}dX@hXE%)"|3q2PV`R"�pq02W 06`7K_P`ZxX0(CNp 5Y0rPZ`) @�m*u#% ,",2[[IɲZ25ZY7[ϲ:P|/p~={ %{:0p/}oP`^2B^I1:0 B^�4JX<` 0S%<h.EW F0W�3PJR,5H&2Jhg �d >pf� % )IN+5`[5eC,\V5@7A57\1`/5c,b$[f t ^3czD…A f0"O:lCrE"a � Qj _ �01֞jc` S�##'{lR<P hhphʈH:xDOG:܀ DsHS@4[@drPr#e�r@%7X[ mfPxc `RУ5*tfvP-8]` .. hxi{ XPB�FoOHE` S s1'%i3PV xF0HI/eA/Hrx-4aQ@ Cl=)Vl4 H65ߑes6ir;pmb2Kp܆modl6BV7m1`eu@Pu$ P$` GO S` {7p SfVX a PsZ#;lJK; 3C$Kv2Ie% <�6�]0\(zP u>A@P `>�0 Q m5V`5�m0д+5m�C;0ns@/B嚚+Aer@ @7>6po+awcCV0"P~/ #C:EU!S$4pq%R@P�-; #d s)0`E=.`gGv0s` ]T]@.GHw p>pD< F> ;/aZ?@<~f` �>0 Buw~em@d` 12d[ `9f p*P@SDP d:^3CzZ #6RdDȬ;c ��Si"@P�9o$qtDP jp0gT_р Pv3zTf �&u�`qKb$$x&,qoF ey{Q J @;) {@h^D͏%Y&H,yz$y0K#9Y"#tJ�EI=],k#i2ñ0SzLŹl9=? V}X(zW\ո p f=#`j �UqIr/zD|Ň|"}!A�}�P�} V@|}٘}LPa 穌 //�@ M P ֐eoPpQ =#ٺBQqPp@= m+ʽ�%`+e ^=Sݦ3"hO݂1OV0bkt}#K!! TOh)@sŀ HB<V>%i0 Z{c0wJVxIqwZy bH x &Q}UKŤrQy:0P t`O'hy�}w}l :q d zI1hd_@ NOv {q0P8@vc`aaq@]OF`FW /PurQ4`!d � QDO ~b�3ʚڈ3,%= l ��0l @Ġ�3L $Zb lSZ-p�7p /`Yp�Ȱ ddp` D@ l03�0QDpFh`\ 6d8P �, 7 ے7L¤ @ a?Q |偑*| >P\Iqy[DM9@RUn1]S3US0YS@Q=p rQJ5^5 #I 8S" } C0ׇ<lPj@ @0aDn@ë�*l``4}X ^wi XwY �`Z0 ZUբ0 o�,,+[Zdz)eUQXVV@=P rzsGw萫0 |dxNݘ<d)# !E�)ט HP\ dLYBL!s6ENL>r]ܰabȤJEPay'h,@3MA\KH Qjt*5g!.5jԃ'|҆״i$I2D>rɭNJ`>8s>-z1D16}ghr!'vXq`~̹sXKGV='}DtD&ONrXEƌaB=dE΄iatHXy"[z� Xr'J9{K t+衭 c ip@>)! 8pgKD&x|J)b@kTQů"0>J2^7|`b(/kgႩAM8k`^3aNB�M3r#'$GB�8ez0PtQFGg2& u`C!VÀLHzb ܨ& ax?,L`@r�`r^0T l( ]bعh2v\ qէ08v'[f. 99bCYI](KXJY,!jॆxC2>b::@:Xm 6|X`댌5k | ;%c ݄k'zxbWe}yV+U."2cZo՗ ;9,r*�X5q [m;C]"q ;l(&>KG# {T% 8’_ ˗#KF0irY{BY-`̊".!Xs%ʯ V�`jlA$b(<$pi8= >V`0;.`f`YAC8bV° X]qլƠ\0�A2nЏbz�:gmW ,p D nhJ:�xbx.690B˨00|U,D6qO@yJP1 A{C؂|!|)g4zSG :3Cʷڼ,~dXVpJ(c?r@DGcX!0+a%|Ou@ ʐPh!)H@nDa pk=L&,GLGP&n?5MRv0N9Th@xJTnU(X ACE>2,M ( Џ`) "Ni(Y&[B>*5= B?4:鬧Cm9-= z�PT$1Cdkl#H =Vpa+4!"6P!,G%�0N,,ISh >CU@؀ 1 {&k [ha'B(/L4D&+ A`-@ʇ)X33�q&;`@$gpS3t)ux0 zlp%ά|.zC* )�I V5ZXO!"` T�u;ԃ4gWu<x+̣?.՘qU=5ac`r /g+B.p^�#BN^`0dvd@l26O~)W?*` Ct5>h)H3%Lr k  i@ HFL*>C,k7q:^BP6?QǬA +,üL-K`T�+GWDTX% d?\9 >cW|A,d"~-�3㠲`@InSt8`8P,@�I'Ɛr DI1Jj qu@3pb.;-IdE �WԈd5wwQ9@"& 3ÝkC>�\5nD*8@hıvo9cW ݂W :<᣽q2U#%{�� c`@?( 1x 2Ӏ$AAIq#a$ (ސT=6 *9�J^:%ct h|g,ucJџ'! Ff�t&x09z 1h <P]'" :!ba CHL:d ;;+�hWp;bo@K /˲' ���� (A4Aq9A݊ ܃ Ap2#7ˣii8/Z6Ӂb9?+xf"(X+�84 =�x(% 0h`\�e+>r093 (Ѕ iH�O ?;002l1U�({7<0 (00�(@4=_ 9"F@6Ix5;ΐ^`(p;x֪@q :XX2rȇsz|*�9?SA(:ۮ2�3 0< ;90c)i �Jn]A!, �@@�!z$&@UC 8&: ;>:М�O0v8Iv`|Xbp"d#>RP ( #(�c<x@#\;@Y(P�jڦ<n#7�QR }GJ1Ƿ:Twrpm' 1t*, z2 \HL8肠g~8vHX+" (HȀi9Q| @qLJd `JX`c"1˄x3t �CK*1t9,ZЇD jV *P,@k(B];VPb Jt^̀ ١L,b/8C,lMͪ̕BWz5""|ηhb� Ls \##tJMFk=KWJH8҆P xY(]y  ;Q S1 9o2 eM5E6} d3% ɰ3(5TRB=TDMTeK!E#PۡFFM T+RETP UQURD8RmUW}UXUX �!Optimized by Ulead SmartSaver!��;���������������������������������������������������������������������������Finance-Quote-1.65/htdocs/Images/bg2.jpg������������������������������������������������������������0000644�0001750�0001750�00000025450�15003302667�017606� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������JFIF��`�`���C�   (1#%(:3=<9387@H\N@DWE78PmQW_bghg>Mqypdx\egc�C//cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc�-,"�����������������5����!1A"Qaq2#BR$3brC����������������������������!1� ��?�Sm�j<13 fLL"*opX1ݬ �LHj6fhYfϊ qFϊuu6$cVyA%~`f ¸;Vbi,R5 IR0h\kQx拺՗PRt#";J 9//jwAg `P-mv=0 B@2$LӥU! 1W~3@M'sJ@<sBޡ0N2g4`28B0h8ASAzbqh ڂafL; Dv޴}ނPfg+03#bAQ"+!?#╵36EAT2< `v3@-b9:{q* V.>?6sZ5CH`cPQm\#$aG xڋ69,dwC`HuNd8čΐ@M䞀F4MV,p@(C2+8]f )TeA&ʫp&.1D11AF}l�"'r"0�c';O�A/P2` 2yVke.!h$ݿp0x[ҩ�d 抟z]vą[.\-P`uЈoTï'ܖk$I5 m=K ɪF#W55Y ǹ:Ayah0'q qedu -'Gg?aYr 6$9MWR].4(;j!U85Chx?k̰UN�I.hG#UJE|(p3MdNJ0ә* #H*ld!'HոڬH'pg$%H,d5&3iU<Q�V$i1m| h|* 6Y#( 04vRorĨcqE9G4D䏊e +Q8ZX("I LS3kkIYք(U\h0#`8]R75B0` 6i!sؑ]!dtqXi�hNXS+[E�g4BY%T) bv,Š7N1VU@QSwh4rĞ5{ךB=*@a�{Bx`d0u�✐iS)JvStks$](cz:Rؙ是QLV-[iD�<Y#+dPRـƠ>Ƙ;4[\I5&pm HXި O} HrOIس- $̚\j:tm&4b C3{ռ/'Y\ޮ`@T MWMam{BҦl`UmTՕ9W0~N ISV_B`"ȟд 9ZϜ;PP#mLjFx!h1'nu;*n nx$ y ˴-l f1ƒ>N&ă3A$?Ny8*ih榁IkgpE%Zcbj]GRxdRI{V�9 v;P0m ] zY"B /,; 8M- 0Wqr\Kchvlm\!:IZDk,`z Nu6jB^�&D.!6"@t;V`AEZV#3S&�@SD;) ڠZ9ިζKq ΓI. mN-8YN{CXsCN1l4m$|w(DY4 ni TKj~EV+BTmkթ?z U4\ޡJf!V%t̓ĈO'.Np0QKvI:T4[J3M@l),jFM: EDd�j{wRx  <S<ChELą�4&v:2Φ)-)#.N@ÒL�9sHDg"0~P3mKP`<\*Bdd|Pd%TMmzB1`t⋕V(I81f0P5SsRer<m.*@Z#(aASP۷ŧd#Q\ዪ`Et[IU]]C"v-ȩ\ ۅCUhtkn+;Oɮ@5e5_D|@@tR svRdh- 3(5PqpH:D6Qpzp�i7F�L$R) Tfrg]*Q� tVn WEW(-�@"N>HT]C�ڹZ^g{nn K (Y`-1c@dޔ+2f2(.ZoMjvpIV~I)fg d6S1 tu o[,GP1AsHX8}Bӊ Y@’f[cިTޘa@ &1>�,'ibaM6☰V'|R ڀ:Li8'xaC |՝a b+#jxA4i`+U?IH,@ 4.HK}38t[�Vw50p=cKc(u-X T9$ad?zFreE*jHXs52ŖtRYO{df~ҩ(cQ";&ju]7ͲpN�kKth,r8J@ȁޘ(Dާw@ί= E j>65v&+r6`Ft]6 ';T^;@q]mmv Eǃ8J]$5;ezvX]MM@$GR.PMGH$s>hi 9@7rbkI74j$׺@ &@J~7njbY v@A  [Kq*.^7*Kt[p(;*\>t�b|oH1 l&v"� dj5$hZUZB&;Kq,s6ΧU*e Vղ (s3d,  ~Ns#&84SS@\:bDNP?Dڝ|V"'`yTH$N@EIz㮠"sPZDy6A@Ϛ FzH܍ڏ 1ϊ WXsV<p4Qj2FƃO]ȬpQf+Eefo} Xm݆m Ҁ}@qₒ4<4X) N =&o<H$YKT:� +6V0cz p%L@z?5K[]>'QҎFgS}Fnv�)L 2|b1qlg5/oQ c bB>(,حǒʯfwqV5Z (W: 3OސeR Lq&w*v^^0)IT繠-i$KaYQ -6ڐu?P}RFMцP8hp{WQRI$1=%EEd@u\cwP.{L?5狥 nx5e~5pHb:̓R{`WGo]8QE ;A\W6(uf^0�޺ ha q9;UJ<g4m-((ǼTɐ%0*VH1/*CȝD4s�HYLF+4 (V]fXxĮlhq b('!x45H7\Kd <ENB1$X;#K)t?q)idT``2;M(rS*M$+- ng-4UL['a.+46';zeq_H˙Ukzj&*~j0HgeD)9h%kVVu*1=s#[QAS�SfkbH3W"OO"�Lp\V ɮp %" `.E195CH�#WJX }+8s.`(H\sAnݰu1k/{Z0Ao2i 3kƥqPk(]QCNNIުߣ]q"&[樢\UWYPpRˠZ�[EAҳյ Jrj4 mQ@?zJP'_IRܚK&1AeNwURG` $q[^ҙZ lR6Do jHVʨzh'�5=!Z� P;NUui}LYZ&>6hkZI?&QA V֡@8##R4LuYkw}#yY-o])x=N}-ʷ=?L%[P"QK5зH>(w&�yo)&ڜ0kA*AgSop*=*v�4޶1AF$@w?cT=ŏS �Էg8H3*.=$$eĈ '`&6E%<7Emd5~ߦ` ZUa H BC0AOb\. !% 3M}5 1'5Z.Y[`##s^_w#P0 ikCl,oj]*w&mtev*G$p*\5e;M 0IިM[yfR el!�usނZյ*4 WbwIl5@%Ac[(m/M=ꖡSmjw {ژh�buÓMB1ёD0k�XyZIPNwO0'NdFG4ePI%P8CX *^G,C)sZBX14`~*u$QC9[J0 \{NT˃,YHGUQFE}Pd8"VE9a�;xEuW^0ԍmF5He#fAΪGPfI+)Ћ֫=}쒀@*lgT d,Gٿ Uwu#C)~ +-[(\TMA2Dbi-2lA[WCZY2cSkY"=BhV_2\`&P�R-fv_pr �M(,Yl5kr<⻑n`MqoQ!a8A%Uv׬`zgp!Uq\, fTݨrL(29]oL( F!,IޠyjqNT*>M4"5C#�14@ւ09r 7):gk`ic0b]@E! 3*]5pHmr@nPKLhJ2҂tdus@T K+='m^F�+;ŧ*Ēvx`A4܉ w"p&Gz( vڪnXF+Ԧ0jJ'"Ƶy[5c,1'Ҧ2T{*k@mn<ozKyB'WD STUs>1TJ ~#5`ůˤA7w�j 'T@;=UKS�1ڀG'vp�E+LwKog_RKSO4pr0Go^fz�l+K2-�';vS^ w}:no-ɨzw?E2I*j7*5k Z:!/|W=mRY'⺘.3{+�PT0�~{W iX +sF$ѷ-5E}Y"nX)cUD'j Ѱ)t>|P c Sm1D fx3@D1f�'j]JJT`80h8+DF 蹤 |T5Pge7uh#@\p8"J6AI #�ArJć4�vXLFMMP;NI?oZAS;M-?WuqڂDQЎEfLTܟa i6r`G޵'H؝/59&)5ipNLfQ#�4m O=G-ɼu:da>€}r&HUvL %Hzk`+30`Uql2F{j[Fz{o21 0JK+6-3QlBfl^`0�bn8W2U;'sT1f&2rrSu R_ i銢7}9mSD5J.U-!K$H` 8Ȧ@"2kZYCa@"N7c? SP|( F P� qPVa� RABөw Ff2 L/5mEc0vw}�bF\7NCn`Qw[ 8-8*(�WI0x>h*:J…, H#4z+PD|fc"w(+v[I#+[ 1S2>⋎1I:]`fUY+ڔjhE#hS (, }9Lam[kNb f@J�O#K`H!q'y3Ojtsgq'z`YdQɩt\G5AE2%l,$JtoԢ0tpW(H&6�A"m144  (m/v�.}KJOڶ=%DL 85&T,zosBUV"r#4 �4TH zSQ3;bQs@!qކT''DysCv'a<PA`bFT,H8q`wImD!@Q�NxV.T�.'PB.vlQ{ ڎ#B0*j|'�PВIѰB��Z�Sҷe4_T߱@/.�JSujuHZ i; #\ )nYs߹D\OH9IM,qiU;bt0C'Y�(h؍#Yd(Of:@V�rMI$nwɠ[]BLP3�ni/BjT@+)帢~G4 Af]<S#`55bD)1#Ia Z4(m,*\Ub$S`3P2osY @=�mZ$Pjww.s΀kb0bLlh p&>TAD\emG&g+{zZqXIhPdsLrQ�( Ep4 &#xLoA݄aAK,ې�Ff=n;^ mUk0Ҋ&T]rIJ"$ KG@ }[GjANOJ'Q3 Ӝ)?ȈvwUBkQj{Օ.i�;NJ#)9$h4hElE t{ QuP$Q vڃaG5_Qί= (d0* `x<EA F` 9oe:nmGI\f$T̙q j( (22 X:B#W5Q[sm84Lj`ec8�̘@Ha?aB;ж4 P �z(2f:4mc04m@V?PVH#z*To0/�:q'RB"E9 �C x<Pc%?H6gdMSLLg$ lGTQ]TOqU}*!P�OVYZ.DmB vaAp3? sIE yl0;_X:DأhiA I$dh3aF5xڙ4'I"陖5nI;)،T(&0@s&v4\̜x0D-߉76">+Ãl3rr4�  jQR`{ (ݺH3QqeCppUژݛd[:_qS`1ޠ@"6IMNwd0y3`CGOGiH4(FڎiC*)r iQ�@6#V4yC% =& iu(̡ <NvUUAc(0,3#T4~ &>GqX}M1%ڥ9(GmDMeTLdCAZVA-BLTQbu"3I{9}v)Slv/Jr)#L-8]RHI.#ā(5B``| qUT&@0jAΨ$f› 0$FsH�&uV\})nBp H&̤H;ԂaIo޶:<KZ3ȫ<7F$}'E@=$.8YҬ[pc5b" bN�fެq\ޢܻI(�sr7=L(ic궊䚣e5`6><{Zي d6v#$>ǑVbQ&{e`7j d cjtV`ADDf4qEcqB 2j�P 2|w#9$x�ʧQ6'EIԻΐS.* TeLֶ`8`RzA"t:qHP*Fj^ݲ RܖRD}(2b }+.l)d@#&rKt�"ȦЗ0mV^PU@{�D-n{<RR�#X)k{u9U@c* AZ,ڐk.1;P i9FfSiAi8⊱X#⠠ $CDNGg|RRar)AHߚ:N lh.%,3@m@94K(wO q }+\--Bxb $|(,$CH8R#5$z[<}WR̓A4 �Oc 2(`El/gBLwLI='"PP&Gj3F.@4=ɧS'd+.P 5z1♴Ld1JVXfkSYĜR接PYpHi @nL�0HX+'V ixP @v &h�!k+�WA p]*WPAuRԠ?N@ ";Ԕ܂z{ʟARIpPmMI"�YLf('z- 2'%[cRad<R"`PLS?�~6AL7 z*F ,Ӌ2 �j*U@H'@xI[2H!# nG;+ڶ4ADA+4$�H&3mP#tQ479 �4rg T u^>(%qZb8"WKrN(ZcpDj3+] �LgT28<UypjI`pj�2h&2 �(6@0dbB `344IemSRUCHc 8;SKi&I58' [jXdqЬI*AW;A*ԀO~4u%}ƚ��QF-dlFi~YAKQ@QTYꤐAv_*@F7- 8 31*mJiw$Oޝ[d�x#Q$#�T�-c &HrDN '^65S,`搀\9\hf6+ZUyU@|֢Lb$f"bQZ&Nxm;1Nmi:;N-W@V="`*ˁ7%T:Dv1g|Qt$c > ?Q-$VG#ĶIE^j85ELJMal)0Uo4e<P�AZwT sH6D &&QOrޔ�V8 OYl՚Ko/V1N49<F,ӥH"yޘ[Ivs#< Mi 3QH$Sܱnv*i TM\\) ɫ=hI|O2*ު lv[څm)moӳZG~(?������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Finance-Quote-1.65/htdocs/documentation.html��������������������������������������������������������0000644�0001750�0001750�00000006227�15003302667�020765� 0����������������������������������������������������������������������������������������������������ustar �bschuck�������������������������bschuck����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <!-- vi: set ignorecase noautoindent showmode showmatch ts=2 sw=2: --> <head> <title>Finance::Quote Homepage
Finance :: Quote
Button Home
Button Discussion Groups and Mailing Lists
Button Bugs, Patches and Development
Button Download
Button SourceForge Page

Documentation

Manual pages, the hacker's guide, and other documentation for Finance::Quote is currently available at GitHub. In the future we may be expanding the use of the SourceForge project website.

The Perl Journal

The Perl Journal (no longer in publication) published an article about Finance::Quote in issue number 19. The final draft of this article is also available via this website. It comes in three parts (text-only):

HTML-ised versions of the article are being worked upon. However, the plain-text versions are very readable.


Finance-Quote-1.65/htdocs/index.html0000644000175000017500000002706715003302667017230 0ustar bschuckbschuck Finance::Quote Homepage
Finance :: Quote
Button Documentation
Button Discussion Groups and Mailing Lists
Button Bugs, Patches and Development
Button Download
Button SourceForge Page

Welcome to the Finance::Quote webpage.

Finance::Quote is a perl module which can be used to obtain information from a variety of sources, including markets in Australia, USA, Canada, Europe, and a number of managed funds.

The latest version of Finance::Quote is (soon to be) available via CPAN, and is currently available on our download page.

News

2025-Apr-26 Release 1.65 is available from CPAN and Sourceforge. Removed DWS and MorningstarAU. Fixes to YahooWeb, YahooJSON, and others. See "Changes" for more.

2024-Nov-27 Release 1.64 is available from CPAN and Sourceforge. Fixes for ASX, Comdirect, OnVista, and more.

2024-Sept-21 Release 1.63 is available from CPAN and Sourceforge. New FinanceAPI module, fixes for TesouroDireto, BVB, BSEIndia, NSEIndia, and NZX.

2024-May-16 Release 1.62 is available from CPAN and Sourceforge. New CurrencyFreaks currency module, fixes to YahooJSON, Fool, MarketWatch, and AEX.

2024-Apr-18 Release 1.61 is available from CPAN and Sourceforge. Fixed YahooJSON.pm module so EU users get required cookies and crumbs for API.

2024-Apr-15 Release 1.60 is available from CPAN and Sourceforge. Fixes to a number of modules including YahooJSON. Please see the Changes file for details.

2023-Dec-31 Release 1.59 is available from CPAN and Sourceforge. Fixes to a number of modules. Please see the Changes file for details.

2023-Aug-12 Release 1.58 is available from CPAN and Sourceforge. Fixes to AlphaVantage, YahooWeb, YahooJSON, and Bloomberg. New modules Consorsbank (primarily Frankfurt Stock Exchange) and Stooq (primarily Warsaw Stock Exchange). See the Changes file for details.

2023-Jul-01 Release 1.57 is available from CPAN and Sourceforge. 3 new modules, YahooWeb, GoogleWeb, and MarketWatch. See the Changes file for a detailed list of updates and fixes in this release.

2023-May-29 Release 1.56 is available from CPAN and Souceforge. Change in Yahoo URL, and Tradeville.pm replaced with BVB.pm. See Changes file in distribution for more details.

2023-May-13 Release 1.55 is available from CPAN and Souceforge. See Changes file in distribution for details.

2022-Dec-26 Release 1.54 is available from CPAN and Souceforge. A few modules updated and fixed. New modules - Sinvestor.pm and Tradegate.pm.

2022-Oct-08 Release 1.53 is available from CPAN and Souceforge. A few modules updated and fixed.

2022-Jul-03 Release 1.52 is available from CPAN and Souceforge. Bugs in test files removed.

2021-Jul-04 Release 1.51 is available from CPAN and Souceforge. Bugs in test files removed.

2021-Jun-26 Release 1.50 is available from CPAN and Souceforge. Deprecated modules removed, some modules updated and fixed.

2019-Jun-30 Release 1.49 is available from CPAN. Lots of bugfixes and code enhancements.

2017-Nov-05 Releases 1.39 and 1.40 are available both from CPAN and Sourceforge. These releases adds the module AlphaVantage to circumvent the shutdown of yahoo API.

2015-Aug-22 Release 1.38 is available both from CPAN and Sourceforge. This release encloses several website module updates.

2015-Feb-02 Release 1.37 is available both from CPAN and Sourceforge. This removes some dependencies to obtain better compatibility.

2014-Feb-17 Release 1.20 is available both from CPAN and Sourceforge. This autoinstalls Date::Calc if needed.

2014-Feb-16 Release 1.19 is available both from CPAN and Sourceforge. It solves Yahoo quote retrieval changes and adds 2 new modules.

2012-Sep-30 Release 1.18 is available both from CPAN and Sourveforge. It solves most used module errors.

2009-Oct-5 Release 1.17 is available both from CPAN and Sourceforge.

2009-Apr-13 Release 1.16 is available both from CPAN and Sourceforge. Thanks to Bradley Dean a currency_lookup function is available. As usual, some modules were patched due to site modifications.

2008-Oct-26 Release 1.15 is available both from CPAN and Sourceforge. It is a bugfix release. Upgrade is strongly recommended.

2008-Oct-15 Release 1.14 considered as the latest stable release has been uploaded to CPAN and Sourceforge.

2008-Sep-21 The development of Finance::Quote is undergoing a lifting. More information is available on the developer page

2006-Jul-10 Finance::Quote 1.12 has been released. The new version includes support for new information sources, including Bourso and LeRevenu in France, and the Helsinki stock exchange in Finland. This release also fixes the problem with retrieving quotes with the DWS and VWD modules. The full set of release notes are available here. You can download this release from our download page.

2006-Jan-11 Finance::Quote 1.11 has been released. The new version includes support for new information sources, including Sharenet (South Africa) and U.S. Federal Bonds. This release also fixes the problem with retrieving currency quotes from Yahoo Finance. The full set of release notes are available here. You can download this release from our download page.

2005-Jul-04 Finance::Quote 1.10 has been released. The new version includes support for new information sources, including the New Zealand stock exchange, TD Waterhouse Canada Efunds, the Swedish Bank, the U.S. Govt. Thrift Savings Plan, and several Yahoo sources. The full set of release notes are available here. You can download this release from our download page.

2005-Jun-30 Finance::Quote 1.09 has been released. The new version includes support for new information sources, including the New Zealand stock exchange, TD Waterhouse Canada Efunds, the Swedish Bank, the U.S. Govt. Thrift Savings Plan, and several Yahoo sources. The full set of release notes are available here. You can download this release from our download page.

2003-Jul-04 Finance::Quote 1.08 was released.

2002-Apr-18 Finance::Quote 1.07 was released.

2001-Jun-26 Finance::Quote 1.06 has been released. The new version includes support for many new information sources, including Dutch stocks and indexes, UK unit trusts, managed funds from TD waterhouse, and quote lookups from fool.com. The full set of release notes are available here. You can download this release from our download page.

2001-Feb-23 Finance::Quote 1.05 has been released. This version fixes some bugs that people have been experiencing with currency conversion, and also provides easier methods for users to load their custom modules at run-time. The new release is available from our download page.

2000-Nov-05 Finance::Quote 1.04 has been released. This version fixes a bug whereby the 40th symbol of a large Yahoo lookup would fail. It also removes some of the headaches people have been experiencing with the test suite. For the adventurous, support for proxy authentication has also been added. See the FAQ for more details.

2000-Oct-30 An article on Finance::Quote was featured in The Perl Journal, issue number 19. The article is a good introduction to the module and well worth reading if you are beginning to work with Finance::Quote.
Sadly, The Perl Journal is no longer published. Thanks to The Web Archive, you can find the issue here.
O'Reilly also publishes The Best of the Perl Journal. The draft article is available from our documentation page.

Older news items and SourceForge page.


Finance-Quote-1.65/htdocs/Stylesheet.css0000644000175000017500000000154015003302667020062 0ustar bschuckbschuckul { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12pt; font-style: normal; color: #FFFFFF} p,li { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12pt; font-style: normal; color: #FFFFFF} h1 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 18pt; font-style: normal; color: #FFFFFF; background-position: center} h2 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16pt; font-style: normal; color: #FFFFFF} h3 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14pt; font-style: normal; color: #FFFFFF} .footer { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 9pt; color: #FFFFFF} a { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12pt; color: #AAAA77} h2[class^=new], p[class^=new], li[class^=new] { color : red }Finance-Quote-1.65/htdocs/discussion.html0000644000175000017500000000635115003302667020275 0ustar bschuckbschuck Finance::Quote Discussion
Finance :: Quote
Button Home
Button Documentation
Button Bugs, Patches and Development
Button Download
Button SourceForge Page

Mailing lists.

There are three Finance::Quote mailing lists. finance-quote-devel is a high-volume list for developers and technical issues.
finance-quote-news is a low-volume list for announcements only. finance-quote-user is our general discussion list.

Archives of the mailing lists, and details on how to join, can be found here.


Discussion Forums.

Public discussion forums, including a general discussion and help forum, can be found here.



Finance-Quote-1.65/htdocs/download.html0000644000175000017500000000755715003302667017732 0ustar bschuckbschuck Finance::Quote Downloads
Finance :: Quote
Button Home
Button Documentation
Button Discussion Groups and Mailing Lists
Button Bugs, Patches and Development
Button SourceForge Page

Latest Release

The latest tarball release of Finance::Quote can be downloaded here.

Previous Releases

The previous tarball release of Finance::Quote (version 1.13) can be downloaded here.

Finance::Quote (version 1.12) can be downloaded here.

Finance::Quote (version 1.11) can be downloaded here.

Finance::Quote (version 1.10) can be downloaded here.

Finance::Quote (version 1.08) can be downloaded here. The Debian package of Finance::Quote 1.08 is here.

All Finance::Quote versions can be found here

Github

The CVS tree is no longer available on SourceForge.
Finance::Quote source has been moved to Github.


Finance-Quote-1.65/htdocs/tpj/0000775000175000017500000000000015003302667016016 5ustar bschuckbschuckFinance-Quote-1.65/htdocs/tpj/finance-quote-example0000644000175000017500000000360115003302667022126 0ustar bschuckbschuck#!/usr/bin/perl -w # # Example stock-ticker program. Can look up stocks from multiple # markets and return the results in local currency. # # Revision: 1.1 use strict; use Finance::Quote; my $CURRENCY = "AUD"; # Set preferred currency here, or empty string for # no conversion. # The stocks array contains a set of array-references. Each reference # has the market as the first element, and a set of stocks thereafter. my @STOCKS = ([qw/australia CML ITE BHP/], [qw/usa MSFT RHAT LNUX/] ); # These define the format. The first item in each pair is the label, # the second is the printf-style formatting, the third is the width # of the field (used in printing headers). my @labels = (["name", "%12s", 15], ["date", "%11s", 11], ["time", "%10s", 11], ["last", "%8.2f", 8], ["high", "%8.2f", 8], ["low", "%8.2f", 8], ["close", "%8.2f", 8], ["volume","%10d", 10]); my $REFRESH = 120; # Seconds between refresh. # --- END CONFIG SECTION --- my $quoter = Finance::Quote->new(); my $clear = `clear`; # So we can clear the screen. # Build our header. my $header = "\t\t\t\tSTOCK REPORT" .($CURRENCY ? " ($CURRENCY)" : "") ."\n\n"; foreach my $tuple (@labels) { my ($name, undef, $width) = @$tuple; $header .= sprintf("%".$width."s",uc($name)); } $header .= "\n".("-"x79)."\n"; # Header is all built. Looks beautiful. $quoter->set_currency($CURRENCY) if $CURRENCY; # Set default currency. for (;;) { # For ever. print $clear,$header; foreach my $stockset (@STOCKS) { my ($exchange, @symbols) = @$stockset; my %info = $quoter->fetch($exchange,@symbols); foreach my $symbol (@symbols) { next unless $info{$symbol,"success"}; # Skip failures. foreach my $tuple (@labels) { my ($label,$format) = @$tuple; printf $format,$info{$symbol,$label}; } print "\n"; } } sleep($REFRESH); } __END__ Finance-Quote-1.65/htdocs/tpj/finance-quote.txt0000644000175000017500000004121615003302667021317 0ustar bschuckbschuck#!/usr/bin/perl -w # # Run perl over this document to get Jon Orwant preferred formatting. :) # use strict; my $code_indent = -1; # -1 means we're not in code. while () { (/(\t*)#!\/\S+\/perl/) and $code_indent = length($1); $code_indent = -1 if (/__END__/); if ($code_indent >= 0) { s/^\t{0,$code_indent}//; } else { s/^\t*//; } print; } __END__ Finance::Quote (for TPJ) Paul Fenwick , Aug 2000 $Revision: 1.1 $ Introduction ============ If you have a reason to watch the world's financial markets, and you know a little about perl, then you may find that the Finance::Quote package comes in handy. I personally use it to remind myself that I should never buy shares, as I have a good history of losing money on the stock exchange. However, you can use Finance::Quote to help track those tasty stock options you've been offered, or even to help you build dynamic artwork driven by fluctuations in the world markets[1]. Finance::Quote is a perl module that makes accessing financial information easy. You can look up stocks and shares, managed funds, currency rates, and all this from a variety of countries and markets. It's even easy to add your own specialised sources if you find something the module does not do. [1] Yes, this actually exists and uses Finance::Quote to help drive the back-end. See . History (or credit where credit is due). ======================================== Finance::Quote started as the Quote module which was distributed as part of Gnucash . This module (simple called "Quote") was a modified version of Finance::YahooQuote written by DJ Padzensky . Linas Vepstas and Yannick LE NY had added extra sources for GnuCash, at which point I asked if anyone had any objections to me breaking it off into a separate project with a range of uses outside of GnuCash. Since that time, many people have contributed to Finance::Quote, including Xose Manoel Ramos (for inheritable Yahoo! methods), Brent Neal, Keith Refson, and Volker Stuerzl for writing pluggable modules, and Peter Thatcher, Jacinta Richardson and Steven Murdoch for various bugfixes and improvements. Ben Hemming was kind enough to save the world from my poor web-design skills and supply a web-page look-and-feel. Legality (Or what can I use it for?) ==================================== When discussing Finance::Quote, one question which often arises is "how legal is all this"? Finance::Quote obtains quotes of various organisations who make this information available via the web. Actually fetching the data is not illegal[2], web-browsers do it all the time, and Finance::Quote is just a rather specialised web-browser. The main problems occur with what you do with that data. Each organisation has a different set of restrictions and conditions pertaining to the data you obtain from them. The most common restriction is that forbidding redistribution, although some of them are as odd to limit the number of copies you can have in memory at any one time! The Finance::Quote manual pages provide some pointers as to where these licenses can be found, and it would be wise to check those licenses if you wish to be in safe legal waters. In general, obtaining information and displaying it or processing it into some useful form for personal use should be okay -- that's what your web-browser does. Obtaining the information and re-badging it as your own is probably a no-no. If in doubt, check with your lawyer. [2] Although there have been a couple of court cases where "deep linking" into web-page has been cast into legal shadow. Terminology =========== Before we begin, it's worth taking the time to explain a few pieces of terminology that may be otherwise a little confusing. For simplicity, a stock, mutual fund, index, or other parcel of information which can be fetched using Finance::Quote we will refer to as a "stock". All stocks have a unique identifier or "symbol" which we can use to look them up, and an "exchange" (or locality) in which they exist. For example, VA Linux Systems have the symbol "LNUX" on the Nasdaq exchange. Symbols are traditionally all upper-case, but there exist some symbols (such as the pseudo-symbols used in the TIAA-CREF module) that are mixed case. Finance::Quote essentially provides a way of taking a list of symbols for a given exchange, and returning information about those symbols. Each bit of information has a "label" (such as volume, close, high, low, etc), which identifies what that information is. All making sense? Good. Let's do something fun then. Using Finance::Quote ==================== The Basics ---------- I'll demonstrate the usage of Finance::Quote by way of a useful example, which will be expanded upon as we go. For starters, let's just say you're interested in stocks from a single market, and wish to print their current value and volume traded. #!/usr/bin/perl -w use strict; use Finance::Quote; @ARGV >= 2 or die "Usage: $0 exchange symbol symbol symbol ...\n"; my $exchange = shift; # Where do we fetch our stocks from. my @symbols = @ARGV; # Which stocks are we interested in. my $quoter = Finance::Quote->new; # Create the F::Q object. $quoter->timeout(30); # Cancel fetch operation if it takes # longer than 30 seconds. # Grab our information and place it into %info. my %info = $quoter->fetch($exchange,@symbols); foreach my $stock (@symbols) { unless ($info{$stock,"success"}) { warn "Lookup of $stock failed - ".$info{$stock,"errormsg"}. "\n"; next; } print "$stock:\t\t", "Volume: ",$info{$stock,"volume"},"\t", "Price: " ,$info{$stock,"price"},"\n"; } __END__ If our script was called "showstocks", and you were interested in Australian supermarkets, you could call it like this: showstocks australia CML WOW This will provide you with information about Coles-Myer and Woolworths. Alternatively, if you were interested some US technology stocks, you could try this: showstocks usa LNUX RHAT MSFT IBM which will provide you with information about VA Linux Systems, Red Hat, Microsoft and International Business Machines. This script demonstrates a number of capabilities of Finance::Quote. In particular: * Finance::Quote is object-oriented, and a Finance::Quote object can be generated using Finance::Quote->new(). * The fetch() method can be used for retrieving information. This method is very powerful, and will be explained in some depth later in this article. * The fetch method returns a two-dimensional hash. This is the topic of our next section. The Return Hash --------------- Finance::Quote's most useful function, fetch(), returns a two-dimensional hash. This contains a variety of information about the stocks you requested, including volume, price, highs and lows, percentage changes, and other information. Each key in the hash has two parts: $info{$symbol,$label} The symbol is the symbol that you've requested. In the examples above, "RHAT" and "LNUX" are examples of symbols. The label refers to a specific type of information about that stock, such as "volume", "price", "close", "p_change" or "name". Labels are always lower-case. There exists some very special labels that you should be aware of. The label "success" is used to indicate if the information could be successfully retrieved. If the value of the success label for a given stock is false, then no useful information could be gained about that symbol at this time. If a failure did occur, the reason for that failure will be in the label "errormsg". The special label "price" is used to indicate the worth or value of the given stock. This varies a little depending upon what you're fetching information on. For stocks, it's usually the last price the stock was traded at. For some investments, it's the current yield (a percentage per-annum). For currencies it's the exchange rate, and for indexes it's the last value of that index (in points). The reason the price label is important is that it allows for applications to track the movement of information without having to know if we're dealing with a stock or a managed fund or an indicator -- the price label will provide us with the information that we (usually) want. This is particularly useful for things such as stock-tickers, allowing them to track things other than just stocks. For applications that care about the details, there are a wide range of labels that can be returned. These include the highest and lowest prices for the day, dividend yields and dates, the time and date that the information is current for, the volume traded, the name of the stock, and many others. The standard labels are listed in the side-bar. It's important to remember that the information fetched by Finance::Quote is usually delayed, and so is often 30 minutes or more behind what's really happening. If you're doing currency conversions as well, then the currency conversion rate may also be delayed. You can use the labels "date" and "time" to determine when the data was valid -- it's not unknown for some sources to provide data that is a week old or more. Advanced Usage ============== Now, the basics of Finance::Quote are pretty simple. You create yourself a F::Q object, you ask it for some information, and you get that information back in a hash. For most applications, this is all you really need. However, Finance::Quote provides a wide range of extra features to help make your life easier. Currency Conversion ------------------- Finance::Quote has the ability to look up currency rates, and can even automatically convert foreign stocks into local currencies. Let's say that you live in Australia, but you have some stocks in the USA. You may be interested in knowing the value of those stocks in Australian dollars, which have more meaning to you than US dollars. Here's how to do it: #!/usr/bin/perl -w use strict; use Finance::Quote; my $market = shift; my @stocks = @ARGV; my $quoter = Finance::Quote->new(); $quoter->set_currency("AUD"); # Aussie dollars, thanks. my %info = $quoter->fetch($market,@stocks); # Print the info here. __END__ The set_currency() method asks Finance::Quote to convert all values into the given currency before returning them to you. Finance::Quote knows which things it can convert (like prices and ranges), and which things it cannot (like percentage changes and volumes). It's even smart enough to (usually) not touch indexes and other abstract indicators that don't have currencies attached to them. Be aware that set_currency() can significantly increase the time of a query, as currency lookup information has to be fetched as well as the stock information. It's also possible to fetch currency exchange rates directly. This is done using the currency() method. Eg: my $exchange_rate = $quoter->currency("USD","AUD"); print "1 US dollar is $exchange_rate Australian dollars.\n"; The currency() method can also do clever things like take a prefix to the FROM currency. Hence the following script is a quick'n'dirty command-line utility to convert between currencies: #!/usr/bin/perl -w use strict; use Finance::Quote; # Command-line currency conversion. die "Usage: $0 FROM TO\n" unless defined($ARGV[1]); my $quoter = Finance::Quote->new(); my $exchange_rate = $quoter->currency($ARGV[0],$ARGV[1]); die "Don't know how to convert $ARGV[0] to $ARGV[1]\n" unless $exchange_rate; print "$ARGV[0] -> $ARGV[1] = $exchange_rate\n"; __END__ If this script were to be called "currency-lookup", you could show the going rate between Australian and American dollars like this: currency-lookup AUD USD If you wanted to know how much 95 Australian Dollars were in French Francs, you could do this: currency-lookup "95 AUD" FRF Fail-over Support and Custom Modules ----------------------------------- Finance::Quote provides automatic fail-over support if you specify the market that you're interested in and not the actual source from which you want to fetch it. This means that if you use "nasdaq" instead of "yahoo" as your source, Finance::Quote will automatically try all sources of nasdaq data in case the first one failed. Fail-over support is on by default in all newly create Finance::Quote objects. Fail-over support can increase the time of a query, especially if you're searching for a non-existent stock. It's possible to (un)set fail-over support explicitly like this: $quoter->failover(0); # Disable failover support. Likewise, when you create your Finance::Quote object, it's possible to state which modules you'd like to be able to fetch information from. For example: my $quoter = Finance::Quote->new("Yahoo::Australia"); will only use the Finance::Quote::Yahoo::Australia module for queries. This is particularly useful if you have a deal with a particular information supplier, or otherwise wish to restrict where Finance::Quote can search for information. Specifying modules to load at creation time can also let you load custom modules that are not part of the standard Finance::Quote distribution. my $quoter = Finance::Quote->new("-defaults", "MyBank"); Here we would load the Finance::Quote::MyBank module, as well as all the default modules that are packaged with Finance::Quote. Note that "-defaults" as an argument to new is only magical when passed as the first argument (although in the future it MAY be legal to pass it anywhere in the arguments list). If you're interested in writing your own modules for Finance::Quote, then you should read the Hacker's Guide that comes with Finance::Quote, or which can be found in the Finance::Quote documentation manager at . Required Labels --------------- Fail-over support is a wonderful thing -- the source you're fetching data from may have fallen over and you might never have to worry. Unfortunately, not all sources provide all the information you're looking for, and having bits of information disappear when you're used to them being there can be a little surprising. Rather than having to worry about your fail-over sources not providing the information you're looking for, Finance::Quote allows you to define a list of things that are important to you, using the require_labels() method. Say that your program relies upon the labels price, date, high, low and volume. You can express this to your Finance::Quote object like so: $quoter->require_labels(qw/price date high low volume/); If you now use the $quoter->fetch() to obtain information, you can be guaranteed that those fields will be available if they apply to what you've requested. This means you can use fail-over methods safe in the knowledge that the information you actually care about will not disappear. Tricks with the user agent -------------------------- If you've ever used LWP::UserAgent before, then you'll be pleased to know that you can customise the underlying LWP::UserAgent object in Finance::Quote. For example: $quoter->user_agent->agent("MyTicker/0.1"); will cause the $quoter object to identify itself as "MyTicker/0.1" in HTTP sessions. Likewise, this lets you set your proxy explicitly: $quoter->user_agent->proxy('http',$MY_PROXY); Note that the UserAgent that Finance::Quote uses automatically respects proxy environment variables (such as "http_proxy") at creation time. For more information on what you can and cannot do here, check out the documentation for LWP::UserAgent. Rolling your own Finance::Quote module ======================================= As well as using the standard Finance::Quote modules, it's also possible to write your own module that you can ask Finance::Quote to use. Writing such a module can be a tricky task, and is beyond the scope of this article. The Finance::Quote package does come with its own Hacker's Guide explaining how to write a Finance::Quote module. This can be found with other documentation at Example program -- Stock ticker =============================== We'll finish off with an example of a useful, real-life program. In this case it's a text-based stock ticker. The code is intentionally simple to display the features of Finance::Quote, but it provides a very clear and useful display for stocks that you may have your eye on. [Example supplied in separate file] Finance-Quote-1.65/htdocs/tpj/finance-quote-sidebar0000644000175000017500000000237415003302667022112 0ustar bschuckbschuckSIDEBAR - Standard Finance::Quote Labels ======================================== Not all exchanges or stocks have all these labels available, but the range of information that Finance::Quote can return includes: name Company or Mutual Fund Name last Last Price high Highest trade today low Lowest trade today date Last Trade Date (MM/DD/YY format) time Last Trade Time net Net Change p_change Percent Change from previous day's close volume Volume avg_vol Average Daily Vol bid Bid ask Ask close Previous Close open Today's Open day_range Day's Range year_range 52-Week Range eps Earnings per Share pe P/E Ratio div_date Dividend Pay Date div Dividend per Share div_yield Dividend Yield cap Market Capitalization ex_div Ex-Dividend Date. nav Net Asset Value yield Yield (usually 30 day avg) exchange The exchange the information was obtained from. success Did the stock successfully return information? (true/false) errormsg If success is false, this field may contain the reason why. method The module (as could be passed to fetch) which found this information. Finance-Quote-1.65/htdocs/developer.html0000644000175000017500000001345015003302667020075 0ustar bschuckbschuck Finance::Quote Homepage
Finance :: Quote
Button Home
Button Documentation
Button Discussion Groups and Mailing Lists
Button Download
Button SourceForge Page

Important note

After the release 1.13 we took two important decisions :

  • Current evolution of development tools made us port the repository to Git
  • Since Finance::Quote releases are available through CPAN we also decided to make full use of the tools available on the CPAN platform.
  • Please note that we are in the early stages of the implementation of these changes. Some of the work has already been done, but some data has still to be ported or disgarded. Please take a look at this page for further evolution. Reports will also be posted to the developers mailing-list. We sincerely apologize for any inconvenience these changes may cause.

    Bug-tracking

    The preferred way to report bugs and other problems is by submitting issues to the Finance::Quote GitHub Repository

    Bugs can also be submitted and tracked via Cpan's RT bug-tracking system. Before submitting a bug, please make sure that someone else has not submitted the bug already.

    Bugs posted on the Sourceforge's bug tracking system after 1.7.2007 are ported to RT. Older bug reports (prior to release 1.13) will certainly be disgarded. Don't use the Sourceforge's BTS for new bug reports as those won't be checked.

    Patches

    Like Bug-tracking above, the preferred way to submit your own enhancements and bug fixes to Finance::Quote is to create a pull request on GitHub.

    Patches must be submitted as file attachments to bug reports (see above).

    Patches submitted on Sourceforge's site after 1.7.2007 will eventually be looked at. Older patches will certainly be disgarded.

    For reference, we'll mention the link to the SourceForge's patch manager. Don't use it for new patches as those won't be checked.

    Before submitting a patch please make sure that you have the most up-to-date version of Finance::Quote, and that nobody has submitted an equivalent patch already.

    Mailing-lists

    A developer's mailing list exists for those people who are using or developing Finance::Quote. For more information, see our Discussion Groups and Mailing Lists page.

    Git

    Finance::Quote development has been moved to GitHub.

    CVS

    The development repository has been ported to GitHub. SourceForge's CVS repositories are no longer available.

    Hacker's Guide

    The Hacker's Guide contains information on how to extend Finance::Quote to make use of your own custom information sources. The most recent version of the Hacker's Guide can be found here.


    Finance-Quote-1.65/README0000644000175000017500000000055615003302667014621 0ustar bschuckbschuckThis archive contains the distribution Finance-Quote, version 1.65: Get stock and mutual fund quotes from various exchanges This software is Copyright (c) 2013 by Erik Colson . This is free software, licensed under: The GNU General Public License, Version 2, June 1991 This README file was generated by Dist::Zilla::Plugin::Readme v6.030. Finance-Quote-1.65/t/0000775000175000017500000000000015003302667014200 5ustar bschuckbschuckFinance-Quote-1.65/t/tsp.t0000755000175000017500000000265415003302667015203 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my $quoter = Finance::Quote->new(); my @symbols = qw/C F G I S L2025 L2030 L2035 L2040 L2045 L2050 L2055 L2060 L2065 LINCOME/; plan tests => 12*(1+$#symbols)+3; my %quotes = $quoter->tsp( @symbols, "BOGUS" ); ok(%quotes); foreach my $symbol (@symbols) { ok($quotes{$symbol, "success" }, "$symbol success"); ok(substr($quotes{$symbol, "isodate"}, 0, 4) == $year || substr($quotes{$symbol, "isodate"}, 0, 4) == $lastyear); ok(substr($quotes{$symbol, "date"}, 6, 4) == $year || substr($quotes{$symbol, "date"}, 6, 4) == $lastyear); ok($quotes{$symbol,"last"} > 0); ok($quotes{$symbol,"currency"}); ok(!defined($quotes{"c","exchange"}) ); } ok( !$quotes{"BOGUS","success"}); %quotes = $quoter->fetch("tsp", @symbols); ok(%quotes); foreach my $symbol (@symbols) { ok($quotes{$symbol, "success" }, "$symbol success"); ok(substr($quotes{$symbol, "isodate"}, 0, 4) == $year || substr($quotes{$symbol, "isodate"}, 0, 4) == $lastyear); ok(substr($quotes{$symbol, "date"}, 6, 4) == $year || substr($quotes{$symbol, "date"}, 6, 4) == $lastyear); ok($quotes{$symbol,"last"} > 0); ok($quotes{$symbol,"currency"}); ok(!defined($quotes{"c","exchange"}) ); } Finance-Quote-1.65/t/stooq.t0000644000175000017500000000260415003302667015532 0ustar bschuckbschuck#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('Stooq', timeout => 30); my @valid = ('DNP', 'ISLN.UK', 'LRQ', 'GBP.UK', 'GBPG.UK'); my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %check = ( 'currency' => sub { $_[0] =~ /^[A-Z]+$/ }, 'date' => sub { $_[0] =~ m{^[0-9]{2}/[0-9]{2}/[0-9]{4}$} }, 'isodate' => sub { $_[0] =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ }, 'last' => sub { $_[0] =~ /^[0-9.]+$/ }, 'method' => sub { $_[0] =~ /^stooq$/ }, 'success' => sub { $_[0] }, 'symbol' => sub { $_[0] eq $_[1] }, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->fetch('stooq', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol), "$key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/consorsbank.t0000644000175000017500000001054415003302667016711 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Scalar::Util qw(looks_like_number); use Date::Simple qw(today); use Date::Range; use Date::Manip; if ( not $ENV{ONLINE_TEST} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $CONSORS_SOURCE_BASE_URL = 'https://www.consorsbank.de/web/Wertpapier/'; # Test cases for Consorsbank my %valid = ( 'DE0007664005' => {currency => 'EUR', days => 3, name => 'VOLKSWAGEN AG'}, # Stock (ISIN), EUR '766400' => {currency => 'EUR', days => 3, name => 'VOLKSWAGEN AG'}, # Stock (WKN), EUR 'DE0008469008' => {currency => 'EUR', days => 7, name => 'DAX PERFORMANCE INDEX'}, # Index: DAX 'FR0003500008' => {currency => 'EUR', days => 7, name => 'CAC 40 INDEX'}, # Index: CAC 40 '_81341467' => {currency => 'USD', days => 7, name => 'S&P 500 (BNPP INDICATION)'}, # Index (Consors internal ID) 'DE0001102580' => {currency => 'EUR', days => 7, name => 'BUNDESREP.DEUTSCHLAND ANL.V.2022 (2032)'}, # Bond 'FR0010411884' => {currency => 'EUR', days => 7, name => 'Amundi CAC 40 Daily (-2x) Invrse ETF Acc'}, # ETF, EUR 'LU1508476725' => {currency => 'EUR', days => 7, name => 'Allianz Global Equity Insights A EUR'}, # Fund, EUR 'EU0009652759' => {currency => 'USD', days => 7, name => 'EURO / US-DOLLAR (EUR/USD)'}, # Currency ); my %invalid = ( 'FR0010037341' => undef, # known by Consors, but no prices tracked on default exchange 'DE000DB4CAT1' => undef, # Commodities: Brent 'BOGUS' => undef, ); my @symbols = (keys %valid, keys %invalid); my $today = today(); my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0]}, 'symbol' => sub {$_[0] =~ /^_?[A-Z0-9]+$/}, 'name' => sub {$_[0] eq $valid{$_[1]}{name}}, 'method' => sub {$_[0] eq 'consorsbank'}, 'source' => sub {$_[0] eq $CONSORS_SOURCE_BASE_URL . $_[1]}, 'exchange' => sub {$_[0] =~ /^.+$/}, 'currency' => sub {defined $valid{$_[1]}{currency} ? $_[0] eq $valid{$_[1]}{currency} : 1}, 'last' => sub {looks_like_number($_[0])}, # last is REQUIRED 'ask' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # ask is optional 'bid' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # bid is optional 'close' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # close is optional 'day_range' => sub {defined $_[0] ? $_[0] =~ /[0-9.]+ - [0-9.]+/ : 1}, # day_range is optional 'high' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # high is optional 'low' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # low is optional 'net' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # net is optional 'open' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # open is optional 'p_change' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # p_change is optional 'time' => sub {defined $_[0] ? $_[0] =~ /^\d{2}:\d{2}$/ : 1}, # time is optional 'volume' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # volume is optional 'year_range' => sub {defined $_[0] ? $_[0] =~ /[0-9.]+ - [0-9.]+/ : 1}, # year_range is optional 'date' => sub { my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $_[0] =~ /^\d{2}\/\d{2}\/\d{4}$/ && $a->cmp($b) == 0;}, 'isodate' => sub {Date::Range->new($today - $valid{$_[1]}{days}, $today)->includes(Date::Simple::ISO->new($_[0]))}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check * %valid + %invalid; my %quotes = $q->fetch('consorsbank', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ### check key: $key ### check res: $quotes{$symbol, $key} ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), defined $quotes{$symbol, $key} ? "$key -> $quotes{$symbol, $key}" : "$key -> "); } } foreach my $symbol (keys %invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/05-data-dumper.t0000755000175000017500000000071715003302667017020 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{TEST_AUTHOR}) { plan( skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to true to run.'); } plan tests => 1; # F::Q doesn't load all its code until we actually create # an object. my $fq = Finance::Quote->new; # Sometimes Data::Dumper gets left in code by accident. Make sure # we haven't done so. ok(! exists $INC{'Data/Dumper.pm'}, "Data::Dumper should not be loaded"); Finance-Quote-1.65/t/author-pod-syntax.t0000644000175000017500000000045415003302667017774 0ustar bschuckbschuck#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); Finance-Quote-1.65/t/currency.t0000755000175000017500000001030315003302667016215 0ustar bschuckbschuck#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: use constant DEBUG => $ENV{DEBUG}; use if DEBUG, Smart::Comments, '###'; use strict; use Test::More; use Finance::Quote; use Scalar::Util qw(looks_like_number); if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } sub module_check { my ($module, $valid, $invalid, $options) = @_; plan tests => 1 + 2*@{$valid} + @{$invalid}; my $hash = {order => [$module]}; $hash->{lc($module)} = $options if defined $options; my $q = Finance::Quote->new('currency_rates' => $hash); ok($q); foreach my $test (@{$valid}) { my ($from, $to) = @{$test}; my $v = $q->currency($from, $to); ok(looks_like_number($v), "$from -> $to = $v"); my ($from_amount, $from_code) = $from =~ m/^([0-9.]+) ([A-Z]+)$/; SKIP: { skip "identity check because different currencies", 1 unless $from_code eq $to; my ($suffix) = $from_amount =~ m/[.]([0-9]+)/; my $string = sprintf("%.*f", length($suffix), $v); ok($from_amount eq $string, "identity check"); }; } foreach my $test (@{$invalid}) { my ($from, $to) = @{$test}; my $v = $q->currency($from, $to); is($v, undef, "$from -> $to failed as expected"); } } plan tests => 8; # Check that FQ fails on bogus CurrencyRates method my $q = Finance::Quote->new('currency_rates' => {order => ['DoesNotExist']}); ok(not $q); # Check AlphaVantage subtest 'AlphaVantage' => sub { if ( not $ENV{TEST_ALPHAVANTAGE_API_KEY} ) { plan skip_all => 'Set $ENV{TEST_ALPHAVANTAGE_API_KEY} to run this test; get one at https://www.alphavantage.co'; } my @valid = (['1.00 GBP', 'IQD'], ['10.00 AUD', 'AUD']); my @invalid = (['20.12 ZZZ', 'GBP']); module_check('AlphaVantage', \@valid, \@invalid, {API_KEY => $ENV{TEST_ALPHAVANTAGE_API_KEY}}); }; # Check CurrencyFreaks subtest 'CurrencyFreaks' => sub { if ( not $ENV{TEST_CURRENCYFREAKS_API_KEY} ) { plan skip_all => 'Set $ENV{TEST_CURRENCYFREAKS_API_KEY} to run this test; get one at https://currencyfreaks.com/'; } my @valid = (['100.00 USD', 'EUR'], ['1.00 GBP', 'IDR'], ['1.23 IDR', 'CAD'], ['10.00 AUD', 'AUD'], ['1.0 INR', 'INR']); my @invalid = (['20.12 ZZZ', 'GBP']); module_check('CurrencyFreaks', \@valid, \@invalid, {API_KEY => $ENV{TEST_CURRENCYFREAKS_API_KEY}}); }; # Check ECB subtest 'ECB' => sub { my @valid = (['100.00 USD', 'EUR'], ['1.00 GBP', 'IDR'], ['1.23 IDR', 'CAD'], ['10.00 AUD', 'AUD']); my @invalid = (['20.12 ZZZ', 'GBP']); module_check('ECB', \@valid, \@invalid); }; # Check Fixer subtest 'Fixer' => sub { if ( not $ENV{TEST_FIXER_API_KEY} ) { plan skip_all => 'Set $ENV{TEST_FIXER_API_KEY} to run this test; get one at https://fixer.io'; } my @valid = (['100.00 USD', 'EUR'], ['1.00 GBP', 'IDR'], ['1.23 IDR', 'CAD'], ['10.00 AUD', 'AUD']); my @invalid = (['20.12 ZZZ', 'GBP']); module_check('Fixer', \@valid, \@invalid, {cache => 1, API_KEY => $ENV{TEST_FIXER_API_KEY}}); }; # Check FinanceAPI subtest 'FinanceAPI' => sub { if ( not $ENV{TEST_FINANCEAPI_API_KEY} ) { plan skip_all => 'Set $ENV{TEST_FINANCEAPI_API_KEY} to run this test; get one at https://financeapi.net'; } my @valid = ( ['100.00 USD', 'EUR'], ['1.00 GBP', 'IDR'], ['1.23 IDR', 'CAD'] ); my @invalid = ( ['20.12 ZZZ', 'GBP'] ); module_check('FinanceAPI', \@valid, \@invalid, {cache => 1, API_KEY => $ENV{TEST_FINANCEAPI_API_KEY}}); }; # Check YahooJSON subtest 'YahooJSON' => sub { my @valid = (['100.00 USD', 'EUR'], ['1.00 GBP', 'IDR'], ['1.23 IDR', 'CAD'], ['10.00 AUD', 'AUD']); my @invalid = (['20.12 ZZZ', 'GBP']); module_check('YahooJSON', \@valid, \@invalid); }; # Check Failover subtest 'Failover' => sub { if ( not $ENV{TEST_FIXER_API_KEY} ) { plan skip_all => 'Set $ENV{TEST_FIXER_API_KEY} to run this test; get one at https://fixer.io'; } plan tests => 2; my $q = Finance::Quote->new('currency_rates' => {order => ['ECB', 'Fixer'], fixer => {API_KEY => $ENV{TEST_FIXER_API_KEY}}}); ok($q); my ($from, $to) = ('1000 KZT', 'JPY'); my $v = $q->currency($from, $to); ok(looks_like_number($v), "$from -> $to = $v"); }; Finance-Quote-1.65/t/xetra.t0000644000175000017500000000440415003302667015510 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @valid = ('DE0008404005', 'NL0011540547', 'FR0000120628', 'XS2630111719', 'NL0000009165'); my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $today = today(); my $window = 32; # XS2630111719 quotes are only updates 1-2 a month my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0]}, 'last' => sub {looks_like_number($_[0])}, 'volume' => sub {looks_like_number($_[0])}, 'price' => sub {looks_like_number($_[0])}, 'close' => sub {looks_like_number($_[0])}, 'change' => sub {looks_like_number($_[0])}, 'p_change' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, ); plan tests => 2 * (1 + %check * @valid + @invalid); my $q1 = Finance::Quote->new(); my %quotes1 = $q1->fetch('xetra', @symbols); my $q2 = Finance::Quote->new('XETRA', 'xetra' => {INST_ID => '0000003'}); my %quotes2 = $q2->fetch('xetra', @symbols); ok(%quotes1); ok(%quotes2); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes1{$symbol, $key}, $symbol, \%quotes1), "$symbol: $key -> $quotes1{$symbol, $key}"); ok($lambda->($quotes2{$symbol, $key}, $symbol, \%quotes2), "$symbol: $key -> $quotes2{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes1{'BOGUS', 'success'}), 'failed as expected'); ok((not $quotes2{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/yahooweb.t0000755000175000017500000000266715003302667016216 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @valid = qw/MSFT AMZN AAPL GOOGL GOOG META CSCO INTC CMCSA PEP BRK-A SEB NVR BKNG IBKR T/; my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $q = Finance::Quote->new('YahooWeb', timeout => 120); my $today = today(); my $window = 7; my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'symbol' => sub {$_[0] eq $_[1]}, 'last' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->yahooweb(@symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/treasurydirect.t0000644000175000017500000000260215003302667017434 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if ( not $ENV{"ONLINE_TEST"} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('TreasuryDirect'); my $year = ( localtime() )[5] + 1900; my $lastyear = $year - 1; my @symbols = qw/ 912810QT8 912810QY7 /; plan tests => 1 + 9*(1+$#symbols) + 3; my %quotes = $q->treasurydirect( @symbols, "BOGUS" ); ok(%quotes); foreach my $symbol (@symbols) { ok( $quotes{ $symbol, "success" }, "$symbol success" ); ok( $quotes{ $symbol, "symbol" } eq $symbol , "$symbol defined" ); ok( $quotes{ $symbol, "bid" } > 0, "$symbol returned bid" ); ok( $quotes{ $symbol, "ask" } > 0, "$symbol returned ask" ); ok( $quotes{ $symbol, "price" } > 0, "$symbol returned price" ); ok( $quotes{ $symbol, "rate" } =~ /^\d+\.\d+%$/, "$symbol returned rate" ); ok( $quotes{ $symbol, "isodate" } =~ /^\d{4}-\d{2}-\d{2}$/, "$symbol returned isodate" ); ok( substr( $quotes{ $symbol, "isodate" }, 0, 4 ) == $year || substr( $quotes{ $symbol, "isodate" }, 0, 4 ) == $lastyear ); ok( substr( $quotes{ $symbol, "date" }, 6, 4 ) == $year || substr( $quotes{ $symbol, "date" }, 6, 4 ) == $lastyear ); } is( $quotes{ "912810QT8", "currency" }, 'USD' ); is( $quotes{ "912810QY7", "currency" }, 'USD' ); ok( !$quotes{ "BOGUS", "success" } ); Finance-Quote-1.65/t/financeapi.t0000755000175000017500000000257215003302667016471 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @valid = qw/MSFT IBM F GE/; my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $q = Finance::Quote->new('FinanceAPI', timeout => 120); my $today = today(); my $window = 7; my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'symbol' => sub {$_[0] eq $_[1]}, 'last' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->financeapi(@valid); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/01-pod.t0000755000175000017500000000043515003302667015370 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; if (not $ENV{TEST_AUTHOR}) { plan( skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to true to run.'); } eval "use Test::Pod 1.00"; ## no critic plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); Finance-Quote-1.65/t/yahoojson.t0000644000175000017500000000443115003302667016376 0ustar bschuckbschuck#!/usr/bin/perl -w # A test script to check for working of the YahooJSON module. use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 58; my $q = Finance::Quote->new(); #List of stocks to fetch. Feel free to change this during testing my @stocks = ( "SUZLON.BO", "RECLTD.NS", "AMZN", "SOLB.BR", "^DJI", "BEL20.BR", "INGDIRECTFNE.BC", "AENA.MC", "CFR.JO" ); my %quotes = $q->fetch( "yahoo_json", @stocks ); ok( %quotes, "Data returned" ); foreach my $stock (@stocks) { my $name = $quotes{ $stock, "name" }; ok( $quotes{ $stock, "success" }, "Retrieved $stock" ); if ( !$quotes{ $stock, "success" } ) { my $errmsg = $quotes{ $stock, "errormsg" }; warn "Error Message:\n$errmsg\n"; } else { ok( $name, "Name is defined : $name" ); my $fetch_method = $quotes{ $stock, "method" }; ok( $fetch_method eq 'yahoo_json', "fetch_method is yahoo_json" ); my $last = $quotes{ $stock, "last" }; ok( $last > 0, "Last $last > 0" ); my $volume = $quotes{ $stock, "volume" }; ok( $volume > 0, "Volume $volume > 0" ) if !( grep { $_ eq $stock } ("BEL20.BR","INGDIRECTFNE.BC") ); my $type = $quotes{ $stock, "type" }; ok( $type, "Symbol type $type" ); #TODO: Add a test to raise a warning if the quote is excessively old my $isodate = $quotes{ $stock, "isodate" }; # print "ISOdate: $isodate "; my $date = $quotes{ $stock, "date" }; # currency for .BO stocks ok( $quotes { $stock, "currency" } eq 'INR', 'Bombay stocks have currency INR' ) if $stock =~ /\.BO$/ ; ok( $quotes { $stock, "currency" } eq 'EUR', 'Barcelona stocks have currency EUR' ) if $stock =~ /\.BC$/ ; ok( $quotes { $stock, "currency" } eq 'EUR', 'Madrid stocks have currency EUR' ) if $stock =~ /\.MC$/ ; # currency for .JO (Johannesburg Stock Exchange) stocks ok( $quotes { $stock, "currency" } eq 'ZAR', 'Johannesburg stocks have currency ZAR' ) if $stock =~ /\.JO$/ ; # print "Date: $date "; } } # Check that a bogus stock returns no-success. %quotes = $q->fetch( "yahoo_json", "BOGUS" ); ok( !$quotes{ "BOGUS", "success" }, "BOGUS failed" ); Finance-Quote-1.65/t/currencies.t0000755000175000017500000000076715003302667016542 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote::Currencies; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 1; my $known_currencies = eval { Finance::Quote::Currencies::known_currencies() }; my $live_currencies = eval { Finance::Quote::Currencies::fetch_live_currencies() }; is_deeply( $known_currencies , $live_currencies , "Stored currency list is up to date with live currency list" ); Finance-Quote-1.65/t/googleweb.t0000644000175000017500000000260715003302667016342 0ustar bschuckbschuck#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('GoogleWeb', timeout => 30); my @valid = ('AAPL', 'ESML', 'KNW', 'GE', 'NULC'); my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %check = ( 'currency' => sub { $_[0] =~ /^[A-Z]+$/ }, 'date' => sub { $_[0] =~ m{^[0-9]{2}/[0-9]{2}/[0-9]{4}$} }, 'isodate' => sub { $_[0] =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ }, 'last' => sub { $_[0] =~ /^[0-9.]+$/ }, 'method' => sub { $_[0] =~ /^googleweb$/ }, 'success' => sub { $_[0] }, 'symbol' => sub { $_[0] eq $_[1] }, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->fetch('googleweb', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol), "$key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/goldmoney.t0000755000175000017500000000401315003302667016361 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if ( not $ENV{ONLINE_TEST} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 46; # Test GoldMoney functions. my $q = Finance::Quote->new("GoldMoney"); foreach my $currency ( 'EUR', 'USD' ) { $q->set_currency($currency); my %quotes = $q->fetch( "goldmoney", "gold", "silver", "platinum", "BOGUS" ); ok(%quotes); # Check that sound information is returned for gold, silver and platinum. ok( $quotes{ "gold", "success" }, 'gold price lookup' ); ok( $quotes{ "gold", "last" } > 0, "Gold is quoted at " . $quotes{ "gold", "last" } ); ok( $quotes{ "gold", "currency" } eq $currency, "currency is $currency" ); ok( length( $quotes{ "gold", "date" } ) > 0 ); ok( length( $quotes{ "gold", "time" } ) > 0 ); ok( $quotes{ "silver", "success" }, 'silver price lookup' ); ok( $quotes{ "silver", "last" } > 0 ); ok( $quotes{ "silver", "currency" } eq $currency, "currency is $currency" ); ok( length( $quotes{ "silver", "date" } ) > 0 ); ok( length( $quotes{ "silver", "time" } ) > 0 ); ok( $quotes{ "platinum", "success" }, 'platinum price lookup' ); ok( $quotes{ "platinum", "last" } > 0 ); ok( $quotes{ "platinum", "currency" } eq $currency, "currency is $currency" ); ok( length( $quotes{ "platinum", "date" } ) > 0 ); ok( length( $quotes{ "platinum", "time" } ) > 0 ); my $year = ( localtime() )[5] + 1900; ok( ( substr( $quotes{ "gold", "isodate" }, 0, 4 ) == $year ) ); ok( ( substr( $quotes{ "gold", "date" }, 6, 4 ) == $year ) ); ok( ( substr( $quotes{ "silver", "isodate" }, 0, 4 ) == $year ) ); ok( ( substr( $quotes{ "silver", "date" }, 6, 4 ) == $year ) ); ok( ( substr( $quotes{ "platinum", "isodate" }, 0, 4 ) == $year ) ); ok( ( substr( $quotes{ "platinum", "date" }, 6, 4 ) == $year ) ); # Check that a bogus symbol returns no-success. ok( !$quotes{ "BOGUS", "success" } ); } Finance-Quote-1.65/t/tradegate.t0000644000175000017500000000455015003302667016327 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @valid = ('DE0008404005', 'NL0011540547', 'FR0000120628', 'XS2630111719', 'NL0000009165', 'GB00BLD4ZM24', 'FR0010510800'); my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $today = today(); my $window = 32; # XS2630111719 quotes are only updates 1-2 a month my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0]}, 'name' => sub {defined($_[0])}, 'last' => sub {looks_like_number($_[0])}, 'volume' => sub {looks_like_number($_[0])}, 'price' => sub {looks_like_number($_[0])}, 'close' => sub {looks_like_number($_[0])}, 'change' => sub {looks_like_number($_[0])}, 'p_change' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, ); plan tests => 2 * (1 + %check * @valid + @invalid); my $q1 = Finance::Quote->new(); my %quotes1 = $q1->fetch('tradegate', @symbols); my $q2 = Finance::Quote->new('Tradegate', 'tradegate' => {INST_ID => '0000003'}); my %quotes2 = $q2->fetch('tradegate', @symbols); ok(%quotes1); ok(%quotes2); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes1{$symbol, $key}, $symbol, \%quotes1), "$symbol: $key -> $quotes1{$symbol, $key}"); ok($lambda->($quotes2{$symbol, $key}, $symbol, \%quotes2), "$symbol: $key -> $quotes2{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes1{'BOGUS', 'success'}), 'failed as expected'); ok((not $quotes2{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/borsa_italiana.t0000644000175000017500000000215515003302667017336 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if ( not $ENV{"ONLINE_TEST"} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @bonds = ("IT0000966017", "IT0005592370", "IT0001086567", "IT0005534984"); plan tests => 1 + 8*(1+$#bonds) + 1; my $q = Finance::Quote->new(); my $year = ( localtime() )[5] + 1900; my $lastyear = $year - 1; my %quotes = $q->fetch("borsa_italiana", @bonds); ok(%quotes); foreach my $bound (@bonds) { ok( $quotes{$bound, "success"}, "$bound success" ); is( $quotes{$bound, "exchange"}, "Borsa Italiana", "$bound exchange correct" ); ok( $quotes{$bound, "name"}, "$bound name correct" ); is( $quotes{$bound, "symbol"}, $bound, "$bound symbol correct" ); ok( $quotes{$bound, "price"} > 0, "$bound price positive" ); ok( $quotes{$bound, "last"} > 0, "$bound last positive" ); is( $quotes{$bound, "method"}, "borsa_italiana", "$bound method correct" ); is( $quotes{$bound, "currency"}, "EUR", "$bound currency correct" ); } %quotes = $q->fetch( "borsa_italiana", "BOGUS" ); ok( !$quotes{ "BOGUS", "success" }, "BOGUS failed" ); Finance-Quote-1.65/t/aex.t0000755000175000017500000000333015003302667015142 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @valid = ('AD', 'AMG', 'LVMH', 'NL0000009165', 'BE0974293251'); my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $today = today(); my $window = 32; # XS0937858271 quotes are only updates 1-2 a month my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0]}, 'last' => sub {looks_like_number($_[0])}, 'volume' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, 'exchange' => sub {defined($_[0])}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->fetch('aex', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$symbol: $key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/tesouro_direto.t0000644000175000017500000000215115003302667017430 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if ( not $ENV{"ONLINE_TEST"} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @bounds = ("Tesouro Prefixado 2031", "Tesouro IPCA+ 2045"); plan tests => 1 + 8*(1+$#bounds) + 1; my $q = Finance::Quote->new(); my $year = ( localtime() )[5] + 1900; my $lastyear = $year - 1; my %quotes = $q->fetch("tesouro_direto", @bounds); ok(%quotes); foreach my $bound (@bounds) { ok( $quotes{$bound, "success"}, "$bound success" ); is( $quotes{$bound, "exchange"}, "Tesouro Direto", "$bound exchange correct" ); is( $quotes{$bound, "name"}, $bound, "$bound name correct" ); is( $quotes{$bound, "symbol"}, $bound, "$bound symbol correct" ); ok( $quotes{$bound, "price"} > 0, "$bound price positive" ); ok( $quotes{$bound, "last"} > 0, "$bound last positive" ); is( $quotes{$bound, "method"}, "tesouro_direto", "$bound method correct" ); is( $quotes{$bound, "currency"}, "BRL", "$bound currency correct" ); } %quotes = $q->fetch( "tesouro_direto", "BOGUS" ); ok( !$quotes{ "BOGUS", "success" }, "BOGUS failed" ); Finance-Quote-1.65/t/quote.t0000644000175000017500000000267715003302667015534 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; plan tests => 28; my $q = Finance::Quote->new(); # test isoTime function ok($q->isoTime("11:39PM") eq "23:39") ; ok($q->isoTime("9:10 AM") eq "09:10") ; ok($q->isoTime("1.32") eq "01:32") ; ok($q->isoTime("1u32") eq "01:32") ; ok($q->isoTime("19h2") eq "19:02") ; ok($q->isoTime("10:62") eq "00:00" ) ; ok($q->isoTime("8:05am") eq "08:05" ) ; ok($q->isoTime("4:00pm") eq "16:00" ) ; ok($q->isoTime("0:59PM") eq "12:59" ) ; ok($q->isoTime("12:00pm") eq "12:00" ) ; ok($q->isoTime("12:10pm") eq "12:10" ) ; # yahoo might return 12:XXPM ! # decimal_shiftup() is($q->decimal_shiftup('1',1), '10'); is($q->decimal_shiftup('1',2), '100'); is($q->decimal_shiftup('1.',1), '10'); is($q->decimal_shiftup('1.',2), '100'); is($q->decimal_shiftup('1.5',1), '15'); is($q->decimal_shiftup('1.5',2), '150'); is($q->decimal_shiftup('1.5',3), '1500'); is($q->decimal_shiftup('56',1), '560'); is($q->decimal_shiftup('56',2), '5600'); is($q->decimal_shiftup('56.00',-1), '5.600'); # we want to keep precision is($q->decimal_shiftup('56.00',1), '560.0'); is($q->decimal_shiftup('1.2345678901234',3), '1234.5678901234'); is($q->decimal_shiftup('0.12345678',1), '1.2345678'); is($q->decimal_shiftup('0.00001',1), '0.0001'); # _B_to_billions() is($q->B_to_billions('1B'), '1000000000'); is($q->B_to_billions('1.5B'), '1500000000'); is($q->B_to_billions('1.23456789876B'), '1234567898.76'); Finance-Quote-1.65/t/bseindia.t0000755000175000017500000000202415003302667016142 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } # Test BSEIndia functions. my $q = Finance::Quote->new(); my @stocks = ("532540", "INE009A01021", "INE062A01020"); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; # Tests: fetch, fetch BOGUS, and 5 per stock plan tests => 2 + 5*@stocks; my %quotes = $q->fetch("bseindia", @stocks); ok(%quotes); # Check that the name and last are defined for all of the stocks. foreach my $stock (@stocks) { ok($quotes{$stock, "last"} > 0); ok($quotes{$stock, "success"}); ok($quotes{$stock, "currency"} eq "INR"); ok(substr($quotes{$stock,"isodate"},0,4) == $year || substr($quotes{$stock,"isodate"},0,4) == $lastyear); ok(substr($quotes{$stock,"date"},6,4) == $year || substr($quotes{$stock,"date"},6,4) == $lastyear); } # Check that a bogus fund returns no-success. %quotes = $q->fetch("bseindia", "BOGUS"); ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/deka.t0000755000175000017500000000170615003302667015276 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 13; my $q = Finance::Quote->new("Deka"); $q->timeout(60); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my @stocks = ("DE0008474511","LU0051755006"); my %quotes = $q->deka(@stocks, "BOGUS"); ok(%quotes); foreach my $stock (@stocks) { ok($quotes{$stock,"success"}); ok($quotes{$stock,"last"} > 0); ok(substr($quotes{$stock,"isodate"},0,4) == $year || substr($quotes{$stock,"isodate"},0,4) == $lastyear); ok(substr($quotes{$stock,"date"},6,4) == $year || substr($quotes{$stock,"date"},6,4) == $lastyear); } ok($quotes{"DE0008474511","currency"} eq "EUR"); ok($quotes{"LU0051755006","currency"} eq "USD"); # Check that a bogus fund returns no-success. ok($quotes{"BOGUS","success"} == 0); ok($quotes{"BOGUS","errormsg"} eq "No data returned"); Finance-Quote-1.65/t/nzx.t0000755000175000017500000000431315003302667015206 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use DateTime qw(now); use DateTime::Duration; use DateTime::Format::ISO8601; use Date::Manip; use Scalar::Util qw(looks_like_number); if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('MNW' => 'Manawa Energy Limited Ordinary Shares'); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'nzx'; # Name of the target method for testing my $currency = 'NZD'; # expected quote curreny my $today = DateTime->now()->set_time_zone('Pacific/Auckland'); # together with $window, validate date/isodate my $window = $today - DateTime::Duration->new(days => 7); # quote must be within last $window days my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'last' => sub {looks_like_number($_[0])}, 'isin' => sub {$_[0] =~ /^[A-Z0-9]{12}$/}, 'name' => sub {$_[0] eq $valid{$_[1]}}, 'currency' => sub {$_[0] eq $currency}, 'isodate' => sub {DateTime->compare($window, DateTime::Format::ISO8601->parse_datetime($_[0])) < 0}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;} ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/tiaacref.t0000755000175000017500000000354415003302667016152 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; use Time::Piece; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 91; # Test TIAA-CREF functions. my $q = Finance::Quote->new(); my $year = localtime()->year; my $lastyear = $year - 1; my @symbols = do { no warnings 'qw'; qw/ QCBMIX TEMLX TLFIX TSBPX W156# W323# W464# W719# /}; ok( my %quotes = $q->tiaacref( @symbols, 'BOGUS' ), 'retrieved quotes' ); for my $symbol (@symbols) { # the following labels are expected to be supplied: # symbol, nav, currency, method, exchange, price, date, isodate ok( $quotes{$symbol,"success"} > 0, "$symbol got retrieved" ); ok( $quotes{$symbol,"symbol"} eq $symbol, "$symbol has matching symbol" ); ok( $quotes{$symbol,"nav"} > 0, "$symbol has a NAV" ); ok( $quotes{$symbol,"nav"} =~ /^[\d\.]+$/, "$symbol NAV looks numeric" ); ok( $quotes{$symbol,"currency"} eq "USD", "$symbol currency is valid" ); ok( $quotes{$symbol,"method"} eq 'tiaacref', "$symbol has matching method" ); ok( $quotes{$symbol,"exchange"} eq 'TIAA', "$symbol has matching exchange" ); ok( length $quotes{$symbol,"name"}, "$symbol has defined name" ); ok( $quotes{$symbol,"price"} == $quotes{$symbol,'nav'}, "$symbol price == NAV" ); ok( substr($quotes{$symbol,"isodate"}, 0, 4) == $year || substr($quotes{$symbol,"isodate"}, 0, 4) == $lastyear, "$symbol isodate is recent" ); ok( substr($quotes{$symbol,"date"}, 6, 4) == $year || substr($quotes{$symbol,"date"}, 6, 4) == $lastyear, "$symbol date is recent" ); }; ok( $quotes{"BOGUS","success"} == 0, "BOGUS failed" ); ok( length $quotes{"BOGUS","errormsg"}, "BOGUS returned error message" ); Finance-Quote-1.65/t/ukfunds.t0000644000175000017500000000143715003302667016047 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 8; my $q = Finance::Quote->new(); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %quotes = $q->ukfunds("GB0031835118","GB0030880032","LU0116926998","BOGUS"); ok(%quotes); # Check the last values are defined. These are the most # used and most reliable indicators of success. ok($quotes{"GB0031835118","last"} > 0); ok($quotes{"GB0031835118","success"}); ok($quotes{"GB0030880032","last"} > 0); ok($quotes{"GB0030880032","success"}); ok($quotes{"LU0116926998","last"} > 0); ok($quotes{"LU0116926998","success"}); # Check that bogus stocks return failure: ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/usfedbonds.t0000755000175000017500000000322715003302667016526 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('E197001.200606' => '137.00', 'E194112.200610' => '94.35', 'E194105.200610' => '90.59', 'S196712.202006' => '156.28'); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'usfedbonds'; # Name of the target method for testing my $currency = 'USD'; # expected quote curreny my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'price' => sub {looks_like_number($_[0]) && $_[0] eq $valid{$_[1]}}, 'currency' => sub {$_[0] eq $currency}, 'date' => sub {$_[0] =~ m,^[0-9]{2}/[0-9]{2}/[0-9]{4},}, 'isodate' => sub {$_[0] =~ m,[0-9]{4}-[0-9]{2}-[0-9]{2},}, 'success' => sub {$_[0] == 1}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/bourso.t0000755000175000017500000001033515003302667015701 0ustar bschuckbschuck#!/usr/bin/perl -w use constant DEBUG => $ENV{DEBUG}; use if DEBUG, Smart::Comments; use strict; use Test::More; use Finance::Quote; use Scalar::Util qw(looks_like_number); use Date::Simple qw(today); use Date::Range; use Date::Manip; if ( not $ENV{ONLINE_TEST} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } # Bourso tests need to cover all the possible cases: # # Name What Test Case # # action Stock 1rPAF, MSFT, FF11-SOLB, 1rPSOLB, 1rPCNP # obligation Bond 1rPFR0010371401 # opcvm Fund MP-802941 # warrant Warrant 1rAHX70B - expired & removed from tests # indice Index 1rPCAC # tracker Tracker 1rTBX4 my %valid = ('MP-802941' => {currency => 'EUR', days => 32, name => 'Covéa Actions Asie C'}, # Fund, EUR '1rPAF' => {currency => 'EUR', days => 7, name => 'AIR FRANCE-KLM'}, # Stock, EUR, Euronext Paris 'MSFT' => {currency => 'USD', days => 7, name => 'MICROSOFT'}, # Stock, USD, NASDAQ 'FF11-SOLB' => {currency => 'EUR', days => 7, name => 'SOLVAY'}, # Stock, EUR, Euronext Bruxelles '2rPDE000CX0QLH6' => {currency => 'EUR', days => 7, name => 'GOLD/CITI WT OPEN'}, # Warrant '1rPFR0010371401' => {currency => '' , days => 100, name => 'FRENCH REPUBLIC 4% 25/10/38 EUR'}, # Bond, EUR, Euronext Paris, '1rPCAC' => {currency => 'Pts', days => 7, name => 'CAC 40'}, # Index, Pts, Paris, '1rTBX4' => {currency => 'EUR', days => 7, name => 'Lyxor CAC 40 Daily Double Short UCITS ETF - Acc'}, # Tracker, EUR 'FR0010037341' => {currency => 'EUR', days => 7, name => 'CM-AM Europe Growth RC'}, # Added to check for spaces in quote ); my %invalid = ('BOGUS' => undef); my @symbols = (keys %valid, keys %invalid); my $today = today(); my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'name' => sub {$valid{$_[1]}{name} eq $_[0]}, # @_ = (value, symbol) 'currency' => sub {$valid{$_[1]}{currency} eq $_[0]}, # 'method' => sub {$_[0] eq 'bourso'}, # 'success' => sub {$_[0]}, # 'volume' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # volume is optional 'close' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # close is optional 'last' => sub {looks_like_number($_[0])}, # last is REQUIRED 'high' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # high is optional 'low' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # low is optional 'net' => sub {defined $_[0] ? looks_like_number($_[0]) : 1}, # net is optional 'exchange' => sub {defined $_[0] ? $_[0] =~ /^[A-Z]+$/ : 1}, # exchange is optional 'isodate' => sub {Date::Range->new($today - $valid{$_[1]}{days}, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + %invalid; my %quotes = $q->fetch('bourso', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ### check key: $key ### check res: $quotes{$symbol, $key} ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), defined $quotes{$symbol, $key} ? "$key -> $quotes{$symbol, $key}" : "$key -> "); } } foreach my $symbol (keys %invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/morningstarUK.t0000644000175000017500000000155215003302667017171 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 6; my $q = Finance::Quote->new(); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; # my %quotes = $q->morningstaruk("GB0031835118","GB0030880032","BOGUS"); my %quotes = $q->morningstaruk("GB00B61M9437","GB00B8H99P30","BOGUS"); ok(%quotes); ### quotes : %quotes # Check the last values are defined. These are the most # used and most reliable indicators of success. ok($quotes{"GB00B61M9437","last"} > 0); ok($quotes{"GB00B61M9437","success"}); ok($quotes{"GB00B8H99P30","last"} > 0); ok($quotes{"GB00B8H99P30","success"}); # Check that bogus stocks return failure: ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/seb.t0000755000175000017500000000235515003302667015144 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new(); my @valid = ("SEB Auto Hållbar 100", "SEB Life - Ethical Global Index"); my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; plan tests => 1 + 5*@valid + @invalid; my %quotes = $q->seb_funds(@symbols); ok(%quotes); ### quotes : %quotes foreach my $symbol (@valid) { ok($quotes{$symbol, 'success'}, "$symbol success"); ok($quotes{$symbol, 'price'} > 0, "$symbol returned price as $quotes{$symbol, 'price'}"); ok(defined $quotes{$symbol, 'name'}, "$symbol returned name as $quotes{$symbol, 'name'}"); ok($quotes{$symbol, 'currency'} eq "SEK", "$symbol returned currency as $quotes{$symbol, 'currency'}"); ok((substr($quotes{$symbol, 'isodate'}, 0, 4) == $year or substr($quotes{$symbol, 'isodate'}, 0, 4) == $lastyear), "$symbol returned isodate as $quotes{$symbol, 'isodate'}"); } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/marketwatch.t0000644000175000017500000000260015003302667016673 0ustar bschuckbschuck#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('MarketWatch', timeout => 30); my @valid = ('AAPL', 'NXPI', 'PCAR'); my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %check = ( 'currency' => sub { $_[0] =~ /^[A-Z]+$/ }, 'date' => sub { $_[0] =~ m{^[0-9]{2}/[0-9]{2}/[0-9]{4}$} }, 'isodate' => sub { $_[0] =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ }, 'last' => sub { $_[0] =~ /^[0-9.]+$/ }, 'method' => sub { $_[0] =~ /^marketwatch$/ }, 'success' => sub { $_[0] }, 'symbol' => sub { $_[0] eq $_[1] }, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->fetch('marketwatch', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol), "$key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/fq-class-methods.t0000644000175000017500000000236215003302667017540 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; plan tests => 8; use Finance::Quote; ok(1, "Finance::Quote loaded"); my @target = qw/last high low net bid ask close open day_range year_range eps div cap nav price/; my @result = Finance::Quote::get_default_currency_fields(); ok( join(",", sort @target) eq join(",", sort @result), "get_default_currency_fields check"); my $timeout = Finance::Quote::get_default_timeout(); ok( !defined $timeout, "check default timeout is undef"); my @methods = Finance::Quote::get_methods(); ok( grep( /alphavantage/, @methods), "check for a known method"); # new tested in fq-object-methods.t ok( Finance::Quote::scale_field("1023","0.01") eq "10.23", "check scale_field"); Finance::Quote::set_default_timeout(4); ok( Finance::Quote::get_default_timeout() == 4, "check set/get default timeout"); my $t4 = Finance::Quote->new(); ok( $t4->get_timeout() == 4, "check default timeout was used"); my %features = Finance::Quote::get_features(); ### [] features: %features ok(exists $features{'quote_methods'} and exists $features{'quote_modules'} and exists $features{'currency_modules'} and exists $features{'parameters'}, "features keys"); Finance-Quote-1.65/t/currency-openexchange.t0000644000175000017500000000326315003302667020663 0ustar bschuckbschuck#!/usr/bin/perl -w use constant DEBUG => $ENV{DEBUG}; use if DEBUG, Smart::Comments, '###'; use strict; use Test::More; use Finance::Quote; use Scalar::Util qw(looks_like_number); if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } sub module_check { my ($module, $valid, $invalid, $options) = @_; plan tests => 1 + 2*@{$valid} + @{$invalid}; my $hash = {order => [$module]}; $hash->{lc($module)} = $options if defined $options; my $q = Finance::Quote->new('currency_rates' => $hash); ok($q); foreach my $test (@{$valid}) { my ($from, $to) = @{$test}; my $v = $q->currency($from, $to); ok(looks_like_number($v), "$from -> $to = $v"); my ($from_amount, $from_code) = $from =~ m/^([0-9.]+) ([A-Z]+)$/; SKIP: { skip "identity check because different currencies", 1 unless $from_code eq $to; my ($suffix) = $from_amount =~ m/[.]([0-9]+)/; my $string = sprintf("%.*f", length($suffix), $v); ok($from_amount eq $string, "identity check"); }; } foreach my $test (@{$invalid}) { my ($from, $to) = @{$test}; my $v = $q->currency($from, $to); is($v, undef, "$from -> $to failed as expected"); } } plan tests => 1; subtest 'OpenExchange' => sub { if ( not $ENV{TEST_OPENEXCHANGE_API_KEY} ) { plan skip_all => 'Set $ENV{TEST_OPENEXCHANGE_API_KEY} to run this test; get one at https://openexchangerates.org'; } my @valid = (['100.00 USD', 'EUR'], ['1.00 GBP', 'IDR'], ['1.23 IDR', 'CAD'], ['10.00 AUD', 'AUD']); my @invalid = (['20.12 ZZZ', 'GBP']); module_check('OpenExchange', \@valid, \@invalid, {cache => 1, API_KEY => $ENV{TEST_OPENEXCHANGE_API_KEY}}); }; Finance-Quote-1.65/t/comdirect.t0000755000175000017500000000437515003302667016350 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('VWAGY' => 'Volkswagen', 'Volkswagen' => 'Volkswagen', 'DE0007664039' => 'Volkswagen', 'FR0010510800' => 'Amundi EUR Overnight Return UCITS ETF' ); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'comdirect'; # Name of the target method for testing my $currency = 'EUR'; # expected quote curreny my $today = today(); # together with $window, validate date/isodate my $window = 7; # quote must be within last $window days my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'method' => sub {$_[0] eq 'comdirect'}, 'open' => sub {not defined $_[0] or looks_like_number($_[0])}, 'low' => sub {not defined $_[0] or looks_like_number($_[0])}, 'high' => sub {not defined $_[0] or looks_like_number($_[0])}, 'last' => sub {not defined $_[0] or looks_like_number($_[0])}, 'currency' => sub {$_[0] =~ /^[A-Z]{3}$/}, 'name' => sub {$_[0] =~ $valid{$_[1]}}, 'isin' => sub {$_[0] =~ /^[A-Z0-9]{12}$/}, 'isodate' => sub {defined $_[0] and Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/sinvestor.t0000644000175000017500000000433415003302667016423 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my @valid = ('DE0008404005', 'NL0011540547', 'FR0000120628', 'DE0009809566', 'DE0008491002'); my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $today = today(); my $window = 32; my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0]}, 'last' => sub {looks_like_number($_[0])}, 'volume' => sub {looks_like_number($_[0])}, 'price' => sub {looks_like_number($_[0])}, 'close' => sub {looks_like_number($_[0])}, 'change' => sub {looks_like_number($_[0])}, 'p_change' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, ); plan tests => 2 * (1 + %check * @valid + @invalid); my $q1 = Finance::Quote->new(); my %quotes1 = $q1->fetch('sinvestor', @symbols); my $q2 = Finance::Quote->new('Sinvestor', 'sinvestor' => {INST_ID => '0000003'}); my %quotes2 = $q2->fetch('sinvestor', @symbols); ok(%quotes1); ok(%quotes2); ### [] quotes: %quotes1 foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes1{$symbol, $key}, $symbol, \%quotes1), "$symbol: $key -> $quotes1{$symbol, $key}"); ok($lambda->($quotes2{$symbol, $key}, $symbol, \%quotes2), "$symbol: $key -> $quotes2{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes1{'BOGUS', 'success'}), 'failed as expected'); ok((not $quotes2{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/asegr.t0000755000175000017500000000464415003302667015477 0ustar bschuckbschuck#!/usr/bin/perl -w use constant DEBUG => $ENV{DEBUG}; use if DEBUG, Smart::Comments; use strict; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %skip = ('bond' => {'high' => 1, 'low' => 1}, 'derivative' => {'isin' => 1}, 'index' => {'isin' => 1}); my %valid = ('CPI' => 'stock', 'KYLO' => 'stock', 'ALPHA25O2.20' => 'derivative', 'AETF' => 'etf', 'ELHAB1' => 'bond', 'OPAPB2' => 'bond', 'FTSE' => 'index', 'GEKTERNAB3' => 'bond' ); my %invalid = ('BOGUS' => undef); my @symbols = (keys %valid, keys %invalid); my $today = today(); my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'close' => sub {looks_like_number($_[0])}, 'volume' => sub {looks_like_number($_[0])}, 'high' => sub {looks_like_number($_[0])}, 'low' => sub {looks_like_number($_[0])}, 'isin' => sub {defined $_[0]}, 'isodate' => sub {Date::Range->new($today - 7, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + %invalid; my %quotes = $q->asegr(@symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { ### [] Testing Symbol: $symbol while (my ($key, $lambda) = each %check) { ### [] Test: $key SKIP: { skip "$key not required for $symbol", 1 if exists $skip{$valid{$symbol}}->{$key}; ok((defined $quotes{$symbol, $key} and $lambda->($quotes{$symbol, $key}, $symbol, \%quotes)), (defined $quotes{$symbol, $key} ? "$key -> $quotes{$symbol, $key}" : "$key -> ")); }; } } foreach my $symbol (keys %invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/alphavantage.t0000755000175000017500000000416315003302667017025 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if ( not $ENV{"ONLINE_TEST"} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } if ( not $ENV{"TEST_ALPHAVANTAGE_API_KEY"} ) { plan skip_all => 'Set $ENV{TEST_ALPHAVANTAGE_API_KEY} to run this test; get one at https://www.alphavantage.co'; } my $q = Finance::Quote->new('AlphaVantage', 'alphavantage' => {'API_KEY' => $ENV{"TEST_ALPHAVANTAGE_API_KEY"}} ); my $year = ( localtime() )[5] + 1900; my $lastyear = $year - 1; my @symbols = qw/ BP.L CSCO DIVO11.SA ERCB.DE IBM MRT-UN.TRT SAP.DE SOLB.BR TD.TO /; plan tests => 1 + 11*(1+$#symbols) + 10; my %quotes = $q->alphavantage( @symbols, "BOGUS" ); ok(%quotes); foreach my $symbol (@symbols) { ok( $quotes{ $symbol, "success" }, "$symbol success" ); ok( $quotes{ $symbol, "symbol" } eq $symbol , "$symbol defined" ); ok( $quotes{ $symbol, "open" } > 0, "$symbol returned open" ); ok( $quotes{ $symbol, "close" } > 0, "$symbol returned close" ); ok( $quotes{ $symbol, "last" } > 0, "$symbol returned last" ); ok( $quotes{ $symbol, "high" } > 0, "$symbol returned high" ); ok( $quotes{ $symbol, "low" } > 0, "$symbol returned low" ); ok( $quotes{ $symbol, "volume" } >= 0, "$symbol returned volume" ); ok( $quotes{ $symbol, "p_change" } =~ /^[\-\.\d]+$/, "$symbol returned p_change" ); ok( substr( $quotes{ $symbol, "isodate" }, 0, 4 ) == $year || substr( $quotes{ $symbol, "isodate" }, 0, 4 ) == $lastyear ); ok( substr( $quotes{ $symbol, "date" }, 6, 4 ) == $year || substr( $quotes{ $symbol, "date" }, 6, 4 ) == $lastyear ); } is( $quotes{ "BP.L", "currency" }, 'GBP' ); is( $quotes{ "CSCO", "currency" }, 'USD' ); is( $quotes{ "DIVO11.SA", "currency" }, 'BRL' ); is( $quotes{ "ERCB.DE", "currency" }, 'EUR' ); is( $quotes{ "IBM", "currency" }, 'USD' ); is( $quotes{ "MRT-UN.TRT", "currency" }, 'CAD' ); is( $quotes{ "SAP.DE", "currency" }, 'EUR' ); is( $quotes{ "SOLB.BR", "currency" }, 'EUR' ); is( $quotes{ "TD.TO", "currency" }, 'CAD' ); ok( !$quotes{ "BOGUS", "success" } ); Finance-Quote-1.65/t/union.t0000755000175000017500000000165315003302667015523 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 7; # Test TD Waterhouse functions. my $q = Finance::Quote->new(); my %quotes = $q->unionfunds("DE0008491002","12345"); ok(%quotes); # Check the last values are defined. These are the most # used and most reliable indicators of success. ok($quotes{"DE0008491002","last"} > 0); ok($quotes{"DE0008491002","success"}); ok($quotes{"DE0008491002", "currency"} eq "EUR"); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; ok(substr($quotes{"DE0008491002","isodate"},0,4) eq $year || substr($quotes{"DE0008491002","isodate"},0,4) eq $lastyear); ok(substr($quotes{"DE0008491002","date"},6,4) eq $year || substr($quotes{"DE0008491002","date"},6,4) eq $lastyear); # Check that bogus stocks return failure: ok(! $quotes{"12345","success"}); Finance-Quote-1.65/t/indiamutual.t0000755000175000017500000000205315003302667016702 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } # Test IndiaMutual functions. my $q = Finance::Quote->new(); my @funds = ("102676", "103131", "148181", "INF194K01W88", "INF090I01FN7", "INF082J01127"); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; plan tests => 6*@funds + 2; my %quotes = $q->fetch("indiamutual", @funds); ok(%quotes); # Check that the name and nav are defined for all of the funds. foreach my $fund (@funds) { ok($quotes{$fund,"nav"} > 0); ok(length($quotes{$fund,"name"})); ok($quotes{$fund,"success"}); ok($quotes{$fund, "currency"} eq "INR"); ok(substr($quotes{$fund,"isodate"},0,4) == $year || substr($quotes{$fund,"isodate"},0,4) == $lastyear); ok(substr($quotes{$fund,"date"},6,4) == $year || substr($quotes{$fund,"date"},6,4) == $lastyear); } # Check that a bogus fund returns no-success. %quotes = $q->fetch("indiamutual", "BOGUS"); ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/tmx.t0000755000175000017500000000337115003302667015202 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Scalar::Util qw(looks_like_number); if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('STN' => 'Stantec Inc.', 'BCE' => 'BCE Inc.', 'BMO' => 'Bank of Montreal', 'HBI:US' => 'Hanesbrands Inc.' ); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'tmx'; # Name of the target method for testing my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'name' => sub {$_[0] eq $valid{$_[1]}}, 'year_range' => sub {$_[0] =~ /[0-9.]+ - [0-9.]+/}, 'exchange' => sub {$_[0] eq 'Toronto Stock Exchange' || $_[0] eq 'New York Stock Exchange'}, 'symbol' => sub {$_[0] =~ /^$_[1](:CA)?$/}, 'high' => sub {looks_like_number($_[0])}, 'low' => sub {looks_like_number($_[0])}, 'open' => sub {looks_like_number($_[0])}, 'close' => sub {looks_like_number($_[0])}, 'currency' => sub {$_[0] eq 'CAD' || $_[0] eq 'USD'}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/bloomberg.t0000755000175000017500000000202515003302667016335 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{'ONLINE_TEST'}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('Bloomberg'); my @valid = qw/MSFT:US AMZN:US AAPL:US GOOGL:US META:US FOLSHM1:LN UKX:IND/; my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; plan tests => 1 + 4*@valid + @invalid; my %quotes = $q->bloomberg(@symbols); ok(%quotes); foreach my $symbol (@valid) { ok($quotes{$symbol, 'success'}, "$symbol success"); ok($quotes{$symbol, 'symbol'} eq $symbol, "$symbol defined"); ok($quotes{$symbol, 'last'} > 0, "$symbol returned last as $quotes{$symbol, 'last'}"); ok((substr($quotes{$symbol, 'isodate'}, 0, 4) == $year or substr($quotes{$symbol, 'isodate'}, 0, 4) == $lastyear), "$symbol returned isodate as $quotes{$symbol, 'isodate'}"); } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/hu.t0000644000175000017500000000511715003302667015003 0ustar bschuckbschuck#!/usr/bin/perl -w # # HU.pm # # Version 0.1 - test of Hungarian (HU) F::Q # This version based on za.t module # # Zoltan Levardy # 2009 # 2019-06-22: Removed failing fund, replaced with HU0000705280. # Surrounded failing equity check for OTP in a TODO block. # Bruce Schuck use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if ( not $ENV{ONLINE_TEST} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 21; # Test za functions. my $q = Finance::Quote->new("HU"); my $year = ( localtime() )[5] + 1900; my $lastyear = $year - 1; # getting quotes for shares by ticker (OTP, MTELEKOM) # funds by ISIN (HU0000702709,HU0000706437) # and finally an incorrect ticker/isin is ZOL, must fail. my %quotes = $q->hu( "OTP", "HU0000705280", "HU0000702709", "ZOL" ); ok(%quotes); ### quotes : %quotes # Check that the last and date values are defined. ok( $quotes{ "OTP", "success" } ); ok( $quotes{ "OTP", "last" } > 0 ); ok( length( $quotes{ "OTP", "date" } ) > 0 ); ok( substr( $quotes{ "OTP", "isodate" }, 0, 4 ) == $year || substr( $quotes{ "OTP", "isodate" }, 0, 4 ) == $lastyear ); ok( substr( $quotes{ "OTP", "date" }, 6, 4 ) == $year || substr( $quotes{ "OTP", "date" }, 6, 4 ) == $lastyear ); ok( $quotes{ "OTP", "currency" } eq "HUF" ); # MKB HUF Liquidity Fund: HU0000705280 ok( $quotes{ "HU0000705280", "success" } ); ok( $quotes{ "HU0000705280", "last" } > 0 ); ok( length( $quotes{ "HU0000705280", "date" } ) > 0 ); ok( substr( $quotes{ "HU0000705280", "isodate" }, 0, 4 ) == $year || substr( $quotes{ "HU0000705280", "isodate" }, 0, 4 ) == $lastyear ); ok( substr( $quotes{ "HU0000705280", "date" }, 6, 4 ) == $year || substr( $quotes{ "HU0000705280", "date" }, 6, 4 ) == $lastyear ); ok( $quotes{ "HU0000705280", "currency" } eq "HUF" ); # Fund: Budapest II, isin: HU0000702709 ok( $quotes{ "HU0000702709", "success" } ); ok( $quotes{ "HU0000702709", "last" } > 0 ); ok( length( $quotes{ "HU0000702709", "date" } ) > 0 ); ok( substr( $quotes{ "HU0000702709", "isodate" }, 0, 4 ) == $year || substr( $quotes{ "HU0000702709", "isodate" }, 0, 4 ) == $lastyear ); ok( substr( $quotes{ "HU0000702709", "date" }, 6, 4 ) == $year || substr( $quotes{ "HU0000702709", "date" }, 6, 4 ) == $lastyear ); ok( $quotes{ "HU0000702709", "currency" } eq "HUF" ); # Check that a ZOL fund returns no-success. ok( !$quotes{ "ZOL", "success" } ); ok( $quotes{ "ZOL", "errormsg" } eq "Fetch from bse or bamosz failed" ); Finance-Quote-1.65/t/fq-object-methods.t0000644000175000017500000000525615003302667017706 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; if (not $ENV{'ONLINE_TEST'}) { plan tests => 16; } else { plan tests => 23; } my $q; my $result; use Finance::Quote; ok(1, "Finance::Quote loaded"); if ( $ENV{'ONLINE_TEST'}) { $q = Finance::Quote->new('YahooJSON'); ok( $q, "specific module constructor"); $result = $q->fetch("yahoo_json", "IBM"); ok( (ref $result eq "HASH" and exists $result->{'IBM','success'}), "check fetch on specific module"); $result = $q->fetch("usa", "IBM"); ok( !defined $result, "check fetch on wrong specific module"); $q = Finance::Quote->new(); ok( $q, "bare constructor"); $result = $q->fetch("usa", "IBM"); ok( (ref $result eq "HASH" and exists $result->{'IBM','success'}), "check fetch on specific module"); } $q = Finance::Quote->new(); ok( $q->B_to_billions("1.234B") eq "1234000000", "B_to_billions check"); ok( $q->decimal_shiftup("6.789", 2) eq "678.9", "decimal_shiftup test"); ok( $q->get_failover(), "check default failover"); $q->set_failover(0); ok( !$q->get_failover(), "check set/get failover"); $q = Finance::Quote->new(failover => 0); ok( !$q->get_failover(), "check failover for named argument constructor"); $q = Finance::Quote->new(); ok( !defined $q->get_fetch_currency(), "default currency is not defined"); $q->set_fetch_currency('aud'); ok( 'aud' eq $q->get_fetch_currency(), "check set/get currency"); $q = Finance::Quote->new(fetch_currency => 'usd'); ok( 'usd' eq $q->get_fetch_currency(), "check named parameter fetch_currency"); $q = Finance::Quote->new(); ok( 0 == @{$q->get_required_labels()}, "check default required labels"); my $labels = ['close', 'isodate', 'last']; $q->set_required_labels($labels); ok( join(",", sort @{$labels}) eq join(",", @{$q->get_required_labels()}), "check set/get required_labels"); if ( $ENV{'ONLINE_TEST'}) { $result = $q->fetch("yahoo_json", "IBM"); ok( (ref $result eq "HASH" and exists $result->{'IBM','success'}), "check fetch on specific module"); } $q = Finance::Quote->new(required_labels => ['does-not-exist']); ok( 'does-not-exist' eq join(",", @{$q->get_required_labels()}), "check set/get required_labels"); if ( $ENV{'ONLINE_TEST'}) { $result = $q->fetch("usa", "IBM"); ok( (ref $result eq "HASH" and !%{$result}), "check required_labels is enforeced"); } $q = Finance::Quote->new(); ok( !defined $q->get_timeout(), "check default timeout"); $q->set_timeout(123); ok( 123 == $q->get_timeout(), "check set/get timeout"); $q = Finance::Quote->new(timeout => 456); ok( 456 == $q->get_timeout(), "check timeout as named parameter"); print ref $q->get_user_agent(), "\n"; ok( 'LWP::UserAgent' eq ref $q->get_user_agent(), "check get_user_agent"); Finance-Quote-1.65/t/stockdata.t0000644000175000017500000000262115003302667016341 0ustar bschuckbschuck#!/usr/bin/perl -w # vi: set ts=2 sw=2 noai ic showmode showmatch: use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } if ( not $ENV{"TEST_STOCKDATA_API_KEY"} ) { plan skip_all => 'Set $ENV{"TEST_STOCKDATA_API_KEY"} to run this test'; } my @valid = qw/CSCO F GE SWAV WM/; my @invalid = ('BUGUS'); my $q = Finance::Quote->new('StockData', timeout => 30); my %check = ( 'currency' => sub { $_[0] =~ /^[A-Z]+$/ }, 'date' => sub { $_[0] =~ m{^[0-9]{2}/[0-9]{2}/[0-9]{4}$} }, 'isodate' => sub { $_[0] =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ }, 'last' => sub { $_[0] =~ /^[0-9.]+$/ }, 'method' => sub { $_[0] =~ /^stockdata$/ }, 'success' => sub { $_[0] }, 'symbol' => sub { $_[0] eq $_[1] }, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->fetch('stockdata', @valid); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol), "$key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/morningstarJP.t0000644000175000017500000000353015003302667017161 0ustar bschuckbschuck#!/usr/bin/perl -w use utf8; use open ":std", ":encoding(UTF-8)"; use strict; use Test::More; use DateTime; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 9 * 2; # Find out today my $calcDay = DateTime->now(); my $year = $calcDay->year(); my $month = $calcDay->month(); my $day = $calcDay->day(); # Test Morningstar JP functions. my $q = Finance::Quote->new(); my %valid = ( "2009100101" => "HSBC インド・インフラ株式オープン", "2002013108" => "HSBC チャイナオープン"); my @funds = keys %valid; my %info = $q->morningstarjp(@funds); ok(%info); # Check that the symbol/name, date, currency and nav defined for all of the funds. foreach my $fund (@funds) { # NAV date should be within 10 days of today, but we will allow +- 1 day # on top of that for running tests outside of Asia/Tokyo timezone my $fndyear = substr( $info{ $fund, "isodate" }, 0, 4 ); my $fndmonth = substr( $info{ $fund, "isodate" }, 5, 2 ); my $fndday = substr( $info{ $fund, "isodate" }, 8, 2 ); my $fnd = DateTime->new(year=>$fndyear,month=>$fndmonth,day=>$fndday); my $deltadays = $calcDay->subtract_datetime($fnd)->in_units('days'); cmp_ok( $deltadays,'<=', 11, 'not more than 11 days before today' ); cmp_ok( $deltadays,'>=', -1, 'not more than 1 day in the future' ); cmp_ok( $info{ $fund, 'currency' }, 'eq', 'JPY', 'currency' ); cmp_ok( $info{ $fund, 'method' }, 'eq', 'MorningstarJP', 'method' ); cmp_ok( $info{ $fund, 'name' }, 'eq', $valid{$fund}, 'name' ); cmp_ok( $info{ $fund, "nav" }, '>', 0, 'nav' ); ok( $info{ $fund, "success" }, 'success' ); cmp_ok( $info{ $fund, 'symbol' }, 'eq', $fund, 'symbol' ); } # Check that a bogus symbol returns no-success. ok( !$info{ "BOGUS", "success" } ); Finance-Quote-1.65/t/twelvedata.t0000755000175000017500000000415515003302667016533 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } if ( not $ENV{"TEST_TWELVEDATA_API_KEY"} ) { plan skip_all => 'Set $ENV{"TEST_TWELVEDATA_API_KEY"} to run this test'; } my @valid = qw/MSFT AMZN AAPL GOOGL META BRK.A/; my @invalid = ('BOGUS'); my @symbols = (@valid, @invalid); my $q = Finance::Quote->new('TwelveData', timeout => 120, twelvedata => {API_KEY => $ENV{"TEST_TWELVEDATA_API_KEY"}}); my $today = today(); my $window = 7; my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'symbol' => sub {$_[0] eq $_[1]}, 'name' => sub {defined $_[0]}, 'exchange' => sub {defined $_[0]}, 'currency' => sub {defined $_[0]}, 'open' => sub {defined $_[0] and looks_like_number($_[0])}, 'high' => sub {defined $_[0] and looks_like_number($_[0])}, 'low' => sub {defined $_[0] and looks_like_number($_[0])}, 'close' => sub {defined $_[0] and looks_like_number($_[0])}, 'volume' => sub {defined $_[0] and looks_like_number($_[0])}, 'method' => sub {$_[0] eq 'twelvedata'}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->twelvedata(@symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/onvista.t0000755000175000017500000000231515003302667016052 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{'ONLINE_TEST'}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('OnVista'); my @valid = qw/MRK AAPL SAP FR0010510800 A3GQ2N/; my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my @labels = qw/symbol isin wkn name open close high low last date currency exchange method p_change/; my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; plan tests => 1 + (3 + @labels) * @valid + @invalid; my %quotes = $q->onvista(@symbols); ok(%quotes); foreach my $symbol (@valid) { ok($quotes{$symbol, 'success'}, "$symbol success"); for my $label (@labels) { ok(defined $quotes{$symbol, $label}, "$symbol returned $label as $quotes{$symbol, $label}"); } ok((substr($quotes{$symbol, 'isodate'}, 0, 4) == $year or substr($quotes{$symbol, 'isodate'}, 0, 4) == $lastyear), "$symbol returned isodate as $quotes{$symbol, 'isodate'}"); ok($quotes{$symbol, 'time'} =~ /^[0-2]?[0-9]:[0-5][0-9]/, "$symbol returned time as $quotes{$symbol, 'time'}"); } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/cse.t0000755000175000017500000000454015003302667015143 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('LOFC.N0000' => 'LOLC FINANCE PLC', 'COMB.N0000' => 'COMMERCIAL BANK OF CEYLON PLC', 'DIST.N0000' => 'DISTILLERIES COMPANY OF SRI LANKA PLC', 'SLTL.N0000' => 'SRI LANKA TELECOM PLC' ); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'cse'; # Name of the target method for testing my $currency = 'LKR'; # expected quote curreny my $today = today(); # together with $window, validate date/isodate my $window = 7; # quote must be within last $window days my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'method' => sub {$_[0] eq 'cse'}, 'isin' => sub {$_[0] =~ /^[A-Z0-9]{12}$/}, 'close' => sub {looks_like_number($_[0])}, 'last' => sub {looks_like_number($_[0])}, 'high' => sub {looks_like_number($_[0])}, 'low' => sub {looks_like_number($_[0])}, 'cap' => sub {looks_like_number($_[0])}, 'change' => sub {looks_like_number($_[0])}, 'p_change' => sub {looks_like_number($_[0])}, 'name' => sub {$_[0] eq $valid{$_[1]}}, 'currency' => sub {$_[0] =~ /^[A-Z]{3}$/}, 'isodate' => sub {defined $_[0] and Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/finanzpartner.t0000644000175000017500000000354715003302667017255 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 20; my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my $q = Finance::Quote->new("Finanzpartner"); my %quotes = $q->finanzpartner("LU0293315023", "BOGUS", "LU0856992614", "LU1720050803"); ok(%quotes); ### quotes : %quotes # Check that the last and date values are defined. ok($quotes{"LU0293315023","success"}); ok($quotes{"LU0293315023","last"} > 0); ok(length($quotes{"LU0293315023","date"}) > 0); ok(substr($quotes{"LU0293315023","isodate"},0,4) == $year || substr($quotes{"LU0293315023","isodate"},0,4) == $lastyear); ok(substr($quotes{"LU0293315023","date"},6,4) == $year || substr($quotes{"LU0293315023","date"},6,4) == $lastyear); ok($quotes{"LU0293315023","currency"} eq "EUR"); ok($quotes{"LU0856992614","success"}); ok($quotes{"LU0856992614","last"} > 0); ok(length($quotes{"LU0856992614","date"}) > 0); ok(substr($quotes{"LU0856992614","isodate"},0,4) == $year || substr($quotes{"LU0856992614","isodate"},0,4) == $lastyear); ok(substr($quotes{"LU0856992614","date"},6,4) == $year || substr($quotes{"LU0856992614","date"},6,4) == $lastyear); ok($quotes{"LU0856992614","currency"} eq "EUR"); ok($quotes{"LU1720050803","success"}); ok($quotes{"LU1720050803","last"} > 0); ok(length($quotes{"LU1720050803","date"}) > 0); ok(substr($quotes{"LU1720050803","isodate"},0,4) == $year || substr($quotes{"LU1720050803","isodate"},0,4) == $lastyear); ok(substr($quotes{"LU1720050803","date"},6,4) == $year || substr($quotes{"LU1720050803","date"},6,4) == $lastyear); ok($quotes{"LU1720050803","currency"} eq "USD"); # Check that a bogus fund returns non-success. ok($quotes{"BOGUS","success"} == 0); Finance-Quote-1.65/t/fool.t0000755000175000017500000000234015003302667015324 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if ( not $ENV{"ONLINE_TEST"} ) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new('Fool'); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my @symbols = qw/MSFT AMZN GOOG CSCO INTC PEP BRK.A SEB NVR BKNG/; plan tests => 8*(1+$#symbols)+2; my %quotes = $q->fool(@symbols, "BOGUS"); ok(%quotes, "Successful quote retrieval"); foreach my $symbol (@symbols) { ok($quotes{$symbol, "symbol"} eq $symbol, "$symbol defined"); ok($quotes{$symbol, "success"}, "$symbol success"); ok($quotes{$symbol, "open"} > 0, "$symbol returned open"); ok($quotes{$symbol, "volume"} >= 0, "$symbol returned volume"); ok($quotes{$symbol, "last"} > 0, "$symbol returned last"); ok($quotes{$symbol, "currency"} eq 'USD', "$symbol returned currency"); ok(substr($quotes{$symbol, "isodate"}, 0, 4) == $year || substr($quotes{$symbol, "isodate"}, 0, 4) == $lastyear, "$symbol returned valid isodate"); ok(substr($quotes{$symbol, "date"}, 6, 4) == $year ||substr($quotes{$symbol, "date"}, 6, 4) == $lastyear, "$symbol returned valid date"); } ok((!$quotes{"BOGUS", "success"}),'BOGUS failed as expected'); Finance-Quote-1.65/t/03-kwalitee.t0000755000175000017500000000043615003302667016416 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; if (not $ENV{TEST_AUTHOR}) { plan( skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to true to run.'); } eval { require Test::Kwalitee; Test::Kwalitee->import() }; plan( skip_all => 'Test::Kwalitee not installed; skipping' ) if $@; Finance-Quote-1.65/t/dws.t0000755000175000017500000000417015003302667015165 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('848646' => 'Albatros Fonds', 'DE0008486465' => 'Albatros Fonds', 'LU0358627221' => 'Zurich Premium Multi Asset Offensiv'); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'dwsfunds'; # Name of the target method for testing my $currency = 'EUR'; # expected quote curreny my $today = today(); # together with $window, validate date/isodate my $window = 7; # quote must be within last $window days my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'last' => sub {looks_like_number($_[0])}, 'name' => sub {$_[0] eq $valid{$_[1]}}, 'currency' => sub {$_[0] eq $currency}, 'isin' => sub {$_[0] =~ /^[A-Z0-9]{12}$/}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;} ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/morningstarCH.t0000644000175000017500000000144515003302667017145 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 8; my $q = Finance::Quote->new(); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %quotes = $q->morningstarch("CH0012056260","CH0014933193","CH0002788567","BOGUS"); ok(%quotes); # Check the last values are defined. These are the most # used and most reliable indicators of success. ok($quotes{"CH0012056260","last"} > 0); ok($quotes{"CH0012056260","success"}); ok($quotes{"CH0014933193","last"} > 0); ok($quotes{"CH0014933193","success"}); ok($quotes{"CH0002788567","last"} > 0); ok($quotes{"CH0002788567","success"}); # Check that bogus stocks return failure: ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/04-critic.t0000755000175000017500000000052115003302667016062 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use File::Spec; if (not $ENV{TEST_AUTHOR}) { plan( skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to true to run.'); } eval { require Test::Perl::Critic; }; if ($@) { plan( skip_all => 'Test::Perl::Critic required for test.'); } Test::Perl::Critic->import(); all_critic_ok(); Finance-Quote-1.65/t/bvb.t0000644000175000017500000000204115003302667015131 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new(); my @stocks = ("TLV", "BAYN", "SNP"); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; # Tests: fetch, fetch BOGUS, and 5 per stock plan tests => 2 + 5*@stocks; my %quotes = $q->fetch("bvb", @stocks); ok(%quotes); # Check that the name and last are defined for all of the stocks. foreach my $stock (@stocks) { ok($quotes{$stock, "last"} > 0); ok($quotes{$stock, "success"}); ok($quotes{$stock, "currency"} eq "RON"); ok(substr($quotes{$stock,"isodate"},0,4) == $year || substr($quotes{$stock,"isodate"},0,4) == $lastyear); ok(substr($quotes{$stock,"date"},6,4) == $year || substr($quotes{$stock,"date"},6,4) == $lastyear); } # Check that a bogus fund returns no-success. %quotes = $q->fetch("bvb", "BOGUS"); ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/ftfunds.t0000644000175000017500000000214515003302667016036 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 12; my $q = Finance::Quote->new(); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %quotes = $q->ftfunds("GB0031834814","GB0030881337","GB0003865176","GB00B7W6PR65","BOGUS"); ok(%quotes); # Check the last values are defined. These are the most # used and most reliable indicators of success. ok($quotes{"GB0031834814","last"} > 0); ok($quotes{"GB0031834814","success"}); ok($quotes{"GB00B7W6PR65","last"} > 0); ok($quotes{"GB00B7W6PR65","success"}); ok($quotes{"GB00B7W6PR65","currency"} eq "GBP", "Currency (GBP) for GB00B7W6PR65 is ".$quotes{"GB00B7W6PR65","currency"}); ok($quotes{"GB00B7W6PR65","price"}<100,"Price for GB00B7W6PR65 < 100 : ".$quotes{"GB00B7W6PR65","price"}); ok($quotes{"GB0030881337","last"} > 0); ok($quotes{"GB0030881337","success"}); ok($quotes{"GB0003865176","last"} > 0); ok($quotes{"GB0003865176","success"}); # Check that bogus stocks return failure: ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/currency_lookup.t0000755000175000017500000000352615003302667017617 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More tests => 12; use Finance::Quote; # Test overall currency lookup my $currencies = Finance::Quote::currency_lookup(); my %test_currencies = ( AUD => "Australian Dollar" , EUR => "Euro" , CAD => "Canadian Dollar" ); while ( my ($code, $name) = each %test_currencies ) { ok( exists $currencies->{$code}, "Expected currency code (${code}) exists" ); is( $currencies->{$code}->{name} , $name , "Expected currency name (${name}) for code (${code})" ); } # Test selective currency lookup $currencies = Finance::Quote::currency_lookup( name => qr/pound/i ); # Test multiple lookup parameters $currencies = Finance::Quote::currency_lookup( name => "Australia" , code => qr/AU/ ); ok( exists $currencies->{AUD} , "Expected currency code (AUD) exists for matching multiple params" ); cmp_ok( scalar keys %{$currencies}, '==', 1 , "Only one currency returned for matching multiple params" ); $currencies = Finance::Quote::currency_lookup( name => "Euro" , code => "AUD" ); cmp_ok( scalar keys %{$currencies}, '==', 0 , "Expected zero-response for non-matching multiple params" ); # Test non-matching currency lookup $currencies = Finance::Quote::currency_lookup( name => qr/rubbish_value/i ); is( ref $currencies , 'HASH' , 'Hash-ref returned for non-matching lookup' ); cmp_ok( scalar keys %{$currencies} , '==', 0 , "Empty hashref returned for non-matching lookup" ); # Suppress warning message in last test local $SIG{__WARN__} = sub {}; # Test that an error returns undef $currencies = Finance::Quote::currency_lookup( invalid_param => 1 ); is( $currencies , undef , "Error results in undef response" ); Finance-Quote-1.65/t/00-store-date.t0000755000175000017500000000665115003302667016662 0ustar bschuckbschuck#!/usr/bin/perl -w # Test to see if Finance::Quote can at least be loaded and store_date is # working use strict; use Test::More; plan tests => 19; use Finance::Quote; ok(1, "Finance::Quote loaded"); my $quote = Finance::Quote->new(); ok($quote, "quote object created"); # Get Today's date my ($month, $day, $year2) = (localtime())[4,3,5]; $month++; my $year4 += $year2 + 1900; # 2007 my $year4m += $year2 + 1900 - 1;# 2006 $year2 -= 100; # 05 my $isotoday = sprintf("%04d-%02d-%02d", $year4, $month, $day); my $ustoday = sprintf("%02d/%02d/%04d", $month, $day, $year4); # Test date functions my %info; $quote->store_date(\%info, "test", {today => 1}); ok($info{"test","isodate"} eq $isotoday,"test->isodate is today"); ok($info{"test","date"} eq $ustoday,"test->date is today"); # Test various permutions of an ISO Date as input %info = (); $quote->store_date(\%info, "test", {isodate => "2004-12-31"}); ok($info{"test","date"} eq "12/31/2004", "ISO date permutation 1"); %info = (); $quote->store_date(\%info, "test", {isodate => "2004 Dec 31"}); ok($info{"test","date"} eq "12/31/2004","ISO date permutation 2"); %info = (); $quote->store_date(\%info, "test", {isodate => "2004 December 31"}); ok($info{"test","date"} eq "12/31/2004","ISO date permutation 3"); # Test various permutions of an US Date as input %info = (); $quote->store_date(\%info, "test", {usdate => "12/31/2004"}); ok($info{"test","isodate"} eq "2004-12-31","US date permutation 1"); %info = (); $quote->store_date(\%info, "test", {usdate => "Dec 31, 2004"}); ok($info{"test","isodate"} eq "2004-12-31","US date permutation 2"); %info = (); $quote->store_date(\%info, "test", {usdate => "December 31 2004"}); ok($info{"test","isodate"} eq "2004-12-31","US date permutation 3"); # Test various permutions of an European Date as input %info = (); $quote->store_date(\%info, "test", {eurodate => "31/12/2004"}); ok($info{"test","isodate"} eq "2004-12-31","EUR date permutation 1" ); %info = (); $quote->store_date(\%info, "test", {eurodate => "31 December 2004"}); ok($info{"test","isodate"} eq "2004-12-31","EUR date permutation 2"); %info = (); $quote->store_date(\%info, "test", {eurodate => "31 Dec, 2004"}); ok($info{"test","isodate"} eq "2004-12-31","EUR date permutation 3"); # Try some other permutions. A recent change to the date handling # code changes the behavior if a year is not explicitly provided. Now # it will look at the month and decide if the date is in the current # year or is from the previous year. This code still has to handle # being executed on 12/31, thus the dual tests for each date. %info = (); $quote->store_date(\%info, "test", {day=>"31", month=>"12"}); ok($info{"test","date"} eq "12/31/$year4" || $info{"test","date"} eq "12/31/$year4m","test year 1"); ok($info{"test","isodate"} eq "$year4-12-31" || $info{"test","isodate"} eq "$year4m-12-31","test year 2"); %info = (); $quote->store_date(\%info, "test", {day=>"31", month=>"December"}); ok($info{"test","date"} eq "12/31/$year4" || $info{"test","date"} eq "12/31/$year4m","test year 3"); ok($info{"test","isodate"} eq "$year4-12-31" || $info{"test","isodate"} eq "$year4m-12-31","test year 4"); %info = (); $quote->store_date(\%info, "test", {day=>"31", month=>"December", year => $year2}); ok($info{"test","date"} eq "12/31/$year4" || $info{"test","date"} eq "12/31/$year4m","test year 5"); ok($info{"test","isodate"} eq "$year4-12-31" || $info{"test","isodate"} eq "$year4m-12-31","test year 6"); Finance-Quote-1.65/t/nseindia.t0000755000175000017500000000174515003302667016167 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 22; # Test NSEIndia functions. my $q = Finance::Quote->new(); my @stocks = ("HDFCBANK", "TCS", "INE009A01021", "INE062A01020"); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; my %quotes = $q->fetch("nseindia", @stocks); ok(%quotes); # Check that the name and last are defined for all of the stocks. foreach my $stock (@stocks) { ok($quotes{$stock, "last"} > 0); ok($quotes{$stock, "success"}); ok($quotes{$stock, "currency"} eq "INR"); ok(substr($quotes{$stock,"isodate"},0,4) == $year || substr($quotes{$stock,"isodate"},0,4) == $lastyear); ok(substr($quotes{$stock,"date"},6,4) == $year || substr($quotes{$stock,"date"},6,4) == $lastyear); } # Check that a bogus fund returns no-success. %quotes = $q->fetch("nseindia", "BOGUS"); ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/za.t0000755000175000017500000000401215003302667014775 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('AGL' => 'ANGLO AMERICAN PLC - AGL', 'AMS' => 'ANGLO AMERICAN PLATINUM CORPORATION LIMITED - AMS' ); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'za'; # Name of the target method for testing my $currency = 'ZAR'; # expected quote curreny my $today = today(); # together with $window, validate date/isodate my $window = 7; # quote must be within last $window days my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0]}, 'currency' => sub {$_[0] eq $currency}, 'name' => sub {$_[0] eq $valid{$_[1]}}, 'price' => sub {looks_like_number($_[0])}, 'isodate' => sub {Date::Range->new($today - $window, $today + 1)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;}, ); my $q = Finance::Quote->new(); plan tests => 1 + (scalar keys %check) * (scalar keys %valid) + scalar @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/troweprice.t0000755000175000017500000000234615003302667016556 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; use Time::Piece; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 42; # Test troweprice functions. my $q = Finance::Quote->new(); my $year = localtime()->year; my @symbols = qw/ PRFDX PRIDX TEUIX RPGEX GTFBX /; ok( my %quotes = $q->troweprice(@symbols, 'BOGUS'), 'Fetched quotes' ); foreach my $symbol (@symbols) { ok( length $quotes{$symbol, "name"} > 0, "$symbol name length > 0"); ok( $quotes{$symbol, "nav"} > 0, "$symbol nav > 0"); ok( $quotes{$symbol, "price"} > 0, "$symbol price > 0"); ok( $quotes{$symbol, "symbol"} eq $symbol, "$symbol symbol match"); ok( length $quotes{$symbol, "date"} > 0, "$symbol date length > 0"); my $quote_year = substr($quotes{$symbol, "isodate"}, 0, 4 ); ok ($quote_year == $year || $quote_year - 1 == $year, "$symbol isodate year check"); ok($quotes{$symbol, "method"} eq "troweprice", "$symbol method is troweprice"); ok($quotes{$symbol, "currency"} eq 'USD', "$symbol currency as expected"); } # Check a bogus fund returns no-success ok(! $quotes{"BOGUS","success"}); Finance-Quote-1.65/t/oslobors.t0000755000175000017500000000176715003302667016243 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new(); my $year = (localtime())[5] + 1900; my $lastyear = $year - 1; plan tests => 17; my @funds = ("OD-HORIA.OSE", "DK-EUROP.OSE", "ST-VEKST.OSE"); my %quotes = $q->fetch("Oslobors", @funds); ok(%quotes, "Data fetched"); foreach my $symbol (@funds) { ok($quotes{ $symbol, "success"}, "Retrieved $symbol"); ok($quotes{ $symbol, "price"} ne "", "Price is defined"); ok($quotes{ $symbol, "currency"} eq "NOK", "Currency is set to NOK"); my $isoyear = substr($quotes{$symbol, "isodate"}, 0, 4); my $dateyear = substr($quotes{$symbol, "date"}, 6, 4); ok($isoyear == $year || $isoyear == $lastyear, "ISODate is this or last year"); ok($dateyear == $year || $dateyear == $lastyear, "Date is this or last year"); } %quotes = $q->fetch("oslobors", "BOGUS"); ok( !$quotes{"BOGUS", "success"}, "BOGUS failed"); Finance-Quote-1.65/t/fondsweb.t0000755000175000017500000000313015003302667016172 0ustar bschuckbschuck#!/usr/bin/perl -w use constant DEBUG => $ENV{DEBUG}; use if DEBUG, Smart::Comments; use strict; use Test::More; use Finance::Quote; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my $q = Finance::Quote->new(); my @valid = ('LU0804734787','DE0008491002','DE0008474503','DE0008491051','DE0009805507'); my @invalid = qw/BOGUS/; my @symbols = (@valid, @invalid); my $year = (localtime())[5] + 1900; my %check = ( 'currency' => sub { $_[0] =~ /^[A-Z]+$/ }, 'date' => sub { $_[0] =~ m{^[0-9]{2}/[0-9]{2}/([0-9]{4})$} and ($1 == $year || $1 == $year + 1) }, 'isin' => sub { $_[0] =~ /^[A-Z]{2}[0-9]{10}$/ }, 'isodate' => sub { $_[0] =~ /^([0-9]{4})-[0-9]{2}-[0-9]{2}$/ and ($1 == $year || $1 == $year + 1) }, 'last' => sub { $_[0] =~ /^[0-9.]+$/ }, 'method' => sub { $_[0] =~ /^fondsweb$/ }, 'name' => sub { length($_[0]) }, 'nav' => sub { $_[0] =~ /^[0-9.]+$/ }, 'success' => sub { $_[0] }, 'type' => sub { $_[0] eq 'fund' }, ); plan tests => 1 + %check*@valid + @invalid; my %quotes = $q->fetch('fondsweb', @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (@valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol), "$key -> $quotes{$symbol, $key}"); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/t/asx.t0000755000175000017500000001403015003302667015157 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use Scalar::Util qw(looks_like_number); use Finance::Quote; use Test::More; use Time::Piece; use feature 'say'; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } plan tests => 6; # Listed securities come and go, so entries on these lists will eventually fail. my @ordinaries = qw/ARG BHP CBA RIO/; my @numerics = qw/360 14D A2M XF1/; my @corp_bonds = qw/AYUHD/; my @govt_bonds = qw/GSBK51 GSIC50/; my @etps = qw/BEAR ETPMPT GOLD IAA/; my @warrants = qw/BKIJOA/; my @indices = qw/XAO XJO/; my @invalids = qw/BOGUS TooLong Non-AN/; my ($q, %quotes); subtest 'Startup: Test object creation' => sub { $q = new_ok( 'Finance::Quote' ); }; subtest 'Test the "normal" securities that use the Primary ASX data source' => sub { ok( %quotes = $q->asx(@ordinaries, @numerics), 'fetch quotes' ); for my $symbol (@ordinaries, @numerics) { ok( $quotes{$symbol, 'success'} == 1, "Success for $symbol" ); subtest "Data tests for $symbol" => sub { plan 'skip_all' => "Bypass data tests for $symbol - no data returned" unless $quotes{$symbol, 'success'} == 1; plan tests => 10; ok( $quotes{$symbol, 'errormsg'} eq '', 'no error message for valid symbol' ); ok( $quotes{$symbol, 'currency'} eq 'AUD', 'got expected currency' ); ok( $quotes{$symbol, 'method' } eq 'asx', 'got expected method' ); ok( length $quotes{$symbol,'name'}, 'got a name' ); ok( $quotes{$symbol,'symbol'} eq $symbol, 'matching symbol' ); # For the securities in this group, expect *all* prices and volume to have non-zero values. # (The occasional trading halt or corporate action may cause some of these to fail - c'est la vie.) for my $field (qw/ask bid last price/) { ok( $quotes{$symbol, $field} > 0, "$field > 0" ); } for my $field (qw/p_change/) { ok( looks_like_number($quotes{$symbol, $field}), "$field looks like a number" ); } done_testing(); }; } done_testing(); }; subtest 'Test the "other" security types, many of which use the Alternate ASX data source' => sub { ok( %quotes = $q->asx(@corp_bonds, @govt_bonds, @etps, ), 'fetch quotes' ); for my $symbol (@corp_bonds, @govt_bonds, @etps) { ok( $quotes{$symbol, 'success'} == 1, "success for $symbol" ); subtest "Data tests for $symbol" => sub { plan 'skip_all' => "Bypass data tests for $symbol - no data returned" unless $quotes{$symbol, 'success'} == 1; plan tests => 10; ok( $quotes{$symbol, 'errormsg'} eq '', 'no error message for valid symbol' ); ok( $quotes{$symbol, 'currency'} eq 'AUD', 'got expected currency' ); ok( $quotes{$symbol, 'method' } eq 'asx', 'got expected method' ); ok( length $quotes{$symbol,'name'}, 'got a name' ); ok( $quotes{$symbol,'symbol'} eq $symbol, 'matching symbol' ); # For the securities in this group, only expect the prices to have non-zero values. for my $field (qw/last price/) { ok( $quotes{$symbol, $field} > 0, "$field > 0" ); } for my $field (qw/ask bid/) { SKIP: { skip "Bypass check for '$field' if not received", 1 if $quotes{$symbol, $field} eq ''; ok( looks_like_number($quotes{$symbol, $field}), "$field looks like a number" ); } } for my $field (qw/p_change/) { ok( looks_like_number($quotes{$symbol, $field}), "$field looks like a number" ); } done_testing(); }; } done_testing(); }; subtest 'Test the "warrants and options" security types, which use the Alternate ASX data source and are thinly traded' => sub { ok( %quotes = $q->asx(@warrants), 'fetch quotes' ); for my $symbol (@warrants) { ok( $quotes{$symbol, 'success'} == 1, "success for $symbol" ); subtest "Data tests for $symbol" => sub { plan 'skip_all' => "Bypass data tests for $symbol - no data returned" unless $quotes{$symbol, 'success'} == 1; plan tests => 9; ok( $quotes{$symbol, 'errormsg'} eq '', 'no error message for valid symbol' ); ok( $quotes{$symbol, 'currency'} eq 'AUD', 'got expected currency' ); ok( $quotes{$symbol, 'method' } eq 'asx', 'got expected method' ); ok( length $quotes{$symbol,'name'}, 'got a name' ); ok( length $quotes{$symbol,'type'}, 'got a type' ); ok( $quotes{$symbol,'symbol'} eq $symbol, 'matching symbol' ); # For the securities in this group, only check for numeric values. for my $field (qw/last price p_change/) { ok( looks_like_number($quotes{$symbol, $field}), "$field looks like a number" ); } done_testing(); }; } done_testing(); }; subtest 'Test the indexes, which use the Alternate ASX data source' => sub { ok( %quotes = $q->asx(@indices), 'fetch quotes' ); for my $symbol (@indices) { ok( $quotes{$symbol, 'success'} == 1, "success for $symbol" ); subtest "Data tests for $symbol" => sub { plan 'skip_all' => "Bypass data tests for $symbol - no data returned" unless $quotes{$symbol, 'success'} == 1; plan tests => 7; ok( $quotes{$symbol, 'errormsg'} eq '', 'no error message for valid symbol' ); ok( $quotes{$symbol, 'method' } eq 'asx', 'got expected method' ); ok( length $quotes{$symbol,'name'}, 'got a name' ); ok( $quotes{$symbol,'symbol'} eq $symbol, 'matching symbol' ); # Indices should have non-zero prices and volumes. for my $field (qw/last price/) { ok( $quotes{$symbol, $field} > 0, "$field > 0" ); } for my $field (qw/p_change/) { ok( looks_like_number($quotes{$symbol, $field}), "$field looks like number" ); } done_testing(); }; } done_testing(); }; subtest 'Test that invalid symbols fail properly' => sub { ok( %quotes = $q->asx(@invalids), 'fetch quotes' ); for my $symbol (@invalids) { subtest "Data tests for $symbol" => sub { plan tests => 2; ok( !$quotes{$symbol, 'success'}, , 'bad symbol returned no result' ); ok( $quotes{$symbol, 'errormsg'} ne '', 'got error message for bad symbol' ); done_testing(); }; } done_testing(); }; done_testing(); Finance-Quote-1.65/t/02-pod-coverage.t0000755000175000017500000000050515003302667017160 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use Test::More; eval "use Test::Pod::Coverage 1.00"; plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD" if $@; if (not $ENV{TEST_AUTHOR}) { plan( skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to true to run.'); } plan tests => 1; pod_coverage_ok("Finance::Quote"); Finance-Quote-1.65/t/six.t0000644000175000017500000000504415003302667015171 0ustar bschuckbschuck#!/usr/bin/perl -w use strict; use warnings; use constant DEBUG => $ENV{DEBUG}; use if DEBUG, 'Smart::Comments'; use Test::More; use Finance::Quote; use Date::Simple qw(today); use Scalar::Util qw(looks_like_number); use Date::Range; use Date::Manip; if (not $ENV{ONLINE_TEST}) { plan skip_all => 'Set $ENV{ONLINE_TEST} to run this test'; } my %valid = ('NESN' => "Nestl\x{e9} AG", 'CSSMI' => 'BlackRock Asset Management Schweiz AG', 'AAPL' => 'Apple Inc' ); my @invalid = ('BOGUS'); my @symbols = (keys %valid, @invalid); my $method = 'six'; # Name of the target method for testing my $currency = 'EUR'; # expected quote curreny my $today = today(); # together with $window, validate date/isodate my $window = 7; # quote must be within last $window days my %check = (# Tests are called with (value_to_test, symbol, quote_hash_reference) 'success' => sub {$_[0] == 1}, 'volume' => sub {not defined $_[0] or looks_like_number($_[0])}, 'open' => sub {not defined $_[0] or looks_like_number($_[0])}, 'low' => sub {not defined $_[0] or looks_like_number($_[0])}, 'high' => sub {not defined $_[0] or looks_like_number($_[0])}, 'close' => sub {not defined $_[0] or looks_like_number($_[0])}, 'ask' => sub {not defined $_[0] or looks_like_number($_[0])}, 'currency' => sub {$_[0] =~ /^[A-Z]{3}$/}, 'name' => sub {$_[0] eq $valid{$_[1]}}, 'isin' => sub {$_[0] =~ /^[A-Z0-9]{12}$/}, 'isodate' => sub {Date::Range->new($today - $window, $today)->includes(Date::Simple::ISO->new($_[0]))}, 'date' => sub {my $a = Date::Manip::Date->new(); $a->parse_format('%m/%d/%Y', $_[0]); my $b = Date::Manip::Date->new(); $b->parse_format('%Y-%m-%d', $_[2]->{$_[1], 'isodate'}); return $a->cmp($b) == 0;} ); my $q = Finance::Quote->new(); plan tests => 1 + %check*%valid + @invalid; my %quotes = $q->fetch($method, @symbols); ok(%quotes); ### [] quotes: %quotes foreach my $symbol (keys %valid) { while (my ($key, $lambda) = each %check) { ok($lambda->($quotes{$symbol, $key}, $symbol, \%quotes), "$key -> " . (defined $quotes{$symbol, $key} ? $quotes{$symbol, $key} : '')); } } foreach my $symbol (@invalid) { ok((not $quotes{'BOGUS', 'success'}), 'failed as expected'); } Finance-Quote-1.65/Documentation/0000775000175000017500000000000015003302667016546 5ustar bschuckbschuckFinance-Quote-1.65/Documentation/FAQ0000644000175000017500000000522015003302667017075 0ustar bschuckbschuckFinance::Quote FAQ Paul Fenwick (pjf at cpan.org) 0. TABLE OF CONTENTS ==================== 0. Table of Contents. 1. Where's the Finance::Quote webpage? 2. Where can I get a beginner's introduction to F::Q? 3. How can I use proxyauth (experimental)? 4. Is there anything similar to F::Q in other programming languages? 5. Where can I get more help? 6. Is there commercial support for F::Q available? 1. Where is the Finance::Quote webpage? ======================================= http://finance-quote.sourceforge.net/ 2. Where can I get a beginner's introduction to F::Q? ===================================================== A good beginner's guide is the Finance::Quote article in The Perl Journal edition #19. You can read the final draft of this essay online at: . 3. How can I use proxyauth (experimental)? ========================================== WARNING: THIS SUPPORT IS EXPERIMENTAL AND SYNTAX _WILL_ CHANGE IN THE FUTURE. If it breaks, you get to keep both pieces. Finance::Quote provides experimental support for authenticated proxies. If you wish to try this, then put the following at the top of your script. use Finance::Quote; $Finance::Quote::USE_EXPERIMENTAL_UA = 1; This adds extra features on top of the regular LWP::UserAgent class. In particular, you can now do things like this: my $q = Finance::Quote->new(); $q->user_agent->default_headers->proxy_authorization_basic($user,$pass); The result of $q->user_agent->default_headers is a HTTP::Headers object, and can use all the regular HTTP::Headers methods. This object is used as a template for any new HTTP requests made by Finance::Quote. 4. Is there anything similar to F::Q in other programming languages? ==================================================================== Vidyut Luther has written a stock-lookup library in PHP. It's available at Bill Bell has written a stock-lookup library in Java. It's available at . Follow the "Code Downloads" link in the left sidebar. 5. Where can I get more help? ============================= If you haven't already done so, try the Finance::Quote webpage at . There are also lots of fun things like bug-tracking systems, support requests, forums, and other goodies at . Finally, you can always try sending mail to the Finance::Quote developer's list, at . The archives of this list are available on-line at . Finance-Quote-1.65/Documentation/License0000644000175000017500000004325415003302667020061 0ustar bschuckbschuck GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. Finance-Quote-1.65/Documentation/Hackers-Guide0000644000175000017500000004666115003302667021117 0ustar bschuckbschuckFinance::Quote Hackers Guide Original Author: Paul Fenwick , May 2000 $Version$ 0. Table of Contents ==================== 1. Introduction 2. Setup a Development Environment 3. How to write a Finance::Quote module 3.1. The package name 3.2. The methods() subroutine 3.3. The functions specified by methods() 3.4. The parameters() subroutine - Modules requiring API Keys 3.5. Currency 3.6. Development & Debugging Code 3.7. Dates 3.8. Things to avoid 3.9. Using your new module 4. How to write a Finance::Quote::CurrencyRates module 4.1. The package name 4.2. The new() constructor 4.3. The multipliers() function 5. How to contribute your module to the world 6. How to find out more 7. How to join the mailing lists 1. Introduction =============== This hacker's guide is primarily a tutorial on how to build your own Finance::Quote pluggable module. After reading this guide, you should be able to write your own module to provide extra methods and functionality to the Finance::Quote library. This guide assumes that you are familiar with perl. 2. Setup a Development Environment ================================== To develop and test modules, you need a clone of the Finance-Quote git repo, a recent version of perl, and the Dist::Zilla module and its dependencies. The following steps provide a recipe for setup. A. Clone the git repo $ git clone https://github.com/finance-quote/finance-quote.git B. (Optional) Install latest stable copy of perl in ~/perl5 $ curl -L https://install.perlbrew.pl | bash $ echo "source ~/perl5/perlbrew/etc/bashrc" >> ~/.bash_profile # or the equivalent for your shell $ perlbrew --notest install perl-5.28.1 # --notest is risky, but significant speeds installation $ perlbrew switch perl-5.28.1 $ perlbrew install-cpanm $ cpanm install Dist::Zilla # inside the finance-quote directory # remove --missing to install missing & upgrade existing required package $ dzil authordeps --missing | cpanm --notest $ dzil listdeps --missing | cpanm --notest $ cpanm install Data::Dumper::Perltidy Smart::Comments C. Test out finance-quote # Light-weight test mode that skips all online tests $ dzil test # To run all the skipped tests, you need three environment variables: # TEST_AUTHOR - when set, tests are run to check for required modules # ONLINE_TEST - when set, online tests are executed # ALPHAVANTAGE_API_KEY - free API key available from https://www.alphavantage.co # # Using bash syntax to set environment variables $ cpanm Test::Pod Test::Pod::Coverage Test::Kwalitee Test::Perl::Critic $ TEST_AUTHOR=1 ONLINE_TEST=1 ALPHAVANTAGE_API_KEY= dzil test # To do an online test for one module $ ONLINE_TEST=1 dzil run prove -lv t/financeapi.t # Use prove to during test development - it is fast $ prove -lv t/fq-class-methods.t 3. How to write a Finance::Quote module ======================================= Finding a source of information, and writing code to parse and interpret that information is a difficult task. As such, we've aimed to make writing a Finance::Quote module as easy as possible. There are only a few simple rules you need to follow: 3.1. The package name --------------------- Finance::Quote expects that its loadable modules will be in the Finance::Quote namespace somewhere. Hence, if you were writing a module called "DodgyBank" that returned information on DodgyBank's managed funds, a reasonable name for that module would be Finance::Quote::DodgyBank. 3.2. The methods() subroutine ----------------------------- Your module must have a subroutine named methods(). This function will be called by the Finance::Quote harness when it loads your module, and is used to determine which methods your module provides. The methods() function must return a hash of method names and subroutine references. For example, if you had written a module which provides access to DodgyBank's managed funds, you might have the following package Finance::Quote::DodgyBank; sub methods { return ( dodgyfunds => \&funds dodgyloans => \&loans ); } This would indicate that your package provides methods for "dodgyfunds" and "dodgyloans", and that the subroutines "funds" and "loans" should be called to access that information. The following method names should be used for the following information sources: Method-Name Source --------------------------------------------------------- australia Australian Stocks canada Canadian Stocks europe European Stocks fidelity Fidelity Investments nasdaq NASDAQ nyse New York Stock Exchange tiaacref TIAA-CREF troweprice T. Rowe. Price usa USA Stocks Method names should be lower-case, consist of alphanumeric characters (including underscore) only, and always begin with a letter. This is not enforced, but future versions of the Finance::Quote framework may rely upon it. It's strongly recommended that you also provide a unique name for your method, in case you (or others) wish to call that method exclusively in code. Hence if you had written a module to fetch information from the NYSE from Yohoo!, you might implement the following methods function: sub methods { return ( nyse => \&yohoo, yohoo => \&yohoo ); } This means that people who only want to use your function can use $quoter->fetch('yohoo',@stocks), but those who don't care where their NYSE stock information is fetched from can use $quoter->fetch('nyse',@stocks). The first form allows you to know exactly where the information is coming from. In the second, failover methods mean that many different functions could be used to fetch the stock information, not just the one you have defined. 3.3 The functions specified by methods() ---------------------------------------- The functions referred to by methods() will be passed a Finance::Quote object when called, and a list of zero or more symbol names. The Finance::Quote object provides the following ready-to-use methods: user_agent(); # Provides a ready-to-use LWP::UserAgent parse_csv(); # Parses a list of comma-separated values # and returns an array. The user_agent() method should be used if possible to fetch the information, as it should be already configured to use the timeout, proxy, and other settings requested by the calling program. Your function should return a two-dimensional hash as specified in the Finance::Quote man-page. Eg: $hash{$symbol,'last'} = $last_price; $hash{$symbol,'name'} = $stock_name; # etc etc. When returning your hash, you should check the context that your function was called in. If it was called in a scalar context, then you should return a hashref instead. This can be easily done with the following: return wantarray() ? %hash : \%hash; It is ESSENTIAL that your hash contain a true value for {$symbol,'success'} for information that has been successfully obtained. If the information was not obtained for any reason, then {$symbol,'success'} should be set to a false value (preferably 0), and a human-readable error message placed in {$symbol,'errormsg'}. The following code snippet demonstrates this: sub funds { my $quoter = shift; # The Finance::Quote object. my @stocks = @_; my %info; my $DODGY_URL = "http://dodgybank.xxx/funds.csv?"; my $ua = $quoter->user_agent; # This gives us a user-agent # with timeouts, proxies, # etc already configured. my $response = $ua->request(GET $DODGY_URL); unless ($response->is_success) { foreach my $stock (@stocks) { $info{$stock,"success"} = 0; $info{$stock,"errormsg"} = "HTTP failure"; } return wantarray ? %info : \%info; } # Do stuff with the information returned.... } It is valid to use "return" with no arguments if all stock lookups failed, however this does not provide any information as to WHY the lookups failed. If at all possible, the errormsg labels should be set. It is also very very strongly recommended that you place your module's name in the {$stock,"source"} field. This allows others to check where information was obtained, and to use it appropriately. 3.4 The parameters() subroutine - Modules requiring API Keys ------------------------------------------------------------ Some data sources require an API Key (sometimes called a token) in order to access the securities or exchange data. AlphaVantage.pm is one example. In order to assist programs using Finance::Quote to identify those modules that require an API Key please include this function if your module is using a data source that requires a key/token. sub parameters { return ('API_KEY'); } 3.5 Currency ------------- Finance::Quote has support for multiple currencies and for currency conversion. As long as you provide a little bit of information about the information you are returning, the Finance::Quote framework can do all the hard stuff for you. If you are returning information on a stock in a particular currency, then you can enter the ISO currency code into the "currency" field associated with the stock. Eg: $info{$stock,"currency"} = "AUD"; # Australian Dollars If the information you are returning does not have a currency (because it's an index like the Dow Jones Industrial or the All Oridinaries, or because you're returning percentages) then you should not set the currency field for that stock. Finance::Quote knows not to attempt currency conversion for stocks without a currency field. If you do have a currency field, then by default Finance::Quote will arrange for the automatic conversion of a number of fields. By default, these fields are last, high, low, net, bid, ask, close, open, day_range, year_range, eps, div, cap, nav and price. Of course, there may be some cases where this set is not appropriate, or where there are extra fields that should be converted. This can be indicated by writing a function called "currency_fields()" in your module, that returns a list of fields that can undergo currency conversion. Eg: sub currency_fields { return qw/high low price bid/; } currency_fields() will be passed a Finance::Quote object as its first argument, and a method called default_currency_fields() is available through this object. This is useful if you want to use the defaults, but also add some of your own: sub currency_fields { my $quoter = shift; return ($quoter->default_currency_fields, "commission"); } In the example above, the default fields would be available for currency conversion, but the "commission" field would also be converted. 3.6 Development & Debugging Code -------------------------------- There are multiple ways to include extra code in Perl modules to facilitate development and debugging. For Finance::Quote modules, please use the following strategy that uses a combination of a DEBUG constant and the Smart::Comments module. If DEBUG is in the environment and set to a value that Perl evaluates to true, then Smart::Comments is active and code blocks wrapped with if(DEBUG) are also active. Otherwise, all the debugging code disappears during compilation. use strict; # use strict & warnings FIRST use warnings; use constant DEBUG => $ENV{DEBUG}; # create a constant from the environment use if DEBUG, 'Smart::Comments'; # conditionally use Smart::Comments use if DEBUG, 'Another::Module'; # example module only needed for debugging use LWP::UserAgent; # and finally production module dependencies my %h = ('a' => 1); # just an example variable # These first two lines are always a comment. The third line # prints a debugging string if DEBUG is in the environment. ### [] h: \%h # For more complicated debugging, the following if-block # will disappear during compilation because of constant folding. if (DEBUG) { print "More complex debugging code\n"; } 3.7 Dates ---------- Do not parse dates directly in your module. Instead you should use the function $q->store_date(), which handles a variety of date formats. In its simplest form, you simply tell the function what format the date is in and it handles all the parsing. The code should look similar to this: $quoter->store_date(\%info, $stock, {eurodate => @$row[1]}); If the web site doesn't have a data available, somply call the function this way: $quoter->store_date(\%info, $stock, {today => 1}); See the documentation in Quote.pm for more information. 3.8 Things to avoid -------------------- Some sources of information will provide more stock information than requested. Some code may rely upon your code only returning information about the stocks that the caller requested. As such, you should never return information about stocks that were not requested, even if you fetch and/or process that information. 3.9 Using your new module -------------------------- Using your new module is easy. Normally when using Finance::Quote you'd do something like the following: use Finance::Quote; my $quoter = Finance::Quote->new(); To use your new module, simply specify the module name (without the Finance::Quote prefix) in the new function. Hence: use Finance::Quote; my $quoter = Finance::Quote->new("DodgyBank"); The DodgyBank methods will now be available: my %loaninfo = $quoter->fetch("dodgyloans","car","boat","house"); my %fundinfo = $quoter->fetch("dodgyfunds","lotto","shares"); The resulting Finance::Quote object will also arrange for your functions to be callable without using fetch. This syntax is strongly discouraged, as it results in pollution of the Finance::Quote namespace and provides little advantages over the fetch() method: my %loaninfo = $quoter->dodgyloans("car","boat","loan"); This mainly exists to maintain compatibility with previous versions of Finance::Quote. 4. How to write a Finance::Quote::CurrencyRates module ====================================================== Currency Rate modules carryout a single task: Given two currencies, return multipliers that describe the relative value of the currencies. In the simplest case, one multiplier is "1.0" and the other multiplier is the exchange rate between the two currencies. In the other case, the multipliers reflect the relative value of each currency to a third base currency. For example, on 2020-10-30, the European Central Bank reported that 1.1698 USD (United States Dollar) was equivalent to 1.00 EUR (Euro). It also reported that 0.90208 GBP (Pound Sterling) was equivalent to 1.00 EUR. To convert from USD to GBP, the following from/to multipliers are equivalent: from to Base Currency -------- -------- ------------- 1.16980 0.90208 EUR 1.00000 0.77114 USD 1.29678 1.00000 GBP Depending on the source of the currency rates, it may be more convenient for a currency rate module to return multipliers that are relative to a third currency (first row), the conversion rate (the second row), or the inverse conversion rate (the third row). Currency rate modules should avoid carrying out arithmetic and leave it to Finance::Quote to use the multipliers to compute the final conversion value. 4.1. The package name --------------------- Currency rate modules belong in the Finance::Quote::CurrencyRates namespace and the name of the module should be descriptive. Use PascalCase (upper CamelCase) for the module name. 4.2. The new() constructor -------------------------- Currency rate modules are called from a Finance::Quote quoter object when currency conversion is either explicitly requested from a caller or to convert a fetched security price to a user specified currency. When instantiating a quoter object, the caller may pass module specific parameters. Suppose the currency rate module ExampleRates requires an API key and also optionally supports caching rates during the lifetime of the quoter object. Then the code my $q = Finance::Quote->new('currency_rates' => {order => ['ExampleRates', 'ECB'], examplerates => {API_KEY => 'x01234566', cache => True}}); instantiates a quoter that uses ExampleRates first and falls back the ECB for currency rates. When ExampleRates is used, the API_KEY can cache options are configured through a module specific hash. Finance::Quote passes just the hash containing the API_KEY and cache key/value pairs to ExampleRates->new() as a hash reference. If the caller does not specify an ExampleRates key in the currency_rates hash, then ExampleRates->new() is called with no arguments. ExamplesRates may, but is not required, to alternately read the API key from an environment variable. Clearly document all currency rate module options in the POD documentation for the module. 4.3. The multipliers() function ------------------------------- Finance::Quote calls the currency rate object method multipliers() with the arguments (object, user_agent, from, to), where object was previously instantiated with a call to new() user_agent is a LWP::UserAgent from is an ISO currency code to is an ISO currency code multipliers() must a pair of multipliers (from_multiplier, to_multiplier), where from_multiplier is a floating point value to_multiplier is a floating point value and to_multiplier/from_multiplier will convert a value in currency "from" into currency "to". On error, multipliers() must either return undef or throw and exception with die(). 5. How to contribute your module to the world ============================================= Check list: o Created a well-named module file in the Finance directory tree o Included POD documentation at the bottom of module file o Included a VERSION comment just *after* the use module statements o Added the module name in alphabetical order to the @MODULES or @CURRENCY_MODULES variable in Quote.pm o Added the module name in alphabetical order in the SEE ALSO section of the Quote.pm POD o Created a well-named test file in the t/ directory, including a test that succeeds and a test that fails o Ensure tests 00-store-date.t, 01-pod.t, 02-pod-coverage.t, 03-kwalitee.t, 04-critic.t, 05-data-dumper.t pass o Added module information in alphabetical order to the Modules-README.yml file o Add a quick description of the change/addition to the Changes file. Most recent changes at the top of the list. Add new lines just below the {{$NEXT}} label at the top of the file. Contributions to Finance-Quote are best presented as pull requests on GitHub.com. A. Create a GitHub account and sign-in B. Go to https://github.com/finance-quote/finance-quote C. Click "Fork" in the upper-right corner D. Commit your new module and other code changes to the fork E. Click "New Pull Request" on https://github.com/finance-quote/finance-quote F. Click "Compare Across Forks" G. Select the appropriate commits and create the merge request Contact developers at finance-quote-devel@sourceforge.net to discuss new modules, ask questions, or get help opening a merge request. 6. How to find out more ======================= The Finance::Quote GitHub page is located at https://github.com/finance-quote/finance-quote and contains information about the project and links to older SourceForge documentation. 7. How to join the mailing lists ================================ There are two mailing lists for Finance::Quote. These can both be accessed from: http://sourceforge.net/mail/?group_id=4232 Finance-Quote-1.65/Documentation/Release.md0000644000175000017500000000262715003302667020455 0ustar bschuckbschuck# Procedure for a CPAN release --- ## FINALIZE DEVELOPMENT * finalize dev branch merges * check for debug code in modules * Modify Changelog * Add missing authors to dist.ini (use `git shortlog -s -n -e` and select everyone with at least 2 commits or `git shortlog -s -n -e | awk '$1 >= 2 {printf "author = %s\n", substr($0,index($0,$2))}' | sort`) * Update htdocs/index.html (Sourceforge project home page) * push all commits up to github * dzil build - build a new release * dzil test - test it out ## RELEASE In order to upload files to PAUSE/CPAN and Sourceforge, the team member must have accounts with the proper privileges on those services. For PAUSE that is co-maint and for Sourceforge the user must be in the Admin group. * Before executing `dzil release` confirm the git configuration settings `user.name` and `user.email` are set. * dzil release - upload to cpan, tweet and mail :) * upload module tarball to sourceforge @ https://sourceforge.net/projects/finance-quote/files/finance-quote/ * Through Web interface * Or sftp \@frs.sourceforge.net * cd /home/frs/project/finance-quote/finance-quote * put Finance-Quote-N.NN.tar.gz * bye * Upload index.html for http://finance-quote.sourceforge.net/index.html * sftp \,finance-quote@web.sourceforge.net * cd /home/project-web/finance-quote/htdocs * put index.html * bye Finance-Quote-1.65/Documentation/README0000644000175000017500000000473315003302667017433 0ustar bschuckbschuckWelcome to Finance::Quote ========================= Maintained by: Paul Fenwick Erik Colson Bruce Schuck What does Finance::Quote provide? ================================= Finance::Quote provides access to time-delayed stockquotes from a number of sources. After you've installed the pacakage, try 'perldoc Finance::Quote' for full information. Alternatively, you can 'perldoc lib/Finance/Quote.pm' before the install. How do I install this package? ============================== If you downloaded the Finance-Quote-N.NN.tar.gz tarball from CPAN (N.NN is the version number, ex: Finance-Quote-1.50.tar.gz), run the following commands: tar xzf Finance-Quote-1.50.tar.gz cd Finance-Quote-1.50.tar.gz perl Makefile.PL make make test make install If you have the CPAN module installed: Using cpanm (Requires App::cpanminus) cpanm Finance::Quote or Using CPAN shell perl -MCPAN -e shell install Finance::Quote I've found a bug / written a patch / have an idea. What do I do? ================================================================= Well, you could always mail it to , which is read by all the active developers. Preferably, you might wish to visit to report issues or to view or create a pull request. The CPAN bug tracker at is not actively monitored. How do I download the most recent copy of Finance::Quote? ========================================================= You can find all releases of Finance::Quote at: . You might also wish to consider subscribing to finance-quote-news, which can be done from: . How can I get a copy of the current CVS development tree? ========================================================= You can't. The CVS repo has been ported to GIT as of version 1.13_01. How can I get a copy of the current GIT development tree? ========================================================= The GIT repository is available at: . Where can I find more information? ================================== Try the Finance::Quote webpage. There are lots of goodies there. http://finance-quote.sourceforge.net/