Beispiel #1
0
    def test_quantize_with_tolerance(self):
        tolerances = defdict.ImmutableDictWithDefault({'USD': D('0.01')},
                                                      default=D('0.000005'))
        self.assertEqual(
            D('100.12'),
            interpolate.quantize_with_tolerance(tolerances, 'USD', D('100.123123123')))
        self.assertEqual(
            D('100.12312'),
            interpolate.quantize_with_tolerance(tolerances, 'CAD', D('100.123123123')))

        tolerances = defdict.ImmutableDictWithDefault({'USD': D('0.01')}, default=ZERO)
        self.assertEqual(
            D('100.12'),
            interpolate.quantize_with_tolerance(tolerances, 'USD', D('100.123123123')))
        self.assertEqual(
            D('100.123123123'),
            interpolate.quantize_with_tolerance(tolerances, 'CAD', D('100.123123123')))
Beispiel #2
0
    def test_dict_with_default(self):
        init_value = {'a': 1, 'b': 2}
        dwd = defdict.ImmutableDictWithDefault(100, init_value)
        self.assertEqual(init_value, dwd)

        self.assertEqual(1, dwd['a'])
        self.assertEqual(2, dwd['b'])
        self.assertEqual(1, dwd.get('a'))
        self.assertEqual(2, dwd.get('b'))

        self.assertEqual(100, dwd['c'])
        self.assertEqual(100, dwd.get('c'))
        self.assertEqual(init_value, dwd)

        with self.assertRaises(NotImplementedError):
            dwd['c'] = 17
Beispiel #3
0
def infer_tolerances(postings, options_map, use_cost=None):
    """Infer tolerances from a list of postings.

    The tolerance is the maximum fraction that is being used for each currency
    (a dict). We use the currency of the weight amount in order to infer the
    quantization precision for each currency. Integer amounts aren't
    contributing to the determination of precision.

    The 'use_cost' option allows one to experiment with letting postings at cost
    and at price influence the maximum value of the tolerance. It's tricky to
    use and alters the definition of the tolerance in a non-trivial way, if you
    use it. The tolerance is expanded by the sum of the cost times a fraction 'M'
    of the smallest digits in the number of units for all postings held at cost.

    For example, in this transaction:

        2006-01-17 * "Plan Contribution"
          Assets:Investments:VWELX 18.572 VWELX {30.96 USD}
          Assets:Investments:VWELX 18.572 VWELX {30.96 USD}
          Assets:Investments:Cash -1150.00 USD

    The tolerance for units of USD will calculated as the MAXIMUM of:

      0.01 * M = 0.005 (from the 1150.00 USD leg)

      The sum of
        0.001 * M x 30.96 = 0.01548 +
        0.001 * M x 30.96 = 0.01548
                          = 0.03096

    So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices
    contribute similarly to the maximum tolerance allowed.

    Note that 'M' above is the inferred_tolerance_multiplier and its default
    value is 0.5.

    Args:
      postings: A list of Posting instances.
      options_map: A dict of options.
      use_cost: A boolean, true if we should be using a combination of the smallest
        digit of the number times the cost or price in order to infer the tolerance.
        If the value is left unspecified (as 'None'), the default value can be
        overridden by setting an option.
    Returns:
      A dict of currency to the tolerated difference amount to be used for it,
      e.g. 0.005.
    """
    if use_cost is None:
        use_cost = options_map["infer_tolerance_from_cost"]

    inferred_tolerance_multiplier = options_map["inferred_tolerance_multiplier"]

    default_tolerances = options_map["inferred_tolerance_default"]
    tolerances = default_tolerances.copy()

    cost_tolerances = collections.defaultdict(D)
    for posting in postings:
        # Skip the precision on automatically inferred postings.
        if posting.meta and AUTOMATIC_META in posting.meta:
            continue
        units = posting.units
        if not (isinstance(units, Amount) and isinstance(units.number, Decimal)):
            continue

        # Compute bounds on the number.
        currency = units.currency
        expo = units.number.as_tuple().exponent
        if expo < 0:
            # Note: the exponent is a negative value.
            tolerance = ONE.scaleb(expo) * inferred_tolerance_multiplier
            tolerances[currency] = max(tolerance,
                                       tolerances.get(currency, -1024))

            if not use_cost:
                continue

            # Compute bounds on the smallest digit of the number implied as cost.
            cost = posting.cost
            if cost is not None:
                cost_currency = cost.currency
                if isinstance(cost, Cost):
                    cost_tolerance = min(tolerance * cost.number, MAXIMUM_TOLERANCE)
                else:
                    assert isinstance(cost, CostSpec)
                    cost_tolerance = MAXIMUM_TOLERANCE
                    for cost_number in cost.number_total, cost.number_per:
                        if cost_number is None or cost_number is MISSING:
                            continue
                        cost_tolerance = min(tolerance * cost_number, cost_tolerance)
                cost_tolerances[cost_currency] += cost_tolerance

            # Compute bounds on the smallest digit of the number implied as cost.
            price = posting.price
            if isinstance(price, Amount) and isinstance(price.number, Decimal):
                price_currency = price.currency
                price_tolerance = min(tolerance * price.number, MAXIMUM_TOLERANCE)
                cost_tolerances[price_currency] += price_tolerance

    for currency, tolerance in cost_tolerances.items():
        tolerances[currency] = max(tolerance, tolerances.get(currency, -1024))

    default = tolerances.pop('*', ZERO)
    return defdict.ImmutableDictWithDefault(tolerances, default=default)
Beispiel #4
0
 def test_pickle_defdict(self):
     dwd = defdict.ImmutableDictWithDefault(100, {'a': 1, 'b': 2})
     pick = pickle.dumps(dwd)
     dwd2 = pickle.loads(pick)
     self.assertEqual(dwd, dwd2)