Compare commits

...
5 Commits
16 changed files with 417 additions and 226 deletions
+52 -61
View File
@@ -1,38 +1,19 @@
# Financials-Extension
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, Financial Times' and Google's (without FX data) finance websites
using old-fashioned web scraping.
spreadsheets - currently supporting Yahoo's and Financial Times' finance websites using old-fashioned web scraping.
Starting with version 3 of the extension, Google was dropped as source as its finance website was changed dramatically
requiring a complete re-work of the logic to pull data of it. No one seems to have complained, so I guess not a lot of
people rely on it. If there is popular demand, I can take a look at adding it back. Alternatively I'm open for
suggestions to add another provider with global exchange and asset class coverage.
Additionally, starting with version 3 of the extension, it contains all non-standard Python packages it uses
(dateutil, pytz, pyparsing). Consequently, it is no longer necessary to install anything to your system.
### Feedback requested:
Please provide feedback about using the extension here: https://github.com/cmallwitz/Financials-Extension/issues/10
### System requirements:
This extension requires the following Python 3 packages (on top of standard libs): dateutil, pytz, pyparsing. These are
from within LibreOffice, so if your LibreOffice comes with it's own Python runtime (e.g. Windows) they need to be
installed there instead somewhere else in the system.
On Ubuntu 18 and 20, dateutil and pytz may already be installed, but you can get all you need by running
- sudo apt-get install python3-dateutil python3-tz python3-pyparsing
On Debian 10, the following steps have worked for me (as root)
- apt install python3-pip
- pip3 install python-dateutil pytz pyparsing
Users on Windows 10 have reported this to work - as LibreOffice on Windows ships with its own, but minimal
Python runtime a few more steps are required
- Download the script https://bootstrap.pypa.io/get-pip.py to your computer
- Start a Command Prompt (CMD) as Administrator
- on this command prompt run (change path as required)
"c:\Program Files\LibreOffice\program\python.exe" c:\temp\get-pip.py
- and then
"c:\Program Files\LibreOffice\program\python.exe" -m pip install python-dateutil pytz pyparsing
### Usage:
Under 'Releases' on GitHub there is downloadable **Financials-Extension.oxt** file - load it into Calc
@@ -40,7 +21,6 @@ under menu item: Tools, Extension Manager...
Getting data should be a simple as having this in a cell:
- `=GETREALTIME("IBM",21,"YAHOO")`
- `=GETREALTIME("NYSE:IBM",21,"GOOGLE")`
- `=GETREALTIME("IBM:NYQ",21,"FT")`
- `=GETHISTORIC("IBM",90,"2020-12-01","YAHOO")`
@@ -68,38 +48,43 @@ Depending on your system's language and default LibreOffice settings, you maybe
You can either specify numbers or names (lower or upper case) - not all bits are available from all sources and they are not necessarily consistent across sources either.
|Name|Code|YAHOO|FT|GOOGLE|YAHOO (historic)|
|Name|Code|YAHOO|FT|YAHOO (historic)|Notes|
| :--- | :--- | :---: | :---: | :---: | :---: |
|PREV_CLOSE|5|Yes|Yes|Yes||
|OPEN|6|Yes|Yes|Yes|Yes|
|CHANGE|7|Yes|Yes|Yes||
|LAST_PRICE_DATE|8|Yes|Yes|Yes||
|LAST_PRICE_TIME|10|Yes|Yes|Yes||
|CHANGE_IN_PERCENT|11|Yes|Yes|Yes||
|LOW|14|Yes|Yes|Yes|Yes|
|HIGH|16|Yes|Yes|Yes|Yes|
|LAST_PRICE|21|Yes|Yes|Yes||
|HIGH_52_WEEK|24|Yes|Yes|Yes||
|LOW_52_WEEK|26|Yes|Yes|Yes||
|MARKET_CAP|27|Yes|Yes|Yes||
|VOLUME|35|Yes|Yes|No|Yes|
|AVG_DAILY_VOL_3MONTH|39|Yes|Yes|No||
|BETA|67|Yes|Yes|No||
|EPS|68|Yes|Yes|No||
|PE_RATIO|69|Yes|Yes|No||
|DIV|70|Yes|Yes|No||
|DIV_YIELD|71|Yes|Yes|No||
|EX_DIV_DATE|72|Yes|Yes|No||
|PAYOUT_RATIO|73|Yes|No|No||
|CLOSE|90|No|No|No|Yes|
|ADJ_CLOSE|91|No|No|No|Yes|
|SECTOR|98|Yes|Yes|No||
|INDUSTRY|99|Yes|Yes|No||
|TICKER|101|Yes|Yes|Yes||
|EXCHANGE|102|Yes|No|Yes||
|CURRENCY|103|Yes|Yes|Yes||
|NAME|104|Yes|Yes|Yes||
|TIMEZONE|105|Yes|Yes|Yes||
|PREV_CLOSE|5|Yes|Yes|||
|OPEN|6|Yes|Yes|Yes||
|CHANGE|7|Yes|Yes|||
|LAST_PRICE_DATE|8|Yes|Yes| |yyyy-mm-dd|
|LAST_PRICE_TIME|10|Yes|Yes|||
|CHANGE_IN_PERCENT|11|Yes|Yes|||
|LOW|14|Yes|Yes|Yes||
|HIGH|16|Yes|Yes|Yes||
|LAST_PRICE|21|Yes|Yes|||
|BID|22|Yes|Yes|||
|ASK|25|Yes|Yes|||
|HIGH_52_WEEK|24|Yes|Yes|||
|LOW_52_WEEK|26|Yes|Yes|||
|MARKET_CAP|27|Yes|Yes|||
|BIDSIZE|30|Yes|Yes|||
|ASKSIZE|31|Yes|Yes|||
|VOLUME|35|Yes|Yes|Yes||
|AVG_DAILY_VOL_3MONTH|39|Yes|Yes|||
|BETA|67|Yes|Yes|||
|EPS|68|Yes|Yes|||
|PE_RATIO|69|Yes|Yes|||
|DIV|70|Yes|Yes|||
|DIV_YIELD|71|Yes|Yes|||
|EX_DIV_DATE|72|Yes|Yes| |yyyy-mm-dd|
|PAYOUT_RATIO|73|Yes|No|||
|EXPIRY_DATE|74|Yes (on options)|No| |yyyy-mm-dd|
|CLOSE|90|No|No|Yes||
|ADJ_CLOSE|91|No|No|Yes||
|SECTOR|98|Yes|Yes|||
|INDUSTRY|99|Yes|Yes|||
|TICKER|101|Yes|Yes|||
|EXCHANGE|102|Yes|No|||
|CURRENCY|103|Yes|Yes|||
|NAME|104|Yes|Yes|||
|TIMEZONE|105|Yes|Yes|||
### Dealing with missing data:
@@ -112,6 +97,10 @@ your favorite web browser (or other tools) to check if the page actually contain
If it does, the file trace.log has a record of all calls to the extension with the value returned to LibreOffice.
Otherwise, the file extension.log in the same location might have more details about errors or exceptions.
In general, web scraping can't be compared to using a stable API - the websites might have issues - from a technical or
data perspective. I have found especially on the weekend it can sometimes be "flaky" and closing/reopening LibreCalc can
refresh things.
### Build:
You will need the LibreOffice SDK installed.
@@ -129,11 +118,13 @@ python3 -m unittest discover src
./compile.sh
### Tested with:
- Windows 10 / LibreOffice Calc 7.1.2.2 / Python 3.8.8
- Ubuntu 20.04 / LibreOffice Calc 6.4.3.2 / Python 3.8.2
(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 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.5 / LibreOffice Calc 6 / Python 3.6.9
- Ubuntu 18.04 / LibreOffice Calc 6 / Python 3.6.7
- Ubuntu 16.04 / LibreOffice Calc 5 (previous versions)
+21
View File
@@ -38,6 +38,27 @@ cp -f "${PWD}"/src/financials_ft.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_google.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_yahoo.py "${PWD}"/build/
# this copies python modules dateutil, pytz, pyparsing to extension so it doesn't have to be installed by user
TMPFILE=`mktemp`
wget "https://files.pythonhosted.org/packages/d4/70/d60450c3dd48ef87586924207ae8907090de0b306af2bce5d134d78615cb/python_dateutil-2.8.1-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
unzip $TMPFILE pytz/\* -d "${PWD}"/build/
rm $TMPFILE
wget "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl" -O $TMPFILE
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
unzip $TMPFILE six.py -d "${PWD}"/build/
rm $TMPFILE
echo "Package into oxt file..."
pushd "${PWD}"/build/
zip -r "${PWD}"/Financials-Extension.zip ./*
BIN
View File
Binary file not shown.
+64 -17
View File
@@ -142,9 +142,12 @@ class BaseClient:
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
# Allow redirects - used by Yahoo for some cookie based consent
redirect_count = 3
if 300 <= self.response.status < 400:
while 300 <= self.response.status < 400 and redirect_count >= 0:
redirect_count -= 1
location = self.response.getheader('Location')
if location and redirect:
@@ -156,21 +159,6 @@ class BaseClient:
self.response = self.request('POST' if data else 'GET', location, data, headers, cookies, **kwargs)
text = self.response.read()
if 300 <= self.response.status < 400:
location = self.response.getheader('Location')
if location and redirect:
if location.startswith('/'):
scheme, _, host, path = url.split('/', 3)
location = '{}//{}{}'.format(scheme, host, location)
self.response = self.request('POST' if data else 'GET', location, data, headers, cookies,
**kwargs)
text = self.response.read()
else:
raise RedirectException(location)
else:
raise RedirectException(location)
@@ -187,6 +175,47 @@ class BaseClient:
return text
def get_ticker(self):
tick = {}
tick[Datacode.ADJ_CLOSE] = None
tick[Datacode.ASKSIZE] = None
tick[Datacode.ASK] = None
tick[Datacode.AVG_DAILY_VOL_3MONTH] = None
tick[Datacode.BETA] = None
tick[Datacode.BIDSIZE] = None
tick[Datacode.BID] = None
tick[Datacode.CHANGE] = None
tick[Datacode.CHANGE_IN_PERCENT] = None
tick[Datacode.CURRENCY] = None
tick[Datacode.DIV] = None
tick[Datacode.DIV_YIELD] = None
tick[Datacode.EPS] = None
tick[Datacode.EXCHANGE] = None
tick[Datacode.EXPIRY_DATE] = None
tick[Datacode.EX_DIV_DATE] = None
tick[Datacode.HIGH] = None
tick[Datacode.HIGH_52_WEEK] = None
tick[Datacode.INDUSTRY] = None
tick[Datacode.LAST_PRICE] = None
tick[Datacode.LAST_PRICE_DATE] = None
tick[Datacode.LAST_PRICE_TIME] = None
tick[Datacode.LOW] = None
tick[Datacode.LOW_52_WEEK] = None
tick[Datacode.MARKET_CAP] = None
tick[Datacode.NAME] = None
tick[Datacode.OPEN] = None
tick[Datacode.PAYOUT_RATIO] = None
tick[Datacode.PE_RATIO] = None
tick[Datacode.PREV_CLOSE] = None
tick[Datacode.SECTOR] = None
tick[Datacode.TICKER] = None
tick[Datacode.TIMEZONE] = None
tick[Datacode.VOLUME] = None
return tick
def _return_value(self, data: dict, datacode: int):
"""
@@ -234,6 +263,18 @@ class BaseClient:
elif datacode == Datacode.LAST_PRICE.value and Datacode.LAST_PRICE in data:
return data[Datacode.LAST_PRICE]
elif datacode == Datacode.BID.value and Datacode.BID in data:
return data[Datacode.BID]
elif datacode == Datacode.ASK.value and Datacode.ASK in data:
return data[Datacode.ASK]
elif datacode == Datacode.BIDSIZE.value and Datacode.BIDSIZE in data:
return data[Datacode.BIDSIZE]
elif datacode == Datacode.ASKSIZE.value and Datacode.ASKSIZE in data:
return data[Datacode.ASKSIZE]
elif datacode == Datacode.LOW_52_WEEK.value and Datacode.LOW_52_WEEK in data:
return data[Datacode.LOW_52_WEEK]
@@ -273,6 +314,12 @@ class BaseClient:
elif datacode == Datacode.PAYOUT_RATIO.value and Datacode.PAYOUT_RATIO in data:
return data[Datacode.PAYOUT_RATIO]
elif datacode == Datacode.EXPIRY_DATE.value and Datacode.EXPIRY_DATE in data:
if data[Datacode.EXPIRY_DATE]:
return data[Datacode.EXPIRY_DATE].isoformat()
else:
return data[Datacode.EXPIRY_DATE]
elif datacode == Datacode.CLOSE.value and Datacode.CLOSE in data:
return data[Datacode.CLOSE]
+6
View File
@@ -24,6 +24,11 @@ class Datacode(Enum):
LAST_PRICE = 21
BID = 22
ASK = 25
BIDSIZE = 30
ASKSIZE = 31
HIGH_52_WEEK = 24
LOW_52_WEEK = 26
MARKET_CAP = 27
@@ -38,6 +43,7 @@ class Datacode(Enum):
DIV_YIELD = 71
EX_DIV_DATE = 72
PAYOUT_RATIO = 73
EXPIRY_DATE = 74
CLOSE = 90
ADJ_CLOSE = 91
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:d="http://openoffice.org/extensions/description/2006"
xmlns:l="http://libreoffice.org/extensions/description/2011"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- only used for testing -->
<dependencies>
<l:LibreOffice-minimal-version value="5.0" d:name="LibreOffice 5.0" />
</dependencies>
<identifier value="com.financials.getinfo" />
<version value="3.0.0" />
<display-name><name lang="en">Financial Market Extension</name></display-name>
<publisher><name xlink:href="https://github.com/cmallwitz/Financials-Extension" lang="en">The Publisher</name></publisher>
<extension-description><src xlink:href="description-en-US.txt" lang="en" /></extension-description>
</description>
+24 -8
View File
@@ -18,6 +18,7 @@ import sys
import time
from functools import wraps
from importlib import util
import xml.etree.ElementTree as ET
import unohelper
from com.financials.getinfo import Financials
@@ -47,12 +48,14 @@ if dateutil_missing or pyparsing_missing or pytz_missing:
raise Exception("THIS EXTENSION NEEDS THE FOLLOWING PYTHON 3 LIBRARIES INSTALLED:" + msg)
import dateutil.parser
import pytz
import pyparsing
import six
from datacode import Datacode
import financials_google as google
import financials_yahoo as yahoo
import financials_ft as ft
from version import version
implementation_name = "com.financials.getinfo.python.FinancialsImpl" # as defined in Financials.xcu
implementation_services = ("com.sun.star.sheet.AddIn",)
@@ -87,7 +90,7 @@ class FinancialsImpl(unohelper.Base, Financials):
@profile
def getRealtime(self, ticker, datacode=None, source=None):
if ticker == 'SUPPORT':
if ticker == 'SUPPORT' or ticker == 'support':
return self.support(datacode)
try:
@@ -120,9 +123,10 @@ class FinancialsImpl(unohelper.Base, Financials):
ticker = str(ticker).strip()
source = str(source).upper()
if source == 'GOOGLE':
s = self.google.getRealtime(ticker, datacode)
elif source == 'YAHOO':
# if source == 'GOOGLE':
# s = self.google.getRealtime(ticker, datacode)
# el
if source == 'YAHOO':
s = self.yahoo.getRealtime(ticker, datacode)
elif source == 'FT':
s = self.ft.getRealtime(ticker, datacode)
@@ -219,7 +223,14 @@ class FinancialsImpl(unohelper.Base, Financials):
@profile
def support(self, datacode):
s = 'ctx={}\nid(self)={}\nversion={}\nfile={}\ncwd={}\nhome={}\nuname={}\npid={}\nsys.executable={}\nsys.version={}\nlocale={}\ndefaultlocale={}'.format(
version = '0.0.0'
description_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'description.xml')
for e in ET.parse(description_file).getroot():
if e.tag.endswith('version'):
version = e.attrib['value']
s = 'ctx={}\nid(self)={}\nversion={}\nfile={}\ncwd={}\nhome={}\nuname={}\npid={}\nsys.executable={}\nsys.version={}\nlocale={}\ndefaultlocale={}\ndateutil={}\npytz={}\npyparsing={}\nsix={}'.format(
self.ctx,
id(self),
version,
@@ -231,7 +242,12 @@ class FinancialsImpl(unohelper.Base, Financials):
sys.executable,
sys.version.replace("\n", " "),
locale.getlocale(),
locale.getdefaultlocale())
locale.getdefaultlocale(),
dateutil.__version__,
pytz.__version__,
pyparsing.__version__,
six.__version__,
)
if datacode:
s = '{}\ntype(datacode)={}\nstr(datacode)={}'.format(
@@ -246,6 +262,6 @@ def createInstance(ctx):
return FinancialsImpl(ctx)
# pythonloader looks for a static g_ImplementationHelper variable
# python loader looks for a static g_ImplementationHelper variable
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(createInstance, implementation_name, implementation_services, )
+26 -44
View File
@@ -12,6 +12,7 @@ import logging
import os
import re
import time
import urllib.parse
import dateutil.parser
@@ -52,16 +53,13 @@ class FT(BaseClient):
def getRealtime(self, ticker: str, datacode: int):
"""
Retrieve data for ticker from Alpha Vantage and cache it for further lookups
Retrieve data for ticker from Financial Times and cache it for further lookups
:param ticker: the ticker symbol e.g. VOD.LON
:param ticker: the ticker symbol e.g. VOD:LSE
: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]
@@ -71,13 +69,13 @@ class FT(BaseClient):
del self.realtime[ticker]
if ticker not in self.realtime:
self.realtime[ticker] = {}
self.realtime[ticker] = self.get_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}'
url = f'https://markets.ft.com/data/{asset_class}/tearsheet/summary?s={urllib.parse.quote_plus(ticker)}'
try:
text = self.urlopen(url, redirect=True, data=None, headers=None)
@@ -86,42 +84,14 @@ class FT(BaseClient):
return f'FT.getRealtime({ticker}, {datacode}) - urlopen endpoint: {str(e)}'
try:
with open(os.path.join(self.basedir, f'ft-{ticker}.html'), "w", encoding="utf-8") as text_file:
temp = ticker.replace(':', '_') # Windows can't have ':' in file names
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 as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
except BaseException:
logger.exception("BaseException ticker=%s datacode=%s %s", ticker, datacode)
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_3MONTH] = None
tick[Datacode.BETA] = None
tick[Datacode.EPS] = None
tick[Datacode.PE_RATIO] = None
tick[Datacode.DIV] = None
tick[Datacode.DIV_YIELD] = None
tick[Datacode.EX_DIV_DATE] = None
tick[Datacode.PAYOUT_RATIO] = None
try:
r = '<h1 class="mod-tearsheet-overview__header__name mod-tearsheet-overview__header__name--large">(.*?)</h1>'
match = re.compile(r, flags=re.DOTALL).search(text)
@@ -148,7 +118,7 @@ class FT(BaseClient):
tick[Datacode.INDUSTRY] = self.save_wrapper(
lambda: html.unescape(match.group(2)).strip())
r = r'<span [^>]*>Price \(([A-Z]+|--)\)</span><span [^>]*>([0-9,\.]+)</span>'
r = r'<span [^>]*>Price \(([A-Za-z]{3}|--)\)</span><span [^>]*>([0-9,\.]+)</span>'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
start = match.span(0)[1]
@@ -205,7 +175,7 @@ class FT(BaseClient):
if len(time_bits) >= 4:
tick[Datacode.TIMEZONE] = time_bits[-1]
except BaseException as e:
except BaseException:
pass
# second attempt at 52 week range
@@ -247,13 +217,25 @@ class FT(BaseClient):
tick[Datacode.LOW] = self.save_wrapper(
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
r = r'<th>\s*Bid\s*</th><td>([0-9,\.]+)</td>'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.BID] = self.save_wrapper(
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
r = r'<th>\s*Offer\s*</th><td>([0-9,\.]+)</td>'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.ASK] = 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>'
r = r'<th>\s*Average volume\s*</th><td>\s*([0-9,\.btnmk]+)\s*</td>'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.AVG_DAILY_VOL_3MONTH] = self.save_wrapper(
@@ -269,7 +251,7 @@ class FT(BaseClient):
tick[Datacode.PE_RATIO] = self.save_wrapper(
lambda: float(value))
r = r'<th>\s*Market cap\s*</th><td>\s*([0-9,\.btnm]+)\s*<'
r = r'<th>\s*Market cap\s*</th><td>\s*([0-9,\.btnmk]+)\s*<'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.MARKET_CAP] = self.save_wrapper(
@@ -300,7 +282,7 @@ class FT(BaseClient):
value = html.unescape(match.group(1)).strip()
dt = dateutil.parser.parse(value, yearfirst=True, dayfirst=False, tzinfos=whois_timezone_info)
tick[Datacode.EX_DIV_DATE] = dt.date()
except BaseException as e:
except BaseException:
pass
except BaseException as e:
+3 -24
View File
@@ -96,40 +96,19 @@ class Google(BaseClient):
return 'Google.getRealtime(\'{}\', {}) - urlopen: {} {}'.format(ticker, datacode, e, url)
try:
with open(os.path.join(self.basedir, 'google-{}.html'.format(ticker)), "w", encoding="utf-8") as text_file:
temp = ticker.replace(':', '_') # Windows can't have ':' in file names
with open(os.path.join(self.basedir, 'google-{}.html'.format(temp)), "w", encoding="utf-8") as text_file:
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
if ticker not in self.realtime:
self.realtime[ticker] = {}
self.realtime[ticker] = self.get_ticker()
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_3MONTH] = None
try:
r = '<span[^>]+role="heading"[^>]+>(.*?)</span>'
pattern = re.compile(r)
+20 -11
View File
@@ -72,7 +72,7 @@ class Yahoo(BaseClient):
ticks = {}
for row in reader:
tick = {}
tick = self.get_ticker()
try:
tick[Datacode.OPEN] = float(row['Open'])
tick[Datacode.LOW] = float(row['Low'])
@@ -113,7 +113,7 @@ class Yahoo(BaseClient):
cookies = [cookiejar.Cookie(version=0,
name="B",
value="7pbfivtfkl00m&b=3&s=if",
value="er7g22lg35od5&b=3&s=8p",
port=None, port_specified=False,
domain=".yahoo.com", domain_specified=True, domain_initial_dot=True,
path="/", path_specified=True,
@@ -134,7 +134,7 @@ class Yahoo(BaseClient):
try:
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w", encoding="utf-8") as text_file:
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
except BaseException as e:
except BaseException:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
try:
@@ -145,7 +145,7 @@ class Yahoo(BaseClient):
pattern = re.compile(r)
match = pattern.search(text)
if match:
if match:
self.crumb = match.group(1)
except BaseException as e:
@@ -181,7 +181,7 @@ class Yahoo(BaseClient):
return 'Could not find price for \'{}\''.format(ticker)
if ticker not in self.realtime:
self.realtime[ticker] = {}
self.realtime[ticker] = self.get_ticker()
tick = self.realtime[ticker]
@@ -203,14 +203,19 @@ class Yahoo(BaseClient):
tick[Datacode.DIV_YIELD] = float(raw(summaryDetail, 'dividendYield'))
tick[Datacode.EX_DIV_DATE] = self.save_wrapper(
lambda: dateutil.parser.parse(str(fmt(summaryDetail, 'exDividendDate')), yearfirst=True, dayfirst=False).date())
tick[Datacode.PAYOUT_RATIO] = float(raw(summaryDetail, 'payoutRatio'))
tick[Datacode.LOW_52_WEEK] = float(raw(summaryDetail, 'fiftyTwoWeekLow'))
tick[Datacode.HIGH_52_WEEK] = float(raw(summaryDetail, 'fiftyTwoWeekHigh'))
tick[Datacode.MARKET_CAP] = float(raw(summaryDetail, 'marketCap'))
tick[Datacode.TIMEZONE] = None
tick[Datacode.LAST_PRICE_DATE] = None
tick[Datacode.LAST_PRICE_TIME] = None
tick[Datacode.BID] = float(raw(summaryDetail, 'bid'))
tick[Datacode.ASK] = float(raw(summaryDetail, 'ask'))
tick[Datacode.BIDSIZE] = float(raw(summaryDetail, 'bidSize'))
tick[Datacode.ASKSIZE] = float(raw(summaryDetail, 'askSize'))
tick[Datacode.EXPIRY_DATE] = self.save_wrapper(
lambda: dateutil.parser.parse(str(fmt(summaryDetail, 'expireDate')), yearfirst=True, dayfirst=False).date())
if quoteType:
t = int(price['regularMarketTime'])
@@ -233,19 +238,23 @@ class Yahoo(BaseClient):
if match:
tick[Datacode.CURRENCY] = match.group(1)
# fallback for yield on US mutual funds and ETFs, which is in different field
if not tick[Datacode.DIV_YIELD]:
tick[Datacode.DIV_YIELD] = float(raw(summaryDetail, 'yield'))
name = price['longName'] or price['shortName']
if name:
tick[Datacode.NAME] = html.unescape(str(name))
else:
tick[Datacode.NAME] = tick[Datacode.TICKER]
tick[Datacode.SECTOR] = self.save_wrapper(lambda: str(results['summaryProfile']['sector']))
tick[Datacode.INDUSTRY] = self.save_wrapper(lambda: str(results['summaryProfile']['industry']))
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
tick[Datacode.SECTOR] = self.save_wrapper(lambda: str(results['summaryProfile']['sector']))
tick[Datacode.INDUSTRY] = self.save_wrapper(lambda: str(results['summaryProfile']['industry']))
return self._return_value(self.realtime[ticker], datacode)
def getHistoric(self, ticker: str, datacode: int, date):
+1 -7
View File
@@ -14,19 +14,13 @@ import os
cur_dir = os.getcwd()
addin_id = "com.financials.getinfo"
addin_version = "2.5.1"
addin_version = "3.0.3"
addin_displayname = "Financial Market Extension"
addin_publisher_link = "https://github.com/cmallwitz/Financials-Extension"
addin_publisher_name = "The Publisher"
print("Generating extension files for version", addin_version)
################################################################################
# version.py
version_file = open(cur_dir + '/build/version.py', 'w')
version_file.write(f"version = '{addin_version}'")
version_file.close()
################################################################################
# description.xml
+65 -1
View File
@@ -134,7 +134,7 @@ class Test(unittest.TestCase):
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), 't_UK_ETF NAME {}'.format(s))
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))
@@ -142,6 +142,23 @@ class Test(unittest.TestCase):
s = financials.getRealtime('VERX:LSE:GBP', 'TIMEZONE', 'FT')
self.assertEqual(str, type(s), 'test_UK_ETF TIMEZONE {}'.format(s))
def test_UK_equity(self):
s = financials.getRealtime('VOD:LSE', 'NAME', 'FT')
self.assertEqual(str, type(s), 'test_UK_equity NAME {}'.format(s))
self.assertEqual('Vodafone Group PLC', s, 'test_UK_ETF NAME {}'.format(s))
s = financials.getRealtime('VOD:LSE', 'BID', 'FT')
self.assertEqual(float, type(s), 'test_UK_equity BID {}'.format(s))
s = financials.getRealtime('VOD:LSE', 'ASK', 'FT')
self.assertEqual(float, type(s), 'test_UK_equity ASK {}'.format(s))
s = financials.getRealtime('VOD:LSE', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_UK_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('VOD:LSE', 'TIMEZONE', 'FT')
self.assertEqual(str, type(s), 'test_UK_equity TIMEZONE {}'.format(s))
def test_DE_equity(self):
s = financials.getRealtime('SAPX:GER', 'NAME', 'FT')
self.assertEqual('SAP SE', s, 'test_DE_equity NAME {}'.format(s))
@@ -237,6 +254,17 @@ class Test(unittest.TestCase):
s = financials.getRealtime('ISHAX:GER', 'EX_DIV_DATE', 'FT')
self.assertIsNone(s, 'test_DE_equity EX_DIV_DATE {}'.format(s))
def test_DK_equity(self):
s = financials.getRealtime('NOVO B:CPH', 'name', 'FT')
self.assertEqual('Novo Nordisk A/S', s, 'test_DK_equity NAME {}'.format(s))
s = financials.getRealtime('NOVO B:CPH', 'currency', 'FT')
self.assertEqual('DKK', s, 'test_DK_equity CURRENCY {}'.format(s))
s = financials.getRealtime('NOVO B:CPH', 'industry', 'FT')
self.assertEqual(str, type(s), 'test_DK_equity INDUSTRY {}'.format(s))
self.assertEqual('Pharmaceuticals & Biotechnology', s, 'test_DK_equity INDUSTRY {}'.format(s))
def test_TY_equity(self):
s = financials.getRealtime('6503:TYO', 'OPEN', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity OPEN {}'.format(s))
@@ -270,6 +298,42 @@ class Test(unittest.TestCase):
self.assertEqual(str, type(s), 'test_TY_equity INDUSTRY {}'.format(s))
self.assertEqual('General Industrials', s, 'test_TY_equity INDUSTRY {}'.format(s))
def test_TLV_equity(self):
s = financials.getRealtime('DELT:TLV', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'OPEN', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity OPEN {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'LOW', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity LOW {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'HIGH', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity HIGH {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'LOW_52_WEEK', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'HIGH_52_WEEK', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'MARKET_CAP', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'VOLUME', 'FT')
self.assertEqual(float, type(s), 'test_TlV_equity VOLUME {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'CURRENCY', 'FT')
self.assertEqual('ILa', s, 'test_TlV_equity CURRENCY')
s = financials.getRealtime('DELT:TLV', 'SECTOR', 'FT')
self.assertEqual(str, type(s), 'test_TlV_equity SECTOR {}'.format(s))
self.assertEqual('Consumer Goods', s, 'test_TY_equity SECTOR {}'.format(s))
s = financials.getRealtime('DELT:TLV', 'INDUSTRY', 'FT')
self.assertEqual(str, type(s), 'test_TlV_equity INDUSTRY {}'.format(s))
self.assertEqual('Personal Goods', s, 'test_TY_equity INDUSTRY {}'.format(s))
def test_index(self):
s = financials.getRealtime('INX:IOM', 'ticker', 'FT')
self.assertEqual('INX:IOM', s, 'test_index TICKER {}'.format(s))
+2 -27
View File
@@ -20,7 +20,8 @@ from datacode import Datacode
financials = financials.createInstance(None)
class Test(unittest.TestCase):
# class Test(unittest.TestCase):
class Test:
def test_currency(self):
s = financials.getRealtime('EURGBP', Datacode.LAST_PRICE.value, 'GOOGLE')
@@ -249,32 +250,6 @@ class Test(unittest.TestCase):
s = financials.getRealtime('NYS:IBM', Datacode.LAST_PRICE.value, cell_range)
self.assertEqual(s, 'Cell range not allowed for source', 'test_errors')
def test_support(self):
cell_range = ((1, 2), ('3', '4'), (5.0, 6.0))
s = financials.getRealtime('SUPPORT')
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', 1)
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'int'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=1" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', 1.0)
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'float'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=1.0" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', '1')
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'str'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=1" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', cell_range)
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'tuple'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=((1, 2), ('3', '4'), (5.0, 6.0))" in s, 'test_errors SUPPORT {}'.format(s))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
+57
View File
@@ -0,0 +1,57 @@
# test_google.py
#
# license: GNU LGPL
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
import argparse
import logging
import sys
import unittest
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import financials
financials = financials.createInstance(None)
class Test(unittest.TestCase):
def test_support(self):
cell_range = ((1, 2), ('3', '4'), (5.0, 6.0))
s = financials.getRealtime('SUPPORT')
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("version=3.0.0" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', 1)
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'int'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=1" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', 1.0)
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'float'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=1.0" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', '1')
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'str'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=1" in s, 'test_errors SUPPORT {}'.format(s))
s = financials.getRealtime('SUPPORT', cell_range)
self.assertTrue(s.startswith("ctx="), 'test_errors SUPPORT {}'.format(s))
self.assertTrue("type(datacode)=<class 'tuple'>" in s, 'test_errors SUPPORT {}'.format(s))
self.assertTrue("str(datacode)=((1, 2), ('3', '4'), (5.0, 6.0))" in s, 'test_errors SUPPORT {}'.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)
+57 -11
View File
@@ -123,6 +123,46 @@ class Test(unittest.TestCase):
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_TIME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_mutuals LAST_PRICE_TIME {}'.format(s))
s = financials.getRealtime('VFIAX', Datacode.DIV_YIELD.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_mutuals DIV_YIELD {}'.format(s))
def test_realtime_US_options(self):
# symbol from https://finance.yahoo.com/quote/IBM/options?p=IBM
s = financials.getRealtime('IBM230120C00130000', Datacode.PREV_CLOSE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options PREV_CLOSE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_options NAME {}'.format(s))
self.assertEqual('IBM Jan 2023 130.000 call', s, 'test_realtime_US_options NAME {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.EXPIRY_DATE.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
self.assertEqual("2023-01-20", s, 'test_realtime_US_options EXPIRY_DATE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options LAST_PRICE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.OPEN.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options OPEN {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.VOLUME.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options VOLUME {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.BID.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options BID {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.ASK.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options ASK {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.BIDSIZE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options BIDSIZE {}'.format(s))
s = financials.getRealtime('IBM230120C00130000', Datacode.ASKSIZE.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_options ASKSIZE {}'.format(s))
def test_realtime_UK_ETF(self):
s = financials.getRealtime('VERX.L', Datacode.LAST_PRICE.value, 'YAHOO')
@@ -158,6 +198,17 @@ class Test(unittest.TestCase):
s = financials.getRealtime('C060.DE', Datacode.EXCHANGE.value, 'YAHOO')
self.assertEqual(s, 'GER', 'test_realtime_DE_equity EXCHANGE')
def test_DK_equity(self):
s = financials.getRealtime('NOVO-B.CO', 'name', 'YAHOO')
self.assertEqual('Novo Nordisk A/S', s, 'test_DK_equity NAME {}'.format(s))
s = financials.getRealtime('NOVO-B.CO', 'currency', 'YAHOO')
self.assertEqual('DKK', s, 'test_DK_equity CURRENCY {}'.format(s))
s = financials.getRealtime('NOVO-B.CO', 'industry', 'YAHOO')
self.assertEqual(str, type(s), 'test_DK_equity INDUSTRY {}'.format(s))
self.assertEqual('Biotechnology', s, 'test_DK_equity INDUSTRY {}'.format(s))
def test_realtime_TY_equity(self):
s = financials.getRealtime('6503.T', Datacode.OPEN.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity OPEN {}'.format(s))
@@ -200,7 +251,7 @@ class Test(unittest.TestCase):
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('Data doesn\'t exist - 21', s, 'test_historic_US_equity LAST_PRICE {}'.format(s))
self.assertIsNone(s, 'test_historic_US_equity LAST_PRICE {}'.format(s))
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
self.assertEqual(167.190002, s, 'test_historic_US_equity CLOSE {}'.format(s))
@@ -224,7 +275,7 @@ class Test(unittest.TestCase):
# 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(139.61322, s, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
self.assertEqual(136.249847, s, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
def test_historic_UK_ETF(self):
@@ -237,14 +288,6 @@ class Test(unittest.TestCase):
financials.yahoo.historicdata = {}
# Inception Date 2014-09-30
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2018-04-02', 'YAHOO') # Easter Monday
self.assertEqual(s, 'Not a trading day \'2018-04-02\'', 'test_historic_UK_ETF CLOSE {}'.format(s))
# Inception Date 2014-09-30
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2015-01-01', 'YAHOO')
self.assertEqual(s, 'Not a trading day \'2015-01-01\'', 'test_historic_UK_ETF CLOSE {}'.format(s))
s = financials.getHistoric('VERX.L', Datacode.LAST_PRICE.value, '2017-01-01', 'YAHOO')
self.assertEqual(s, 'Not a trading day \'2017-01-01\'', 'test_historic_UK_ETF LAST_PRICE {}'.format(s))
@@ -287,6 +330,9 @@ class Test(unittest.TestCase):
s = financials.getRealtime('NO_NAME', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertIsNone(s, 'test_realtime_errors LAST_PRICE {}'.format(s))
s = financials.getRealtime('C060.DE', -1, 'YAHOO')
self.assertEqual('Datacode -1 not supported', s, 'test_realtime_errors -1 {}'.format(s))
def test_historic_errors(self):
s = financials.getHistoric('NO_NAME', Datacode.LAST_PRICE.value, '2018-01-08', 'YAHOO')
@@ -299,7 +345,7 @@ class Test(unittest.TestCase):
self.assertEqual(s, 'Datacode 9999 not supported', 'test_historic_errors 9999')
s = financials.getRealtime('IBM', Datacode.ADJ_CLOSE.value, 'YAHOO')
self.assertEqual(s, 'Data doesn\'t exist - 91', 'test_historic_errors ADJ_CLOSE {}'.format(s))
self.assertIsNone(s, 'test_historic_errors ADJ_CLOSE {}'.format(s))
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2030-01-01', 'YAHOO')
self.assertEqual(s, 'Future date \'2030-01-01\'', 'test_historic_errors CLOSE {}'.format(s))
-15
View File
@@ -1,15 +0,0 @@
# version.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.
# This file will not be actually used as it is regenerated with the correct build version
# by generate_metainfo.py in the "compile" step
# This is a bit of a hack but I can't find out how to access the version in description.xml once deployed
version = '0.0.0'