Compare commits

...
10 Commits
16 changed files with 625 additions and 732 deletions
+13 -11
View File
@@ -1,9 +1,12 @@
# Financials-Extension
### Update (18 Dec 2022) Yahoo deployed a major change to their website breaking the extension. Latest version 3.2.0 restores fields from Summary tab (values from Statistics and Profile tabe will be fixed later)
Version 3.3.0 includes improved cookie handling and somewhat improved logic to deal with network issues.
This is a Python based extension for LibreOffice Calc to make stock market, index and FX data available in Calc
spreadsheets - currently supporting Yahoo's and Financial Times' finance websites using old-fashioned web scraping.
## Overview
This is a Python based extension for LibreOffice Calc to make market data available in Calc
spreadsheets - currently supporting Yahoo's (FX, crypto, equities, indices, futures, options) and Financial Times'
(FX, equities, indices, futures) websites using old-fashioned web scraping.
Starting with version 3.1.0, we received a contribution to get crypto data directly from Coinbase
@@ -30,7 +33,8 @@ Only Yahoo has historic data available.
There is a file **examples.ods** there too with usage examples and possible arguments to functions.
You have to check the respective websites to work out what symbol is the right one for you. If a website doesn't have
You have to check the respective websites to work out what symbol is the right one for you. Make sure today or the date
requested is a trading day (exchange is not closed). If a website doesn't have
the symbol/asset you want, this extension can't help you either. Having said that, I mostly look at US and West European
equities, ETFs and mutual funds and major FX rates - if you have issues with the data available for other assets or
assets in other regions, drop me a line (best to include full URLs and possibly the same asset listed on more than one
@@ -64,8 +68,8 @@ You can either specify numbers or names (lower or upper case) - not all bits are
| PREV_CLOSE | 5 | Yes | Yes |||
| OPEN | 6 | Yes | Yes | Yes | Yes ||
| CHANGE | 7 | Yes | Yes |||
| LAST_PRICE_DATE | 8 | Yes | Yes | | | yyyy-mm-dd |
| LAST_PRICE_TIME | 10 | Yes | Yes |||
| LAST_PRICE_DATE | 8 | | Yes | | | yyyy-mm-dd |
| LAST_PRICE_TIME | 10 | | Yes |||
| CHANGE_IN_PERCENT | 11 | Yes | Yes |||
| LOW | 14 | Yes | Yes | Yes | Yes ||
| HIGH | 16 | Yes | Yes | Yes | Yes ||
@@ -89,6 +93,7 @@ You can either specify numbers or names (lower or upper case) - not all bits are
| EXPIRY_DATE | 74 | Yes (on options) | No | | | yyyy-mm-dd |
| SHARES_OUT | 75 | Yes | Yes | | ||
| FREE_FLOAT | 76 | Yes | Yes | | ||
| SETTLEMENT_DATE | 77 | Yes (on futures) | | | | yyyy-mm-dd |
| CLOSE | 90 | No | No | Yes | ||
| ADJ_CLOSE | 91 | No | No | Yes | ||
| SECTOR | 98 | Yes | Yes |||
@@ -132,13 +137,10 @@ python3 -m unittest discover src
### Tested with:
- Windows 10 / LibreOffice Calc 7.1.2.2 / Python 3.8.8
- Ubuntu 20.04.5 / LibreOffice Calc 6.4.7.2 / Python 3.8.10
- Ubuntu 22.04.1 / LibreOffice Calc 7.3.7.2 / Python 3.10.6
- MacOS 10.15.7 / LibreOffice Calc 7.2.0.4 / Python 3.8.10
(Previous versions)
- Debian 10.3 / LibreOffice Calc 6.1.5.2 / Python 3.7.3
- Ubuntu 20.10 / LibreOffice Calc 7.0.3.1 / Python 3.8.6
- Ubuntu 19.10 / LibreOffice Calc 6.3.5.2 / Python 3.7.6
- Ubuntu 19.04 / LibreOffice Calc 6.2 / Python 3.7.3
- Ubuntu 20.04.5 / LibreOffice Calc 6.4.7.2 / Python 3.8.10
- Ubuntu 18.04.5 / LibreOffice Calc 6 / Python 3.6.9
- Ubuntu 18.04 / LibreOffice Calc 6 / Python 3.6.7
+18 -9
View File
@@ -29,13 +29,23 @@ mkdir "${PWD}"/build/META-INF/
# Compile the binaries
echo "Calling idlc..."
idlc -w -verbose "${PWD}"/idl/XFinancials.idl
if command -v idlc &> /dev/null
then
echo "Calling idlc..."
idlc -w -verbose "${PWD}"/idl/XFinancials.idl
echo "Calling regmerge..."
regmerge -v "${PWD}"/build/XFinancials.rdb UCR "${PWD}"/idl/XFinancials.urd
echo "Calling regmerge..."
regmerge -v "${PWD}"/build/XFinancials.rdb UCR "${PWD}"/idl/XFinancials.urd
rm "${PWD}"/idl/XFinancials.urd
rm "${PWD}"/idl/XFinancials.urd
else
# LibreOffice 7.4+
export unoTypes=/usr/lib/libreoffice/program/types.rdb
export offTypes=/usr/lib/libreoffice/program/types/offapi.rdb
echo "Calling unoidl-write..."
unoidl-write $unoTypes $offTypes "${PWD}"/idl/XFinancials.idl "${PWD}"/build/XFinancials.rdb
fi
echo "Generating meta files..."
python3 "${PWD}"/src/generate_metainfo.py
@@ -47,7 +57,6 @@ cp -f "${PWD}"/src/jsonParser.py "${PWD}"/build/
cp -f "${PWD}"/src/naivehtmlparser.py "${PWD}"/build/
cp -f "${PWD}"/src/tz.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_ft.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_google.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_yahoo.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_coinbase.py "${PWD}"/build/
@@ -55,11 +64,11 @@ cp -f "${PWD}"/src/financials_coinbase.py "${PWD}"/build/
TMPFILE=`mktemp`
wget "https://files.pythonhosted.org/packages/d4/70/d60450c3dd48ef87586924207ae8907090de0b306af2bce5d134d78615cb/python_dateutil-2.8.1-py2.py3-none-any.whl" -O $TMPFILE
wget "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE dateutil/\* -d "${PWD}"/build/
rm $TMPFILE
wget "https://files.pythonhosted.org/packages/70/94/784178ca5dd892a98f113cdd923372024dc04b8d40abe77ca76b5fb90ca6/pytz-2021.1-py2.py3-none-any.whl" -O $TMPFILE
wget "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE pytz/\* -d "${PWD}"/build/
rm $TMPFILE
@@ -68,7 +77,7 @@ unzip $TMPFILE pyparsing.py -d "${PWD}"/build/
rm $TMPFILE
# Windows LibreOffice 7.1 Python is missing this...
wget "https://files.pythonhosted.org/packages/ee/ff/48bde5c0f013094d729fe4b0316ba2a24774b3ff1c52d924a8a4cb04078a/six-1.15.0-py2.py3-none-any.whl" -O $TMPFILE
wget "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE six.py -d "${PWD}"/build/
rm $TMPFILE
BIN
View File
Binary file not shown.
+58 -30
View File
@@ -43,30 +43,24 @@ class BaseClient:
self.connections = {}
self.cookies = cookiejar.CookieJar()
self.last_url = None
self.redirect_count = 0 # will be set later
self.basedir = os.path.join(str(pathlib.Path.home()), '.financials-extension')
os.makedirs(self.basedir, exist_ok=True)
user_agents = [
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/96.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/97.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4104.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4149.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/120.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/121.0'
]
self.default_headers = {
@@ -80,16 +74,15 @@ class BaseClient:
self.response = None
def request(self, method: str, url: str, data=None, headers={}, cookies=[], **kwargs):
def request(self, method: str, url: str, data=None, headers={}, **kwargs):
_headers = self.default_headers.copy()
if headers:
for key, value in headers.items():
_headers[key] = value
if cookies:
for c in cookies:
self.cookies.set_cookie(c)
if method == 'POST' and 'Content-Type' not in _headers:
_headers['Content-Type'] = 'application/x-www-form-urlencoded'
connection = None
@@ -107,7 +100,7 @@ class BaseClient:
connection = HTTPConnection(host, **kwargs) if scheme == 'http:' else HTTPSConnection(host, **kwargs)
logger.debug('Creating request -----------------------------------------------------')
logger.info("url='%s'", url)
logger.debug("%s %s", method, url)
self.last_url = url
@@ -127,7 +120,7 @@ class BaseClient:
logger.debug('Processing response --------------------------------------------------')
# logger.debug('response.status={}'.format(response.status))
logger.debug('response.status=%s', response.status)
for key, value in response.getheaders():
logger.debug('Header: %s=%s', key, value)
@@ -138,15 +131,28 @@ class BaseClient:
def urlopen(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
self.response = self.request('POST' if data else 'GET', url, data, headers, cookies, **kwargs)
if cookies:
for c in cookies:
self.cookies.set_cookie(c)
self.last_url = None
self.response = self.request('POST' if data else 'GET', url, data, headers, **kwargs)
text = self.response.read()
# Allow redirects - used by Yahoo for some cookie based consent
redirect_count = 3
self.redirect_count = 5
while 300 <= self.response.status < 400 and redirect_count >= 0:
# (for Yahoo) AWS CloudFront occasionally returns an incorrect, cached error responses
# try mitigating by re-requesting straight away
if 400 <= self.response.status < 500:
if self.response.getheader('X-Cache') == 'Error from cloudfront':
self.response = self.request('POST' if data else 'GET', url, data, headers, **kwargs)
text = self.response.read()
redirect_count -= 1
while 300 <= self.response.status < 400 and self.redirect_count >= 0:
self.redirect_count -= 1
location = self.response.getheader('Location')
if location and redirect:
@@ -155,13 +161,15 @@ class BaseClient:
scheme, _, host, path = url.split('/', 3)
location = '{}//{}{}'.format(scheme, host, location)
self.response = self.request('POST' if data else 'GET', location, data, headers, cookies, **kwargs)
self.response = self.request('GET', location, None, headers, **kwargs)
text = self.response.read()
else:
raise RedirectException(location)
if self.response.status >= 400:
logger.warning("last_url='%s' status=%s headers=%s", self.last_url, self.response.status,
'\n'.join(sorted(self.response.headers.__str__().splitlines(), key=lambda l: l.lower())))
raise HttpException(url, self.response.status)
if self.response.getheader('Content-Encoding') == 'gzip':
@@ -195,6 +203,7 @@ class BaseClient:
tick[Datacode.EXPIRY_DATE] = None
tick[Datacode.EX_DIV_DATE] = None
tick[Datacode.FREE_FLOAT] = None
tick[Datacode.SETTLEMENT_DATE] = None
tick[Datacode.HIGH] = None
tick[Datacode.HIGH_52_WEEK] = None
tick[Datacode.INDUSTRY] = None
@@ -215,6 +224,11 @@ class BaseClient:
tick[Datacode.TIMEZONE] = None
tick[Datacode.VOLUME] = None
tick[Datacode.YAHOO_SUMMARY_RECEIVED] = False
tick[Datacode.YAHOO_STATISTIC_RECEIVED] = False
tick[Datacode.YAHOO_PROFILE_RECEIVED] = False
tick[Datacode.TIMESTAMP] = None
return tick
def _return_value(self, data: dict, datacode: int):
@@ -324,6 +338,12 @@ class BaseClient:
elif datacode == Datacode.FREE_FLOAT.value and Datacode.FREE_FLOAT in data:
return data[Datacode.FREE_FLOAT]
elif datacode == Datacode.SETTLEMENT_DATE.value and Datacode.SETTLEMENT_DATE in data:
if data[Datacode.SETTLEMENT_DATE]:
return data[Datacode.SETTLEMENT_DATE].isoformat()
else:
return data[Datacode.SETTLEMENT_DATE]
elif datacode == Datacode.SHARES_OUT.value and Datacode.SHARES_OUT in data:
return data[Datacode.SHARES_OUT]
@@ -371,3 +391,11 @@ class BaseClient:
pass
return None
def close(self):
for connection in self.connections.values():
try:
connection.close()
except BaseException:
pass
self.connections = {}
+4
View File
@@ -46,6 +46,7 @@ class Datacode(Enum):
EXPIRY_DATE = 74
SHARES_OUT = 75
FREE_FLOAT = 76
SETTLEMENT_DATE = 77
CLOSE = 90
ADJ_CLOSE = 91
@@ -59,6 +60,9 @@ class Datacode(Enum):
NAME = 104
TIMEZONE = 105
YAHOO_SUMMARY_RECEIVED = 996
YAHOO_STATISTIC_RECEIVED = 997
YAHOO_PROFILE_RECEIVED = 998
TIMESTAMP = 999
@classmethod
+6 -6
View File
@@ -30,7 +30,7 @@ os.makedirs(basedir, exist_ok=True)
logging.basicConfig(
handlers=[logging.FileHandler(filename=os.path.join(basedir, 'extension.log'), encoding='utf-8', mode='a+')],
format="%(asctime)s %(name)s %(levelname)s %(message)s",
level=logging.WARN)
level=logging.INFO)
# Add current directory to import path
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
@@ -54,7 +54,6 @@ import pyparsing
import six
from datacode import Datacode
import financials_google as google
import financials_yahoo as yahoo
import financials_coinbase as coinbase
import financials_ft as ft
@@ -95,7 +94,6 @@ class FinancialsImpl(unohelper.Base, Financials):
def __init__(self, ctx):
self.ctx = ctx
self.google = google.createInstance(ctx)
self.yahoo = yahoo.createInstance(ctx)
self.coinbase = coinbase.createInstance(ctx)
self.ft = ft.createInstance(ctx)
@@ -136,9 +134,6 @@ class FinancialsImpl(unohelper.Base, Financials):
ticker = str(ticker).strip()
source = str(source).upper()
# if source == 'GOOGLE':
# s = self.google.getRealtime(ticker, datacode)
# el
if source == 'YAHOO':
s = self.yahoo.getRealtime(ticker, datacode)
elif source == 'FT':
@@ -273,6 +268,11 @@ class FinancialsImpl(unohelper.Base, Financials):
return s
def close(self):
if self.yahoo: self.yahoo.close()
if self.coinbase: self.coinbase.close()
if self.ft: self.ft.close()
def createInstance(ctx):
return FinancialsImpl(ctx)
+7 -4
View File
@@ -53,7 +53,7 @@ class Coinbase(BaseClient):
# use cached value for up to 60 seconds
if ticker in self.realtime:
tick = self.realtime[ticker]
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
if Datacode.TIMESTAMP in tick and type(tick[Datacode.TIMESTAMP]) == float and time.time() - 60 < tick[Datacode.TIMESTAMP]:
return self._return_value(tick, datacode)
else:
del self.realtime[ticker]
@@ -63,20 +63,22 @@ class Coinbase(BaseClient):
try:
text = self.urlopen(url, redirect=True, data=None, headers=None)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
del self.realtime[ticker]
return 'Coinbase.getRealtime({}, {}) - urlopen: {}'.format(ticker, datacode, e)
try:
with open(os.path.join(self.basedir, 'coinbase-{}.json'.format(ticker)), "w", encoding="utf-8") as text_file:
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
print(f"<!-- '{self.last_url}' -->\r\n\r\n{text}", file=text_file)
except BaseException:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
logger.exception("BaseException open/write ticker=%s datacode=%s", ticker, datacode)
try:
results = json.loads(text)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Coinbase.getRealtime({}, {}) - crumb: {}'.format(ticker, datacode, e)
try:
@@ -101,6 +103,7 @@ class Coinbase(BaseClient):
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Coinbase.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
return self._return_value(self.realtime[ticker], datacode)
+6 -4
View File
@@ -63,7 +63,7 @@ class FT(BaseClient):
# use cached value for up to 60 seconds
if ticker in self.realtime:
tick = self.realtime[ticker]
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
if Datacode.TIMESTAMP in tick and type(tick[Datacode.TIMESTAMP]) == float and time.time() - 60 < tick[Datacode.TIMESTAMP]:
return self._return_value(tick, datacode)
else:
del self.realtime[ticker]
@@ -80,7 +80,8 @@ class FT(BaseClient):
try:
text = self.urlopen(url, redirect=True, data=None, headers=None)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
del self.realtime[ticker]
return f'FT.getRealtime({ticker}, {datacode}) - urlopen endpoint: {str(e)}'
try:
@@ -88,7 +89,7 @@ class FT(BaseClient):
with open(os.path.join(self.basedir, f'ft-{temp}.html'), "w", encoding="utf-8") as text_file:
print(f"<!-- '{self.last_url}' -->\r\n\r\n{text}", file=text_file)
except BaseException:
logger.exception("BaseException ticker=%s datacode=%s %s", ticker, datacode)
logger.exception("BaseException open/write ticker=%s datacode=%s %s", ticker, datacode)
tick[Datacode.TIMESTAMP] = time.time()
@@ -299,9 +300,10 @@ class FT(BaseClient):
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return f'FT.getRealtime({ticker}, {datacode}) - process: {str(e)}'
logger.info(tick)
logger.debug(tick)
return self._return_value(self.realtime[ticker], datacode)
-248
View File
@@ -1,248 +0,0 @@
# financials_google.py
#
# license: GNU LGPL
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
import html
import logging
import os
import re
import time
import xml.etree.ElementTree as ET
import dateutil
from baseclient import BaseClient, RedirectException
from datacode import Datacode
from naivehtmlparser import NaiveHTMLParser
from tz import whois_timezone_info
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
def handle_abbreviations(s):
s = str(s).strip()
if s.endswith('M'):
return float(s[:-1]) * 1000000
elif s.endswith('B'):
return float(s[:-1]) * 1000000000
elif s.endswith('T'):
return float(s[:-1]) * 1000000000000
return float(s)
def un_span(s):
return re.sub(r'<span [^>]*>', '', s).replace('</span>', '')
class Google(BaseClient):
def __init__(self, ctx):
super().__init__()
self.realtime = {}
self.location = None
def getRealtime(self, ticker: str, datacode: int):
"""
Retrieve realtime data for ticker from Google Finance and cache it for further lookups
:param ticker: the ticker symbol e.g. LON:VOD
:param datacode: the requested datacode
:return:
"""
# remove white space
ticker = "".join(ticker.split())
# use cached value for up to 60 seconds
if ticker in self.realtime:
tick = self.realtime[ticker]
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
return self._return_value(tick, datacode)
else:
del self.realtime[ticker]
q_param = 'q=' + ticker
if not self.location:
url = 'https://www.google.com/search?hl=en&tbm=fin&' + q_param
try:
self.urlopen(url, redirect=False)
except RedirectException as e:
self.location = e.location.replace('&' + q_param, '')
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Google.getRealtime(\'{}\', {}) - location: {}'.format(ticker, datacode, e)
if not self.location:
url = 'https://www.google.com/search?tbm=fin&' + q_param
else:
url = self.location + '&' + q_param
try:
text = self.urlopen(url)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Google.getRealtime(\'{}\', {}) - urlopen: {} {}'.format(ticker, datacode, e, url)
try:
temp = ticker.replace(':', '_') # Windows can't have ':' in file names
with open(os.path.join(self.basedir, 'google-{}.html'.format(temp)), "w", encoding="utf-8") as text_file:
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
if ticker not in self.realtime:
self.realtime[ticker] = self.get_ticker()
tick = self.realtime[ticker]
tick[Datacode.TIMESTAMP] = time.time()
try:
r = '<span[^>]+role="heading"[^>]+>(.*?)</span>'
pattern = re.compile(r)
match = pattern.search(text)
if not match:
return None
start = match.span(0)[1]
tick[Datacode.NAME] = self.save_wrapper(
lambda: html.unescape(un_span(match.group(1)).strip()))
# next div is TICKER
r = '<div [^>]*><div [^>]*>(.*?)</div></div>'
pattern = re.compile(r)
match = pattern.search(text, start)
if not match:
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
ticker = self.save_wrapper(
lambda: html.unescape(match.group(1)).replace(' ', ''))
tick[Datacode.EXCHANGE] = self.save_wrapper(lambda: ticker.split(':')[0])
tick[Datacode.TICKER] = self.save_wrapper(lambda: ticker.split(':')[1])
except BaseException as e:
return 'Google.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
try:
r = '<sticky-header [^>]*>(.*?)</sticky-header>'
pattern = re.compile(r, flags=re.DOTALL)
match = re.search(pattern, text)
if match:
text = match.group(1)
else:
return 'Data for \'{}\' not found'.format(ticker)
parser = NaiveHTMLParser()
root = parser.feed(text)
parser.close()
cards = root.findall('.//g-card-section')
if len(cards) < 4:
return 'Data for \'{}\' not found'.format(ticker)
header = cards[1]
tick[Datacode.LAST_PRICE] = self.save_wrapper(
lambda: float(
html.unescape(header.find('./span[1]/span[1]/span[1]').text).replace(',', '').strip()))
tick[Datacode.CURRENCY] = self.save_wrapper(
lambda: html.unescape(header.find('./span[1]/span[1]/span[2]').text).strip())
tick[Datacode.CHANGE] = self.save_wrapper(
lambda: float(
html.unescape(header.find('./span[2]/span[1]').text).replace('', '-').replace(',', '').strip()))
# percentage is always wrapped in (...) and always positive even if there is a price drop
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(
lambda: float(
html.unescape(header.find('./span[2]/span[2]/span[1]').text).strip()
.replace('(', '').replace(')', '').replace('%', '')))
try:
value = html.unescape(header.find('./div[1]/span[1]/span[2]').text).replace('·', '').strip()
logger.debug(value)
dt = dateutil.parser.parse(value, yearfirst=True, dayfirst=False, tzinfos=whois_timezone_info)
tick[Datacode.LAST_PRICE_DATE] = dt.date()
tick[Datacode.LAST_PRICE_TIME] = dt.time()
time_bits = value.split(' ')
if len(time_bits) >= 4:
tick[Datacode.TIMEZONE] = time_bits[-1]
except BaseException as e:
pass
footer = cards[3]
logger.debug(ET.tostring(footer))
# parse 'footer' for remaining fields
table = footer.find('./div[1]/div[1]/div[1]/table[1]')
tick[Datacode.OPEN] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[1]/td[2]').text).replace(',', '').strip()))
tick[Datacode.HIGH] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[2]/td[2]').text).replace(',', '').strip()))
tick[Datacode.LOW] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[3]/td[2]').text).replace(',', '').strip()))
tick[Datacode.MARKET_CAP] = self.save_wrapper(
lambda: handle_abbreviations(
html.unescape(table.find('./tr[4]/td[2]').text).replace(',', '').replace('-', '').strip()))
table = footer.find('./div[1]/div[1]/div[2]/table[1]')
# for indices: first item on right side is LOW
if html.unescape(table.find('./tr[1]/td[1]').text).strip() == 'Low':
tick[Datacode.LOW] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[1]/td[2]').text).replace(',', '').strip()))
tick[Datacode.PREV_CLOSE] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[2]/td[2]').text).replace(',', '').strip()))
tick[Datacode.HIGH_52_WEEK] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[3]/td[2]').text).replace(',', '').strip()))
tick[Datacode.LOW_52_WEEK] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[4]/td[2]').text).replace(',', '').strip()))
logger.info(tick)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Google.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
return self._return_value(self.realtime[ticker], datacode)
def getHistoric(self, ticker, datacode, date):
return 'Google.getHistoric: Historic Data not implemented.'
def createInstance(ctx):
return Google(ctx)
+263 -101
View File
@@ -9,18 +9,14 @@
import csv
import datetime
import html
import logging
import os
import pprint
import re
import time
import urllib.parse
from http import cookiejar
import dateutil.parser
import pytz
import jsonParser
from baseclient import BaseClient, HttpException
@@ -33,6 +29,22 @@ logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
def default(obj, prop, fallback=''):
try:
if obj is None or property is None:
return fallback
v = None
if hasattr(obj, prop):
v = getattr(obj, prop)
elif prop in obj:
v = obj[prop]
return v if v is not None else fallback
except:
pass
return fallback
def handle_abbreviations(s):
s = str(s).strip()
if s.endswith('M'):
@@ -43,30 +55,6 @@ def handle_abbreviations(s):
return float(s[:-1]) * 1000000000000
return float(s)
def raw(m, key, default=0.0):
try:
return m[key]['raw']
except:
pass
return default
def fmt(m, key, default=0.0):
try:
return m[key]['fmt']
except:
pass
return default
def cookie(name, value):
return cookiejar.Cookie(version=0, name=name, value=value,
port=None, port_specified=False, domain=".yahoo.com", domain_specified=True,
domain_initial_dot=True, path="/", path_specified=True, secure=True, expires=None,
discard=False, comment=None, comment_url=None, rest=dict())
class Yahoo(BaseClient):
def __init__(self, ctx):
@@ -90,7 +78,7 @@ class Yahoo(BaseClient):
ticks = {}
for row in reader:
tick = self.get_ticker()
tick = self.get_ticker()
try:
tick[Datacode.OPEN] = float(row['Open'])
tick[Datacode.LOW] = float(row['Low'])
@@ -119,84 +107,145 @@ class Yahoo(BaseClient):
# remove white space
ticker = "".join(ticker.split())
needStatistics = datacode in [Datacode.SHARES_OUT.value, Datacode.FREE_FLOAT.value, Datacode.PAYOUT_RATIO.value]
needProfile = datacode in [Datacode.SECTOR.value, Datacode.INDUSTRY.value]
# use cached value for up to 60 seconds
if ticker in self.realtime:
tick = self.realtime[ticker]
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
return self._return_value(tick, datacode)
if Datacode.TIMESTAMP in tick and type(tick[Datacode.TIMESTAMP]) == float and time.time() - 60 < tick[Datacode.TIMESTAMP]:
if (tick[Datacode.YAHOO_STATISTIC_RECEIVED] or not needStatistics) and (
tick[Datacode.YAHOO_PROFILE_RECEIVED] or not needProfile) and (
tick[Datacode.YAHOO_SUMMARY_RECEIVED]):
return self._return_value(tick, datacode)
else:
del self.realtime[ticker]
if ticker not in self.realtime:
self.realtime[ticker] = self.get_ticker()
tick = self.realtime[ticker]
if needStatistics:
return self.getRealtimeStatistics(ticker, datacode)
url = 'https://finance.yahoo.com/quote/{}?p={}'.format(ticker, ticker)
if needProfile:
return self.getRealtimeProfile(ticker, datacode)
cookies = [
cookie("A1", "d=AQABBDcIZWMCEHYhFYqQ7qyTvvD2eAT87mcFEgABCAGDlGPBY_bPb2UB9qMAAAcILwhlY6iIogg&S=AQAAAjZvTuAn1nH4h71eKJtCEHk"),
cookie("A1S", "d=AQABBDcIZWMCEHYhFYqQ7qyTvvD2eAT87mcFEgABCAGDlGPBY_bPb2UB9qMAAAcILwhlY6iIogg&S=AQAAAjZvTuAn1nH4h71eKJtCEHk&j=GDPR"),
cookie("A3", "d=AQABBDcIZWMCEHYhFYqQ7qyTvvD2eAT87mcFEgABCAGDlGPBY_bPb2UB9qMAAAcILwhlY6iIogg&S=AQAAAjZvTuAn1nH4h71eKJtCEHk"),
cookie("GUC", "AQABCAFjlINjwUIcFQQQ&s=AQAAAFOQKXn7&g=Y5M5Jg"),
cookie("GUCS", "ASHFadZS"),
cookie("maex", "{\"v2\":{}}"),
cookie("PRF", "t=TQQQ%2BASTO.L%2BCHMI%2BVFIAX%2BIBM%2BXMR-USD%2BMVV%2BSECU-B.ST%2BMSFT"),
cookie("thamba", "1")
]
return self.getRealtimeSummary(ticker, datacode)
def getData(self, url, ticker, datacode, html_file):
try:
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=cookies)
text = self.urlopen(url, redirect=True)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - urlopen: {}'.format(ticker, datacode, e)
logger.exception("BaseException (1) ticker=%s datacode=%s last_url=%s redirect_count=%s %s",
ticker, datacode, self.last_url, self.redirect_count, e)
return None
try:
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w", encoding="utf-8") as text_file:
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
except BaseException:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
try:
r = '"crumb":"([^"]{11})"'
pattern = re.compile(r)
match = pattern.search(text)
if match:
self.crumb = match.group(1)
with open(os.path.join(self.basedir, html_file), "w", encoding="utf-8") as text_file:
print(f"<!-- '{self.last_url}' -->\r\n\r\n{text}", file=text_file)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - crumb: {}'.format(ticker, datacode, e)
logger.exception("BaseException (2) ticker=%s datacode=%s %s", ticker, datacode, e)
tick[Datacode.TIMESTAMP] = time.time()
if not text:
return None
try:
parser = NaiveHTMLParser()
root = parser.feed(text)
parser.close()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - HTML parsing: {}'.format(ticker, datacode, e)
logger.exception("BaseException (3) ticker=%s datacode=%s - HTML parsing - %s", ticker, datacode, e)
return None
form = root.find(f".//form[@class='consent-form']")
if form:
inputs = form.findall(f".//input")
if inputs:
data = {'reject': 'reject'}
for d in inputs:
data[d.attrib['name']] = d.attrib['value']
try:
text = self.urlopen(self.last_url, redirect=True, data=urllib.parse.urlencode(data))
except BaseException as e:
logger.exception("BaseException (4) ticker=%s datacode=%s last_url=%s redirect_count=%s %s",
ticker, datacode, self.last_url, self.redirect_count, e)
return text
def getRealtimeSummary(self, ticker, datacode):
"""
Retrieve realtime data from Yahoo Finance - Summary tab
"""
tick = self.realtime[ticker]
url = 'https://finance.yahoo.com/quote/{}?p={}'.format(ticker, ticker)
text = self.getData(url, ticker, datacode, f'yahoo-{ticker}.html')
if text is None:
del self.realtime[ticker]
return 'Yahoo.getRealtimeSummary({}, {}) - getData'.format(ticker, datacode)
try:
r = '"crumb":"([^"]{11})"'
pattern = re.compile(r)
match = pattern.search(text)
if match:
self.crumb = match.group(1)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Yahoo.getRealtimeSummary({}, {}) - crumb: {}'.format(ticker, datacode, e)
try:
parser = NaiveHTMLParser()
root = parser.feed(text)
parser.close()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtimeSummary({}, {}) - HTML parsing: {}'.format(ticker, datacode, e)
try:
if not root:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtimeSummary({}, {}) - root missing'.format(ticker, datacode)
tick[Datacode.TICKER] = ticker
tick[Datacode.TIMESTAMP] = time.time()
tick[Datacode.YAHOO_SUMMARY_RECEIVED] = True
parsed = {}
found = root.findall(f".//fin-streamer[@data-symbol='{ticker}']")
for d in found:
if hasattr(d, 'attrib') and 'data-field' in d.attrib and 'value' in d.attrib:
parsed[d.attrib['data-field']] = d.attrib['value'].replace('', '-').replace(',', '').strip()
if hasattr(d, 'attrib') and 'data-field' in d.attrib:
value = default(d.attrib, 'value') or default(d.attrib, 'data-value')
parsed[d.attrib['data-field']] = value.replace('', '-').replace(',', '').strip()
# for futures "regularMarketVolume" is from actual future ticker (potentially different to requested one)
if 'regularMarketVolume' not in parsed:
found = root.findall(f".//fin-streamer[@data-field='regularMarketVolume']")
for d in found:
if hasattr(d, 'attrib') and 'data-field' in d.attrib and 'data-symbol' in d.attrib:
value = default(d.attrib, 'value') or default(d.attrib, 'data-value')
parsed[d.attrib['data-field']] = value.replace('', '-').replace(',', '').strip()
tick[Datacode.TICKER] = default(d.attrib, 'data-symbol').strip()
found = root.findall(f".//td[@data-test]")
for d in found:
if d:
span = d.find('./span')
if hasattr(span, 'text'):
parsed[d.attrib['data-test']] = span.text.replace('', '-').replace(',', '').strip()
if hasattr(d, 'attrib') and hasattr(span, 'text'):
parsed[d.attrib['data-test']] = default(span, 'text').replace('', '-').replace(',', '').strip()
else:
if hasattr(d, 'attrib') and hasattr(d, 'text'):
parsed[d.attrib['data-test']] = d.text.replace('', '-').replace(',', '').strip()
parsed[d.attrib['data-test']] = default(d, 'text').replace('', '-').replace(',', '').strip()
if 'regularMarketPrice' not in parsed:
return None
@@ -206,8 +255,7 @@ class Yahoo(BaseClient):
tick[Datacode.CHANGE] = self.save_wrapper(lambda: float(parsed['regularMarketChange']))
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(lambda: float(parsed['regularMarketChangePercent']))
t = parsed['DAYS_RANGE-value'] if 'DAYS_RANGE-value' in parsed else ''
t = t.split(' - ')
t = default(parsed, 'DAYS_RANGE-value').split(' - ')
tick[Datacode.LOW] = self.save_wrapper(lambda: float(t[0]))
tick[Datacode.HIGH] = self.save_wrapper(lambda: float(t[1]))
@@ -218,44 +266,38 @@ class Yahoo(BaseClient):
tick[Datacode.EPS] = self.save_wrapper(lambda: float(parsed['EPS_RATIO-value']))
tick[Datacode.PE_RATIO] = self.save_wrapper(lambda: float(parsed['PE_RATIO-value']))
t = parsed['DIVIDEND_AND_YIELD-value'] if 'DIVIDEND_AND_YIELD-value' in parsed else ''
t = t.replace('(', '').replace(')', '').replace('%', '').strip().split(' ')
t = default(parsed, 'DIVIDEND_AND_YIELD-value').replace('(', '').replace(')', '').replace('%', '').strip().split(' ')
tick[Datacode.DIV] = self.save_wrapper(lambda: float(t[0]))
tick[Datacode.DIV_YIELD] = self.save_wrapper(lambda: float(t[1])/100.0)
tick[Datacode.EX_DIV_DATE] = self.save_wrapper(
lambda: dateutil.parser.parse(str(parsed['EX_DIVIDEND_DATE-value']), yearfirst=True, dayfirst=False).date())
lambda: dateutil.parser.parse(parsed['EX_DIVIDEND_DATE-value'], yearfirst=True, dayfirst=False).date())
# https://finance.yahoo.com/quote/IBM/key-statistics?p=IBM
# tick[Datacode.SHARES_OUT] = float(raw(defaultKeyStatistics, 'sharesOutstanding'))
# tick[Datacode.FREE_FLOAT] = float(raw(defaultKeyStatistics, 'floatShares'))
# tick[Datacode.PAYOUT_RATIO] = float(raw(summaryDetail, 'payoutRatio'))
t = parsed['FIFTY_TWO_WK_RANGE-value'] if 'FIFTY_TWO_WK_RANGE-value' in parsed else ''
t = t.split(' - ')
t = default(parsed, 'FIFTY_TWO_WK_RANGE-value').split(' - ')
tick[Datacode.LOW_52_WEEK] = self.save_wrapper(lambda: float(t[0]))
tick[Datacode.HIGH_52_WEEK] = self.save_wrapper(lambda: float(t[1]))
tick[Datacode.MARKET_CAP] = self.save_wrapper(lambda: float(handle_abbreviations(parsed['MARKET_CAP-value'])))
t = parsed['BID-value'] if 'BID-value' in parsed else ''
t = t.split(' x ')
t = default(parsed, 'BID-value').split(' x ')
tick[Datacode.BID] = self.save_wrapper(lambda: float(t[0]))
tick[Datacode.BIDSIZE] = self.save_wrapper(lambda: float(t[1]))
t = parsed['ASK-value'] if 'ASK-value' in parsed else ''
t = t.split(' x ')
t = default(parsed, 'ASK-value').split(' x ')
tick[Datacode.ASK] = self.save_wrapper(lambda: float(t[0]))
tick[Datacode.ASKSIZE] = self.save_wrapper(lambda: float(t[1]))
tick[Datacode.EXPIRY_DATE] = self.save_wrapper(
lambda: dateutil.parser.parse(str(parsed['EXPIRE_DATE-value']), yearfirst=True, dayfirst=False).date())
lambda: dateutil.parser.parse(parsed['EXPIRE_DATE-value'], yearfirst=True, dayfirst=False).date())
r = '<div id="quote-market-notice"[^>]*><span>([^>]*)</span></div>'
tick[Datacode.SETTLEMENT_DATE] = self.save_wrapper(
lambda: dateutil.parser.parse(parsed['SETTLEMENT_DATE-value'], yearfirst=True, dayfirst=False).date())
r = '<div id="quote-market-notice"[^>]*><span>([^>]*?)(. Market open.)?</span></div>'
match = re.compile(r, flags=re.DOTALL).search(text)
if match:
t = html.unescape(match.group(1)).strip().split(' ')
tick[Datacode.TIMEZONE] = self.save_wrapper(lambda: pytz.timezone(t[-1]))
tick[Datacode.TIMEZONE] = self.save_wrapper(lambda: t[-1])
# if quoteType:
# t = int(price['regularMarketTime'])
@@ -267,17 +309,17 @@ class Yahoo(BaseClient):
# tick[Datacode.LAST_PRICE_DATE] = dt.date()
# tick[Datacode.LAST_PRICE_TIME] = dt.time()
tick[Datacode.TICKER] = ticker
r = '<span>(\\w+?) - [^>]*Currency in ([\\w]+)[^>]*</span>'
r = '<span>([ \\w]+?) - [^>]*Currency in ([\\w]+)[^>]*</span>'
match = re.compile(r, flags=re.DOTALL).search(text)
if match:
tick[Datacode.EXCHANGE] = self.save_wrapper(lambda: html.unescape(match.group(1)).strip())
tick[Datacode.CURRENCY] = self.save_wrapper(lambda: html.unescape(match.group(2)).strip())
# fallback for yield on US mutual funds and ETFs, which is in different field
# fallback for dividend/yield on mutual funds and ETFs
if not tick[Datacode.DIV]:
tick[Datacode.DIV] = self.save_wrapper(lambda: float(parsed['LAST_DIVIDEND-value']))
if not tick[Datacode.DIV_YIELD]:
tick[Datacode.DIV_YIELD] = self.save_wrapper(lambda: float(parsed['LAST_DIVIDEND-value']))
tick[Datacode.DIV_YIELD] = self.save_wrapper(lambda: float(parsed['TD_YIELD-value'].replace('%', '').strip())/100.0)
tick[Datacode.NAME] = self.save_wrapper(
lambda: html.unescape(root.find('.//h1').text).strip())
@@ -285,13 +327,133 @@ class Yahoo(BaseClient):
if not tick[Datacode.NAME]:
tick[Datacode.NAME] = tick[Datacode.TICKER]
# https://finance.yahoo.com/quote/IBM/profile?p=IBM
# tick[Datacode.SECTOR] = self.save_wrapper(lambda: str(results['summaryProfile']['sector']))
# tick[Datacode.INDUSTRY] = self.save_wrapper(lambda: str(results['summaryProfile']['industry']))
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
del self.realtime[ticker]
return 'Yahoo.getRealtimeSummary({}, {}) - process: {}'.format(ticker, datacode, e)
return self._return_value(self.realtime[ticker], datacode)
def getRealtimeStatistics(self, ticker, datacode):
"""
Retrieve realtime data from Yahoo Finance - Statistics tab
"""
tick = self.realtime[ticker]
url = 'https://finance.yahoo.com/quote/{}/key-statistics?p={}'.format(ticker, ticker)
text = self.getData(url, ticker, datacode, f'yahoo-{ticker}-statistics.html')
if text is None:
del self.realtime[ticker]
return 'Yahoo.getRealtimeStatistics({}, {}) - getData'.format(ticker, datacode)
try:
parser = NaiveHTMLParser()
root = parser.feed(text)
parser.close()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Yahoo.getRealtimeStatistics({}, {}) - HTML parsing: {}'.format(ticker, datacode, e)
statistics = root.find(".//section[@data-test='qsp-statistics']")
tick[Datacode.TICKER] = ticker
tick[Datacode.TIMESTAMP] = time.time()
tick[Datacode.YAHOO_STATISTIC_RECEIVED] = True
tick[Datacode.SHARES_OUT] = None
tick[Datacode.FREE_FLOAT] = None
tick[Datacode.PAYOUT_RATIO] = None
if statistics is None:
return None
parsed = {}
try:
# Valuation Measures
found = statistics.find('./div[2]/div[1]//table')
if found:
for d in found.findall('.//tr'):
key = d.find('./td[1]/span').text
if key is not None:
parsed[key] = d.find('./td[2]').text
# Stock Price History
found = statistics.find('./div[2]/div[2]/div[1]/div[1]//table')
if found:
for d in found.findall('.//tr'):
key = d.find('./td[1]/span').text
if key is not None:
parsed[key] = d.find('./td[2]').text
# Share Statistics
found = statistics.find('./div[2]/div[2]/div[1]/div[2]//table')
if found:
for d in found.findall('.//tr'):
key = d.find('./td[1]/span').text
if key is not None:
parsed[key] = d.find('./td[2]').text
# Dividends & Splits
found = statistics.find('./div[2]/div[2]/div[1]/div[3]//table')
if found:
for d in found.findall('.//tr'):
key = d.find('./td[1]/span').text
if key is not None:
parsed[key] = d.find('./td[2]').text
except KeyError:
pass
tick[Datacode.SHARES_OUT] = self.save_wrapper(
lambda: float(handle_abbreviations(parsed['Shares Outstanding'])))
tick[Datacode.FREE_FLOAT] = self.save_wrapper(
lambda: float(handle_abbreviations(parsed['Float'])))
tick[Datacode.PAYOUT_RATIO] = self.save_wrapper(
lambda: float(handle_abbreviations(parsed['Payout Ratio'].replace('%', '').strip()))/100.0)
return self._return_value(self.realtime[ticker], datacode)
def getRealtimeProfile(self, ticker, datacode):
"""
Retrieve realtime data from Yahoo Finance - Profile tab
"""
tick = self.realtime[ticker]
url = 'https://finance.yahoo.com/quote/{}/profile?p={}'.format(ticker, ticker)
text = self.getData(url, ticker, datacode, f'yahoo-{ticker}-profile.html')
if text is None:
del self.realtime[ticker]
return 'Yahoo.getRealtimeProfile({}, {}) - getData'.format(ticker, datacode)
try:
parser = NaiveHTMLParser()
root = parser.feed(text)
parser.close()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Yahoo.getRealtimeProfile({}, {}) - HTML parsing: {}'.format(ticker, datacode, e)
tick[Datacode.TICKER] = ticker
tick[Datacode.TIMESTAMP] = time.time()
tick[Datacode.YAHOO_PROFILE_RECEIVED] = True
p = None
if root:
p = root.find(".//*[span='Sector(s)']")
tick[Datacode.SECTOR] = self.save_wrapper(lambda: p.find("./span[2]").text)
tick[Datacode.INDUSTRY] = self.save_wrapper(lambda: p.find("./span[4]").text)
return self._return_value(self.realtime[ticker], datacode)
@@ -319,7 +481,7 @@ class Yahoo(BaseClient):
try:
date_as_dt = dateutil.parser.parse(date, yearfirst=True, dayfirst=False)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
return 'Yahoo.getHistoric({}, {}, {}) - date_as_dt: {}'.format(ticker, datacode, date, e)
if ticker in self.historicdata:
@@ -342,7 +504,7 @@ class Yahoo(BaseClient):
min_tick_date = int(dateutil.parser.parse(min(ticks), yearfirst=True, dayfirst=False).timestamp()) # remember current earliest date
if not self.crumb:
self.getRealtime(ticker, datacode)
self.getRealtime(ticker, Datacode.LAST_PRICE)
if not self.crumb:
return 'Yahoo.getHistoric({}, {}, {}) - crumb missing'.format(ticker, datacode, date)
+1 -1
View File
@@ -14,7 +14,7 @@ import os
cur_dir = os.getcwd()
addin_id = "com.financials.getinfo"
addin_version = "3.2.0"
addin_version = "3.5.0"
addin_displayname = "Financial Market Extension"
addin_publisher_link = "https://github.com/cmallwitz/Financials-Extension"
addin_publisher_name = "The Publisher"
+5
View File
@@ -25,6 +25,11 @@ financials = financials.createInstance(None)
class Test(unittest.TestCase):
@classmethod
def tearDownClass(cls):
# this avoids "ResourceWarning: unclosed..." on cached socket connections
financials.close()
def test_currency(self):
s = financials.getRealtime('ETH-EUR', Datacode.LAST_PRICE.value, 'COINBASE')
self.assertEqual(float, type(s), 'test_currency LAST_PRICE')
+35 -3
View File
@@ -15,6 +15,7 @@ import unittest
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import financials
from datacode import Datacode
import testutils
financials = financials.createInstance(None)
@@ -22,6 +23,11 @@ financials = financials.createInstance(None)
class Test(unittest.TestCase):
@classmethod
def tearDownClass(cls):
# this avoids "ResourceWarning: unclosed..." on cached socket connections
financials.close()
def test_currency(self):
s = financials.getRealtime('EURGBP', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_currency LAST_PRICE')
@@ -134,14 +140,40 @@ class Test(unittest.TestCase):
s = financials.getRealtime('VFIAX', 'LAST_PRICE_TIME', 'FT')
self.assertEqual(str, type(s), 'test_US_mutuals LAST_PRICE_TIME {}'.format(s))
def test_US_futures(self):
# https://markets.ft.com/data/commodities/tearsheet/summary?s=775326843 ESH25:IOM
s = financials.getRealtime('775326843', Datacode.NAME.value, 'FT')
self.assertEqual(str, type(s), 'test_realtime_US_futures NAME {}'.format(s))
self.assertEqual('EMINI S&P MAR5', s, 'test_US_futures NAME {}'.format(s))
s = financials.getRealtime('775326843', Datacode.LAST_PRICE.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures LAST_PRICE {}'.format(s))
# s = financials.getRealtime('775326843', Datacode.OPEN.value, 'FT')
# self.assertEqual(float, type(s), 'test_US_futures OPEN {}'.format(s))
s = financials.getRealtime('775326843', Datacode.VOLUME.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures VOLUME {}'.format(s))
s = financials.getRealtime('775326843', Datacode.LOW_52_WEEK.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('775326843', Datacode.HIGH_52_WEEK.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('775326843', Datacode.CHANGE.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures CHANGE {}'.format(s))
s = financials.getRealtime('775326843', Datacode.CHANGE_IN_PERCENT.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures CHANGE_IN_PERCENT {}'.format(s))
def test_UK_ETF(self):
s = financials.getRealtime('CSP1:LSE:GBX', 'NAME', 'FT')
self.assertEqual(str, type(s), 'test_UK_ETF NAME {}'.format(s))
self.assertEqual('iShares Core S&P 500 UCITS ETF USD (Acc)', s, 'test_UK_ETF NAME {}'.format(s))
s = financials.getRealtime('C060:GER:EUR', 'NAME', 'FT')
self.assertEqual(str, type(s), 'test_UK_ETF NAME {}'.format(s))
s = financials.getRealtime('VERX:LSE:GBP', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_UK_ETF LAST_PRICE {}'.format(s))
-259
View File
@@ -1,259 +0,0 @@
# test_google.py
#
# license: GNU LGPL
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
import argparse
import logging
import sys
import unittest
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import financials
from datacode import Datacode
financials = financials.createInstance(None)
# class Test(unittest.TestCase):
class Test:
def test_currency(self):
s = financials.getRealtime('EURGBP', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertIsNone(s, 'test_currency LAST_PRICE')
def test_UK_equity(self):
s = financials.getRealtime('LON:VOD', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_UK_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('LON:VOD', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual(s, 'VOD', 'test_UK_equity TICKER')
s = financials.getRealtime('LON:VOD', Datacode.NAME.value, 'GOOGLE')
self.assertEqual(type(s), str, 'test_UK_equity NAME')
s = financials.getRealtime('LON:VOD', Datacode.EXCHANGE.value, 'GOOGLE')
self.assertEqual(s, 'LON', 'test_UK_equity EXCHANGE')
s = financials.getRealtime('LON:VOD', Datacode.PREV_CLOSE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_UK_equity PREV_CLOSE {}'.format(s))
# MARKET_CAP missing for UK stock but available for German stock - weekend issue (FX) ?
s = financials.getRealtime('LON:VOD', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_UK_equity MARKET_CAP {}'.format(s))
def test_UK_ETF(self):
s = financials.getRealtime('LON:CSP1', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_UK_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('LON:CSP1', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'GBX', 'test_UK_ETF CURRENCY')
s = financials.getRealtime('LON:FTAL', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_UK_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('LON:FTAL', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'GBP', 'test_UK_ETF CURRENCY')
s = financials.getRealtime('LON:FTAL', Datacode.NAME.value, 'GOOGLE')
self.assertEqual(type(s), str, 'test_UK_ETF NAME')
def test_DE_equity(self):
s = financials.getRealtime('FRA:SAP', 7, 'GOOGLE')
self.assertEqual(float, type(s), 'test_DE_equity 7')
s = financials.getRealtime('FRA:SAP', '7', 'GOOGLE')
self.assertEqual(float, type(s), 'test_DE_equity \'7\'')
s = financials.getRealtime('FRA:SAP', 8, 'GOOGLE')
self.assertEqual(type(s), str, 'test_DE_equity 8')
s = financials.getRealtime('FRA:SAP', 8.1, 'GOOGLE')
self.assertEqual(type(s), str, 'test_DE_equity 8.1')
s = financials.getRealtime('FRA:SAP', '8.1', 'GOOGLE')
self.assertEqual(type(s), str, 'test_DE_equity \'8.1\'')
s = financials.getRealtime('FRA:SAP', 10, 'GOOGLE')
self.assertEqual(type(s), str, 'test_DE_equity 10')
s = financials.getRealtime('FRA:SAP', '11', 'GOOGLE')
self.assertEqual(float, type(s), 'test_DE_equity \'11\'')
s = financials.getRealtime('FRA:SAP', '21', 'GOOGLE')
self.assertEqual(float, type(s), 'test_DE_equity \'21\'')
s = financials.getRealtime('FRA:SAP', Datacode.TIMEZONE.value, 'GOOGLE')
# self.assertEqual(s, 'Europe/Berlin', 'test_DE_equity TIMEZONE')
self.assertTrue(s == 'CET' or s == 'CEST', 'test_DE_equity TIMEZONE: {}'.format(s))
def test_DE_ETF(self):
s = financials.getRealtime('FRA:C060', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_DE_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('FRA:C060', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual('EUR', s, 'test_DE_ETF CURRENCY')
s = financials.getRealtime('FRA:C060', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual('C060', s, 'test_DE_ETF TICKER')
s = financials.getRealtime('FRA:C060', Datacode.EXCHANGE.value, 'GOOGLE')
self.assertEqual('FRA', s, 'test_DE_ETF EXCHANGE')
s = financials.getRealtime('FRA:C060', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual('EUR', s, 'test_DE_ETF CURRENCY')
s = financials.getRealtime('FRA:C060', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertIsNone(s, 'test_DE_ETF MARKET_CAP {}'.format(s))
def test_TY_equity(self):
s = financials.getRealtime('TYO:6503', Datacode.OPEN.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_TY_equity OPEN {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.LOW.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_TY_equity LOW {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.HIGH.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_TY_equity HIGH {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.LOW_52_WEEK.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_TY_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.HIGH_52_WEEK.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_TY_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_TY_equity MARKET_CAP {}'.format(s))
# s = financials.getRealtime('TYO:6503', Datacode.VOLUME.value, 'GOOGLE')
# self.assertEqual(float, type(s), 'test_TY_equity VOLUME {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'JPY', 'test_TY_equity CURRENCY')
def test_US_equity(self):
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual(s, 'AAPL', 'test_US_equity TICKER')
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.EXCHANGE.value, 'GOOGLE')
self.assertEqual(s, 'NASDAQ', 'test_US_equity EXCHANGE')
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'USD', 'test_US_equity CURRENCY')
s = financials.getRealtime('NYSE:IBM', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual(s, 'IBM', 'test_US_equity TICKER')
s = financials.getRealtime('NYSE:IBM', Datacode.EXCHANGE.value, 'GOOGLE')
self.assertEqual(s, 'NYSE', 'test_US_equity EXCHANGE')
s = financials.getRealtime('NYSE:IBM', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'USD', 'test_US_equity CURRENCY')
s = financials.getRealtime('NYSE:IBM', Datacode.NAME.value, 'GOOGLE')
self.assertEqual(type(s), str, 'test_US_equity NAME')
self.assertEqual(s, 'IBM Common Stock', 'test_US_equity NAME')
s = financials.getRealtime('NYSE:IBM', Datacode.LOW.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity LOW {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.HIGH.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity HIGH {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.LOW_52_WEEK.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.HIGH_52_WEEK.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_equity MARKET_CAP {}'.format(s))
# s = financials.getRealtime('NYSE:IBM', Datacode.VOLUME.value, 'GOOGLE')
# self.assertEqual(float, type(s), 'test_US_equity VOLUME {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.TIMESTAMP.value, 'GOOGLE')
self.assertEqual(s, 'Data doesn\'t exist - 999', 'test_US_equity TIMESTAMP')
s = financials.getRealtime('NYSE:IBM', Datacode.TIMEZONE.value, 'GOOGLE')
# self.assertEqual('America/New_York', s, 'test_US_equity TIMEZONE')
# self.assertEqual('GMT-4', s, 'test_US_equity TIMEZONE')
self.assertEqual('GMT-5', s, 'test_US_equity TIMEZONE')
def test_US_mutuals(self):
s = financials.getRealtime('MUTF:VFIAX', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_US_mutuals LAST_PRICE - {}'.format(s))
s = financials.getRealtime('MUTF:VFIAX', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'USD', 'test_US_mutuals CURRENCY')
s = financials.getRealtime('MUTF:VFIAX', Datacode.TIMEZONE.value, 'GOOGLE')
self.assertIsNone(s, 'test_US_mutuals TIMEZONE')
def test_index(self):
s = financials.getRealtime('INDEXDB:DAX', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index LAST_PRICE {}'.format(s))
s = financials.getRealtime('INDEXDB:DAX', Datacode.CHANGE_IN_PERCENT.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index CHANGE_IN_PERCENT')
s = financials.getRealtime('INDEXDB:DAX', Datacode.CHANGE.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index CHANGE')
s = financials.getRealtime('INDEXDB:DAX', Datacode.OPEN.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index OPEN')
s = financials.getRealtime('INDEXDB:DAX', Datacode.LOW.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index LOW')
s = financials.getRealtime('INDEXDB:DAX', Datacode.HIGH.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index HIGH')
def test_errors(self):
s = financials.getRealtime(None, Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(s, 'Ticker is empty', 'test_errors')
s = financials.getRealtime('NYS:IBM', None, 'GOOGLE')
self.assertEqual(s, 'Datacode is empty', 'test_errors')
s = financials.getRealtime('DOES_NOT_EXISTS', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertIsNone(s, 'test_errors')
s = financials.getRealtime('NYS:IBM', 'Foo', 'GOOGLE')
self.assertEqual(s, 'Datacode is invalid', 'test_errors')
# Historic data not supported on GOOGLE
s = financials.getHistoric('NYS:IBM', Datacode.LAST_PRICE.value, '2017-01-01', 'GOOGLE')
self.assertEqual(s, 'Source \'GOOGLE\' not supported', 'test_errors')
def test_errors_cell_range_passed(self):
cell_range = ((1, 2), ('3', '4'), (5.0, 6.0))
s = financials.getRealtime(cell_range, Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(s, 'Cell range not allowed for ticker', 'test_errors')
s = financials.getRealtime('NYS:IBM', cell_range, 'GOOGLE')
self.assertEqual(s, 'Cell range not allowed for datacode', 'test_errors')
s = financials.getRealtime('NYS:IBM', Datacode.LAST_PRICE.value, cell_range)
self.assertEqual(s, 'Cell range not allowed for source', 'test_errors')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
unit_argv = [sys.argv[0]] + args.unittest_args
unittest.main(argv=unit_argv)
+1 -1
View File
@@ -1,4 +1,4 @@
# test_google.py
# test_support.py
#
# license: GNU LGPL
#
+208 -55
View File
@@ -16,6 +16,7 @@ import unittest
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import baseclient
import financials
from datacode import Datacode
import testutils
@@ -23,8 +24,31 @@ import testutils
financials = financials.createInstance(None)
def urlopen_fail(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
raise baseclient.HttpException(url, 'ERROR: simulated urlopen() failed')
class Test(unittest.TestCase):
@classmethod
def tearDownClass(cls):
# this avoids "ResourceWarning: unclosed..." on cached socket connections
financials.close()
def test_recovery_from_urlopen_error_issue(self):
financials.yahoo.last_url = 'blank'
urlopen_saved = baseclient.BaseClient.urlopen
baseclient.BaseClient.urlopen = urlopen_fail
s = financials.getRealtime('U1IH.F', Datacode.LAST_PRICE.value, 'YAHOO')
baseclient.BaseClient.urlopen = urlopen_saved
s = financials.getRealtime('U1IH.F', Datacode.PREV_CLOSE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_recovery_from_urlopen_error_issue PREV_CLOSE {}'.format(s))
def test_currency(self):
s = financials.getRealtime('EURGBP=X', Datacode.CURRENCY.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_currency CURRENCY')
@@ -32,6 +56,17 @@ class Test(unittest.TestCase):
s = financials.getRealtime('EURGBP=X', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_currency LAST_PRICE')
def test_realtime_US_ZVZZT(self):
s = financials.getRealtime('ZVZZT', Datacode.PAYOUT_RATIO.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_US_ZVZZT PAYOUT_RATIO {}'.format(s))
s = financials.getRealtime('ZVZZT', Datacode.SECTOR.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_US_ZVZZT SECTOR {}'.format(s))
s = financials.getRealtime('ZVZZT', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_ZVZZT LAST_PRICE {}'.format(s))
def test_realtime_US_equity(self):
s = financials.getRealtime('^GSPC', Datacode.NAME.value, 'YAHOO')
@@ -73,16 +108,16 @@ class Test(unittest.TestCase):
self.assertEqual(s, 'International Business Machines Corporation (IBM)',
'test_realtime_US_equity NAME {}'.format(s))
# s = financials.getRealtime('IBM', Datacode.SECTOR.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_realtime_US_equity SECTOR {}'.format(s))
# self.assertEqual(s, 'Technology', 'test_realtime_US_equity SECTOR {}'.format(s))
s = financials.getRealtime('IBM', 'SECTOR', 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_equity SECTOR {}'.format(s))
self.assertEqual(s, 'Technology', 'test_realtime_US_equity SECTOR {}'.format(s))
# s = financials.getRealtime('IBM', Datacode.INDUSTRY.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_realtime_US_equity INDUSTRY {}'.format(s))
# self.assertEqual(s, 'Information Technology Services', 'test_realtime_US_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('IBM', 'INDUSTRY', 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Information Technology Services', 'test_realtime_US_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('IBM', Datacode.TIMEZONE.value, 'YAHOO')
self.assertEqual(s, 'EST', 'test_realtime_US_equity TIMEZONE {}'.format(s))
self.assertTrue(s == 'EST' or s == 'EDT', 'test_realtime_US_equity TIMEZONE: {}'.format(s))
s = financials.getRealtime('IBM', Datacode.BETA.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity BETA {}'.format(s))
@@ -103,14 +138,17 @@ class Test(unittest.TestCase):
self.assertEqual(str, type(s), 'test_realtime_US_equity EX_DIV_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_realtime_US_equity EX_DIV_DATE {}'.format(s))
# s = financials.getRealtime('IBM', Datacode.PAYOUT_RATIO.value, 'YAHOO')
# self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity PAYOUT_RATIO {}'.format(s))
s = financials.getRealtime('IBM', 'PAYOUT_RATIO', 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity PAYOUT_RATIO {}'.format(s))
# self.assertEqual(None, s, 'test_realtime_US_equity PAYOUT_RATIO {}'.format(s))
# s = financials.getRealtime('IBM', Datacode.SHARES_OUT.value, 'YAHOO')
# self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity SHARES_OUT {}'.format(s))
s = financials.getRealtime('IBM', 'SHARES_OUT', 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity SHARES_OUT {}'.format(s))
# self.assertEqual(None, s, 'test_realtime_US_equity SHARES_OUT {}'.format(s))
# s = financials.getRealtime('IBM', Datacode.FREE_FLOAT.value, 'YAHOO')
# self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity FREE_FLOAT {}'.format(s))
s = financials.getRealtime('IBM', 'FREE_FLOAT', 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity FREE_FLOAT {}'.format(s))
# self.assertEqual(None, s, 'test_realtime_US_equity FREE_FLOAT {}'.format(s))
s = financials.getRealtime('IBM', Datacode.EXCHANGE.value, 'YAHOO')
self.assertEqual(s, 'NYSE', 'test_realtime_US_equity EXCHANGE')
@@ -123,51 +161,105 @@ class Test(unittest.TestCase):
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_mutuals LAST_PRICE {}'.format(s))
# s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_DATE.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_realtime_US_mutuals LAST_PRICE_DATE {}'.format(s))
# s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_TIME.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_realtime_US_mutuals LAST_PRICE_TIME {}'.format(s))
s = financials.getRealtime('VFIAX', Datacode.DIV.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_mutuals DIV {}'.format(s))
s = financials.getRealtime('VFIAX', Datacode.DIV_YIELD.value, 'YAHOO')
# self.assertIsNone(s, 'test_realtime_US_mutuals DIV_YIELD {}'.format(s)) # no yield
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_mutuals DIV_YIELD {}'.format(s))
s = financials.getRealtime('SHRAX', Datacode.DIV.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_mutuals DIV {}'.format(s))
s = financials.getRealtime('SHRAX', Datacode.DIV_YIELD.value, 'YAHOO')
# self.assertIsNone(s, 'test_realtime_US_mutuals DIV_YIELD {}'.format(s)) # no yield
self.assertEqual(float, type(s), 'test_realtime_US_mutuals DIV_YIELD {}'.format(s))
# s = financials.getRealtime('VERX.L', Datacode.DIV.value, 'YAHOO')
# self.assertIsNone(s, 'test_realtime_US_mutuals DIV {}'.format(s)) # no dividend
s = financials.getRealtime('VERX.L', Datacode.DIV_YIELD.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_US_mutuals DIV_YIELD {}'.format(s)) # no yield
# self.assertEqual(float, type(s), 'test_realtime_US_mutuals DIV_YIELD {}'.format(s))
def test_realtime_US_options(self):
# symbol from https://finance.yahoo.com/quote/IBM/options?p=IBM
s = financials.getRealtime('IBM230120C00130000', Datacode.PREV_CLOSE.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.PREV_CLOSE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options PREV_CLOSE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.NAME.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_options NAME {}'.format(s))
self.assertEqual('IBM Jan 2023 130.000 call', s, 'test_realtime_US_options NAME {}'.format(s))
self.assertEqual('IBM Jan 2025 165.000 call', s, 'test_realtime_US_options NAME {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.EXPIRY_DATE.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.EXPIRY_DATE.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
self.assertEqual("2023-01-20", s, 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
self.assertEqual("2025-01-17", s, 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.LAST_PRICE.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options LAST_PRICE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.OPEN.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.OPEN.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options OPEN {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.VOLUME.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.VOLUME.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options VOLUME {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.BID.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.BID.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options BID {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.ASK.value, 'YAHOO')
s = financials.getRealtime('IBM250117C00165000', Datacode.ASK.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options ASK {}'.format(s))
# s = financials.getRealtime('IBM230120C00130000', Datacode.BIDSIZE.value, 'YAHOO')
# self.assertEqual(float, type(s), 'test_realtime_US_options BIDSIZE {}'.format(s))
s = financials.getRealtime('IBM250117C00165000', Datacode.PAYOUT_RATIO.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_US_options PAYOUT_RATIO {}'.format(s))
# s = financials.getRealtime('IBM230120C00130000', Datacode.ASKSIZE.value, 'YAHOO')
# self.assertEqual(float, type(s), 'test_realtime_US_options ASKSIZE {}'.format(s))
s = financials.getRealtime('IBM250117C00165000', Datacode.SECTOR.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_US_options SECTOR {}'.format(s))
def test_realtime_US_futures(self):
s = financials.getRealtime('ES=F', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_futures NAME {}'.format(s))
self.assertEqual('E-Mini S&P 500 Mar 24 (ES=F)', s, 'test_realtime_US_futures NAME {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.TICKER.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_futures TICKER {}'.format(s))
self.assertEqual('ESH24.CME', s, 'test_realtime_US_futures TICKER {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.SETTLEMENT_DATE.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_futures SETTLEMENT_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_realtime_US_futures SETTLEMENT_DATE {}'.format(s))
self.assertEqual("2024-03-15", s, 'test_realtime_US_futures SETTLEMENT_DATE {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures LAST_PRICE {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.OPEN.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures OPEN {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.VOLUME.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures VOLUME {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.BID.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures BID {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.ASK.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures ASK {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.CHANGE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures CHANGE {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.CHANGE_IN_PERCENT.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures CHANGE_IN_PERCENT {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.LOW.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures LOW {}'.format(s))
s = financials.getRealtime('ES=F', Datacode.HIGH.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_futures HIGH {}'.format(s))
def test_realtime_UK_ETF(self):
@@ -175,34 +267,83 @@ class Test(unittest.TestCase):
self.assertEqual(float, type(s), 'test_realtime_UK_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('VERX.L', Datacode.TIMEZONE.value, 'YAHOO')
self.assertEqual('GMT', s, 'test_realtime_UK_ETF TIMEZONE {}'.format(s))
self.assertTrue(s == 'GMT' or s == 'BST', 'test_realtime_UK_ETF TIMEZONE: {}'.format(s))
s = financials.getRealtime('CSP1.L', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_UK_ETF NAME {}'.format(s))
self.assertEqual('iShares Core S&P 500 UCITS ETF USD (Acc) (CSP1.L)', s, 'test_realtime_UK_ETF NAME {}'.format(s))
s = financials.getRealtime('VERX.L', 'SECTOR', 'YAHOO')
self.assertIsNone(s, 'test_realtime_UK_ETF SECTOR {}'.format(s))
s = financials.getRealtime('VERX.L', 'INDUSTRY', 'YAHOO')
self.assertIsNone(s, 'test_realtime_UK_ETF INDUSTRY {}'.format(s))
s = financials.getRealtime('VERX.L', 'PAYOUT_RATIO', 'YAHOO')
self.assertIsNone(s, 'test_realtime_UK_ETF PAYOUT_RATIO {}'.format(s))
s = financials.getRealtime('VERX.L', 'SHARES_OUT', 'YAHOO')
self.assertIsNone(s, 'test_realtime_UK_ETF SHARES_OUT {}'.format(s))
def test_realtime_DE_equity(self):
s = financials.getRealtime('SAP.DE', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_DE_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('SAP.DE', Datacode.TIMEZONE.value, 'YAHOO')
self.assertEqual('CET', s, 'test_realtime_DE_equity TIMEZONE {}'.format(s))
self.assertTrue(s == 'CET' or s == 'CEST', 'test_realtime_DE_equity TIMEZONE: {}'.format(s))
# s = financials.getRealtime('SAP.DE', Datacode.SECTOR.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_realtime_DE_equity SECTOR {}'.format(s))
# self.assertEqual('Technology', s, 'test_realtime_DE_equity SECTOR {}'.format(s))
s = financials.getRealtime('SAP.DE', Datacode.SECTOR.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_DE_equity SECTOR {}'.format(s))
self.assertEqual('Technology', s, 'test_realtime_DE_equity SECTOR {}'.format(s))
# s = financials.getRealtime('SAP.DE', Datacode.INDUSTRY.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_realtime_DE_equity INDUSTRY {}'.format(s))
# self.assertEqual(s, 'SoftwareApplication', 'test_realtime_DE_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('SAP.DE', Datacode.INDUSTRY.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_DE_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Software - Application', 'test_realtime_DE_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('C060.DE', Datacode.NAME.value, 'YAHOO')
s = financials.getRealtime('LYY8.DE', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_DE_equity NAME {}'.format(s))
s = financials.getRealtime('C060.DE', Datacode.EXCHANGE.value, 'YAHOO')
s = financials.getRealtime('LYY8.DE', Datacode.EXCHANGE.value, 'YAHOO')
self.assertEqual(s, 'XETRA', 'test_realtime_DE_equity EXCHANGE')
def test_TA_equity(self):
s = financials.getRealtime('LUMI.TA', 'LAST_PRICE', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'OPEN', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity OPEN {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'LOW', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity LOW {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'HIGH', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity HIGH {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'LOW_52_WEEK', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'HIGH_52_WEEK', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'MARKET_CAP', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'VOLUME', 'YAHOO')
self.assertEqual(float, type(s), 'test_TA_equity VOLUME {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'CURRENCY', 'YAHOO')
self.assertEqual('ILA', s, 'test_TA_equity CURRENCY')
s = financials.getRealtime('LUMI.TA', 'SECTOR', 'YAHOO')
self.assertEqual(str, type(s), 'test_TLV_equity SECTOR {}'.format(s))
self.assertEqual('Financial Services', s, 'test_TA_equity SECTOR {}'.format(s))
s = financials.getRealtime('LUMI.TA', 'INDUSTRY', 'YAHOO')
self.assertEqual(str, type(s), 'test_TLV_equity INDUSTRY {}'.format(s))
self.assertEqual('Banks - Regional', s, 'test_TA_equity INDUSTRY {}'.format(s))
def test_DK_equity(self):
s = financials.getRealtime('NOVO-B.CO', 'last_price', 'YAHOO')
self.assertEqual(float, type(s), 'test_DK_equity LAST_PRICE {}'.format(s))
@@ -213,11 +354,22 @@ class Test(unittest.TestCase):
s = financials.getRealtime('NOVO-B.CO', 'currency', 'YAHOO')
self.assertEqual('DKK', s, 'test_DK_equity CURRENCY {}'.format(s))
# s = financials.getRealtime('NOVO-B.CO', 'industry', 'YAHOO')
# self.assertEqual(str, type(s), 'test_DK_equity INDUSTRY {}'.format(s))
# self.assertEqual('Biotechnology', s, 'test_DK_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('NOVO-B.CO', 'industry', 'YAHOO')
self.assertEqual(str, type(s), 'test_DK_equity INDUSTRY {}'.format(s))
self.assertEqual('Biotechnology', s, 'test_DK_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('MAERSK-B.CO', 'currency', 'YAHOO')
self.assertEqual('DKK', s, 'test_DK_equity CURRENCY {}'.format(s))
def test_realtime_TY_equity(self):
s = financials.getRealtime('6503.T', Datacode.SECTOR.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_TY_equity SECTOR {}'.format(s))
self.assertEqual(s, 'Industrials', 'test_TY_equity SECTOR {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.INDUSTRY.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_TY_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Electrical Equipment & Parts', 'test_TY_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.OPEN.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity OPEN {}'.format(s))
@@ -242,13 +394,8 @@ class Test(unittest.TestCase):
s = financials.getRealtime('6503.T', Datacode.CURRENCY.value, 'YAHOO')
self.assertEqual(s, 'JPY', 'test_TY_equity CURRENCY')
# s = financials.getRealtime('6503.T', Datacode.SECTOR.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_TY_equity SECTOR {}'.format(s))
# self.assertEqual(s, 'Industrials', 'test_TY_equity SECTOR {}'.format(s))
# s = financials.getRealtime('6503.T', Datacode.INDUSTRY.value, 'YAHOO')
# self.assertEqual(str, type(s), 'test_TY_equity INDUSTRY {}'.format(s))
# self.assertEqual(s, 'Electrical Equipment & Parts', 'test_TY_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.TIMEZONE.value, 'YAHOO')
self.assertEqual(s, 'JST', 'test_TY_equity TIMEZONE')
def test_historic_US_equity(self):
@@ -329,15 +476,21 @@ class Test(unittest.TestCase):
s = financials.getHistoric('SAP.DE', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
self.assertEqual(s, 82.889999, 'test_historic_DE_equity CLOSE {}'.format(s))
s = financials.getHistoric('C060.DE', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
self.assertEqual(s, 72.870003, 'test_historic_DE_equity CLOSE {}'.format(s))
s = financials.getHistoric('LYY8.DE', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
self.assertEqual(s, 96.010002, 'test_historic_DE_equity CLOSE {}'.format(s))
def test_realtime_errors(self):
s = financials.getRealtime('NO_NAME', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_errors LAST_PRICE {}'.format(s))
s = financials.getRealtime('C060.DE', -1, 'YAHOO')
s = financials.getRealtime('NO_NAME', Datacode.PAYOUT_RATIO.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_errors PAYOUT_RATIO {}'.format(s))
s = financials.getRealtime('NO_NAME', Datacode.SECTOR.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_errors SECTOR {}'.format(s))
s = financials.getRealtime('LYY8.DE', -1, 'YAHOO')
self.assertEqual('Datacode -1 not supported', s, 'test_realtime_errors -1 {}'.format(s))
def test_historic_errors(self):