cd /news/developer-tools/ldbd-dev-log-4-longer-charts-simpler… · home topics developer-tools article
[ARTICLE · art-69381] src=ldbd.app ↗ pub= topic=developer-tools verified=true sentiment=· neutral

LDBD Dev Log #4 — Longer Charts, Simpler Lines, a Humbler Score

LDBD developer fixed three stacked bugs that limited candlestick charts to two years of data, replaced a dead notifications tab with an Explore tab, and added a build guard to catch dead links, after the headline score overhaul in Dev Log #3 raised concerns about whether the AI-driven score is actually better.

read13 min views1 publishedJul 10, 2026
LDBD Dev Log #4 — Longer Charts, Simpler Lines, a Humbler Score
Image: Ldbd (auto-discovered)

In Dev Log #3 I overhauled the headline score and built the growth features — the feed, candlestick charts, chart annotations. This entry covers the weeks after: the things that broke — or only became visible — once I actually used them. A profile photo that squished into an oval on mobile, three stacked reasons a chart refused to show more than two years, a redesign of the drawing tools I had just built, and one uncomfortable question — is the AI actually better? Fewer shiny new features this time; more work making what already exists honest. The thing I wrestled with longest was the score: after the switch, the leaderboard looked better, and that’s exactly what bothered me.

The notifications tab that built fine but was dead #

It started with a bug I stepped on myself. Tapping “Notifications” in the mobile bottom tab bar returned a 404 — page not found. Fair enough, since I had never created a dedicated notifications page. The odd part was that the build (the automated pre-deploy check) had been passing without a single complaint.

Here’s the fun part. LDBD has a dynamic route that handles profile URLs of the form /@handle

, and any single-word URL without a dedicated page of its own falls through to it. /notifications

was being read as “the profile of a user named notifications,” no such user exists, so the page answered 404. From the build’s point of view every URL resolves to something, so nothing looks wrong. It’s a link that looks alive but is dead.

The fix itself is trivial — create the page. What mattered was making sure it can’t happen again, because the same mistake would sail through the build just as quietly next time. So the build now runs a guard that checks every menu and tab link resolves to a dedicated page. This class of dead link now fails the build before it can ship.

Once it was fixed, I realized the tab itself had been questionable. The header already has a bell icon for notifications, so the bottom tab was a duplicate. I swapped it for Explore (which had been buried in the hamburger menu of all places) and linked the full notifications page from a “view all” entry in the bell popover instead.

Why the chart stopped at two years — three separate reasons #

The candlestick chart on asset pages (the kind that draws each day’s open, high, low, and close as one bar) would only scroll back two years. I assumed a “2y” setting was sitting somewhere and asked Claude what the downside of raising it would be. It turned out “two years” wasn’t a setting at all — it was three different causes stacked on top of each other.

First, the backfill — the job that retroactively fills in historical data — had only ever fetched two years of highs and lows, so anything older fell back from candles to a plain line of closing prices. Second, there was a deeper bottleneck: the API in front of the database returns at most 1,000 rows per request by default, so even when the chart asked for five years it got the most recent 1,000 rows (about four years of trading days) and the rest was silently cut. Third, the closing prices themselves went back to 2016 — about ten years — already sitting in the database. The ingredients were all there; they were being trimmed at two different points in the kitchen.

Each cause needed its own fix. Raising the 1,000-row cap globally would affect every other API, so I left the global setting alone and had the chart API fetch the data in several batches and stitch them together. The backfill called for one precaution: LDBD’s scores are computed from closing prices (adjusted for dividends and splits, to be precise), so carelessly touching historical price data could corrupt the basis of already-settled scores. Fortunately, this backfill only adds highs and lows to existing rows and never touches the score inputs — which I confirmed before running it. Of the 603 assets in scope at the time, 601 succeeded (two were tickers that Yahoo Finance, the data source, couldn’t resolve), filling about 1.47 million rows.

