|
1 | 1 | import math
|
2 | 2 | from decimal import Decimal
|
3 | 3 |
|
| 4 | +import hypothesis.extra.numpy as npst |
| 5 | +import hypothesis.strategies as st |
| 6 | + |
4 | 7 | # Don't use 'import numpy as np', to avoid accidentally testing
|
5 | 8 | # the versions in numpy instead of numpy_financial.
|
6 | 9 | import numpy
|
7 | 10 | import pytest
|
| 11 | +from hypothesis import given, settings |
8 | 12 | from numpy.testing import (
|
9 | 13 | assert_,
|
10 | 14 | assert_allclose,
|
|
15 | 19 | import numpy_financial as npf
|
16 | 20 |
|
17 | 21 |
|
| 22 | +def float_dtype(): |
| 23 | + return npst.floating_dtypes(sizes=[32, 64], endianness="<") |
| 24 | + |
| 25 | + |
| 26 | +def int_dtype(): |
| 27 | + return npst.integer_dtypes(sizes=[32, 64], endianness="<") |
| 28 | + |
| 29 | + |
| 30 | +def uint_dtype(): |
| 31 | + return npst.unsigned_integer_dtypes(sizes=[32, 64], endianness="<") |
| 32 | + |
| 33 | + |
| 34 | +real_scalar_dtypes = st.one_of(float_dtype(), int_dtype(), uint_dtype()) |
| 35 | + |
| 36 | + |
| 37 | +cashflow_array_strategy = npst.arrays( |
| 38 | + dtype=real_scalar_dtypes, |
| 39 | + shape=npst.array_shapes(min_dims=1, max_dims=2, min_side=0, max_side=25), |
| 40 | +) |
| 41 | +cashflow_list_strategy = cashflow_array_strategy.map(lambda x: x.tolist()) |
| 42 | + |
| 43 | +cashflow_array_like_strategy = st.one_of( |
| 44 | + cashflow_array_strategy, |
| 45 | + cashflow_list_strategy, |
| 46 | +) |
| 47 | + |
| 48 | +short_scalar_array = npst.arrays( |
| 49 | + dtype=real_scalar_dtypes, |
| 50 | + shape=npst.array_shapes(min_dims=0, max_dims=1, min_side=0, max_side=5), |
| 51 | +) |
| 52 | + |
| 53 | + |
18 | 54 | def assert_decimal_close(actual, expected, tol=Decimal("1e-7")):
|
19 | 55 | # Check if both actual and expected are iterable (like arrays)
|
20 | 56 | if hasattr(actual, "__iter__") and hasattr(expected, "__iter__"):
|
@@ -244,11 +280,27 @@ def test_npv(self):
|
244 | 280 | rtol=1e-2,
|
245 | 281 | )
|
246 | 282 |
|
247 |
| - def test_npv_decimal(self): |
248 |
| - assert_equal( |
249 |
| - npf.npv(Decimal("0.05"), [-15000, 1500, 2500, 3500, 4500, 6000]), |
250 |
| - Decimal("122.894854950942692161628715"), |
251 |
| - ) |
| 283 | + @given(rates=short_scalar_array, values=cashflow_array_strategy) |
| 284 | + @settings(deadline=None) |
| 285 | + def test_fuzz(self, rates, values): |
| 286 | + npf.npv(rates, values) |
| 287 | + |
| 288 | + @pytest.mark.parametrize("rates", ([[1, 2, 3]], numpy.empty(shape=(1, 1, 1)))) |
| 289 | + def test_invalid_rates_shape(self, rates): |
| 290 | + cashflows = [1, 2, 3] |
| 291 | + with pytest.raises(ValueError): |
| 292 | + npf.npv(rates, cashflows) |
| 293 | + |
| 294 | + @pytest.mark.parametrize("cf", ([[[1, 2, 3]]], numpy.empty(shape=(1, 1, 1)))) |
| 295 | + def test_invalid_cashflows_shape(self, cf): |
| 296 | + rates = [1, 2, 3] |
| 297 | + with pytest.raises(ValueError): |
| 298 | + npf.npv(rates, cf) |
| 299 | + |
| 300 | + @pytest.mark.parametrize("rate", (-1, -1.0)) |
| 301 | + def test_rate_of_negative_one_returns_nan(self, rate): |
| 302 | + cashflow = numpy.arange(5) |
| 303 | + assert numpy.isnan(npf.npv(rate, cashflow)) |
252 | 304 |
|
253 | 305 |
|
254 | 306 | class TestPmt:
|
@@ -336,68 +388,6 @@ def test_mirr(self, values, finance_rate, reinvest_rate, expected):
|
336 | 388 | else:
|
337 | 389 | assert_(numpy.isnan(result))
|
338 | 390 |
|
339 |
| - @pytest.mark.parametrize("number_type", [Decimal, float]) |
340 |
| - @pytest.mark.parametrize( |
341 |
| - "args, expected", |
342 |
| - [ |
343 |
| - ( |
344 |
| - { |
345 |
| - "values": [ |
346 |
| - "-4500", |
347 |
| - "-800", |
348 |
| - "800", |
349 |
| - "800", |
350 |
| - "600", |
351 |
| - "600", |
352 |
| - "800", |
353 |
| - "800", |
354 |
| - "700", |
355 |
| - "3000", |
356 |
| - ], |
357 |
| - "finance_rate": "0.08", |
358 |
| - "reinvest_rate": "0.055", |
359 |
| - }, |
360 |
| - "0.066597175031553548874239618", |
361 |
| - ), |
362 |
| - ( |
363 |
| - { |
364 |
| - "values": ["-120000", "39000", "30000", "21000", "37000", "46000"], |
365 |
| - "finance_rate": "0.10", |
366 |
| - "reinvest_rate": "0.12", |
367 |
| - }, |
368 |
| - "0.126094130365905145828421880", |
369 |
| - ), |
370 |
| - ( |
371 |
| - { |
372 |
| - "values": ["100", "200", "-50", "300", "-200"], |
373 |
| - "finance_rate": "0.05", |
374 |
| - "reinvest_rate": "0.06", |
375 |
| - }, |
376 |
| - "0.342823387842176663647819868", |
377 |
| - ), |
378 |
| - ( |
379 |
| - { |
380 |
| - "values": ["39000", "30000", "21000", "37000", "46000"], |
381 |
| - "finance_rate": "0.10", |
382 |
| - "reinvest_rate": "0.12", |
383 |
| - }, |
384 |
| - numpy.nan, |
385 |
| - ), |
386 |
| - ], |
387 |
| - ) |
388 |
| - def test_mirr_decimal(self, number_type, args, expected): |
389 |
| - values = [number_type(v) for v in args["values"]] |
390 |
| - result = npf.mirr( |
391 |
| - values, |
392 |
| - number_type(args["finance_rate"]), |
393 |
| - number_type(args["reinvest_rate"]), |
394 |
| - ) |
395 |
| - |
396 |
| - if expected is not numpy.nan: |
397 |
| - assert_decimal_close(result, number_type(expected), tol=1e-15) |
398 |
| - else: |
399 |
| - assert numpy.isnan(result) |
400 |
| - |
401 | 391 | def test_mirr_no_real_solution_exception(self):
|
402 | 392 | # Test that if there is no solution because all the cashflows
|
403 | 393 | # have the same sign, then npf.mirr returns NoRealSolutionException
|
|
0 commit comments