Source code for biweeklybudget.prime_rate

"""
The latest version of this package is available at:
<http://github.com/jantman/biweeklybudget>

################################################################################
Copyright 2016 Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>

    This file is part of biweeklybudget, also known as biweeklybudget.

    biweeklybudget is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    biweeklybudget is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with biweeklybudget.  If not, see <http://www.gnu.org/licenses/>.

The Copyright and Authors attributions contained herein may not be removed or
otherwise altered, except to add the Author attribution of a contributor to
this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
################################################################################
While not legally required, I sincerely request that anyone who finds
bugs please submit them at <https://github.com/jantman/biweeklybudget> or
to me via email, and that you send any contributions or improvements
either as a pull request on GitHub, or to me via email.
################################################################################

AUTHORS:
Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
################################################################################
"""

from decimal import Decimal
import json
from datetime import timedelta

import lxml.html
from lxml.etree import tostring
import requests
import logging

from biweeklybudget.models.dbsetting import DBSetting
from biweeklybudget.utils import dtnow, decode_json_datetime
from biweeklybudget.flaskapp.jsonencoder import MagicJSONEncoder

logger = logging.getLogger(__name__)


[docs]class PrimeRateCalculator(object): def __init__(self, db_session): """ :param db_session: Database session :type db_session: sqlalchemy.orm.session.Session """ self._db_sess = db_session
[docs] def _rate_from_marketwatch(self): url = 'https://www.wsj.com/market-data/bonds/moneyrates' logger.debug('Requesting %s for prime rate', url) r = requests.get( url, headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/79.0.3945.130 Safari/537.36'} ) doc = lxml.html.fromstring(r.text) prtitle = doc.xpath('//caption[starts-with(text(), "Prime Rates")]') if prtitle is None or len(prtitle) != 1: logger.error( 'Could not find "Prime rates" TD; page: %s', r.text ) raise RuntimeError('ERROR: format change in %s' % url) prtbody = prtitle[0].getparent().findall('tbody')[0] prthead = prtitle[0].getparent().findall('thead')[0] headrows = prthead.findall('tr') if not headrows[1].findall('th')[1].text == 'Latest': logger.error( 'tr2,td1 contains unexpected text; prtbody: %s', tostring(prtbody) ) raise RuntimeError('ERROR: format change in %s' % url) rows = prtbody.findall('tr') if not rows[0].findall('td')[0].text == 'U.S.': logger.error( 'tr3,td0 contains unexpected text; prtbody: %s', tostring(prtbody) ) raise RuntimeError('ERROR: format change in %s' % url) pr_amt = rows[0].findall('td')[1].text logger.debug( 'Found prime rate from %s: %s', url, pr_amt ) return pr_amt
[docs] def _get_prime_rate(self): """ Get the US Prime Rate from MarketWatch; update the DB and return the value. :return: current US Prime Rate :rtype: decimal.Decimal """ rate = self._rate_from_marketwatch() rate = Decimal(rate) * Decimal('0.01') s = self._db_sess.query(DBSetting).get('prime_rate') if s is None: s = DBSetting(name='prime_rate') logger.info('Got Prime Rate from MarketWatch: %s', rate) s.value = json.dumps({ 'value': '%s' % rate, 'date': dtnow() }, cls=MagicJSONEncoder) self._db_sess.add(s) self._db_sess.flush() self._db_sess.commit() return rate
@property def prime_rate(self): """ Return the current US Prime Rate :return: current US Prime Rate :rtype: decimal.Decimal """ pr = self._db_sess.query(DBSetting).get('prime_rate') if pr is None: return self._get_prime_rate() j = json.loads(pr.value) d = decode_json_datetime(j['date']) if d >= (dtnow() - timedelta(hours=48)): return Decimal(j['value']) return self._get_prime_rate()
[docs] def calculate_apr(self, margin): """ Calculate an APR based on the prime rate. :param margin: margin added to Prime Rate to get APR :type margin: decimal.Decimal :return: effective APR :rtype: decimal.Decimal """ return self.prime_rate + margin