AlgoPro UniversityCourse home
Part 10 · Lesson

Journaling & reviewing a live system

Keeping a decision journal, running periodic reviews, and comparing live vs backtest — slippage, expectancy drift, degradation — so you can tell a normal drawdown from a decayed edge.

A backtest is judged once. A live system is judged *forever*. Markets change, your broker changes, other traders arbitrage away the inefficiency you found — and edges decay. The job now is not to build; it is to monitor the edge itself and catch its death before it catches your account. That is what journaling and periodic review are for.

The decision journal

Separate from the machine's logs, keep a short human journal — every time you *change* something or *override* the system, write down what you did and, crucially, *why you believed it at the time*. Months later this is the only honest record of whether your judgement adds value or destroys it.

Live vs backtest: the three degradation signals

Every review, load your structured logs (Lesson 2) and compare the live sample to the backtest that justified going live. Three numbers tell you almost everything:

  1. Slippage — is your realised slippage (intended price vs fill price) worse than the cost model you backtested? Persistent excess slippage quietly eats a thin edge.
  2. Expectancy — average P&L per trade, live vs backtest. Some shortfall is expected; a large, sustained gap means the backtest was optimistic or the edge is fading.
  3. Win rate & distribution — is the *shape* of returns the same, or are the winners smaller and losers bigger than history? Distribution drift is an early warning before the average moves.
pythonComparing live fills to the backtest from your logs
import json, pandas as pd

class="c"># Load the JSON-lines log the bot wrote in Lesson class="n">2.
rows = [json.loads(l) for l in open(class="s">"bot.log") if l.strip()]
fills = pd.DataFrame([r for r in rows if r[class="s">"kind"] == class="s">"fill"])

live_slippage = fills[class="s">"slippage_bps"].mean()
backtest_slippage = class="n">2.0     class="c"># what your cost model assumed

print(fclass="s">"Live avg slippage:     {live_slippage:.2f} bps")
print(fclass="s">"Backtest assumption:   {backtest_slippage:.2f} bps")

drift = live_slippage - backtest_slippage
if drift > class="n">1.0:
    print(fclass="s">"WARNING: slippage {drift:.1f} bps worse than modelled. Re-check the edge.")

class="c"># Expectancy drift, live vs backtest
pnl = pd.DataFrame([r for r in rows if r[class="s">"kind"] == class="s">"trade_closed"])
live_expectancy = pnl[class="s">"pnl"].mean()
print(fclass="s">"Live expectancy/trade: {live_expectancy:.2f}  (backtest: class="n">0.85)")

Is it a drawdown, or is it dead?

This is the hardest question in live trading, and it has no clean answer — only a disciplined way to think about it. A losing streak is *expected*: even a great strategy spends real time underwater. The question is whether the current pain is inside the range your backtest already showed you, or beyond it.

Live equity vs backtest expectation band
Blue is the live equity curve; violet is the backtest's expected path. While live stays near the expected band, a drawdown is just weather. When live diverges persistently below it — as at the right — the edge may be decaying, not just resting.

Review cadence

Match the frequency of review to the frequency of trading, and never review so often that you act on noise:

Reviewing on this cadence — and *resisting* the urge to fiddle in between — is what separates a trader with a process from a gambler with a computer. Which brings us to the final, and hardest, subject of the entire course: you.