← Back to blog

Published

How to Build a News Trading Bot for Stocks and Crypto

A news trading bot can watch far more companies, tokens, regulators, and source sites than one person can refresh by hand. Its real advantage is coverage and consistency. It can run the same queries on schedule, notice a new source, and package the evidence for review.

The dangerous version trades because a headline contains words such as "approval," "beat," or "hack." The useful version treats search as discovery, verifies the source, checks whether the information is new, and keeps market prices and order execution in separate systems.

This guide builds the useful version with Python and live Google or Bing results from PrismCrawl. The output is a source-linked paper signal, not financial advice or an automated order.

The research pipeline

Asset watchlist       -> symbols, names, aliases, primary sources
PrismCrawl            -> live Google or Bing results
Evidence processor    -> normalize, deduplicate, classify, verify
Market-data feed      -> timestamped bid, ask, volume, volatility
Risk policy           -> stale-data checks, limits, paper signal
Optional executor     -> isolated broker or exchange integration

Each component answers a different question. Search asks, "What new public evidence is discoverable?" A market feed asks, "What can I trade now, and at what price?" Neither should silently stand in for the other.

Benefits of using PrismCrawl for news discovery

  • Wide source coverage: Google and Bing can surface company releases, agency pages, local reporting, specialist publications, and follow-up analysis with the same query interface.
  • Fresh, uncached requests: Each accepted search runs live instead of substituting a previously stored result for the query.
  • Normalized evidence: Ranked URLs, domains, titles, snippets, and request IDs arrive in one documented response shape.
  • Precise queries: Country, language, time filters, News search, and site: operators let each asset use a focused monitoring plan.
  • Predictable billing: One successful search uses one credit. Failed searches use no credit, which keeps search cost tied to delivered results.

Step 1: Define an asset by more than its ticker

Ticker strings are ambiguous. CAT, AI, and many token symbols are ordinary words or are shared by unrelated projects. Save the entity name, aliases, asset class, primary domains, and event queries together.

from dataclasses import dataclass


@dataclass(frozen=True)
class AssetPlan:
    symbol: str
    name: str
    asset_class: str
    aliases: tuple[str, ...]
    primary_domains: frozenset[str]
    queries: tuple[str, ...]


stock_plan = AssetPlan(
    symbol="EXAMPLE",
    name="Example Corporation",
    asset_class="stock",
    aliases=("Example Corp",),
    primary_domains=frozenset({"sec.gov", "investor.example.com"}),
    queries=(
        '"Example Corporation" earnings OR guidance OR acquisition',
        'site:sec.gov/Archives/edgar/data "Example Corporation"',
        'site:investor.example.com "Example Corporation"',
    ),
)

crypto_plan = AssetPlan(
    symbol="TOKEN-USD",
    name="Example Network",
    asset_class="crypto",
    aliases=("Example Token", "TOKEN"),
    primary_domains=frozenset({"foundation.example", "github.com"}),
    queries=(
        '"Example Network" upgrade OR outage OR exploit',
        'site:foundation.example "Example Network"',
        'site:github.com/example-network release security',
    ),
)

These names and domains are illustrative. For a real stock, include the company's investor-relations domain and the relevant regulator. For a crypto asset, identify the official project, code, governance, exchange, and regulator sources that actually matter to your thesis.

Step 2: Retrieve recent Google or Bing results

The wrapper below uses Google's News tab and a one-hour filter. Bing's time filter has day-level precision, so its branch requests the previous day. Both engines use the same PrismCrawl response shape, which means the evidence processor does not need a second parser.

import os
from urllib.parse import urlsplit

import requests

PRISMCRAWL_API_KEY = os.environ["PRISMCRAWL_API_KEY"]
PRISMCRAWL_BASE_URL = "https://api.prismcrawl.com/v1"


def search_news(query: str, engine: str = "google") -> list[dict]:
    if engine not in {"google", "microsoft"}:
        raise ValueError("engine must be 'google' or 'microsoft'")

    payload = {"query": query}
    if engine == "google":
        payload.update({"gl": "us", "hl": "en-US", "udm": 12, "tbs": "qdr:h"})
    else:
        payload.update({"cc": "us", "setlang": "en-US", "tbs": "qdr:d"})

    response = requests.post(
        f"{PRISMCRAWL_BASE_URL}/{engine}/search",
        headers={"x-api-key": PRISMCRAWL_API_KEY},
        json=payload,
        timeout=60,
    )
    response.raise_for_status()
    body = response.json()
    if not body["success"]:
        raise RuntimeError(body["error"]["message"])

    return [
        {
            "request_id": body["request_id"],
            "engine": engine,
            "rank": item["rank"],
            "title": item["title"],
            "url": item["url"],
            "domain": (urlsplit(item["url"]).hostname or "").removeprefix("www."),
            "snippet": item.get("snippet") or "",
        }
        for item in body["data"]["content"]["results"]
        if item.get("url")
    ]

Google's udm: 12 selects News. Widen its tbs to qdr:d or a custom date range for slower events. Bing accepts day, week, month, year, and custom date filters, but rejects qdr:h. The API reference lists the supported fields for both engines.

Search engines discover and rank pages on their own schedules. If a filing, exchange notice, or protocol feed has an official API, consume that source directly as well. Search is the catchment layer for evidence you did not already know to request.

Step 3: Normalize, deduplicate, and mark primary sources

A story copied by five sites is not five confirmations. Remove fragments and tracking parameters, merge Google and Bing matches, and label allowlisted primary domains.

from urllib.parse import urlsplit, urlunsplit


