▶ Full Post Text
I took into account the feedback from last week’s post (found [here](https://www.reddit.com/r/algotrading/comments/1q5op3l/backtested_16000_retail_trading_strategies_how_do/)). I’m trying to figure out how to be more rigorous about my testing, and below are the steps I took to try to mitigate biases in both the data and the process.
To recap, last week I wrote that I’ve been running about 16k backtests per day (80 strategies × 50 symbols × 4 timeframes).
The 80 strategies span different types of mean reversion, momentum, and some ICT-style concepts.
The 50 symbols are a mix of highly liquid names plus some recent trending symbols pulled from various subreddits.
The 4 timeframes are 4h, 1h, 15m, and 5m bars. I deliberately avoided 1m bars because trading them would be much harder in practice. Alpha decay becomes a real issue at that frequency, and I’m intentionally trying to avoid strategies that rely on ultra-low-latency execution.
For the portfolio backtests, the setup was:
Initial cash of $100,000
Bet size of $5k
Max 20 concurrent bets
No parameter tuning
Long-only
ISSUE #1: Survivorship Bias
I initially ran the strategies starting from January 2020 and quickly realized I was introducing survivorship bias because the symbols were chosen based on what exists today. If you take today’s symbols and go back in time, you’re implicitly filtering for companies that survived until now.
What I needed to do instead was recalculate the opportunity set before the trade dates using historical volume data.
I defined daily dollar volume as closing price times daily volume, where daily volume is the sum of volume from 1m bars. Liquidity rank was based on a 7-day rolling average of daily dollar volume.
For simplicity, I recalculated liquidity ranks quarterly. So my 100-symbol universe is being recomputed every quarter based on the data available at that time.
ISSUE #2: Lack of Regime Variety / Short History
Initially, I kept the lookback window short to see if I could detect strategies that worked in the most recent period. But as some of you pointed out in the previous post, that’s not very robust.
I first went back about 10 years to cover a few different regimes. Then I figured I might as well test all the data I had access to. I loaded all available OHLCV history from Massive, which went back to around the end of 2003.
Because long backtests on hourly data take a while, I only took the strategies that performed best in the recent period and then tested those against the liquidity-ranked universe across the full 22-year history to see if they held up.
ISSUE #3: Liquidity Concentration Bias
The third issue was that maybe these strategies only worked on the most liquid names.
To test that, I took the liquidity rankings and divided them into deciles of 100 symbols each, covering the top 1000 liquid stocks at each quarterly rebalance. I then ran the strategies against each liquidity bucket separately to see how sensitive they were to liquidity.
Some strategies held up across multiple buckets. Many did not.
ISSUE #4: Corporate Actions Mishandling
I started seeing random spikes of amazing performance. A $5,000 bet would suddenly show a $45k gain in a day. That obviously didn’t make sense.
It turned out I wasn’t adjusting for reverse splits, like 10-for-1 reverse splits (Citibank being a good example). Massive’s historical OHLCV bars aren’t split-adjusted by default, and you have to handle that yourself.
Once I corrected for splits and reverse splits, performance came down a bit, which was expected. I think my earlier short tests just didn’t run into many corporate actions, so this issue didn’t show up at first.
ISSUE #5: Execution Bias (too optimistic)
Originally, when a signal triggered, I used the open price of the next bar if it was lower than the limit price, and then applied a naive 5bps slippage. Realistically, I wouldn’t be able to consistently get the open.
So instead, I moved execution to the next 1-minute bar after the signal triggered. For buys, I used the higher of the close or high of that bar. For sells, I used the lower of the close or low.
Even that might still be optimistic. I’m considering something like using the VWAP of the next 5 minutes after a signal instead. Got any suggestions for this?
A couple of interesting things I noticed along the way.
Because the liquidity-ranked universe sometimes included short ETFs, the portfolio naturally picked up some downside exposure during market downturns, which actually helped. In other words my Long-only strategy picked up some short exposure unintentionally.
Also, I originally evaluated stops on 1-hour bars. That turned out to be a big mistake. One hour is a long time, and trades could have hit stops mid-bar without being detected.
When I switched to evaluating stops on 1-minute bars, trade counts went up significantly, but performance improved as well due to many more at-bats. On average, this resulted in about 50 trades per week. Entries are still based on non-overlapping 1-hour bars.
Next steps.
After identifying a handful of strategies that seem to hold up over a long history, across multiple liquidity buckets and multiple regimes, I’m moving to paper trading to get a true out-of-sample result.
I’ve frozen the strategy set, symbol universe logic, and execution assumptions. That is unless you guys find more flaws. I plan to run this for about a month to see whether there’s any real alpha here, beyond just backtest results.
Questions for the group.
1. Should I be using limit orders to execute these strategies (Alpaca seems to only do limit orders with paper trading), or is it more realistic to assume market orders?
2. How should I be modeling slippage and transaction costs at this frequency?
3. Does this transition from large-scale sweeps to paper trading the strategies that withstand the broader tests make sense?
4. Are there other biases I may still be missing, or other steps I should be taking?