Post

The Gut Feeling: Teaching the Cluster Market Conviction

The Gut Feeling: Teaching the Cluster Market Conviction

The human gut has more neurons than the spinal cord. The enteric nervous system — four hundred million nerve cells lining the digestive tract — processes sensory input, coordinates motor responses, and produces neurotransmitters entirely independently of the brain. Serotonin, the molecule most associated with mood and cognition? Ninety percent of it is manufactured in the gut. The brain thinks it’s running the show. The gut has already formed an impression.

The cluster had eyes watching traffic cameras across five countries. It had ears listening to aircraft transponders over three continents. It had a proprioceptive sense tracking its own node health. It had an altdata service that could fetch real-time options flow, congressional trades, and earnings calendars.

What it didn’t have was an opinion.


The Embryo

This wasn’t the cluster’s first attempt at reading the market.

In September 2023, a Python project called dipylon made the same attempt. Named after the ancient Athenian city gate — the main entrance to Athens, symbolically appropriate for a financial data gateway — it queried eight upstream sources: UnusualWhales, QuiverQuant, EarningsWhispers, NASDAQ, Yahoo Finance, CoinCodex, CoinMarketCap, and CoinDesk. It offered verbose mode. It could export to a downloads folder. It was, at its core, a command-line tool you ran when you wanted data, and which produced no data when you weren’t running it.

That last part is the important distinction between a script and a service.

The data sources are almost identical to what the altdata-api serves today. The instinct was right in 2023 — these were the right receptors. UnusualWhales has the flow. QuiverQuant has the political trades. NASDAQ has the insider filings. EarningsWhispers has the calendar. The Python CLI knew which signals mattered. It just couldn’t do anything with them unless you were at a terminal, and it forgot everything the moment you closed the window.

In 2026, the cluster got around to doing something about that. The Python CLI became a Go service. Eight scrapers became thirty-plus endpoints. The --verbose flag became Prometheus metrics. The downloads folder became an S3 archival pipeline. The terminal session became a K8s Deployment in the networking namespace that runs continuously, whether or not anyone is paying attention to it.

The MCP integration — the part that wires the gut to the brain — was implemented inside Claude Code using its live MCP connection to the orchestrator as a development environment. Each of the eleven tool definitions was written and tested against the real endpoint in the same session. Development occurred against the live cluster, which ensured the tool layer reflected real runtime conditions rather than a mocked interface.

Dipylon: September 2023, Python, MIT license, eight sources, works when running. Altdata-api: February 2026, Go, always-on, thirty-plus endpoints, five scoring models, eleven MCP tools, queries while you sleep.

Same genome. Different organism.


The Raw Feed

The altdata-api had been running for two weeks. A Go service in the networking namespace, proxying four upstream data sources into a unified API: UnusualWhales for options flow, QuiverQuant for political trading disclosures, EarningsWhispers for the earnings calendar, and NASDAQ for insider trading filings. Thirty-plus endpoints. In-memory cache with per-source TTLs. Prometheus metrics. An embedded Swagger-style API explorer at the root. Clean.

But the data it served was raw. The /options/hot-chains endpoint returned whatever UnusualWhales had — fifty option chains with premium, sweep volume, open interest, delta, and an OCC option symbol that looked like this:

1
SPY260221C00590000

That’s SPY, expiring Feb 21 2026, Call, strike $590.00. The OCC symbology packs ticker, date, type, and strike into a single string with no delimiters. C0 in the middle means call. P0 means put. If neither pattern appears, you fall back to reading the delta — positive delta means call exposure, negative means put. The data was all there, scattered across dozens of chains per ticker, with premium values encoded as strings because the upstream API was designed by someone who hates type systems. (The sort of person who also encodes booleans as the strings "true" and "false", just to keep options open.)

The hot-chains endpoint alone tells you which option contracts are moving. The flow endpoint tells you individual trades hitting the tape in real-time. But neither answers the question a trader actually asks: is this ticker bullish or bearish, and how confident should I be?

That question requires synthesis. Aggregation. A score.


The Scoring Algorithm

The signal layer lives in a single file: signal.go, 289 lines. Two endpoints:

1
2
GET /signal/{ticker}   → conviction signal for one ticker
GET /signal/scan       → top 30 signals across all hot chains

Both endpoints cache for two minutes — long enough to avoid hammering upstream on every page load, short enough to track fast-moving flow during market hours.

Step 1: Parse the Chains

The first operation is fetching hot chains and classifying each one as a call or put. This sounds trivial. It is not.

