How KlineVision does AI technical analysis with Next.js + Gemini, draws it on the real candles, and verifies every call it makes after the fact — misses included.
Most "AI stock analysis" tools confidently tell you a chart looks bullish and then never look back. I wanted the opposite: a tool that records every call it makes and later checks whether the market actually agreed — and publishes the hit rate, misses included.
That one constraint — keep yourself honest — ended up shaping most of the interesting engineering. Here's how KlineVision is put together.
What it does
- Type a ticker (
AAPL,600519.SH,0700.HK) — or drop a chart screenshot. - You get a structured read: trend, support/resistance, candlestick + chart patterns, and 缠论 (Chan theory) structure — drawn directly onto the real candles, not hand-waved in prose.
- Works across US, A-shares, and Hong Kong markets.
The stack
- Next.js (App Router) on Vercel
- Supabase (Postgres + RLS) for data & auth
- Gemini 3 Flash for the analysis itself
- EODHD + Yahoo Finance for market data
- GitHub Actions + Vercel Cron for the background jobs
- PostHog for product analytics
Now the parts that were actually interesting to build.
1. "Never analyze fake data"
The market-data layer had a silent fallback: when a provider rate-limited, it returned synthetic candles so the UI never broke. Great for a demo, terrible for a product whose entire value is trust — the model would write a beautiful, confident analysis of a chart that never existed.
The fix was to make the data layer fail honestly:
Yahoo (primary) → EODHD (fallback) → if only demo data is available → 503, no analysis
If we can't get real candles, the user gets "live market data temporarily unavailable" — not a hallucinated read. For a trust tool, a silent fallback to fake data is the worst bug on the board.
2. Make the AI show its work
A text blob saying "there's a double bottom" isn't credible. You have to see it on the chart.
So the model returns structured overlays keyed to candle timestamps, and an isomorphic canvas renderer maps them onto the real OHLCV:
{
"zones": [{ "kind": "support", "price_low": 188.2, "price_high": 190.1 }],
"trend_lines": [{ "from": "2026-05-02", "to": "2026-06-01", "label": "neckline" }],
"markers": [{ "type": "double_bottom", "time": "2026-05-20" }],
"chan": { "bi_direction": "up", "zhongshu": [ ... ] }
}
The same renderer runs server-side (for share cards / OG images) and client-side (interactive, with layer toggles). Structuring LLM output into something a renderer can trust — and validating it hard — was most of the work here.
3. The honest track record (the whole point)
This is the part I'm proudest of. Every analysis snapshots its Chan bi-direction call (is the current move up or down?). Then a nightly cron — verify-chan — does the unglamorous thing:
- Wait 7 days.
- Re-fetch the candles.
- Check what actually happened.
- Mark the call
verified_correct,verified_incorrect, orexpired(inconclusive).
The published hit rate is deliberately conservative:
accuracy = correct / (correct + incorrect) // 'expired' excluded
…and we don't show a percentage at all until there are enough decisive samples. No small-sample theatre, no cherry-picking. A generic LLM can't offer this; a system that remembers its own predictions and checks them can. It's the one moat that AI hype can't fake — you either kept the receipts or you didn't.
4. The pSEO lesson that cost me a weekend
I generated ~6,300 programmatic pages — every stock × every detected pattern × two locales. Google discovered them and indexed about 400.
The trap: those ~4,500 templated "does AAPL have a doji right now?" combo pages weren't just sitting unindexed — they were dragging the whole site's quality signal down. A ~6% index rate is Google telling you the site is mostly thin content.
The fix wasn't more pages. It was fewer:
- Cut the sitemap from 6,322 → ~1,834 genuinely-differentiated URLs (pattern/indicator explainers, comparison pages, per-symbol pages).
- Dropped the combinatorial filler (kept the pages reachable, just stopped advertising them).
On a young domain, crawl budget is a ceiling set by authority — not by how many URLs you shove into a sitemap. Submitting 6k pages doesn't get you 6k indexed; it gets your good pages buried with the junk.
5. Latency honesty
A full Gemini analysis takes ~70 seconds (it's doing multi-step technical + Chan reasoning). Running that synchronously during SSR meant a blank tab and gateway-timeout risk for the first visitor to an uncached symbol.
So the daily pages:
- Serve a fresh cache instantly when one exists (refreshed nightly for popular symbols).
- For cold symbols, fall back to a non-blocking client loader with staged progress ("fetching market data → running technical analysis → detecting Chan structure → drawing the chart"), then cache the result for the day.
Slow is fine if it's honest about being slow. A spinner with real status beats a frozen page every time.
Try it (and the obligatory disclaimer)
You can use the assistant free, no sign-up — type a ticker and see what it reads. It's at klinevision.ai.
And to keep with the theme: this is an analysis aid, not investment advice. Past performance doesn't predict future results — which is exactly why the track record verifies itself instead of asking you to take its word for it.
I'd genuinely love feedback from devs who've wrestled with structuring LLM output, programmatic SEO at scale, or financial data pipelines — those were the three hardest parts. What would you have done differently?
Top comments (0)