From 9cf06a7d8850455338dc3aa438a9107098987ac9 Mon Sep 17 00:00:00 2001 From: Juliette James Date: Wed, 7 Feb 2024 11:29:52 +0000 Subject: [PATCH] QSTrader update version 0.2.4 (#386) * updated performance.py for future warnings and fixed get_loc deprecation * updated tearsheet.py for future warnings * Updated package requirements * updated tests * Updated version number --- CHANGELOG.md | 6 ++++++ README.md | 11 ++++++++--- qstrader/data/daily_bar_csv.py | 8 +++++--- qstrader/statistics/performance.py | 6 +++--- qstrader/statistics/tearsheet.py | 4 ++-- qstrader/system/rebalance/end_of_month.py | 2 +- requirements/base.txt | 8 ++++---- setup.py | 15 ++++++++------- tests/integration/trading/test_backtest_e2e.py | 5 +---- 9 files changed, 38 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d91248..b5b4c510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.2.4 + +* Fixed bug involving NaN at Timestamp in sixty_forty example. +* Removed support for python 3.7 and 3.8 +* Updated the python package requirements to work with matplotlib 3.8, numpy 1.26 and pandas 2.2.0 + # 0.2.3 * Updated the python package requirements to work with matplotlib 3.4, numpy 1.21 and pandas 1.3 diff --git a/README.md b/README.md index 5ba359fd..bd4a95f3 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,19 @@ Any issues with installation should be reported to the development team as issue The following command will create a brand new environment called `backtest`. ``` -conda create -n backtest +conda create -n backtest python +``` +This will use the conda default Python version. At time of writing this was Python 3.12. QSTrader currently supports Python 3.9, 3.10, 3.11 and 3.12. Optionally you can specify a python version by substituting python==3.9 into the command as follows: + +``` +conda create -n backtest python==3.9 ``` In order to start using QSTrader, you need to activate this new environment and install QSTrader using pip. ``` conda activate backtest -pip install qstrader +pip3 install qstrader ``` ## pip @@ -51,7 +56,7 @@ Alternatively, you can use [venv](https://docs.python.org/3/tutorial/venv.html#c ``` python -m venv backtest source backtest/bin/activate # Need to activate environment before installing package -pip install qstrader +pip3 install qstrader ``` # Full Documentation diff --git a/qstrader/data/daily_bar_csv.py b/qstrader/data/daily_bar_csv.py index 370038e5..5798f5fb 100644 --- a/qstrader/data/daily_bar_csv.py +++ b/qstrader/data/daily_bar_csv.py @@ -174,7 +174,7 @@ def _convert_bar_frame_into_bid_ask_df(self, bar_df): dp_df = seq_oc_df[['Date', 'Price']] dp_df['Bid'] = dp_df['Price'] dp_df['Ask'] = dp_df['Price'] - dp_df = dp_df.loc[:, ['Date', 'Bid', 'Ask']].fillna(method='ffill').set_index('Date').sort_index() + dp_df = dp_df.loc[:, ['Date', 'Bid', 'Ask']].ffill().set_index('Date').sort_index() return dp_df def _convert_bars_into_bid_ask_dfs(self): @@ -215,8 +215,9 @@ def get_bid(self, dt, asset): The bid price. """ bid_ask_df = self.asset_bid_ask_frames[asset] + bid_series = bid_ask_df.iloc[bid_ask_df.index.get_indexer([dt], method='pad')]['Bid'] try: - bid = bid_ask_df.iloc[bid_ask_df.index.get_loc(dt, method='pad')]['Bid'] + bid = bid_series.iloc[0] except KeyError: # Before start date return np.NaN return bid @@ -239,8 +240,9 @@ def get_ask(self, dt, asset): The ask price. """ bid_ask_df = self.asset_bid_ask_frames[asset] + ask_series = bid_ask_df.iloc[bid_ask_df.index.get_indexer([dt], method='pad')]['Ask'] try: - ask = bid_ask_df.iloc[bid_ask_df.index.get_loc(dt, method='pad')]['Ask'] + ask = ask_series.iloc[0] except KeyError: # Before start date return np.NaN return ask diff --git a/qstrader/statistics/performance.py b/qstrader/statistics/performance.py index 807120d2..5defcf95 100644 --- a/qstrader/statistics/performance.py +++ b/qstrader/statistics/performance.py @@ -9,7 +9,7 @@ def aggregate_returns(returns, convert_to): Aggregates returns by day, week, month, or year. """ def cumulate_returns(x): - return np.exp(np.log(1 + x).cumsum())[-1] - 1 + return np.exp(np.log(1 + x).cumsum()).iloc[-1] - 1 if convert_to == 'weekly': return returns.groupby( @@ -38,7 +38,7 @@ def create_cagr(equity, periods=252): periods - Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc. """ years = len(equity) / float(periods) - return (equity[-1] ** (1.0 / years)) - 1.0 + return (equity.iloc[-1] ** (1.0 / years)) - 1.0 def create_sharpe_ratio(returns, periods=252): @@ -89,7 +89,7 @@ def create_drawdowns(returns): # Calculate the drawdown and duration statistics perf = pd.DataFrame(index=idx) perf["Drawdown"] = (hwm - returns) / hwm - perf["Drawdown"].iloc[0] = 0.0 + perf.loc[perf.index[0], 'Drawdown'] = 0.0 perf["DurationCheck"] = np.where(perf["Drawdown"] == 0, 0, 1) duration = max( sum(1 for i in g if i == 1) diff --git a/qstrader/statistics/tearsheet.py b/qstrader/statistics/tearsheet.py index 74911561..9fced874 100644 --- a/qstrader/statistics/tearsheet.py +++ b/qstrader/statistics/tearsheet.py @@ -195,7 +195,7 @@ def format_perc(x, pos): # Strategy statistics returns = stats["returns"] cum_returns = stats['cum_returns'] - tot_ret = cum_returns[-1] - 1.0 + tot_ret = cum_returns.iloc[-1] - 1.0 cagr = perf.create_cagr(cum_returns, self.periods) sharpe = perf.create_sharpe_ratio(returns, self.periods) sortino = perf.create_sortino_ratio(returns, self.periods) @@ -205,7 +205,7 @@ def format_perc(x, pos): if bench_stats is not None: bench_returns = bench_stats["returns"] bench_cum_returns = bench_stats['cum_returns'] - bench_tot_ret = bench_cum_returns[-1] - 1.0 + bench_tot_ret = bench_cum_returns.iloc[-1] - 1.0 bench_cagr = perf.create_cagr(bench_cum_returns, self.periods) bench_sharpe = perf.create_sharpe_ratio(bench_returns, self.periods) bench_sortino = perf.create_sortino_ratio(bench_returns, self.periods) diff --git a/qstrader/system/rebalance/end_of_month.py b/qstrader/system/rebalance/end_of_month.py index b67c6acb..e3a75ba1 100644 --- a/qstrader/system/rebalance/end_of_month.py +++ b/qstrader/system/rebalance/end_of_month.py @@ -65,7 +65,7 @@ def _generate_rebalances(self): rebalance_dates = pd.date_range( start=self.start_dt, end=self.end_dt, - freq='BM' + freq='BME' ) rebalance_times = [ diff --git a/requirements/base.txt b/requirements/base.txt index 6e55d6cd..302f77be 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,5 +1,5 @@ Click>=7.0 -matplotlib==3.4.3 -numpy==1.21.2 -pandas==1.3.3 -seaborn==0.11.2 +matplotlib==3.8.2 +numpy==1.26.4 +pandas==2.2.0 +seaborn==0.13.2 diff --git a/setup.py b/setup.py index aebadd59..b905950b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="qstrader", - version="0.2.3", + version="0.2.4", description="QSTrader backtesting simulation engine", long_description=long_description, long_description_content_type="text/markdown", @@ -17,17 +17,18 @@ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], packages=find_packages(exclude=("tests",)), include_package_data=True, install_requires=[ "Click==7.1.2", - "matplotlib>=3.3.4", - "numpy>=1.18.4", - "pandas>=1.3.3", - "seaborn>=0.10.1" + "matplotlib>=3.8.2", + "numpy>=1.26.4", + "pandas>=2.2.0", + "seaborn>=0.13.2" ] ) diff --git a/tests/integration/trading/test_backtest_e2e.py b/tests/integration/trading/test_backtest_e2e.py index c6612e90..59e8c653 100644 --- a/tests/integration/trading/test_backtest_e2e.py +++ b/tests/integration/trading/test_backtest_e2e.py @@ -69,10 +69,7 @@ def test_backtest_sixty_forty(etf_filepath): # Pandas 1.1.5 and 1.2.0 very slightly for symbol in expected_dict.keys(): for metric in expected_dict[symbol].keys(): - assert pytest.approx( - portfolio_dict[symbol][metric], - expected_dict[symbol][metric] - ) + assert portfolio_dict[symbol][metric] == pytest.approx(expected_dict[symbol][metric]) def test_backtest_long_short_leveraged(etf_filepath):