Generated backtests often fill at a price you already knew, or use tomorrow's close. QuantConnect closed a stale-price loophole on daily and hourly data for that reason. After work, the skill is point-in-time Python — a pretty curve is not a strategy.
You can paste a prompt into ChatGPT and get a backtest in minutes. The equity curve will look finished. After a full day of work, that is a dangerous kind of finished: the code ran, the plot went up, and you still do not know whether the script used information you would not have had at the moment of the decision.
This is research hygiene, not a trading course. Tools got easy. Discipline did not.
QuantConnect recently changed how daily and hourly fills work in LEAN because generated algorithms kept filling market orders against a stale previous close — a print the algorithm already “knew.” See Closing the Stale-Price Loophole on Daily and Hourly Data. Their warning logs were already there. AI-written code often never reads them.
Vendor programmes such as Quantra Agentic AI, EPAT, and CQF GenAI electives exist as part of the wider market; this page is not a comparison and not a sign-up for them.
The live path here is Python literacy so you can see the leak. Introduction to Python Programming is 19 published lessons for evenings. Check the course page for the current enrollment window; this article will not freeze a date in the title.
A table of daily prices is not a clock. It is a grid. Every row already contains the close of that day — a number that did not exist at 10:00.
If your script decides today’s trade using today’s close, then fills at that same close, you used an end-of-day fact as if it were available at the open. If you use shift(-1), you used tomorrow.
import pandas as pd
close = pd.Series(
[100.0, 101.0, 99.0, 102.0, 103.0, 101.0, 104.0],
index=pd.date_range("2026-01-05", periods=7, freq="B"),
name="close",
)
# LOOK-AHEAD: tomorrow's close sits on today's row
tomorrow = close.shift(-1)
used_future = tomorrow > close
# POINT-IN-TIME: only last night's close is known at today's open
known = close.shift(1)
ma = known.rolling(3).mean()
signal = known > ma
shift(-1) is the usual accident. A label from the future inside a “predict” column is the same accident in a fancier jacket. Point-in-time means: at timestamp t, the script may see only rows that would have been published by t.
You do not need a broker API to practise this. You need to be able to read a Series, which is the job of Your first Python program that uses a library (not another print).
Look-ahead uses the future. Stale price uses a past print as if it were live.
If you subscribe only to daily bars and you place a “noon” market order, there is no fresh midday price in that series. Filling immediately at yesterday’s close means you executed at a level you already knew. Live, noon is a different number, or the order waits for a real open or close.
That is the loophole QuantConnect closed for daily and hourly data: generated code often ignored the stale-price warning, so the engine now holds or converts those orders instead of filling against the last bar.
# You wanted a midday fill. You only have yesterday's daily close.
yesterday_close = close.shift(1)
# Filling "now" at yesterday_close is not a market order.
# It is a replay of a number already on the row.
# A realistic daily-only fill waits for a genuine open or close.
The Python idea is the same as look-ahead: do not treat the last number in the table as the number you could have traded. Resolution (daily vs minute) is a data-availability question, not a style choice.
ChatGPT will also write the plot. So will pandas. So will Excel. A rising line is a picture of a Series. It is not evidence that the rule was tradable, that costs existed, or that you withheld the future.
# A leaky "equity" is just a cumsum of a series that used tomorrow
leaky_pnl = close.pct_change().shift(-1).fillna(0)
leaky_curve = (1 + leaky_pnl).cumprod()
# Plotting leaky_curve does not make it a strategy
You can save a leaky curve as a PNG tonight with the same skills as Make one chart tonight: from Excel to a Python file. The chart will not confess. You have to.
A short audit after work:
head() and tail(). Confirm the index is a time index, not row numbers you later treat as dates.shift(-1), joins that pull a future column onto today’s timestamp, and any rolling window that includes the current bar when the fill is that same bar’s close.shift(-1) with shift(1), the original curve was a story about tomorrow.None of this is a strategy. It is a refusal to lie to yourself with a plot.
The employed-learner pain is calendar plus temptation. After work you have one sitting. A chatbot will give you 200 lines and a performance number. The sitting is gone. You still have not checked whether the fill used a stale close.
Discipline is slower than codegen:
close.shift(1) or an explicit as-of merge) and refuse to plot anything else as “the trade.”Short lessons beat a three-hour binge of generated notebooks. See Learn Python after work: 19 short lessons, a few evenings a week. You cannot audit shift if you still fight the interpreter.
It will not teach you to trade. It will not claim a certificate, a job title, or a return. It will not enrol you in unpublished SoloQuant material — those courses are not enrollable from this page.
If you cannot yet run a .py file, that is the actual next step. Introduction to Python Programming — 19 published lessons, built for after work. Check the course page for the current enrollment window. Then come back to shift(1) with a file you can re-run on Thursday.