
Building a News-To-Trade Workflow for Polymarket
Prediction markets move on information. A market resolves on a specific, checkable question — did the rate cut happen, did the bill pass, did the company hit the number — and every time new information about that question becomes public, there's a brief window before the market price catches up with it. Most of that window closes in minutes. Nobody is checking Polymarket by hand fast enough to consistently catch it.
This post walks through a pipeline that does the watching for you: Perigon delivers matching news the moment it finds it, a Sigrex Data Hook receives that payload, an LLM Reaction researches Polymarket programmatically, reasons about whether the story actually points to a mispriced market, and — only when it finds something concrete — hands off a structured recommendation to a risk-checked executor. Nothing here promises a guaranteed edge. What it does is turn "I wonder if that news affects any Polymarket markets" from a manual, too-slow-to-matter question into an automated, consistent one.
The pipeline, end to end
Perigon Signal / Monitor
│ (native webhook delivery)
▼
Sigrex Data Hook ──▶ LLM Reaction (research agent)
│
▼
structured JSON recommendation
│
▼
Code Reaction (risk-gated executor)
│
▼
Signal Bot ──▶ PolymarketTwo Sigrex strategy types do the work:
- An LLM Reaction reads the incoming Perigon payload, searches Polymarket's public API for a market it might actually relate to, checks the market's real resolution criteria and current price, and reports back a structured verdict — trade or skip, with a confidence score and reasoning attached.
- A Code Reaction receives that verdict, re-validates it (never trust a model's output blindly — more on that below), sizes the position, and forwards an order to a Polymarket signal bot.
Nothing executes a trade until it's passed through both.
Step 1 — Deliver Perigon into a Sigrex Data Hook
Create a Data Hook in Sigrex. Copy the URL — it looks like https://hook.sigrex.io/data/<hash>.
On the Perigon side, open the Signal or Monitor you actually care about and add that URL as a webhook destination. Point the Signal at the beat you want covered: Fed speakers, a bill, a ticker, a country, a named official. Contextual Signals are usually cleaner than raw keyword alerts, because Perigon is already doing a first pass of "is this actually about the thing I asked for."
Do not assume a field layout. Perigon payloads vary by product (Signal vs Monitor vs a custom schema you defined in the delivery settings). Send one test event, open the Data Hook's incoming request in Sigrex, and write down the actual keys for:
- headline / title
- summary / body
- source URL
- published timestamp
- any entity or topic tags Perigon already attached
Those keys are what the LLM Reaction will see inside {{data}}. If you guess story.name and Perigon sent title, the research step is reading noise.
A Data Hook does not trade. It validates the POST, stores it, and forwards the body to every Reaction attached to it. Attach the LLM Reaction here. That is the entire ingestion layer.
Step 2 — Let an LLM research each story
This is the part that can't just be keyword matching. Finding a market that shares words with a headline is trivial; figuring out whether the headline actually changes what that market should be priced at requires real reasoning about the market's specific resolution criteria versus what the news actually says. That's exactly the kind of judgment call worth handing to an LLM Reaction rather than hand-rolled heuristics.
The reaction:
- Pulls out the concrete entities and claims from the Perigon payload.
- Searches https://gamma-api.polymarket.com/public-search?q=... for candidate markets.
- Pulls full details (resolution question, token IDs, liquidity, end date) for the plausible ones.
- Checks the market's live price.
- Reasons through whether the price looks stale relative to what the news implies — and just as importantly, when it doesn't, says so.
- Reports a structured verdict, only forwarding it onward when it actually found something.
The key design choice: it's told explicitly not to force a recommendation. An LLM that's rewarded for always finding something will always find something — most of what it doesn't act on is the point, not a shortfall.
A prompt that works in this shape looks like this (adapt the field names to the payload you actually received):
You receive a Perigon news payload as JSON.
{{data}}
Your job is not to trade. Your job is to decide whether this item
materially changes the correct price of a live Polymarket market.
Steps:
1. Extract the concrete claim, named entities, and what would have
to be true for this news to matter.
2. Search Polymarket public markets for candidates that could
resolve on that claim. Ignore loose keyword overlap.
3. For each plausible market, read the official resolution criteria,
end date, liquidity, and current price.
4. Ask: given only this news, is the current price stale?
If the news is already priced in, off-topic, or too vague to
map onto the resolution text, skip.
5. Reply with JSON only:
{
"action": "trade" | "skip",
"confidence": 0.0,
"slug": "polymarket-market-slug-or-null",
"outcome": "Yes" | "No" | null,
"side": "BUY" | "SELL" | null,
"reasoning": "short justification tied to resolution criteria",
"priceSeen": 0.0,
"headline": "as received",
"url": "source url"
}
Do not invent a market. Do not upgrade "maybe related" into "trade".
Most inputs should be "skip".When the model returns trade, forward that JSON to a second Data Hook — the one your Code Reaction is attached to. Keep the news hook and the execution hook separate. The LLM should not be able to talk to the signal bot directly.
Step 3 — Never trust model output directly
A Code Reaction sits between the LLM's output and any actual money. It runs when the second Data Hook receives the verdict. It:
- Validates the payload shape (an LLM's JSON is free-form output, not a fixed contract your own code produced — treat it accordingly).
- Re-gates on confidence independently of whatever threshold the prompt used internally.
- Re-checks the live price against what the LLM saw, and rejects anything that's drifted too far by the time it's ready to execute.
- Applies a cooldown per market so the same signal can't fire repeatedly.
- Includes a circuit breaker that halts the strategy after a set number of trades, so a bad prompt or an unusually confident run can't run unattended indefinitely.
That separation — research agent proposes, deterministic code disposes — is the difference between "an AI trades my account" and "an AI surfaces opportunities that a set of fixed rules is allowed to act on."
A starting point for the executor:
// Sigrex Code Reaction — risk-gates an LLM verdict, then posts
// a Prediction Signal Bot payload. Attach this to the *verdict*
// Data Hook, not the Perigon news hook.
const BOT_WEBHOOK_URL = $.Env.BOT_WEBHOOK_URL;
const BOT_KEY = $.Env.BOT_WEBHOOK_KEY;
const MIN_CONFIDENCE = Number($.Env.MIN_CONFIDENCE || 0.8);
const MAX_PRICE_DRIFT = Number($.Env.MAX_PRICE_DRIFT || 0.04);
const COOLDOWN_MS = Number($.Env.COOLDOWN_MS || 30 * 60 * 1000);
const MAX_TRADES = Number($.Env.MAX_TRADES || 8);
const state = (await $.Storage.get()) || { trades: 0, lastBySlug: {} };
if (state.trades >= MAX_TRADES) return;
let verdict;
try {
verdict = JSON.parse($.Request.data);
} catch (e) {
return;
}
if (verdict.action !== "trade") return;
if (typeof verdict.confidence !== "number" || verdict.confidence < MIN_CONFIDENCE) return;
if (!verdict.slug || !verdict.outcome || !verdict.side) return;
if (verdict.side !== "BUY" && verdict.side !== "SELL") return;
const last = state.lastBySlug[verdict.slug] || 0;
if (Date.now() - last < COOLDOWN_MS) return;
// Re-check the live book against what the model claimed it saw.
// Confirm the exact public endpoint and response shape against
// Polymarket's current API before running this against size.
if (typeof verdict.priceSeen === "number") {
const live = await $.Http.get(
"https://gamma-api.polymarket.com/markets",
{ slug: verdict.slug }
);
if (live.status !== 200) return;
const markets = JSON.parse(live.text);
const market = Array.isArray(markets) ? markets[0] : markets;
const livePrice = Number(market?.outcomePrices?.[0] ?? market?.lastTradePrice);
if (Number.isFinite(livePrice) && Math.abs(livePrice - verdict.priceSeen) > MAX_PRICE_DRIFT) {
return;
}
}
const payload = {
id: `perigon-${verdict.slug}-${Date.now()}`,
key: BOT_KEY,
slug: verdict.slug,
outcome: verdict.outcome,
side: verdict.side,
dilution: false,
debug: {
confidence: verdict.confidence,
headline: verdict.headline,
url: verdict.url,
reasoning: verdict.reasoning,
},
};
const res = await $.Http.post(BOT_WEBHOOK_URL, payload);
if (res.status >= 200 && res.status < 300) {
state.trades += 1;
state.lastBySlug[verdict.slug] = Date.now();
await $.Storage.set(state);
}Prediction Signal Bots currently execute from a Bot Webhook payload, not from a native Code Reaction action. That's why the last step is an HTTP POST with slug, outcome, and side — not $.Strategy.action(). Size the bot conservatively on the bot itself; let the Code Reaction decide whether to fire, not how large the default clip is, until you've seen the log-only data in the next step.
Step 4 — Prove it before you fund it
Before pointing this at a funded signal bot, run the Code Reaction in log-only mode: forward candidates to a webhook that just records them, and compare what it flagged against what the market actually did over the following hours. This tells you two things no amount of code review can: whether the LLM's confidence scores are actually calibrated, and whether the "edge" it finds survives real order-book depth and fees. Both are answerable with a few days of data and unanswerable without it.
Watch three failure modes specifically, because webhook delivery changes the shape of the mistakes:
- Duplicate deliveries. Providers retry. Your cooldown and a stable id on the bot payload matter more than they do in a poller that already de-duplicated by story id.
- Fat payloads. A Signal with a custom schema can dump more than a headline. If the model starts trading on a related-article cluster instead of the triggering claim, tighten the Perigon definition or strip the payload in a tiny Code Reaction in front of the LLM (same news Data Hook, reaction that posts a slimmed JSON to a third hook the LLM listens on).
- Over-narrow Signals. A webhook that almost never fires feels clean and teaches you nothing. Start slightly broad, log every skip, then tighten.
Building this on Sigrex
Everything described here maps directly onto Sigrex's building blocks — Data Hooks for ingestion, LLM Reactions for research, Code Reactions for risk-checked execution, and Signal Bots for the actual order routing. The difference from an API-poll pipeline is only the first hop: Perigon already knows how to deliver. Use that.
If you're already comfortable writing the Code Reaction side, the two things worth spending real time on are the Perigon Signal definition and the LLM Reaction prompt. One decides what the model is allowed to see. The other decides whether it is allowed to speak.


