mirror of
https://github.com/cmallwitz/Financials-Extension.git
synced 2026-08-27 10:04:17 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8933347508 | ||
|
|
7aadaad7dc | ||
|
|
01538a13a8 |
@@ -1,6 +1,7 @@
|
||||
# Financials-Extension
|
||||
|
||||
Extension for LibreOffice Calc to make stock, index and FX (Yahoo only) market data available in a Calc spread sheet - currently supports Yahoo and Google using web scraping.
|
||||
Extension for LibreOffice Calc to make stock market, index and FX data available in a Calc spread
|
||||
sheet - currently supports Yahoo's, Financial Times' and Google's (without FX data) finance web sites using web scraping.
|
||||
|
||||
### System dependencies:
|
||||
|
||||
@@ -14,10 +15,28 @@ The following steps have worked for me on a fresh Debian installation (as root)
|
||||
|
||||
### Usage:
|
||||
|
||||
Have a look at the 'releases' tab above: download a prebuild **Financials-Extension.oxt** file and load it into Calc under menu item: Tools, Extension Manager...
|
||||
Have a look at the 'releases' tab above: download a prebuild **Financials-Extension.oxt** file and load it into Calc
|
||||
under menu item: Tools, Extension Manager...
|
||||
|
||||
There is a file **examples.ods** there too with usage examples and possible arguments to functions.
|
||||
|
||||
You have to check the respective web sites to work out what symbol is the right one for you. If a web site 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
|
||||
site for comparison). While data for last price is most likely consistent across sites, they will differ when it comes to
|
||||
less well defined data points.
|
||||
|
||||
### Dealing with missing data:
|
||||
|
||||
First of all, a hint for using LibreCalc: if you want to refresh data you can press SHIFT-CTRL-F9 - this will force a
|
||||
recalculation of all formulas in all sheets.
|
||||
|
||||
Secondly the extension saves some debug information under your user directory in a directory ".financials-extension":
|
||||
the HTML for each stock symbol is saved in a separate file (depending on source and ticker symbol). You can open it
|
||||
your favorite web browser (or other tools) to check if the page actually contained the information you are looking for.
|
||||
If it does the file trace.log has a record of all calls to the extension and the value returned to LibreOffice.
|
||||
|
||||
### Build:
|
||||
|
||||
You will need the LibreOffice SDK installed.
|
||||
@@ -38,6 +57,7 @@ python3 src/test_google.py
|
||||
|
||||
### Tested with:
|
||||
- Debian 10.3 / LibreOffice Calc 6.1.5.2 / Python 3.7.3
|
||||
- Ubuntu 20.04 / LibreOffice Calc 6.4.3.2 / Python 3.8.2
|
||||
- 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 18.04 / LibreOffice Calc 6 / Python 3.6.7
|
||||
|
||||
@@ -29,6 +29,7 @@ cp -f "${PWD}"/src/baseclient.py "${PWD}"/build/
|
||||
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/ft.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/google.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/yahoo.py "${PWD}"/build/
|
||||
|
||||
|
||||
Binary file not shown.
+56
-25
@@ -42,27 +42,38 @@ class BaseClient:
|
||||
def __init__(self):
|
||||
self.connections = {}
|
||||
self.cookies = cookiejar.CookieJar()
|
||||
self.last_url = None
|
||||
|
||||
self.basedir = os.path.join(str(pathlib.Path.home()), '.financials-extension')
|
||||
os.makedirs(self.basedir, exist_ok=True)
|
||||
|
||||
user_agents = [
|
||||
'Mozilla/5.0 (Windows NT 6.1; rv:73.0) Gecko/20100101 Firefox/73.0',
|
||||
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0',
|
||||
'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0'
|
||||
'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0',
|
||||
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0',
|
||||
'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0',
|
||||
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0',
|
||||
'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0',
|
||||
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0',
|
||||
'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0',
|
||||
|
||||
'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 (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/78.0.3904.108 Safari/537.36',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
|
||||
]
|
||||
|
||||
self.default_headers = {
|
||||
'User-Agent': random.sample(user_agents, 1)[0],
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Accept-Language': 'en-US,en;q=0.5'
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Connection': 'keep-alive',
|
||||
'Cache-Control': 'max-age=0'
|
||||
}
|
||||
|
||||
self.response = None
|
||||
|
||||
def request(self, method: str, url: str, data=None, headers={}, cookies=[], **kwargs):
|
||||
|
||||
_headers = self.default_headers.copy()
|
||||
@@ -92,6 +103,8 @@ class BaseClient:
|
||||
logger.debug('Creating request -----------------------------------------------------')
|
||||
logger.info('url=%s', url)
|
||||
|
||||
self.last_url = url
|
||||
|
||||
# generate and add cookie headers
|
||||
request = urllib.request.Request(url)
|
||||
|
||||
@@ -119,13 +132,13 @@ class BaseClient:
|
||||
|
||||
def urlopen(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
|
||||
|
||||
response = self.request('POST' if data else 'GET', url, data, headers, cookies, **kwargs)
|
||||
text = response.read()
|
||||
self.response = self.request('POST' if data else 'GET', url, data, headers, cookies, **kwargs)
|
||||
text = self.response.read()
|
||||
|
||||
# Allow two redirects: used by Yahoo for some cookie based consent
|
||||
|
||||
if 300 <= response.status < 400:
|
||||
location = response.getheader('Location')
|
||||
if 300 <= self.response.status < 400:
|
||||
location = self.response.getheader('Location')
|
||||
|
||||
if location and redirect:
|
||||
|
||||
@@ -133,11 +146,11 @@ class BaseClient:
|
||||
scheme, _, host, path = url.split('/', 3)
|
||||
location = '{}//{}{}'.format(scheme, host, location)
|
||||
|
||||
response = self.request('POST' if data else 'GET', location, data, headers, cookies, **kwargs)
|
||||
text = response.read()
|
||||
self.response = self.request('POST' if data else 'GET', location, data, headers, cookies, **kwargs)
|
||||
text = self.response.read()
|
||||
|
||||
if 300 <= response.status < 400:
|
||||
location = response.getheader('Location')
|
||||
if 300 <= self.response.status < 400:
|
||||
location = self.response.getheader('Location')
|
||||
|
||||
if location and redirect:
|
||||
|
||||
@@ -145,21 +158,21 @@ class BaseClient:
|
||||
scheme, _, host, path = url.split('/', 3)
|
||||
location = '{}//{}{}'.format(scheme, host, location)
|
||||
|
||||
response = self.request('POST' if data else 'GET', location, data, headers, cookies, **kwargs)
|
||||
text = response.read()
|
||||
self.response = self.request('POST' if data else 'GET', location, data, headers, cookies, **kwargs)
|
||||
text = self.response.read()
|
||||
else:
|
||||
raise RedirectException(location)
|
||||
|
||||
else:
|
||||
raise RedirectException(location)
|
||||
|
||||
if response.status >= 400:
|
||||
raise HttpException(url, response.status)
|
||||
if self.response.status >= 400:
|
||||
raise HttpException(url, self.response.status)
|
||||
|
||||
if response.getheader('Content-Encoding') == 'gzip':
|
||||
if self.response.getheader('Content-Encoding') == 'gzip':
|
||||
text = gzip.decompress(text)
|
||||
|
||||
content_type = response.headers.get_content_charset()
|
||||
content_type = self.response.headers.get_content_charset()
|
||||
if content_type is None:
|
||||
content_type = 'utf-8'
|
||||
text = codecs.decode(text, encoding=content_type, errors='ignore')
|
||||
@@ -176,6 +189,9 @@ class BaseClient:
|
||||
:return: value or None
|
||||
"""
|
||||
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
if datacode == Datacode.PREV_CLOSE.value and Datacode.PREV_CLOSE in data:
|
||||
return data[Datacode.PREV_CLOSE]
|
||||
@@ -187,10 +203,16 @@ class BaseClient:
|
||||
return data[Datacode.CHANGE]
|
||||
|
||||
elif datacode == Datacode.LAST_PRICE_DATE.value and Datacode.LAST_PRICE_DATE in data:
|
||||
return data[Datacode.LAST_PRICE_DATE].isoformat()
|
||||
if data[Datacode.LAST_PRICE_DATE]:
|
||||
return data[Datacode.LAST_PRICE_DATE].isoformat()
|
||||
else:
|
||||
return data[Datacode.LAST_PRICE_DATE]
|
||||
|
||||
elif datacode == Datacode.LAST_PRICE_TIME.value and Datacode.LAST_PRICE_TIME in data:
|
||||
return data[Datacode.LAST_PRICE_TIME].isoformat()
|
||||
if data[Datacode.LAST_PRICE_TIME]:
|
||||
return data[Datacode.LAST_PRICE_TIME].isoformat()
|
||||
else:
|
||||
return data[Datacode.LAST_PRICE_TIME]
|
||||
|
||||
elif datacode == Datacode.CHANGE_IN_PERCENT.value and Datacode.CHANGE_IN_PERCENT in data:
|
||||
return data[Datacode.CHANGE_IN_PERCENT]
|
||||
@@ -210,7 +232,7 @@ class BaseClient:
|
||||
elif datacode == Datacode.HIGH_52_WEEK.value and Datacode.HIGH_52_WEEK in data:
|
||||
return data[Datacode.HIGH_52_WEEK]
|
||||
|
||||
elif datacode == Datacode.MARKET_CAP.value and Datacode.MARKET_CAP in data and data[Datacode.MARKET_CAP]:
|
||||
elif datacode == Datacode.MARKET_CAP.value and Datacode.MARKET_CAP in data:
|
||||
return data[Datacode.MARKET_CAP]
|
||||
|
||||
elif datacode == Datacode.VOLUME.value and Datacode.VOLUME in data:
|
||||
@@ -225,20 +247,29 @@ class BaseClient:
|
||||
elif datacode == Datacode.ADJ_CLOSE.value and Datacode.ADJ_CLOSE in data:
|
||||
return data[Datacode.ADJ_CLOSE]
|
||||
|
||||
elif datacode == Datacode.SECTOR.value and Datacode.SECTOR in data:
|
||||
return data[Datacode.SECTOR]
|
||||
|
||||
elif datacode == Datacode.INDUSTRY.value and Datacode.INDUSTRY in data:
|
||||
return data[Datacode.INDUSTRY]
|
||||
|
||||
elif datacode == Datacode.TICKER.value and Datacode.TICKER in data:
|
||||
return data[Datacode.TICKER]
|
||||
|
||||
elif datacode == Datacode.EXCHANGE.value and data[Datacode.EXCHANGE]:
|
||||
elif datacode == Datacode.EXCHANGE.value and Datacode.EXCHANGE in data:
|
||||
return data[Datacode.EXCHANGE]
|
||||
|
||||
elif datacode == Datacode.CURRENCY.value and Datacode.CURRENCY in data:
|
||||
return data[Datacode.CURRENCY]
|
||||
|
||||
elif datacode == Datacode.NAME.value and data[Datacode.NAME]:
|
||||
elif datacode == Datacode.NAME.value and Datacode.NAME in data:
|
||||
return data[Datacode.NAME]
|
||||
|
||||
elif datacode == Datacode.TIMEZONE.value and Datacode.TIMEZONE in data and data[Datacode.TIMEZONE]:
|
||||
return str(data[Datacode.TIMEZONE])
|
||||
elif datacode == Datacode.TIMEZONE.value and Datacode.TIMEZONE in data:
|
||||
if data[Datacode.TIMEZONE] is not None and type(data[Datacode.TIMEZONE]) != str:
|
||||
return str(data[Datacode.TIMEZONE])
|
||||
else:
|
||||
return data[Datacode.TIMEZONE]
|
||||
|
||||
except BaseException as e:
|
||||
return 'BaseClient.return_value(\'{}\', {}) - {}'.format(data, datacode, e)
|
||||
|
||||
+3
-2
@@ -34,6 +34,9 @@ class Datacode(Enum):
|
||||
CLOSE = 90
|
||||
ADJ_CLOSE = 91
|
||||
|
||||
SECTOR = 98
|
||||
INDUSTRY = 99
|
||||
|
||||
TICKER = 101
|
||||
EXCHANGE = 102
|
||||
CURRENCY = 103
|
||||
@@ -42,8 +45,6 @@ class Datacode(Enum):
|
||||
|
||||
TIMESTAMP = 999
|
||||
|
||||
# TODO YAHOO fundInceptionDate
|
||||
|
||||
@classmethod
|
||||
def has_value(cls, value):
|
||||
return any(value == item.value for item in cls)
|
||||
|
||||
+5
-1
@@ -29,6 +29,7 @@ if current_dir not in sys.path:
|
||||
from datacode import Datacode
|
||||
import google
|
||||
import yahoo
|
||||
import ft
|
||||
from version import version
|
||||
|
||||
implementation_name = "com.financials.getinfo.python.FinancialsImpl" # as defined in Financials.xcu
|
||||
@@ -47,7 +48,7 @@ def profile(fn):
|
||||
|
||||
with open(os.path.join(basedir, 'trace.log'), "a+") as text_file:
|
||||
print(
|
||||
f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')} {fn.__name__} *args={args} r='{r}' {(1000 * elapsed):.3f} ms",
|
||||
f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')} {fn.__name__} *args={args[1:]} r='{r}' {(1000 * elapsed):.3f} ms",
|
||||
file=text_file)
|
||||
|
||||
return r
|
||||
@@ -62,6 +63,7 @@ class FinancialsImpl(unohelper.Base, Financials):
|
||||
self.ctx = ctx
|
||||
self.google = google.createInstance(ctx)
|
||||
self.yahoo = yahoo.createInstance(ctx)
|
||||
self.ft = ft.createInstance(ctx)
|
||||
|
||||
@profile
|
||||
def getRealtime(self, ticker, datacode=None, source=None):
|
||||
@@ -100,6 +102,8 @@ class FinancialsImpl(unohelper.Base, Financials):
|
||||
s = self.google.getRealtime(ticker, datacode)
|
||||
elif source == 'YAHOO':
|
||||
s = self.yahoo.getRealtime(ticker, datacode)
|
||||
elif source == 'FT':
|
||||
s = self.ft.getRealtime(ticker, datacode)
|
||||
else:
|
||||
s = 'Source \'{}\' not supported'.format(source)
|
||||
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
# ft.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 dateutil.parser
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import jsonParser
|
||||
from baseclient import BaseClient
|
||||
from datacode import Datacode
|
||||
from tz import whois_timezone_info
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def handle_abbreviations(s):
|
||||
s = str(s).strip().replace(',', '')
|
||||
if s.endswith('k'):
|
||||
return float(s[:-1]) * 1000
|
||||
elif s.endswith('m'):
|
||||
return float(s[:-1]) * 1000000
|
||||
elif s.endswith('bn'):
|
||||
return float(s[:-2]) * 1000000000
|
||||
elif s.endswith('tn'):
|
||||
return float(s[:-2]) * 1000000000000
|
||||
return float(s)
|
||||
|
||||
|
||||
class FT(BaseClient):
|
||||
|
||||
def __init__(self, ctx):
|
||||
super().__init__()
|
||||
|
||||
self.crumb = None
|
||||
self.realtime = {}
|
||||
self.historicdata = {}
|
||||
self.js = jsonParser.jsonObject
|
||||
|
||||
def getRealtime(self, ticker: str, datacode: int):
|
||||
|
||||
"""
|
||||
Retrieve data for ticker from Alpha Vantage and cache it for further lookups
|
||||
|
||||
:param ticker: the ticker symbol e.g. VOD.LON
|
||||
:param datacode: the requested datacode
|
||||
:return:
|
||||
"""
|
||||
|
||||
# remove white space
|
||||
ticker = "".join(ticker.split())
|
||||
|
||||
# use cached value for up to 60 seconds
|
||||
if ticker in self.realtime:
|
||||
tick = self.realtime[ticker]
|
||||
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
|
||||
return self._return_value(tick, datacode)
|
||||
else:
|
||||
del self.realtime[ticker]
|
||||
|
||||
if ticker not in self.realtime:
|
||||
self.realtime[ticker] = {}
|
||||
|
||||
tick = self.realtime[ticker]
|
||||
|
||||
asset_class = self.guess_asset_class(ticker)
|
||||
|
||||
url = f'https://markets.ft.com/data/{asset_class}/tearsheet/summary?s={ticker}'
|
||||
|
||||
try:
|
||||
text = self.urlopen(url, redirect=True, data=None, headers=None)
|
||||
with open(os.path.join(self.basedir, f'ft-{ticker}.html'), "w") as text_file:
|
||||
print(f"<!-- '{self.last_url}' -->\r\n\r\n{text}", file=text_file)
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return f'FT.getRealtime({ticker}, {datacode}) - urlopen endpoint: {str(e)}'
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
|
||||
tick[Datacode.NAME] = None
|
||||
tick[Datacode.TICKER] = None
|
||||
tick[Datacode.CURRENCY] = None
|
||||
tick[Datacode.LAST_PRICE] = None
|
||||
tick[Datacode.CHANGE] = None
|
||||
tick[Datacode.CHANGE_IN_PERCENT] = None
|
||||
tick[Datacode.VOLUME] = None
|
||||
tick[Datacode.LOW_52_WEEK] = None
|
||||
tick[Datacode.HIGH_52_WEEK] = None
|
||||
tick[Datacode.LAST_PRICE_DATE] = None
|
||||
tick[Datacode.LAST_PRICE_TIME] = None
|
||||
tick[Datacode.TIMEZONE] = None
|
||||
|
||||
tick[Datacode.OPEN] = None
|
||||
tick[Datacode.HIGH] = None
|
||||
tick[Datacode.LOW] = None
|
||||
tick[Datacode.PREV_CLOSE] = None
|
||||
tick[Datacode.MARKET_CAP] = None
|
||||
|
||||
tick[Datacode.EXCHANGE] = None
|
||||
tick[Datacode.AVG_DAILY_VOL_3MOMTH] = None
|
||||
|
||||
try:
|
||||
r = '<div class="mod-tearsheet-overview__header"><h1 [^>]*>(.*?)</h1>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text)
|
||||
if not match:
|
||||
return None
|
||||
start = match.span(0)[1]
|
||||
|
||||
tick[Datacode.NAME] = self.save_wrapper(
|
||||
lambda: html.unescape(match.group(1)).strip())
|
||||
|
||||
r = '<div class="mod-tearsheet-overview__header__symbol">(?:<div [^>]*>)?<span *[^>]*>(.*?)<'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
tick[Datacode.TICKER] = self.save_wrapper(
|
||||
lambda: html.unescape(match.group(1)).strip())
|
||||
|
||||
r = '<div class="mod-tearsheet-overview__esi">(.*?)<i.*?</i>(.*?)<'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
tick[Datacode.SECTOR] = self.save_wrapper(
|
||||
lambda: html.unescape(match.group(1)).strip())
|
||||
tick[Datacode.INDUSTRY] = self.save_wrapper(
|
||||
lambda: html.unescape(match.group(2)).strip())
|
||||
|
||||
r = r'<span [^>]*>Price \(([A-Z]+|--)\)</span><span [^>]*>([0-9,\.]+)</span>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
if match.group(1) != '--':
|
||||
tick[Datacode.CURRENCY] = self.save_wrapper(
|
||||
lambda: html.unescape(match.group(1)).strip())
|
||||
tick[Datacode.LAST_PRICE] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(2)).replace(',', '').strip()))
|
||||
|
||||
r = r'<span[^>]*>Today\'s Change</span><span[^>]*><span[^>]*>(?:<i[^>]*></i>)?([0-9,\.-]+) */ *([0-9,\.-]+)%</span>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
tick[Datacode.CHANGE] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(2)).replace(',', '').strip()))
|
||||
|
||||
r = r'<span[^>]*>Shares traded</span><span[^>]*>([0-9mk,\.]+)</span>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
tick[Datacode.VOLUME] = self.save_wrapper(
|
||||
lambda: handle_abbreviations(html.unescape(match.group(1))))
|
||||
|
||||
r = r'<span[^>]*>52 week range</span><span[^>]*>([0-9,\.]+) *- *([0-9,\.]+)</span>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
tick[Datacode.LOW_52_WEEK] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
tick[Datacode.HIGH_52_WEEK] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(2)).replace(',', '').strip()))
|
||||
|
||||
r = r'<div class="mod-disclaimer">.+?as of (.+?)\.?</div>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
|
||||
try:
|
||||
value = html.unescape(match.group(1)).strip()
|
||||
dt = dateutil.parser.parse(value, 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
|
||||
|
||||
# second attempt at 52 week range
|
||||
if Datacode.LOW_52_WEEK not in tick or not tick[Datacode.LOW_52_WEEK]:
|
||||
r = r'<span class="mod-ui-range-bar__container__label--lo"><span[^>]*>([0-9,\.]+)</span>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.LOW_52_WEEK] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
|
||||
if Datacode.HIGH_52_WEEK not in tick or not tick[Datacode.HIGH_52_WEEK]:
|
||||
r = r'<span class="mod-ui-range-bar__container__label--hi"><span[^>]*>([0-9,\.]+)</span>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.HIGH_52_WEEK] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
|
||||
# just moving forward to data table
|
||||
r = '<div class="mod-tearsheet-key-stats__data__table">'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
start = match.span(0)[1]
|
||||
|
||||
r = r'<th>Open</th><td>([0-9,\.]+)</td>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.OPEN] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
|
||||
r = r'<th>High</th><td>([0-9,\.]+)</td>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.HIGH] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
|
||||
r = r'<th>Low</th><td>([0-9,\.]+)</td>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.LOW] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
|
||||
r = r'<th>\s*Previous close\s*</th><td>\s*([0-9,\.]+)\s*</td>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.PREV_CLOSE] = self.save_wrapper(
|
||||
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
|
||||
|
||||
r = r'<th>\s*Average volume\s*</th><td>\s*([0-9,\.btnm]+)\s*</td>'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.AVG_DAILY_VOL_3MOMTH] = self.save_wrapper(
|
||||
lambda: handle_abbreviations(html.unescape(match.group(1))))
|
||||
|
||||
r = r'<th>\s*Market cap\s*</th><td>\s*([0-9,\.btnm]+)\s*<'
|
||||
match = re.compile(r, flags=re.DOTALL).search(text, start)
|
||||
if match:
|
||||
tick[Datacode.MARKET_CAP] = self.save_wrapper(
|
||||
lambda: handle_abbreviations(html.unescape(match.group(1))))
|
||||
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return f'FT.getRealtime({ticker}, {datacode}) - process: {str(e)}'
|
||||
|
||||
logger.info(tick)
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
|
||||
def getHistoric(self, ticker, datacode, date):
|
||||
return 'FT.getHistoric: Historic Data not implemented.'
|
||||
|
||||
def guess_asset_class(self, ticker):
|
||||
|
||||
if len(ticker) == 6:
|
||||
if ticker[0:2] in ['USD', 'EUR', 'GBP', 'JPY', 'CHF']:
|
||||
return 'currencies'
|
||||
if ticker[3:5] in ['USD', 'EUR', 'GBP', 'JPY', 'CHF']:
|
||||
return 'currencies'
|
||||
|
||||
colon_count = ticker.count(':')
|
||||
|
||||
if colon_count == 0:
|
||||
return 'funds'
|
||||
elif colon_count == 3:
|
||||
return 'etfs'
|
||||
|
||||
return 'equities'
|
||||
|
||||
|
||||
def createInstance(ctx):
|
||||
return FT(ctx)
|
||||
@@ -14,7 +14,7 @@ import os
|
||||
cur_dir = os.getcwd()
|
||||
|
||||
addin_id = "com.financials.getinfo"
|
||||
addin_version = "1.0.6"
|
||||
addin_version = "2.1.1"
|
||||
addin_displayname = "Financial Market Extension"
|
||||
addin_publisher_link = "https://github.com/cmallwitz/Financials-Extension"
|
||||
addin_publisher_name = "The Publisher"
|
||||
|
||||
+34
-13
@@ -8,6 +8,7 @@
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
|
||||
import dateutil
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
@@ -16,8 +17,6 @@ import time
|
||||
import traceback
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import dateutil
|
||||
|
||||
from baseclient import BaseClient, RedirectException
|
||||
from datacode import Datacode
|
||||
from naivehtmlparser import NaiveHTMLParser
|
||||
@@ -31,12 +30,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def handle_abbreviations(s):
|
||||
s = str(s).strip()
|
||||
if s.endswith('T'):
|
||||
return float(s.replace('T', '')) * 1000
|
||||
if s.endswith('M'):
|
||||
return float(s.replace('M', '')) * 1000000
|
||||
if s.endswith('B'):
|
||||
return float(s.replace('B', '')) * 1000000000
|
||||
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)
|
||||
|
||||
|
||||
@@ -56,7 +55,7 @@ class Google(BaseClient):
|
||||
"""
|
||||
Retrieve realtime data for ticker from Google Finance and cache it for further lookups
|
||||
|
||||
:param ticker: the ticker symbol e.g. VOD.L or LON:VOD
|
||||
:param ticker: the ticker symbol e.g. LON:VOD
|
||||
:param datacode: the requested datacode
|
||||
:return:
|
||||
"""
|
||||
@@ -103,22 +102,46 @@ class Google(BaseClient):
|
||||
|
||||
tick = self.realtime[ticker]
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
|
||||
tick[Datacode.NAME] = None
|
||||
tick[Datacode.TICKER] = None
|
||||
tick[Datacode.CURRENCY] = None
|
||||
tick[Datacode.LAST_PRICE] = None
|
||||
tick[Datacode.CHANGE] = None
|
||||
tick[Datacode.CHANGE_IN_PERCENT] = None
|
||||
tick[Datacode.VOLUME] = None
|
||||
tick[Datacode.LOW_52_WEEK] = None
|
||||
tick[Datacode.HIGH_52_WEEK] = None
|
||||
tick[Datacode.LAST_PRICE_DATE] = None
|
||||
tick[Datacode.LAST_PRICE_TIME] = None
|
||||
tick[Datacode.TIMEZONE] = None
|
||||
|
||||
tick[Datacode.OPEN] = None
|
||||
tick[Datacode.HIGH] = None
|
||||
tick[Datacode.LOW] = None
|
||||
tick[Datacode.PREV_CLOSE] = None
|
||||
tick[Datacode.MARKET_CAP] = None
|
||||
|
||||
tick[Datacode.EXCHANGE] = None
|
||||
tick[Datacode.AVG_DAILY_VOL_3MOMTH] = None
|
||||
|
||||
try:
|
||||
r = '<span[^>]+role="heading"[^>]+>(.*?)</span>'
|
||||
pattern = re.compile(r)
|
||||
|
||||
match = pattern.search(text)
|
||||
if not match:
|
||||
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
|
||||
return None
|
||||
start = match.span(0)[1]
|
||||
|
||||
tick[Datacode.NAME] = self.save_wrapper(
|
||||
lambda: html.unescape(un_span(match.group(1)).strip()))
|
||||
|
||||
r = '<div [^>]*>(.*?)</div>'
|
||||
# next div is TICKER
|
||||
r = '<div [^>]*><div [^>]*>(.*?)</div></div>'
|
||||
pattern = re.compile(r)
|
||||
|
||||
# first div is TICKER
|
||||
match = pattern.search(text, start)
|
||||
if not match:
|
||||
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
|
||||
@@ -226,8 +249,6 @@ class Google(BaseClient):
|
||||
lambda: float(
|
||||
html.unescape(table.find('./tr[4]/td[2]').text).replace(',', '').strip()))
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
|
||||
logger.info(tick)
|
||||
|
||||
except BaseException as e:
|
||||
|
||||
+47
-50
@@ -8,20 +8,25 @@
|
||||
# Updated 8 Jan 2007 - fixed dict grouping bug, and made elements and
|
||||
# members optional in array and object collections
|
||||
#
|
||||
# Updated 9 Aug 2016 - use more current pyparsing constructs/idioms
|
||||
#
|
||||
|
||||
# https://github.com/pyparsing/pyparsing/blob/master/examples/jsonParser.py - revision 53d1b4a on 1 Nov 2019
|
||||
|
||||
json_bnf = """
|
||||
object
|
||||
{ members }
|
||||
{}
|
||||
members
|
||||
string : value
|
||||
members , string : value
|
||||
array
|
||||
object
|
||||
{ members }
|
||||
{}
|
||||
members
|
||||
string : value
|
||||
members , string : value
|
||||
array
|
||||
[ elements ]
|
||||
[]
|
||||
elements
|
||||
value
|
||||
elements , value
|
||||
value
|
||||
[]
|
||||
elements
|
||||
value
|
||||
elements , value
|
||||
value
|
||||
string
|
||||
number
|
||||
object
|
||||
@@ -31,40 +36,38 @@ value
|
||||
null
|
||||
"""
|
||||
|
||||
from pyparsing import *
|
||||
import pyparsing as pp
|
||||
from pyparsing import pyparsing_common as ppc
|
||||
|
||||
TRUE = Keyword("true").setParseAction(replaceWith(True))
|
||||
FALSE = Keyword("false").setParseAction(replaceWith(False))
|
||||
NULL = Keyword("null").setParseAction(replaceWith(None))
|
||||
|
||||
jsonString = dblQuotedString.setParseAction(removeQuotes)
|
||||
jsonNumber = Combine(Optional('-') + ('0' | Word('123456789', nums)) +
|
||||
Optional('.' + Word(nums)) +
|
||||
Optional(Word('eE', exact=1) + Word(nums + '+-', nums)))
|
||||
def make_keyword(kwd_str, kwd_value):
|
||||
return pp.Keyword(kwd_str).setParseAction(pp.replaceWith(kwd_value))
|
||||
|
||||
jsonObject = Forward()
|
||||
jsonValue = Forward()
|
||||
jsonElements = delimitedList(jsonValue)
|
||||
jsonArray = Group(Suppress('[') + Optional(jsonElements) + Suppress(']'))
|
||||
jsonValue << (jsonString | jsonNumber | Group(jsonObject) | jsonArray | TRUE | FALSE | NULL)
|
||||
memberDef = Group(jsonString + Suppress(':') + jsonValue)
|
||||
jsonMembers = delimitedList(memberDef)
|
||||
jsonObject << Dict(Suppress('{') + Optional(jsonMembers) + Suppress('}'))
|
||||
|
||||
jsonComment = cppStyleComment
|
||||
TRUE = make_keyword("true", True)
|
||||
FALSE = make_keyword("false", False)
|
||||
NULL = make_keyword("null", None)
|
||||
|
||||
LBRACK, RBRACK, LBRACE, RBRACE, COLON = map(pp.Suppress, "[]{}:")
|
||||
|
||||
jsonString = pp.dblQuotedString().setParseAction(pp.removeQuotes)
|
||||
jsonNumber = ppc.number()
|
||||
|
||||
jsonObject = pp.Forward()
|
||||
jsonValue = pp.Forward()
|
||||
jsonElements = pp.delimitedList(jsonValue)
|
||||
jsonArray = pp.Group(LBRACK + pp.Optional(jsonElements, []) + RBRACK)
|
||||
jsonValue << (
|
||||
jsonString | jsonNumber | pp.Group(jsonObject) | jsonArray | TRUE | FALSE | NULL
|
||||
)
|
||||
memberDef = pp.Group(jsonString + COLON + jsonValue)
|
||||
jsonMembers = pp.delimitedList(memberDef)
|
||||
jsonObject << pp.Dict(LBRACE + pp.Optional(jsonMembers) + RBRACE)
|
||||
|
||||
jsonComment = pp.cppStyleComment
|
||||
jsonObject.ignore(jsonComment)
|
||||
|
||||
|
||||
def convertNumbers(s, l, toks):
|
||||
n = toks[0]
|
||||
try:
|
||||
return int(n)
|
||||
except ValueError as ve:
|
||||
return float(n)
|
||||
|
||||
|
||||
jsonNumber.setParseAction(convertNumbers)
|
||||
|
||||
if __name__ == "__main__":
|
||||
testdata = """
|
||||
{
|
||||
@@ -72,7 +75,7 @@ if __name__ == "__main__":
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList":
|
||||
"GlossList":
|
||||
{
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
@@ -96,20 +99,14 @@ if __name__ == "__main__":
|
||||
}
|
||||
"""
|
||||
|
||||
import pprint
|
||||
|
||||
results = jsonObject.parseString(testdata)
|
||||
pprint.pprint(results.asList())
|
||||
print
|
||||
|
||||
results.pprint()
|
||||
print()
|
||||
|
||||
def testPrint(x):
|
||||
print
|
||||
type(x), repr(x)
|
||||
print(type(x), repr(x))
|
||||
|
||||
|
||||
print
|
||||
results.glossary.GlossDiv.GlossList.keys()
|
||||
print(list(results.glossary.GlossDiv.GlossList.keys()))
|
||||
testPrint(results.glossary.title)
|
||||
testPrint(results.glossary.GlossDiv.GlossList.ID)
|
||||
testPrint(results.glossary.GlossDiv.GlossList.FalseValue)
|
||||
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
# test_yahoo.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
|
||||
|
||||
import financials
|
||||
from datacode import Datacode
|
||||
|
||||
financials = financials.createInstance(None)
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_currency(self):
|
||||
s = financials.getRealtime('EURGBP', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_currency LAST_PRICE')
|
||||
|
||||
s = financials.getRealtime('EURGBP', Datacode.CURRENCY.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_currency CURRENCY')
|
||||
|
||||
def test_US_equity(self):
|
||||
|
||||
s = financials.getRealtime('INTC:NSQ', Datacode.CHANGE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity CHANGE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('INTC:NSQ', Datacode.CHANGE_IN_PERCENT.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity CHANGE_IN_PERCENT {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('INTC:NSQ', Datacode.AVG_DAILY_VOL_3MOMTH.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity AVG_DAILY_VOL_3MOMTH {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('INTC:NSQ', Datacode.MARKET_CAP.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity MARKET_CAP {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.TICKER.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_equity TICKER {}'.format(s))
|
||||
self.assertEqual(s, 'IBM:NYQ', 'test_US_equity TICKER {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.PREV_CLOSE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity PREV_CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.OPEN.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.LOW.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity LOW {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.HIGH.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity HIGH {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.VOLUME.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_equity VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.NAME.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_equity NAME {}'.format(s))
|
||||
self.assertEqual(s, 'International Business Machines Corp',
|
||||
'test_US_equity NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.SECTOR.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_equity SECTOR {}'.format(s))
|
||||
self.assertEqual(s, 'Technology', 'test_US_equity SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.INDUSTRY.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_equity INDUSTRY {}'.format(s))
|
||||
self.assertEqual(s, 'Software & Computer Services', 'test_US_equity INDUSTRY {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM:NYQ', Datacode.TIMEZONE.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_equity TIMEZONE {}'.format(s))
|
||||
|
||||
def test_US_mutuals(self):
|
||||
|
||||
s = financials.getRealtime('VGSLX', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_mutuals LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VGSLX', Datacode.NAME.value, 'FT')
|
||||
self.assertEqual(s, "Vanguard Real Estate Index Fund Admiral Shares",
|
||||
'test_US_mutuals NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VGSLX', Datacode.CURRENCY.value, 'FT')
|
||||
self.assertEqual(s, "USD", 'test_US_mutuals CURRENCY {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VGSLX', Datacode.CHANGE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_mutuals CHANGE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VGSLX', Datacode.CHANGE_IN_PERCENT.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_mutuals CHANGE_IN_PERCENT {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_US_mutuals LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_DATE.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_mutuals LAST_PRICE_DATE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_TIME.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_US_mutuals LAST_PRICE_TIME {}'.format(s))
|
||||
|
||||
def test_UK_ETF(self):
|
||||
|
||||
s = financials.getRealtime('CSP1:LSE:GBX', Datacode.NAME.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_UK_ETF NAME {}'.format(s))
|
||||
self.assertEqual(s, 'iShares Core S&P 500 UCITS ETF USD (Acc)',
|
||||
'test_UK_ETF NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('C060:GER:EUR', Datacode.NAME.value, 'FT')
|
||||
self.assertEqual(type(s), str, 't_UK_ETF NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VERX:LSE:GBP', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_UK_ETF LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VERX:LSE:GBP', Datacode.TIMEZONE.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_UK_ETF TIMEZONE {}'.format(s))
|
||||
|
||||
def test_DE_equity(self):
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.NAME.value, 'FT')
|
||||
self.assertEqual(s, "SAP SE", 'test_DE_equity NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.TICKER.value, 'FT')
|
||||
self.assertEqual(s, "SAPX:GER", 'test_DE_equity TICKER {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.CURRENCY.value, 'FT')
|
||||
self.assertEqual(s, 'EUR', 'test_DE_equity CURRENCY {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.CHANGE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity CHANGE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.CHANGE_IN_PERCENT.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity CHANGE_IN_PERCENT {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.VOLUME.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.OPEN.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.HIGH.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity HIGH {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.LOW.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity LOW {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.PREV_CLOSE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity PREV_CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.MARKET_CAP.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity MARKET_CAP {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.SECTOR.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_DE_equity SECTOR {}'.format(s))
|
||||
self.assertEqual(s, 'Technology', 'test_DE_equity SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.INDUSTRY.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_DE_equity INDUSTRY {}'.format(s))
|
||||
self.assertEqual(s, 'Software & Computer Services', 'test_DE_equity INDUSTRY {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.LAST_PRICE_DATE.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_DE_equity LAST_PRICE_DATE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAPX:GER', Datacode.TIMEZONE.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_DE_equity TIMEZONE {}'.format(s))
|
||||
|
||||
def test_TY_equity(self):
|
||||
s = financials.getRealtime('6503:TYO', Datacode.OPEN.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.LOW.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity LOW {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.HIGH.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity HIGH {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.LOW_52_WEEK.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity LOW_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.HIGH_52_WEEK.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity HIGH_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.MARKET_CAP.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity MARKET_CAP {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.VOLUME.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.CURRENCY.value, 'FT')
|
||||
self.assertEqual(s, 'JPY', 'test_TY_equity CURRENCY')
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.SECTOR.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_TY_equity SECTOR {}'.format(s))
|
||||
self.assertEqual(s, 'Industrials', 'test_TY_equity SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503:TYO', Datacode.INDUSTRY.value, 'FT')
|
||||
self.assertEqual(type(s), str, 'test_TY_equity INDUSTRY {}'.format(s))
|
||||
self.assertEqual(s, 'General Industrials', 'test_TY_equity INDUSTRY {}'.format(s))
|
||||
|
||||
def test_index(self):
|
||||
|
||||
s = financials.getRealtime('INX:IOM', Datacode.TICKER.value, 'FT')
|
||||
self.assertEqual(s, "INX:IOM", 'test_index TICKER {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.TICKER.value, 'FT')
|
||||
self.assertEqual(s, "DAXX:GER", 'test_index TICKER {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_index LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.VOLUME.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.LOW_52_WEEK.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity LOW_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.HIGH_52_WEEK.value, 'FT')
|
||||
self.assertEqual(type(s), float, 'test_DE_equity HIGH_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.OPEN.value, 'FT')
|
||||
self.assertIsNone(s, 'test_DE_equity OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.HIGH.value, 'FT')
|
||||
self.assertIsNone(s, 'test_DE_equity HIGH {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.LOW.value, 'FT')
|
||||
self.assertIsNone(s, 'test_DE_equity LOW {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.PREV_CLOSE.value, 'FT')
|
||||
self.assertIsNone(s, 'test_DE_equity PREV_CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('DAXX:GER', Datacode.MARKET_CAP.value, 'FT')
|
||||
self.assertIsNone(s, 'test_DE_equity MARKET_CAP {}'.format(s))
|
||||
|
||||
def test_errors(self):
|
||||
|
||||
s = financials.getRealtime('NO_NAME', Datacode.LAST_PRICE.value, 'FT')
|
||||
self.assertIsNone(s, 'test_errors LAST_PRICE {}'.format(s))
|
||||
|
||||
|
||||
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)
|
||||
+9
-13
@@ -24,11 +24,7 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_currency(self):
|
||||
s = financials.getRealtime('EURGBP', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual('Google.getRealtime(EURGBP, 21) - no match', s, 'test_currency LAST_PRICE')
|
||||
|
||||
# s = financials.getRealtime('EURGBP', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
# self.assertEqual(type(s), str, 'test_currency CURRENCY')
|
||||
# self.assertEqual(s, '', 'test_currency CURRENCY')
|
||||
self.assertIsNone(s, 'test_currency LAST_PRICE')
|
||||
|
||||
def test_UK_equity(self):
|
||||
s = financials.getRealtime('LON:VOD', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
@@ -97,22 +93,22 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_DE_ETF(self):
|
||||
s = financials.getRealtime('FRA:C060', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_DE_ETF LAST_PRICE {}'.format(s))
|
||||
self.assertEqual(float, type(s), 'test_DE_ETF LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('FRA:C060', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'EUR', 'test_DE_ETF CURRENCY')
|
||||
self.assertEqual('EUR', s, 'test_DE_ETF CURRENCY')
|
||||
|
||||
s = financials.getRealtime('FRA:C060', Datacode.TICKER.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'C060', 'test_DE_ETF TICKER')
|
||||
self.assertEqual('C060', s, 'test_DE_ETF TICKER')
|
||||
|
||||
s = financials.getRealtime('FRA:C060', Datacode.EXCHANGE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'FRA', 'test_DE_ETF EXCHANGE')
|
||||
self.assertEqual('FRA', s, 'test_DE_ETF EXCHANGE')
|
||||
|
||||
s = financials.getRealtime('FRA:C060', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'EUR', 'test_DE_ETF CURRENCY')
|
||||
self.assertEqual('EUR', s, 'test_DE_ETF CURRENCY')
|
||||
|
||||
s = financials.getRealtime('FRA:C060', Datacode.MARKET_CAP.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'Data doesn\'t exist - 27', 'test_DE_ETF TIMESTAMP {}'.format(s))
|
||||
self.assertIsNone(s, 'test_DE_ETF MARKET_CAP {}'.format(s))
|
||||
|
||||
def test_TY_equity(self):
|
||||
s = financials.getRealtime('TYO:6503', Datacode.OPEN.value, 'GOOGLE')
|
||||
@@ -202,7 +198,7 @@ class Test(unittest.TestCase):
|
||||
self.assertEqual(s, 'USD', 'test_US_mutuals CURRENCY')
|
||||
|
||||
s = financials.getRealtime('MUTF:VFIAX', Datacode.TIMEZONE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'Data doesn\'t exist - 105', 'test_US_mutuals')
|
||||
self.assertIsNone(s, 'test_US_mutuals TIMEZONE')
|
||||
|
||||
def test_index(self):
|
||||
s = financials.getRealtime('INDEXDB:DAX', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
@@ -231,7 +227,7 @@ class Test(unittest.TestCase):
|
||||
self.assertEqual(s, 'Datacode is empty', 'test_errors')
|
||||
|
||||
s = financials.getRealtime('DOES_NOT_EXISTS', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'Google.getRealtime(DOES_NOT_EXISTS, 21) - no match', 'test_errors')
|
||||
self.assertIsNone(s, 'test_errors')
|
||||
|
||||
s = financials.getRealtime('NYS:IBM', 'Foo', 'GOOGLE')
|
||||
self.assertEqual(s, 'Datacode is not a number', 'test_errors')
|
||||
|
||||
+59
-10
@@ -72,6 +72,14 @@ class Test(unittest.TestCase):
|
||||
self.assertEqual(s, 'International Business Machines Corporation',
|
||||
'test_realtime_US_equity NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM', Datacode.SECTOR.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 'test_realtime_US_equity SECTOR {}'.format(s))
|
||||
self.assertEqual(s, 'Technology', 'test_realtime_US_equity SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM', Datacode.INDUSTRY.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, '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, 'America/New_York', 'test_realtime_US_equity TIMEZONE {}'.format(s))
|
||||
|
||||
@@ -102,8 +110,8 @@ class Test(unittest.TestCase):
|
||||
self.assertEqual(s, 'iShares VII Public Limited Company - iShares Core S&P 500 UCITS ETF',
|
||||
'test_realtime_UK_ETF NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('C060.DE', 104, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 't_realtime_UK_ETF AME {}'.format(s))
|
||||
s = financials.getRealtime('C060.DE', Datacode.NAME.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 't_realtime_UK_ETF NAME {}'.format(s))
|
||||
|
||||
def test_realtime_DE_equity(self):
|
||||
|
||||
@@ -113,24 +121,65 @@ class Test(unittest.TestCase):
|
||||
s = financials.getRealtime('SAP.DE', Datacode.TIMEZONE.value, 'YAHOO')
|
||||
self.assertEqual(s, 'Europe/Berlin', 'test_realtime_DE_equity TIMEZONE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAP.DE', Datacode.SECTOR.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 'test_realtime_DE_equity SECTOR {}'.format(s))
|
||||
self.assertEqual(s, 'Technology', 'test_realtime_DE_equity SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('SAP.DE', Datacode.INDUSTRY.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 'test_realtime_DE_equity INDUSTRY {}'.format(s))
|
||||
self.assertEqual(s, 'Software—Application', 'test_realtime_DE_equity INDUSTRY {}'.format(s))
|
||||
|
||||
def test_TY_equity(self):
|
||||
s = financials.getRealtime('6503.T', Datacode.OPEN.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity OPEN {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.LOW.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity LOW {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.HIGH.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity HIGH {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.LOW_52_WEEK.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity LOW_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.HIGH_52_WEEK.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity HIGH_52_WEEK {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.MARKET_CAP.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity MARKET_CAP {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.VOLUME.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_TY_equity VOLUME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.CURRENCY.value, 'YAHOO')
|
||||
self.assertEqual(s, 'JPY', 'test_TY_equity CURRENCY')
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.SECTOR.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 'test_TY_equity SECTOR {}'.format(s))
|
||||
self.assertEqual(s, 'Industrials', 'test_TY_equity SECTOR {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('6503.T', Datacode.INDUSTRY.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 'test_TY_equity INDUSTRY {}'.format(s))
|
||||
self.assertEqual(s, 'Electrical Equipment & Parts', 'test_TY_equity INDUSTRY {}'.format(s))
|
||||
|
||||
def test_historic_US_equity(self):
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.LAST_PRICE.value, '2017-01-01', 'YAHOO')
|
||||
self.assertEqual(s, 'Not a trading day \'2017-01-01\'', 'test_historic_US_equity LAST_PRICE {}'.format(s))
|
||||
self.assertEqual('Not a trading day \'2017-01-01\'', s, 'test_historic_US_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2017-01-01', 'YAHOO')
|
||||
self.assertEqual(s, 'Not a trading day \'2017-01-01\'', 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
self.assertEqual('Not a trading day \'2017-01-01\'', s, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.LAST_PRICE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 'Data doesn\'t exist - 21', 'test_historic_US_equity LAST_PRICE {}'.format(s))
|
||||
self.assertEqual('Data doesn\'t exist - 21', s, 'test_historic_US_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 167.190002, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
self.assertEqual(167.190002, s, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
|
||||
financials.yahoo.historicdata = {}
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 167.190002, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
self.assertEqual(167.190002, s, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
|
||||
directory = os.path.join(str(pathlib.Path.home()), '.financials-extension')
|
||||
ibm = os.path.join(directory, 'yahoo-IBM.csv')
|
||||
@@ -142,11 +191,11 @@ class Test(unittest.TestCase):
|
||||
financials.yahoo.historicdata = {}
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 167.190002, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
self.assertEqual(167.190002, s, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
|
||||
# Note: quarterly dividend and splits will change past adjusted prices - will fail after the next dividend
|
||||
s = financials.getHistoric('IBM', Datacode.ADJ_CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 145.416626, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
|
||||
self.assertEqual(141.637695, s, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
|
||||
|
||||
def test_historic_UK_ETF(self):
|
||||
|
||||
@@ -202,7 +251,7 @@ class Test(unittest.TestCase):
|
||||
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))
|
||||
self.assertEqual(s, 68.209999, 'test_historic_DE_equity CLOSE {}'.format(s))
|
||||
|
||||
def test_realtime_errors(self):
|
||||
|
||||
|
||||
+18
-15
@@ -83,7 +83,7 @@ class Yahoo(BaseClient):
|
||||
"""
|
||||
Retrieve realtime data for ticker from Yahoo Finance and cache it for further lookups
|
||||
|
||||
:param ticker: the ticker symbol e.g. VOD.L or LON:VOD
|
||||
:param ticker: the ticker symbol e.g. VOD.L
|
||||
:param datacode: the requested datacode
|
||||
:return:
|
||||
"""
|
||||
@@ -103,7 +103,7 @@ class Yahoo(BaseClient):
|
||||
|
||||
cookies = [cookiejar.Cookie(version=0,
|
||||
name="B",
|
||||
value="9898htldgiar5&b=3&s=gt",
|
||||
value="7pbfivtfkl00m&b=3&s=if",
|
||||
port=None, port_specified=None,
|
||||
domain=".yahoo.com", domain_specified=True, domain_initial_dot=True,
|
||||
path="/", path_specified=True,
|
||||
@@ -117,6 +117,8 @@ class Yahoo(BaseClient):
|
||||
|
||||
try:
|
||||
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=cookies)
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w") as text_file:
|
||||
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - urlopen: {}'.format(ticker, datacode, e)
|
||||
@@ -131,9 +133,6 @@ class Yahoo(BaseClient):
|
||||
|
||||
if match:
|
||||
self.crumb = match.group(1)
|
||||
else:
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w") as text_file:
|
||||
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
|
||||
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -143,22 +142,22 @@ class Yahoo(BaseClient):
|
||||
start = text.find('"QuoteSummaryStore":{')
|
||||
|
||||
if start < 0:
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w") as text_file:
|
||||
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
|
||||
return None
|
||||
|
||||
start = start + len('"QuoteSummaryStore":')
|
||||
results = self.js.parseString(text[start:])
|
||||
|
||||
if not results:
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w") as text_file:
|
||||
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
|
||||
return None
|
||||
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - parsing: {}'.format(ticker, datacode, e)
|
||||
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.js'.format(ticker)), "w") as text_file:
|
||||
print(f"// '{url}' QuoteSummaryStore:\n", file=text_file)
|
||||
pprint.pprint(results.asList(), stream=text_file)
|
||||
|
||||
try:
|
||||
price = results['price']
|
||||
quoteType = results['quoteType']
|
||||
@@ -172,6 +171,8 @@ class Yahoo(BaseClient):
|
||||
|
||||
tick = self.realtime[ticker]
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
|
||||
tick[Datacode.PREV_CLOSE] = float(raw(price, 'regularMarketPreviousClose'))
|
||||
tick[Datacode.OPEN] = float(raw(price, 'regularMarketOpen'))
|
||||
tick[Datacode.CHANGE] = float(raw(price, 'regularMarketChange'))
|
||||
@@ -206,15 +207,17 @@ class Yahoo(BaseClient):
|
||||
else:
|
||||
tick[Datacode.NAME] = tick[Datacode.TICKER]
|
||||
|
||||
tick[Datacode.TIMESTAMP] = time.time()
|
||||
|
||||
except BaseException as e:
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.js'.format(ticker)), "w") as text_file:
|
||||
pprint.pprint(f"// '{url}'\r\n\r\n{results.asList()}", stream=text_file)
|
||||
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
|
||||
|
||||
try:
|
||||
summaryProfile = results['summaryProfile']
|
||||
tick[Datacode.SECTOR] = str(summaryProfile['sector'])
|
||||
tick[Datacode.INDUSTRY] = str(summaryProfile['industry'])
|
||||
except KeyError as e:
|
||||
pass
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
|
||||
def getHistoric(self, ticker: str, datacode: int, date):
|
||||
@@ -222,7 +225,7 @@ class Yahoo(BaseClient):
|
||||
"""
|
||||
Retrieve historic data for ticker from Yahoo Finance and cache it for further lookups
|
||||
|
||||
:param ticker: the ticker symbol e.g. VOD.L or LON:VOD
|
||||
:param ticker: the ticker symbol e.g. VOD.L
|
||||
:param datacode: the requested datacode
|
||||
:param date: the requested date
|
||||
:return:
|
||||
|
||||
Reference in New Issue
Block a user