earnings-call-guidance-lora

LoRA adapter for Qwen 2.5 7B Instruct that extracts forward-looking financial guidance from corporate earnings call transcripts and emits a structured JSON object.

Code, dataset construction, and eval scripts: github.com/BABAKAFSHINPOUR/earnings-call-signal-extraction (update if the public repo URL differs)

What it does

Given CEO + CFO prepared remarks from a quarterly earnings call, the model returns one JSON record per piece of forward-looking guidance with metric, period, direction (raised / lowered / reiterated / introduced / withdrawn / narrowed), numeric range, unit, vs-consensus framing, the verbatim source span, and the speaker role. Every number in a numeric field must appear verbatim in the cited span β€” the training data enforces this.

Quick start

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

BASE = "Qwen/Qwen2.5-7B-Instruct"
ADAPTER = "BABAKAFSHINPOUR/earnings-call-guidance-lora"

tokenizer = AutoTokenizer.from_pretrained(BASE)
base = AutoModelForCausalLM.from_pretrained(BASE, torch_dtype=torch.float16, device_map="auto")
model = PeftModel.from_pretrained(base, ADAPTER).eval()

system = "You extract forward-looking financial guidance from earnings call transcripts. ..."  # see repo
user = "[Satya Nadella β€” CEO]\n..."  # CEO + CFO prepared remarks

prompt = tokenizer.apply_chat_template(
    [{"role": "system", "content": system},
     {"role": "user",   "content": user}],
    tokenize=False, add_generation_prompt=True,
)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=4096, do_sample=False,
                     eos_token_id=tokenizer.convert_tokens_to_ids("<|im_end|>"))
print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))

The full system prompt (~80 lines, defines the schema and rules) is in src/build_training_jsonl.py in the linked repo.

Output schema

{"guidance": [
  {
    "metric": "revenue",
    "metric_detail": null,
    "period": "Q4-2026",
    "direction": "raised",
    "new_low": 4200, "new_high": 4300, "new_point": null,
    "old_low": null, "old_high": null, "old_point": null,
    "unit": "usd_millions",
    "currency": "USD",
    "vs_consensus": "not_stated",
    "confidence_language": "we now expect",
    "source_span": "...",
    "speaker": "cfo"
  }
]}

metric ∈ {revenue, eps_gaap, eps_adjusted, operating_income, operating_margin, gross_margin, fcf, capex, segment_revenue, other}. unit ∈ {usd_millions, usd_billions, usd, percent, count, other}. period normalized as Q1-2026, FY2026, H1-2026, CY2026, or long_term.

Training data

13 hand-labeled transcripts from 5 companies across 3 sectors, sourced from Motley Fool: Microsoft, JPMorgan, Caterpillar, Costco, UnitedHealth.

  • Train (10): MSFT_Q2-2026, JPM_Q2-2025, JPM_Q4-2025, CAT_Q4-2025, CAT_Q1-2026, COST_Q1-2026, COST_Q2-2026, COST_Q3-2026, UNH_Q1-2026, UNH_Q2-2025
  • Eval (3): MSFT_Q3-2026, JPM_Q1-2026, CAT_Q4-2024

Inputs are CEO + CFO + other-exec prepared remarks only β€” IR housekeeping, operator, and Q&A are excluded. One example per transcript (no chunking).

Training procedure

QLoRA on a single A100 40GB (GCP a2-highgpu-1g), using Unsloth + πŸ€— transformers.

Base model unsloth/Qwen2.5-7B-Instruct-bnb-4bit (4-bit NF4)
LoRA rank / alpha 32 / 64
LoRA dropout 0.0
Target modules q_proj, k_proj, v_proj, o_proj
Trainable params ~0.4% of base
Sequence length 16,384 (covers the densest transcripts)
Optimizer adamw_8bit
LR / schedule 2e-4, cosine, 10% warmup
Weight decay 0.01
Epochs 5
Effective batch size 4 (per-device 1 Γ— grad accum 4)
Precision bf16 (fallback fp16)
Prompt masking system+user masked to -100 via DataCollatorForSeq2Seq

A subtle correctness note baked into train.py: the default DataCollatorForLanguageModeling silently overwrites pre-set labels with input_ids.clone(), which defeats prompt masking entirely. This run uses DataCollatorForSeq2Seq so the masked labels are preserved.

Evaluation

Eval is generative β€” for each held-out transcript the model produces the full JSON, which is then parsed and compared to the human-labeled ground truth. See eval_predictions.jsonl in the linked repo for raw outputs; the headline checks are JSON validity and record count vs. truth.

This is a small, single-annotator dataset β€” treat the numbers as a sanity check on whether QLoRA fits the schema and the financial vocabulary, not as a benchmark.

Intended use & limitations

Intended: research and education β€” exploring whether a small open model can be specialized into a structured-extraction pipeline for finance text with a tiny labeled corpus.

Not intended: investment decisions, automated trading signals, or any downstream use where a hallucinated number could cause harm. The model can still emit fabricated figures, misattribute speakers, or miss guidance that was only stated in Q&A (Q&A is excluded from training inputs by design).

The training set covers 5 US large-cap tickers across tech, banking, industrials, retail, and healthcare. Coverage of small caps, non-US issuers, non-USD reporting, REITs, biotech milestone language, and insurance-specific metrics is untested.

Citation

If you reference this adapter:

@misc{afshinpour2026earnings,
  title  = {Earnings call guidance extraction via QLoRA on Qwen 2.5 7B},
  author = {Afshin-Pour, Babak},
  year   = {2026},
  howpublished = {\url{https://huggingface.co/BABAKAFSHINPOUR/earnings-call-guidance-lora}},
}

Framework versions

  • PEFT 0.19.1
Downloads last month
14
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for BABAKAFSHINPOUR/earnings-call-guidance-lora

Base model

Qwen/Qwen2.5-7B
Adapter
(61)
this model