CASE STUDY 01 · TRADING INTELLIGENCE PLATFORM

Trade on evidence, not intuition.

A trading intelligence platform where AI is constrained by deterministic analytics: Wilson-bounded statistics, agentic copilots that never compute a number themselves, multimodal chart analysis and a user-owned knowledge graph. The journal is one module, not the product.

GROUNDED AI LOCAL-FIRST GRAPHRAG FASTAPI + REACT WEB PLATFORM IN PROGRESS
SCROLL
WILSON CONFIDENCELOCAL INFERENCE GRAPHRAG RETRIEVALAGENTIC TOOL LOOP KNOWLEDGE GRAPHZERO CLOUD WILSON CONFIDENCELOCAL INFERENCE GRAPHRAG RETRIEVALAGENTIC TOOL LOOP KNOWLEDGE GRAPHZERO CLOUD
0LINES SHIPPED
0PHASES SHIPPED
0AI COPILOTS
0TESTS GREEN
0CLOUD CALLS
◆ WHY IT EXISTS

What happened vs. why it happened

MOST TRADING SOFTWARE ANSWERS ONE QUESTION · WHAT HAPPENED?

Why did it happen? ANSWERED BY

GraphRAG multi-hop retrieval over your own history, ranked by the win-rate swing a factor actually causes.

What can I learn? ANSWERED BY

An agentic coach that queries the journal mid-conversation and quotes the analytics layer verbatim, never its own arithmetic.

What should I improve? ANSWERED BY

Co-occurrence and cohort analysis that isolates the setup, session and liquidity combinations costing you the most R.

How confident is that? ANSWERED BY

A 95% Wilson interval and a confidence tier on every single bucket, deciding what the platform is even allowed to claim.

Most trading journals answer one question: what happened? Very few answer why it happened, and almost none distinguish between meaningful statistical patterns and random noise.

A trader might believe a strategy has a 73% win rate, while ignoring that the statistic comes from only 11 trades. Spreadsheets don't warn you. Generic AI chatbots make it worse, because they'll happily narrate confidence the data can't support.

NexTrade was built around a single philosophy, enforced in every module, every SQL macro and every AI prompt of the codebase:

Never let a number, human or AI, imply more confidence than the sample size supports.
73% WIN RATE
FROM 11 TRADES · NOISE, NOT SIGNAL
⚠ NOT ENOUGH DATA · VERDICT REFUSED

NexTrade attaches a 95% Wilson confidence interval to every win-rate stat, labels each bucket Insufficient / Tentative / Reliable, and its pre-trade gate refuses to issue a verdict on insufficient samples. It says "not enough data" rather than guess.

◆ CAPABILITIES

Powerful and Extensive

One platform covering the full loop: log, analyze, consult, validate and remember, with nothing leaving your machine.

Runs entirely
on your machine

  • 🗄DuckDB journal
  • 🧩Ollama runtime
  • 🕸Obsidian vault
  • FastAPI core
  • Web platform next
📊

Rigorous Analytics

Wilson intervals, confidence tiers and break-even-aware win rates on every bucket: equity curves, monthly cohorts and strategy intelligence built on one canonical engine.

🧠

Agentic AI Coach

A LangGraph tool loop that queries your journal mid-conversation, then delivers a full audit: mistakes, patterns and concrete recommendations, every number quoted verbatim.

👁

Vision Copilot

A multimodal analyst that reads annotated chart screenshots for support and resistance, trend and sentiment, then auto-journals trades by strict extraction, never invention.

🛡

Pre-Trade Gate

Before you enter, query your own history for that exact setup, session and direction. Pure Wilson-interval math, no LLM, and it refuses to judge a thin sample.

NEXTRADE
◆ PRODUCT SHOWCASE

See it working

Real captures of the running app, from the landing dashboard to the AI copilots. Every screen below is the actual product.

NEXTRADE · FULL PRODUCT TOUR
LANDING · TRADE WITH PRECISION
◆ 01 · THE PLATFORM

