αAlphaware
TECH8 min read

Automating Quant Research with the Claude API: Decide the Criteria Before the Model

Instead of comparing model names, design a Claude API integration around task shape, context length, batch and cache cost structure, structured output, and data retention.

Automating Quant Research with the Claude API: Decide the Criteria Before the Model

Updated November 3, 2026. The April 2026 model-by-model comparison is rewritten around decision criteria. Model IDs, prices, and limits were checked against Anthropic’s official documentation on November 3, 2026; the routing and validation code is a proposed design, and no live API call or benchmark was run.

Keep decision criteria, not model names

The April version of this article compared Claude 3.5 Sonnet and GPT-4o by name and price. Anthropic’s model deprecations page shows that claude-3-5-sonnet-20241022 was retired on October 28, 2025, and requests to retired models fail. Model comparison tables age within months, and the stale table stays in the code. Model deprecations

What lasts is not a model name but which work you hand over, and under which conditions. This article uses five criteria.

  1. Task shape: interactive analysis, bulk batch extraction, or code changes.
  2. Context length: how much material must go into one request.
  3. Cost structure: whether the same instructions repeat, and whether you must wait for the answer.
  4. Output format: whether a person reads the result or a program stores it.
  5. Data sensitivity: whether the material may leave your systems and be retained.

As of November 3, 2026, Anthropic’s models overview lists Claude Fable 5.1 (claude-fable-5-1), Claude Opus 5.5 (claude-opus-5-5), Claude Sonnet 5.5 (claude-sonnet-5-5), and Claude Haiku 5.5 (claude-haiku-5-5) as current models. The same page suggests starting most workloads on Opus 5.5 and describes Haiku 5.5 for high-volume, latency-sensitive work such as classification, extraction, and routing. This list will change too. Keep the model ID in configuration and re-check it against your own eval set whenever you switch. Models overview

Task shape decides the call pattern

Asking an LLM “where will BTC go?” is a question you cannot verify. Research automation fits work whose result can be checked: turning a paper’s factor definition into code, picking windows to investigate in a backtest log, extracting figures from a filing.

Task shape Check first Matching call pattern
Interactive analysis and code review Answer quality, round-trip time Synchronous Messages API
Bulk extraction or classification Cost per item, throughput, format Message Batches API + structured output
Repeated questions over the same material Length of the repeated prefix Prompt caching
Repository-wide code changes File permissions, tests, review Agentic coding tool

Claude Code is an example of the last row. Its documentation describes it as an agentic coding tool that reads your codebase, edits files, and runs commands, available in the terminal, IDE, desktop app, and browser. Because it changes files directly, a strategy-code repository needs a boundary: changes on a branch, tests, and human review before merge. Claude Code overview

The AI research stack article covers when to use a fixed workflow and when to use an agent.

Long context also raises cost and verification load

As of November 3, 2026, the models overview lists a 1M-token context window and 128K-token maximum output for all four current models. Several papers or a large code file fit in one request. That does not mean you should always send everything.

  • Billing: input tokens are billed for everything you send. Haiku 5.5 switches to higher rates when a prompt exceeds 100,000 tokens.
  • Token counts: the pricing page notes that the tokenizer in Claude 4.7 and later models produces about 30% more tokens for the same text than earlier models. Do not carry an old token budget over; re-measure with the token counting API.
  • Traceability: the longer the input, the harder it is to see which passage an answer came from. Assign IDs to documents and passages and have the response cite them.

The test is not “can it fit in one request” but “does this question need all of that material.” Retrieving only the relevant parts is covered in the RAG section of the AI research stack article.

Cost depends on call structure, not unit price

As of November 3, 2026, Anthropic’s pricing page lists these base rates (USD per million tokens).

Model Input Output Cache read
Claude Fable 5.1 $10 $50 $0.25
Claude Opus 5.5 $4 $20 $0.20
Claude Sonnet 5.5 $2 $10 $0.10
Claude Haiku 5.5 (prompts up to 100K tokens) $0.10 $0.50 $0.01

Call structure changes the total more than unit price does. Two mechanisms are documented.

  • Batch processing: the Message Batches API is 50% off both input and output. A batch holds up to 100,000 requests or 256 MB; most finish within an hour, and requests not processed within 24 hours expire. Results can be downloaded for 29 days after creation. Batch processing
  • Prompt caching: a 5-minute cache write costs 1.25× the base input price, a 1-hour write 2×, and a cache read 0.1× (0.05× on Opus 5.5 and Sonnet 5.5). The default lifetime is 5 minutes and is refreshed on each use. The minimum cacheable length on current models is 512 tokens; shorter prompts are silently not cached. Prompt caching

The code below costs a hypothetical job three ways: 1,000 documents of 3,000 tokens each, sharing a 6,000-token block of instructions and schema. Prices are the Sonnet 5.5 rates above. No API is called.

# Claude Sonnet 5.5 rates as of 2026-11-03 (USD per million tokens)
# Source: https://platform.claude.com/docs/en/about-claude/pricing
PRICE = {"input": 2.00, "output": 10.00,
         "cache_write_5m": 2.50, "cache_read": 0.10}
BATCH_DISCOUNT = 0.5

def job_cost(n_docs, prefix_tokens, doc_tokens, out_tokens,
             price, mode="standard"):
    """Cost (USD) of processing n_docs with the same instruction prefix."""
    if mode == "cached":
        # Assume the first request writes the cache and later requests read it
        prefix = (price["cache_write_5m"] * prefix_tokens
                  + price["cache_read"] * prefix_tokens * (n_docs - 1))
    else:
        prefix = price["input"] * prefix_tokens * n_docs
    body = (price["input"] * doc_tokens
            + price["output"] * out_tokens) * n_docs
    total = (prefix + body) / 1_000_000
    return total * BATCH_DISCOUNT if mode == "batch" else total