def normalize_url(raw_url: str) -> str:
    parts = urlsplit(raw_url)
    return urlunsplit((parts.scheme, parts.netloc.lower(), parts.path.rstrip("/"), "", ""))


def merge_results(plan: AssetPlan, batches: list[list[dict]]) -> list[dict]:
    by_url: dict[str, dict] = {}
    for batch in batches:
        for item in batch:
            url = normalize_url(item["url"])
            existing = by_url.get(url)
            if existing:
                existing["engines"].add(item["engine"])
                existing["request_ids"].add(item["request_id"])
                continue

            by_url[url] = {
                **item,
                "url": url,
                "is_primary": item["domain"] in plan.primary_domains,
                "engines": {item["engine"]},
                "request_ids": {item["request_id"]},
            }

    return list(by_url.values())

For stronger deduplication, fetch the underlying pages and compare their canonical URLs, publication times, quoted statements, and content fingerprints. Keep corrections and retractions linked to the earlier story instead of treating them as unrelated events.

Step 4: Classify events without pretending sentiment is a strategy

Simple keywords can route an item to a review queue. They should not decide direction or order size. The classifier below tags event types and returns unclassified when it does not know.

EVENT_TERMS = {
    "earnings": {"earnings", "revenue", "guidance", "quarterly results"},
    "regulatory": {"sec filing", "investigation", "approval", "injunction"},
    "corporate_action": {"acquisition", "merger", "buyback", "offering"},
    "security": {"exploit", "vulnerability", "hack", "stolen funds"},
    "network": {"upgrade", "fork", "outage", "halted"},
}


def classify_event(item: dict) -> set[str]:
    text = f"{item['title']} {item['snippet']}".lower()
    labels = {
        label
        for label, terms in EVENT_TERMS.items()
        if any(term in text for term in terms)
    }
    return labels or {"unclassified"}

This is a triage example. "Company receives approval" can refer to a minor permit, a major product, or an old article resurfacing. Read the source and resolve the entity, event time, and scope before attaching market meaning.

Step 5: Create a review event

The gate below requires more than one domain unless an approved primary source is present. It also keeps all source and request identifiers in the output.

from datetime import UTC, datetime


def create_review_event(plan: AssetPlan, evidence: list[dict]) -> dict | None:
    if not evidence:
        return None

    domains = {item["domain"] for item in evidence}
    primary_items = [item for item in evidence if item["is_primary"]]
    if len(domains) < 2 and not primary_items:
        return None

    labels = sorted({label for item in evidence for label in classify_event(item)})
    return {
        "action": "research_review",
        "detected_at": datetime.now(UTC).isoformat(),
        "symbol": plan.symbol,
        "asset_class": plan.asset_class,
        "event_labels": labels,
        "has_primary_source": bool(primary_items),
        "source_urls": [item["url"] for item in evidence[:10]],
        "prismcrawl_request_ids": sorted(
            {request_id for item in evidence for request_id in item["request_ids"]}
        ),
    }

The next stage should open the source pages, extract the relevant statements, and verify timestamps. If an LLM assists, require citations and an unclear result. Save the prompt, model version, source text, and structured output so a reviewer can reproduce the decision.

Step 6: Join news with market data only after verification

Get a timestamped price snapshot from a source appropriate for the asset and your rights to use it. Then apply deterministic checks before even creating a paper order:

  • the news event is newer than the configured cutoff;
  • the market quote is newer than its own cutoff;
  • the symbol and entity match exactly;
  • the spread and liquidity are within limits;
  • the signal is not a duplicate or correction of an earlier item;
  • maximum position, loss, order count, and daily exposure limits pass;
  • the system is in paper mode unless an authorized operator changed it.

Backtests need historical search or news evidence as it appeared at the time. Testing today's article set against yesterday's price creates look-ahead bias. Include fees, spreads, partial fills, halts, rejected orders, and delisted assets.

FINRA's algorithmic trading guidance emphasizes risk assessment, testing, validation, supervision, and post-deployment review for member firms. For crypto, the CFTC warns that false stories and social promotion can be used in pump-and-dump schemes. Those are practical reasons to favor primary sources and fail closed.

Where search-driven trading research fits

Search-driven monitoring is useful for events whose public evidence is fragmented: regulatory decisions, litigation, product recalls, management changes, local permits, supplier problems, protocol incidents, and follow-up reporting that connects an event to an asset.

It is not high-frequency news infrastructure. If seconds or milliseconds determine the strategy, use direct licensed feeds with explicit delivery guarantees and rights. PrismCrawl is better suited to broad discovery, repeatable research queries, and evidence collection that a person or slower strategy can inspect.

Start with alerts. Measure precision, missed events, source latency, duplicate rate, and false entity matches. Move to paper trading only after those measurements are stable. The ability to place an order is the last feature a news bot should receive.

Create a PrismCrawl account to run the first live news query, or see how the same evidence layer works in a Polymarket bot and a Kalshi bot.

Frequently asked questions

Does PrismCrawl provide stock or cryptocurrency prices?

No. PrismCrawl provides live Google and Bing search results for news discovery. Use a suitable market-data source for executable prices and a separately controlled broker or exchange integration for orders.

Can a news trading bot trade from one search result?

It should not. A search result can be stale, duplicated, ambiguous, or false. Verify the underlying page, prioritize primary sources, compare independent coverage, and paper trade before considering execution.

Is web search suitable for high-frequency trading?

No. Search is useful for broad discovery, monitoring, and context. Millisecond strategies need direct licensed news and market feeds designed for that latency and use case.