The result: stocks and ETFs now show candles back to 2016, and crypto goes back as far as the data allows. The default view stays at six months; zoom out for the long view. The symptom was a single line — “the chart only shows two years” — but the cause was three layers deep. Tracing where the data actually got cut before reaching for a fix is what worked this time.

Small but annoying screen fixes #

A batch of smaller fixes shipped in the same stretch. On asset pages, the reasoning attached to open predictions used to be one flat list; on wide screens it now splits into two columns — bullish reasoning on one side, bearish on the other — so the two cases can be compared at a glance (on mobile they stack vertically). Cards that actually contain reasoning sort to the top. Pure front-end work; the server wasn’t touched.

I also fixed profile photos squishing into ovals on mobile. My guess was “the row of buttons next to it leaves too little room, doesn’t it?” — and that turned out to be exactly right. The buttons in that row were built to never give up space even on a narrow screen, so the only element allowed to shrink was the photo, which got squeezed horizontally; the cropping that fits the image into its box then produced an oval. When something looks like an image bug, the culprit is usually the layout rules, not the image. The fix was simple: tell the photo not to shrink and let the button row wrap when space gets tight.

That pass also surfaced a real gap: someone browsing from Explore into an asset page had no button to actually make a prediction. Follow and share were right there, but the core action of the entire service was missing. I added a predict button at the top of the asset page that opens the prediction form with that asset pre-selected. The first version was noticeably bigger than the follow and share buttons next to it, which looked off; one styling round later they are all the same size and differ only by color — predict in green, the others in white, and a gold star when the asset is on your watchlist.

Support and resistance were the same line all along #

The most fun work in this entry was tearing up the chart drawing tools I had built in Dev Log #3. Back then I made support lines, resistance lines, trend lines, and boxes as separate tools. Using them raised a question: is splitting tools by meaning like this actually how charting tools are supposed to work?

I looked into TradingView and several other major charting platforms, and the answer was clear. None of the ones I checked has a “support line tool” or a “resistance line tool.” They all offer geometric tools— horizontal lines, trend lines, boxes, Fibonacci — and whether a given line is support or resistance is interpreted from where the price sits relative to it. Chartists even have a saying, “broken resistance becomes support”: role reversal is the norm, and hard-coding the label at draw time, the way we did, was the nonstandard choice.

Opening the code made it more embarrassing. Our support line and our resistance line were exactly the same horizontal line, differing only in color (green versus red) and the label string. We had built the same thing twice.

There was one more argument from the AI side. LDBD has a bot that draws lines on charts and cites them as prediction reasoning, and when you have an AI generate drawings, fewer tools with simpler rules mean fewer mistakes. A small set of geometric primitives plus a “role” attribute is exactly the kind of shape an AI can produce reliably — and it happened to match the industry standard as well.