1
2
isCall := strings.Contains(strings.ToUpper(c.OptionSymbol), "C0") ||
    (!strings.Contains(strings.ToUpper(c.OptionSymbol), "P0") && c.Delta > 0)

The OCC symbol check (C0/P0) is the primary signal. But some chains arrive with non-standard symbols, or the symbol field is empty — institutional dark pool crosses, index options with different naming conventions. The delta fallback handles those: positive delta means long call exposure.

Step 2: Aggregate Per Ticker

For the scan endpoint, chains are grouped by ticker_symbol. For each ticker, four values accumulate:

  • Call premium: total dollar premium on all call chains
  • Put premium: total dollar premium on all put chains
  • Large flows: count of chains with premium exceeding $500,000
  • Sweep volume: total contracts traded as sweeps (aggressive fills across multiple exchanges)

For the single-ticker endpoint, the hot chains are supplemented with real-time flow data from /options/flow. This catches smaller trades that don’t appear in the hot-chains feed but still carry directional signal.

Tickers with less than $50,000 total premium are dropped. Below that threshold, the data is noise — a few retail orders, no institutional footprint.

Step 3: Score

The scoring function is the core of the system:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func scoreSignal(ticker string, callPrem, putPrem float64,
    largeFlows, sweepVol int) Signal {

    total := callPrem + putPrem
    ratio := callPrem / total

    bias := "neutral"
    score := 0.0

    if ratio > 0.62 {
        bias = "bullish"
        score = (ratio - 0.5) * 20   // 0 → +10 range
    } else if ratio < 0.38 {
        bias = "bearish"
        score = (ratio - 0.5) * 20   // -10 → 0 range
    }

The ratio is simple: what fraction of total premium is calls? Fifty-fifty is noise. Above 62% calls, the flow is bullish. Below 38% (i.e., 62%+ puts), it’s bearish. The dead zone between 38% and 62% is classified as neutral and filtered out of the scan results entirely.

The base score maps this ratio to a -10 to +10 range. A 50/50 split produces zero. A 100% call flow produces +10. Pure puts produce -10.

Then the amplifiers kick in:

1
2
3
4
5
6
7
8
    // Large flows amplify conviction
    score += float64(largeFlows) * math.Copysign(0.4, score)

    // Sweep volume adds weight (scaled)
    if sweepVol > 0 {
        score += math.Copysign(
            math.Log10(float64(sweepVol)+1)*0.3, score)
    }

Each large flow (>$500K premium per chain) adds 0.4 to the absolute score. A ticker with three large bullish chains gets +1.2 amplification. The sign follows the bias direction — math.Copysign ensures put-heavy large flows amplify the bearish score, not dampen it.

Sweep volume gets log-scaled amplification. Sweeps are the signal within the signal — when someone needs to fill an order so urgently they route it simultaneously across multiple exchanges, they’re paying extra for speed. A thousand sweep contracts contribute more conviction than ten, but not a hundred times more. log10(sweepVol) * 0.3 captures that diminishing return.

The result for NVDA on a typical trading day:

1
2
3
4
5
6
7
8
9
10
{
  "ticker": "NVDA",
  "flow_bias": "bullish",
  "call_premium": 36144441,
  "put_premium": 5804521,
  "net_premium": 30339920,
  "large_flows": 3,
  "sweep_volume": 21868,
  "score": 9.73
}

$36M call premium against $5.8M put. Call ratio: 86%. Three chains over $500K. Nearly 22,000 sweep contracts. Score: +9.73 out of a theoretical maximum of ~13. The gut says bullish.


The Scan

The scan endpoint runs this scoring against every ticker in the hot chains feed simultaneously. Typically fifty chains produce eight to fifteen non-neutral signals after the $50K minimum and neutral-zone filters. Results are sorted by absolute score — the most convicted positions, whether bullish or bearish, float to the top.

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /signal/scan

{
  "signals": [
    { "ticker": "IWM",  "flow_bias": "bullish", "score": 11.50, "net_premium": 5358740 },
    { "ticker": "VIX",  "flow_bias": "bullish", "score": 11.47, "net_premium": 6598816 },
    { "ticker": "AMZN", "flow_bias": "bullish", "score": 11.44, "net_premium": 2347724 },
    { "ticker": "DHT",  "flow_bias": "bullish", "score": 11.20, "net_premium": 142915789 },
    { "ticker": "GLD",  "flow_bias": "bullish", "score": 11.16, "net_premium": 102471194 },
    ...
  ],
  "chain_count": 50
}

IWM (Russell 2000) and VIX both flashing bullish at +11.5 on the same scan. That’s interesting — VIX calls are typically a hedge, not a directional bet. Someone was buying small-cap exposure and volatility protection simultaneously. Draw your own conclusions. The gut doesn’t interpret. It just reports what it feels, and right now it feels like someone is very confident and also not very confident at the same time.

DHT, a crude oil tanker company, with $143M in net call premium. That’s not retail flow. That’s an institutional position. Score: +11.2.

Composite conviction scan: WBD BEARISH −8.6, SPXW BULLISH +7.6, NFLX BULLISH +6.2, AMZN BULLISH +6.2, SPY BULLISH +6.0, NVDA BEARISH −5.9 — ranked by absolute score, 9 signals from 50 chains


The Conviction Scanner

The frontend needed a way to surface these signals without forcing anyone to read JSON. The Signals tab on the altdata module renders the scan as a ranked conviction grid:

Each signal gets a horizontal bar proportional to its absolute score, colored green for bullish and red for bearish. The ticker, bias label, and numeric score sit alongside. Click any row and the detail drawer expands with the full breakdown — call premium, put premium, net, large flow count, sweep volume.

A ticker lookup box at the top lets you query any symbol directly. Type TSLA, hit enter, and the single-ticker endpoint returns the full conviction card with all the underlying numbers. The scan shows what’s hot. The lookup answers “what does the flow say about this specific name?”

The data refreshes lazily — it loads when you switch to the Signals tab, and a manual scan button forces a fresh fetch. No polling. Options flow data moves on a minutes-to-hours timescale during market hours; polling every five seconds would add load with no information gain.


The Ticker Tape

The other piece of the frontend upgrade was the scrolling ticker strip at the top of the altdata page — a continuous horizontal scroll of stock prices and crypto quotes, styled like a Bloomberg terminal feed.

The stock ticker was supposed to use Yahoo Finance. Yahoo Finance v7 (/v7/finance/quote) had been the standard unauthenticated stock data API for years. “Unauthenticated” being the operative word — Yahoo deprecated it sometime in late 2025, and by February 2026 it returns:

1
{"finance":{"error":{"code":"Unauthorized","description":"User is unable to access this feature"}}}

Even from a Cloudflare Worker — server-side, no CORS, clean IP. The API doesn’t just block browsers. It blocks everyone who isn’t paying.

The v8 chart endpoint? Same. Empty responses, connection resets. Yahoo Finance locked down.

The replacement is Stooq, a Polish financial data provider that has been quietly serving free stock quotes via a JSON endpoint with no authentication for over a decade. Nobody talk about it. The format:

1
2
GET https://stooq.com/q/l/?s=spy.us&f=sd2t2ohlcv&h&e=json
→ { "symbols": [{ "symbol": "SPY.US", "open": 683.5, "close": 689.42, ... }] }

Append .us to any ticker. Get back OHLCV data. No auth, no rate limits, no CORS headers to fight. The Cloudflare Worker proxies all ten stock symbols in parallel through a /proxy/quotes route, computes intraday change percentage from open-to-close, and returns a normalized response with 60-second edge caching.

The animation is pure CSS: two copies of the ticker content inside a track div, translated -50% over a configurable duration. will-change: transform for GPU compositing. Hover pauses the animation. The speed changes based on market status — 60% faster when NYSE is open, because the numbers are actually changing.

1
2
3
4
@keyframes ticker-scroll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}