Trade with precision.
Analyze with AI.

A single-page React 19 app behind a FastAPI backend. The dashboard hub loads lazily, Plotly rides in its own chunk keeping the initial bundle around 312 KB, and every tab reads from one cached, version-keyed analytics API.

REACT 19 + VITE FASTAPI PLOTLY · APEXCHARTS · THREE.JS
TRADE GALLERY
◆ 02 · TRADE GALLERY

Every trade, fully documented

Filter 250+ logged trades by year, month, result, setup, position, liquidity or hour, then drill into any single trade: entry/exit chart, full setup context, RR and result. Screenshots are validated by magic bytes on upload, and external chart links are fetched through an SSRF-guarded proxy.

MULTI-DIMENSIONAL FILTERS MAGIC-BYTE VALIDATION
PRE-TRADE GATE
◆ 03 · PRE-TRADE GATE

Ask your own history before you enter

Describe the trade you're about to take (setup, session, direction, liquidity) and the gate compares that combination's 95% win-rate interval against your whole journal. The verdict is pure math, no LLM involved, and on a thin sample it answers "not enough data" instead of guessing. A tilt guardrail watches loss streaks the same way.

RULE-BASED VERDICT WILSON SEPARATION TILT GUARDRAIL
AI RISK MANAGER · NEMOTRON-3 · LOCAL
◆ 04 · TEXT COACH

An analyst grounded in your numbers

The AI Risk Manager runs a LangGraph tool loop over the journal: mid-conversation it calls its query_journal tool, agentic RAG over a hand-built entity graph, to pull the exact stat block it needs, then answers a full audit. Every number it quotes arrives verbatim from the analytics layer.

LANGGRAPH TOOL LOOP AGENTIC RAG 4× SPEED CAPTURE
VISION AI CANVAS · GEMMA VISION
◆ 05 · VISION COPILOT

An AI that reads your charts

Upload a trade screenshot and the multimodal copilot annotates it: support and resistance, trend detection and sentiment, each scored and explained, grounded in the same journal. It also auto-journals: a strictly extractive path prefills the Journal Builder from a screenshot, dropping anything not clearly visible rather than inventing it.

MULTIMODAL AUTO-JOURNALING 4× SPEED CAPTURE
◆ CHAPTER · THE KNOWLEDGE GRAPH

Years of behavior, visible

This is the part of NexTrade that stops being a journal. Every trade you log becomes a permanent node in a personal knowledge graph you own as plain Markdown, and the graph rewrites itself after every session without a single click.

OBSIDIAN · AI TRADING HUB · GRAPH VIEW · GENERATED, NOT DRAWN
Obsidian knowledge graph of the trading journal: hundreds of trade nodes linked to setup, session, liquidity, direction and market nodes
◆ WHAT BECOMES A NODE, AUTOMATICALLY
TRADE SETUP SESSION LIQUIDITY DIRECTION MARKET WEEKLY AI DEBRIEF BREAK-EVEN PAYOFF NOTE

Nobody drew this

Not one node in that image was placed by hand. The vault exporter walks the journal, writes a Markdown note per trade, and links it to its dimension notes: setup, session, liquidity, direction, market. Obsidian's graph view does the rest.

The clusters you can see are real behavioral structure. A dense knot around one setup means most of your history runs through it, which is exactly the bucket the statistics engine has enough sample size to speak about with confidence.

The sync contract

Continuous export into a user's personal vault is a data-loss problem disguised as a feature. NexTrade solves it with a manifest: every file the sync creates is recorded, and the sync is only ever allowed to touch files on that list.

  • Never writes outside the namespaced AI Trading Hub folder
  • Never overwrites or deletes a file it did not create
  • Never regenerates a weekly debrief: history is deliberately kept off the manifest, so it cannot be rewritten by a later sync

Why Obsidian, not a custom graph

Building a force-directed graph UI would have cost weeks and produced something worse than a tool that already exists, is free, is offline, and that users may already run.

