mirror of
https://github.com/cmallwitz/Financials-Extension.git
synced 2026-08-25 10:04:10 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
601b91c8c3 | ||
|
|
7b806ce527 | ||
|
|
240cfea126 | ||
|
|
99c660f1ad | ||
|
|
19215e4e98 | ||
|
|
0fc9d4335f | ||
|
|
625a18eb02 | ||
|
|
1c10237b60 | ||
|
|
8933347508 | ||
|
|
7aadaad7dc | ||
|
|
01538a13a8 | ||
|
|
9b464e22b1 | ||
|
|
d7a7e4f791 | ||
|
|
12ecbfbbf8 | ||
|
|
5783736d23 |
Binary file not shown.
@@ -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
@@ -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/
|
||||
|
||||
Binary file not shown.
+104
-45
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user