<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: grahammccain</title>
    <description>The latest articles on DEV Community by grahammccain (@grahammccain).</description>
    <link>https://dev.to/grahammccain</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3864299%2Faf068cac-f0f7-4b6d-8f25-ea88b6f8746d.png</url>
      <title>DEV Community: grahammccain</title>
      <link>https://dev.to/grahammccain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/grahammccain"/>
    <language>en</language>
    <item>
      <title>We Added 5 Regime Filters. They Don't Do Much. Here's Why That's Interesting.</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Tue, 14 Apr 2026 17:30:43 +0000</pubDate>
      <link>https://dev.to/grahammccain/we-added-5-regime-filters-they-dont-do-much-heres-why-thats-interesting-23lf</link>
      <guid>https://dev.to/grahammccain/we-added-5-regime-filters-they-dont-do-much-heres-why-thats-interesting-23lf</guid>
      <description>&lt;h2&gt;
  
  
  What we tested
&lt;/h2&gt;

&lt;p&gt;This week we added 5 regime filters to the cohort API: same_vrp_bucket (variance risk premium), same_term_bucket (VIX term structure), same_credit_bucket (HYG/LQD credit spread proxy), same_curve_bucket (yield curve slope), and same_breadth_bucket (market breadth). The academic literature on return predictability says these should materially condition forward return distributions, with VRP specifically called out as the best single-factor regime predictor.&lt;/p&gt;

&lt;p&gt;We ran the test honestly: 200 anchors with known 5d and 10d forward returns, six cohort modes per anchor (baseline + each regime filter applied alone), 2,400 total cohort runs. For each, we measured interquartile range width, [p10, p90] band width, and held-out-coverage of actuals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Across 5 and 10 day horizons, the 5 regime filters produced distribution widths that differed from baseline by 0.2 percentage points or less. Empirical coverage shifted by 1-2 percentage points. The n (cohort size) barely changed — baseline drew 198 neighbors; every filtered version drew 199-200.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5d baseline IQR: 4.17%. same_vrp: 4.21%. same_curve: 4.07%. Max shift: 0.14pp.&lt;/li&gt;
&lt;li&gt;5d baseline 80-band width: 8.78%. Max shift across filters: 0.21pp.&lt;/li&gt;
&lt;li&gt;10d baseline IQR: 5.88%. same_credit: 6.25%. Max shift: 0.37pp.&lt;/li&gt;
&lt;li&gt;Empirical [p10,p90] coverage on held-out actuals: baseline 73.5% (5d) / 71.0% (10d), regime-filtered all within ±2pp.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the filters don't bite
&lt;/h2&gt;

&lt;p&gt;The filters are real and the columns they reference are populated across 24M+ embeddings. But at the ±0.15 percentile bucketing we chose, the filter keeps roughly 70% of the base pool. When you already have 200 near-neighbors from a 24M-row kNN, dropping 30% of candidates barely changes which 200 bubble to the top.&lt;/p&gt;

&lt;p&gt;There's a second, subtler reason: the kNN search is over shape embeddings that were computed from price + volume + volatility signals. Patterns that are shape-similar tend to already be drawn from similar regimes — you don't get a roaring-bull-market pattern and a 2008-crash pattern as nearest neighbors. The regime filter is redundant with information the embedding already captured.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this tells an agent builder
&lt;/h2&gt;

&lt;p&gt;The lesson isn't that regime doesn't matter — it's that regime matters implicitly once you retrieve by shape. If you're already using shape-based kNN, layering a loose regime filter on top buys you very little. The cases where regime filtering WILL bite are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tight bucketing (±0.05 percentile) instead of loose (±0.15). This drops cohort size materially and should move distributions — at the cost of higher variance on the remaining estimate.&lt;/li&gt;
&lt;li&gt;Interaction filters (same_vrp AND same_term AND same_credit) that restrict to a specific regime combination — probably the correct default when an agent is reasoning about a specific macro setup.&lt;/li&gt;
&lt;li&gt;Regime-stratified calibration: fit separate conformal offsets per regime bucket so the bands reflect 'what happens in high-VIX high-VRP environments specifically.' This is probably where the real win lives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we're doing about it
&lt;/h2&gt;

&lt;p&gt;The filters ship as-is because they do still constrain the cohort (just mildly), they're cheap to apply, and they give agents a clean way to say 'only match within similar macro conditions.' Users who want stronger effects can stack them — our MCP tool documentation now reflects that.&lt;/p&gt;

&lt;p&gt;The next experiment is interaction filters: same_vrp AND same_term AND same_credit simultaneously, at a ±0.10 bucket. That should materially change cohort composition. If it does, we'll publish the delta; if it doesn't, we'll publish that too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CTA&lt;/strong&gt; — This is the kind of audit agent builders should demand from any historical-pattern API. If a provider claims their filters condition distributions, ask for the IQR shift. If they can't produce it, the filters are decoration. Ours are documented at chartlibrary.io/calibration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chartlibrary.io/blog/regime-conditioning-empirical-study" rel="noopener noreferrer"&gt;chartlibrary.io&lt;/a&gt;. Chart Library is the stock-market memory for AI agents — free Sandbox tier at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Retrieval to Calibrated Retrieval: Conformal Prediction on Agent Base Rates</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Tue, 14 Apr 2026 13:16:41 +0000</pubDate>
      <link>https://dev.to/grahammccain/from-retrieval-to-calibrated-retrieval-conformal-prediction-on-agent-base-rates-kh7</link>
      <guid>https://dev.to/grahammccain/from-retrieval-to-calibrated-retrieval-conformal-prediction-on-agent-base-rates-kh7</guid>
      <description>&lt;h2&gt;
  
  
  The problem we caught in our own product
