A simple, hackable framework for backtesting investment strategies, geared towards simple strategies that can be executed by a retail investor. Written in plain Python and shell, with the only external dependencies being pandas and matplotlib. The goal is to enable a minimally proficient Python programmer to easily express and visualise investment strategies for personal investment.
Features include:
- Utilities for pulling and parsing tick data from free sources (AlphaVantage, Financial Modeling Prep) and index data (MSCI, S&P)
- Modeling of IBKR transaction fees (using fees for Singapore users)
- Simple dashboard for tweaking strategy parameters and observing their impact
First, register for the free data services of your choice and obtain API keys for them. Then, write these API keys, and the ticker symbols stocks whose data you wish to pull in, to Data/dataconfig.mk (following the example Data/example_dataconfig.mk). AlphaVantage and Financial Modeling Prep are supported out of the box; some tweaking of the Makefile would allow other sources to be supported. While in the Data/ directory, pull in the data with make (using make makes it easier to pull the data in batches, e.g. pulling part of the required data whenever your free-tier API key refreshes).
Install pandas and matplotlib:
python -m venv <VENV NAME>
source <VENV PATH>/bin/activate
pip install pandas matplotlib
Currently, the strategies are assumed to be parametrised by assigning weights to certain tickers. Specify the tickers to use in config.py (following the example example_config.py), then start
python -i investment.py
ipython and ipdb are recommended for hacking (set plots to blocking, i.e. plt.show(block=True), to use ipdb).
The framework is designed assuming that all information about a particular ticker is stored in one JSON file. The Makefile should be modified to ensure this (see, e.g., the implementation for FMP).
To use different data sources:
- Modify
Data/Makefileas mentioned above - Extend the
Extractorclass indata.py - Update the entry point in
investment.pyto use the newExtractor
As hinted above, the current design only supports strategies following a specific format:
- a
Strategyobject is initialised using the tick data, and an allocation of weights to tickers - the
Strategyobject'sbuysellfunction evaluates the price data at each time step, to decide whether to buy or to sell - If buying, calculate the total value of the portfolio, and calculate the fraction of that value which should go to that ticker. Purchase the corresponding number of shares.
- If selling, put any extra cash in a reserve fund (SGOV by default).
Trades use the closing price of the day, ignoring bid-ask spreads, and fractional shares are not supported. Margin interest is not accounted for, although buy orders will not be filled if there is a cash deficit and no reserve funds, which should limit the deficit.
The buysell function will be called in sequence over the range of dates which the strategy is being evaluated over. Management of any internal state is left to the implementation of the buysell function.
Currently, only two strategies are implemented, buy-and-hold and a simple momentum strategy. Additional strategies following this format can be implemented in strategy.py.
