Product · 2026 · MarketLane AI
Three generations of an LLM stock-scan pipeline
MarketLane scans the NSE every day for high-quality swing setups. The scan pipeline has been rebuilt three times. Each generation moved accuracy, cost and latency in different directions, and the one in production is the one that balances all three.
What MarketLane does
MarketLane AI is a market-intelligence product for active NSE investors. It combines deterministic market analytics, signal detection and LLM reasoning into one decision-support system: dashboards, screeners, stock-level intelligence, alerts and an AI-written technical read for every candidate.
The heart of the product is the daily scan. Every evening it takes the full stock universe, narrows it to roughly a hundred candidates and asks: which of these are genuinely worth a swing trader’s attention tomorrow, and why?
That question is where most of the engineering lives. This page is about how the scan pipeline evolved, what each generation cost, and the architecture and practices underneath it.
The metric that drove every decision
Before touching the pipeline I needed a number that would not move with prompt wording.
Each trading day a reference set of top setups is produced by the most expensive analysis path we have, run over every candidate with no screening in between. The daily pipeline is then scored on how many of those it surfaced, weighted by the star rating of each setup so that missing a five-star setup hurts more than missing a two-star one.
Σ stars of top setups the pipeline surfaced
hit rate = ─────────────────────────────────────────────────
Σ stars of all top setups that day
The second and third numbers are the obvious ones: model cost per 100-symbol scan, and wall-clock time for the whole universe. Every architecture change below was judged on those three together.
Three generations side by side
v0 · Prefilter + single shot
RetiredOne structured LLM call per symbol over a feature packet built in code. Heavy caching kept it cheap, but one model had to discover, judge and explain in a single pass.
1 model call per symbol
v1 · Discover, analyze, validate, criticize
In productionA high-recall screen, a multimodal analyst reading daily and weekly charts, a deterministic validator over every measurable claim, and an adversarial critic. Code resolves the final state.
3 to 4 model calls per surviving symbol
v2 · Multi-agent analysis and ranking
Shadow evaluationIndependent technical and fundamental analysts, a risk manager backed by a deterministic risk engine, a critic, a verifier, one bounded correction cycle, then deterministic universe-level ranking.
5 model calls per symbol, up to 10 with correction
| Version | Top-setup hit rate | Cost per 100-symbol scan | Wall clock, 100 symbols |
|---|---|---|---|
| v0 | 81 to 85% | $1.4 | 8 min |
| v1 | 90% | $4.5 ($2.0 with web search off) | 17 min (9 min with setup gate + small-model critic) |
| v2 | 87 to 88% | $7 to 9 | 55 min (20 to 25 min with gate, fundamentals after technical pass) |
v0: intelligent prefilter and one shot
The first pipeline was deliberately simple. A deterministic prefilter cut the universe to around a hundred names using trend, liquidity and volatility rules. For each survivor, code built a compact feature packet (returns, moving-average structure, relative volume, nearby levels) and a single structured LLM call produced the verdict and the explanation.
Caching did most of the heavy lifting. Feature packets, prompts and model responses were keyed on the market snapshot, so re-runs and partial re-scans were nearly free, and a full universe cost about $1.4.
It worked well enough to ship, with a hit rate of 81 to 85%. Its ceiling was structural. One model, in one pass, had to discover candidates, judge them, check its own arithmetic and write the explanation. When it was wrong there was no second opinion and no cheap way to tell.
v1: discover, analyze, validate, criticize
The second generation splits those responsibilities into stages, each with one job and the cheapest model that can do it.
Deterministic prefilter code universe → ~100
↓
High-recall screen small batched, KEEP / BORDERLINE / REJECT
↓
Chart generation code daily + weekly images
↓
Deep multimodal analyst vision reads the charts, proposes setup + thesis
↓
Deterministic validator code recomputes every measurable claim
↓
Bounded correction model only if the validator found contradictions
↓
Adversarial critic small looks for what the analyst missed
↓
Final-state resolver code ACTIONABLE · WATCH · DEVELOPING · REJECT
Three ideas carry this design.
High recall first, precision later. The screen is intentionally permissive. Its question is “could this plausibly be a worthwhile setup?”, and uncertainty favors survival. Precision is added by the deeper, more expensive stages rather than lost at the gate.
Code owns facts. Moving averages, ATR, volume ratios, pivots, breakout conditions and trade geometry are computed in code and handed to the model. The validator then recomputes every number the analyst asserts. A claim that does not match the data is a contradiction, and a contradiction triggers one bounded correction, never a free retry loop.
An adversary before publication. The critic receives the analyst’s thesis and the validated facts and is asked only to find weaknesses: extension, overhead supply, weak volume confirmation, a trigger that has not actually fired. It is also allowed to return nothing. Code, not the critic, resolves the final state from the combined evidence.
The multimodal step is where Gemini earns its place: daily and weekly charts are analyzed together, which is how a discretionary trader reads structure and which the feature packet alone never captured. Reasoning stages run on Claude Sonnet 5.5 and the critic on Claude Haiku 5.5.
The result was a hit rate of 90% at about $4.5 per 100 symbols with web search enabled, or $2 without it. Per-symbol latency roughly doubled against v0, which is acceptable for a nightly scan and is where the optimisations below were aimed.
v2: multi-agent analysis and ranking
The third generation asks a broader question. v1 says whether a setup is good. v2 tries to say how good it is relative to every other candidate, with fundamentals and risk included, and with every claim traceable to frozen evidence.
Freeze universe, as-of cutoff, policies, prompt hashes
↓
Acquire and seal the evidence bundle market · technical facts · fundamentals · events
↓
Deterministic data gates stale bars, missing periods, unsupported sectors
↓
Technical agent ∥ Fundamental agent independent, neither sees the other's verdict
↓ ↓
Risk engine (code) + Risk-manager agent code sizes and vetoes, the model advises
↓
Critic challenges all three reports
↓
Verifier facts and arithmetic in code, semantics in a model
↓
One bounded correction cycle rerun only the implicated roles
↓
Deterministic resolver → universe barrier → deterministic ranking → optional allocation
The important design choices are the ones that keep authority out of the models.
- Evidence is immutable and addressable. Every fact gets an ID with a value, unit, source and availability time. A claim must reference the facts that support it, and the verifier resolves those references rather than trusting that they exist.
- Roles are typed calls, not autonomous agents. Each role has its own prompt, schema and authority. Agents cannot browse, call tools, amend evidence or see credentials. There is no open-ended debate loop.
- Critic findings have no veto until verified. A fabricated criticism is marked refuted and cannot suppress a candidate just because it was labelled critical.
- Ranking never comes from an LLM. Models propose categorical rubric labels with evidence. Code validates the prerequisites, maps labels to points, applies penalties for verified concerns, and sorts with deterministic tie-breaks. Missing data excludes a candidate rather than being scored as zero.
- Point-in-time discipline. Fundamentals and events must have been publicly available before the cutoff. Retrieval time is not proof of historical availability.
In shadow evaluation v2 scores 87 to 88% on the hit rate, slightly below v1, at $7 to 9 per scan and several times the latency. That is not a failure. The hit-rate metric is a technical-setup metric, and v2 is deliberately spending budget on fundamental and risk dimensions that it does not reward. It is also the pipeline whose outputs are fully replayable, which matters more as the product moves toward portfolio-level reasoning.
Why v1 is the one in production
| v0 | v1 (prod) | v2 (shadow) | |
|---|---|---|---|
| Hit rate, star-weighted | 81 to 85% | 90% | 87 to 88% |
| Cost per 100 symbols | $1.4 | $4.5 ($2 without web search) | $7 to 9 |
| Per-symbol latency | ~9 s | ~20 s (~10 s optimised) | ~65 to 85 s (~22 s optimised) |
| 100 symbols, two workers | ~8 min | ~17 min (~9 min optimised) | ~55 min to 1.2 h (~20 to 25 min optimised) |
| Model calls per symbol | 1 | 3 to 4 | 5, up to 10 with correction |
v1 is the best balance the product has found: the highest measured accuracy, a cost that supports daily scans for a growing user base, and a nightly wall clock with room to spare. It is the one being refined. v2 runs beside it on the same frozen universes, and promotion is gated on predeclared quality, cost and latency thresholds rather than on a good week.
Optimisations that moved the numbers
Most of the cost and latency wins came from deciding what not to send to a model, not from faster models.
- Setup gate before the expensive stages. A deterministic check on setup state drops candidates that cannot be actionable yet, so the multimodal analyst and critic only run where they can change the answer. This alone halves v1’s average per-symbol time.
- Right-size every stage. The screen and the critic run on a small model. The multimodal analyst is the only stage that needs chart vision. Reasoning-heavy synthesis is the only stage that needs the large model.
- Batch the recall screen, retry only the gaps. Stage one evaluates candidates in batches. One malformed result retries one candidate, never the batch.
- Fundamentals after the technical pass. In v2, running the fundamental agent only for candidates that pass the technical gate cuts projected wall clock by more than half with no change in ranking for the survivors.
- Cache on the data snapshot, not the symbol. A cache key is the candidate plus the market snapshot plus the feature and prompt versions. A new candle invalidates the analysis; a re-run on the same snapshot is free.
- Prompt caching and compact context. Stable instructions and rubrics sit in the cached prefix; the per-candidate packet is the only variable part.
- Bounded concurrency. Candidate-level parallelism behind a semaphore sized to provider quotas, with two workers per scan in production.
- Web search as a switch. External context is the single largest cost line in v1. It is a per-scan policy, measured separately, so the product can trade it for cost on quiet days.
Infrastructure architecture
React UI
│
Authenticated FastAPI create run · status · evidence · rankings · cancel
│
Durable run record + outbox PostgreSQL, idempotency key per request
│
Queue (SQS) ──────────► Scan workers (bounded concurrency, lease + fencing token)
│
┌──────────────────┼──────────────────┐
│ │ │
Market data Model gateway Object storage
(broker-first OHLCV, (Vertex AI · Anthropic; (charts, raw and validated
provenance recorded) per-role model config) reports, content-addressed)
│
PostgreSQL: runs · candidates · stages · attempts · rankings
│
Langfuse traces · metrics · structured logs
The application runs on AWS. Model inference runs behind a thin gateway against Vertex AI and Anthropic, so the product is not architecturally tied to one provider and each role can be re-pointed by configuration. The same repository protocol has a SQLite and local-file implementation, so a full pipeline run works on a laptop against the same durable state machine, and both backends pass one shared contract test suite.
Reliability patterns
- Explicit state machines, one for the scan and one per candidate, with execution state kept strictly separate from trading state.
- Persist each stage before dependent work. A failed fundamental call never triggers a fresh technical call on resume.
- Stage identity is a hash of run, symbol, stage, evidence, upstream report hashes, config, prompt and model. Retrying the same identity returns the same artifact. Late responses from an expired lease cannot overwrite newer results.
- Budgets are reserved before a call and reconciled after. Admission fails closed when pricing or configuration is missing.
- Circuit breakers on timeout, rate-limit and schema-failure rates stop a scan as PARTIAL rather than publishing misleading results.
- Atomic publication. A ranking is written in full and then a single pointer flips. A failed publish leaves the previous one untouched.
- Duplicate delivery is expected. Idempotency keys, a transactional outbox and conservative accounting for calls that may have run.
Observability: Langfuse, OpenTelemetry and CloudWatch
A nightly scan is a few hundred model calls spread across queue-driven workers, two cloud providers and three model vendors. When a scan is slow, expensive or wrong, the question is always “which stage, which symbol, which call?”. The telemetry is built so that question has a single answer, reachable from any of three entry points.
scan worker / API (Lambda)
│
┌───────────────────────┼────────────────────────┐
│ │ │
OpenTelemetry Langfuse SDK EMF metrics + logs
TracerProvider + (built on OTel, shares (stdout, no extra
MeterProvider the same trace tree) IAM permissions)
│ │ │
OTLP/HTTP → collector Langfuse Cloud CloudWatch metrics,
(Grafana, X-Ray, sessions · traces · dashboard, alarms,
Honeycomb, …) generations · scores Logs Insights
│ │ │
└─── same trace id ─────┘ run_id / stage attempt records
One trace tree
The application registers one global OpenTelemetry tracer and meter provider. The Langfuse SDK is itself built on OpenTelemetry and attaches its span processor to whatever provider is registered, so a Langfuse generation and the OTel spans around it are the same trace with the same trace id. Nothing is instrumented twice.
The span hierarchy follows the pipeline:
| Span | What it wraps | Key attributes |
|---|---|---|
| Worker invocation | One queue message, including continuations after a Lambda soft deadline | run id, continuation flag, worker result |
| Scan stage | One stage attempt for one symbol: screen, analyst, validator, critic, resolver | stage, symbol, attempt, status, artifact reference |
| Model request | One structured call to a provider | gen_ai.system, requested and served model, finish reason, token usage, cache read and write tokens, thinking and effort settings, fallback used, error type |
Model spans use the OpenTelemetry GenAI semantic conventions where they exist, with MarketLane-specific attributes namespaced separately. The served model is recorded from the provider response, never from configuration, which is how a silent provider-side model swap gets noticed.
Metrics with the right dimensions
Counters and histograms are emitted from the same instrumentation: model calls, input tokens, output tokens, estimated USD, and a stage-latency histogram. Every one is dimensioned by stage, model, status and environment. That is the difference between “cost went up 30%” and “the critic’s cache-read ratio dropped after Tuesday’s prompt change”. Symbols, run ids and user ids are deliberately kept out of metric dimensions and live in structured logs and traces instead, so cardinality stays bounded.
Langfuse as the AI-specific lens
Langfuse carries what a generic tracing backend does not model well: prompt versions, structured-output schemas, per-generation token and cache accounting, and scores.
- Sessions keyed by run id. Each daily scan creates one root trace, and every model generation in that scan records the same session id. Opening a session shows the scan-level funnel beside its per-symbol generations.
- Scores make operations filterable. Alerts written, AI calls, cache hits, fallbacks, error count and estimated cost are attached to the root trace as numeric scores, so a week of scans can be sorted by cost or fallback rate without leaving Langfuse.
- Stage events for shadow experiments. Frozen-snapshot experiment runs log one event per stage attempt with outcome, elapsed time, served model and tokens, and carry the OTel trace id so the same attempt can be opened in the OTLP backend.
- Redaction by default. Prompts, chart images and parsed model outputs are replaced by a hash and an approximate byte size before leaving the process. Full capture is a separate explicit flag, off in every deployed environment, because scan prompts contain user-supplied notes and pasted fundamentals.
CloudWatch as the paging source
Langfuse and the OTLP backend are for diagnosis. Paging comes from CloudWatch, where the signals are cheapest and closest to the infrastructure.
- Native metrics for API Gateway 5xx, Lambda errors, throttles and near-timeout durations, queue age and dead-letter depth, and database storage and connections.
- Embedded Metric Format from stdout for application metrics: API latency and errors, model calls, tokens, estimated cost and fallbacks, market-data failures, scan outcomes and the count of stale active scans. Emitting through logs means the functions need no extra metrics permission.
- A scan watchdog on a five-minute schedule reads the database and emits a stuck-scan metric for runs that have been queued or running past a threshold. It never starts or cancels scans.
- Alarms are opt-in and only notify through an SNS topic that already has an on-call subscription. The stack never creates an unowned alert route.
Initial SLOs are operational targets rather than customer commitments: API availability and p95 latency, scans finalized within a window of starting, queue age, model fallback rate and market-data failure rate, each reviewed against two weeks of staging baseline before thresholds are tightened.
Cost accounting that matches the bill
Every priced provider call made under a stage checkpoint is written to a spend ledger with model, uncached, cache-read, cache-write and output tokens, and USD at published rates, including the TTL multiplier for prompt-cache writes. A budget policy enforces a hard programme cap and a per-experiment cap: launch is refused when the per-symbol estimate exceeds the remainder, and every call re-checks the ledger. Exhaustion is a terminal, recorded run status, not an exception in a log. The provider console remains the billing authority; the ledger exists so that the cost figures on this page can be attributed to stages and models.
Rules the telemetry follows
- Fail open. A Langfuse or OTLP outage never changes a stage checkpoint, retry or scan outcome. With the flags off, the no-op tracer is used and spans cost nothing.
- Flush before freeze. Workers flush batched spans and metrics before returning, so a Lambda freeze does not drop the tail of a scan.
- Secrets stay out of config. Langfuse credentials are loaded at runtime from a secrets manager entry whose ARN is the only thing in infrastructure state. Roles get read access to that one entry.
- Cross-link everything. Stage-attempt records in the database carry the trace id. From a bad row in the UI you can reach the Langfuse generation, the OTel span and the CloudWatch log line.
Evaluation
The hit-rate metric above is the headline, but it sits inside a wider harness.
- Recall against an oracle. The screen is measured against the expensive analyst run directly over the whole universe. A high pass rate is not a quality signal; recall against the reference set is.
- A false-negative dataset. Every setup the screen dropped and the oracle kept becomes a labelled record used to tune the screening prompt.
- Forward outcomes. Each scan is frozen at its timestamp. Trigger reached, returns at one, three, five and ten sessions, maximum favorable and adverse excursion, and stop-or-target-first are computed later and compared across final state, setup family and critic severity.
- Shadow comparison on identical inputs. v1 and v2 run on the same frozen universes and cutoffs, with ablations (technical only, technical plus fundamental, full critic and verifier) so each stage’s contribution is visible.
- No post-hoc thresholds. Promotion gates for cost, latency and quality are declared before results are collected.
Principles that held across all three generations
- Deterministic before probabilistic. If software can compute it reliably, a model does not.
- Models interpret; code decides. Every verdict, risk veto and rank is produced by code from validated inputs.
- High recall at the gate, precision in depth. Cheap stages are permissive; expensive stages are selective.
- AI output is application data. Strict schemas, forbidden extra fields, bounded lists, finite numbers, versioned contracts.
- No fake probabilities. The system never emits “85% chance of success”. Judgments are technical interpretations, not calibrated forecasts.
- Every decision is explainable. A verdict ships with its evidence and its concerns, and the raw model response is retained next to the validated one.
- Freeze, hash, replay. Universes, cutoffs, prompts and policies are frozen per run, so any published row can be reproduced.
- Measure the trade-off, not the demo. A new generation is adopted only when accuracy, cost and latency are all measured on the same inputs.
Security boundaries
Model providers are treated as external dependencies. Document text, uploaded notes and web results are data, never instructions, and the pipeline tests prompt-injection text in source material explicitly. Agents have no tool access and no credentials. Reads are owner-scoped, inaccessible runs return 404, and public requests cannot override prompts, models or policies. Secrets stay server-side; the frontend only ever talks to MarketLane’s own API.
What is next
The near-term work is on v1: finishing the setup-gate rollout, moving the correction step to the small model where the validator’s feedback is purely numeric, and tightening the web-search policy. In parallel, v2’s shadow evaluation is collecting enough prospective data to decide whether its fundamental and risk dimensions earn their cost. Longer term, the evidence-and-ranking machinery from v2 is the foundation for portfolio-level reasoning and for connecting the scan to MarketLane Research, where filings and earnings material are retrieved as cited evidence.
Related project
MarketLane Research covers the other half of the problem: evidence-grounded equity research over SEC filings, XBRL facts and investor material through retrieval-augmented generation. The scan pipeline produces structured market intelligence; Research produces cited fundamental evidence. Together they form the base of a broader AI-native investment intelligence platform.