repository for 46-902 final project
Yichen (Sam) Hu
andrewid: samhu
-
This backtester module enables users to flexibly test out their trading strategies by easily swapping
Signal,Exec,Evalcomponents.- Imagine the
Signalmodule as a quantitative researcher, who uses past market data to provide their estimate about where future prices will be. - Imagine the
Execmodule as a trader, who utilizes the future price estimate to buy/sell the assets at an optimal quantity and moment. - Imagine the
Evalmodule as a portfolio manager, who looks over the past trading records and comprehensively analyze how well the team is doing, beyond just profit and losses.
- Imagine the
-
This backtester contain numerous built-in
Sign,Exec,Evalfor users to experiment with. The API for these components are also Dev-friendly, and users can create their own components at ease! -
This backtester support multipler trading strategies to be tested concurrently over a universe of tickers -- so users can test the optimal setup to form a viable strategy.
-
This backtester supports per-strategy and across-strategy trade summary, evaluation, visualization, and local storage -- so users can get the most useful feedback from a backtest.
-
This backtester by default uses Yahoo Finance historical price series, but it will support user-provided dataset in the future.
- Start by ensuring your dependencies are up to date by
pip install uvand runuv sync - In your python script, simply follow the below approach to begin backtesting!:
from backtester.engine import BacktestEngine
mybt = BacktestEngine(tickers='TSLA', start='2020-01-01', end='2025-01-01')
from backtester.trader import Trader
from backtester.signal import LastPriceChange
from backtester.execution import DirectionalExec
from backtester.eval import PnL, SharpeRatioCash, MaxDrawdown
trader = Trader(signal=LastPriceChange(tickers='TSLA'),
execution=DirectionalExec(tradeable_tickers='TSLA'),
eval=[PnL(), SharpeRatioCash(), MaxDrawdown()])
mybt.add_trader(trader)
mybt.run()
mybt.summary()You should expect the following output in your CLI:
Backtesting: 100%|████████████████████████| 1258/1258 [00:01<00:00, 1048.52it/s]
============================================================
Trader 1
Signal: LastPriceChange
Execution: DirectionalExec; market orders with atclose filling
Eval: ['PnL', 'SharpeRatioCash', 'MaxDrawdown']
Final timestamp: 2024-12-31 00:00:00
Final cash: 3660595.34
Final positions:
TSLA: -4802.2181
Final eval metrics:
PnL: 1721267.6123
SharpeRatioCash: 0.0402
MaxDrawdown: 810270.2322
B. Tuning your Signal (see details of various signals you can choose from and their respective param schemes in signal.py):
mybt = BacktestEngine(tickers='TSLA', start='2020-01-01', end='2025-01-01')
# traderA: exact same as above
traderA = Trader(signal=LastPriceChange(tickers='TSLA'),
execution=DirectionalExec(tradeable_tickers='TSLA'),
eval=[PnL(), SharpeRatioCash(), MaxDrawdown()])
mybt.add_trader(traderA)
# traderB: same stategy as A, but tuned behavior
traderB = Trader(signal=LastPriceChange(tickers='TSLA',
confidence_max=0.8,
visibility=20),
execution=DirectionalExec(tradeable_tickers='TSLA',
sensitivity=0.5),
eval=[PnL(), SharpeRatioCash(), MaxDrawdown()])
mybt.add_trader(traderB)Different parameters can lead to different performances:
Backtesting: 100%|█████████████████████████| 1258/1258 [00:02<00:00, 534.91it/s]
============================================================
Trader 1
Signal: LastPriceChange
Execution: DirectionalExec; market orders with atclose filling
Eval: ['PnL', 'SharpeRatioCash', 'MaxDrawdown']
Final timestamp: 2024-12-31 00:00:00
Final cash: 3660595.34
Final positions:
TSLA: -4802.2181
Final eval metrics:
PnL: 1721267.6123
SharpeRatioCash: 0.0402
MaxDrawdown: 810270.2322
============================================================
Trader 2
Signal: LastPriceChange
Execution: DirectionalExec; market orders with atclose filling
Eval: ['PnL', 'SharpeRatioCash', 'MaxDrawdown']
Final timestamp: 2024-12-31 00:00:00
Final cash: 5582682.83
Final positions:
TSLA: -9123.3121
Final eval metrics:
PnL: 1898324.4899
SharpeRatioCash: 0.0305
MaxDrawdown: 965469.4280
You can also show visualizations of trader's performance across selected metrics
mybt.run()
mybt.eval_summary()tickers = ['TSLA', 'NVDA', 'AAPL', 'META']
mybt = BacktestEngine(tickers= tickers, start='2023-01-01', end='2025-01-01')
# first group of traders: runs exponentially weighted mean reversion
from backtester.signal import ExponentialMeanReversion
from backtester.execution import DirectionalExec
for decay in np.linspace(0.5, 1, 10):
trader = Trader(signal=ExponentialMeanReversion(tickers=tickers, decay=decay),
execution=DirectionalExec(tradeable_tickers=tickers),
eval=[PnL(), ])
mybt.add_trader(trader, verbose=verbose)
# second group of traders: run bar-midpoint based fair price signal with a different Exec subclass
from backtester.signal import BarMA
from backtester.execution import EdgeExec
for windowsize in range(10, 110, 10):
trader = Trader(signal=BarMA(tickers=tickers, window = windowsize),
execution=EdgeExec(tradeable_tickers=tickers, order_style='market', fill_style='immediate'),
eval=[PnL(), ])
mybt.add_trader(trader, verbose=verbose)
mybt.run()
mybt.eval_summary()mybt.save_logs('./my_folder')Saved logs for Trader 1 to ./test_output_presentation/trader_1_logs.csv
Saved logs for Trader 2 to ./test_output_presentation/trader_2_logs.csv
Saved logs for Trader 3 to ./test_output_presentation/trader_3_logs.csv
...


