Yeah, you heard that right.
This is part 6 of my Building Stocksimpy series, where I build a light-weight Python backtesting library, sharing every step along the way.
In the last post, I explained why I created Portfolio
class: to keep everything organised and divide the load between classes. Portfolio
supports the main backtesting loop, which is what I’ll dive into today.
Strategy
Backtester
class is simple by design, and it should be. The strategy
—deciding whether to buy or sell — is provided when the class is initialized. My initial thought was to have strategy
as a simple function returning buy
, sell
, or hold
. And that's the way it works in the code right now.
However, some strategies may require multiple calculations and calling a bunch of different functions to calculate whether it is worth selling or not. So I might pivot to allowing either a function or a class. And if it is a class, I can decide on a signal_generator()
that can still be used to generate the signal.
This approach offers more flexibility, but since I am keeping things relatively lightweight, I will revisit this in the future.
Main Loop
The core loop is literally a simple for
loop iterating over the length of the data. At first, I worried about performance — would processing large datasets take forever? Running 5–10 years of daily data took about 2 minutes on my machine, which is acceptable for a fully Python implementation.
High-performance backtesting libraries often rely on C/C++ and create Python bindings. So I wasn’t expecting a performance similar to those top libraries, but still, it shouldn’t take hours to complete.
But something I didn’t look too much into was Pandas
built-in functions. I could’ve used strategy
to precompute signals beforehand and storing them in a Pandas.Series.
Then I could run a for loop for the dates where there is a trade. This would have been much more efficient, and I might implement it in the future.
Fixed vs. Dynamic Backtesting
I’ve built two run_backtest()
functions to run it. Why? I wanted to keep things simple while also offering flexibility. I wanted to allow users to just trade a fix amount. But then I realised this can get very limiting, even for simple testing purposes.
I wanted to write a strategy that sells half of the owned stock tied to a logic, but this was not possible by only getting a signal. So I created run_backtest_dynamic()
to allow returning how many stocks to buy, as well as a run_backtest_fixed()
to still allow for a custom amount of money per trade.
What’s Next
I’ve considered adding features like running multiple strategies simultaneously and more advanced metrics. One comment reminded me of risk-adjusted performance measures — without metrics like Sharpe ratio or drawdowns, Stocksimpy is just a toy. Implementing these will be the focus of the next post.
Visualization and documentation cleanup are still on the roadmap, but risk-adjusted calculations take priority. Stay tuned.
Please check out Stocksimpy Github and give any feedback that you see appropriate. I hope this project helps out instead of wrestling with complex libraries. See you in the next post 👋:
GitHub - SuleymanSade/StockSimPy: A lightweight Python library for backtesting stock strategies
Also check out my socials and let's connect:
- 🧠 Follow me on Twitter / X
- 🔷 I’m now on Bluesky
- 📰 Or read more of my posts on Medium
- 💬 Let’s connect on LinkedIn
Top comments (0)