Compare commits

..
15 Commits
18 changed files with 1331 additions and 535 deletions
Binary file not shown.
+85 -6
View File
@@ -1,12 +1,23 @@
# 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.
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.
### System dependencies:
### Feedback requested:
Requires the following Python 3 (used by LibreOffice) packages (on top of standard libs): dateutil, pytz, pyparsing
Please provide about using the extension here: https://github.com/cmallwitz/Financials-Extension/issues/10
The following steps have worked for me on a fresh Debian installation (as root)
### 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
- pip3 install pytz
@@ -14,15 +25,80 @@ 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 on GitHub and download a prebuilt **Financials-Extension.oxt** file and load it into Calc
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")`
Codes 21 and 90 stand for "last price" and "close" (see below), respectively.
Only Yahoo has historic data available.
There is a file **examples.ods** there too with usage examples and possible arguments to functions.
You have to check the respective websites to work out what symbol is the right one for you. If a website doesn't have
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 may differ for other data
points.
### List of supported data points
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)|
| :--- | :--- | :---: | :---: | :---: | :---: |
|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||
### Dealing with missing data:
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 the 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 with the value returned to LibreOffice.
Otherwise, the file extension.log in the same location might have more details about errors or exceptions.
### Build:
You will need the LibreOffice SDK installed.
On my system I installed packages: libreoffice-dev libreoffice-java-common libreoffice-script-provider-python
On my system (Ubuntu) I installed packages: libreoffice-dev libreoffice-java-common libreoffice-script-provider-python
\# depending on your location...
@@ -38,7 +114,10 @@ python3 src/test_google.py
### Tested with:
- 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)
+9 -3
View File
@@ -1,4 +1,9 @@
#!/bin/bash
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
set -o noclobber
export PATH=$PATH:/usr/lib/libreoffice/sdk/bin
export PATH=$PATH:/usr/lib/libreoffice/program
@@ -29,8 +34,9 @@ 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/google2.py "${PWD}"/build/
cp -f "${PWD}"/src/yahoo.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_ft.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_google.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_yahoo.py "${PWD}"/build/
echo "Package into oxt file..."
pushd "${PWD}"/build/
BIN
View File
Binary file not shown.
+104 -45
View File
@@ -11,17 +11,19 @@
import codecs
import gzip
import logging
import os
import pathlib
import random
import select
from http.client import HTTPConnection, HTTPSConnection, HTTPException
from http import cookiejar
import urllib.request
from http import cookiejar
from http.client import HTTPConnection, HTTPSConnection, HTTPException
from datacode import Datacode
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
@@ -40,33 +42,45 @@ 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 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0'
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.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 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0',
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.13; rv:59.0) Gecko/20100101 Firefox/59.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:59.0) Gecko/20100101 Firefox/59.0',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/59.0'
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 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/79.0.3945.130 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4104.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4149.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
]
self.default_headers = {
'User-Agent': random.sample(user_agents, 1)[0],
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8'
'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()
@@ -94,7 +108,9 @@ class BaseClient:
connection = HTTPConnection(host, **kwargs) if scheme == 'http:' else HTTPSConnection(host, **kwargs)
logger.debug('Creating request -----------------------------------------------------')
logger.info('url=%s', url)
logger.info("url='%s'", url)
self.last_url = url
# generate and add cookie headers
request = urllib.request.Request(url)
@@ -123,13 +139,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:
@@ -137,11 +153,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:
@@ -149,21 +165,22 @@ 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')
@@ -180,6 +197,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]
@@ -191,10 +211,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]
@@ -214,14 +240,38 @@ 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:
return data[Datacode.VOLUME]
elif datacode == Datacode.AVG_DAILY_VOL_3MOMTH.value and Datacode.AVG_DAILY_VOL_3MOMTH in data:
return data[Datacode.AVG_DAILY_VOL_3MOMTH]
elif datacode == Datacode.AVG_DAILY_VOL_3MONTH.value and Datacode.AVG_DAILY_VOL_3MONTH in data:
return data[Datacode.AVG_DAILY_VOL_3MONTH]
elif datacode == Datacode.BETA.value and Datacode.BETA in data:
return data[Datacode.BETA]
elif datacode == Datacode.EPS.value and Datacode.EPS in data:
return data[Datacode.EPS]
elif datacode == Datacode.PE_RATIO.value and Datacode.PE_RATIO in data:
return data[Datacode.PE_RATIO]
elif datacode == Datacode.DIV.value and Datacode.DIV in data:
return data[Datacode.DIV]
elif datacode == Datacode.DIV_YIELD.value and Datacode.DIV_YIELD in data:
return data[Datacode.DIV_YIELD]
elif datacode == Datacode.EX_DIV_DATE.value and Datacode.EX_DIV_DATE in data:
if data[Datacode.EX_DIV_DATE]:
return data[Datacode.EX_DIV_DATE].isoformat()
else:
return data[Datacode.EX_DIV_DATE]
elif datacode == Datacode.PAYOUT_RATIO.value and Datacode.PAYOUT_RATIO in data:
return data[Datacode.PAYOUT_RATIO]
elif datacode == Datacode.CLOSE.value and Datacode.CLOSE in data:
return data[Datacode.CLOSE]
@@ -229,20 +279,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)
+12 -3
View File
@@ -29,11 +29,22 @@ class Datacode(Enum):
MARKET_CAP = 27
VOLUME = 35
AVG_DAILY_VOL_3MOMTH = 39
AVG_DAILY_VOL_3MONTH = 39
BETA = 67
EPS = 68
PE_RATIO = 69
DIV = 70
DIV_YIELD = 71
EX_DIV_DATE = 72
PAYOUT_RATIO = 73
CLOSE = 90
ADJ_CLOSE = 91
SECTOR = 98
INDUSTRY = 99
TICKER = 101
EXCHANGE = 102
CURRENCY = 103
@@ -42,8 +53,6 @@ class Datacode(Enum):
TIMESTAMP = 999
# TODO YAHOO fundInceptionDate
@classmethod
def has_value(cls, value):
return any(value == item.value for item in cls)
+72 -14
View File
@@ -8,31 +8,72 @@
# version 3 of the License, or (at your option) any later version.
import datetime
import dateutil.parser
import inspect
import locale
import logging
import os
import sys
import pathlib
import platform
import sys
import time
from functools import wraps
from importlib import util
import dateutil.parser
import unohelper
from com.financials.getinfo import Financials
basedir = os.path.join(str(pathlib.Path.home()), '.financials-extension')
os.makedirs(basedir, exist_ok=True)
logging.basicConfig(
handlers=[logging.FileHandler(filename=os.path.join(basedir, 'extension.log'), encoding='utf-8', mode='a+')],
format="%(asctime)s %(name)s %(levelname)s %(message)s",
level=logging.WARN)
# Add current directory to import path
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
dateutil_missing = util.find_spec("dateutil") is None
pyparsing_missing = util.find_spec("pyparsing") is None
pytz_missing = util.find_spec("pytz") is None
if dateutil_missing or pyparsing_missing or pytz_missing:
msg = ''
msg += ' dateutil' if dateutil_missing else ''
msg += ' pyparsing' if pyparsing_missing else ''
msg += ' pytz' if pytz_missing else ''
raise Exception("THIS EXTENSION NEEDS THE FOLLOWING PYTHON 3 LIBRARIES INSTALLED:" + msg)
from datacode import Datacode
import google2 as google
import yahoo
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",)
def profile(fn):
@wraps(fn)
def with_profiling(*args, **kwargs):
start = time.perf_counter()
r = fn(*args, **kwargs)
elapsed = time.perf_counter() - start
with open(os.path.join(basedir, 'trace.log'), "a+", encoding="utf-8") as text_file:
print(
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
return with_profiling
class FinancialsImpl(unohelper.Base, Financials):
"""Define the main class for the Financials extension """
@@ -40,7 +81,9 @@ 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):
if ticker == 'SUPPORT':
@@ -63,9 +106,12 @@ class FinancialsImpl(unohelper.Base, Financials):
return 'Datacode is empty'
try:
datacode = int(float(str(datacode).strip()))
dc = str(datacode).strip().upper()
if dc in Datacode.__members__:
dc = Datacode[dc].value
datacode = int(float(dc))
except:
return 'Datacode is not a number'
return 'Datacode is invalid'
if not Datacode.has_value(datacode):
return 'Datacode {} not supported'.format(datacode)
@@ -77,6 +123,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)
@@ -90,6 +138,7 @@ class FinancialsImpl(unohelper.Base, Financials):
return x
@profile
def getHistoric(self, ticker, datacode=None, date=None, source=None):
if ticker == 'SUPPORT':
@@ -118,9 +167,12 @@ class FinancialsImpl(unohelper.Base, Financials):
return 'Date is empty'
try:
datacode = int(float(str(datacode).strip()))
dc = str(datacode).strip().upper()
if dc in Datacode.__members__:
dc = Datacode[dc].value
datacode = int(float(dc))
except:
return 'Datacode {} is not a number'.format(datacode)
return 'Datacode {} is invalid'.format(datacode)
if not Datacode.has_value(datacode):
return 'Datacode {} not supported'.format(datacode)
@@ -128,7 +180,7 @@ class FinancialsImpl(unohelper.Base, Financials):
if type(date) == float or type(date) == int:
try:
offset = int(date) # offset for 1899-12-30
offset = int(date) # offset for 1899-12-30
d = dateutil.parser.parse('1899-12-30') + datetime.timedelta(days=offset)
d = d.date().isoformat()
except:
@@ -163,16 +215,21 @@ class FinancialsImpl(unohelper.Base, Financials):
return x
@profile
def support(self, datacode):
s = 'ctx={}\nid(self)={}\npid={}\nuname={}\nsys.executable={}\nsys.version={}\nhome={}'.format(
s = 'ctx={}\nid(self)={}\nversion={}\nfile={}\ncwd={}\nhome={}\nuname={}\npid={}\nsys.executable={}\nsys.version={}\nlocale={}'.format(
self.ctx,
id(self),
os.getpid(),
version,
os.path.realpath(__file__),
os.path.realpath(os.getcwd()),
str(pathlib.Path.home()),
' '.join(platform.uname()),
os.getpid(),
sys.executable,
sys.version.replace("\n", " "),
str(pathlib.Path.home()))
locale.getlocale())
if datacode:
s = '{}\ntype(datacode)={}\nstr(datacode)={}'.format(
@@ -182,6 +239,7 @@ class FinancialsImpl(unohelper.Base, Financials):
return s
def createInstance(ctx):
return FinancialsImpl(ctx)
+336
View File
@@ -0,0 +1,336 @@
# financials_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 html
import logging
import os
import re
import time
import dateutil.parser
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)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
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:
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)
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)
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[^>]*>Beta</span><span[^>]*>([0-9,\.]+)</span>'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
start = match.span(0)[1]
tick[Datacode.BETA] = 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_3MONTH] = self.save_wrapper(
lambda: handle_abbreviations(html.unescape(match.group(1))))
r = r'<th>\s*P/E.*?</th><td>\s*([0-9,\.\-]+)\s*<'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
value = html.unescape(match.group(1))
if value == '--':
tick[Datacode.PE_RATIO] = 0.0
else:
tick[Datacode.PE_RATIO] = self.save_wrapper(
lambda: float(value))
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))))
r = r'<th>\s*EPS.*?</th><td>\s*([0-9,\.\-]+)\s*<'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.EPS] = self.save_wrapper(
lambda: float(html.unescape(match.group(1))))
r = r'<th>\s*Annual div.*?</th><td>\s*([0-9,\.]+)\s*<'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.DIV] = self.save_wrapper(
lambda: float(html.unescape(match.group(1))))
r = r'<th>\s*Annual div yield.*?</th><td>\s*([0-9,\.]+)%\s*<'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
tick[Datacode.DIV_YIELD] = self.save_wrapper(
lambda: float(html.unescape(match.group(1))))
r = r'<th>\s*Div ex-date\s*</th><td><span[^>]*>(.*?)</span><'
match = re.compile(r, flags=re.DOTALL).search(text, start)
if match:
try:
value = html.unescape(match.group(1)).strip()
dt = dateutil.parser.parse(value, tzinfos=whois_timezone_info)
tick[Datacode.EX_DIV_DATE] = dt.date()
except BaseException as e:
pass
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
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)
+59 -58
View File
@@ -1,4 +1,4 @@
# google.py
# financials_google.py
#
# license: GNU LGPL
#
@@ -8,21 +8,18 @@
# version 3 of the License, or (at your option) any later version.
import dateutil
import locale
import logging
import html
import logging
import os
import pathlib
import re
import time
import traceback
import xml.etree.ElementTree as ET
from naivehtmlparser import NaiveHTMLParser
from datacode import Datacode
import dateutil
from baseclient import BaseClient, RedirectException
from datacode import Datacode
from naivehtmlparser import NaiveHTMLParser
from tz import whois_timezone_info
logger = logging.getLogger(__name__)
@@ -33,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)
@@ -53,15 +50,12 @@ class Google(BaseClient):
self.realtime = {}
self.location = None
self.basedir = os.path.join(str(pathlib.Path.home()), '.financials-extension')
os.makedirs(self.basedir, exist_ok=True)
def getRealtime(self, ticker: str, datacode: int):
"""
Retrieve realtime data for ticker from Google Finance and cache it for further lookups
:param ticker: the ticker symbol e.g. VOD.L or LON:VOD
:param ticker: the ticker symbol e.g. LON:VOD
:param datacode: the requested datacode
:return:
"""
@@ -87,7 +81,7 @@ class Google(BaseClient):
except RedirectException as e:
self.location = e.location.replace('&' + q_param, '')
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Google.getRealtime(\'{}\', {}) - location: {}'.format(ticker, datacode, e)
if not self.location:
@@ -97,53 +91,61 @@ class Google(BaseClient):
try:
text = self.urlopen(url)
with open(os.path.join(self.basedir, 'google-{}.html'.format(ticker)), "w") as text_file:
print(text, file=text_file)
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
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:
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] = {}
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)
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()))
# match = pattern.search(text, start)
# if not match:
# return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
# start = match.span(0)[1]
r = '<div [^>]*>(.*?)</div>'
# next div is TICKER
r = '<div [^>]*><div [^>]*>(.*?)</div></div>'
pattern = re.compile(r)
# # first div ignored
# match = pattern.search(text, start)
# if not match:
# return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
# start = match.span(0)[1]
# # second div is NAME
# match = pattern.search(text, start)
# if not match:
# return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
# start = match.span(0)[1]
#
# tick[Datacode.NAME] = self.save_wrapper(
# lambda: html.unescape(un_span(match.group(1)).strip()))
# first div is TICKER
match = pattern.search(text, start)
if not match:
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
@@ -178,28 +180,23 @@ class Google(BaseClient):
header = cards[1]
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
tick[Datacode.LAST_PRICE] = self.save_wrapper(
lambda: locale.atof(
html.unescape(header.find('./span[1]/span[1]/span[1]').text).strip()))
lambda: float(
html.unescape(header.find('./span[1]/span[1]/span[1]').text).replace(',', '').strip()))
tick[Datacode.CURRENCY] = self.save_wrapper(
lambda: html.unescape(header.find('./span[1]/span[1]/span[2]').text).strip())
tick[Datacode.CHANGE] = self.save_wrapper(
lambda: locale.atof(
html.unescape(header.find('./span[2]/span[1]').text).replace('', '-').strip()))
lambda: float(
html.unescape(header.find('./span[2]/span[1]').text).replace('', '-').replace(',', '').strip()))
# percentage is always wrapped in (...) and always positive even if there is a price drop
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(
lambda: float(
html.unescape(header.find('./span[2]/span[2]/span[1]').text).strip()
.replace('(', '').replace(')', '').replace('%', '')))
if tick[Datacode.CHANGE_IN_PERCENT] != 0.0:
if html.unescape(header.find('./span[2]/span[2]/span[1]').text).strip().startswith('('):
tick[Datacode.CHANGE_IN_PERCENT] = -tick[Datacode.CHANGE_IN_PERCENT]
try:
value = html.unescape(header.find('./div[1]/span[1]/span[2]').text).replace('·', '').strip()
logger.debug(value)
@@ -238,6 +235,12 @@ class Google(BaseClient):
table = footer.find('./div[1]/div[1]/div[2]/table[1]')
# for indices: first item on right side is LOW
if html.unescape(table.find('./tr[1]/td[1]').text).strip() == 'Low':
tick[Datacode.LOW] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[1]/td[2]').text).replace(',', '').strip()))
tick[Datacode.PREV_CLOSE] = self.save_wrapper(
lambda: float(
html.unescape(table.find('./tr[2]/td[2]').text).replace(',', '').strip()))
@@ -250,12 +253,10 @@ 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:
logger.warning(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Google.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
return self._return_value(self.realtime[ticker], datacode)
+64 -44
View File
@@ -1,4 +1,4 @@
# yahoo.py
# financials_yahoo.py
#
# license: GNU LGPL
#
@@ -10,25 +10,25 @@
import csv
import datetime
import dateutil.parser
import html
import logging
import os
import pathlib
import pprint
import pytz
import re
import time
import traceback
import urllib.parse
from datacode import Datacode
from baseclient import BaseClient, HttpException
from http import cookiejar
import jsonParser
import dateutil.parser
import pytz
import jsonParser
from baseclient import BaseClient, HttpException
from datacode import Datacode
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
@@ -41,6 +41,15 @@ def raw(m, key, default=0.0):
return default
def fmt(m, key, default=0.0):
try:
return m[key]['fmt']
except:
pass
return default
class Yahoo(BaseClient):
def __init__(self, ctx):
super().__init__()
@@ -50,9 +59,6 @@ class Yahoo(BaseClient):
self.historicdata = {}
self.js = jsonParser.jsonObject
self.basedir = os.path.join(str(pathlib.Path.home()), '.financials-extension')
os.makedirs(self.basedir, exist_ok=True)
def _read_ticker_csv_file(self, ticker):
fn = os.path.join(self.basedir, 'yahoo-{}.csv'.format(ticker))
@@ -60,7 +66,7 @@ class Yahoo(BaseClient):
if not os.path.isfile(fn):
return
with open(fn, newline='') as csvfile:
with open(fn, newline='', encoding="utf-8") as csvfile:
reader = csv.DictReader(csvfile)
ticks = {}
@@ -87,7 +93,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:
"""
@@ -95,10 +101,10 @@ class Yahoo(BaseClient):
# remove white space
ticker = "".join(ticker.split())
# use cached value for up to 5 minutes
# use cached value for up to 60 seconds
if ticker in self.realtime:
tick = self.realtime[ticker]
if time.time() - 5*60 < tick[Datacode.TIMESTAMP]:
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
return self._return_value(tick, datacode)
else:
del self.realtime[ticker]
@@ -107,8 +113,8 @@ class Yahoo(BaseClient):
cookies = [cookiejar.Cookie(version=0,
name="B",
value="9898htldgiar5&b=3&s=gt",
port=None, port_specified=None,
value="7pbfivtfkl00m&b=3&s=if",
port=None, port_specified=False,
domain=".yahoo.com", domain_specified=True, domain_initial_dot=True,
path="/", path_specified=True,
secure=True,
@@ -116,15 +122,21 @@ class Yahoo(BaseClient):
discard=False,
comment=None,
comment_url=None,
rest=None)
rest=dict())
]
try:
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=cookies)
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - urlopen: {}'.format(ticker, datacode, e)
try:
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w", encoding="utf-8") as text_file:
print(f"<!-- '{url}' -->\r\n\r\n{text}", file=text_file)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
try:
text = urllib.parse.unquote(text)
text = text.replace('\\u002F', '/')
@@ -135,34 +147,31 @@ 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(text, file=text_file)
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - crumb: {}'.format(ticker, datacode, e)
try:
start = text.find('"QuoteSummaryStore":{')
if start < 0:
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w") as text_file:
print(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(text, file=text_file)
return None
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getRealtime({}, {}) - parsing: {}'.format(ticker, datacode, e)
with open(os.path.join(self.basedir, 'yahoo-{}.js'.format(ticker)), "w", encoding="utf-8") 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']
@@ -176,6 +185,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'))
@@ -184,12 +195,23 @@ class Yahoo(BaseClient):
tick[Datacode.HIGH] = float(raw(price, 'regularMarketDayHigh'))
tick[Datacode.LAST_PRICE] = float(raw(price, 'regularMarketPrice'))
tick[Datacode.VOLUME] = float(raw(price, 'regularMarketVolume'))
tick[Datacode.AVG_DAILY_VOL_3MOMTH] = float(raw(price, 'averageDailyVolume3Month'))
tick[Datacode.AVG_DAILY_VOL_3MONTH] = float(raw(price, 'averageDailyVolume3Month'))
tick[Datacode.BETA] = float(raw(summaryDetail, 'beta'))
tick[Datacode.EPS] = self.save_wrapper(lambda: float(raw(results['defaultKeyStatistics'], 'trailingEps')))
tick[Datacode.PE_RATIO] = float(raw(summaryDetail, 'trailingPE'))
tick[Datacode.DIV] = float(raw(summaryDetail, 'dividendRate'))
tick[Datacode.DIV_YIELD] = float(raw(summaryDetail, 'dividendYield'))
tick[Datacode.EX_DIV_DATE] = self.save_wrapper(
lambda: dateutil.parser.parse(str(fmt(summaryDetail, 'exDividendDate'))).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
if quoteType:
t = int(price['regularMarketTime'])
tz = pytz.timezone(quoteType['exchangeTimezoneName'])
@@ -210,15 +232,13 @@ 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(results.asList(), stream=text_file)
logger.error(traceback.format_exc())
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):
@@ -226,7 +246,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:
@@ -283,7 +303,7 @@ class Yahoo(BaseClient):
t1 = t1 - 2682000 # pad with extra month
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getHistoric({}, {}, {}) - date: {}'.format(ticker, datacode, date, e)
try:
@@ -294,18 +314,18 @@ class Yahoo(BaseClient):
text = self.urlopen(url)
with open(os.path.join(self.basedir, 'yahoo-{}.csv'.format(ticker)), "w") as csv_file:
with open(os.path.join(self.basedir, 'yahoo-{}.csv'.format(ticker)), "w", encoding="utf-8") as csv_file:
print(text, file=csv_file)
self._read_ticker_csv_file(ticker)
except HttpException:
logger.error(traceback.format_exc())
logger.exception("HttpException ticker=%s datacode=%s date=%s", ticker, datacode, date)
return None
except BaseException as e:
logger.error(traceback.format_exc())
return 'Yahoo.getHistoric({}, {}, {}) - read: {}'.format(ticker, datacode, date, e)
logger.exception("BaseException ticker=%s datacode=%s date=%s", ticker, datacode, date)
return 'Yahoo.getHistoric({}, {}, {}) - urlopen: {}'.format(ticker, datacode, date, e)
try:
if ticker in self.historicdata:
@@ -322,7 +342,7 @@ class Yahoo(BaseClient):
return 'Not a trading day \'{}\''.format(date)
except BaseException as e:
logger.error(traceback.format_exc())
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
return 'Yahoo.getHistoric({}, {}, {}) - process: {}'.format(ticker, datacode, date, e)
return None
+30 -14
View File
@@ -10,15 +10,23 @@
#
import os
cur_dir = os.getcwd()
addin_id = "com.financials.getinfo"
addin_version = "1.0.5"
addin_version = "2.4.0"
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)
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
@@ -35,15 +43,17 @@ desc_xml.write(' <l:LibreOffice-minimal-version value="5.0" d:name="LibreOffi
desc_xml.write('</dependencies> \n')
desc_xml.write('\n')
desc_xml.write('<identifier value="' + addin_id + '" /> \n')
desc_xml.write('<version value="' + addin_version + '" />\n')
desc_xml.write('<version value="' + addin_version + '" />\n')
desc_xml.write('<display-name><name lang="en">' + addin_displayname + '</name></display-name>\n')
desc_xml.write('<publisher><name xlink:href="' + addin_publisher_link + '" lang="en">' + addin_publisher_name + '</name></publisher>\n')
desc_xml.write(
'<publisher><name xlink:href="' + addin_publisher_link + '" lang="en">' + addin_publisher_name + '</name></publisher>\n')
desc_xml.write('<extension-description><src xlink:href="description-en-US.txt" lang="en" /></extension-description>')
desc_xml.write('\n \n')
desc_xml.write('</description> \n')
desc_xml.close()
################################################################################
# manifest.xml
# List of files in package and their types.
@@ -53,9 +63,10 @@ def add_manifest_entry(xml_file, file_type, file_name):
xml_file.write('<manifest:file-entry manifest:media-type="application/vnd.sun.star.' + file_type + '" \n')
xml_file.write(' manifest:full-path="' + file_name + '"/> \n')
manifest_xml = open(cur_dir + '/build/META-INF/manifest.xml', 'w')
manifest_xml.write('<manifest:manifest>\n');
manifest_xml.write('<manifest:manifest>\n')
add_manifest_entry(manifest_xml, 'uno-typelibrary;type=RDB', 'XFinancials.rdb')
add_manifest_entry(manifest_xml, 'configuration-data', 'Financials.xcu')
add_manifest_entry(manifest_xml, 'uno-component;type=Python', 'financials.py')
@@ -63,6 +74,7 @@ manifest_xml.write('</manifest:manifest> \n')
manifest_xml.close()
################################################################################
# Financials.xcu
@@ -71,12 +83,13 @@ def define_function(xml_file, function_name, description, parameters):
xml_file.write(' <prop oor:name="DisplayName"><value xml:lang="en">' + function_name + '</value></prop>\n')
xml_file.write(' <prop oor:name="Description"><value xml:lang="en">' + description + '</value></prop>\n')
xml_file.write(' <prop oor:name="Category"><value>Add-In</value></prop>\n')
xml_file.write(' <prop oor:name="CompatibilityName"><value xml:lang="en">AutoAddIn.Financials.' + function_name + '</value></prop>\n')
xml_file.write(
' <prop oor:name="CompatibilityName"><value xml:lang="en">AutoAddIn.Financials.' + function_name + '</value></prop>\n')
xml_file.write(' <node oor:name="Parameters">\n')
for p, desc in parameters:
# Optional parameters will have a displayname enclosed in square brackets.
p_name = p.strip("[]")
# Optional parameters will have a display name enclosed in square brackets.
p_name = p.strip("[]")
xml_file.write(' <node oor:name="' + p_name + '" oor:op="replace">\n')
xml_file.write(' <prop oor:name="DisplayName"><value xml:lang="en">' + p_name + '</value></prop>\n')
xml_file.write(' <prop oor:name="Description"><value xml:lang="en">' + desc + '</value></prop>\n')
@@ -85,6 +98,7 @@ def define_function(xml_file, function_name, description, parameters):
xml_file.write(' </node>\n')
xml_file.write(' </node>\n')
# instance_id references the named UNO component instantiated by Python code (that is my understanding at least).
implementation_name = "com.financials.getinfo.python.FinancialsImpl"
@@ -94,17 +108,19 @@ excel_addin_name = "Financials.xlam"
financials_xml = open(cur_dir + '/build/Financials.xcu', 'w')
financials_xml.write('<?xml version="1.0" encoding="UTF-8"?>\n')
financials_xml.write('<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="CalcAddIns" oor:package="org.openoffice.Office">\n')
financials_xml.write(
'<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="CalcAddIns" oor:package="org.openoffice.Office">\n')
financials_xml.write('<node oor:name="AddInInfo">\n')
financials_xml.write('<node oor:name="' + implementation_name + '" oor:op="replace">\n')
financials_xml.write('<node oor:name="AddInFunctions">\n')
define_function(financials_xml, \
'getRealtime', 'Fetches Realtime Financial Data.', \
define_function(financials_xml,
'getRealtime', 'Fetches Realtime Financial Data.',
[('ticker', 'The ticker symbol.'), ('datacode', 'The data code.'), ('source', 'The source.')])
define_function(financials_xml, \
'getHistoric', 'Fetches Historic Financial Data.', \
[('ticker', 'The ticker symbol.'), ('datacode', 'The data code.'), ('date', 'The date.'), ('source', 'The source.')])
define_function(financials_xml,
'getHistoric', 'Fetches Historic Financial Data.',
[('ticker', 'The ticker symbol.'), ('datacode', 'The data code.'), ('date', 'The date.'),
('source', 'The source.')])
financials_xml.write('</node>\n')
financials_xml.write('</node>\n')
-214
View File
@@ -1,214 +0,0 @@
# 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 datetime
import locale
import logging
import html
import re
import time
import traceback
import urllib.parse
from datacode import Datacode
from baseclient import BaseClient
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
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)
class Google(BaseClient):
def __init__(self, ctx):
super().__init__()
self.realtime = {}
def getRealtime(self, ticker: str, datacode: int):
"""
Retrieve realtime data for ticker from Google Finance and cache it for further lookups
:param ticker: the ticker symbol e.g. VOD.L or LON:VOD
:param datacode: the requested datacode
:return:
"""
# remove white space
ticker = "".join(ticker.split())
# use cached value for up to 5 minutes
if ticker in self.realtime:
tick = self.realtime[ticker]
if time.time() - 5*60 < tick[Datacode.TIMESTAMP]:
return self._return_value(tick, datacode)
else:
del self.realtime[ticker]
url = 'https://finance.google.com/finance?{}'.format(urllib.parse.urlencode({'q': ticker}))
try:
text = self.urlopen(url)
except BaseException as e:
logger.error(traceback.format_exc())
return 'Google.getRealtime(\'{}\', {}) - read: {}'.format(ticker, datacode, e)
try:
r = '<meta\s*itemprop="([^"]+)"\s*content="([^"]+)"\s*/>'
pattern = re.compile(r)
result = pattern.findall(text)
if len(result) == 0:
return None
if ticker not in self.realtime:
self.realtime[ticker] = {}
tick = self.realtime[ticker]
for key, value in result:
if key == 'exchangeTimezone':
tick[Datacode.TIMEZONE] = self.save_wrapper(lambda: str(value))
elif key == 'priceChange':
tick[Datacode.CHANGE] = self.save_wrapper(lambda: float(value))
elif key == 'quoteTime':
try:
dt = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
tick[Datacode.LAST_PRICE_DATE] = dt.date()
tick[Datacode.LAST_PRICE_TIME] = dt.time()
except:
pass
elif key == 'priceChangePercent':
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(lambda: (float(value)))
elif key == 'price':
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
tick[Datacode.LAST_PRICE] = self.save_wrapper(lambda: locale.atof(str(value)))
elif key == 'priceCurrency':
tick[Datacode.CURRENCY] = self.save_wrapper(lambda: str(value))
elif key == 'exchange':
tick[Datacode.EXCHANGE] = self.save_wrapper(lambda: str(value))
elif key == 'name':
tick[Datacode.NAME] = self.save_wrapper(lambda: html.unescape(str(value)))
elif key == 'tickerSymbol':
tick[Datacode.TICKER] = self.save_wrapper(lambda: str(value))
else:
logger.info('ignored key=%s value=%s', key, value)
start = 0
r = '<td[^>]+data-snapfield="range">[^<]+</td>\s*<td class="val">\s*([^<]+)\s*</td>'
pattern = re.compile(r, flags=re.DOTALL)
match = pattern.search(text, start)
if match:
lowhigh = self.save_wrapper(
lambda: list(map(
lambda s: float(s),
html.unescape(match.group(1))
.replace('-', '').replace(',', '').strip().split())))
if lowhigh and len(lowhigh) == 2:
tick[Datacode.LOW] = lowhigh[0]
tick[Datacode.HIGH] = lowhigh[1]
start = match.span(0)[1]
r = '<td[^>]+data-snapfield="range_52week">[^<]+</td>\s*<td class="val">\s*([^<]+)\s*</td>'
pattern = re.compile(r, flags=re.DOTALL)
match = pattern.search(text, start)
if match:
lowhigh = self.save_wrapper(
lambda: list(map(
lambda s: float(s),
html.unescape(match.group(1))
.replace('-', '').replace(',', '').strip().split())))
if lowhigh and len(lowhigh) == 2:
tick[Datacode.LOW_52_WEEK] = lowhigh[0]
tick[Datacode.HIGH_52_WEEK] = lowhigh[1]
start = match.span(0)[1]
r = '<td[^>]+data-snapfield="open">[^<]+</td>\s*<td class="val">\s*([^<]+)\s*</td>'
pattern = re.compile(r, flags=re.DOTALL)
match = pattern.search(text, start)
if match:
tick[Datacode.OPEN] = self.save_wrapper(
lambda: float(html.unescape(match.group(1)).replace(',', '').strip()))
start = match.span(0)[1]
r = '<td[^>]+data-snapfield="vol_and_avg">[^<]+</td>\s*<td class="val">\s*([^<]+)\s*</td>'
pattern = re.compile(r, flags=re.DOTALL)
match = pattern.search(text, start)
if match:
volavg = self.save_wrapper(
lambda: list(map(
lambda s: handle_abbreviations(s),
html.unescape(match.group(1)).replace('/', ' ').strip().split())))
if volavg:
if len(volavg) > 0:
tick[Datacode.VOLUME] = volavg[0]
start = match.span(0)[1]
r = '<td[^>]+data-snapfield="market_cap">[^<]+</td>\s*<td class="val">\s*([^<]+)'
pattern = re.compile(r, flags=re.DOTALL)
match = pattern.search(text, start)
if match:
mcap = self.save_wrapper(
lambda: handle_abbreviations(html.unescape(match.group(1)).replace('-', ' ').strip()))
if mcap:
tick[Datacode.MARKET_CAP] = mcap
# start = match.span(0)[1]
tick[Datacode.TIMESTAMP] = time.time()
if tick[Datacode.EXCHANGE] == 'CURRENCY' and Datacode.CURRENCY not in tick:
tick[Datacode.CURRENCY] = ''
logger.info(tick)
except BaseException as e:
logger.warning(traceback.format_exc())
return 'Google.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
return self._return_value(self.realtime[ticker], datacode)
def getHistoric(self, ticker, datacode, date):
return 'Google.getHistoric: Historic Data not implemented.'
def createInstance(ctx):
return Google(ctx)
+47 -50
View File
@@ -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)
+320
View File
@@ -0,0 +1,320 @@
# 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
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import financials
import testutils
financials = financials.createInstance(None)
class Test(unittest.TestCase):
def test_currency(self):
s = financials.getRealtime('EURGBP', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_currency LAST_PRICE')
s = financials.getRealtime('EURGBP', 'CURRENCY', 'FT')
self.assertEqual(str, type(s), 'test_currency CURRENCY')
def test_US_equity(self):
s = financials.getRealtime('INTC:NSQ', 'CHANGE', 'FT')
self.assertEqual(float, type(s), 'test_US_equity CHANGE {}'.format(s))
s = financials.getRealtime('INTC:NSQ', 'CHANGE_IN_PERCENT', 'FT')
self.assertEqual(float, type(s), 'test_US_equity CHANGE_IN_PERCENT {}'.format(s))
s = financials.getRealtime('INTC:NSQ', 'AVG_DAILY_VOL_3MONTH', 'FT')
self.assertEqual(float, type(s), 'test_US_equity AVG_DAILY_VOL_3MONTH {}'.format(s))
s = financials.getRealtime('INTC:NSQ', 'MARKET_CAP', 'FT')
self.assertEqual(float, type(s), 'test_US_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'TICKER', 'FT')
self.assertEqual(str, type(s), 'test_US_equity TICKER {}'.format(s))
self.assertEqual('IBM:NYQ', s, 'test_US_equity TICKER {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'PREV_CLOSE', 'FT')
self.assertEqual(float, type(s), 'test_US_equity PREV_CLOSE {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'OPEN', 'FT')
self.assertEqual(float, type(s), 'test_US_equity OPEN {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_US_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'LOW', 'FT')
self.assertEqual(float, type(s), 'test_US_equity LOW {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'HIGH', 'FT')
self.assertEqual(float, type(s), 'test_US_equity HIGH {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'VOLUME', 'FT')
self.assertEqual(float, type(s), 'test_US_equity VOLUME {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'BETA', 'FT')
self.assertTrue(testutils.is_positive_float(s), 'test_US_equity BETA {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'EPS', 'FT')
self.assertEqual(float, type(s), 'test_US_equity EPS {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'PE_RATIO', 'FT')
self.assertEqual(float, type(s), 'test_US_equity PE_RATIO {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'DIV', 'FT')
self.assertEqual(float, type(s), 'test_US_equity DIV {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'DIV_YIELD', 'FT')
self.assertEqual(float, type(s), 'test_US_equity DIV_YIELD {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'EX_DIV_DATE', 'FT')
self.assertEqual(str, type(s), 'test_US_equity EX_DIV_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_US_equity EX_DIV_DATE {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'PAYOUT_RATIO', 'FT')
self.assertIsNone(s, 'test_US_equity PAYOUT_RATIO {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'NAME', 'FT')
self.assertEqual(str, type(s), 'test_US_equity NAME {}'.format(s))
self.assertEqual(s, 'International Business Machines Corp', 'test_US_equity NAME {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'SECTOR', 'FT')
self.assertEqual(str, type(s), 'test_US_equity SECTOR {}'.format(s))
self.assertEqual(s, 'Technology', 'test_US_equity SECTOR {}'.format(s))
s = financials.getRealtime('IBM:NYQ', 'INDUSTRY', 'FT')
self.assertEqual(str, type(s), 'test_US_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Software & Computer Services', 'test_US_equity INDUSTRY {}'.format(s))
# may fail (s is None) on weekends when date/time displayed doesn't have time component with TZ
s = financials.getRealtime('IBM:NYQ', 'TIMEZONE', 'FT')
self.assertEqual(str, type(s), 'test_US_equity TIMEZONE {}'.format(s))
def test_US_mutuals(self):
s = financials.getRealtime('VGSLX', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_US_mutuals LAST_PRICE {}'.format(s))
s = financials.getRealtime('VGSLX', 'NAME', 'FT')
self.assertEqual("Vanguard Real Estate Index Fund Admiral Shares", s, 'test_US_mutuals NAME {}'.format(s))
s = financials.getRealtime('VGSLX', 'CURRENCY', 'FT')
self.assertEqual('USD', s, 'test_US_mutuals CURRENCY {}'.format(s))
s = financials.getRealtime('VGSLX', 'CHANGE', 'FT')
self.assertEqual(float, type(s), 'test_US_mutuals CHANGE {}'.format(s))
s = financials.getRealtime('VGSLX', 'CHANGE_IN_PERCENT', 'FT')
self.assertEqual(float, type(s), 'test_US_mutuals CHANGE_IN_PERCENT {}'.format(s))
s = financials.getRealtime('VFIAX', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_US_mutuals LAST_PRICE {}'.format(s))
s = financials.getRealtime('VFIAX', 'LAST_PRICE_DATE', 'FT')
self.assertEqual(str, type(s), 'test_US_mutuals LAST_PRICE_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_US_mutuals LAST_PRICE_DATE {}'.format(s))
s = financials.getRealtime('VFIAX', 'LAST_PRICE_TIME', 'FT')
self.assertEqual(str, type(s), 'test_US_mutuals LAST_PRICE_TIME {}'.format(s))
def test_UK_ETF(self):
s = financials.getRealtime('CSP1:LSE:GBX', 'NAME', 'FT')
self.assertEqual(str, type(s), 'test_UK_ETF NAME {}'.format(s))
self.assertEqual('iShares Core S&P 500 UCITS ETF USD (Acc)', s, 'test_UK_ETF NAME {}'.format(s))
s = financials.getRealtime('C060:GER:EUR', 'NAME', 'FT')
self.assertEqual(str, type(s), 't_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))
s = financials.getRealtime('VERX:LSE:GBP', 'TIMEZONE', 'FT')
self.assertEqual(str, type(s), 'test_UK_ETF 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))
s = financials.getRealtime('SAPX:GER', 'TICKER', 'FT')
self.assertEqual('SAPX:GER', s, 'test_DE_equity TICKER {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'CURRENCY', 'FT')
self.assertEqual('EUR', s, 'test_DE_equity CURRENCY {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'LAST_PRICE', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'CHANGE', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity CHANGE {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'CHANGE_IN_PERCENT', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity CHANGE_IN_PERCENT {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'VOLUME', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity VOLUME {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'OPEN', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity OPEN {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'HIGH', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity HIGH {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'LOW', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity LOW {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'PREV_CLOSE', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity PREV_CLOSE {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'MARKET_CAP', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'SECTOR', 'FT')
self.assertEqual(str, type(s), 'test_DE_equity SECTOR {}'.format(s))
self.assertEqual(s, 'Technology', 'test_DE_equity SECTOR {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'INDUSTRY', 'FT')
self.assertEqual(str, type(s), 'test_DE_equity INDUSTRY {}'.format(s))
self.assertEqual('Software & Computer Services', s, 'test_DE_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'LAST_PRICE_DATE', 'FT')
self.assertEqual(str, type(s), 'test_DE_equity LAST_PRICE_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_DE_equity LAST_PRICE_DATE {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'TIMEZONE', 'FT')
self.assertEqual(str, type(s), 'test_DE_equity TIMEZONE {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'BETA', 'FT')
self.assertTrue(testutils.is_positive_float(s), 'test_DE_equity BETA {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'EPS', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity EPS {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'PE_RATIO', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity PE_RATIO {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'DIV', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity DIV {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'DIV_YIELD', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity DIV_YIELD {}'.format(s))
s = financials.getRealtime('SAPX:GER', 'EX_DIV_DATE', 'FT')
self.assertEqual(str, type(s), 'test_DE_equity EX_DIV_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_DE_equity EX_DIV_DATE {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'NAME', 'FT')
self.assertEqual('Intershop Communications AG', s, 'test_DE_equity NAME {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'BETA', 'FT')
self.assertTrue(testutils.is_positive_float(s), 'test_DE_equity BETA {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'MARKET_CAP', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'EPS', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity EPS {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'PE_RATIO', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity PE_RATIO {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'DIV', 'FT')
self.assertIsNone(s, 'test_DE_equity DIV {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'DIV_YIELD', 'FT')
self.assertIsNone(s, 'test_DE_equity DIV_YIELD {}'.format(s))
s = financials.getRealtime('ISHAX:GER', 'EX_DIV_DATE', 'FT')
self.assertIsNone(s, 'test_DE_equity EX_DIV_DATE {}'.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))
s = financials.getRealtime('6503:TYO', 'LOW', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity LOW {}'.format(s))
s = financials.getRealtime('6503:TYO', 'HIGH', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity HIGH {}'.format(s))
s = financials.getRealtime('6503:TYO', 'LOW_52_WEEK', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('6503:TYO', 'HIGH_52_WEEK', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('6503:TYO', 'MARKET_CAP', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('6503:TYO', 'VOLUME', 'FT')
self.assertEqual(float, type(s), 'test_TY_equity VOLUME {}'.format(s))
s = financials.getRealtime('6503:TYO', 'CURRENCY', 'FT')
self.assertEqual('JPY', s, 'test_TY_equity CURRENCY')
s = financials.getRealtime('6503:TYO', 'SECTOR', 'FT')
self.assertEqual(str, type(s), 'test_TY_equity SECTOR {}'.format(s))
self.assertEqual('Industrials', s, 'test_TY_equity SECTOR {}'.format(s))
s = financials.getRealtime('6503:TYO', 'INDUSTRY', 'FT')
self.assertEqual(str, type(s), 'test_TY_equity INDUSTRY {}'.format(s))
self.assertEqual('General Industrials', 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))
s = financials.getRealtime('DAXX:GER', 'ticker', 'FT')
self.assertEqual('DAXX:GER', s, 'test_index TICKER {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'last_price', 'FT')
self.assertEqual(float, type(s), 'test_index LAST_PRICE {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'volume', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity VOLUME {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'low_52_week', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'high_52_week', 'FT')
self.assertEqual(float, type(s), 'test_DE_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'open', 'FT')
self.assertIsNone(s, 'test_DE_equity OPEN {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'high', 'FT')
self.assertIsNone(s, 'test_DE_equity HIGH {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'low', 'FT')
self.assertIsNone(s, 'test_DE_equity LOW {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'prev_close', 'FT')
self.assertIsNone(s, 'test_DE_equity PREV_CLOSE {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'market_cap', 'FT')
self.assertIsNone(s, 'test_DE_equity MARKET_CAP {}'.format(s))
def test_errors(self):
s = financials.getRealtime('NO_NAME', 'LAST_PRICE', 'FT')
self.assertIsNone(s, 'test_errors LAST_PRICE {}'.format(s))
s = financials.getRealtime('DAXX:GER', 'Foo', 'FT')
self.assertEqual('Datacode is invalid', s, 'test_errors')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
unit_argv = [sys.argv[0]] + args.unittest_args
unittest.main(argv=unit_argv)
+52 -47
View File
@@ -12,27 +12,23 @@ import logging
import sys
import unittest
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import financials
from datacode import Datacode
financials = financials.createInstance(None)
logging.basicConfig(level=logging.ERROR)
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')
self.assertEqual(type(s), float, 'test_UK_equity LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_UK_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('LON:VOD', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual(s, 'VOD', 'test_UK_equity TICKER')
@@ -44,21 +40,21 @@ class Test(unittest.TestCase):
self.assertEqual(s, 'LON', 'test_UK_equity EXCHANGE')
s = financials.getRealtime('LON:VOD', Datacode.PREV_CLOSE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_UK_equity PREV_CLOSE {}'.format(s))
self.assertEqual(float, type(s), 'test_UK_equity PREV_CLOSE {}'.format(s))
# MARKET_CAP missing for UK stock but available for German stock - weekend issue (FX) ?
s = financials.getRealtime('LON:VOD', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_UK_equity MARKET_CAP {}'.format(s))
self.assertEqual(float, type(s), 'test_UK_equity MARKET_CAP {}'.format(s))
def test_UK_ETF(self):
s = financials.getRealtime('LON:CSP1', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_UK_ETF LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_UK_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('LON:CSP1', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'GBX', 'test_UK_ETF CURRENCY')
s = financials.getRealtime('LON:FTAL', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_UK_ETF LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_UK_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('LON:FTAL', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'GBP', 'test_UK_ETF CURRENCY')
@@ -68,10 +64,10 @@ class Test(unittest.TestCase):
def test_DE_equity(self):
s = financials.getRealtime('FRA:SAP', 7, 'GOOGLE')
self.assertEqual(type(s), float, 'test_DE_equity 7')
self.assertEqual(float, type(s), 'test_DE_equity 7')
s = financials.getRealtime('FRA:SAP', '7', 'GOOGLE')
self.assertEqual(type(s), float, 'test_DE_equity \'7\'')
self.assertEqual(float, type(s), 'test_DE_equity \'7\'')
s = financials.getRealtime('FRA:SAP', 8, 'GOOGLE')
self.assertEqual(type(s), str, 'test_DE_equity 8')
@@ -86,10 +82,10 @@ class Test(unittest.TestCase):
self.assertEqual(type(s), str, 'test_DE_equity 10')
s = financials.getRealtime('FRA:SAP', '11', 'GOOGLE')
self.assertEqual(type(s), float, 'test_DE_equity \'11\'')
self.assertEqual(float, type(s), 'test_DE_equity \'11\'')
s = financials.getRealtime('FRA:SAP', '21', 'GOOGLE')
self.assertEqual(type(s), float, 'test_DE_equity \'21\'')
self.assertEqual(float, type(s), 'test_DE_equity \'21\'')
s = financials.getRealtime('FRA:SAP', Datacode.TIMEZONE.value, 'GOOGLE')
# self.assertEqual(s, 'Europe/Berlin', 'test_DE_equity TIMEZONE')
@@ -97,51 +93,51 @@ 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')
self.assertEqual(type(s), float, 'test_TY_equity OPEN {}'.format(s))
self.assertEqual(float, type(s), 'test_TY_equity OPEN {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.LOW.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_TY_equity LOW {}'.format(s))
self.assertEqual(float, type(s), 'test_TY_equity LOW {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.HIGH.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_TY_equity HIGH {}'.format(s))
self.assertEqual(float, type(s), 'test_TY_equity HIGH {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.LOW_52_WEEK.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_TY_equity LOW_52_WEEK {}'.format(s))
self.assertEqual(float, type(s), 'test_TY_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.HIGH_52_WEEK.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_TY_equity HIGH_52_WEEK {}'.format(s))
self.assertEqual(float, type(s), 'test_TY_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_TY_equity MARKET_CAP {}'.format(s))
self.assertEqual(float, type(s), 'test_TY_equity MARKET_CAP {}'.format(s))
# s = financials.getRealtime('TYO:6503', Datacode.VOLUME.value, 'GOOGLE')
# self.assertEqual(type(s), float, 'test_TY_equity VOLUME {}'.format(s))
# self.assertEqual(float, type(s), 'test_TY_equity VOLUME {}'.format(s))
s = financials.getRealtime('TYO:6503', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'JPY', 'test_TY_equity CURRENCY')
def test_US_equity(self):
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual(s, 'AAPL', 'test_US_equity TICKER')
@@ -153,7 +149,7 @@ class Test(unittest.TestCase):
self.assertEqual(s, 'USD', 'test_US_equity CURRENCY')
s = financials.getRealtime('NYSE:IBM', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.TICKER.value, 'GOOGLE')
self.assertEqual(s, 'IBM', 'test_US_equity TICKER')
@@ -169,50 +165,59 @@ class Test(unittest.TestCase):
self.assertEqual(s, 'IBM Common Stock', 'test_US_equity NAME')
s = financials.getRealtime('NYSE:IBM', Datacode.LOW.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity LOW {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity LOW {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.HIGH.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity HIGH {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity HIGH {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.LOW_52_WEEK.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity LOW_52_WEEK {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.HIGH_52_WEEK.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity HIGH_52_WEEK {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.MARKET_CAP.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_equity MARKET_CAP {}'.format(s))
self.assertEqual(float, type(s), 'test_US_equity MARKET_CAP {}'.format(s))
# s = financials.getRealtime('NYSE:IBM', Datacode.VOLUME.value, 'GOOGLE')
# self.assertEqual(type(s), float, 'test_US_equity VOLUME {}'.format(s))
# self.assertEqual(float, type(s), 'test_US_equity VOLUME {}'.format(s))
s = financials.getRealtime('NYSE:IBM', Datacode.TIMESTAMP.value, 'GOOGLE')
self.assertEqual(s, 'Data doesn\'t exist - 999', 'test_US_equity TIMESTAMP')
s = financials.getRealtime('NYSE:IBM', Datacode.TIMEZONE.value, 'GOOGLE')
# self.assertEqual(s, 'America/New_York', 'test_US_equity TIMEZONE')
self.assertEqual(s, 'GMT-4', 'test_US_equity TIMEZONE')
# self.assertEqual(s, 'GMT-5', 'test_US_equity TIMEZONE')
# self.assertEqual('America/New_York', s, 'test_US_equity TIMEZONE')
# self.assertEqual('GMT-4', s, 'test_US_equity TIMEZONE')
self.assertEqual('GMT-5', s, 'test_US_equity TIMEZONE')
def test_US_mutuals(self):
s = financials.getRealtime('MUTF:VFIAX', Datacode.LAST_PRICE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_US_mutuals LAST_PRICE - {}'.format(s))
self.assertEqual(float, type(s), 'test_US_mutuals LAST_PRICE - {}'.format(s))
s = financials.getRealtime('MUTF:VFIAX', Datacode.CURRENCY.value, 'GOOGLE')
self.assertEqual(s, 'USD', 'test_US_mutuals CURRENCY')
s = financials.getRealtime('MUTF:VFIAX', Datacode.TIMEZONE.value, 'GOOGLE')
self.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')
self.assertEqual(type(s), float, 'test_index LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_index LAST_PRICE {}'.format(s))
s = financials.getRealtime('INDEXDB:DAX', Datacode.CHANGE_IN_PERCENT.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_index CHANGE_IN_PERCENT')
self.assertEqual(float, type(s), 'test_index CHANGE_IN_PERCENT')
s = financials.getRealtime('INDEXDB:DAX', Datacode.CHANGE.value, 'GOOGLE')
self.assertEqual(type(s), float, 'test_index CHANGE')
self.assertEqual(float, type(s), 'test_index CHANGE')
s = financials.getRealtime('INDEXDB:DAX', Datacode.OPEN.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index OPEN')
s = financials.getRealtime('INDEXDB:DAX', Datacode.LOW.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index LOW')
s = financials.getRealtime('INDEXDB:DAX', Datacode.HIGH.value, 'GOOGLE')
self.assertEqual(float, type(s), 'test_index HIGH')
def test_errors(self):
s = financials.getRealtime(None, Datacode.LAST_PRICE.value, 'GOOGLE')
@@ -222,10 +227,10 @@ 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')
self.assertEqual(s, 'Datacode is invalid', 'test_errors')
# Historic data not supported on GOOGLE
+116 -37
View File
@@ -14,123 +14,201 @@ import pathlib
import sys
import unittest
logging.basicConfig(level=logging.ERROR, format="%(asctime)s %(name)s %(levelname)s %(message)s")
import financials
from datacode import Datacode
import testutils
financials = financials.createInstance(None)
logging.basicConfig(level=logging.ERROR)
class Test(unittest.TestCase):
def test_currency(self):
s = financials.getRealtime('EURGBP=X', Datacode.CURRENCY.value, 'YAHOO')
self.assertEqual(type(s), str, 'test_currency CURRENCY')
self.assertEqual(str, type(s), 'test_currency CURRENCY')
s = financials.getRealtime('EURGBP=X', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_currency LAST_PRICE')
self.assertEqual(float, type(s), 'test_currency LAST_PRICE')
def test_realtime_US_equity(self):
s = financials.getRealtime('^GSPC', Datacode.NAME.value, 'YAHOO')
self.assertEqual(type(s), str, 'test_realtime_US_equity NAME {}'.format(s))
self.assertEqual(str, type(s), 'test_realtime_US_equity NAME {}'.format(s))
self.assertIn('500', s, 'test_realtime_US_equity NAME {}'.format(s))
s = financials.getRealtime('IBM', Datacode.PREV_CLOSE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity PREV_CLOSE {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity PREV_CLOSE {}'.format(s))
s = financials.getRealtime('IBM', Datacode.OPEN.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity OPEN {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity OPEN {}'.format(s))
s = financials.getRealtime('IBM', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity LAST_PRICE {}'.format(s))
s = financials.getRealtime('IBM', Datacode.LOW.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity LOW {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity LOW {}'.format(s))
s = financials.getRealtime('IBM', Datacode.HIGH.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity HIGH {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity HIGH {}'.format(s))
s = financials.getRealtime('IBM', Datacode.HIGH_52_WEEK.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity HIGH_52_WEEK {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('IBM', Datacode.LOW_52_WEEK.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity LOW_52_WEEK {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('IBM', Datacode.MARKET_CAP.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity MARKET_CAP {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('IBM', Datacode.VOLUME.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity VOLUME {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_equity VOLUME {}'.format(s))
s = financials.getRealtime('IBM', Datacode.AVG_DAILY_VOL_3MOMTH.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_equity AVG_DAILY_VOL_3MOMTH {}'.format(s))
s = financials.getRealtime('IBM', Datacode.AVG_DAILY_VOL_3MONTH.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_realtime_US_equity AVG_DAILY_VOL_3MONTH {}'.format(s))
s = financials.getRealtime('IBM', Datacode.NAME.value, 'YAHOO')
self.assertEqual(type(s), str, 'test_realtime_US_equity NAME {}'.format(s))
self.assertEqual(str, type(s), 'test_realtime_US_equity NAME {}'.format(s))
self.assertEqual(s, 'International Business Machines Corporation',
'test_realtime_US_equity NAME {}'.format(s))
s = financials.getRealtime('IBM', Datacode.SECTOR.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_equity SECTOR {}'.format(s))
self.assertEqual(s, 'Technology', 'test_realtime_US_equity SECTOR {}'.format(s))
s = financials.getRealtime('IBM', Datacode.INDUSTRY.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Information Technology Services', 'test_realtime_US_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('IBM', Datacode.TIMEZONE.value, 'YAHOO')
self.assertEqual(s, 'America/New_York', 'test_realtime_US_equity TIMEZONE {}'.format(s))
s = financials.getRealtime('IBM', Datacode.BETA.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity BETA {}'.format(s))
s = financials.getRealtime('IBM', Datacode.EPS.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity EPS {}'.format(s))
s = financials.getRealtime('IBM', Datacode.PE_RATIO.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity PE_RATIO {}'.format(s))
s = financials.getRealtime('IBM', Datacode.DIV.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity DIV {}'.format(s))
s = financials.getRealtime('IBM', Datacode.DIV_YIELD.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity DIV_YIELD {}'.format(s))
s = financials.getRealtime('IBM', Datacode.EX_DIV_DATE.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_US_equity EX_DIV_DATE {}'.format(s))
self.assertTrue(testutils.is_date(s), 'test_realtime_US_equity EX_DIV_DATE {}'.format(s))
s = financials.getRealtime('IBM', Datacode.PAYOUT_RATIO.value, 'YAHOO')
self.assertTrue(testutils.is_positive_float(s), 'test_realtime_US_equity PAYOUT_RATIO {}'.format(s))
s = financials.getRealtime('IBM', Datacode.EXCHANGE.value, 'YAHOO')
self.assertEqual(s, 'NYQ', 'test_realtime_US_equity EXCHANGE')
def test_realtime_US_mutuals(self):
s = financials.getRealtime('VGSLX', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_mutuals LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_mutuals LAST_PRICE {}'.format(s))
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_US_mutuals LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_US_mutuals LAST_PRICE {}'.format(s))
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_DATE.value, 'YAHOO')
self.assertEqual(type(s), str, 'test_realtime_US_mutuals LAST_PRICE_DATE {}'.format(s))
self.assertEqual(str, type(s), 'test_realtime_US_mutuals LAST_PRICE_DATE {}'.format(s))
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE_TIME.value, 'YAHOO')
self.assertEqual(type(s), str, 'test_realtime_US_mutuals LAST_PRICE_TIME {}'.format(s))
self.assertEqual(str, type(s), 'test_realtime_US_mutuals LAST_PRICE_TIME {}'.format(s))
def test_realtime_UK_ETF(self):
s = financials.getRealtime('VERX.L', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_UK_ETF LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_UK_ETF LAST_PRICE {}'.format(s))
s = financials.getRealtime('VERX.L', Datacode.TIMEZONE.value, 'YAHOO')
self.assertEqual(s, 'Europe/London', 'test_realtime_UK_ETF TIMEZONE {}'.format(s))
s = financials.getRealtime('CSP1.L', Datacode.NAME.value, 'YAHOO')
self.assertEqual(type(s), str, 'test_realtime_UK_ETF NAME {}'.format(s))
self.assertEqual(str, type(s), 'test_realtime_UK_ETF NAME {}'.format(s))
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))
def test_realtime_DE_equity(self):
s = financials.getRealtime('SAP.DE', Datacode.LAST_PRICE.value, 'YAHOO')
self.assertEqual(type(s), float, 'test_realtime_DE_equity LAST_PRICE {}'.format(s))
self.assertEqual(float, type(s), 'test_realtime_DE_equity LAST_PRICE {}'.format(s))
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(str, type(s), '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(str, type(s), 'test_realtime_DE_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Software—Application', 'test_realtime_DE_equity INDUSTRY {}'.format(s))
s = financials.getRealtime('C060.DE', Datacode.NAME.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_realtime_DE_equity NAME {}'.format(s))
s = financials.getRealtime('C060.DE', Datacode.EXCHANGE.value, 'YAHOO')
self.assertEqual(s, 'GER', 'test_realtime_DE_equity EXCHANGE')
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))
s = financials.getRealtime('6503.T', Datacode.LOW.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity LOW {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.HIGH.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity HIGH {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.LOW_52_WEEK.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity LOW_52_WEEK {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.HIGH_52_WEEK.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity HIGH_52_WEEK {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.MARKET_CAP.value, 'YAHOO')
self.assertEqual(float, type(s), 'test_TY_equity MARKET_CAP {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.VOLUME.value, 'YAHOO')
self.assertEqual(float, type(s), '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(str, type(s), 'test_TY_equity SECTOR {}'.format(s))
self.assertEqual(s, 'Industrials', 'test_TY_equity SECTOR {}'.format(s))
s = financials.getRealtime('6503.T', Datacode.INDUSTRY.value, 'YAHOO')
self.assertEqual(str, type(s), 'test_TY_equity INDUSTRY {}'.format(s))
self.assertEqual(s, 'Electrical Equipment & Parts', 'test_TY_equity INDUSTRY {}'.format(s))
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 +220,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(139.61322, s, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
def test_historic_UK_ETF(self):
@@ -160,7 +238,7 @@ 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
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
@@ -202,7 +280,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):
@@ -233,7 +311,8 @@ class Test(unittest.TestCase):
self.assertEqual(s, 'Date format not supported: \'abcdef\'', 'test_historic_errors CLOSE {}'.format(s))
s = financials.getHistoric('IBM', Datacode.CLOSE.value, True, 'YAHOO')
self.assertEqual(s, 'Date type not supported: <class \'bool\'> \'True\'', 'test_historic_errors CLOSE {}'.format(s))
self.assertEqual(s, 'Date type not supported: <class \'bool\'> \'True\'',
'test_historic_errors CLOSE {}'.format(s))
s = financials.getHistoric('IBM', Datacode.CLOSE.value, -1000000, 'YAHOO')
self.assertEqual(s, 'Date format not supported: -1000000', 'test_historic_errors CLOSE {}'.format(s))
+10
View File
@@ -0,0 +1,10 @@
import re
def is_positive_float(f):
return type(f) == float and f > 0.0
def is_date(s):
match = re.compile(r'^20\d\d-\d\d-\d\d$', flags=re.DOTALL).search(s)
return match is not None
+15
View File
@@ -0,0 +1,15 @@
# 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'