DEV Community

Cover image for No Backtesting Needed! I Punched My Hypothesis's "Heart" Directly with Raw Data and It Died Before I Even Measured Returns
oji - building AI in public
oji - building AI in public

Posted on

No Backtesting Needed! I Punched My Hypothesis's "Heart" Directly with Raw Data and It Died Before I Even Measured Returns

Hey there, it's your old man developer. I'm a 38-year-old side-hustle engineer, tinkering with AI agents and automated trading bots on weeknights and weekends.

Developing an automated trading bot often means a long road after you get an idea. Translating a strategy into code, backtesting it with historical data, tuning parameters... it can easily take weeks. And usually, most ideas get trashed at the backtesting stage with a simple "this is useless." This kind of rework is soul-crushing for a solo developer.

Recently, a new strategy popped into my head: "Aren't undervalued small-cap stocks more likely to be acquired (TOB) by larger companies?" If a TOB (Tender Offer Bid) is announced, the stock price usually skyrockets. So, if this hypothesis was true, I might be able to play it smart.

Normally, I'd start coding the backtest from here:
"Define conditions for undervalued small-caps, simulate TOB occurrences with historical stock data, calculate returns..."

But this time, I decided to skip all that heavy lifting and investigate something more fundamental first.

That is, punching the "heart" of the hypothesis directly with primary data.

Measuring the "Mechanism" Before Measuring Returns

Every investment hypothesis has a "mechanism" that is supposed to generate returns.

For my hypothesis, it looked like this:

  1. Mechanism: "Undervalued small-cap stocks are more likely to be acquired (TOB)."
  2. Result: TOB announcement drives up stock price, generating returns.

Many people jump straight to building complex backtests to measure "2. Result." But if the core premise, "1. Mechanism," is wrong, all that time spent on backtesting becomes wasted. Brutal.

So this time, instead of looking at the indirect metric of returns, I decided to directly verify the heart of the hypothesis: "Are undervalued small-cap stocks actually more likely to be acquired?" with raw data. It's similar to a PoC (Proof of Concept) in software development. Attack the most critical and uncertain part first.

I Punched It With 582 TOB Cases

What I did was simple:

  1. Data Acquisition: I gathered about 582 TOB cases (public information) from the past few years.
  2. Definition:
    • Small-cap: Stocks in the bottom 20% of market capitalization.
    • Undervalued: Stocks with a PBR (Price-to-Book Ratio) below 1x.
    • I defined "undervalued small-cap stock" as meeting both conditions.
  3. Validation:
    • Divide all listed companies into an "undervalued small-cap" group (approx. 20% of the total) and an "other" group (approx. 80%).
    • Calculate the proportion of companies in each group that actually had a TOB occur.

If the hypothesis was correct, the TOB occurrence rate in the "undervalued small-cap" group should be significantly higher than in the "other" group.

The code looked something like this. It's 100 times simpler than backtesting code: just combining TOB case data and financial data of all listed companies using pandas, flagging each company if it meets the "undervalued small-cap" conditions, and then aggregating by group.

import pandas as pd

# all_stocks_df: DataFrame of all listed companies (with is_small_cap, is_value_stock flags)
# tob_cases_df: DataFrame of TOB cases (with a 'ticker' column)

# List of tickers that were subject to TOB
tob_tickers = set(tob_cases_df['ticker'])

# Add a flag to all stocks indicating whether they were TOB'd
all_stocks_df['was_tob'] = all_stocks_df['ticker'].isin(tob_tickers)

# Define conditions for "undervalued small-cap"
condition = (all_stocks_df['is_small_cap'] == True) & (all_stocks_df['is_value_stock'] == True)

# Grouping
target_group = all_stocks_df[condition]
other_group = all_stocks_df[~condition]

# Calculate TOB occurrence rate in each group
tob_rate_target = target_group['was_tob'].mean()
tob_rate_other = other_group['was_tob'].mean()

print(f"TOB occurrence rate for Undervalued Small-Cap Group: {tob_rate_target:.2%}")
print(f"TOB occurrence rate for Other Group: {tob_rate_other:.2%}")
Enter fullscreen mode Exit fullscreen mode

And the result from running this simple code was this:

TOB occurrence rate for Undervalued Small-Cap Group: 2.33%
TOB occurrence rate for Other Group: 3.02%
Enter fullscreen mode Exit fullscreen mode

...Huh?

It was actually lower.

The probability of undervalued small-caps being acquired via TOB was lower than other stocks. The heart of my hypothesis was refuted instantly by the data. A literal instant death.

Honestly, I laughed a little when I saw this result. The thought of spending weeks writing backtesting code without realizing this... it's genuinely terrifying. Phew.

(Of course, the results might change if I redefined "undervalued" or "small-cap." But given such a clear difference with common definitions, I decided that the priority for exploring this hypothesis was low.)

Lesson Learned: Before Heavy Processing, Aim for the Heart of the Hypothesis

The lesson from this experience is simple:

Before conducting computationally or implementation-heavy validation, consider if there's a simpler way to verify if the core "mechanism" of your hypothesis is correct.

This isn't just for automated trading.

  • If building an AI agent, test if the core prompt works as intended in isolation before assembling a complex flow.
  • If building a web scraping tool, try to reliably extract the target element from a single page before crawling all pages.

As a solo developer, resources are always limited. Time, especially, is finite. That's why tackling the riskiest hypothesis, the most fragile part that could ruin everything if it breaks – the "heart" – first is incredibly important. I felt that more keenly than ever.

So, this "undervalued small-cap TOB strategy" went to its grave without a single cent of return ever being calculated, before even entering the development process. But thanks to this, I avoided weeks of wasted effort. That, in itself, is a valuable failure log.

I'll be back to write here if I screw up again.


I'm also on X (Twitter):
@oji_ai_dev where I tweet more detailed development logs and other random stuff.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev

I like the idea of killing the hypothesis before backtesting. If the core data assumption is false, a beautiful backtest only adds ceremony. The earlier check should be cheap, direct, and hard to rationalize away.