|
6 | 6 | import numpy as np |
7 | 7 | import pandas as pd |
8 | 8 | import pytest |
| 9 | +from hypothesis import given, settings |
| 10 | +from hypothesis import strategies as st |
| 11 | +from hypothesis.extra.numpy import arrays |
9 | 12 |
|
10 | 13 | from pycharting.data.ingestion import DataManager, DataValidationError, validate_input |
11 | 14 |
|
@@ -779,3 +782,80 @@ def test_get_chunk_data_types(self): |
779 | 782 |
|
780 | 783 | # Individual values should be Python numbers |
781 | 784 | assert isinstance(chunk["open"][0], (int, float)) |
| 785 | + |
| 786 | + |
| 787 | +# --------------------------------------------------------------------------- |
| 788 | +# Property-based tests — run by `make hypothesis-test` (-m "hypothesis or property") |
| 789 | +# and, being under tests/pycharting/, also by the regular `make test` run. |
| 790 | +# |
| 791 | +# validate_input's contract is stated as invariants rather than examples: every |
| 792 | +# output is a length-n ndarray, length disagreement always raises, and the |
| 793 | +# single-vs-multi series mode is decided purely by how many of OHLC are present. |
| 794 | +# Those are properties, so they are tested as properties. |
| 795 | +# --------------------------------------------------------------------------- |
| 796 | + |
| 797 | + |
| 798 | +@pytest.mark.property |
| 799 | +@given( |
| 800 | + values=arrays( |
| 801 | + dtype=np.float64, |
| 802 | + shape=st.integers(min_value=1, max_value=200), |
| 803 | + elements=st.floats(min_value=-1e6, max_value=1e6, allow_nan=False), |
| 804 | + ), |
| 805 | +) |
| 806 | +@settings(max_examples=50, deadline=None) |
| 807 | +def test_close_only_input_always_normalizes_to_line_mode(values): |
| 808 | + """Any single series is mapped to `close`, leaving open/high/low unset.""" |
| 809 | + result = validate_input(np.arange(len(values)), close=values) |
| 810 | + |
| 811 | + assert isinstance(result["close"], np.ndarray) |
| 812 | + assert len(result["close"]) == len(values) |
| 813 | + assert result["open"] is None |
| 814 | + assert result["high"] is None |
| 815 | + assert result["low"] is None |
| 816 | + |
| 817 | + |
| 818 | +@pytest.mark.property |
| 819 | +@given( |
| 820 | + n=st.integers(min_value=1, max_value=200), |
| 821 | + delta=st.integers(min_value=1, max_value=20), |
| 822 | +) |
| 823 | +@settings(max_examples=50, deadline=None) |
| 824 | +def test_length_mismatch_always_raises(n, delta): |
| 825 | + """A series whose length differs from the index is always rejected.""" |
| 826 | + index = np.arange(n) |
| 827 | + close = np.zeros(n + delta) |
| 828 | + |
| 829 | + with pytest.raises(DataValidationError, match="does not match index length"): |
| 830 | + validate_input(index, close=close) |
| 831 | + |
| 832 | + |
| 833 | +@pytest.mark.property |
| 834 | +@given( |
| 835 | + values=arrays( |
| 836 | + dtype=np.float64, |
| 837 | + shape=st.integers(min_value=1, max_value=100), |
| 838 | + elements=st.floats(min_value=1.0, max_value=1e5, allow_nan=False), |
| 839 | + ), |
| 840 | +) |
| 841 | +@settings(max_examples=50, deadline=None) |
| 842 | +def test_ohlc_high_is_never_below_low(values): |
| 843 | + """With open and close supplied, the auto-filled high never falls below low.""" |
| 844 | + result = validate_input(np.arange(len(values)), open=values, close=values + 1.0) |
| 845 | + |
| 846 | + assert np.all(result["high"] >= result["low"]) |
| 847 | + |
| 848 | + |
| 849 | +@pytest.mark.property |
| 850 | +@given( |
| 851 | + values=st.lists(st.floats(min_value=-1e6, max_value=1e6, allow_nan=False), min_size=1, max_size=100), |
| 852 | +) |
| 853 | +@settings(max_examples=50, deadline=None) |
| 854 | +def test_list_series_and_ndarray_series_agree(values): |
| 855 | + """Passing a list and the equivalent ndarray produce the same normalized output.""" |
| 856 | + index = np.arange(len(values)) |
| 857 | + |
| 858 | + from_list = validate_input(index, close=values) |
| 859 | + from_array = validate_input(index, close=np.array(values, dtype=np.float64)) |
| 860 | + |
| 861 | + assert np.array_equal(from_list["close"], from_array["close"]) |
0 commit comments