&lt;/h2&gt;

&lt;p&gt;Our cohort API returns a distribution of forward returns for a chart pattern — p10, p25, median, p75, p90. An agent calling it should be able to trust those numbers for sizing. If the agent reads '[p10, p90] is [-3%, +5%]' it should act on roughly an 80% confidence that the outcome lands in that range.&lt;/p&gt;

&lt;p&gt;It didn't. We audited our own endpoint against 400 held-out anchors with known forward returns and measured how often the actual return fell inside the band we published.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nominal [p10, p90] coverage: 80%. Empirical: 68.2% (5d), 64.2% (10d).&lt;/li&gt;
&lt;li&gt;Nominal [p25, p75] coverage: 50%. Empirical: 40.0% (5d), 43.2% (10d).&lt;/li&gt;
&lt;li&gt;The medians were fine — 0.19% actual vs 0.15% predicted at 5d. The failure was entirely in the band widths.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put bluntly: if an agent used our raw bands to size a position, it was taking roughly 1.2× more risk than it thought. That's the kind of silent failure mode that makes people distrust AI-assisted trading tools, and it was hiding in our own product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why retrieved quantiles miscalibrate
&lt;/h2&gt;

&lt;p&gt;The raw cohort quantiles come from nearest-neighbor retrieval in embedding space — we pull 200 historical patterns similar to the anchor and read off their return percentiles. The math treats those 200 matches as if they were an iid sample from the same distribution the anchor came from. They aren't.&lt;/p&gt;

&lt;p&gt;Near-neighbor matches are systematically closer to each other than to the anchor — they're selected for shape similarity, not randomness. That shrinks the variance of the empirical quantiles. p10 reads as -3% when the real tail is closer to -5%. The mechanism is structural, not a bug, and it shows up in any system that publishes quantiles derived from retrieval without a calibration step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: split conformal prediction
&lt;/h2&gt;

&lt;p&gt;Conformal prediction is the standard statistical tool for this. The version we use is split conformal for quantile regression (CQR-style):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hold out a calibration set of anchors with known forward returns.&lt;/li&gt;
&lt;li&gt;For each calibration sample, compute a nonconformity score: max(p_lo - y, y - p_hi) — how far outside the raw band the actual outcome was.&lt;/li&gt;
&lt;li&gt;Take the (1 - α)(n+1)/n empirical quantile of those scores. That's your additive offset q.&lt;/li&gt;
&lt;li&gt;Calibrated band = [p_lo - q, p_hi + q]. By construction this hits ~(1 - α) coverage on exchangeable data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a band correction, not a median shift — the p50 is already unbiased. The offset is fit per-horizon, checked into the repo as services/conformal_offsets.json, and applied on every /cohort response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation on the held-out half
&lt;/h2&gt;