Doubled content eliminates the gap between loop iterations. When the first copy scrolls fully off-screen, the second copy is in the exact starting position of the first. Seamless.

One refinement: the strips break out of the page container to span the full viewport width, edge to edge. The CSS trick is old but effective — width: 100vw; margin-left: calc(-50vw + 50%) on an element inside a centered max-width container. The border-radius disappears; horizontal rules replace the rounded box. A financial ticker that stops at a max-width content column feels constrained. The data should scroll across everything.


Teaching the Agents

The signal layer had an audience of one: me, looking at a browser. That’s useful but limited. The cluster runs AI agents — the agent orchestrator dispatches work from Forgejo issues to Claude Code agents. Those agents can read code, write code, and create pull requests. What they couldn’t do was check the market.

An MCP-compatible tool layer on the orchestrator fixes that. Eleven tool definitions — altdata_signal_scan, altdata_signal_ticker, altdata_flow, altdata_hot_chains, altdata_news, altdata_congress_trades, altdata_company, altdata_earnings, altdata_composite_scan, altdata_composite_ticker, altdata_insider — each with a JSON Schema describing their inputs and a route that proxies to the altdata-api’s in-cluster endpoint.

1
var altdataBase = "http://altdata-api.networking.svc.cluster.local:8080"

The tool call handler is simple: decode the request, look up the route, substitute parameters (ticker, date), fetch from altdata-api, wrap the response. Errors follow MCP convention — returned in the content body with is_error: true, not as HTTP status codes. A tool that returns 502 is broken. A tool that returns {"error": "upstream timeout", "is_error": true} is working correctly and reporting a problem.

