secfeed-crewai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SecFeed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: secfeed-crewai
3
+ Version: 0.1.0
4
+ Summary: SEC EDGAR tools for CrewAI agents: 10-K/10-Q sections, financial statements, insider trades, 8-Ks
5
+ License: MIT
6
+ Project-URL: Homepage, https://secfeedapi.com
7
+ Project-URL: Source, https://github.com/secfeedapi/secfeed-crewai
8
+ Keywords: crewai,sec,edgar,10-k,insider trading,financial statements,agents,tools
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: secfeed>=0.1.3
13
+ Requires-Dist: crewai>=0.80
14
+ Dynamic: license-file
15
+
16
+ # SEC filing tools for CrewAI
17
+
18
+ Give your CrewAI agents SEC EDGAR data: 10-K and 10-Q sections, XBRL financial statements, Form 4 insider trades and 8-K reports. Powered by the [SecFeed API](https://secfeedapi.com).
19
+
20
+ No vector store, no embeddings and no OpenAI key needed for the tools: SecFeed returns the exact filing section (Risk Factors, MD&A, Business), and the tool hands your agent the paragraphs that match its question.
21
+
22
+ ```bash
23
+ pip install "secfeed-crewai @ git+https://github.com/secfeedapi/secfeed-crewai"
24
+ export SECFEED_API_KEY=sf_... # free key: https://secfeedapi.com/signup/
25
+ ```
26
+
27
+ ```python
28
+ from crewai import Agent
29
+ from secfeed_crewai import SearchTenKTool, FinancialStatementsTool, InsiderTradesTool
30
+
31
+ analyst = Agent(
32
+ role="Equity analyst",
33
+ goal="Explain NVDA's risks and financial health from its filings",
34
+ backstory="You only trust primary sources.",
35
+ tools=[SearchTenKTool(), FinancialStatementsTool(), InsiderTradesTool()],
36
+ )
37
+ ```
38
+
39
+ ## Tools
40
+
41
+ | Tool | What the agent gets | API calls |
42
+ |---|---|---|
43
+ | `SearchTenKTool` | The paragraphs of the latest 10-K that answer a question, from the right section (Business, Risk Factors, MD&A, Market Risk), with the filing link | 2 |
44
+ | `SearchTenQTool` | Same for the latest 10-Q | 2 |
45
+ | `FinancialStatementsTool` | Income statement, balance sheet and cash flow from XBRL, last periods side by side | 1 |
46
+ | `InsiderTradesTool` | Latest Form 4 trades: who, role, buy or sell, shares, price, value, 10b5-1 flag | 1 |
47
+ | `RecentEightKTool` | Latest 8-K reports and the items each one covers, with links | 2 |
48
+
49
+ `all_tools()` returns all five. The plain functions behind them live in `secfeed_crewai.core` and work without CrewAI (LangChain, LlamaIndex, plain scripts).
50
+
51
+ Example output of `InsiderTradesTool` for MSFT:
52
+
53
+ ```
54
+ MSFT insider transactions (3817 on file, latest 3):
55
+ - 2026-11-10 ... (EVP, Chief Marketing Officer): open market sale 5.00K sh @ 531.0 = $2.65M
56
+ - 2026-11-09 ... (EVP, Chief Financial Officer): open market sale 5.91K sh @ 544.2359 = $3.22M, 10b5-1 plan
57
+ ```
58
+
59
+ ## Example: stock research crew
60
+
61
+ `examples/stock_analysis/main.py` runs three agents (filings researcher, financial analyst, memo writer) and writes `memo-<TICKER>.md`:
62
+
63
+ ```bash
64
+ export OPENAI_API_KEY=... # or any model CrewAI supports:
65
+ export MODEL=gpt-4o-mini # e.g. MODEL=anthropic/<model> or MODEL=openrouter/<model>
66
+ python examples/stock_analysis/main.py NVDA
67
+ ```
68
+
69
+ A full run makes about a dozen SecFeed calls (tested: 11 for AMD), so the free trial covers it.
70
+
71
+ ## Coming from the CrewAI stock analysis example?
72
+
73
+ The classic CrewAI stock analysis example looks up the latest filing, downloads the whole HTML page, splits it into chunks and embeds them with OpenAI to answer a question. These tools replace that whole file:
74
+
75
+ ```python
76
+ # before
77
+ from tools.sec_tools import SECTools
78
+ tools = [SECTools.search_10q, SECTools.search_10k]
79
+
80
+ # after
81
+ from secfeed_crewai import SearchTenQTool, SearchTenKTool
82
+ tools = [SearchTenQTool(), SearchTenKTool()]
83
+ ```
84
+
85
+ You drop `unstructured`, `faiss` and the embeddings call, and the agent reads the actual section instead of random chunks of the page.
86
+
87
+ ## Also available
88
+
89
+ - [secfeed](https://pypi.org/project/secfeed/) Python and Node SDKs for every SecFeed endpoint (13F holdings, Form D, full text search, XBRL frames and more)
90
+ - [SecFeed MCP server](https://secfeedapi.com/mcp/) for Claude, Cursor and other MCP clients
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,79 @@
1
+ # SEC filing tools for CrewAI
2
+
3
+ Give your CrewAI agents SEC EDGAR data: 10-K and 10-Q sections, XBRL financial statements, Form 4 insider trades and 8-K reports. Powered by the [SecFeed API](https://secfeedapi.com).
4
+
5
+ No vector store, no embeddings and no OpenAI key needed for the tools: SecFeed returns the exact filing section (Risk Factors, MD&A, Business), and the tool hands your agent the paragraphs that match its question.
6
+
7
+ ```bash
8
+ pip install "secfeed-crewai @ git+https://github.com/secfeedapi/secfeed-crewai"
9
+ export SECFEED_API_KEY=sf_... # free key: https://secfeedapi.com/signup/
10
+ ```
11
+
12
+ ```python
13
+ from crewai import Agent
14
+ from secfeed_crewai import SearchTenKTool, FinancialStatementsTool, InsiderTradesTool
15
+
16
+ analyst = Agent(
17
+ role="Equity analyst",
18
+ goal="Explain NVDA's risks and financial health from its filings",
19
+ backstory="You only trust primary sources.",
20
+ tools=[SearchTenKTool(), FinancialStatementsTool(), InsiderTradesTool()],
21
+ )
22
+ ```
23
+
24
+ ## Tools
25
+
26
+ | Tool | What the agent gets | API calls |
27
+ |---|---|---|
28
+ | `SearchTenKTool` | The paragraphs of the latest 10-K that answer a question, from the right section (Business, Risk Factors, MD&A, Market Risk), with the filing link | 2 |
29
+ | `SearchTenQTool` | Same for the latest 10-Q | 2 |
30
+ | `FinancialStatementsTool` | Income statement, balance sheet and cash flow from XBRL, last periods side by side | 1 |
31
+ | `InsiderTradesTool` | Latest Form 4 trades: who, role, buy or sell, shares, price, value, 10b5-1 flag | 1 |
32
+ | `RecentEightKTool` | Latest 8-K reports and the items each one covers, with links | 2 |
33
+
34
+ `all_tools()` returns all five. The plain functions behind them live in `secfeed_crewai.core` and work without CrewAI (LangChain, LlamaIndex, plain scripts).
35
+
36
+ Example output of `InsiderTradesTool` for MSFT:
37
+
38
+ ```
39
+ MSFT insider transactions (3817 on file, latest 3):
40
+ - 2026-11-10 ... (EVP, Chief Marketing Officer): open market sale 5.00K sh @ 531.0 = $2.65M
41
+ - 2026-11-09 ... (EVP, Chief Financial Officer): open market sale 5.91K sh @ 544.2359 = $3.22M, 10b5-1 plan
42
+ ```
43
+
44
+ ## Example: stock research crew
45
+
46
+ `examples/stock_analysis/main.py` runs three agents (filings researcher, financial analyst, memo writer) and writes `memo-<TICKER>.md`:
47
+
48
+ ```bash
49
+ export OPENAI_API_KEY=... # or any model CrewAI supports:
50
+ export MODEL=gpt-4o-mini # e.g. MODEL=anthropic/<model> or MODEL=openrouter/<model>
51
+ python examples/stock_analysis/main.py NVDA
52
+ ```
53
+
54
+ A full run makes about a dozen SecFeed calls (tested: 11 for AMD), so the free trial covers it.
55
+
56
+ ## Coming from the CrewAI stock analysis example?
57
+
58
+ The classic CrewAI stock analysis example looks up the latest filing, downloads the whole HTML page, splits it into chunks and embeds them with OpenAI to answer a question. These tools replace that whole file:
59
+
60
+ ```python
61
+ # before
62
+ from tools.sec_tools import SECTools
63
+ tools = [SECTools.search_10q, SECTools.search_10k]
64
+
65
+ # after
66
+ from secfeed_crewai import SearchTenQTool, SearchTenKTool
67
+ tools = [SearchTenQTool(), SearchTenKTool()]
68
+ ```
69
+
70
+ You drop `unstructured`, `faiss` and the embeddings call, and the agent reads the actual section instead of random chunks of the page.
71
+
72
+ ## Also available
73
+
74
+ - [secfeed](https://pypi.org/project/secfeed/) Python and Node SDKs for every SecFeed endpoint (13F holdings, Form D, full text search, XBRL frames and more)
75
+ - [SecFeed MCP server](https://secfeedapi.com/mcp/) for Claude, Cursor and other MCP clients
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "secfeed-crewai"
7
+ version = "0.1.0"
8
+ description = "SEC EDGAR tools for CrewAI agents: 10-K/10-Q sections, financial statements, insider trades, 8-Ks"
9
+ requires-python = ">=3.10"
10
+ readme = "README.md"
11
+ license = {text = "MIT"}
12
+ dependencies = ["secfeed>=0.1.3", "crewai>=0.80"]
13
+ keywords = ["crewai", "sec", "edgar", "10-k", "insider trading", "financial statements", "agents", "tools"]
14
+
15
+ [project.urls]
16
+ Homepage = "https://secfeedapi.com"
17
+ Source = "https://github.com/secfeedapi/secfeed-crewai"
18
+
19
+ [tool.setuptools]
20
+ packages = ["secfeed_crewai"]
@@ -0,0 +1,8 @@
1
+ """SEC EDGAR tools for CrewAI agents, powered by the SecFeed API (https://secfeedapi.com)."""
2
+ __version__ = "0.1.0"
3
+
4
+ try:
5
+ from .tools import (FinancialStatementsTool, InsiderTradesTool, RecentEightKTool, SearchTenKTool, # noqa: F401
6
+ SearchTenQTool, all_tools)
7
+ except ImportError: # crewai not installed: the plain functions in secfeed_crewai.core still work
8
+ pass
@@ -0,0 +1,172 @@
1
+ """Plain functions behind the CrewAI tools. No CrewAI import here, so they work in any agent framework (or none).
2
+
3
+ Every function returns compact text sized for an LLM context window, and costs one or two SecFeed API calls.
4
+ """
5
+ import os
6
+ import re
7
+
8
+ from secfeed import Client, SecFeedError
9
+
10
+ # Which part of a filing answers which kind of question. 10-K items: 1 Business, 1A Risk Factors, 7 MD&A, 7A market risk.
11
+ # 10-Q items: part1item2 MD&A, part2item1a risk factor updates.
12
+ SECTIONS = {
13
+ "10-K": {"business": "1", "risk": "1A", "mdna": "7", "market_risk": "7A"},
14
+ "10-Q": {"business": "part1item2", "risk": "part2item1a", "mdna": "part1item2", "market_risk": "part1item3"},
15
+ }
16
+ ROUTES = [ # (words in the question, section) checked in order; MD&A is the default
17
+ (("risk", "threat", "uncertain", "litigation", "regulat", "lawsuit"), "risk"),
18
+ (("interest rate", "currency", "foreign exchange", "hedg", "market risk"), "market_risk"),
19
+ (("product", "segment", "customer", "competit", "employee", "strategy", "business model", "what does"), "business"),
20
+ ]
21
+ SECTION_NAMES = {"business": "Business", "risk": "Risk Factors", "mdna": "Management's Discussion and Analysis",
22
+ "market_risk": "Market Risk"}
23
+ STOP = set("the a an of to in and or for on is are was were be by with what how did does do its it this that from as at "
24
+ "which who their they has have had last year quarter company".split())
25
+ MAX_CHARS = 6000
26
+
27
+
28
+ def client(api_key=None):
29
+ key = api_key or os.environ.get("SECFEED_API_KEY")
30
+ if not key:
31
+ raise SecFeedError("Set SECFEED_API_KEY (free key at https://secfeedapi.com/signup/)")
32
+ return Client(key)
33
+
34
+
35
+ def _section_for(question):
36
+ q = question.lower()
37
+ for words, section in ROUTES:
38
+ if any(w in q for w in words):
39
+ return section
40
+ return "mdna"
41
+
42
+
43
+ def _terms(text):
44
+ return {w for w in re.findall(r"[a-z][a-z0-9&\-]+", text.lower()) if w not in STOP and len(w) > 2}
45
+
46
+
47
+ def best_passages(text, question, max_chars=MAX_CHARS):
48
+ """Paragraphs of `text` ranked by how many of the question's words they contain, kept in document order.
49
+ Simple keyword ranking: no embeddings, no vector store, no extra API key."""
50
+ paras = [p.strip() for p in re.split(r"\n\s*\n", text) if len(p.strip()) > 80]
51
+ terms = _terms(question)
52
+ if not paras:
53
+ return text[:max_chars]
54
+ scored = sorted(range(len(paras)), key=lambda i: -len(terms & _terms(paras[i])))
55
+ keep, size = set(), 0
56
+ for i in scored:
57
+ if size + len(paras[i]) > max_chars and keep:
58
+ continue
59
+ keep.add(i)
60
+ size += len(paras[i])
61
+ if size >= max_chars:
62
+ break
63
+ return "\n\n".join(paras[i] for i in sorted(keep))
64
+
65
+
66
+ _FILINGS, _SECTIONS = {}, {} # per-process caches: agents often ask several questions about the same filing
67
+
68
+
69
+ def latest_filing(sf, ticker, form):
70
+ k = (ticker.upper(), form)
71
+ if k not in _FILINGS:
72
+ res = sf.filings(f'ticker:{ticker.upper()} AND formType:"{form}"', size=1)
73
+ _FILINGS[k] = (res.get("filings") or [None])[0]
74
+ return _FILINGS[k]
75
+
76
+
77
+ def _section_text(sf, url, item):
78
+ if (url, item) not in _SECTIONS:
79
+ _SECTIONS[(url, item)] = sf.extract(url, item)
80
+ return _SECTIONS[(url, item)]
81
+
82
+
83
+ def search_filing(ticker, question, form="10-K", section=None, api_key=None):
84
+ """Answer material for `question` from the company's latest 10-K or 10-Q: picks the right section (or `section`:
85
+ business, risk, mdna, market_risk), then returns the most relevant paragraphs."""
86
+ sf = client(api_key)
87
+ form = form.upper()
88
+ if form not in SECTIONS:
89
+ return "form must be 10-K or 10-Q"
90
+ f = latest_filing(sf, ticker, form)
91
+ if not f:
92
+ return f"No {form} found for {ticker}. Check the ticker."
93
+ section = section if section in SECTIONS[form] else _section_for(question)
94
+ try:
95
+ text = _section_text(sf, f["linkToFilingDetails"], SECTIONS[form][section])
96
+ except SecFeedError as e:
97
+ return f"Could not read {SECTION_NAMES[section]} of {f['companyName']} {form} filed {f['filedAt']}: {e}"
98
+ head = (f"{f['companyName']} ({ticker.upper()}) {form} filed {f['filedAt']}, section: {SECTION_NAMES[section]}\n"
99
+ f"Source: {f['linkToFilingDetails']}\n\n")
100
+ return head + best_passages(text, question)
101
+
102
+
103
+ def _money(v):
104
+ try:
105
+ v = float(v)
106
+ except (TypeError, ValueError):
107
+ return str(v)
108
+ for div, unit in ((1e9, "B"), (1e6, "M"), (1e3, "K")):
109
+ if abs(v) >= div:
110
+ return f"{v / div:,.2f}{unit}"
111
+ return f"{v:,.2f}"
112
+
113
+
114
+ def financials(ticker, form="10-K", api_key=None):
115
+ """Income statement, balance sheet and cash flow from the latest 10-K or 10-Q (XBRL), newest periods first."""
116
+ sf = client(api_key)
117
+ rows = sf.financial_statements(ticker.upper(), form_type=form.upper(), n=1)
118
+ if not rows:
119
+ return f"No XBRL financial statements found for {ticker}."
120
+ data = rows[0]
121
+ out = [f"{ticker.upper()} {form.upper()} financial statements (accession {data.get('accessionNo')}), USD unless noted"]
122
+ for name, items in data.items():
123
+ if not isinstance(items, dict):
124
+ continue
125
+ out.append(f"\n{name}")
126
+ for concept, facts in list(items.items())[:25]:
127
+ if not isinstance(facts, list):
128
+ continue
129
+ vals = []
130
+ for x in [x for x in facts if not x.get("segment")][:2]:
131
+ p = x.get("period") or {}
132
+ when = p.get("endDate") or p.get("instant") or ""
133
+ vals.append(f"{when}: {_money(x.get('value'))}")
134
+ if vals:
135
+ out.append(f" {concept}: " + "; ".join(vals))
136
+ return "\n".join(out)[:12000]
137
+
138
+
139
+ CODES = {"P": "open market buy", "S": "open market sale", "A": "grant", "M": "option exercise", "F": "tax withholding",
140
+ "G": "gift"}
141
+
142
+
143
+ def insider_trades(ticker, code=None, limit=20, api_key=None):
144
+ """Latest Form 4 insider transactions. code: P (buys), S (sales) or None for all."""
145
+ sf = client(api_key)
146
+ res = sf.insider_trades(ticker=ticker.upper(), code=code, size=min(int(limit), 50))
147
+ tx = res.get("transactions") or []
148
+ if not tx:
149
+ return f"No insider transactions found for {ticker}."
150
+ out = [f"{ticker.upper()} insider transactions ({res.get('total')} on file, latest {len(tx)}):"]
151
+ for t in tx:
152
+ o = (t.get("owners") or [{}])[0]
153
+ role = o.get("officerTitle") or ("Director" if o.get("isDirector") else "10% owner" if o.get("isTenPercentOwner") else "")
154
+ val = f" = ${_money(t['shares'] * t['price'])}" if t.get("shares") and t.get("price") else ""
155
+ plan = ", 10b5-1 plan" if t.get("aff10b5one") else ""
156
+ out.append(f"- {t.get('tx_date')} {o.get('name')} ({role}): {CODES.get(t.get('code'), t.get('code'))} "
157
+ f"{_money(t.get('shares'))} sh @ {t.get('price')}{val}{plan}")
158
+ return "\n".join(out)
159
+
160
+
161
+ def recent_8k(ticker, limit=10, api_key=None):
162
+ """Latest 8-K current reports with the items each one covers (earnings, deals, leadership, etc.)."""
163
+ sf = client(api_key)
164
+ f = latest_filing(sf, ticker, "8-K")
165
+ if not f:
166
+ return f"No 8-K found for {ticker}."
167
+ res = sf.events_8k(cik=f["cik"], size=min(int(limit), 50))
168
+ out = [f"{f['companyName']} ({ticker.upper()}) latest 8-K reports:"]
169
+ for e in res.get("filings") or []:
170
+ items = "; ".join(f"{i['item']} {i['title']}" for i in e.get("items") or [])
171
+ out.append(f"- {e.get('filed')}: {items} {e.get('url')}")
172
+ return "\n".join(out)
@@ -0,0 +1,85 @@
1
+ """CrewAI tools for SEC EDGAR data via the SecFeed API. Set SECFEED_API_KEY in the environment."""
2
+ from typing import Literal, Optional, Type
3
+
4
+ from crewai.tools import BaseTool
5
+ from pydantic import BaseModel, Field
6
+
7
+ from . import core
8
+
9
+
10
+ class FilingQuestion(BaseModel):
11
+ ticker: str = Field(..., description="Stock ticker, e.g. NVDA")
12
+ question: str = Field(..., description="What you want to find out from the filing")
13
+ section: Optional[str] = Field(
14
+ None, description="Optional: business, risk, mdna or market_risk. Picked from the question when left empty.")
15
+
16
+
17
+ class SearchTenKTool(BaseTool):
18
+ name: str = "Search latest 10-K"
19
+ description: str = ("Reads the company's latest annual report (10-K) and returns the passages most relevant to the "
20
+ "question, with a link to the filing. Good for business model, risk factors and full-year results.")
21
+ args_schema: Type[BaseModel] = FilingQuestion
22
+
23
+ def _run(self, ticker: str, question: str, section: Optional[str] = None) -> str:
24
+ return core.search_filing(ticker, question, "10-K", section or None)
25
+
26
+
27
+ class SearchTenQTool(BaseTool):
28
+ name: str = "Search latest 10-Q"
29
+ description: str = ("Reads the company's latest quarterly report (10-Q) and returns the passages most relevant to the "
30
+ "question, with a link to the filing. Good for the most recent quarter's results and outlook.")
31
+ args_schema: Type[BaseModel] = FilingQuestion
32
+
33
+ def _run(self, ticker: str, question: str, section: Optional[str] = None) -> str:
34
+ return core.search_filing(ticker, question, "10-Q", section or None)
35
+
36
+
37
+ class FinancialsInput(BaseModel):
38
+ ticker: str = Field(..., description="Stock ticker, e.g. NVDA")
39
+ form: Literal["10-K", "10-Q"] = Field("10-K", description="10-K for annual figures, 10-Q for the latest quarter")
40
+
41
+
42
+ class FinancialStatementsTool(BaseTool):
43
+ name: str = "Financial statements"
44
+ description: str = ("Income statement, balance sheet and cash flow figures from the latest 10-K or 10-Q, taken from "
45
+ "the filing's XBRL data, with the last few periods side by side.")
46
+ args_schema: Type[BaseModel] = FinancialsInput
47
+
48
+ def _run(self, ticker: str, form: str = "10-K") -> str:
49
+ return core.financials(ticker, form)
50
+
51
+
52
+ class InsiderInput(BaseModel):
53
+ ticker: str = Field(..., description="Stock ticker, e.g. NVDA")
54
+ code: Optional[str] = Field(None, description="P for open market buys only, S for sales only, empty for all")
55
+ limit: int = Field(20, description="How many transactions, up to 50")
56
+
57
+
58
+ class InsiderTradesTool(BaseTool):
59
+ name: str = "Insider trades"
60
+ description: str = ("Latest Form 4 insider transactions for a company: who traded, their role, buy or sell, shares, "
61
+ "price, value and whether it was under a 10b5-1 plan.")
62
+ args_schema: Type[BaseModel] = InsiderInput
63
+
64
+ def _run(self, ticker: str, code: Optional[str] = None, limit: int = 20) -> str:
65
+ code = (code or "").strip().upper()[:1] or None
66
+ return core.insider_trades(ticker, code if code in ("P", "S") else None, limit)
67
+
68
+
69
+ class EightKInput(BaseModel):
70
+ ticker: str = Field(..., description="Stock ticker, e.g. NVDA")
71
+ limit: int = Field(10, description="How many reports, up to 50")
72
+
73
+
74
+ class RecentEightKTool(BaseTool):
75
+ name: str = "Recent 8-K reports"
76
+ description: str = ("The company's latest 8-K current reports and what each one covers (earnings releases, material "
77
+ "agreements, leadership changes, other events), with links.")
78
+ args_schema: Type[BaseModel] = EightKInput
79
+
80
+ def _run(self, ticker: str, limit: int = 10) -> str:
81
+ return core.recent_8k(ticker, limit)
82
+
83
+
84
+ def all_tools():
85
+ return [SearchTenKTool(), SearchTenQTool(), FinancialStatementsTool(), InsiderTradesTool(), RecentEightKTool()]
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: secfeed-crewai
3
+ Version: 0.1.0
4
+ Summary: SEC EDGAR tools for CrewAI agents: 10-K/10-Q sections, financial statements, insider trades, 8-Ks
5
+ License: MIT
6
+ Project-URL: Homepage, https://secfeedapi.com
7
+ Project-URL: Source, https://github.com/secfeedapi/secfeed-crewai
8
+ Keywords: crewai,sec,edgar,10-k,insider trading,financial statements,agents,tools
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: secfeed>=0.1.3
13
+ Requires-Dist: crewai>=0.80
14
+ Dynamic: license-file
15
+
16
+ # SEC filing tools for CrewAI
17
+
18
+ Give your CrewAI agents SEC EDGAR data: 10-K and 10-Q sections, XBRL financial statements, Form 4 insider trades and 8-K reports. Powered by the [SecFeed API](https://secfeedapi.com).
19
+
20
+ No vector store, no embeddings and no OpenAI key needed for the tools: SecFeed returns the exact filing section (Risk Factors, MD&A, Business), and the tool hands your agent the paragraphs that match its question.
21
+
22
+ ```bash
23
+ pip install "secfeed-crewai @ git+https://github.com/secfeedapi/secfeed-crewai"
24
+ export SECFEED_API_KEY=sf_... # free key: https://secfeedapi.com/signup/
25
+ ```
26
+
27
+ ```python
28
+ from crewai import Agent
29
+ from secfeed_crewai import SearchTenKTool, FinancialStatementsTool, InsiderTradesTool
30
+
31
+ analyst = Agent(
32
+ role="Equity analyst",
33
+ goal="Explain NVDA's risks and financial health from its filings",
34
+ backstory="You only trust primary sources.",
35
+ tools=[SearchTenKTool(), FinancialStatementsTool(), InsiderTradesTool()],
36
+ )
37
+ ```
38
+
39
+ ## Tools
40
+
41
+ | Tool | What the agent gets | API calls |
42
+ |---|---|---|
43
+ | `SearchTenKTool` | The paragraphs of the latest 10-K that answer a question, from the right section (Business, Risk Factors, MD&A, Market Risk), with the filing link | 2 |
44
+ | `SearchTenQTool` | Same for the latest 10-Q | 2 |
45
+ | `FinancialStatementsTool` | Income statement, balance sheet and cash flow from XBRL, last periods side by side | 1 |
46
+ | `InsiderTradesTool` | Latest Form 4 trades: who, role, buy or sell, shares, price, value, 10b5-1 flag | 1 |
47
+ | `RecentEightKTool` | Latest 8-K reports and the items each one covers, with links | 2 |
48
+
49
+ `all_tools()` returns all five. The plain functions behind them live in `secfeed_crewai.core` and work without CrewAI (LangChain, LlamaIndex, plain scripts).
50
+
51
+ Example output of `InsiderTradesTool` for MSFT:
52
+
53
+ ```
54
+ MSFT insider transactions (3817 on file, latest 3):
55
+ - 2026-11-10 ... (EVP, Chief Marketing Officer): open market sale 5.00K sh @ 531.0 = $2.65M
56
+ - 2026-11-09 ... (EVP, Chief Financial Officer): open market sale 5.91K sh @ 544.2359 = $3.22M, 10b5-1 plan
57
+ ```
58
+
59
+ ## Example: stock research crew
60
+
61
+ `examples/stock_analysis/main.py` runs three agents (filings researcher, financial analyst, memo writer) and writes `memo-<TICKER>.md`:
62
+
63
+ ```bash
64
+ export OPENAI_API_KEY=... # or any model CrewAI supports:
65
+ export MODEL=gpt-4o-mini # e.g. MODEL=anthropic/<model> or MODEL=openrouter/<model>
66
+ python examples/stock_analysis/main.py NVDA
67
+ ```
68
+
69
+ A full run makes about a dozen SecFeed calls (tested: 11 for AMD), so the free trial covers it.
70
+
71
+ ## Coming from the CrewAI stock analysis example?
72
+
73
+ The classic CrewAI stock analysis example looks up the latest filing, downloads the whole HTML page, splits it into chunks and embeds them with OpenAI to answer a question. These tools replace that whole file:
74
+
75
+ ```python
76
+ # before
77
+ from tools.sec_tools import SECTools
78
+ tools = [SECTools.search_10q, SECTools.search_10k]
79
+
80
+ # after
81
+ from secfeed_crewai import SearchTenQTool, SearchTenKTool
82
+ tools = [SearchTenQTool(), SearchTenKTool()]
83
+ ```
84
+
85
+ You drop `unstructured`, `faiss` and the embeddings call, and the agent reads the actual section instead of random chunks of the page.
86
+
87
+ ## Also available
88
+
89
+ - [secfeed](https://pypi.org/project/secfeed/) Python and Node SDKs for every SecFeed endpoint (13F holdings, Form D, full text search, XBRL frames and more)
90
+ - [SecFeed MCP server](https://secfeedapi.com/mcp/) for Claude, Cursor and other MCP clients
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ secfeed_crewai/__init__.py
5
+ secfeed_crewai/core.py
6
+ secfeed_crewai/tools.py
7
+ secfeed_crewai.egg-info/PKG-INFO
8
+ secfeed_crewai.egg-info/SOURCES.txt
9
+ secfeed_crewai.egg-info/dependency_links.txt
10
+ secfeed_crewai.egg-info/requires.txt
11
+ secfeed_crewai.egg-info/top_level.txt
12
+ tests/test_core.py
@@ -0,0 +1,2 @@
1
+ secfeed>=0.1.3
2
+ crewai>=0.80
@@ -0,0 +1 @@
1
+ secfeed_crewai
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ """Offline tests: section routing and passage ranking. Run: python -m unittest discover -s tests"""
2
+ import unittest
3
+
4
+ from secfeed_crewai import core
5
+
6
+
7
+ class Routing(unittest.TestCase):
8
+ def test_sections(self):
9
+ self.assertEqual(core._section_for("What are the biggest risks?"), "risk")
10
+ self.assertEqual(core._section_for("Who are its main competitors?"), "business")
11
+ self.assertEqual(core._section_for("How exposed is it to interest rate changes?"), "market_risk")
12
+ self.assertEqual(core._section_for("How did revenue grow?"), "mdna")
13
+
14
+
15
+ class Passages(unittest.TestCase):
16
+ def test_keeps_matching_paragraphs_in_order(self):
17
+ filler = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore. " * 3
18
+ text = "\n\n".join([filler, "Export controls on China reduced our data center revenue in the quarter. " * 2,
19
+ filler, "Tariffs and export controls may change again and hurt China sales. " * 2])
20
+ out = core.best_passages(text, "export controls China", max_chars=400)
21
+ self.assertIn("reduced our data center", out)
22
+ self.assertIn("Tariffs", out)
23
+ self.assertLess(out.index("reduced"), out.index("Tariffs"))
24
+ self.assertNotIn("Lorem", out)
25
+
26
+ def test_money(self):
27
+ self.assertEqual(core._money("215938000000"), "215.94B")
28
+ self.assertEqual(core._money(5000), "5.00K")
29
+
30
+
31
+ if __name__ == "__main__":
32
+ unittest.main()