&lt;p&gt;Split the 800-row calibration sample 50/50. Fit the conformal offsets on one half, measure empirical coverage on the other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5d [p10, p90] — raw 68.0%, calibrated 82.5% (target 80%)&lt;/li&gt;
&lt;li&gt;5d [p25, p75] — raw 40.0%, calibrated 48.5% (target 50%)&lt;/li&gt;
&lt;li&gt;10d [p10, p90] — raw 64.5%, calibrated 80.5% (target 80%)&lt;/li&gt;
&lt;li&gt;10d [p25, p75] — raw 42.5%, calibrated 53.5% (target 50%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Numbers are now inside the tolerance a reasonable agent would expect. The offsets themselves are small in absolute terms — ±1.4pp for 5d, ±2.6pp for 10d — but the coverage gap they close is large because it compounds at the tails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What agents should ask of any retrieval API
&lt;/h2&gt;

&lt;p&gt;If you're building on any historical-pattern API, demand three things before you size a trade off a retrieved quantile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An empirical coverage number validated on held-out anchors, not just 'p10' as a label.&lt;/li&gt;
&lt;li&gt;A calibrated band you can use for sizing, separate from the raw band you use for ranking.&lt;/li&gt;
&lt;li&gt;The calibration set size and method, so you can judge whether their 80% means the same thing as your 80%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We now return calibrated_return_pct alongside return_pct on every cohort response, plus a calibration meta block with coverage_80_validated, coverage_50_validated, and n_validation. That's the evidence, not the claim. The MCP tool description tells agents which band to use for which job. None of this was here a week ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we still owe you
&lt;/h2&gt;

&lt;p&gt;Split conformal is the minimum viable calibration — it gives one offset per horizon across all cohort configurations. Small cohorts and regime-extreme buckets are almost certainly miscalibrated in their own ways, and a uniform offset under-corrects for them. The next version is bucket-aware: separate offsets by cohort size and by regime bin. That work is queued.&lt;/p&gt;

&lt;p&gt;Longer term, the calibration model should consume cohort features (size, filter-stack, distance distribution) and output band widths directly. But the honest version of the story is that 800 anchors isn't enough to fit that yet, so we shipped the simpler correction that already closes most of the gap and we'll keep widening the calibration set.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CTA&lt;/strong&gt; — Ready to build on calibrated retrieval? Grab an API key at chartlibrary.io/developers and the MCP server on PyPI (chartlibrary-mcp v1.4.0). Every cohort response now includes calibrated_return_pct and a validated coverage number.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chartlibrary.io/blog/calibrated-retrieval-conformal-prediction-on-base-rates" rel="noopener noreferrer"&gt;chartlibrary.io&lt;/a&gt;. Chart Library is the stock-market memory for AI agents — free Sandbox tier at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Build a Stock-Research Agent That Doesn't Hallucinate</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Tue, 14 Apr 2026 12:29:25 +0000</pubDate>
      <link>https://dev.to/grahammccain/how-to-build-a-stock-research-agent-that-doesnt-hallucinate-1o03</link>
      <guid>https://dev.to/grahammccain/how-to-build-a-stock-research-agent-that-doesnt-hallucinate-1o03</guid>
      <description>&lt;h2&gt;
  
  
  The problem every stock-research agent has
&lt;/h2&gt;

&lt;p&gt;If you've built an AI agent that answers questions like 'what usually happens after a breakout like this in NVDA,' you've hit the same wall everyone does: the model confidently narrates a number that has no historical backing. The base rate is either invented or pulled from the model's training cut-off, not from real data conditioned on the actual setup.&lt;/p&gt;

&lt;p&gt;The fix is structural, not prompt-engineered. You need a tool the agent calls that returns real conditional base rates — not 'on average, NVDA goes up X%' but 'given this chart shape, filtered by current regime and sector, in a corpus of historical analogs that includes delisted names, here's the distribution of forward returns.' One call, one number the agent can reason about, one sample size so it knows when to hedge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The primitive: POST /api/v1/cohort
&lt;/h2&gt;

&lt;p&gt;Chart Library's Conditional Distribution endpoint is the smallest composable unit for this pattern. You send an anchor (symbol + date) and optional filters, you get back a cohort of historical matches plus the distribution of outcomes at 1/5/10 day horizons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anchor&lt;/li&gt;
&lt;li&gt;symbol&lt;/li&gt;
&lt;li&gt;NVDA&lt;/li&gt;
&lt;li&gt;date&lt;/li&gt;
&lt;li&gt;2024-06-18&lt;/li&gt;
&lt;li&gt;horizons&lt;/li&gt;
&lt;li&gt;top_k&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;coh_...&lt;/p&gt;

&lt;p&gt;5&lt;/p&gt;

&lt;p&gt;n&lt;/p&gt;

&lt;p&gt;return_pct&lt;/p&gt;

&lt;p&gt;p10&lt;/p&gt;

&lt;p&gt;p50&lt;/p&gt;

&lt;p&gt;p90&lt;/p&gt;

&lt;p&gt;hit_rate&lt;/p&gt;

&lt;p&gt;above_entry&lt;/p&gt;

&lt;p&gt;included_delisted&lt;/p&gt;

&lt;p&gt;total_matches&lt;/p&gt;

&lt;p&gt;Every response includes a 15-minute cohort_id you can refine progressively, and a survivorship flag so the agent knows whether delisted names are part of the base rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three filter dimensions that matter
&lt;/h2&gt;

&lt;p&gt;The reason shape-only matching doesn't produce alpha on its own is that outcomes are conditional on context. The cohort API takes three filter dimensions that meaningfully shift the distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same_as_anchor&lt;/li&gt;
&lt;li&gt;filters.regime.same_vix_bucket = true keeps only matches whose VIX regime is within ±15 percentile of today's&lt;/li&gt;
&lt;li&gt;filters.regime.same_trend = true matches the sign of the SPY 20d trend at the match date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real example: NVDA 2024-06-18 unfiltered shows 54% up at 5 days across 492 analogs. Apply same_sector + same_vix_bucket and 1d drops to 48.6% up while 10d rises to 55.2% — a meaningful conditional pattern (short-term mean reversion, medium-term continuation) that's invisible in the unconditional stats.&lt;/p&gt;

&lt;h2&gt;
  
  
  The edge-mining loop (where it gets powerful)
&lt;/h2&gt;

&lt;p&gt;Single calls are fine. The real leverage is the loop: start broad, ask which filter matters, narrow, repeat. Three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST /api/v1/cohort — the initial cohort. Returns cohort_id.&lt;/li&gt;
&lt;li&gt;GET /api/v1/cohort/{id}/explain — ranks candidate filters (VIX regime, trend, recent-5-years) by how much each one shifts the above-entry hit rate. Tells the agent which dimension is actually moving the distribution for this specific setup.&lt;/li&gt;
&lt;li&gt;POST /api/v1/cohort/{id}/filter — narrows the stored cohort with whichever filter was most informative. No kNN re-run (sub-second) and returns a new cohort_id so agents can branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how agents (and humans) discover conditional structure rather than pattern-match to a canned base rate. The cohort_id keeps the expensive embedding search cached, so refinement is free. Fork, compare, keep the branch with the highest-confidence distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP: one tool call in any agent framework
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NVDA&lt;/li&gt;
&lt;li&gt;2024-06-18&lt;/li&gt;
&lt;li&gt;coh_...&lt;/li&gt;
&lt;li&gt;coh_...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop the MCP server into your CrewAI, LangGraph, AutoGen, or Claude function-calling setup. The agent discovers the tool, calls it, and returns a number grounded in real historical base rates instead of a number it made up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;The next wave of AI agents in finance will be judged on whether their answers are wrong in ways users can't detect. A hallucinated base rate is indistinguishable from a real one at the language-output level. The only structural defense is to ground every claim in a retrieval call backed by real data — conditional, explicit, sample-sized, and survivorship-aware.&lt;/p&gt;

&lt;p&gt;Chart Library's cohort primitive is built for exactly that pattern. Free sandbox tier, $29 Builder, $299 Agent (with burst + session handles + 1K req/min), and the MCP server is one pip install away.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CTA&lt;/strong&gt; — Ready to build? Grab an API key at chartlibrary.io/developers and the MCP server on PyPI (chartlibrary-mcp). The conditional distribution primitive is live on the Free tier.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chartlibrary.io/blog/how-to-build-a-stock-agent-that-doesnt-hallucinate" rel="noopener noreferrer"&gt;chartlibrary.io&lt;/a&gt;. Chart Library is the stock-market memory for AI agents — free Sandbox tier at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>3 Patterns for AI Agents That Analyze Stock Charts</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Tue, 14 Apr 2026 12:26:32 +0000</pubDate>
      <link>https://dev.to/grahammccain/3-patterns-for-ai-agents-that-analyze-stock-charts-3p2</link>
      <guid>https://dev.to/grahammccain/3-patterns-for-ai-agents-that-analyze-stock-charts-3p2</guid>
      <description>&lt;h2&gt;
  
  
  Why these three
&lt;/h2&gt;

&lt;p&gt;If you've shipped an AI agent that answers stock questions, you've hit a predictable set of failure modes: it invents base rates, stops at the first retrieval instead of probing conditional structure, and strips the narrative hooks users actually remember. This post names the three patterns we ship in our own API so you can apply them regardless of which chart-data provider you end up using.&lt;/p&gt;

&lt;p&gt;All three map to the same underlying idea: a stock-research agent should expose retrieval-first composable primitives to the LLM, and force synthesis into the final turn. Everything here is domain-independent — the techniques port to any agent answering 'what usually happens after X' questions in finance, sports, operations, or science.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Grounded base rates (no hallucinated statistics)
&lt;/h2&gt;

&lt;p&gt;The failure: Claude/GPT is happy to answer 'what usually happens after a NVDA-style breakout' with invented percentiles and sample sizes. The numbers sound real because they're formatted like real numbers.&lt;/p&gt;

&lt;p&gt;The fix: a single tool that returns real conditional distributions with sample size and survivorship flag, plus a system prompt that forbids inventing forward-return statistics. The agent MUST call the tool before making any claim about 'typically' or 'usually.'&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool returns: percentile distribution of forward returns (p10/p25/p50/p75/p90)&lt;/li&gt;
&lt;li&gt;Per-horizon MAE (max adverse excursion) and MFE (max favorable excursion)&lt;/li&gt;
&lt;li&gt;Realized vol distribution (for options or vol-scaling)&lt;/li&gt;
&lt;li&gt;Hit rates: above-entry, MFE&amp;gt;1%, MAE&amp;lt;-1%&lt;/li&gt;
&lt;li&gt;Sample size n AND survivorship flag (how many delisted names in cohort)&lt;/li&gt;
&lt;li&gt;Every response gets a cohort_id for downstream tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;System prompt template: 'You are a stock-research assistant. If the user asks about forward returns, hit rates, drawdowns, or pattern outcomes, you MUST call get_cohort_distribution first. Quote the sample size in your answer. Disclose the survivorship flag. Never quote a percentile you did not see in tool output.'&lt;/p&gt;

&lt;p&gt;Seems obvious, but agents written without this constraint invariably produce authoritative-sounding sentences with zero grounding. Add the tool + the constraint together; neither works alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: The edge-mining loop
&lt;/h2&gt;

&lt;p&gt;The failure: agents stop at the first retrieval. They call the base-rate tool once, get an answer, and write it up. Whatever conditional structure lives INSIDE the cohort — the part that actually matters for trading — never surfaces.&lt;/p&gt;

&lt;p&gt;The fix: expose two more tools the agent can chain after the initial cohort. One ranks which additional filter would move the distribution most (explain). The other applies a filter to fork a narrower cohort (refine). Agents iterate until they've identified the dimension that actually matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cohort(anchor, filters) returns cohort_id&lt;/li&gt;
&lt;li&gt;explain(cohort_id, horizon) ranks candidate filters by |shift on above-entry rate|&lt;/li&gt;
&lt;li&gt;refine(cohort_id, filter) applies that filter, returns new cohort_id&lt;/li&gt;
&lt;li&gt;Agent loops: cohort → explain → refine → maybe explain again → synthesize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why this works: sub-second refinement on a stored cohort (no repeat retrieval) means the agent can fork 5 branches and compare. Agents trained on tool use will do this naturally once they have the primitives. Agents routed through a LangGraph StateGraph execute the loop deterministically and use the model only for the final synthesis step.&lt;/p&gt;

&lt;p&gt;Outputs agents write when given these tools: 'Baseline: 54% above-entry across 491 analogs. Narrowing by same_vix_bucket drops to 48% at 5d but climbs to 55% at 10d — short-term mean reversion, medium-term continuation.' That's a real trading insight, and it only emerges from the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Named analogs for narrative
&lt;/h2&gt;

&lt;p&gt;The failure: your API returns 10 similar historical patterns, each a (symbol, date) tuple with a number. The agent dutifully lists them. The user's eyes glaze over. The most valuable piece — 'one of these analogs was Silicon Valley Bank the week it collapsed' — is invisible.&lt;/p&gt;

&lt;p&gt;The fix: attach a &lt;code&gt;named_event&lt;/code&gt; field to any match that falls inside a curated window of a famous market moment. It's a small curation job (30-50 events cover most of what retail traders and content agents care about) but it turns every match row into a potential headline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Curate notable (symbol, date-range, label, description) tuples&lt;/li&gt;
&lt;li&gt;At match-return time, check each match against the catalog&lt;/li&gt;
&lt;li&gt;Attach {slug, label, description} to matches inside a window&lt;/li&gt;
&lt;li&gt;UI renders a small colored pill; content agents pull label into a headline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events to seed: bank collapses (SIVB, FRC, SBNY, CS, PACW), M&amp;amp;A exits (TWTR, ATVI), macro inflection points (COVID crash + bottom, 2022 bear low, Russia invasion, peak CPI), narrative blowoffs (GME squeeze, 2021 growth peak), AI-era milestones (NVDA 2023-05 breakout, DeepSeek shock). 30 events get you 80% coverage.&lt;/p&gt;

&lt;p&gt;Outputs: 'NVDA today maps onto SIVB 2023-03-08 (bank collapse, -71% in 10 days) as the 4th-closest analog' — that's the sentence a market writer needs, and it comes for free from a tiny curated catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;All three patterns are live in Chart Library's API and MCP server. The system prompt in our &lt;a href="https://dev.to/blog/how-to-build-a-stock-agent-that-doesnt-hallucinate"&gt;Claude Agent example&lt;/a&gt; enforces pattern 1. The LangGraph example at github.com/grahammccain/chart-library/tree/main/integrations/langgraph shows pattern 2 as a StateGraph. Pattern 3 is a single &lt;code&gt;named_event&lt;/code&gt; field on every top_match in cohort responses.&lt;/p&gt;

&lt;p&gt;But the patterns are bigger than us. If you're building a stock-research agent on a different stack (Bloomberg API, Polygon direct, your own embeddings), implement these three primitives the same way and you'll have a system that grounds claims, discovers conditional structure, and surfaces narrative hooks automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CTA&lt;/strong&gt; — All three patterns work on the free Sandbox tier (200 calls/day). Grab a key at chartlibrary.io/developers and try the full loop in under 20 lines of Python.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chartlibrary.io/blog/3-patterns-for-agents-that-analyze-stock-charts" rel="noopener noreferrer"&gt;chartlibrary.io&lt;/a&gt;. Chart Library is the stock-market memory for AI agents — free Sandbox tier at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Eval Integrity: How We Found the Leakage and Why Our Baseline Lied</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Tue, 14 Apr 2026 12:26:31 +0000</pubDate>
      <link>https://dev.to/grahammccain/eval-integrity-how-we-found-the-leakage-and-why-our-baseline-lied-cf6</link>
      <guid>https://dev.to/grahammccain/eval-integrity-how-we-found-the-leakage-and-why-our-baseline-lied-cf6</guid>
      <description>&lt;h2&gt;
  
  
  Why this matters for agent developers
&lt;/h2&gt;

&lt;p&gt;If you're building an AI agent that calls Chart Library, you're trusting our historical base rates. If those base rates are inflated by leakage, your agent's sizing, stop placement, and confidence calibration are all downstream of a lie. That's not acceptable for anyone we want as a customer, so we audited ourselves and published what we found.&lt;/p&gt;

&lt;p&gt;Short version: our internal baseline for shape-embedding direction accuracy was 51.6% — barely above the 51.2% coin-flip floor. That 0.4 percentage points was being measured against a split that let the model find near-duplicates of every query in the training set. Once we fixed it, the number went back to ~51.2%. We never had signal where we thought we did. Now we know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #1 — Same-symbol cross-split correlation
&lt;/h2&gt;

&lt;p&gt;We split training from validation by date (train &amp;lt; 2025, val = 2025). The problem: AAPL has a chart-pattern embedding for every trading day. Its embedding on 2024-12-30 and 2025-01-02 are almost the same vector (same symbol, mostly the same preceding bars).&lt;/p&gt;

&lt;p&gt;When we ran k-nearest-neighbor evaluation on val samples, the nearest training neighbor for AAPL 2025-01-02 was AAPL 2024-12-30 — which IS in the training set. The model wasn't finding 'similar historical patterns.' It was finding itself from a few days earlier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;53.6% of validation samples had a same-symbol training neighbor within 20 trading days&lt;/li&gt;
&lt;li&gt;Direction-accuracy lift was driven almost entirely by this correlation&lt;/li&gt;
&lt;li&gt;Symbol-disjoint splits (hold out tickers entirely, not dates) give honest numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bug #2 — Forward-return window leakage
&lt;/h2&gt;

&lt;p&gt;The 5-day forward return for a training sample dated 2024-12-30 uses closing prices through 2025-01-06. That's inside the validation window. So the training label itself depends on bars the model supposedly hasn't seen.&lt;/p&gt;

&lt;p&gt;This is smaller than bug #1 (24,448 train rows affected, ~3K in our 1M random sample), but it's additive. The correct fix is a purge-and-embargo window at every split boundary equal to the longest forward horizon. We use 10 trading days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;p&gt;Going forward, every evaluation on Chart Library's embedding quality uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symbol-disjoint splits — 70% of tickers in train, 15% in val, 15% in test. No ticker appears in more than one split.&lt;/li&gt;
&lt;li&gt;Purge-embargo of 10 trading days at any remaining date boundary (e.g. walk-forward).&lt;/li&gt;
&lt;li&gt;Sample-size reporting on every reported metric, with confidence intervals.&lt;/li&gt;
&lt;li&gt;Open publication of the baseline so future model updates have to beat an honest number, not an inflated one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this implies about the product
&lt;/h2&gt;

&lt;p&gt;Pure shape-similarity direction accuracy on a symbol-disjoint, embargoed holdout is at or near 51% — essentially coin-flip. This isn't a flaw in our embeddings; it's the actual state of the problem. Predicting 5-day direction from a single chart shape is one of the hardest signal-extraction problems in finance, and it's well-documented in the academic literature that pure-price features have very low information ratio before regime/liquidity/volume conditioning.&lt;/p&gt;

&lt;p&gt;The leverage is not in the average. It's in the cohort. When you condition on regime bucket + sector + liquidity + event proximity, the conditional distribution of outcomes is materially different from the unconditional one. That's why we're building toward a Conditional Distribution API — one call, filter by context, get back path percentiles with sample size.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;INFO&lt;/strong&gt; — If you're evaluating a historical-pattern vendor and their baseline looks too good, ask them: (1) how are splits constructed, (2) what's the embargo window, (3) how do you handle same-symbol overlap. If they can't answer, the numbers are probably lying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Our ongoing commitment
&lt;/h2&gt;

&lt;p&gt;We'll keep publishing what we find, including when our own numbers go the wrong direction. Agent developers should be able to trust the calibration of any base rate we expose. That means honest audits, honest baselines, and honest docs — even when the honest answer is less impressive than the marketing one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chartlibrary.io/blog/eval-integrity-leakage-and-baselines" rel="noopener noreferrer"&gt;chartlibrary.io&lt;/a&gt;. Chart Library is the stock-market memory for AI agents — free Sandbox tier at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Multi-Agent Stock Research with CrewAI + Chart Library</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Mon, 06 Apr 2026 17:23:46 +0000</pubDate>
      <link>https://dev.to/grahammccain/multi-agent-stock-research-with-crewai-chart-library-4jgi</link>
      <guid>https://dev.to/grahammccain/multi-agent-stock-research-with-crewai-chart-library-4jgi</guid>
      <description>&lt;p&gt;When you ask a single AI agent to research a stock, it tries to do everything at once: check the chart pattern, assess the market, evaluate sectors, and synthesize a view. The result is usually shallow.&lt;/p&gt;

&lt;p&gt;Multi-agent systems fix this by splitting work across specialists -- exactly how institutional research desks operate. CrewAI lets you build that structure with AI agents and Chart Library's pattern intelligence API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Crew Design: Two Specialist Agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pattern Analyst&lt;/strong&gt; -- specializes in individual stock chart analysis. Uses Chart Library's intelligence endpoint to find historically similar charts, reads forward return statistics, and runs scenario stress tests. Always cites specific numbers: "7 of 10 similar patterns went up over 5 days, averaging +2.3%."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regime Analyst&lt;/strong&gt; -- specializes in the market-wide environment. Checks SPY/QQQ regime status, sector rotation, and crowding risk. Frames everything as historical analogy: "similar conditions historically led to..." rather than predictions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Agent backstories matter more than you'd expect. A backstory that says "you never make predictions, you present historical context" produces very different output than "you are a bold market forecaster."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Defining the Tools
&lt;/h2&gt;

&lt;p&gt;Each agent gets curated tools from Chart Library's Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;crewai crewai-tools chartlibrary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pattern Analyst tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chart_intelligence&lt;/code&gt; -- calls &lt;code&gt;cl.intelligence(symbol, date)&lt;/code&gt; for 10 most similar historical patterns + forward returns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scenario_stress_test&lt;/code&gt; -- calls &lt;code&gt;cl.scenario(symbol, market_move_pct)&lt;/code&gt; for "what if SPY drops 5%?"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;daily_top_picks&lt;/code&gt; -- calls &lt;code&gt;cl.discover(limit=10)&lt;/code&gt; for today's top patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Regime Analyst tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;market_regime&lt;/code&gt; -- calls &lt;code&gt;cl.regime(compact=True)&lt;/code&gt; for SPY, QQQ, and 11 sector ETFs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;crowding_detector&lt;/code&gt; -- calls &lt;code&gt;cl.crowding()&lt;/code&gt; for systematic risk signals&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sector_rotation&lt;/code&gt; -- calls &lt;code&gt;cl.sector_rotation(lookback)&lt;/code&gt; for momentum rankings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the Tasks
&lt;/h2&gt;

&lt;p&gt;The order matters: regime assessment first (establishes context), then stock analysis (uses that context), then synthesis (combines both).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 1 - Regime Assessment:&lt;/strong&gt; Check SPY/QQQ, identify leading/lagging sectors, evaluate crowding risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 2 - Stock Analysis:&lt;/strong&gt; For each symbol in your watchlist, report match count, quality, forward returns. Receives Task 1's output as context -- so findings can be framed like "NVDA's bullish pattern aligns with the current risk-on regime."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 3 - Synthesis:&lt;/strong&gt; Produces a structured briefing: Market Environment, Stock Highlights, Risk Factors, Bottom Line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Crew
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;crew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pattern_analyst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regime_analyst&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;regime_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;synthesis_task&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Output
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Regime Analyst&lt;/strong&gt; checks SPY: 7 of 10 historically similar regimes gained over 10 days. Tech (XLK) and Industrials (XLI) leading. Moderate crowding in large-cap tech.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Pattern Analyst&lt;/strong&gt; analyzes each symbol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NVDA&lt;/strong&gt;: 10 matches, top match 94% similarity, 8/10 positive over 5 days (+3.1% avg)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AAPL&lt;/strong&gt;: More mixed -- 6/10 positive, +0.8% average&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TSLA&lt;/strong&gt;: Historically volatile, wide outcome range&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;synthesis&lt;/strong&gt; combines both: bullish regime supports NVDA, AAPL is neutral, TSLA's wide range means sizing matters more than direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the Crew
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Risk Manager agent&lt;/strong&gt;: Run stress tests for -3%, -5%, -10% market moves across the watchlist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio Optimizer agent&lt;/strong&gt;: Suggest position sizes based on conviction and correlation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical process&lt;/strong&gt;: A Research Director that delegates dynamically instead of fixed sequence&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Start with two agents and add complexity only when you hit a real limitation. Multi-agent systems are powerful but harder to debug.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The complete working example is at &lt;a href="https://github.com/grahammccain/chart-library-mcp" rel="noopener noreferrer"&gt;github.com/grahammccain/chart-library-mcp&lt;/a&gt; in examples/crewai_tutorial.py.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Get your free API key at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt; and build your first research crew today. 24M patterns. 10 years. One API call.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>crewai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Use an MCP Server for Stock Analysis with Claude</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Mon, 06 Apr 2026 17:17:12 +0000</pubDate>
      <link>https://dev.to/grahammccain/how-to-use-an-mcp-server-for-stock-analysis-with-claude-b9c</link>
      <guid>https://dev.to/grahammccain/how-to-use-an-mcp-server-for-stock-analysis-with-claude-b9c</guid>
      <description>&lt;p&gt;Model Context Protocol (MCP) is an open standard that lets AI assistants like Claude connect to external data sources and tools. Instead of copying data into a prompt, you install an MCP server that gives the AI direct access to a service's API.&lt;/p&gt;

&lt;p&gt;Chart Library's MCP server gives Claude (and any MCP-compatible AI) access to 24 million pre-computed chart pattern embeddings, real-time regime data, sector analysis, and forward return statistics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation: Two Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;chartlibrary-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Get a free API key at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt; (200 calls/day free)&lt;/li&gt;
&lt;li&gt;Add the server to Claude Desktop: &lt;strong&gt;Settings &amp;gt; Developer &amp;gt; MCP Servers &amp;gt; Add&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enter the command: &lt;code&gt;chartlibrary-mcp --api-key YOUR_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restart Claude Desktop -- Chart Library tools appear in the tools panel&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;The MCP server also works with &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Windsurf&lt;/strong&gt;, and any editor or agent framework that supports MCP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What the 19 Tools Do
&lt;/h2&gt;

&lt;p&gt;You never call them directly -- Claude picks the right tool based on your question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pattern Intelligence (3 tools)&lt;/strong&gt;: Search for similar chart patterns, analyze ticker+date combos, get full intelligence reports with AI summaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market Context (4 tools)&lt;/strong&gt;: Market regime for SPY/QQQ, sector rotation rankings, crowding detection, scenario analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery (3 tools)&lt;/strong&gt;: Today's top patterns, trending tickers, interesting setups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio &amp;amp; Alerts (4 tools)&lt;/strong&gt;: Add holdings, portfolio analysis, pattern alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data &amp;amp; Utilities (5 tools)&lt;/strong&gt;: Ticker lookup, historical prices, API usage, accuracy stats&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example Conversation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "What does NVDA's chart look like right now? Anything interesting?"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Claude calls: &lt;code&gt;get_intelligence(symbol="NVDA")&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; "NVDA's current intraday pattern closely matches 10 historical setups. The top match is AMD from August 2024 at 93% similarity. The average 5-day return is +1.6% with 7 of 10 matches moving higher."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "What's the broader market doing? Should I be worried?"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Claude calls: &lt;code&gt;get_regime()&lt;/code&gt;, &lt;code&gt;get_crowding()&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; "The current SPY regime most closely resembles March 2024 -- a slow grind higher on moderate volume. 8 of 10 similar periods saw gains over 10 days. However, the crowding detector shows elevated correlation across tech stocks, which historically precedes short-term pullbacks in about 40% of cases."&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP vs. REST API: When to Use Which
&lt;/h2&gt;

&lt;p&gt;Both access the same data. The difference is the interface:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Best Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conversational research&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated trading systems&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building dashboards&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ad-hoc market analysis&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-agent workflows&lt;/td&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Many users keep the MCP server running in Claude Desktop for quick research while also calling the REST API from their trading scripts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;chartlibrary-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The free tier gives you 200 calls per day -- more than enough for a full day of research. For more advanced use cases, check out the &lt;a href="https://chartlibrary.io/blog/build-stock-research-agent-langchain" rel="noopener noreferrer"&gt;LangChain tutorial&lt;/a&gt; and &lt;a href="https://chartlibrary.io/blog/multi-agent-stock-research-crewai" rel="noopener noreferrer"&gt;CrewAI tutorial&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Chart Library: 24 million chart pattern embeddings. 10 years of history. Pattern intelligence for AI agents. &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
      <category>python</category>
    </item>
    <item>
      <title>Build a Stock Research Agent with LangChain + Chart Library in 20 Minutes</title>
      <dc:creator>grahammccain</dc:creator>
      <pubDate>Mon, 06 Apr 2026 17:17:11 +0000</pubDate>
      <link>https://dev.to/grahammccain/build-a-stock-research-agent-with-langchain-chart-library-in-20-minutes-39o5</link>
      <guid>https://dev.to/grahammccain/build-a-stock-research-agent-with-langchain-chart-library-in-20-minutes-39o5</guid>
      <description>&lt;p&gt;Most AI trading agents work with raw price data: OHLCV bars, moving averages, maybe some technical indicators. They can tell you &lt;em&gt;what the price is&lt;/em&gt;, but not &lt;em&gt;what it means&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Chart Library's API gives agents something they can't get anywhere else: pre-computed pattern similarity across 24 million embeddings spanning 10 years and 19,000+ symbols. Instead of asking "what's the price of NVDA?", your agent can ask "find the 10 most similar historical charts to NVDA right now and tell me how those patterns resolved."&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;p&gt;You need three packages and two API keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;langchain langchain-openai chartlibrary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Get a free Chart Library API key at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt; (200 calls/day on the free tier)&lt;/li&gt;
&lt;li&gt;Set your environment variables: &lt;code&gt;CHART_LIBRARY_KEY=cl_...&lt;/code&gt; and &lt;code&gt;OPENAI_API_KEY=sk-...&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can swap OpenAI for Anthropic, Groq, Ollama, or any LangChain-compatible model. Just change the LLM initialization -- the tools stay the same.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating the Tools
&lt;/h2&gt;

&lt;p&gt;LangChain agents work by selecting from a set of tools based on the user's question. Each tool wraps one or more API calls and returns a formatted string the LLM can reason over. We define five tools that cover Chart Library's core capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  The chart_intelligence Tool
&lt;/h3&gt;

&lt;p&gt;Here's the core tool that powers most agent interactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chart_intelligence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get full pattern intelligence for a stock ticker.
    Returns the 10 most similar historical chart patterns,
    what happened after those patterns (1/3/5/10-day forward
    returns), and an AI-generated summary.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intelligence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Format matches, forward returns, and summary
&lt;/span&gt;    &lt;span class="c1"&gt;# into a readable string for the LLM
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;formatted_result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool extracts the top 3 matches with similarity scores, forward return statistics (average return, win rate) for each horizon, and the AI-generated summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Market Regime and Sector Tools
&lt;/h3&gt;

&lt;p&gt;Beyond individual stocks, agents need market context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;market_regime&lt;/strong&gt; -- calls the &lt;code&gt;/regime&lt;/code&gt; endpoint for SPY, QQQ, and all 11 S&amp;amp;P sector ETFs with historical pattern matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sector_rotation&lt;/strong&gt; -- sector momentum rankings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crowding_detector&lt;/strong&gt; -- checks if many stocks are trading the same pattern (signals systematic risk)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;scenario_analysis&lt;/strong&gt; -- stress-tests a stock against a hypothetical market move&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the Agent
&lt;/h2&gt;

&lt;p&gt;With the tools defined, assembling the agent takes about 10 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chart_intelligence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;market_regime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;crowding_detector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scenario_analysis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;sector_rotation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_tool_calling_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Conversation
&lt;/h2&gt;

&lt;p&gt;Here's what a real session looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "What does NVDA's chart look like right now? How did similar patterns play out?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent calls:&lt;/strong&gt; &lt;code&gt;chart_intelligence("NVDA")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent:&lt;/strong&gt; "NVDA's current chart pattern matches 10 historical patterns with high similarity. The top match is AMD from 2023-08-14 at 94% similarity. Looking at what happened after these patterns: over 5 days, the average return was +1.8% with 7 of 10 matches moving higher. Over 10 days, the average was +2.4% with 6 of 10 positive."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "What about the broader market? Is there anything concerning?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent calls:&lt;/strong&gt; &lt;code&gt;market_regime()&lt;/code&gt;, &lt;code&gt;crowding_detector()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent:&lt;/strong&gt; "SPY's 30-day change is at +3.2%, with 8 of 10 similar historical regimes seeing positive 10-day returns. However, the crowding detector is showing elevated readings -- many stocks are trading in lockstep, which historically signals higher correlation risk."&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the Agent
&lt;/h2&gt;

&lt;p&gt;The five tools above cover the core use cases, but Chart Library's API has more endpoints you can wrap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alerts&lt;/strong&gt;: Let the agent set up pattern alerts ("tell me when a chart similar to AAPL 2024-01-15 appears again")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio analysis&lt;/strong&gt;: Connect the agent to a user's portfolio for nightly pattern monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical backtest&lt;/strong&gt;: Run pattern-based backtests to validate strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-timeframe&lt;/strong&gt;: Search across 5min, 15min, 30min, 1hr, or multi-day windows&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The full tutorial code is on GitHub at &lt;a href="https://github.com/grahammccain/chart-library-mcp" rel="noopener noreferrer"&gt;github.com/grahammccain/chart-library-mcp&lt;/a&gt; in the examples/ directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's Next: Multi-Agent Workflows
&lt;/h2&gt;

&lt;p&gt;A single agent is useful. The next level is multi-agent workflows where specialized agents collaborate -- a Pattern Analyst, a Regime Strategist, and a Risk Manager that discuss, debate, and produce a unified research brief.&lt;/p&gt;

&lt;p&gt;Chart Library's MCP server (&lt;code&gt;pip install chartlibrary-mcp&lt;/code&gt;) works with any MCP-compatible agent framework -- including Claude Desktop, Cursor, and Windsurf.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Get a free API key at &lt;a href="https://chartlibrary.io/developers" rel="noopener noreferrer"&gt;chartlibrary.io/developers&lt;/a&gt; and start building. 24 million chart pattern embeddings, 10 years of history, one API call.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>langchain</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