1
2
3
4
5
6
7
8
POST /tools/call
{ "name": "altdata_signal_scan", "arguments": {} }

→ {
    "name": "altdata_signal_scan",
    "content": { "signals": [...], "chain_count": 50 },
    "is_error": false
  }

An agent working on a trading-related issue can now call altdata_signal_scan as a tool, receive the conviction signals, and incorporate market context into its decision-making. The gut feeling propagates through the nervous system.

A JupyterLab notebook template rounds out the integration — six cells walking through the signal scan, single-ticker deep-dive, raw flow analysis, hot chains breakdown, a Claude briefing builder that formats everything into markdown for an AI to analyze, and an orchestrator tool call demo. The notebook lives in the jupyterlab-claude repo and is available on any cluster-connected JupyterLab instance.


The Five Senses

A gut feeling isn’t one signal. The real enteric nervous system has five types of sensory neurons: chemoreceptors that taste the local chemistry, mechanoreceptors that feel stretch and pressure, thermoreceptors that track temperature, nociceptors that detect damage, and enterochromaffin cells that monitor the broader hormonal environment. Each type reports independently. The gut integrates them into a single qualitative assessment — this is fine or something is very wrong — weighted by which senses are loudest.

The flow score was a single receptor. One type of input, one output, one confidence range. Useful, but like trying to diagnose a stomach ache with only a thermometer.

The composite scoring system adds four more receptor types.

The Insider Nerve

NASDAQ’s Form 4 filing API (api.nasdaq.com/api/company/{ticker}/insider-trades) surfaces every officer and director trade within two business days. The endpoint requires a Referer: https://www.nasdaq.com/ header or it returns 403 — hard requirement, not a polite suggestion. The model scores on a 90-day lookback: purchases positive, sales negative, awards ignored (compensation, not conviction). Confidence scales with trade count from 0.3 (one trade) to 1.0 (ten or more).

CEOs buying their own stock is the corporate equivalent of a chef eating at their own restaurant. When the CFO of a mid-cap buys $2M on the open market, they know something the options flow doesn’t. They might also just really believe in the company. The data cannot distinguish between these two conditions and doesn’t particularly try.

The Earnings Proximity Nerve

The EarningsWhispers calendar doubles as a catalyst detector. The model checks two proximity windows: within 14 weekdays (approaching) and within 3 days (imminent). It doesn’t predict direction — earnings are binary events, and pre-earnings flow is often hedging, not conviction. Instead it returns a catalyst proximity flag that tells the composite layer to weight the other signals more heavily.

The News Nerve

The noisiest receptor. UnusualWhales’ news feed gets keyword sentiment analysis — nine bullish words (upgrade, beat, buy, outperform, raises, breakthrough, surges, rallies, positive) versus nine bearish (downgrade, miss, sell, warning, cuts, fails, drops, tanks, negative) — scored as (bulls - bears) / total * 10. Confidence is deliberately capped at 0.3–0.7. Headlines are lagging indicators; a headline saying “NVDA surges on AI demand” appeared at 2:47 PM and is describing a move that started at 9:31 AM. But five bearish articles with no counterweight is still a data point the composite should acknowledge, even if it whispers.

The Political Nerve

The enterochromaffin cell of the system — it doesn’t detect a single substance, it monitors the entire regulatory environment. Four sub-signals feed into one political/institutional score: government contracts from QuiverQuant (value-scaled, under $1M → +3, over $10M → +8), lobbying spend (active lobbying reads as mild bullish — a company investing in policy is planning to be around), inflation sensitivity (QuiverQuant’s per-stock Inflation Risk score, inverted so low risk = bullish), and UnusualWhales’ insider monthly aggregates (buy/sell totals at monthly cadence, complementing the NASDAQ model’s 90-day per-transaction view).

Confidence scales with how many sub-signals fired: one source = 0.3, two = 0.5, three = 0.7, all four = 0.9. A ticker with contracts, lobbying, low inflation risk, and net insider buying gets near-maximum confidence. A ticker that only appears in the inflation database gets a whisper.


