Compare commits

...
6 Commits
Author SHA1 Message Date
cmallwitz 06d9b91793 Update version and some cleanups 2024-03-13 12:11:42 +00:00
cmallwitzandGitHub 60d5d7b764 Merge pull request #99 from beckjake/handle-data-value
Handle the case where there is a data-value but no value
2024-03-12 17:18:17 +00:00
Jacob Beck 345f57420c Handle the case where there is a data-value but no value 2024-03-08 21:51:21 -07:00
cmallwitz 88b039d471 Yahoo: Don't re-fetch non-existing statistic/profile data 2023-12-29 22:22:28 +00:00
cmallwitz 7e751928de Rework Yahoo cookies (always fetch from scratch) 2023-12-29 18:52:19 +00:00
cmallwitz 0c07c4c3ba Update build for LibeOffice 7.4+ 2023-12-24 19:49:56 +00:00
12 changed files with 211 additions and 725 deletions
+15 -6
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/
BIN
View File
Binary file not shown.
+31 -17
View File
@@ -49,15 +49,18 @@ class BaseClient:
os.makedirs(self.basedir, exist_ok=True)
user_agents = [
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0'
'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 = {
@@ -71,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
@@ -98,7 +100,7 @@ class BaseClient:
connection = HTTPConnection(host, **kwargs) if scheme == 'http:' else HTTPSConnection(host, **kwargs)
logger.debug('Creating request -----------------------------------------------------')
logger.debug("url='%s'", url)
logger.debug("%s %s", method, url)
self.last_url = url
@@ -129,9 +131,13 @@ class BaseClient:
def urlopen(self, url, redirect=True, data=None, 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, cookies, **kwargs)
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
@@ -141,7 +147,7 @@ class BaseClient:
# 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, cookies, **kwargs)
self.response = self.request('POST' if data else 'GET', url, data, headers, **kwargs)
text = self.response.read()
while 300 <= self.response.status < 400 and self.redirect_count >= 0:
@@ -155,7 +161,7 @@ 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:
@@ -385,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 = {}
+5 -5
View File
@@ -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)
-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 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]
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)
+89 -149
View File
@@ -15,7 +15,6 @@ import os
import re
import time
import urllib.parse
from http import cookiejar
import dateutil.parser
@@ -57,25 +56,6 @@ def handle_abbreviations(s):
return float(s)
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())
def get_cookies():
return [
cookie("A1", "d=AQABBE6aomQCEJUamSIzqCl9UJ-spkNvMfkFEgABCAHqo2TKZPbPb2UBAiAAAAcIS5qiZF3wy-8&S=AQAAAnSEZV80kjd9J2RUh8TD5cY"),
cookie("A1S", "d=AQABBE6aomQCEJUamSIzqCl9UJ-spkNvMfkFEgABCAHqo2TKZPbPb2UBAiAAAAcIS5qiZF3wy-8&S=AQAAAnSEZV80kjd9J2RUh8TD5cY&j=GDPR"),
cookie("A3", "d=AQABBE6aomQCEJUamSIzqCl9UJ-spkNvMfkFEgABCAHqo2TKZPbPb2UBAiAAAAcIS5qiZF3wy-8&S=AQAAAnSEZV80kjd9J2RUh8TD5cY"),
cookie("EuConsent", "CPuVJsAPuVJsAAOACKENDICgAAAAAAAAACiQAAAAAABhoAMAAQSrEQAYAAglWKgAwABBKsA"),
cookie("GUC", "AQ81G6Da"),
cookie("maex", "{\"v2\":{}}"),
cookie("thamba", "1")
]
class Yahoo(BaseClient):
def __init__(self, ctx):
super().__init__()
@@ -85,61 +65,6 @@ class Yahoo(BaseClient):
self.historicdata = {}
self.js = jsonParser.jsonObject
self.create_cookies()
def create_cookies(self):
cookiejar_path = os.path.join(self.basedir, 'yahoo.cookiejar')
cookiejar_exists = os.path.isfile(cookiejar_path) and os.stat(cookiejar_path).st_size >= 0
if cookiejar_exists:
try:
self.cookies = cookiejar.LWPCookieJar()
self.cookies.load(cookiejar_path, ignore_discard=True)
required_cookie_names = ["A1", "A1S", "A3", "GUC", "maex", "thamba"]
for c in self.cookies:
if c.name in required_cookie_names:
required_cookie_names.remove(c.name)
if len(required_cookie_names) > 0:
cookiejar_exists = False
logger.info("Overriding cookiejar '%s'", cookiejar_path)
except BaseException:
cookiejar_exists = False
logger.exception("BaseException initial loading cookiejar_path=%s", cookiejar_path)
if not cookiejar_exists:
try:
lwp_cookiejar = cookiejar.LWPCookieJar()
for c in get_cookies():
lwp_cookiejar.set_cookie(c)
lwp_cookiejar.save(cookiejar_path, ignore_discard=True)
logger.info("Created cookiejar '%s'", cookiejar_path)
except BaseException:
logger.exception("BaseException creating cookiejar_path=%s", cookiejar_path)
try:
self.cookies = cookiejar.LWPCookieJar()
self.cookies.load(cookiejar_path, ignore_discard=True)
logger.info("Loaded cookiejar '%s'", cookiejar_path)
for c in self.cookies:
logger.info("Cookie name'%s' value='%s' path='%s'", c.name, c.value, c.path)
except BaseException:
logger.exception("BaseException loading cookiejar_path=%s", cookiejar_path)
def save_cookies(self):
cookiejar_path = os.path.join(self.basedir, 'yahoo.cookiejar')
try:
self.cookies.save(cookiejar_path, ignore_discard=True)
logger.debug("Saved cookiejar '%s'", cookiejar_path)
except BaseException:
logger.exception("BaseException saving cookiejar_path=%s", cookiejar_path)
def _read_ticker_csv_file(self, ticker):
fn = os.path.join(self.basedir, 'yahoo-{}.csv'.format(ticker))
@@ -207,6 +132,51 @@ class Yahoo(BaseClient):
return self.getRealtimeSummary(ticker, datacode)
def getData(self, url, ticker, datacode, html_file):
try:
text = self.urlopen(url, redirect=True)
except BaseException as 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, 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 (2) ticker=%s datacode=%s %s", ticker, datacode, e)
if not text:
return None
try:
parser = NaiveHTMLParser()
root = parser.feed(text)
parser.close()
except BaseException as 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):
"""
@@ -216,20 +186,11 @@ class Yahoo(BaseClient):
tick = self.realtime[ticker]
url = 'https://finance.yahoo.com/quote/{}?p={}'.format(ticker, ticker)
text = self.getData(url, ticker, datacode, f'yahoo-{ticker}.html')
try:
text = self.urlopen(url, redirect=True)
self.save_cookies()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
if text is None:
del self.realtime[ticker]
return 'Yahoo.getRealtimeSummary({}, {}) - urlopen: {}'.format(ticker, datacode, e)
try:
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "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", ticker, datacode)
return 'Yahoo.getRealtimeSummary({}, {}) - getData'.format(ticker, datacode)
try:
r = '"crumb":"([^"]{11})"'
@@ -264,14 +225,16 @@ class Yahoo(BaseClient):
found = root.findall(f".//fin-streamer[@data-symbol='{ticker}']")
for d in found:
if hasattr(d, 'attrib') and 'data-field' in d.attrib:
parsed[d.attrib['data-field']] = default(d.attrib, 'value').replace('', '-').replace(',', '').strip()
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:
parsed[d.attrib['data-field']] = default(d.attrib, 'value').replace('', '-').replace(',', '').strip()
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]")
@@ -380,20 +343,11 @@ class Yahoo(BaseClient):
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')
try:
text = self.urlopen(url, redirect=True)
self.save_cookies()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
if text is None:
del self.realtime[ticker]
return 'Yahoo.getRealtimeStatistics({}, {}) - urlopen: {}'.format(ticker, datacode, e)
try:
with open(os.path.join(self.basedir, 'yahoo-{}-statistics.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 open/write ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtimeStatistics({}, {}) - getData'.format(ticker, datacode)
try:
parser = NaiveHTMLParser()
@@ -404,19 +358,23 @@ class Yahoo(BaseClient):
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:
parsed = {}
statistics = root.find(".//section[@data-test='qsp-statistics']")
if statistics is None:
return None
tick[Datacode.TICKER] = ticker
tick[Datacode.TIMESTAMP] = time.time()
tick[Datacode.YAHOO_STATISTIC_RECEIVED] = True
# Valuation Measures
found = statistics.find('./div[2]/div[1]//table')
if found:
@@ -449,17 +407,15 @@ class Yahoo(BaseClient):
if key is not None:
parsed[key] = d.find('./td[2]').text
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)
except KeyError:
pass
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Yahoo.getRealtimeStatistics({}, {}) - process: {}'.format(ticker, datacode, e)
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)
@@ -472,20 +428,11 @@ class Yahoo(BaseClient):
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')
try:
text = self.urlopen(url, redirect=True)
self.save_cookies()
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
if text is None:
del self.realtime[ticker]
return 'Yahoo.getRealtimeProfile({}, {}) - urlopen: {}'.format(ticker, datacode, e)
try:
with open(os.path.join(self.basedir, 'yahoo-{}-profile.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 open/write ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtimeProfile({}, {}) - getData'.format(ticker, datacode)
try:
parser = NaiveHTMLParser()
@@ -496,24 +443,17 @@ class Yahoo(BaseClient):
del self.realtime[ticker]
return 'Yahoo.getRealtimeProfile({}, {}) - HTML parsing: {}'.format(ticker, datacode, e)
try:
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)']")
if p is None:
return None
tick[Datacode.TICKER] = ticker
tick[Datacode.TIMESTAMP] = time.time()
tick[Datacode.YAHOO_PROFILE_RECEIVED] = True
tick[Datacode.SECTOR] = self.save_wrapper(lambda: p.find("./span[2]").text)
tick[Datacode.INDUSTRY] = self.save_wrapper(lambda: p.find("./span[4]").text)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Yahoo.getRealtimeProfile({}, {}) - process: {}'.format(ticker, datacode, e)
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)
+1 -1
View File
@@ -14,7 +14,7 @@ import os
cur_dir = os.getcwd()
addin_id = "com.financials.getinfo"
addin_version = "3.3.2"
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')
+17 -10
View File
@@ -23,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')
@@ -137,29 +142,31 @@ class Test(unittest.TestCase):
def test_US_futures(self):
s = financials.getRealtime('ESU3:IOM', Datacode.NAME.value, 'FT')
self.assertEqual(str, type(s), 'test_realtime_US_futures NAME {}'.format(s))
self.assertEqual('EMINI S&P SEP3', s, 'test_US_futures NAME {}'.format(s))
# https://markets.ft.com/data/commodities/tearsheet/summary?s=775326843 ESH25:IOM
s = financials.getRealtime('ESU3:IOM', Datacode.LAST_PRICE.value, 'FT')
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('ESH3:IOM', Datacode.OPEN.value, 'FT')
# s = financials.getRealtime('775326843', Datacode.OPEN.value, 'FT')
# self.assertEqual(float, type(s), 'test_US_futures OPEN {}'.format(s))
s = financials.getRealtime('ESU3:IOM', Datacode.VOLUME.value, 'FT')
s = financials.getRealtime('775326843', Datacode.VOLUME.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures VOLUME {}'.format(s))
s = financials.getRealtime('ESU3:IOM', Datacode.LOW_52_WEEK.value, 'FT')
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('ESU3:IOM', Datacode.HIGH_52_WEEK.value, 'FT')
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('ESU3:IOM', Datacode.CHANGE.value, 'FT')
s = financials.getRealtime('775326843', Datacode.CHANGE.value, 'FT')
self.assertEqual(float, type(s), 'test_US_futures CHANGE {}'.format(s))
s = financials.getRealtime('ESU3:IOM', Datacode.CHANGE_IN_PERCENT.value, 'FT')
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):
-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
#
+47 -29
View File
@@ -25,11 +25,16 @@ financials = financials.createInstance(None)
def urlopen_fail(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
raise baseclient.HttpException(url, 'simulated urlopen() failed')
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'
@@ -103,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')
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')
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, 'EDT', '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))
@@ -133,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')
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')
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')
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')
@@ -178,53 +186,53 @@ class Test(unittest.TestCase):
# symbol from https://finance.yahoo.com/quote/IBM/options?p=IBM
s = financials.getRealtime('IBM240119C00120000', 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('IBM240119C00120000', 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 2024 120.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('IBM240119C00120000', 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("2024-01-19", 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('IBM240119C00120000', 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('IBM240119C00120000', 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('IBM240119C00120000', 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('IBM240119C00120000', 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('IBM240119C00120000', 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('IBM240119C00120000', Datacode.PAYOUT_RATIO.value, 'YAHOO')
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.SECTOR.value, 'YAHOO')
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 Sep 23 (ES=F)', 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('ESU23.CME', 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("2023-09-15", 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))
@@ -259,12 +267,23 @@ 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.assertEqual('BST', 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 VII PLC - iShares Core S&P 500 UCITS ETF (CSP1.L)', 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):
@@ -272,8 +291,7 @@ class Test(unittest.TestCase):
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.assertEqual('CEST', 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))
@@ -281,7 +299,7 @@ class Test(unittest.TestCase):
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))
self.assertEqual(s, 'Software - Application', 'test_realtime_DE_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('LYY8.DE', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_DE_equity NAME {}'.format(s))
@@ -324,7 +342,7 @@ class Test(unittest.TestCase):
s = financials.getRealtime('LUMI.TA', 'INDUSTRY', 'YAHOO')
self.assertEqual(str, type(s), 'test_TLV_equity INDUSTRY {}'.format(s))
self.assertEqual('BanksRegional', s, 'test_TA_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')