Exporting to Markdown instead bought a world-class graph explorer for the price of a file writer, and it left the data in a format that outlives the app. If NexTrade disappeared tomorrow, the vault would still open.

The cost is honest and stated in the trade-off ledger below: the sync is one-way, so edits made in Obsidian do not flow back into the journal.

◆ ONE ENGINE

Every consumer, same math

A single canonical statistics module grounds the dashboards, both AI copilots, the pre-trade gate and the knowledge graph. One implementation, imported everywhere, never copied.

bucket_stats CANONICAL ENGINE
◆ ARCHITECTURE

The path a number takes

Everything on the left of this diagram is deterministic. The model only ever sits at the end of the pipeline, and by the time it speaks, every figure it can quote has already been computed, bounded and labeled.

DETERMINISTIC ZONE · NO MODEL COMPUTES A NUMBER HERE GENERATIVE ZONE · NARRATES ONLY GROUNDED ANSWER · EVERY NUMBER PRE-COMPUTED BACKGROUND SCHEDULER PURE MATH · NO LLM UI REACT 19 + VITE SPA · lazy chart chunks API FASTAPI async AI · sync journal STORE DUCKDB one embedded file CANONICAL MATH ANALYTICS ENGINE bucket_stats · wilson_ci RETRIEVAL GRAPHRAG multi-hop, hand-built GENERATIVE LLM COPILOTS Ollama · LangGraph EXPORT OBSIDIAN VAULT user-owned Markdown GUARDRAIL PRE-TRADE GATE verdict, or a refusal

The interesting property of this diagram is the boundary. Retrieval, statistics and the pre-trade verdict all resolve before the model is invoked, so the LLM's job is language, never arithmetic. Remove the model entirely and the platform still answers the quantitative questions.

◆ LAYER DETAIL

Open any layer

Seven layers, each with the decision that shaped it.

REACT 19 + VITE

~16,500 lines JSX · 30 components+

Single-page frontend: dashboard hub with lazy-loaded Plotly charts, journal builder, annotation canvas, dual AI chat panels. A canonical invariants.js mirrors the backend's win-rate math, and a Vitest parity suite catches any drift between the two implementations.

FASTAPI

~5,800 lines Python · 46 tests green+

Async API with sync journal endpoints on purpose, so DuckDB/pandas work runs in worker threads and never blocks the AI event loop. A lifespan-managed background loop ticks every 5 minutes: vault sync when data changed, backups every 24h, weekly AI debriefs. That replaced three manual buttons with a self-driving system which survives dev-server reloads without leaking tasks.

ANALYTICS ENGINE

graph.py + derived.py · canonical math+

bucket_stats and wilson_ci exist exactly once. Break-even trades are never wins but stay in the denominator; every stat carries its confidence tier and 95% interval. Monte-Carlo drawdown projections are deterministically seeded from the data so both AIs quote identical numbers. Grounding tests assert the exact figures that reach the LLM prompts.

GRAPHRAG

hand-built retrieval, DB-agnostic+

A traversable entity graph over plain trade dicts: multi-hop retrieval ranked by win-rate swing, 2-way co-occurrence blocks (Setup×Session), 3-way deepening, and cohort trend blocks. Because it's DB-agnostic, the same module grounds the DuckDB-fed Vision AI and the payload-fed text coach, then powers the pre-trade gate and tilt guardrail with zero LLM involvement.

DUCKDB

embedded analytical store, single file+

One journal.duckdb holds multi-journal trades, vision strategies and AI feedback telemetry. Single-writer by design: one shared connection with cursor-per-call threading, writes serialized through one lock, live backups via COPY FROM DATABASE, and SQL macros mirroring the win/lose/BE predicates so the invariant holds in both the Python and SQL paths.

OLLAMA

nemotron-3-ultra · gemma4:31b · all local+

Both copilots run against a local Ollama server through LangGraph with MemorySaver checkpointing. The system prompt is injected exactly once per session; history trimming never drops it. If the model rejects tool calling, the coach gracefully falls back to tool-less mode. A live eval suite runs golden questions against the real model.