So support and resistance are no longer separate types; they merged into “horizontal line + role.” The role comes from a fixed list — support, resistance, target, stop, and so on — and the color is derived from the role. Because annotations were already stored as flexible JSON (the decision in #3 to store data instead of images), existing data kept working untouched, and the UI keeps the familiar support and resistance buttons — they just set a role now. While I was in there I added the biggest missing tool, Fibonacci retracement (pick a high and a low, and it auto-draws retracement levels like 38.2%, 50%, and 61.8% — a classic tool for gauging how far a pullback might go), plus a text tool for leaving short notes on the chart.

The guardrails got a cleanup too. Whether a drawing comes from the web UI, from a bot through the API, or from an external AI through MCP (the standard protocol AI agents use to connect to outside services), it passes the same validator — roles and colors only from an allowlist, label length capped, and a ceiling on the number of annotations. Text is painted onto the canvas rather than inserted into the page as HTML, so at least the class of attack where typed text ends up executing as code doesn’t apply. The text input is still a plain browser prompt, which is admittedly clunky — that one stays on the polish list.

“Is the AI actually better?” — doubting the score one more time #

The deepest work in this entry wasn’t a feature; it was an argument. After #3 switched the headline score to an annualized rate, I looked at the leaderboard: the AI bots had climbed to the top and the simple bots that always bet “up” had slid down. As the person who built the AI bots, that’s a pleasing picture. And that’s exactly what nagged me. It’s hard to claim the AI is clearly better — so was the new metric designed in a way that quietly punishes anyone who keeps betting on assets that drift upward over the long run?

Claude’s first answer was that the old score was a running total, so bots with more predictions were favored, and the new score simply removed that distortion. Plausible — and wrong. The old headline score was also an average, so prediction volume had nothing to do with it. When I pushed back, Claude dug into the data again, and the real cause turned out to be the per-bet formula. The old formula didn’t use the raw size of a return; it was relatively generous to small moves, which inflated the always-up bots that win small and often. The new formula drops that bonus, and an always-up bot’s score converges to the asset’s actual long-run return. The bot that always predicts “up” on the Nasdaq ETF now scores about +19% a year — roughly in line with QQQ’s actual long-run average. The new score hadn’t cheated these bots; the old score had been overrating them. I had asked an AI to help me doubt the score, and its own explanation turned out to be the thing I couldn’t take at face value.

If it had ended there, the story would be “the new score was right after all.” But digging through the data exposed the real problem: the ranking was ignoring confidence. The smoothing in the table below is a device that pulls a score toward zero when the sample is small; here are the numbers from around June 20.

Bot Resolved predictions Score, old smoothing Score, stronger smoothing 95% confidence interval
Claude daily bot 37 +99% +41% −61% to +367% (unproven)
Early test AI bot 176 +36% +25% −37% to +116% (unproven)
Always-up QQQ bot 7,451 +19% +19% +14% to +25% (proven)
Always-up VOO bot 7,359 +14% +14% +10% to +19% (proven)

A confidence interval is the range where the true skill plausibly sits. The top AI bot’s +99% came from just 37 bets, so its interval spans −61% to +367% — meaning we can’t yet tell luck from skill at all. The always-up bot’s +19% below it is backed by 7,451 bets and a tight interval. In short, unproven luck was sitting on top of proven skill.

The fix was to crank that smoothing five times stronger. The per-bet formula stays; only the strength of the pull toward zero changed. Since scores are computed at read time rather than stored, no re-scoring was needed — it came down to changing one constant. Bots with 7,000-bet histories barely move; the 37-bet +99% comes down to +41%. If a bot still holds first place after that, it has earned it. The more fundamental fix — ranking by the lower bound of the confidence interval — went on the candidate list.

The root of the problem is that annualizing a one-day prediction magnifies every single bet’s swing enormously. If you don’t want a short winning streak sitting on top of a ten-year track record, you either smooth hard or build confidence into the ranking itself. As for the original question — is the AI actually better? — if a model can call both directions in a choppy market, beating a fixed-direction bot is exactly what you would expect; but the sample isn’t there yet to say so statistically. Making sure the leaderboard doesn’t look like it’s handing down that verdict prematurely — that was the actual job.

What I’m taking away #

One symptom can hide three causes.“The chart only shows two years” was the backfill range, plus the response row cap, plus the fact that closing prices actually went back ten years — three facts stacked together. Trace where things get cut before fixing the one-line symptom.

Splitting categories feels intuitive, but merging is often the right call. Making support and resistance separate tools meant building the same geometry twice. The industry standard and the AI-friendly design pointed to the same answer.

Passing the build doesn’t mean it’s alive. The dead notifications tab sailed through the deploy checks, and if I hadn’t happened to tap it myself it would have stayed dead much longer. There’s a limit to how much a person can click through every time, so this class of check belongs in the build.

And if you change the score, doubting the new score is part of the same job. If I had stopped at “nice, the AI is winning,” a 37-bet lucky streak would still be sitting on top of a 7,451-bet track record. The conclusion lands where the last entry did: get clear on what you are measuring, and how much you can trust the number, before anything built on top of it can mean much.

── more in #developer-tools 4 stories · sorted by recency
── more on @ldbd 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/ldbd-dev-log-4-longe…] indexed:0 read:13min 2026-07-10 ·