Documentation Index
Fetch the complete documentation index at: https://docs.secapi.ai/llms.txt
Use this file to discover all available pages before exploring further.
Build an Earnings Preview Agent
Before a company reports earnings, analysts need a quick-hit research brief covering the company’s fundamentals, factor exposures, and recent insider activity. This tutorial builds a Node.js agent that pulls data from multiple OMNI Datastream endpoints and assembles a pre-earnings research brief you can share with your team.
What you will build
- A Node.js script that generates pre-earnings research briefs
- Company intelligence summary from the OMNI intelligence bundle
- Factor decomposition showing what is driving the stock
- Recent insider transactions leading up to earnings
- A formatted markdown report ready for distribution
Prerequisites
- An Omni Datastream API key (set as
OMNI_DATASTREAM_API_KEY)
- Node.js 18+
- Basic familiarity with the OMNI Datastream API
Step 1 — Set up the project
Create the project and install dependencies.
mkdir -p earnings-preview-agent
cd earnings-preview-agent
npm init -y
Create package.json:
{
"name": "earnings-preview-agent",
"version": "1.0.0",
"type": "module",
"scripts": {
"preview": "node index.js"
},
"dependencies": {
"@omni-datastream/sdk-js": "latest",
"dotenv": "^16.4.0"
}
}
Install dependencies:
Create a .env file:
OMNI_DATASTREAM_API_KEY=your-api-key
Step 2 — Build the data fetchers
Create index.js with functions that pull data from each OMNI Datastream endpoint.
import "dotenv/config";
import { writeFileSync } from "fs";
import { OmniDatastreamClient } from "@omni-datastream/sdk-js";
const client = new OmniDatastreamClient({
apiKey: process.env.OMNI_DATASTREAM_API_KEY,
});
const ticker = process.argv[2] || "AAPL";
/**
* Fetch company intelligence bundle — a high-level overview of the company
* including business description, key metrics, and recent developments.
*/
async function fetchCompanyIntel(ticker) {
const intel = await client.intelligence.company({ ticker });
return intel;
}
/**
* Fetch the earnings preview intelligence bundle — pre-built earnings
* context including consensus estimates, recent guidance, and key topics.
*/
async function fetchEarningsPreview(ticker) {
const preview = await client.intelligence.earningsPreview({ ticker });
return preview;
}
/**
* Fetch factor decomposition — shows how much of the stock's recent
* return is explained by systematic factors vs. idiosyncratic moves.
*/
async function fetchFactorDecomposition(ticker) {
const factors = await client.factors.decomposition({ ticker });
return factors;
}
/**
* Fetch recent insider transactions — Form 4 filings showing buys,
* sells, and option exercises by officers and directors.
*/
async function fetchInsiderActivity(ticker) {
const insiders = await client.insiders.list({
ticker,
limit: 20,
sort: "filed_at:desc",
});
return insiders;
}
/**
* Fetch upcoming earnings date from the market calendar.
*/
async function fetchEarningsDate(ticker) {
const calendar = await client.market.earningsCalendar({ ticker });
return calendar;
}
Step 3 — Build the report generator
Add the logic that assembles all the data into a structured markdown report.
function formatCurrency(value) {
if (!value && value !== 0) return "N/A";
if (Math.abs(value) >= 1e12) return `$${(value / 1e12).toFixed(2)}T`;
if (Math.abs(value) >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;
if (Math.abs(value) >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;
return `$${value.toLocaleString()}`;
}
function formatDate(dateStr) {
if (!dateStr) return "N/A";
return new Date(dateStr).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
}
function generateReport(ticker, intel, preview, factors, insiders, earnings) {
const lines = [];
const now = new Date().toISOString().split("T")[0];
// Header
lines.push(`# Earnings Preview: ${ticker}`);
lines.push("");
lines.push(`**Generated:** ${now} `);
if (earnings?.data?.[0]) {
const next = earnings.data[0];
lines.push(
`**Next earnings:** ${formatDate(next.date)} (${next.time || "TBD"}) `
);
}
lines.push("");
// Company overview
lines.push("## Company Overview");
lines.push("");
if (intel?.data) {
const d = intel.data;
lines.push(`**${d.company_name || ticker}**`);
lines.push("");
if (d.description) {
lines.push(d.description.slice(0, 500));
lines.push("");
}
lines.push("| Metric | Value |");
lines.push("|--------|-------|");
if (d.market_cap) lines.push(`| Market Cap | ${formatCurrency(d.market_cap)} |`);
if (d.sector) lines.push(`| Sector | ${d.sector} |`);
if (d.industry) lines.push(`| Industry | ${d.industry} |`);
if (d.employees) lines.push(`| Employees | ${d.employees.toLocaleString()} |`);
lines.push("");
}
// Earnings preview context
lines.push("## Earnings Context");
lines.push("");
if (preview?.data) {
const p = preview.data;
if (p.consensus_eps) {
lines.push(`**Consensus EPS:** $${p.consensus_eps} `);
}
if (p.consensus_revenue) {
lines.push(
`**Consensus Revenue:** ${formatCurrency(p.consensus_revenue)} `
);
}
if (p.key_topics && p.key_topics.length > 0) {
lines.push("");
lines.push("### Key topics to watch");
lines.push("");
for (const topic of p.key_topics) {
lines.push(`- ${topic}`);
}
}
if (p.recent_guidance) {
lines.push("");
lines.push("### Recent guidance");
lines.push("");
lines.push(p.recent_guidance);
}
lines.push("");
} else {
lines.push("_No earnings preview data available._");
lines.push("");
}
// Factor decomposition
lines.push("## Factor Decomposition");
lines.push("");
if (factors?.data) {
lines.push(
"Shows how much of the stock's recent return is attributable to systematic factors."
);
lines.push("");
lines.push("| Factor | Exposure | Contribution |");
lines.push("|--------|----------|-------------|");
const exposures = factors.data.exposures || factors.data;
if (Array.isArray(exposures)) {
for (const f of exposures.slice(0, 10)) {
const name = f.factor_name || f.factor || "Unknown";
const exposure = f.exposure?.toFixed(3) ?? "N/A";
const contribution = f.contribution
? `${(f.contribution * 100).toFixed(2)}%`
: "N/A";
lines.push(`| ${name} | ${exposure} | ${contribution} |`);
}
}
lines.push("");
} else {
lines.push("_No factor decomposition available._");
lines.push("");
}
// Insider activity
lines.push("## Recent Insider Activity");
lines.push("");
if (insiders?.data && insiders.data.length > 0) {
lines.push("| Date | Name | Title | Type | Shares | Price |");
lines.push("|------|------|-------|------|--------|-------|");
for (const txn of insiders.data.slice(0, 15)) {
const date = txn.filed_at || txn.transaction_date || "N/A";
const name = txn.reporting_owner || txn.insider_name || "Unknown";
const title = txn.title || "";
const type = txn.transaction_type || txn.type || "N/A";
const shares = txn.shares ? txn.shares.toLocaleString() : "N/A";
const price = txn.price ? `$${txn.price.toFixed(2)}` : "N/A";
lines.push(`| ${date} | ${name} | ${title} | ${type} | ${shares} | ${price} |`);
}
lines.push("");
// Insider sentiment summary
const buys = insiders.data.filter(
(t) =>
t.transaction_type === "P" ||
t.transaction_type === "Purchase"
).length;
const sells = insiders.data.filter(
(t) =>
t.transaction_type === "S" ||
t.transaction_type === "Sale"
).length;
lines.push(
`**Insider sentiment (last 20 transactions):** ${buys} buys, ${sells} sells`
);
lines.push("");
} else {
lines.push("_No recent insider activity found._");
lines.push("");
}
// Footer
lines.push("---");
lines.push(
"*Data sourced from SEC EDGAR via OMNI Datastream API. This is not investment advice.*"
);
return lines.join("\n");
}
Step 4 — Wire up the main function
Add the entry point that orchestrates all the data fetches and generates the report.
async function main() {
console.log(`Generating earnings preview for ${ticker}...`);
// Fetch all data in parallel for speed
const [intel, preview, factors, insiders, earnings] =
await Promise.allSettled([
fetchCompanyIntel(ticker),
fetchEarningsPreview(ticker),
fetchFactorDecomposition(ticker),
fetchInsiderActivity(ticker),
fetchEarningsDate(ticker),
]);
// Extract values, using null for any that failed
const getValue = (result) =>
result.status === "fulfilled" ? result.value : null;
const report = generateReport(
ticker,
getValue(intel),
getValue(preview),
getValue(factors),
getValue(insiders),
getValue(earnings)
);
// Write report
const filename = `earnings-preview-${ticker.toLowerCase()}-${new Date().toISOString().split("T")[0]}.md`;
writeFileSync(filename, report);
console.log(`Report saved to ${filename}`);
console.log("");
console.log(report);
}
main().catch((err) => {
console.error("Error generating earnings preview:", err.message);
process.exit(1);
});
Step 5 — Run the agent
Generate an earnings preview for any ticker.
# Default: AAPL
npm run preview
# Specify a ticker
node index.js MSFT
node index.js NVDA
Expected output
Generating earnings preview for AAPL...
Report saved to earnings-preview-aapl-2025-04-11.md
# Earnings Preview: AAPL
**Generated:** 2025-04-11
**Next earnings:** May 1, 2025 (AMC)
## Company Overview
**Apple Inc**
Apple Inc. designs, manufactures, and markets smartphones, personal
computers, tablets, wearables, and accessories worldwide...
| Metric | Value |
|--------|-------|
| Market Cap | $3.45T |
| Sector | Technology |
| Industry | Consumer Electronics |
| Employees | 161,000 |
## Earnings Context
**Consensus EPS:** $1.62
**Consensus Revenue:** $94.2B
### Key topics to watch
- Services revenue growth trajectory
- China market headwinds
- AI feature adoption and monetization
- Capital return program updates
## Factor Decomposition
| Factor | Exposure | Contribution |
|--------|----------|-------------|
| Market | 1.102 | 8.24% |
| Momentum | 0.342 | 1.87% |
| Quality | 0.891 | 2.14% |
| Size | -0.234 | -0.42% |
## Recent Insider Activity
| Date | Name | Title | Type | Shares | Price |
|------|------|-------|------|--------|-------|
| 2025-04-01 | Tim Cook | CEO | Sale | 200,000 | $178.23 |
| 2025-03-15 | Luca Maestri | SVP, CFO | Sale | 50,000 | $171.45 |
**Insider sentiment (last 20 transactions):** 0 buys, 8 sells
Step 6 — Generate batch previews
Add support for generating previews for all companies reporting in a given week.
async function batchPreview() {
// Fetch the earnings calendar for the next 7 days
const calendar = await client.market.earningsCalendar({
start_date: new Date().toISOString().split("T")[0],
end_date: new Date(Date.now() + 7 * 86400000).toISOString().split("T")[0],
});
const tickers = (calendar.data || []).map((e) => e.ticker).slice(0, 10);
console.log(`Generating previews for ${tickers.length} companies...`);
for (const t of tickers) {
try {
// Re-run main logic for each ticker
const [intel, preview, factors, insiders, earnings] =
await Promise.allSettled([
fetchCompanyIntel(t),
fetchEarningsPreview(t),
fetchFactorDecomposition(t),
fetchInsiderActivity(t),
fetchEarningsDate(t),
]);
const getValue = (result) =>
result.status === "fulfilled" ? result.value : null;
const report = generateReport(
t,
getValue(intel),
getValue(preview),
getValue(factors),
getValue(insiders),
getValue(earnings)
);
const filename = `earnings-preview-${t.toLowerCase()}-${new Date().toISOString().split("T")[0]}.md`;
writeFileSync(filename, report);
console.log(` ${t}: saved to ${filename}`);
} catch (err) {
console.error(` ${t}: failed — ${err.message}`);
}
}
}
// Run batch mode with --batch flag
if (process.argv.includes("--batch")) {
batchPreview().catch(console.error);
}
Step 7 — Extend with custom queries
Use the OMNI intelligence query endpoint to add custom research questions to the brief.
async function askQuestion(ticker, question) {
const result = await client.intelligence.query({
ticker,
question,
});
return result?.data?.answer || null;
}
// Example: add to the report generation
const customQuestions = [
"What were the key takeaways from the last earnings call?",
"What are the biggest risks heading into this quarter?",
"How has management guidance changed over the last two quarters?",
];
for (const q of customQuestions) {
const answer = await askQuestion(ticker, q);
if (answer) {
lines.push(`### ${q}`);
lines.push("");
lines.push(answer);
lines.push("");
}
}
Next steps
- Add technical indicators: Pull price bars from
/v1/market/bars and compute moving averages, RSI, or other signals.
- Include peer comparison: Use
/v1/intelligence/security for multiple tickers to build a peer comparison table.
- Automate distribution: Pipe the markdown report to a Slack channel, email, or Notion page.
- Schedule before earnings: Use the earnings calendar to automatically trigger preview generation 3 days before each report date.
See the JavaScript SDK documentation for the full list of available methods.