{adamcoding}
Part I
05
Chapter 5

Your First System

Time to build. The goal of this chapter is not a profitable strategy - it is a correctly measured one, which is a harder and more valuable thing.

Specify before you implement

Write the spec in English first. If you can't state it unambiguously in prose, you don't understand it well enough to code it, and you'll paper over the ambiguity with an arbitrary implementation choice you'll later mistake for a design decision.

A complete spec answers:

  • Universe - which instruments, and why those?
  • Timeframe - what bars, and what does that imply about costs relative to your expected move size?
  • Entry - the exact condition, evaluated on what data, acted on when?
  • Exit - stop, target, time-based, signal-based? All of them?
  • Sizing - how many units, derived from what?
  • Filters - what prevents a trade that would otherwise fire?
  • Mechanism - why should this make money? Who's on the other side?

That last one is not optional and not rhetorical. Write an actual answer down. You will return to it later and find it embarrassing, which is the point.

The deliberately mediocre example

We'll use an EMA crossover with a trend filter and ATR-based exits - the strategy in Appendix A. I've chosen it precisely because it is unlikely to have a meaningful edge. That's a feature. You'll learn more from correctly establishing that a strategy doesn't work than from incorrectly concluding that one does, and the second outcome is the one that costs money.

The spec:

Universe: liquid instruments with tight spreads. Timeframe: 1 hour. Entry long: 13-EMA crosses above 21-EMA, and close is above the 200-EMA. Entry short: the mirror. Stop: 2 × ATR(14) from entry, fixed at entry. Target: 3 × ATR(14) from entry, fixed at entry. Sizing: 1% of equity risked per trade. Mechanism: trend persistence - a documented behavioural effect where participants under-react to information initially and over-react later. The crossover is a crude sensor for it.

Note that the mechanism is real and documented, and also that a 13/21 EMA crossover on a 1-hour chart is an extremely widely used sensor for it. Both things are true. The second is why we should expect the edge to be small or absent.

Pine Script or Python?

Pine Script - fast iteration, charts come free, the backtester works, no infrastructure. Limited control over fills, hard to do portfolio-level or cross-instrument work, and you're inside someone else's execution model with limited visibility into its assumptions.

Python - full control, real statistics, proper portfolio simulation, reusable tooling. Slower to start, and you will write bugs that flatter you. Every one of the biases in the next section is something you can accidentally implement yourself.

The recommendation: prototype in Pine to find out quickly whether an idea is worth pursuing, then rebuild in Python for anything you're serious about. The rebuild is not wasted effort - implementing the same strategy twice in two systems is one of the better bug-detection methods available, and discrepancies between the two are almost always a bug in one of them rather than a platform quirk.

The five bugs you will write

These are not hypothetical. Every one is a bug people write repeatedly.

1. Lookahead bias. Using information that wasn't available at decision time. In Python it's usually an off-by-one in an index or a .shift() you forgot. In Pine it's using close in a calculation that then acts on the same bar's open.

The mental model: this is a causality violation, the trading analogue of reading a variable before the write that produces it. And it's insidious because it doesn't crash - it just makes your equity curve beautiful. A suspiciously good backtest is a lookahead bug until proven otherwise.

2. Repainting. Your signal changes after the fact. ta.crossover evaluated intrabar is true, then false by the close. Anything using request.security with default settings on a higher timeframe. The strategy looked like it caught every move because the signal retroactively agreed with what happened.

3. Survivorship bias. Your instrument list is today's list. The companies that went bankrupt aren't in it. Any strategy backtested on "current S&P 500 constituents" over ten years has an enormous invisible advantage.

4. Off-by-one on bar close. A signal computed on a bar's close cannot be acted on at that bar's close in most real setups - you act at the next bar's open. Getting this wrong on short timeframes can invert your results entirely.

5. Timezone and session errors. Your data is in UTC, the exchange session is in New York time, and daylight saving moves it twice a year. Session filters silently misalign. This one is dull, common, and completely destroys intraday strategies.

Establish the baseline, then stop

Run the strategy with default parameters. Record everything: trade count, expectancy in R, profit factor, max drawdown, the standard error on your expectancy from Chapter 4.

Then stop. Do not tune. Do not add a filter. Do not try 21/55 instead of 13/21.

I know exactly how this feels - it's the same instinct that makes you refactor working code, and it's usually a good instinct. Here it is the mechanism by which people destroy their own results, and the reason is in the sample-size table in Chapter 4. You cannot tune your way to significance on a sample too small to detect the effect. You can only fit noise.

The baseline is the number every future version must beat by a margin large enough to justify the degrees of freedom you spent. Chapters 8 and 9 are the disciplined way to spend them.

Set up a lab notebook

You are running experiments. Treat them as such:

  • Every strategy version in git. Tag the commit that produced each result.
  • Configuration in a file, hashed. Record the hash alongside the result. "I think it was ATR 2.0" is not a record.
  • An experiment log - one row per run: date, config hash, data range, instrument, trade count, expectancy, drawdown, and what hypothesis you were testing. That last column is the important one.
  • A count of every distinct configuration you've tested. This is your multiple-comparisons budget, and it is the number that determines how much to believe your best result. Almost nobody tracks it. It's the difference between doing science and doing search.

That last point deserves emphasis, because it is the bridge from this chapter to the rest of the book: the credibility of your final result depends on how many results you rejected to get there. An engineer's instinct is to iterate until it works. A scientist's is to count the iterations and discount accordingly. This book is asking you to do the second thing, using the tools of the first.