Changeset - b270db02e8d7
[Not reviewed]
0 3 0
Brett Smith - 7 years ago 2017-06-29 20:54:16
brettcsmith@brettcsmith.org
historical: Ledger conversions show enough rate precision to stay balanced.
3 files changed with 36 insertions and 9 deletions:
0 comments (0 inline, 0 general)
oxrlib/commands/historical.py
Show inline comments
...
 
@@ -25,12 +25,16 @@ class Formatter:
 
        if code in self.signed_currencies:
 
            fmt = '¤' + fmt
 
        else:
 
            fmt = fmt + ' ¤¤'
 
        return babel.numbers.format_currency(amount, code, fmt, currency_digits=currency_digits)
 

	
 
    def currency_decimal(self, amount, currency):
 
        amt_s = babel.numbers.format_currency(amount, currency, '###0.###')
 
        return decimal.Decimal(amt_s)
 

	
 
    def format_rate(self, rate):
 
        return "{:g}".format(rate)
 

	
 
    def format_rate_pair(self, from_curr, to_curr):
 
        from_amt = 1
 
        to_amt = self.rate.convert(from_amt, from_curr, to_curr)
...
 
@@ -58,32 +62,36 @@ class LedgerFormatter(Formatter):
 
    def __init__(self, rate, signed_currencies=(), base_fmt='#,##0.###',
 
                 rate_precision=5, denomination=None):
 
        super().__init__(rate, signed_currencies, base_fmt)
 
        self.rate_prec = rate_precision
 
        self.denomination = denomination
 

	
 
    def normalize_rate(self, rate):
 
    def normalize_rate(self, rate, prec=None):
 
        if prec is None:
 
            prec = self.rate_prec
 
        _, digits, exponent = rate.normalize().as_tuple()
 
        # Return ``self.rate_prec`` nonzero digits of precision, if available.
 
        prec = self.rate_prec - min(0, exponent + len(digits))
 
        # Return ``prec`` nonzero digits of precision, if available.
 
        prec -= min(0, exponent + len(digits))
 
        quant_to = '1.{}'.format('0' * prec)
 
        try:
 
            qrate = rate.quantize(decimal.Decimal(quant_to))
 
        except decimal.InvalidOperation:
 
            # The original rate doesn't have that much precision, so use it raw.
 
            qrate = rate
 
        return qrate.normalize()
 

	
 
    def format_rate(self, rate):
 
        return str(self.normalize_rate(rate))
 

	
 
    def format_ledger_rate(self, rate, curr):
 
        nrate = self.normalize_rate(rate)
 
        rate_s = self.format_currency(nrate, curr, currency_digits=False)
 
    def format_ledger_rate_raw(self, rate, curr):
 
        rate_s = self.format_currency(rate, curr, currency_digits=False)
 
        return "{{={0}}} @ {0}".format(rate_s)
 

	
 
    def format_ledger_rate(self, rate, curr):
 
        return self.format_ledger_rate_raw(self.normalize_rate(rate), curr)
 

	
 
    def format_rate_pair(self, from_curr, to_curr):
 
        from_amt = 1
 
        to_amt = self.rate.convert(from_amt, from_curr, to_curr)
 
        return "{} {} {}".format(
 
            from_amt, from_curr, self.format_ledger_rate(to_amt, to_curr))
 

	
...
 
@@ -97,15 +105,20 @@ class LedgerFormatter(Formatter):
 

	
 
    def format_denominated_rate(self, amount, currency, default_denomination):
 
        denomination = self._denomination_for(currency, default_denomination)
 
        amt_s = self.format_currency(amount, currency)
 
        if denomination is None:
 
            return amt_s
 
        else:
 
            rate = self.rate.convert(1, currency, denomination)
 
            return "{} {}".format(amt_s, self.format_ledger_rate(rate, denomination))
 
        full_rate = self.rate.convert(1, currency, denomination)
 
        to_amt = self.currency_decimal(amount * full_rate, denomination)
 
        for prec in itertools.count(self.rate_prec):
 
            rate = self.normalize_rate(full_rate, prec)
 
            got_amt = self.currency_decimal(amount * rate, denomination)
 
            if (got_amt == to_amt) or (rate == full_rate):
 
                break
 
        return "{} {}".format(amt_s, self.format_ledger_rate_raw(rate, denomination))
 

	
 
    def format_conversion(self, from_amt, from_curr, to_curr):
 
        to_amt = self.rate.convert(from_amt, from_curr, to_curr)
 
        return "{}\n{}".format(
 
            self.format_denominated_rate(from_amt, from_curr, to_curr),
 
            self.format_denominated_rate(to_amt, to_curr, None),
tests/historical1.json
Show inline comments
...
 
@@ -4,9 +4,10 @@
 
    "timestamp": 982342800,
 
    "base": "USD",
 
    "rates": {
 
        "AED": 3.67246,
 
        "ALL": 144.529793,
 
        "ANG": 1.79,
 
        "RUB": 57.0763,
 
        "USD": 1
 
    }
 
}
tests/test_historical.py
Show inline comments
...
 
@@ -143,6 +143,19 @@ def test_from_denomination(historical1_responder, output):
 
                          to_currency='ALL', amount=10,
 
                          ledger=True, denomination='USD')
 
    lines = lines_from_run(config, output)
 
    assert next(lines) == '$10.00\n'
 
    assert next(lines) == '1,445 ALL {=$0.006919} @ $0.006919\n'
 
    assert next(lines, None) is None
 

	
 
def test_rate_precision_added_as_needed(historical1_responder, output):
 
    config = build_config(historical1_responder, from_currency='RUB',
 
                          to_currency='USD', amount=63805,
 
                          ledger=True, denomination='USD')
 
    lines = lines_from_run(config, output)
 
    # 63,805 / 57.0763 (the RUB rate) == $1,117.89
 
    # But using the truncated rate: 63,805 * .01752 == $1,117.86
 
    # Make sure the rate is specified with enough precision to get the
 
    # correct conversion amount.
 
    assert next(lines) == '63,805.00 RUB {=$0.0175204} @ $0.0175204\n'
 
    assert next(lines) == '$1,117.89\n'
 
    assert next(lines, None) is None
0 comments (0 inline, 0 general)