mirror of
https://github.com/cmallwitz/Financials-Extension.git
synced 2026-08-25 10:04:10 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e21e3c51ab | ||
|
|
e113b5f2f2 | ||
|
|
9eee2fb96c | ||
|
|
39d4424ce4 | ||
|
|
da3733f285 | ||
|
|
70ef0ad4ec | ||
|
|
901ff46906 |
Binary file not shown.
@@ -2,7 +2,11 @@
|
||||
|
||||
Extension for LibreOffice Calc to access stock market data. Currently supports Yahoo and Google.
|
||||
|
||||
Only tested this with Ubuntu 16.04 and LibreOffice 5
|
||||
Requires the following packages: python-dateutil python3-dateutil python3-pyparsing
|
||||
|
||||
Only tested this with
|
||||
- Ubuntu 16.04 and LibreOffice 5
|
||||
- Ubuntu 18.04 and LibreOffice 6
|
||||
|
||||
To Build:
|
||||
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@ cp -f "${PWD}"/src/financials.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/datacode.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/baseclient.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/jsonParser.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/google.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/naivehtmlparser.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/google2.py "${PWD}"/build/
|
||||
cp -f "${PWD}"/src/yahoo.py "${PWD}"/build/
|
||||
|
||||
echo "Package into oxt file..."
|
||||
|
||||
Binary file not shown.
+93
-30
@@ -10,21 +10,30 @@
|
||||
|
||||
import codecs
|
||||
import gzip
|
||||
import logging
|
||||
import random
|
||||
import sys
|
||||
import select
|
||||
|
||||
from http.client import HTTPConnection, HTTPSConnection
|
||||
from http.client import HTTPConnection, HTTPSConnection, HTTPException
|
||||
from http import cookiejar
|
||||
|
||||
import urllib.request
|
||||
|
||||
from datacode import Datacode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
|
||||
def log(str):
|
||||
# print(str, file=sys.stderr)
|
||||
pass
|
||||
|
||||
class RedirectException(HTTPException):
|
||||
def __init__(self, location):
|
||||
self.location = location
|
||||
|
||||
|
||||
class HttpException(HTTPException):
|
||||
def __init__(self, url, status):
|
||||
self.url = url
|
||||
self.status = status
|
||||
|
||||
|
||||
class BaseClient:
|
||||
@@ -33,12 +42,22 @@ class BaseClient:
|
||||
self.cookies = cookiejar.CookieJar()
|
||||
|
||||
user_agents = [
|
||||
'Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0',
|
||||
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0'
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
|
||||
'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: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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 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'
|
||||
]
|
||||
|
||||
self.default_headers = {
|
||||
@@ -48,11 +67,16 @@ class BaseClient:
|
||||
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8'
|
||||
}
|
||||
|
||||
def request(self, method: str, url: str, data=None, headers={}, **kwargs):
|
||||
def request(self, method: str, url: str, data=None, headers={}, cookies=[], **kwargs):
|
||||
|
||||
_headers = self.default_headers.copy()
|
||||
for key, value in headers.items():
|
||||
_headers[key] = value
|
||||
if headers:
|
||||
for key, value in headers.items():
|
||||
_headers[key] = value
|
||||
|
||||
if cookies:
|
||||
for c in cookies:
|
||||
self.cookies.set_cookie(c)
|
||||
|
||||
connection = None
|
||||
|
||||
@@ -66,11 +90,11 @@ class BaseClient:
|
||||
connection = None
|
||||
|
||||
if not connection:
|
||||
log('Creating HTTP connection --------- ----------------------------------------')
|
||||
logger.debug('Creating connection --------------------------------------------------')
|
||||
connection = HTTPConnection(host, **kwargs) if scheme == 'http:' else HTTPSConnection(host, **kwargs)
|
||||
|
||||
log('Creating HTTP request ------------ ----------------------------------------')
|
||||
log(url)
|
||||
logger.debug('Creating request -----------------------------------------------------')
|
||||
logger.info('url=%s', url)
|
||||
|
||||
# generate and add cookie headers
|
||||
request = urllib.request.Request(url)
|
||||
@@ -80,41 +104,61 @@ class BaseClient:
|
||||
_headers['Cookie'] = request.get_header('Cookie')
|
||||
|
||||
for key, value in _headers.items():
|
||||
log('{}: {}'.format(key, value))
|
||||
logger.debug('Header: %s=%s', key, value)
|
||||
|
||||
# request
|
||||
connection.request(method, '/' + path, data, _headers)
|
||||
response = connection.getresponse()
|
||||
|
||||
log('Processing HTTP response --------- ----------------------------------------')
|
||||
logger.debug('Processing response --------------------------------------------------')
|
||||
|
||||
# log('response.status={}'.format(response.status))
|
||||
# logger.debug('response.status={}'.format(response.status))
|
||||
for key, value in response.getheaders():
|
||||
log('{}: {}'.format(key, value))
|
||||
logger.debug('Header: %s=%s', key, value)
|
||||
|
||||
self.cookies.extract_cookies(response, request)
|
||||
self.connections[(scheme, host)] = connection
|
||||
|
||||
return response
|
||||
|
||||
def urlopen(self, url, data=None, headers={}, **kwargs):
|
||||
def urlopen(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
|
||||
|
||||
response = self.request('POST' if data else 'GET', url, data, headers, **kwargs)
|
||||
response = self.request('POST' if data else 'GET', url, data, headers, cookies, **kwargs)
|
||||
text = response.read()
|
||||
|
||||
# Allow two redirects: used by Yahoo for some cookie based consent
|
||||
|
||||
if 300 <= response.status < 400:
|
||||
location = response.getheader('Location')
|
||||
|
||||
scheme, _, host, path = url.split('/', 3)
|
||||
redirect_to = response.getheader('Location')
|
||||
if host not in redirect_to:
|
||||
redirect_to = scheme + '//' + host + redirect_to
|
||||
if location and redirect:
|
||||
|
||||
if response.getheader('Location'):
|
||||
response = self.request('POST' if data else 'GET', redirect_to, data, headers, **kwargs)
|
||||
if location.startswith('/'):
|
||||
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()
|
||||
|
||||
assert response.status < 400, \
|
||||
'HTTP Status={} Reason={} url={}'.format(response.status, response.reason, url)
|
||||
if 300 <= response.status < 400:
|
||||
location = response.getheader('Location')
|
||||
|
||||
if location and redirect:
|
||||
|
||||
if location.startswith('/'):
|
||||
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()
|
||||
else:
|
||||
raise RedirectException(location)
|
||||
|
||||
else:
|
||||
raise RedirectException(location)
|
||||
|
||||
if response.status >= 400:
|
||||
raise HttpException(url, response.status)
|
||||
|
||||
if response.getheader('Content-Encoding') == 'gzip':
|
||||
text = gzip.decompress(text)
|
||||
@@ -164,6 +208,15 @@ class BaseClient:
|
||||
elif datacode == Datacode.LAST_PRICE.value and Datacode.LAST_PRICE in data:
|
||||
return data[Datacode.LAST_PRICE]
|
||||
|
||||
elif datacode == Datacode.LOW_52_WEEK.value and Datacode.LOW_52_WEEK in data:
|
||||
return data[Datacode.LOW_52_WEEK]
|
||||
|
||||
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]:
|
||||
return data[Datacode.MARKET_CAP]
|
||||
|
||||
elif datacode == Datacode.VOLUME.value and Datacode.VOLUME in data:
|
||||
return data[Datacode.VOLUME]
|
||||
|
||||
@@ -188,10 +241,20 @@ class BaseClient:
|
||||
elif datacode == Datacode.NAME.value and data[Datacode.NAME]:
|
||||
return data[Datacode.NAME]
|
||||
|
||||
elif datacode == Datacode.TIMEZONE.value and data[Datacode.TIMEZONE]:
|
||||
elif datacode == Datacode.TIMEZONE.value and Datacode.TIMEZONE in data and data[Datacode.TIMEZONE]:
|
||||
return str(data[Datacode.TIMEZONE])
|
||||
|
||||
except BaseException as e:
|
||||
return 'BaseClient.return_value(\'{}\', {}) - {}'.format(data, datacode, e)
|
||||
|
||||
return "Data doesn't exist - {}".format(datacode)
|
||||
|
||||
def save_wrapper(self, f):
|
||||
try:
|
||||
value = f()
|
||||
logger.debug(value)
|
||||
return value
|
||||
except BaseException as e:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
+5
-1
@@ -24,6 +24,10 @@ class Datacode(Enum):
|
||||
|
||||
LAST_PRICE = 21
|
||||
|
||||
HIGH_52_WEEK = 24
|
||||
LOW_52_WEEK = 26
|
||||
MARKET_CAP = 27
|
||||
|
||||
VOLUME = 35
|
||||
AVG_DAILY_VOL_3MOMTH = 39
|
||||
|
||||
@@ -42,4 +46,4 @@ class Datacode(Enum):
|
||||
|
||||
@classmethod
|
||||
def has_value(cls, value):
|
||||
return (any(value == item.value for item in cls))
|
||||
return any(value == item.value for item in cls)
|
||||
|
||||
+9
-7
@@ -26,7 +26,7 @@ if current_dir not in sys.path:
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
from datacode import Datacode
|
||||
import google
|
||||
import google2 as google
|
||||
import yahoo
|
||||
|
||||
implementation_name = "com.financials.getinfo.python.FinancialsImpl" # as defined in Financials.xcu
|
||||
@@ -70,14 +70,15 @@ class FinancialsImpl(unohelper.Base, Financials):
|
||||
if not Datacode.has_value(datacode):
|
||||
return 'Datacode {} not supported'.format(datacode)
|
||||
|
||||
source = source.upper()
|
||||
ticker = str(ticker).strip()
|
||||
source = str(source).upper()
|
||||
|
||||
if source == 'GOOGLE':
|
||||
s = self.google.getRealtime(str(ticker).strip(), datacode)
|
||||
s = self.google.getRealtime(ticker, datacode)
|
||||
elif source == 'YAHOO':
|
||||
s = self.yahoo.getRealtime(str(ticker).strip(), datacode)
|
||||
s = self.yahoo.getRealtime(ticker, datacode)
|
||||
else:
|
||||
s = 'getRealtime:Source \'{}\' not supported'.format(source)
|
||||
s = 'Source \'{}\' not supported'.format(source)
|
||||
|
||||
except Exception as ex:
|
||||
return str(ex)
|
||||
@@ -144,12 +145,13 @@ class FinancialsImpl(unohelper.Base, Financials):
|
||||
else:
|
||||
return 'Date type not supported: {} \'{}\''.format(type(date), date)
|
||||
|
||||
source = source.upper()
|
||||
ticker = str(ticker).strip()
|
||||
source = str(source).upper()
|
||||
|
||||
if source == 'YAHOO':
|
||||
s = self.yahoo.getHistoric(str(ticker).strip(), datacode, date)
|
||||
else:
|
||||
s = 'getHistoric: Source \'{}\' not supported'.format(source)
|
||||
s = 'Source \'{}\' not supported'.format(source)
|
||||
|
||||
except Exception as ex:
|
||||
return str(ex)
|
||||
|
||||
@@ -13,7 +13,7 @@ import os
|
||||
cur_dir = os.getcwd()
|
||||
|
||||
addin_id = "com.financials.getinfo"
|
||||
addin_version = "0.0.3"
|
||||
addin_version = "1.0.3"
|
||||
addin_displayname = "Financial Market Extension"
|
||||
addin_publisher_link = "https://github.com/cmallwitz/Financials-Extension"
|
||||
addin_publisher_name = "The Publisher"
|
||||
|
||||
+100
-54
@@ -10,9 +10,9 @@
|
||||
|
||||
import datetime
|
||||
import locale
|
||||
import logging
|
||||
import html
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
@@ -21,17 +21,19 @@ import urllib.parse
|
||||
from datacode import Datacode
|
||||
from baseclient import BaseClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
|
||||
def log(str):
|
||||
# print(str, file=sys.stderr)
|
||||
pass
|
||||
|
||||
# TODO migrate to:
|
||||
# https://www.google.com/search?q=NYSE:IBM&tbm=fin
|
||||
# https://www.google.com/search?q=NASDAQ:INTC&tbm=fin
|
||||
# https://www.google.com/search?q=LON:VOD&tbm=fin
|
||||
# https://www.google.com/search?q=EURGBP
|
||||
# https://www.google.com/search?q=INDEXSP:.INX
|
||||
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):
|
||||
@@ -53,10 +55,10 @@ class Google(BaseClient):
|
||||
# remove white space
|
||||
ticker = "".join(ticker.split())
|
||||
|
||||
# use cached value for up to 60 seconds
|
||||
# use cached value for up to 5 minutes
|
||||
if ticker in self.realtime:
|
||||
tick = self.realtime[ticker]
|
||||
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
|
||||
if time.time() - 5*60 < tick[Datacode.TIMESTAMP]:
|
||||
return self._return_value(tick, datacode)
|
||||
else:
|
||||
del self.realtime[ticker]
|
||||
@@ -66,16 +68,16 @@ class Google(BaseClient):
|
||||
try:
|
||||
text = self.urlopen(url)
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
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 = re.findall(pattern, text)
|
||||
result = pattern.findall(text)
|
||||
|
||||
if len(result) == 0:
|
||||
return 'Data for \'{}\' not found'.format(ticker)
|
||||
return None
|
||||
|
||||
if ticker not in self.realtime:
|
||||
self.realtime[ticker] = {}
|
||||
@@ -85,16 +87,10 @@ class Google(BaseClient):
|
||||
for key, value in result:
|
||||
|
||||
if key == 'exchangeTimezone':
|
||||
try:
|
||||
tick[Datacode.TIMEZONE] = str(value)
|
||||
except:
|
||||
pass
|
||||
tick[Datacode.TIMEZONE] = self.save_wrapper(lambda: str(value))
|
||||
|
||||
elif key == 'priceChange':
|
||||
try:
|
||||
tick[Datacode.CHANGE] = float(value)
|
||||
except:
|
||||
pass
|
||||
tick[Datacode.CHANGE] = self.save_wrapper(lambda: float(value))
|
||||
|
||||
elif key == 'quoteTime':
|
||||
try:
|
||||
@@ -105,57 +101,107 @@ class Google(BaseClient):
|
||||
pass
|
||||
|
||||
elif key == 'priceChangePercent':
|
||||
try:
|
||||
tick[Datacode.CHANGE_IN_PERCENT] = float(value)
|
||||
except:
|
||||
pass
|
||||
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(lambda: (float(value)))
|
||||
|
||||
elif key == 'price':
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||
tick[Datacode.LAST_PRICE] = locale.atof(str(value))
|
||||
except:
|
||||
pass
|
||||
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||
tick[Datacode.LAST_PRICE] = self.save_wrapper(lambda: locale.atof(str(value)))
|
||||
|
||||
elif key == 'priceCurrency':
|
||||
try:
|
||||
tick[Datacode.CURRENCY] = str(value)
|
||||
except:
|
||||
pass
|
||||
|
||||
elif key == 'priceCurrency':
|
||||
pass
|
||||
tick[Datacode.CURRENCY] = self.save_wrapper(lambda: str(value))
|
||||
|
||||
elif key == 'exchange':
|
||||
try:
|
||||
tick[Datacode.EXCHANGE] = str(value)
|
||||
except:
|
||||
pass
|
||||
tick[Datacode.EXCHANGE] = self.save_wrapper(lambda: str(value))
|
||||
|
||||
elif key == 'name':
|
||||
try:
|
||||
tick[Datacode.NAME] = html.unescape(str(value))
|
||||
except:
|
||||
pass
|
||||
tick[Datacode.NAME] = self.save_wrapper(lambda: html.unescape(str(value)))
|
||||
|
||||
elif key == 'tickerSymbol':
|
||||
try:
|
||||
tick[Datacode.TICKER] = str(value)
|
||||
except:
|
||||
pass
|
||||
tick[Datacode.TICKER] = self.save_wrapper(lambda: str(value))
|
||||
|
||||
else:
|
||||
log('ignored {} {}'.format(key, value))
|
||||
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] = ''
|
||||
|
||||
log(tick)
|
||||
logger.info(tick)
|
||||
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
logger.warning(traceback.format_exc())
|
||||
return 'Google.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
# 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 dateutil
|
||||
import locale
|
||||
import logging
|
||||
import html
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from naivehtmlparser import NaiveHTMLParser
|
||||
|
||||
from datacode import Datacode
|
||||
from baseclient import BaseClient, RedirectException
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def un_span(s):
|
||||
return s.replace('<span>', '').replace('</span>', '')
|
||||
|
||||
|
||||
class Google(BaseClient):
|
||||
def __init__(self, ctx):
|
||||
super().__init__()
|
||||
|
||||
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 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]
|
||||
|
||||
q_param = 'q=' + ticker
|
||||
|
||||
if not self.location:
|
||||
url = 'https://www.google.com/search?hl=en&tbm=fin&' + q_param
|
||||
|
||||
try:
|
||||
self.urlopen(url, redirect=False)
|
||||
except RedirectException as e:
|
||||
self.location = e.location.replace('&' + q_param, '')
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Google.getRealtime(\'{}\', {}) - location: {}'.format(ticker, datacode, e)
|
||||
|
||||
if not self.location:
|
||||
url = 'https://www.google.com/search?tbm=fin&' + q_param
|
||||
else:
|
||||
url = self.location + '&' + q_param
|
||||
|
||||
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())
|
||||
return 'Google.getRealtime(\'{}\', {}) - urlopen: {} {}'.format(ticker, datacode, e, url)
|
||||
|
||||
if ticker not in self.realtime:
|
||||
self.realtime[ticker] = {}
|
||||
|
||||
tick = self.realtime[ticker]
|
||||
|
||||
try:
|
||||
r = '<div [^>]+ role="heading">'
|
||||
pattern = re.compile(r)
|
||||
|
||||
# ignore first <div ... role="heading">
|
||||
match = pattern.search(text)
|
||||
if not match:
|
||||
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
|
||||
start = match.span(0)[1]
|
||||
|
||||
# after second <div ... role="heading"> - get name
|
||||
match = pattern.search(text, start)
|
||||
if not match:
|
||||
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
|
||||
start = match.span(0)[1]
|
||||
|
||||
r = '<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()))
|
||||
|
||||
# third div is TICKER
|
||||
match = pattern.search(text, start)
|
||||
if not match:
|
||||
return 'Google.getRealtime({}, {}) - no match'.format(ticker, datacode)
|
||||
|
||||
ticker = self.save_wrapper(
|
||||
lambda: html.unescape(match.group(1)).replace(' ', ''))
|
||||
|
||||
tick[Datacode.EXCHANGE] = self.save_wrapper(lambda: ticker.split(':')[0])
|
||||
tick[Datacode.TICKER] = self.save_wrapper(lambda: ticker.split(':')[1])
|
||||
|
||||
except BaseException as e:
|
||||
return 'Google.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
|
||||
|
||||
try:
|
||||
r = '<sticky-header [^>]*>(.*?)</sticky-header>'
|
||||
pattern = re.compile(r, flags=re.DOTALL)
|
||||
match = re.search(pattern, text)
|
||||
|
||||
if match:
|
||||
text = match.group(1)
|
||||
else:
|
||||
return 'Data for \'{}\' not found'.format(ticker)
|
||||
|
||||
parser = NaiveHTMLParser()
|
||||
root = parser.feed(text)
|
||||
parser.close()
|
||||
|
||||
cards = root.findall('.//g-card-section')
|
||||
|
||||
if len(cards) < 4:
|
||||
return 'Data for \'{}\' not found'.format(ticker)
|
||||
|
||||
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('./div[1]/span[1]/span[1]/span[1]').text).strip()))
|
||||
|
||||
tick[Datacode.CURRENCY] = self.save_wrapper(
|
||||
lambda: html.unescape(header.find('./div[1]/span[1]/span[1]/span[2]').text).strip())
|
||||
|
||||
tick[Datacode.CHANGE] = self.save_wrapper(
|
||||
lambda: locale.atof(
|
||||
html.unescape(header.find('./div[1]/span[2]/span[1]').text).replace('−', '-').strip()))
|
||||
|
||||
tick[Datacode.CHANGE_IN_PERCENT] = self.save_wrapper(
|
||||
lambda: float(
|
||||
html.unescape(header.find('./div[1]/span[2]/span[2]/span[1]').text).strip()
|
||||
.replace('(', '').replace(')', '').replace('%', '')))
|
||||
|
||||
try:
|
||||
value = html.unescape(header.find('./div[2]/span[1]/span[2]').text).replace('·', '').strip()
|
||||
logger.debug(value)
|
||||
dt = dateutil.parser.parse(value)
|
||||
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
|
||||
|
||||
footer = cards[3]
|
||||
logger.debug(ET.tostring(footer))
|
||||
|
||||
# parse 'footer' for remaining fields
|
||||
table = footer.find('./div[1]/div[1]/div[1]/table[1]')
|
||||
|
||||
tick[Datacode.OPEN] = self.save_wrapper(
|
||||
lambda: float(
|
||||
html.unescape(table.find('./tr[1]/td[2]').text).replace(',', '').strip()))
|
||||
|
||||
tick[Datacode.HIGH] = self.save_wrapper(
|
||||
lambda: float(
|
||||
html.unescape(table.find('./tr[2]/td[2]').text).replace(',', '').strip()))
|
||||
|
||||
tick[Datacode.LOW] = self.save_wrapper(
|
||||
lambda: float(
|
||||
html.unescape(table.find('./tr[3]/td[2]').text).replace(',', '').strip()))
|
||||
|
||||
tick[Datacode.MARKET_CAP] = self.save_wrapper(
|
||||
lambda: handle_abbreviations(
|
||||
html.unescape(table.find('./tr[4]/td[2]').text).replace(',', '').replace('-', '').strip()))
|
||||
|
||||
table = footer.find('./div[1]/div[1]/div[2]/table[1]')
|
||||
|
||||
tick[Datacode.PREV_CLOSE] = self.save_wrapper(
|
||||
lambda: float(
|
||||
html.unescape(table.find('./tr[2]/td[2]').text).replace(',', '').strip()))
|
||||
|
||||
tick[Datacode.HIGH_52_WEEK] = self.save_wrapper(
|
||||
lambda: float(
|
||||
html.unescape(table.find('./tr[3]/td[2]').text).replace(',', '').strip()))
|
||||
|
||||
tick[Datacode.LOW_52_WEEK] = self.save_wrapper(
|
||||
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())
|
||||
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)
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# https://github.com/marmelo/python-htmlparser - revision cbe9633 on 25 Dec 2013
|
||||
# Copyright by Rafael Marmelo
|
||||
|
||||
"""
|
||||
Python 3.x HTMLParser extension with ElementTree support.
|
||||
"""
|
||||
|
||||
from html.parser import HTMLParser
|
||||
from xml.etree import ElementTree
|
||||
|
||||
|
||||
class NaiveHTMLParser(HTMLParser):
|
||||
"""
|
||||
Python 3.x HTMLParser extension with ElementTree support.
|
||||
@see https://github.com/marmelo/python-htmlparser
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.root = None
|
||||
self.tree = []
|
||||
HTMLParser.__init__(self)
|
||||
|
||||
def feed(self, data):
|
||||
HTMLParser.feed(self, data)
|
||||
return self.root
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if len(self.tree) == 0:
|
||||
element = ElementTree.Element(tag, dict(self.__filter_attrs(attrs)))
|
||||
self.tree.append(element)
|
||||
self.root = element
|
||||
else:
|
||||
element = ElementTree.SubElement(self.tree[-1], tag, dict(self.__filter_attrs(attrs)))
|
||||
self.tree.append(element)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
self.tree.pop()
|
||||
|
||||
def handle_startendtag(self, tag, attrs):
|
||||
self.handle_starttag(tag, attrs)
|
||||
self.handle_endtag(tag)
|
||||
pass
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.tree:
|
||||
self.tree[-1].text = data
|
||||
|
||||
def get_root_element(self):
|
||||
return self.root
|
||||
|
||||
def __filter_attrs(self, attrs):
|
||||
return filter(lambda x: x[0] and x[1], attrs) if attrs else []
|
||||
|
||||
|
||||
# example usage
|
||||
if __name__ == "__main__":
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head>
|
||||
<title>GitHub</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://github.com/marmelo">GitHub</a>
|
||||
<a href="https://github.com/marmelo/python-htmlparser">GitHub Project</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
parser = NaiveHTMLParser()
|
||||
root = parser.feed(html)
|
||||
parser.close()
|
||||
|
||||
# root is an xml.etree.Element and supports the ElementTree API
|
||||
# (e.g. you may use its limited support for XPath expressions)
|
||||
|
||||
# get title
|
||||
print(root.find('head/title').text)
|
||||
|
||||
# get all anchors
|
||||
for a in root.findall('.//a'):
|
||||
print(a.get('href'))
|
||||
|
||||
# for more information, see:
|
||||
# http://docs.python.org/2/library/xml.etree.elementtree.html
|
||||
# http://docs.python.org/2/library/xml.etree.elementtree.html#xpath-support
|
||||
+105
-35
@@ -7,6 +7,9 @@
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import financials
|
||||
@@ -14,42 +17,48 @@ from datacode import Datacode
|
||||
|
||||
financials = financials.createInstance(None)
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
class TestGoogle(unittest.TestCase):
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_currency(self):
|
||||
s = financials.getRealtime('EURGBP', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_currency LAST_PRICE')
|
||||
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')
|
||||
# s = financials.getRealtime('EURGBP', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
# self.assertEqual(type(s), str, 'test_currency CURRENCY')
|
||||
# self.assertEqual(s, '', 'test_currency CURRENCY')
|
||||
|
||||
def test_UK_equity(self):
|
||||
s = financials.getRealtime('EURGBP', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_UK_equity LAST_PRICE')
|
||||
|
||||
s = financials.getRealtime('LON:VOD', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_UK_equity LAST_PRICE')
|
||||
self.assertEqual(type(s), float, 'test_UK_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('VOD.L', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_UK_equity LAST_PRICE')
|
||||
|
||||
s = financials.getRealtime('VOD.L', Datacode.TICKER.value, 'GOOGLE')
|
||||
s = financials.getRealtime('LON:VOD', Datacode.TICKER.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'VOD', 'test_UK_equity TICKER')
|
||||
|
||||
s = financials.getRealtime('VOD.L', Datacode.NAME.value, 'GOOGLE')
|
||||
s = financials.getRealtime('LON:VOD', Datacode.NAME.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), str, 'test_UK_equity NAME')
|
||||
|
||||
s = financials.getRealtime('LON:VOD', Datacode.EXCHANGE.value, 'GOOGLE')
|
||||
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))
|
||||
|
||||
# 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))
|
||||
|
||||
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')
|
||||
self.assertEqual(type(s), float, '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')
|
||||
self.assertEqual(type(s), float, 'test_UK_ETF LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('LON:FTAL', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'GBP', 'test_UK_ETF CURRENCY')
|
||||
@@ -83,30 +92,57 @@ class TestGoogle(unittest.TestCase):
|
||||
self.assertEqual(type(s), float, 'test_DE_equity \'21\'')
|
||||
|
||||
s = financials.getRealtime('FRA:SAP', Datacode.TIMEZONE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'Europe/Berlin', 'test_DE_equity TIMEZONE')
|
||||
# self.assertEqual(s, 'Europe/Berlin', 'test_DE_equity TIMEZONE')
|
||||
self.assertTrue(s == 'CET' or s == 'CEST', 'test_DE_equity TIMEZONE: {}'.format(s))
|
||||
|
||||
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')
|
||||
self.assertEqual(type(s), float, 'test_DE_ETF LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('FRA:C060', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'EUR', 'test_DE_ETF CURRENCY')
|
||||
|
||||
s = financials.getRealtime('C060.de', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_DE_ETF LAST_PRICE')
|
||||
s = financials.getRealtime('FRA:C060', Datacode.TICKER.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'C060', 'test_DE_ETF TICKER')
|
||||
|
||||
s = financials.getRealtime('C060.de', Datacode.TICKER.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'C060', 'test_DE_ETF CURRENCY')
|
||||
s = financials.getRealtime('FRA:C060', Datacode.EXCHANGE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'FRA', 'test_DE_ETF EXCHANGE')
|
||||
|
||||
s = financials.getRealtime('C060.de', Datacode.EXCHANGE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'FRA', 'test_DE_ETF CURRENCY')
|
||||
|
||||
s = financials.getRealtime('C060.de', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
s = financials.getRealtime('FRA:C060', Datacode.CURRENCY.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'EUR', '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))
|
||||
|
||||
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))
|
||||
|
||||
s = financials.getRealtime('TYO:6503', Datacode.LOW.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
s = financials.getRealtime('TYO:6503', Datacode.LOW_52_WEEK.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
s = financials.getRealtime('TYO:6503', Datacode.MARKET_CAP.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
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')
|
||||
self.assertEqual(type(s), float, 'test_US_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime(' NASDAQ : AAPL ', Datacode.TICKER.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'AAPL', 'test_US_equity TICKER')
|
||||
@@ -118,7 +154,7 @@ class TestGoogle(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')
|
||||
self.assertEqual(type(s), float, 'test_US_equity LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('NYSE:IBM', Datacode.TICKER.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'IBM', 'test_US_equity TICKER')
|
||||
@@ -132,22 +168,52 @@ class TestGoogle(unittest.TestCase):
|
||||
s = financials.getRealtime('NYSE:IBM', Datacode.NAME.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), str, 'test_US_equity NAME')
|
||||
|
||||
s = financials.getRealtime('NYSE:IBM', Datacode.NAME.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), str, 'test_US_equity NAME')
|
||||
s = financials.getRealtime('NYSE:IBM', Datacode.LOW.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
s = financials.getRealtime('NYSE:IBM', Datacode.LOW_52_WEEK.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
s = financials.getRealtime('NYSE:IBM', Datacode.MARKET_CAP.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
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, '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')
|
||||
|
||||
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')
|
||||
self.assertEqual(type(s), float, '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')
|
||||
|
||||
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))
|
||||
|
||||
s = financials.getRealtime('INDEXDB:DAX', Datacode.CHANGE_IN_PERCENT.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_index CHANGE_IN_PERCENT')
|
||||
|
||||
s = financials.getRealtime('INDEXDB:DAX', Datacode.CHANGE.value, 'GOOGLE')
|
||||
self.assertEqual(type(s), float, 'test_index CHANGE')
|
||||
|
||||
def test_errors(self):
|
||||
s = financials.getRealtime(None, Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'Ticker is empty', 'test_errors')
|
||||
@@ -156,7 +222,7 @@ class TestGoogle(unittest.TestCase):
|
||||
self.assertEqual(s, 'Datacode is empty', 'test_errors')
|
||||
|
||||
s = financials.getRealtime('DOES_NOT_EXISTS', Datacode.LAST_PRICE.value, 'GOOGLE')
|
||||
self.assertEqual(s, 'Data for \'DOES_NOT_EXISTS\' not found', 'test_errors')
|
||||
self.assertEqual(s, 'Google.getRealtime(DOES_NOT_EXISTS, 21) - no match', 'test_errors')
|
||||
|
||||
s = financials.getRealtime('NYS:IBM', 'Foo', 'GOOGLE')
|
||||
self.assertEqual(s, 'Datacode is not a number', 'test_errors')
|
||||
@@ -164,7 +230,7 @@ class TestGoogle(unittest.TestCase):
|
||||
# Historic data not supported on GOOGLE
|
||||
|
||||
s = financials.getHistoric('NYS:IBM', Datacode.LAST_PRICE.value, '2017-01-01', 'GOOGLE')
|
||||
self.assertEqual(s, 'getHistoric: Source \'GOOGLE\' not supported', 'test_errors')
|
||||
self.assertEqual(s, 'Source \'GOOGLE\' not supported', 'test_errors')
|
||||
|
||||
def test_errors_cell_range_passed(self):
|
||||
cell_range = ((1, 2), ('3', '4'), (5.0, 6.0))
|
||||
@@ -206,4 +272,8 @@ class TestGoogle(unittest.TestCase):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('unittest_args', nargs='*')
|
||||
args = parser.parse_args()
|
||||
unit_argv = [sys.argv[0]] + args.unittest_args
|
||||
unittest.main(argv=unit_argv)
|
||||
|
||||
+57
-16
@@ -7,8 +7,11 @@
|
||||
# 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 os
|
||||
import pathlib
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import financials
|
||||
@@ -16,8 +19,18 @@ 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=X', Datacode.CURRENCY.value, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 'test_currency CURRENCY')
|
||||
|
||||
s = financials.getRealtime('EURGBP=X', Datacode.LAST_PRICE.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_currency LAST_PRICE')
|
||||
|
||||
class TestYahoo(unittest.TestCase):
|
||||
def test_realtime_US_equity(self):
|
||||
|
||||
s = financials.getRealtime('^GSPC', Datacode.NAME.value, 'YAHOO')
|
||||
@@ -39,6 +52,15 @@ class TestYahoo(unittest.TestCase):
|
||||
s = financials.getRealtime('IBM', Datacode.HIGH.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
s = financials.getRealtime('IBM', Datacode.LOW_52_WEEK.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, '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))
|
||||
|
||||
s = financials.getRealtime('IBM', Datacode.VOLUME.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_realtime_US_equity VOLUME {}'.format(s))
|
||||
|
||||
@@ -53,6 +75,9 @@ class TestYahoo(unittest.TestCase):
|
||||
|
||||
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))
|
||||
|
||||
s = financials.getRealtime('VFIAX', Datacode.LAST_PRICE.value, 'YAHOO')
|
||||
self.assertEqual(type(s), float, 'test_realtime_US_mutuals LAST_PRICE {}'.format(s))
|
||||
|
||||
@@ -75,6 +100,9 @@ class TestYahoo(unittest.TestCase):
|
||||
self.assertEqual(s, 'iShares VII Public Limited Company - iShares Core S&P 500 UCITS ETF',
|
||||
'test_realtime_UK_ETF NAME {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('C060.DE', 104, 'YAHOO')
|
||||
self.assertEqual(type(s), str, 't_realtime_UK_ETF AME {}'.format(s))
|
||||
|
||||
def test_realtime_DE_equity(self):
|
||||
|
||||
s = financials.getRealtime('SAP.DE', Datacode.LAST_PRICE.value, 'YAHOO')
|
||||
@@ -114,8 +142,9 @@ class TestYahoo(unittest.TestCase):
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 167.190002, 'test_historic_US_equity CLOSE {}'.format(s))
|
||||
|
||||
# Note: quarterly dividend and splits will change past adjusted prices
|
||||
s = financials.getHistoric('IBM', Datacode.ADJ_CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 160.947433, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
|
||||
self.assertEqual(s, 157.628433, 'test_historic_US_equity ADJ_CLOSE {}'.format(s))
|
||||
|
||||
def test_historic_UK_ETF(self):
|
||||
|
||||
@@ -129,8 +158,8 @@ class TestYahoo(unittest.TestCase):
|
||||
financials.yahoo.historicdata = {}
|
||||
|
||||
# Inception Date 2014-09-30
|
||||
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2014-01-06', 'YAHOO')
|
||||
self.assertEqual(s, 'Not a trading day \'2014-01-06\'', 'test_historic_UK_ETF CLOSE {}'.format(s))
|
||||
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2018-04-02', 'YAHOO') # Easter Monday
|
||||
self.assertEqual(s, 'Not a trading day \'2018-04-02\'', 'test_historic_UK_ETF CLOSE {}'.format(s))
|
||||
|
||||
# Inception Date 2014-09-30
|
||||
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2015-01-01', 'YAHOO')
|
||||
@@ -146,8 +175,8 @@ class TestYahoo(unittest.TestCase):
|
||||
self.assertEqual(s, 22.26, 'test_historic_UK_ETF CLOSE {}'.format(s))
|
||||
|
||||
# Inception Date 2014-09-30
|
||||
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2014-01-06', 'YAHOO')
|
||||
self.assertEqual(s, 'Not a trading day \'2014-01-06\'', 'test_historic_UK_ETF CLOSE {}'.format(s))
|
||||
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2018-04-02', 'YAHOO')
|
||||
self.assertEqual(s, 'Not a trading day \'2018-04-02\'', 'test_historic_UK_ETF CLOSE {}'.format(s))
|
||||
|
||||
# Inception Date 2014-09-30
|
||||
s = financials.getHistoric('VERX.L', Datacode.CLOSE.value, '2015-01-01', 'YAHOO')
|
||||
@@ -173,31 +202,39 @@ class TestYahoo(unittest.TestCase):
|
||||
s = financials.getHistoric('C060.DE', Datacode.CLOSE.value, '2017-01-03', 'YAHOO')
|
||||
self.assertEqual(s, 72.870003, 'test_historic_DE_equity CLOSE {}'.format(s))
|
||||
|
||||
def test_errors(self):
|
||||
def test_realtime_errors(self):
|
||||
|
||||
s = financials.getRealtime('NO_NAME', Datacode.LAST_PRICE.value, 'YAHOO')
|
||||
self.assertIsNone(s, 'test_realtime_errors LAST_PRICE {}'.format(s))
|
||||
|
||||
def test_historic_errors(self):
|
||||
|
||||
s = financials.getHistoric('NO_NAME', Datacode.LAST_PRICE.value, '2018-01-08', 'YAHOO')
|
||||
self.assertIsNone(s, 'test_historic_errors LAST_PRICE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2030-01-01', 'YAHOO')
|
||||
self.assertEqual(s, 'Future date \'2030-01-01\'', 'test_errors CLOSE {}'.format(s))
|
||||
self.assertEqual(s, 'Future date \'2030-01-01\'', 'test_historic_errors CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getRealtime('IBM', 9999, 'YAHOO')
|
||||
self.assertEqual(s, 'Datacode 9999 not supported', 'test_errors 9999')
|
||||
self.assertEqual(s, 'Datacode 9999 not supported', 'test_historic_errors 9999')
|
||||
|
||||
s = financials.getRealtime('IBM', Datacode.ADJ_CLOSE.value, 'YAHOO')
|
||||
self.assertEqual(s, 'Data doesn\'t exist - 91', 'test_errors ADJ_CLOSE {}'.format(s))
|
||||
self.assertEqual(s, 'Data doesn\'t exist - 91', 'test_historic_errors ADJ_CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '2030-01-01', 'YAHOO')
|
||||
self.assertEqual(s, 'Future date \'2030-01-01\'', 'test_errors CLOSE {}'.format(s))
|
||||
self.assertEqual(s, 'Future date \'2030-01-01\'', 'test_historic_errors CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, '1990-01-01', 'YAHOO')
|
||||
self.assertEqual(s, 'Date before 2000 \'1990-01-01\'', 'test_errors CLOSE {}'.format(s))
|
||||
self.assertEqual(s, 'Date before 2000 \'1990-01-01\'', 'test_historic_errors CLOSE {}'.format(s))
|
||||
|
||||
s = financials.getHistoric('IBM', Datacode.CLOSE.value, 'abcdef', 'YAHOO')
|
||||
self.assertEqual(s, 'Date format not supported: \'abcdef\'', 'test_errors CLOSE {}'.format(s))
|
||||
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_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_errors CLOSE {}'.format(s))
|
||||
self.assertEqual(s, 'Date format not supported: -1000000', 'test_historic_errors CLOSE {}'.format(s))
|
||||
|
||||
def test_errors_cell_range_passed(self):
|
||||
cell_range = ((1, 2), ('3', '4'), (5.0, 6.0))
|
||||
@@ -216,4 +253,8 @@ class TestYahoo(unittest.TestCase):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.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
-22
@@ -12,36 +12,36 @@ import csv
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import pprint
|
||||
import pytz
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import urllib.parse
|
||||
|
||||
from datacode import Datacode
|
||||
import baseclient
|
||||
from baseclient import BaseClient, HttpException
|
||||
from http import cookiejar
|
||||
import jsonParser
|
||||
|
||||
|
||||
def log(str):
|
||||
# print(str, file=sys.stderr)
|
||||
pass
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def raw(price, key, default=0.0):
|
||||
def raw(m, key, default=0.0):
|
||||
try:
|
||||
return price[key]['raw']
|
||||
return m[key]['raw']
|
||||
except:
|
||||
pass
|
||||
|
||||
return default
|
||||
|
||||
|
||||
class Yahoo(baseclient.BaseClient):
|
||||
class Yahoo(BaseClient):
|
||||
def __init__(self, ctx):
|
||||
super().__init__()
|
||||
|
||||
@@ -95,20 +95,34 @@ class Yahoo(baseclient.BaseClient):
|
||||
# remove white space
|
||||
ticker = "".join(ticker.split())
|
||||
|
||||
# use cached value for up to 60 seconds
|
||||
# use cached value for up to 5 minutes
|
||||
if ticker in self.realtime:
|
||||
tick = self.realtime[ticker]
|
||||
if time.time() - 60 < tick[Datacode.TIMESTAMP]:
|
||||
if time.time() - 5*60 < tick[Datacode.TIMESTAMP]:
|
||||
return self._return_value(tick, datacode)
|
||||
else:
|
||||
del self.realtime[ticker]
|
||||
|
||||
url = 'https://finance.yahoo.com/quote/{}?p={}'.format(ticker, ticker)
|
||||
|
||||
cookies = [cookiejar.Cookie(version=0,
|
||||
name="B",
|
||||
value="9898htldgiar5&b=3&s=gt",
|
||||
port=None, port_specified=None,
|
||||
domain=".yahoo.com", domain_specified=True, domain_initial_dot=True,
|
||||
path="/", path_specified=True,
|
||||
secure=True,
|
||||
expires=None,
|
||||
discard=False,
|
||||
comment=None,
|
||||
comment_url=None,
|
||||
rest=None)
|
||||
]
|
||||
|
||||
try:
|
||||
text = self.urlopen(url)
|
||||
text = self.urlopen(url, redirect=True, data=None, headers=None, cookies=cookies)
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - urlopen: {}'.format(ticker, datacode, e)
|
||||
|
||||
try:
|
||||
@@ -117,7 +131,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
|
||||
r = '"CrumbStore":{"crumb":"([^"]{11})"'
|
||||
pattern = re.compile(r)
|
||||
match = re.search(pattern, text)
|
||||
match = pattern.search(text)
|
||||
|
||||
if match:
|
||||
self.crumb = match.group(1)
|
||||
@@ -126,7 +140,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
print(text, file=text_file)
|
||||
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - crumb: {}'.format(ticker, datacode, e)
|
||||
|
||||
try:
|
||||
@@ -135,8 +149,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
if start < 0:
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.html'.format(ticker)), "w") as text_file:
|
||||
print(text, file=text_file)
|
||||
|
||||
return 'Could not find QuoteSummaryStore for \'{}\''.format(ticker)
|
||||
return None
|
||||
|
||||
start = start + len('"QuoteSummaryStore":')
|
||||
results = self.js.parseString(text[start:])
|
||||
@@ -146,8 +159,14 @@ class Yahoo(baseclient.BaseClient):
|
||||
print(text, file=text_file)
|
||||
return None
|
||||
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - parsing: {}'.format(ticker, datacode, e)
|
||||
|
||||
try:
|
||||
price = results['price']
|
||||
quoteType = results['quoteType']
|
||||
summaryDetail = results['summaryDetail']
|
||||
|
||||
if not price:
|
||||
return 'Could not find price for \'{}\''.format(ticker)
|
||||
@@ -167,6 +186,10 @@ class Yahoo(baseclient.BaseClient):
|
||||
tick[Datacode.VOLUME] = float(raw(price, 'regularMarketVolume'))
|
||||
tick[Datacode.AVG_DAILY_VOL_3MOMTH] = float(raw(price, 'averageDailyVolume3Month'))
|
||||
|
||||
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'))
|
||||
|
||||
if quoteType:
|
||||
t = int(price['regularMarketTime'])
|
||||
tz = pytz.timezone(quoteType['exchangeTimezoneName'])
|
||||
@@ -193,7 +216,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
with open(os.path.join(self.basedir, 'yahoo-{}.js'.format(ticker)), "w") as text_file:
|
||||
pprint.pprint(results.asList(), stream=text_file)
|
||||
|
||||
log(traceback.format_exc())
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getRealtime({}, {}) - process: {}'.format(ticker, datacode, e)
|
||||
|
||||
return self._return_value(self.realtime[ticker], datacode)
|
||||
@@ -213,7 +236,10 @@ class Yahoo(baseclient.BaseClient):
|
||||
ticker = "".join(ticker.split())
|
||||
min_tick_date = None
|
||||
|
||||
if ticker not in self.historicdata:
|
||||
# dividend and splits will change past adjusted prices
|
||||
# the moment we are asked for ADJ_CLOSE we ignore the ticker cache to refresh
|
||||
|
||||
if Datacode.ADJ_CLOSE != datacode and ticker not in self.historicdata:
|
||||
self._read_ticker_csv_file(ticker)
|
||||
|
||||
if ticker in self.historicdata:
|
||||
@@ -222,7 +248,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
if date in ticks:
|
||||
return self._return_value(ticks[date], datacode)
|
||||
|
||||
# weekend, trading holiday or as yet unfetched
|
||||
# weekend, trading holiday or as yet un-fetched
|
||||
if min(ticks) <= date <= max(ticks):
|
||||
return 'Not a trading day \'{}\''.format(date)
|
||||
|
||||
@@ -257,7 +283,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
t1 = t1 - 2682000 # pad with extra month
|
||||
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getHistoric({}, {}, {}) - date: {}'.format(ticker, datacode, date, e)
|
||||
|
||||
try:
|
||||
@@ -273,8 +299,12 @@ class Yahoo(baseclient.BaseClient):
|
||||
|
||||
self._read_ticker_csv_file(ticker)
|
||||
|
||||
except HttpException:
|
||||
logger.error(traceback.format_exc())
|
||||
return None
|
||||
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getHistoric({}, {}, {}) - read: {}'.format(ticker, datacode, date, e)
|
||||
|
||||
try:
|
||||
@@ -292,7 +322,7 @@ class Yahoo(baseclient.BaseClient):
|
||||
return 'Not a trading day \'{}\''.format(date)
|
||||
|
||||
except BaseException as e:
|
||||
log(traceback.format_exc())
|
||||
logger.error(traceback.format_exc())
|
||||
return 'Yahoo.getHistoric({}, {}, {}) - process: {}'.format(ticker, datacode, date, e)
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user