args = dict(n_docs=1_000, prefix_tokens=6_000,
            doc_tokens=3_000, out_tokens=400, price=PRICE)
standard = job_cost(**args)
cached = job_cost(**args, mode="cached")
batch = job_cost(**args, mode="batch")

assert round(standard, 2) == 22.00
assert round(cached, 2) == 10.61
assert round(batch, 2) == 11.00
assert cached < batch < standard

The cached scenario assumes requests keep arriving within the 5-minute window so the cache stays warm. The batch scenario assumes no cache hits. Confirm real cost from the cache write and read token counts in each response’s usage field, and add retries and review time to the total.

Other providers fit the same frame. OpenAI’s pricing page also lists batch and cached-input rates separately from standard rates. Plugging your own call structure into a function like this ages better than copying a price table. OpenAI API pricing

Structured output guarantees format only

If a program stores the result, specify a schema rather than accept free-form text. Anthropic’s documentation describes constraining the response with a JSON schema in output_config.format. The same page lists numeric constraints such as minimum/maximum and string constraints such as minLength as unsupported, and notes that responses stopped by a refusal (refusal) or the token limit (max_tokens) may not match the schema. Structured outputs

The code below builds the request body and tests only the local logic that sends a fake response to store, retry, or hold. It runs without a network or an API key.

import json

MODEL = "claude-sonnet-5-5"  # Example ID. Re-check the current ID on the models overview
SCHEMA = {
    "type": "object",
    "properties": {
        "metric": {"type": "string"},
        "period": {"type": "string"},
        "value": {"type": "string"},
        "unit": {"type": "string"},
        "passage_id": {"type": "string"},
    },
    "required": ["metric", "period", "value", "unit", "passage_id"],
    "additionalProperties": False,
}

def build_request(doc_id, passages):
    body = "\n".join(f'<passage id="{pid}">{text}</passage>'
                     for pid, text in passages.items())
    return {
        "model": MODEL,
        "max_tokens": 1024,
        "system": "Extract one figure. Cite only passage ids that appear "
                  "in the document. If no figure is stated, say so.",
        "messages": [{"role": "user",
                      "content": f'<document id="{doc_id}">\n{body}\n</document>'}],
        "output_config": {"format": {"type": "json_schema", "schema": SCHEMA}},
    }

def route(response, passage_ids):
    """Send a response to store / retry / hold."""
    stop = response["stop_reason"]
    if stop == "max_tokens":
        return "retry", "truncated"
    if stop != "end_turn":          # refusal and others
        return "hold", stop
    text = next(b["text"] for b in response["content"] if b["type"] == "text")
    data = json.loads(text)
    if set(data) != set(SCHEMA["required"]):
        return "hold", "unexpected_keys"
    if data["passage_id"] not in passage_ids:
        return "hold", "unknown_passage"
    return "store", data

passages = {"p1": "Revenue for Q3 was USD 12.5 million."}
request = build_request("demo-report", passages)
assert request["output_config"]["format"]["schema"]["additionalProperties"] is False
assert 'id="p1"' in request["messages"][0]["content"]

def fake(stop, payload=None):
    content = [] if payload is None else [{"type": "text", "text": json.dumps(payload)}]
    return {"stop_reason": stop, "content": content}

good = {"metric": "revenue", "period": "Q3", "value": "12.5",
        "unit": "USD million", "passage_id": "p1"}
assert route(fake("end_turn", good), passages) == ("store", good)
assert route(fake("end_turn", {**good, "passage_id": "p9"}), passages) == ("hold", "unknown_passage")
assert route(fake("refusal"), passages) == ("hold", "refusal")
assert route(fake("max_tokens"), passages) == ("retry", "truncated")

A real call passes the same request body to the official Python SDK. The block below needs an API key and a network, so it was not executed for this article. Its shape follows the Python examples in the structured outputs and batch processing documentation.

# Not executed: requires ANTHROPIC_API_KEY and network access
import json
import anthropic
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request

client = anthropic.Anthropic()

# One synchronous call
response = client.messages.create(**build_request("demo-report", passages))
text = next(block.text for block in response.content if block.type == "text")
record = json.loads(text)

# The same request submitted as a batch (match results by custom_id)
batch = client.messages.batches.create(
    requests=[
        Request(
            custom_id="demo-report",
            params=MessageCreateParamsNonStreaming(**build_request("demo-report", passages)),
        )
    ]
)

A store decision does not make the figure true. It only establishes that the passage ID exists and the format is right. Comparing the figure against the source is the subject of the structured output and evidence validation article.

Check retention terms before sending sensitive data

Before sending strategy code or positions, “is it used for training” is not the only question. Whether it is stored, for how long, and by which feature matter as much. As of November 3, 2026, Anthropic’s API data retention page states the following. API and data retention

  • Retained data is never used for model training without your express permission.
  • Zero data retention (ZDR) is not the default; it is an arrangement enabled per organization through the sales team.
  • The Message Batches API is not ZDR-eligible and has 29-day retention. Moving work to batch to save cost also changes the retention terms.
  • Claude Fable 5.1 and Fable 5 require 30-day retention and are not available under ZDR unless expressly authorized.
  • Regardless of arrangement, content required by law or flagged by automated safety systems may be retained for up to 2 years.

Material that does not meet these terms needs a path that never reaches an external API. Where to place a local model and how to split work is covered in the local LLM versus cloud API article. Contract terms differ by organization, so the final call rests on your own agreement and account representative.

Sources and scope

Signal

Was this brief useful?

Subscribe

Get new briefs

Get an email when a new brief ships. RSS and X stay available.

Subscribe
More in this categoryTECH →