mirror of
https://github.com/cmallwitz/Financials-Extension.git
synced 2026-08-25 02:24:11 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f6249a6b6 | ||
|
|
d05ca9bb55 | ||
|
|
023fa71a81 |
@@ -1,13 +1,12 @@
|
||||
# Financials-Extension
|
||||
|
||||
## Update (20 Dec 2022) Yahoo deployed a major website change
|
||||
|
||||
The latest version 3.2.1 restores almost all extension functionality. The only fields missing is LAST_PRICE_DATE and LAST_PRICE_TIME - somehow this is handled now using JavaScript making it a pain to extract.
|
||||
Version 3.3.0 includes improved cookie handling and somewhat improved logic to deal with network issues.
|
||||
|
||||
## Overview
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -34,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
|
||||
@@ -93,7 +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 | | | ||
|
||||
| SETTLEMENT_DATE | 77 | Yes (on futures) | | | | yyyy-mm-dd |
|
||||
| CLOSE | 90 | No | No | Yes | ||
|
||||
| ADJ_CLOSE | 91 | No | No | Yes | ||
|
||||
| SECTOR | 98 | Yes | Yes |||
|
||||
@@ -137,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
|
||||
|
||||
+3
-3
@@ -55,11 +55,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 +68,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
|
||||
|
||||
|
||||
Binary file not shown.
+26
-24
@@ -43,30 +43,21 @@ 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: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'
|
||||
]
|
||||
|
||||
self.default_headers = {
|
||||
@@ -107,7 +98,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("url='%s'", url)
|
||||
|
||||
self.last_url = url
|
||||
|
||||
@@ -127,7 +118,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 +129,24 @@ class BaseClient:
|
||||
|
||||
def urlopen(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
|
||||
|
||||
self.last_url = None
|
||||
|
||||
self.response = self.request('POST' if data else 'GET', url, data, headers, cookies, **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, cookies, **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:
|
||||
@@ -162,6 +162,8 @@ class BaseClient:
|
||||
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':
|
||||
|
||||
+1
-1
@@ -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())))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class Google(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]
|
||||
|
||||
+138
-54
@@ -64,6 +64,18 @@ def cookie(name, value):
|
||||
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__()
|
||||
@@ -73,6 +85,61 @@ 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))
|
||||
@@ -102,18 +169,6 @@ class Yahoo(BaseClient):
|
||||
|
||||
self.historicdata[ticker] = ticks
|
||||
|
||||
def get_cookies(self):
|
||||
return [
|
||||
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")
|
||||
]
|
||||
|
||||
def getRealtime(self, ticker, datacode):
|
||||
|
||||
"""
|
||||
@@ -133,7 +188,7 @@ class Yahoo(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]:
|
||||
if (tick[Datacode.YAHOO_STATISTIC_RECEIVED] or not needStatistics) and (
|
||||
tick[Datacode.YAHOO_PROFILE_RECEIVED] or not needProfile) and (
|
||||
tick[Datacode.YAHOO_SUMMARY_RECEIVED]):
|
||||
@@ -163,14 +218,16 @@ class Yahoo(BaseClient):
|
||||
url = 'https://finance.yahoo.com/quote/{}?p={}'.format(ticker, ticker)
|
||||
|
||||
try:
|
||||
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=self.get_cookies())
|
||||
text = self.urlopen(url, redirect=True)
|
||||
self.save_cookies()
|
||||
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 '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"<!-- '{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)
|
||||
|
||||
@@ -182,11 +239,9 @@ class Yahoo(BaseClient):
|
||||
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)
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
tick[Datacode.YAHOO_SUMMARY_RECEIVED] = True
|
||||
|
||||
try:
|
||||
parser = NaiveHTMLParser()
|
||||
root = parser.feed(text)
|
||||
@@ -196,6 +251,14 @@ class Yahoo(BaseClient):
|
||||
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}']")
|
||||
@@ -203,6 +266,14 @@ class Yahoo(BaseClient):
|
||||
if hasattr(d, 'attrib') and 'data-field' in d.attrib:
|
||||
parsed[d.attrib['data-field']] = default(d.attrib, '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()
|
||||
tick[Datacode.TICKER] = default(d.attrib, 'data-symbol').strip()
|
||||
|
||||
found = root.findall(f".//td[@data-test]")
|
||||
for d in found:
|
||||
if d:
|
||||
@@ -275,8 +346,6 @@ 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>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text)
|
||||
if match:
|
||||
@@ -297,6 +366,7 @@ class Yahoo(BaseClient):
|
||||
|
||||
except BaseException as e:
|
||||
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
|
||||
del self.realtime[ticker]
|
||||
return 'Yahoo.getRealtimeSummary({}, {}) - process: {}'.format(ticker, datacode, e)
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
@@ -312,16 +382,18 @@ class Yahoo(BaseClient):
|
||||
url = 'https://finance.yahoo.com/quote/{}/key-statistics?p={}'.format(ticker, ticker)
|
||||
|
||||
try:
|
||||
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=self.get_cookies())
|
||||
text = self.urlopen(url, redirect=True)
|
||||
self.save_cookies()
|
||||
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 '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 ticker=%s datacode=%s", ticker, datacode)
|
||||
logger.exception("BaseException open/write ticker=%s datacode=%s", ticker, datacode)
|
||||
|
||||
try:
|
||||
parser = NaiveHTMLParser()
|
||||
@@ -329,11 +401,9 @@ class Yahoo(BaseClient):
|
||||
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)
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
tick[Datacode.YAHOO_STATISTIC_RECEIVED] = True
|
||||
|
||||
try:
|
||||
|
||||
parsed = {}
|
||||
@@ -343,33 +413,41 @@ class Yahoo(BaseClient):
|
||||
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').findall('.//tr')
|
||||
for d in found:
|
||||
key = d.find('./td[1]/span').text
|
||||
if key is not None:
|
||||
parsed[key] = d.find('./td[2]').text
|
||||
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').findall('.//tr')
|
||||
for d in found:
|
||||
key = d.find('./td[1]/span').text
|
||||
if key is not None:
|
||||
parsed[key] = d.find('./td[2]').text
|
||||
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').findall('.//tr')
|
||||
for d in found:
|
||||
key = d.find('./td[1]/span').text
|
||||
if key is not None:
|
||||
parsed[key] = d.find('./td[2]').text
|
||||
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').findall('.//tr')
|
||||
for d in found:
|
||||
key = d.find('./td[1]/span').text
|
||||
if key is not None:
|
||||
parsed[key] = d.find('./td[2]').text
|
||||
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
|
||||
|
||||
tick[Datacode.SHARES_OUT] = self.save_wrapper(
|
||||
lambda: float(handle_abbreviations(parsed['Shares Outstanding'])))
|
||||
@@ -380,6 +458,7 @@ class Yahoo(BaseClient):
|
||||
|
||||
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)
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
@@ -395,16 +474,18 @@ class Yahoo(BaseClient):
|
||||
url = 'https://finance.yahoo.com/quote/{}/profile?p={}'.format(ticker, ticker)
|
||||
|
||||
try:
|
||||
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=self.get_cookies())
|
||||
text = self.urlopen(url, redirect=True)
|
||||
self.save_cookies()
|
||||
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 '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 ticker=%s datacode=%s", ticker, datacode)
|
||||
logger.exception("BaseException open/write ticker=%s datacode=%s", ticker, datacode)
|
||||
|
||||
try:
|
||||
parser = NaiveHTMLParser()
|
||||
@@ -412,11 +493,9 @@ class Yahoo(BaseClient):
|
||||
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.TIMESTAMP] = time.time()
|
||||
tick[Datacode.YAHOO_PROFILE_RECEIVED] = True
|
||||
|
||||
try:
|
||||
|
||||
p = root.find(".//*[span='Sector(s)']")
|
||||
@@ -424,11 +503,16 @@ class Yahoo(BaseClient):
|
||||
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)
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
@@ -457,7 +541,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:
|
||||
|
||||
@@ -14,7 +14,7 @@ import os
|
||||
cur_dir = os.getcwd()
|
||||
|
||||
addin_id = "com.financials.getinfo"
|
||||
addin_version = "3.2.1"
|
||||
addin_version = "3.3.2"
|
||||
addin_displayname = "Financial Market Extension"
|
||||
addin_publisher_link = "https://github.com/cmallwitz/Financials-Extension"
|
||||
addin_publisher_name = "The Publisher"
|
||||
|
||||
+8
-11
@@ -137,29 +137,29 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_US_futures(self):
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.NAME.value, 'FT')
|
||||
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 MAR3', s, 'test_US_futures NAME {}'.format(s))
|
||||
self.assertEqual('EMINI S&P SEP3', s, 'test_US_futures NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.LAST_PRICE.value, 'FT')
|
||||
s = financials.getRealtime('ESU3:IOM', 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')
|
||||
# self.assertEqual(float, type(s), 'test_US_futures OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.VOLUME.value, 'FT')
|
||||
s = financials.getRealtime('ESU3:IOM', Datacode.VOLUME.value, 'FT')
|
||||
self.assertEqual(float, type(s), 'test_US_futures VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.LOW_52_WEEK.value, 'FT')
|
||||
s = financials.getRealtime('ESU3:IOM', Datacode.LOW_52_WEEK.value, 'FT')
|
||||
self.assertEqual(float, type(s), 'test_US_futures LOW_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.HIGH_52_WEEK.value, 'FT')
|
||||
s = financials.getRealtime('ESU3:IOM', Datacode.HIGH_52_WEEK.value, 'FT')
|
||||
self.assertEqual(float, type(s), 'test_US_futures HIGH_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.CHANGE.value, 'FT')
|
||||
s = financials.getRealtime('ESU3:IOM', Datacode.CHANGE.value, 'FT')
|
||||
self.assertEqual(float, type(s), 'test_US_futures CHANGE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH3:IOM', Datacode.CHANGE_IN_PERCENT.value, 'FT')
|
||||
s = financials.getRealtime('ESU3:IOM', Datacode.CHANGE_IN_PERCENT.value, 'FT')
|
||||
self.assertEqual(float, type(s), 'test_US_futures CHANGE_IN_PERCENT {}'.format(s))
|
||||
|
||||
def test_UK_ETF(self):
|
||||
@@ -167,9 +167,6 @@ class Test(unittest.TestCase):
|
||||
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))
|
||||
|
||||
|
||||
+56
-25
@@ -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,26 @@ import testutils
|
||||
financials = financials.createInstance(None)
|
||||
|
||||
|
||||
def urlopen_fail(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
|
||||
raise baseclient.HttpException(url, 'simulated urlopen() failed')
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
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')
|
||||
@@ -93,7 +112,7 @@ class Test(unittest.TestCase):
|
||||
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.assertEqual(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))
|
||||
@@ -138,19 +157,22 @@ class Test(unittest.TestCase):
|
||||
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.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.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.assertEqual(float, type(s), 'test_realtime_US_mutuals DIV_YIELD {}'.format(s))
|
||||
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):
|
||||
|
||||
@@ -191,40 +213,44 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_realtime_US_futures(self):
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.NAME.value, 'YAHOO')
|
||||
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 23 (ESH23.CME)', s, 'test_realtime_US_options NAME {}'.format(s))
|
||||
self.assertEqual('E-Mini S&P 500 Sep 23 (ES=F)', s, 'test_realtime_US_futures NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.SETTLEMENT_DATE.value, 'YAHOO')
|
||||
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))
|
||||
|
||||
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-03-17", s, 'test_realtime_US_futures SETTLEMENT_DATE {}'.format(s))
|
||||
self.assertEqual("2023-09-15", s, 'test_realtime_US_futures SETTLEMENT_DATE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.LAST_PRICE.value, 'YAHOO')
|
||||
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('ESH23.CME', Datacode.OPEN.value, 'YAHOO')
|
||||
s = financials.getRealtime('ES=F', Datacode.OPEN.value, 'YAHOO')
|
||||
self.assertEqual(float, type(s), 'test_realtime_US_futures OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.VOLUME.value, 'YAHOO')
|
||||
s = financials.getRealtime('ES=F', Datacode.VOLUME.value, 'YAHOO')
|
||||
self.assertEqual(float, type(s), 'test_realtime_US_futures VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.BID.value, 'YAHOO')
|
||||
s = financials.getRealtime('ES=F', Datacode.BID.value, 'YAHOO')
|
||||
self.assertEqual(float, type(s), 'test_realtime_US_futures BID {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.ASK.value, 'YAHOO')
|
||||
s = financials.getRealtime('ES=F', Datacode.ASK.value, 'YAHOO')
|
||||
self.assertEqual(float, type(s), 'test_realtime_US_futures ASK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.CHANGE.value, 'YAHOO')
|
||||
s = financials.getRealtime('ES=F', Datacode.CHANGE.value, 'YAHOO')
|
||||
self.assertEqual(float, type(s), 'test_realtime_US_futures CHANGE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.CHANGE_IN_PERCENT.value, 'YAHOO')
|
||||
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('ESH23.CME', Datacode.LOW.value, 'YAHOO')
|
||||
s = financials.getRealtime('ES=F', Datacode.LOW.value, 'YAHOO')
|
||||
self.assertEqual(float, type(s), 'test_realtime_US_futures LOW {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('ESH23.CME', Datacode.HIGH.value, 'YAHOO')
|
||||
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):
|
||||
@@ -233,11 +259,12 @@ 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('GMT', s, 'test_realtime_UK_ETF TIMEZONE {}'.format(s))
|
||||
self.assertEqual('BST', s, '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))
|
||||
self.assertEqual('iShares VII PLC - iShares Core S&P 500 UCITS ETF (CSP1.L)', s, 'test_realtime_UK_ETF NAME {}'.format(s))
|
||||
|
||||
def test_realtime_DE_equity(self):
|
||||
|
||||
@@ -245,7 +272,8 @@ 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('CET', s, 'test_realtime_DE_equity TIMEZONE {}'.format(s))
|
||||
self.assertEqual('CEST', s, '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))
|
||||
@@ -255,10 +283,10 @@ class Test(unittest.TestCase):
|
||||
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):
|
||||
@@ -312,6 +340,9 @@ class Test(unittest.TestCase):
|
||||
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))
|
||||
@@ -427,8 +458,8 @@ 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):
|
||||
|
||||
@@ -441,7 +472,7 @@ class Test(unittest.TestCase):
|
||||
s = financials.getRealtime('NO_NAME', Datacode.SECTOR.value, 'YAHOO')
|
||||
self.assertIsNone(s, 'test_realtime_errors SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('C060.DE', -1, 'YAHOO')
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user