From 244bd8dbc2cfcfd828e3138877e9b93634471e89 Mon Sep 17 00:00:00 2001 From: joel-becker Date: Wed, 28 Aug 2024 17:45:51 -0700 Subject: [PATCH] add CI --- .github/workflows/main.yml | 25 +++++++++++ requirements.txt | 90 +++----------------------------------- tests/personal_finance.py | 70 +++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 tests/personal_finance.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5d2fb1a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,25 @@ +name: Python package + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run tests + run: | + python -m unittest discover tests \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c197e41..4a3c931 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,84 +1,8 @@ -altair==5.0.1 -appdirs==1.4.4 -attrs==23.1.0 -backports.zoneinfo==0.2.1 -beautifulsoup4==4.12.2 -blinker==1.6.2 -cachetools==5.3.1 -certifi==2023.5.7 -cffi==1.15.1 -charset-normalizer==3.1.0 -click==8.1.3 -codecov==2.1.13 -contourpy==1.0.7 -coverage==7.2.7 -cryptography==41.0.1 -cycler==0.11.0 -decorator==5.1.1 -dill==0.3.6 -exceptiongroup==1.1.1 -fonttools==4.39.4 -fredapi==0.5.0 -frozendict==2.3.8 -gitdb==4.0.10 -GitPython==3.1.31 -html5lib==1.1 -idna==3.4 -importlib-metadata==6.6.0 -importlib-resources==5.12.0 -iniconfig==2.0.0 -Jinja2==3.1.2 -jsonschema==4.17.3 -kiwisolver==1.4.4 -lxml==4.9.2 -markdown-it-py==2.2.0 -MarkupSafe==2.1.3 -matplotlib==3.7.1 -mdurl==0.1.2 -msgspec==0.15.1 -multiprocess==0.70.14 -multitasking==0.0.11 -numpy==1.24.3 -packaging==23.1 -pandas==2.0.2 -pathos==0.3.0 -Pillow==9.5.0 -pkgutil_resolve_name==1.3.10 -pluggy==1.0.0 -pox==0.3.2 -ppft==1.7.6.6 -protobuf==4.23.2 -pyarrow==12.0.0 -pycparser==2.21 -pydeck==0.8.1b0 -Pygments==2.15.1 -Pympler==1.0.1 -pyparsing==3.0.9 -pyrsistent==0.19.3 -pytest==7.3.1 -pytest-cov==4.1.0 -python-dateutil==2.8.2 -pytz==2023.3 -pytz-deprecation-shim==0.1.0.post0 -requests==2.31.0 -rich==13.4.1 -scipy==1.10.1 -six==1.16.0 -smmap==5.0.0 -soupsieve==2.4.1 -squigglepy==0.25 -streamlit==1.23.1 -tenacity==8.2.2 +fredapi==0.5.2 +matplotlib==3.9.2 +numpy==2.1.0 +pandas==2.2.2 +squigglepy==0.28 +streamlit==1.38.0 toml==0.10.2 -tomli==2.0.1 -toolz==0.12.0 -tornado==6.3.2 -tqdm==4.65.0 -typing_extensions==4.6.3 -tzdata==2023.3 -tzlocal==4.3 -urllib3==2.0.2 -validators==0.20.0 -webencodings==0.5.1 -yfinance==0.2.18 -zipp==3.15.0 +yfinance==0.2.43 diff --git a/tests/personal_finance.py b/tests/personal_finance.py new file mode 100644 index 0000000..51a48bc --- /dev/null +++ b/tests/personal_finance.py @@ -0,0 +1,70 @@ +import unittest +import numpy as np +from models.personal_finance import PersonalFinanceModel + +class TestPersonalFinanceModel(unittest.TestCase): + def setUp(self): + # Set up a basic model for testing + self.input_params = { + "m": 1000, + "years": 40, + "r": 0.05, + "years_until_retirement": 30, + "years_until_death": 60, + "retirement_income": 50000, + "income_path": None, # You'll need to provide a mock or real income path + "min_income": 20000, + "inflation_rate": 0.02, + "ar_inflation_coefficients": [0.5], + "ar_inflation_sd": 0.01, + "min_cash_threshold": 5000, + "max_cash_threshold": 20000, + "cash_start": 10000, + "market_start": 50000, + "tax_region": "US", + "portfolio_weights": [1.0], # Simplified for testing + "asset_returns": [0.07], + "asset_volatilities": [0.15], + "asset_correlations": [[1.0]] + } + self.model = PersonalFinanceModel(self.input_params) + + def test_initialization(self): + self.assertEqual(self.model.m, 1000) + self.assertEqual(self.model.years, 40) + self.assertEqual(self.model.r, 0.05) + + def test_generate_market_returns(self): + returns = self.model.generate_market_returns() + self.assertEqual(returns.shape, (1000, 40)) + self.assertTrue(np.all(returns > -1)) # Returns should be greater than -100% + + def test_generate_ar_inflation(self): + inflation = self.model.generate_ar_inflation() + self.assertEqual(inflation.shape, (1000, 40)) + self.assertTrue(np.all(inflation > 0)) # Inflation should be positive + + def test_simulate(self): + self.model.simulate() + results = self.model.get_results() + + # Check that all result arrays have the correct shape + for key, value in results.items(): + self.assertEqual(value.shape, (1000, 40), f"{key} has incorrect shape") + + # Check that financial wealth is non-negative + self.assertTrue(np.all(results['financial_wealth'] >= 0)) + + # Check that consumption is always positive + self.assertTrue(np.all(results['consumption'] > 0)) + + def test_calculate_charitable_donations(self): + self.model.charitable_giving_rate = 0.05 + self.model.charitable_giving_cap = 10000 + total_real_income = np.array([100000, 200000, 300000]) + donations = self.model.calculate_charitable_donations(0, total_real_income) + expected_donations = np.array([5000, 10000, 10000]) # Cap should be applied to last value + np.testing.assert_array_almost_equal(donations, expected_donations) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file