OBSIDIAN VAULT

the user-owned knowledge graph+

One-way export into a namespaced AI Trading Hub folder: trades, dimensions, per-journal break-even payoff notes and weekly debriefs as linked Markdown. Manifest-tracked so sync only overwrites or deletes files it created, which makes the user's own notes untouchable. Debriefs live outside the manifest on purpose: history must never be regenerated.

◆ CONFIDENCE TIERS

What the system will say, and won't

Every bucket is labeled by sample size, and the label decides what the platform and its AIs are allowed to claim.

INSUFFICIENT

< 30TRADES IN BUCKET
  • Wilson interval shown
  • Stat displayed in UI
  • Keep / avoid verdict
  • AI may recommend
  • Pre-trade gate passes

TENTATIVE

30 – 99TRADES IN BUCKET
  • Wilson interval shown
  • Stat displayed in UI
  • Keep / avoid verdict
  • AI may recommend
  • Monte-Carlo risk model

RELIABLE

≥ 100TRADES IN BUCKET
  • Wilson interval shown
  • Stat displayed in UI
  • Keep / avoid verdict
  • AI may recommend
  • Used as journal baseline
◆ ENGINEERING DECISIONS

Chosen, not defaulted

01Canonical statistical engine

One shared implementation computes every metric used across the app and both AI pipelines. "Import it, never copy it" is written into the module docs, and where code can't be shared, the duplication is tested instead of ignored.

02AI grounding contract

The language models never compute statistics. Every numerical insight is retrieved from the analytics layer before it reaches the LLM, and dedicated grounding tests assert the exact numbers entering each system prompt.

03Integrate, don't rebuild

Instead of spending weeks on a custom force-directed graph UI, the knowledge graph exports to Obsidian, a tool that already does graph exploration exceptionally well.

04The DuckDB race fix

A real concurrency bug, since DuckDB connections aren't thread-safe, solved with a cursor-per-call pattern on one shared connection rather than reaching for a heavier database engine. Pragmatic, correct, documented as a permanent rule.

05Explicit cache invalidation

Read caches are keyed on a data version that every mutating endpoint must bump, never on TTLs. A little discipline traded for hard correctness guarantees.

06Lifecycle-aware automation

Background maintenance is wired through FastAPI's modern lifespan context and built to survive dev-server reload cycles without leaking orphaned tasks. Idempotent ticks mean a down Ollama just retries next tick.

◆ SECURITY

Hardened before it was needed

Unusually thorough for a single-user local tool. The kind of defenses that usually appear only after an incident are here pre-emptively, and they are exactly what a hosted version will need on day one.

SSRF-GUARDED PROXYEvery hop of the image proxy (initial URL, og:image target, HTTP redirects) must resolve to a public address before fetching.
MAGIC-BYTE VALIDATIONUploads are validated by file signature, never by the declared Content-Type header.
LOCALHOST-ONLYNo auth exists yet, so the backend never binds wider than 127.0.0.1. Authentication is the first gate the web release has to clear.
THREAD-SAFE DUCKDBCursor-per-call access on a single shared connection; all writes serialized through one lock.
CONTROLLED CORSA hardcoded allowlist of the two Vite ports, credentialed CORS disabled, and internal errors replaced by generic 500s so stack traces never leak to the client.
◆ CHALLENGES

The hard parts

Making AI statistically honest

The hardest problem wasn't integrating an LLM. It was ensuring the AI never presents confidence beyond what the sample size justifies. The answer became a system: Wilson intervals on every stat, three confidence tiers enforced in prompts and data structures, grounding tests that assert the exact numbers entering the model, and a live eval suite running golden questions against the real model after every prompt change.

Synchronizing knowledge without destroying user data

A one-way sync engine that continuously exports to Obsidian while preserving the user's own notes required manifest-based tracking and a strict separation between generated and permanent content. Weekly debriefs are deliberately excluded from the manifest so history can never be regenerated or deleted by a later sync.

