Load candles through the proxy you already have
Nothing new to build for data. Your proxy from course 1 already forwards to the API with the key on the server side; a backtest is one more route through it.
The shape
A daily bar from /eod/AAPL.US, captured live:
{"date":"2026-08-24","open":311.47,"high":313.36,"low":309.97,"close":310.34,"adjusted_close":310.34,"volume":34673600}
Seven fields, one row per trading day, oldest first. A bare array this time — not the data envelope the screener used. Two endpoints from one vendor with two different shapes is normal, and it is why the normalisation you wrote in course 1 belongs at the boundary rather than inside a component. Which shape you will get is in the reference beside each entry, so it is a thing to look up before you write the parser rather than a thing to discover from a stack trace after.
Note the two closes. close is what printed that day; adjusted_close is that price rewritten for every split and dividend since. Which one a backtest uses is the subject of two later lessons, and it is the single most common way a beginner's results become nonsense.
What one call costs
Measure it rather than assume it, the same as always. Here is the measurement, taken today:
- One
/eodcall for five days: the counter moved by 1. - One
/eodcall for forty-five years — 11,516 rows, from 1980-12-12 to 2026-08-24: the counter moved by 1.
Range is free. The expensive axis in a backtester is instruments, not time. Courses 1 and 2 both taught that cost follows requests — reload frequency in the watchlist, one call per row in the screener's N+1. The same rule holds here, and the consequence is new: since a request buys any range, pull the whole history once and cache it with the date you pulled it — then refresh deliberately after an ex-dividend or a split, because the adjusted column is rewritten each time. A cache here is a dated snapshot, not the truth.
A second measurement worth having: ten consecutive reads of /user left the counter unmoved at 1424, so on this account the meter itself is free. Check it on yours — it takes thirty seconds and it changes how often you can afford to look.
The failure the proxy must handle
Ask for a symbol that does not exist and the API answers:
HTTP 404
Ticker Not Found.
Plain text. Not JSON. So await res.json() throws a SyntaxError, which is a different failure from the one your error handling expects, and it happens on the most ordinary mistake there is — a typo in a ticker.
This is exactly what the res.ok check from course 1 was for, and here is the concrete payoff: check res.ok first, read the body as text on failure, and surface "Ticker Not Found" to the person who typed it.
Cache it on disk, not in memory
A backtest run reloads the same history dozens of times while you iterate. Write the fetched series to a local file keyed by symbol and range, and read from there when it exists.
Two reasons, and the second is the real one. It saves calls, obviously. But it also makes your research reproducible: the run you did on Tuesday used exactly the bytes you still have, so a result you cannot explain can be re-derived rather than argued about.
Try it now
Pull the full history of one symbol through your proxy and count the rows. Then request a deliberately misspelled ticker and watch what your error handling does with a plain-text 404 — if you get a white screen or a silent empty chart, fix that before writing any strategy code.