Introduction
We will build an earnings transcript analyzer that turns unstructured call text into structured investment signals. It extracts financial metrics, scores management sentiment, and flags forward-looking risks. This is for developers and analysts who want to automate equity research without surprise token bills.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
Step 1: Instantiate the Oxlo.ai client
I always verify the endpoint before building logic. This snippet creates the client, hits the Oxlo.ai OpenAI-compatible API, and confirms the connection with Llama 3.3 70B.
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ.get("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": "Say 'connected'"}],
)
assert "connected" in response.choices[0].message.content.lower()
print("Oxlo.ai client ready")
Step 2: Lock in the system prompt
The system prompt is the only guardrail we need. It forces the model to behave like a sell-side analyst and output strict JSON.
SYSTEM_PROMPT = """You are a senior equity research analyst. Read the earnings call transcript and return a JSON object with these keys:
- ticker: string
- quarter: string
- revenue_growth_commentary: string, max 30 words
- margin_trend: one of ["expanding", "stable", "contracting"]
- management_sentiment: integer 1-10
- guidance_change: one of ["raised", "unchanged", "lowered", "no guidance"]
- top_risks: array of strings, max 3 items
Rules:
- Output ONLY valid JSON.
- Do not include markdown code fences.
- If a metric is not mentioned, use null."""
Step 3: Extract structured signals with JSON mode
We pass the transcript and the system prompt to Llama 3.3 70B. JSON mode guarantees parseable output, so downstream code stays simple.
import json
def extract_signals(transcript):
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": transcript},
],
response_format={"type": "json_object"},
temperature=0.1,
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 4: Layer in deep reasoning for risk
Raw extraction is useful, but interpretation matters. We feed the structured JSON into DeepSeek V3.2 to generate a forward-looking risk paragraph and an investment stance.
def reason_risk(signals):
prompt = f"""Given the following structured signals from an earnings call:
{json.dumps(signals, indent=2)}
Write a 2-sentence forward-looking risk assessment and pick an investment stance from: Bullish, Neutral, or Bearish.
Return ONLY JSON with keys: risk_assessment, stance."""
response = client.chat.completions.create(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": "You are a cautious portfolio manager. Be concise."},
{"role": "user", "content": prompt},
],
response_format={"type": "json_object"},
temperature=0.2,
)
return json.loads(response.choices[0].message.content)
Step 5: Wire the CLI agent together
This orchestrator reads a local text file, runs extraction, then reasoning, and prints a merged report. You can pipe any transcript through it.
def analyze_earnings(file_path):
with open(file_path, "r", encoding="utf-8") as f:
transcript = f.read()
print("Extracting signals...")
signals = extract_signals(transcript)
print("Running risk reasoning...")
risk = reason_risk(signals)
report = {
"ticker": signals.get("ticker"),
"quarter": signals.get("quarter"),
"margin_trend": signals.get("margin_trend"),
"management_sentiment": signals.get("management_sentiment"),
"guidance_change": signals.get("guidance_change"),
"top_risks": signals.get("top_risks"),
"risk_assessment": risk.get("risk_assessment"),
"stance": risk.get("stance"),
}
print(json.dumps(report, indent=2))
return report
if __name__ == "__main__":
analyze_earnings("transcript.txt")
Run it
Create a file named transcript.txt with this sample text:
Good afternoon and welcome to the Q3 2024 call for ACME Corp.
Revenue grew 12% year over year driven by cloud adoption.
Gross margins contracted slightly due to supply chain headwinds.
Management raised full year guidance and sounded optimistic about Q4.
Key risks include currency fluctuation and competitive pricing pressure.
Then execute the script. You should see output similar to this:
{
"ticker": "ACME",
"quarter": "Q3 2024",
"margin_trend": "contracting",
"management_sentiment": 8,
"guidance_change": "raised",
"top_risks": [
"currency fluctuation",
"competitive pricing pressure"
],
"risk_assessment": "Margin contraction paired with FX headwinds could offset revenue momentum in the near term.",
"stance": "Neutral"
}
Next steps
Try batch processing an entire folder of transcripts by wrapping analyze_earnings in a loop over pathlib.Path("transcripts/").glob("*.txt"). If you need to analyze slide decks or charts, swap the reasoning layer to Kimi K2.6 and pass base64 images through the same Oxlo.ai client.
Because Oxlo.ai uses flat per-request pricing, long transcripts or full 10-K filings do not inflate your bill the way token-based providers do. See the exact rates at https://oxlo.ai/pricing.
Top comments (0)