Keeping two AI systems consistent

The text coach reads the request payload; the vision copilot reads the database directly. They stay consistent because both are grounded by the same DB-agnostic statistics module, and deterministic Monte-Carlo seeding means both quote identical risk numbers across sessions. Same shape, different retrieval strategy, one source of truth.

◆ ROADMAP

Built in tiers, not in one bang

Seven scoped increments, each shipped with tests before the next began. That habit kept a solo build from collapsing under itself, and phase 08 is where NexTrade leaves the desktop.

◈ NEXT MILESTONE

From local-first to the open web

Everything you see above runs on a single machine today. The next release takes NexTrade online as a hosted platform, so a trader can open a browser, sign in and get the same grounded analytics without installing anything. The engineering rules do not change: the statistics stay canonical, the AI stays grounded, and your journal stays yours.

  • Hosted appManaged deployment, nothing to install
  • Accounts & isolationAuth, per-user data boundaries, encrypted at rest
  • Hybrid inferenceManaged models, or keep pointing at your own Ollama
  • Local mode staysOffline, single-machine use remains fully supported
PHASE 01
01Phase

Trading Journal

The DuckDB-backed multi-journal core.

  • Manual logging + Excel import
  • Column-level date repair
  • Screenshot pipeline & proxy
PHASE 02
02Phase

Statistics Engine

Where the philosophy became code.

  • Wilson confidence intervals
  • Three-tier confidence ladder
  • Break-even math as invariant #1
PHASE 03
03Phase

GraphRAG

Retrieval over your own history.

  • Entity graph + multi-hop retrieval
  • Co-occurrence, triples, cohorts
  • Pre-trade gate & tilt guardrail
PHASE 04
04Phase

AI Agents

The LangGraph era.

  • Nemotron coach + tool loop
  • Gemma vision copilot
  • Grounding tests & live evals
PHASE 05
05Phase

Knowledge Graph

Trades become a living note graph.

  • Obsidian vault export
  • Manifest-tracked, non-destructive
  • Weekly AI debriefs
PHASE 06
06Phase

Automation NOW

Buttons replaced by a self-driving loop.

  • Vault sync on data change
  • Rolling 24h backups
  • Lifespan-managed scheduler
PHASE 07
07Phase

Reporting

Evidence you can hand to someone else.

  • PDF audit dossiers
  • News briefings with correlation
  • Every figure quoted verbatim
BACKLOG
09Phase

Engineering Debt

The honest list.

  • TypeScript across the frontend
  • Splitting the 1,673-line main.py
  • CI pipeline for the test suites
◆ ENGINEERING TRADE-OFFS

Every choice cost something

A stack list says what you used. This says what each decision bought, what it cost, and what was done about the cost. Nothing here is free, and pretending otherwise is how architectures rot.

DUCKDB

Embedded analytical store, one portable file.

BOUGHT

Columnar speed for analytical queries with zero server administration, and a journal that is a single file the user can copy, back up or delete.

COST

Connections are not thread-safe and the engine is single-writer. It is the wrong choice for distributed or high-concurrency writes.

HANDLED

Cursor-per-call on one shared connection, all writes serialized through a single lock, live backups via COPY FROM DATABASE. The hosted release will need a different write path, and that is already scoped.

OLLAMA · LOCAL INFERENCE

Both copilots run on the user's own machine.

BOUGHT

Privacy by construction and offline capability. A trading journal is among the most sensitive files a person owns; it never leaves the device, and there is no per-token cost.

COST

Answer quality and latency are bounded by local hardware, and not every local model reliably supports tool calling.

HANDLED

A graceful tool-less fallback when the model rejects tool calls, and phase 08 adds hybrid inference: managed models optional, your own Ollama still supported.

GRAPHRAG · HAND-BUILT

An entity graph over trades, not a vector store.

BOUGHT

Multi-hop retrieval over structured relationships: setup × session × liquidity, ranked by the win-rate swing a factor causes. No embedding infrastructure, and results traceable back to specific trades.

