Section 02

Algorithm architecture

How I wired the formulas into a small Python program: files in, signals out.

What the program sees

The market does not hand my laptop a “buy” flag. It hands someone a tape of trades, which data vendors then pack into bars. A daily bar has five numbers I care about: open, high, low, close, and volume. That bundle is usually called OHLCV. My beginner pipeline only used the close for indicators, but I still stored the rest so I could pretend to fill at the next open instead of the same close I just used to make the decision.

DateOpenHighLowCloseVolume
2024-01-02185.2186.9184.1185.652.1M
2024-01-03185.4185.8181.9182.361.4M
2024-01-04182.1184.0181.5183.748.8M
2024-01-05183.9186.2183.4185.944.2M
2024-01-08186.0187.5185.1187.139.6M

Table 1. Example daily bars, made up so the page has a concrete shape. Real files look like this, only longer. Yahoo Finance and similar sources export this layout (Yahoo Finance, n.d.).

Four stages

  1. 01

    Ingest

    Read a CSV of daily bars. One row per date.

  2. 02

    Transform

    Compute SMA, RSI, and returns from closes only.

  3. 03

    Decide

    Apply a rule using yesterday’s indicator, not today’s.

  4. 04

    Execute

    Log a fill at the next open in the notebook. No broker.

I treated these as a pipeline, not as four separate apps. One script reads the file, computes columns, writes a signal column, then walks that column to grow a cash account. If I split it too early I ended up with three notebooks that did not agree on the date index.

Lookahead, in plain words

The most important engineering bug in this project is using information you would not have had at the time. If I compute today’s SMA including today’s close, then “buy today at today’s close,” I have cheated. In real life the close is the last print of the day. You cannot also trade at that print with a signal that needed that print. So the rule I used is:

  • At the close of day t, compute SMA(t) and RSI(t).
  • If the rule says buy, the fill is the open of day t + 1.
  • Returns between t and t + 1 still belong to the old position.

That delay makes the backtest worse than the fantasy version. That is the point. Quantpedia’s write-ups of timing rules are careful about when the signal is known versus when the trade happens (Quantpedia, 2023). I copied that habit even though my series is only illustrative.

Pseudocode I actually followed

for each day t in the file:
    sma50[t] = mean(close[t-49 : t])   # need 50 days
    if close[t] > sma50[t]:
        signal[t] = LONG
    else:
        signal[t] = CASH

position[t] = signal[t-1]              # trade next day
if position[t] == LONG:
    equity[t] = equity[t-1] * (1 + r[t])
else:
    equity[t] = equity[t-1]            # cash earns 0 here

Cash earning 0% is a student shortcut. Faber’s monthly timing often parks in T-bills when the rule is out (Faber, 2007). I did not model a bill rate. If I had, the SMA rule would look a little better in the long cash stretches, not worse.

Costs and why I left a line for them

Every time the signal flips I should subtract a small cost: spread, commission, maybe slippage. I used a placeholder of 5 basis points per flip in one notebook (0.05% of the traded value). On 7 flips that is not huge. On 80 flips it would eat the whole project. This is why I prefer a slow 50-day rule over a 5-day rule for a first test. Faster rules look smarter until you bill them.

New York Stock Exchange
Figure 7. Execution, in the real world, happens against an exchange. My project stops at a log file. Photo: The Wall Street Experience blog.

I did not connect a broker. For this study a CSV plus a notebook is the system. The architecture still matters: same formulas, same date alignment, same “decide on t, trade on t + 1” rule I would keep if I ever sent an order.

Sources

  1. Yahoo Finance. Historical data downloads.
  2. Faber, M. (2007). A quantitative approach to tactical asset allocation. Journal of Wealth Management.
  3. Quantpedia (2023). Avoid equity bear markets with a market timing strategy.