The Composite

Five independent model scores. Five confidence values. One question: what does the cluster think?

1
2
3
4
5
6
7
type CompositeScore struct {
    Ticker      string       `json:"ticker"`
    Score       float64      `json:"score"`
    Bias        string       `json:"bias"`
    Models      []ModelScore `json:"models"`
    ModelCount  int          `json:"model_count"`
}

The composite is a confidence-weighted average with fixed model weights:

  • Flow: 35% — real-time institutional money, highest signal-to-noise ratio
  • Insider: 25% — corporate officer activity, quietest but most reliable
  • Political: 15% — government contracts, lobbying, inflation risk, institutional buy/sell
  • Earnings: 15% — catalyst proximity, context not direction
  • News: 10% — headline sentiment, noisiest signal

Each model’s contribution is weight × score × confidence. A flow score of +8.0 with 0.9 confidence and a news score of +3.0 with 0.4 confidence produce very different inputs to the weighted average. The flow signal is loud and clear. The news signal is a whisper the gut mostly ignores.

The composite scan fans out scoring for every ticker in the hot chains universe. NASDAQ rate limits require a semaphore — five concurrent calls maximum. Most of the other data is already cached (hot chains at 2 minutes, news at 2 minutes, earnings at 6 hours). The bottleneck is always the NASDAQ insider lookup, which hits a fresh API call per ticker.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /signal/composite/scan

{
  "signals": [
    {
      "ticker": "NVDA",
      "score": 7.82,
      "bias": "bullish",
      "model_count": 5,
      "models": [
        { "model": "flow",      "score": 9.73, "confidence": 0.95 },
        { "model": "insider",   "score": 4.20, "confidence": 0.80 },
        { "model": "political", "score": 1.56, "confidence": 0.70 },
        { "model": "earnings",  "score": 0.00, "confidence": 0.70 },
        { "model": "news",      "score": 6.00, "confidence": 0.40 }
      ]
    },
    ...
  ]
}

Five models reporting on NVDA. The flow is overwhelmingly bullish. Insiders are net buyers. The political model found government contracts, lobbying activity, and low inflation risk. Earnings are approaching in four days — catalyst imminent. Headlines are positive but the model doesn’t trust them much. Composite: +7.82. The gut has five senses, and four of them are saying the same thing.

Not every day looks like that. A few sessions later, the same ticker came back looking like this:

NVDA composite conviction card: BEARISH −5.91. Flow −5.1 (put dominance, 7 large flows, 67K sweeps). Insider −10.0 at 100% confidence ($254M sold, 21 trades over 98 days). Political neutral. Four models, majority bearish.

The insider model at -10.0 with 100% confidence. $254 million in sales across 21 trades over 98 days. That’s not a tax event. The gut didn’t have an opinion about what it meant. It just reported the nerve reading, labeled it bearish, and moved on.


What the Gut Knows

The signal layer evolved from one receptor to five. It still doesn’t predict price movement. It doesn’t calculate expected value. It doesn’t backtest. But where it used to take one piece of information — options flow — and distill it into a single number, it now triangulates from five independent sources: what the money is doing, what the executives are doing, what the government is buying and who’s lobbying for it, what’s on the earnings calendar, and what the headlines are saying.

That’s closer to how the real enteric nervous system works. No single neuron type tells the whole story. The chemoreceptors taste one thing, the mechanoreceptors feel another, the enterochromaffin cells monitor the hormonal backdrop, and somewhere in the integration layer a qualitative signal emerges that the brain interprets as a feeling.

NVDA at +7.82 composite doesn’t mean buy NVDA. It means the options flow is 86% calls, insiders are net buyers over 90 days, the political model found contracts and low inflation risk, earnings are approaching in four days, and the news is cautiously positive. Five senses, four agreeing, one whispering context. The cluster’s gut says something is happening here. What you do with that feeling is between you and your prefrontal cortex.

The altdata-api now serves thirty-plus endpoints across four upstream sources — options flow, political disclosures, earnings, insider filings — feeding five scoring models into a weighted composite conviction signal. Eleven MCP tools expose them to AI agents. The frontend renders them as a real-time dashboard with full-viewport ticker tape. The agents can check the market. The market doesn’t check back.

The cluster has a gut feeling. It has five kinds of nerve endings now, and it’s surprisingly hard to ignore.

This post is licensed under CC BY 4.0 by the author.