COST

No semantic similarity, so free-text questions outside the schema retrieve nothing, and the traversal logic is code I have to maintain rather than a library.

HANDLED

Traversal is capped and whitelisted, every block is grounded in canonical stats, and it is kept DB-agnostic so the same module serves the DuckDB-fed vision copilot and the payload-fed text coach.

OBSIDIAN AS THE GRAPH UI

Integrate, don't rebuild.

BOUGHT

A world-class graph explorer for the cost of a Markdown exporter, plus a data format that outlives the application.

COST

A hard dependency on someone else's app for that experience, and the sync is one-way: edits made in the vault do not flow back into the journal.

HANDLED

Manifest-tracked, non-destructive export, so the vault stays valid Markdown with or without NexTrade and the user's own notes are untouchable.

SYNC ENDPOINTS IN AN ASYNC API

Deliberately un-idiomatic FastAPI.

BOUGHT

Declaring journal endpoints sync pushes DuckDB and pandas work into worker threads, so a heavy analytics query can never block the event loop the AI streams on.

COST

Throughput on those routes is bounded by the threadpool, and it reads as a mistake until you know why it is there.

HANDLED

Only the blocking routes are sync; every AI path stays async. The reasoning is written into the module docs so the next person does not "fix" it.

VERSION-KEYED CACHE, NOT TTL

Correctness over convenience.

BOUGHT

A read cache that is never stale. Analytics are keyed on a data version, so a user never sees a number that contradicts the trade they just logged.

COST

Every mutating endpoint must remember to bump the version. That is discipline, not a guarantee, and a forgotten bump is a silent bug.

HANDLED

One documented invariant, one helper to call, and tests that assert the version moves. A TTL would have hidden the same bug behind a timer instead.

JAVASCRIPT, NOT TYPESCRIPT

The debt I chose knowingly.

BOUGHT

Velocity. Solo, across eight phases, with the hard correctness problems living in the Python analytics layer rather than in the UI.

COST

No compile-time safety across ~16,500 lines of JSX, and refactors lean on tests instead of the type checker.

HANDLED

A canonical invariants.js mirrors the backend win-rate math and a Vitest parity suite fails if the two implementations drift. The TypeScript migration is on the roadmap as debt, not sold as a feature.

LOCALHOST-ONLY, NO AUTH

The safest auth system is the one you don't ship early.

BOUGHT

No credential handling, no session management, no auth surface to get wrong while the product was still single-user.

COST

The app cannot be exposed as it stands, which is precisely why it is the first gate the web release has to clear.

HANDLED

The backend never binds wider than 127.0.0.1, and the defenses that usually come after an incident (SSRF-guarded proxy, magic-byte upload validation, allowlisted CORS, generic 500s) were built before they were needed.

◆ THE REST OF THE STACK

Chosen for a reason

TECHNOLOGYWHY IT WAS CHOSEN
LangGraphStructured tool-calling workflows with explicit state management and checkpointing: an auditable agent loop instead of an opaque chain.
React 19 + ViteA fast SPA with lazy loading and code-splitting: three chart libraries on demand while the initial bundle stays ~312 KB.
Pytest + VitestGrounding tests, invariant parity suites and live LLM evals, testing the hard parts (what reaches the AI), not just chasing coverage.
◆ WHAT I LEARNED

The real lesson

Building NexTrade taught me that integrating an LLM is the easy part. Designing the system around it so that it stays statistically sound, maintainable, secure and genuinely useful is where the software engineering actually begins. Every interesting decision in this case study came from that one constraint: never let a number, human or machine, imply more confidence than the sample size supports.
0LINES OF PYTHON
0LINES OF JSX
0TESTS GREEN
0WRITTEN INVARIANTS
0AI COPILOTS
0% LOCAL
◆ TRANSMISSION

Evidence over
intuition

Want a walkthrough of the code, the architecture, or the grounding test suite? Open a channel. The engine is always listening.

Send a signal Back to orbit