Prediction markets like Polymarket and Kalshi price event probabilities based on trader sentiment, but traders often react slowly to breaking news. An agent that combines real-time news search with prediction market data can identify discrepancies: a market prices an event at 30% but recent news strongly suggests it will happen. This tutorial builds a Python agent that fetches prediction market positions via their public APIs, searches for the latest news on each market question via Scavio, and produces a briefing that highlights markets where news sentiment diverges from current odds. The search cost is $0.005 per market question checked.
Prerequisites
- Python 3.10+ installed
- requests library installed
- A Scavio API key from scavio.dev
- Familiarity with REST APIs and JSON
Walkthrough
Step 1: Fetch active prediction markets
Pull active markets from Polymarket's public API. Each market has a question, current probability, and metadata. Filter to markets with enough volume to be meaningful.
import os, requests, json
from datetime import datetime
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
SEARCH_HEADERS = {'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'}
def get_active_markets(min_volume=50000, limit=20):
resp = requests.get('https://gamma-api.polymarket.com/markets',
params={'limit': limit, 'active': True, 'ascending': False,
'order': 'volume'})
markets = resp.json()
return [m for m in markets
if float(m.get('volume', 0)) >= min_volume
and m.get('question')]Step 2: Search for recent news on each market question
For each market, search Google News via the Scavio API with the market question as the query. Collect the top 5 headlines and snippets as the news context.
def search_news(question):
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers=SEARCH_HEADERS,
json={'query': question, 'country_code': 'us'})
data = resp.json()
results = data.get('organic_results', [])[:5]
news_items = []
for r in results:
news_items.append({
'title': r.get('title', ''),
'snippet': r.get('snippet', ''),
'url': r.get('link', ''),
'date': r.get('date', ''),
})
return news_itemsStep 3: Analyze sentiment and produce a briefing
For each market, compare the news headlines against the market probability. Flag markets where news strongly suggests a different outcome than the market price implies.
def analyze_market(market, news):
question = market['question']
probability = float(market.get('outcomePrices', '[0.5]').strip('[]').split(',')[0])
positive_words = ['will', 'confirmed', 'expected', 'likely', 'approved', 'passed', 'wins']
negative_words = ['unlikely', 'rejected', 'denied', 'fails', 'canceled', 'delayed']
headline_text = ' '.join(n['title'].lower() + ' ' + n['snippet'].lower() for n in news)
pos_count = sum(1 for w in positive_words if w in headline_text)
neg_count = sum(1 for w in negative_words if w in headline_text)
news_sentiment = 'POSITIVE' if pos_count > neg_count else 'NEGATIVE' if neg_count > pos_count else 'NEUTRAL'
divergence = False
if news_sentiment == 'POSITIVE' and probability < 0.4:
divergence = True
elif news_sentiment == 'NEGATIVE' and probability > 0.6:
divergence = True
return {
'question': question,
'market_probability': f"{probability:.0%}",
'news_sentiment': news_sentiment,
'divergence': divergence,
'top_headline': news[0]['title'] if news else '',
'source': news[0]['url'] if news else '',
}Step 4: Run the full agent and output the briefing
Combine all steps: fetch markets, search news for each, analyze, and print a briefing sorted by divergence signals.
def run_agent():
markets = get_active_markets(limit=15)
print(f'Analyzing {len(markets)} active markets...\n')
briefing = []
for m in markets:
news = search_news(m['question'])
analysis = analyze_market(m, news)
briefing.append(analysis)
divergent = [b for b in briefing if b['divergence']]
aligned = [b for b in briefing if not b['divergence']]
if divergent:
print('--- DIVERGENCE SIGNALS ---')
for b in divergent:
print(f" {b['question'][:70]}")
print(f" Market: {b['market_probability']} | News: {b['news_sentiment']}")
print(f" Headline: {b['top_headline'][:80]}")
print()
print(f'--- SUMMARY ---')
print(f'{len(divergent)} divergence signals, {len(aligned)} aligned')
print(f'Cost: ${len(markets) * 0.005:.3f}')
return briefing
run_agent()Python Example
import os, requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'}
def get_markets(limit=10):
return requests.get('https://gamma-api.polymarket.com/markets',
params={'limit': limit, 'active': True, 'order': 'volume',
'ascending': False}).json()
def search_news(question):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': question, 'country_code': 'us'}).json()
return data.get('organic_results', [])[:5]
def run():
markets = get_markets()
for m in markets:
if not m.get('question'): continue
news = search_news(m['question'])
prob = float(m.get('outcomePrices', '[0.5]').strip('[]').split(',')[0])
headlines = ' '.join(n.get('title', '').lower() for n in news)
pos = sum(1 for w in ['confirmed', 'approved', 'wins', 'likely'] if w in headlines)
neg = sum(1 for w in ['rejected', 'unlikely', 'fails', 'denied'] if w in headlines)
sentiment = 'POS' if pos > neg else 'NEG' if neg > pos else 'NEUTRAL'
flag = '*' if (sentiment == 'POS' and prob < 0.4) or (sentiment == 'NEG' and prob > 0.6) else ' '
print(f'{flag} [{prob:.0%}] {m["question"][:60]} | News: {sentiment}')
print(f'Cost: ${len(markets) * 0.005:.3f}')
run()JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
const SH = { 'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json' };
async function getMarkets(limit = 10) {
return fetch(`https://gamma-api.polymarket.com/markets?limit=${limit}&active=true&order=volume&ascending=false`)
.then(r => r.json());
}
async function searchNews(question) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH, body: JSON.stringify({ query: question, country_code: 'us' })
}).then(r => r.json());
return (data.organic_results || []).slice(0, 5);
}
async function run() {
const markets = await getMarkets();
for (const m of markets) {
if (!m.question) continue;
const news = await searchNews(m.question);
const prob = parseFloat((m.outcomePrices || '[0.5]').replace(/[\[\]]/g, '').split(',')[0]);
const headlines = news.map(n => (n.title || '').toLowerCase()).join(' ');
const pos = ['confirmed', 'approved', 'wins'].filter(w => headlines.includes(w)).length;
const neg = ['rejected', 'unlikely', 'fails'].filter(w => headlines.includes(w)).length;
const sentiment = pos > neg ? 'POS' : neg > pos ? 'NEG' : 'NEUTRAL';
const flag = (sentiment === 'POS' && prob < 0.4) || (sentiment === 'NEG' && prob > 0.6) ? '*' : ' ';
console.log(`${flag} [${(prob * 100).toFixed(0)}%] ${m.question.slice(0, 60)} | News: ${sentiment}`);
}
console.log(`Cost: $${(markets.length * 0.005).toFixed(3)}`);
}
run().catch(console.error);Expected Output
Analyzing 15 active markets...
--- DIVERGENCE SIGNALS ---
Will the Fed cut interest rates in June 2026?
Market: 28% | News: POSITIVE
Headline: Fed officials signal June rate cut is likely amid cooling inflation
Will SpaceX complete Starship orbital flight by July 2026?
Market: 65% | News: NEGATIVE
Headline: SpaceX delays Starship test flight after engine review
--- SUMMARY ---
2 divergence signals, 13 aligned
Cost: $0.075