secfeed 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.
- secfeed-0.1.0/PKG-INFO +32 -0
- secfeed-0.1.0/README.md +21 -0
- secfeed-0.1.0/pyproject.toml +19 -0
- secfeed-0.1.0/secfeed/__init__.py +150 -0
- secfeed-0.1.0/secfeed.egg-info/PKG-INFO +32 -0
- secfeed-0.1.0/secfeed.egg-info/SOURCES.txt +7 -0
- secfeed-0.1.0/secfeed.egg-info/dependency_links.txt +1 -0
- secfeed-0.1.0/secfeed.egg-info/top_level.txt +1 -0
- secfeed-0.1.0/setup.cfg +4 -0
secfeed-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: secfeed
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the SecFeed SEC EDGAR API
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://secfeedapi.com
|
|
7
|
+
Project-URL: Documentation, https://secfeedapi.com/docs/
|
|
8
|
+
Keywords: sec,edgar,filings,xbrl,13f,insider trading,api
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# secfeed (Python)
|
|
13
|
+
|
|
14
|
+
Client for the [SecFeed](https://secfeedapi.com) SEC EDGAR API. No dependencies, Python 3.8+.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install secfeed
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from secfeed import Client
|
|
22
|
+
|
|
23
|
+
sf = Client("sf_...") # free key at https://secfeedapi.com
|
|
24
|
+
sf.filings('ticker:AAPL AND formType:"10-K"', size=5)
|
|
25
|
+
sf.insider_trades(ticker="NVDA", code="P")
|
|
26
|
+
sf.financial_statements("AAPL")
|
|
27
|
+
sf.events_8k(item="5.02")
|
|
28
|
+
for filing in sf.stream(form_types=["8-K"]):
|
|
29
|
+
print(filing["companyName"], filing["formType"])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Every endpoint in the [API docs](https://secfeedapi.com/docs/) has a method. Errors raise `SecFeedError`.
|
secfeed-0.1.0/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# secfeed (Python)
|
|
2
|
+
|
|
3
|
+
Client for the [SecFeed](https://secfeedapi.com) SEC EDGAR API. No dependencies, Python 3.8+.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install secfeed
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from secfeed import Client
|
|
11
|
+
|
|
12
|
+
sf = Client("sf_...") # free key at https://secfeedapi.com
|
|
13
|
+
sf.filings('ticker:AAPL AND formType:"10-K"', size=5)
|
|
14
|
+
sf.insider_trades(ticker="NVDA", code="P")
|
|
15
|
+
sf.financial_statements("AAPL")
|
|
16
|
+
sf.events_8k(item="5.02")
|
|
17
|
+
for filing in sf.stream(form_types=["8-K"]):
|
|
18
|
+
print(filing["companyName"], filing["formType"])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Every endpoint in the [API docs](https://secfeedapi.com/docs/) has a method. Errors raise `SecFeedError`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "secfeed"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the SecFeed SEC EDGAR API"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
keywords = ["sec", "edgar", "filings", "xbrl", "13f", "insider trading", "api"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://secfeedapi.com"
|
|
16
|
+
Documentation = "https://secfeedapi.com/docs/"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
packages = ["secfeed"]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""SecFeed Python SDK. Stdlib only.
|
|
2
|
+
|
|
3
|
+
pip install secfeed
|
|
4
|
+
|
|
5
|
+
from secfeed import Client
|
|
6
|
+
sf = Client("sf_...")
|
|
7
|
+
sf.filings('ticker:AAPL AND formType:"10-K"')
|
|
8
|
+
sf.insider_trades(ticker="NVDA", code="P")
|
|
9
|
+
for filing in sf.stream(form_types=["8-K"]): ...
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import urllib.parse
|
|
13
|
+
import urllib.request
|
|
14
|
+
|
|
15
|
+
__all__ = ["Client", "SecFeedError"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SecFeedError(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Client:
|
|
23
|
+
def __init__(self, api_key, base_url="https://secfeedapi.com"):
|
|
24
|
+
self.key, self.base = api_key, base_url.rstrip("/")
|
|
25
|
+
|
|
26
|
+
def _req(self, method, path, params=None, body=None, raw=False):
|
|
27
|
+
q = urllib.parse.urlencode({k: v for k, v in (params or {}).items() if v is not None})
|
|
28
|
+
req = urllib.request.Request(f"{self.base}{path}{'?' + q if q else ''}", method=method,
|
|
29
|
+
data=json.dumps(body).encode() if body is not None else None,
|
|
30
|
+
headers={"Authorization": f"Bearer {self.key}", "Content-Type": "application/json"})
|
|
31
|
+
try:
|
|
32
|
+
with urllib.request.urlopen(req, timeout=120) as r:
|
|
33
|
+
data = r.read()
|
|
34
|
+
except urllib.error.HTTPError as e:
|
|
35
|
+
raise SecFeedError(f"{e.code}: {e.read().decode(errors='replace')}") from None
|
|
36
|
+
return data if raw else json.loads(data)
|
|
37
|
+
|
|
38
|
+
# search
|
|
39
|
+
def filings(self, query, start=0, size=50, sort="desc"):
|
|
40
|
+
return self._req("POST", "/v1/filings", body={"query": query, "from": start, "size": size, "sort": sort})
|
|
41
|
+
|
|
42
|
+
def full_text_search(self, query, form_types=None, start_date=None, end_date=None, page=1):
|
|
43
|
+
return self._req("GET", "/v1/full-text-search", {"query": query, "formTypes": ",".join(form_types or []) or None,
|
|
44
|
+
"startDate": start_date, "endDate": end_date, "page": page})
|
|
45
|
+
|
|
46
|
+
# documents
|
|
47
|
+
def files(self, cik, accession_no):
|
|
48
|
+
return self._req("GET", "/v1/files", {"cik": cik, "accessionNo": accession_no})
|
|
49
|
+
|
|
50
|
+
def download(self, url):
|
|
51
|
+
return self._req("GET", "/v1/download", {"url": url}, raw=True)
|
|
52
|
+
|
|
53
|
+
def pdf(self, url):
|
|
54
|
+
return self._req("GET", "/v1/pdf", {"url": url}, raw=True)
|
|
55
|
+
|
|
56
|
+
def extract(self, url, item, type="text"):
|
|
57
|
+
return self._req("GET", "/v1/extractor", {"url": url, "item": item, "type": type}, raw=True).decode()
|
|
58
|
+
|
|
59
|
+
# financials
|
|
60
|
+
def xbrl_to_json(self, accession_no=None, htm_url=None, cik=None):
|
|
61
|
+
return self._req("GET", "/v1/xbrl-to-json", {"accessionNo": accession_no, "htmUrl": htm_url, "cik": cik})
|
|
62
|
+
|
|
63
|
+
def financial_statements(self, ticker, form_type="10-K", n=1):
|
|
64
|
+
return self._req("GET", "/v1/financial-statements", {"ticker": ticker, "formType": form_type, "n": n})
|
|
65
|
+
|
|
66
|
+
def company_facts(self, ticker):
|
|
67
|
+
return self._req("GET", "/v1/xbrl/company-facts", {"ticker": ticker})
|
|
68
|
+
|
|
69
|
+
def frames(self, concept, period, unit="USD"):
|
|
70
|
+
return self._req("GET", "/v1/xbrl/frames", {"concept": concept, "period": period, "unit": unit})
|
|
71
|
+
|
|
72
|
+
# ownership, events, forms
|
|
73
|
+
def insider_trades(self, **filters):
|
|
74
|
+
return self._req("GET", "/v1/insider-trading", filters)
|
|
75
|
+
|
|
76
|
+
def holdings_13f(self, cusip=None, cik=None, period=None):
|
|
77
|
+
return self._req("GET", "/v1/form-13f/holdings", {"cusip": cusip, "cik": cik, "period": period})
|
|
78
|
+
|
|
79
|
+
def form(self, family, **filters):
|
|
80
|
+
"""family: form-144, form-13f, form-13d-13g, form-d, form-c, reg-a, n-port, n-cen, n-px"""
|
|
81
|
+
return self._req("GET", f"/v1/{family}", filters)
|
|
82
|
+
|
|
83
|
+
def events_8k(self, item=None, **filters):
|
|
84
|
+
return self._req("GET", "/v1/form-8k", {"item": item, **filters})
|
|
85
|
+
|
|
86
|
+
# company data & reference
|
|
87
|
+
def entity(self, ticker_or_cik):
|
|
88
|
+
return self._req("GET", "/v1/entities", {"cik": ticker_or_cik})
|
|
89
|
+
|
|
90
|
+
def map_ticker(self, ticker):
|
|
91
|
+
return self._req("GET", "/v1/mapping/ticker", {"ticker": ticker})
|
|
92
|
+
|
|
93
|
+
def map_cusip(self, cusip):
|
|
94
|
+
return self._req("GET", "/v1/mapping/cusip", {"cusip": cusip})
|
|
95
|
+
|
|
96
|
+
def shares_outstanding(self, ticker):
|
|
97
|
+
return self._req("GET", "/v1/shares-outstanding", {"ticker": ticker})
|
|
98
|
+
|
|
99
|
+
def auditor(self, ticker):
|
|
100
|
+
return self._req("GET", "/v1/auditor", {"ticker": ticker})
|
|
101
|
+
|
|
102
|
+
def executive_compensation(self, ticker):
|
|
103
|
+
return self._req("GET", "/v1/executive-compensation", {"ticker": ticker})
|
|
104
|
+
|
|
105
|
+
def directors(self, ticker):
|
|
106
|
+
return self._req("GET", "/v1/directors", {"ticker": ticker})
|
|
107
|
+
|
|
108
|
+
def subsidiaries(self, ticker):
|
|
109
|
+
return self._req("GET", "/v1/subsidiaries", {"ticker": ticker})
|
|
110
|
+
|
|
111
|
+
# datasets extracted from proxy statements, audit reports and enforcement releases
|
|
112
|
+
# filters: cik, ticker, accessionNo, startDate, endDate, from, size, and data.<path>=value
|
|
113
|
+
def dataset(self, path, **filters):
|
|
114
|
+
return self._req("GET", f"/v1/{path}", filters)
|
|
115
|
+
|
|
116
|
+
def audit_report_content(self, id):
|
|
117
|
+
return self._req("GET", "/v1/audit-reports/content", {"id": id}, raw=True).decode()
|
|
118
|
+
|
|
119
|
+
def ipos(self, **filters):
|
|
120
|
+
return self._req("GET", "/v1/ipo", filters)
|
|
121
|
+
|
|
122
|
+
# enforcement
|
|
123
|
+
def enforcement(self, kind="litigation-releases", page=0, details=False):
|
|
124
|
+
return self._req("GET", f"/v1/enforcement/{kind}", {"page": page, "details": str(details).lower()})
|
|
125
|
+
|
|
126
|
+
# real time
|
|
127
|
+
def stream(self, form_types=None, ciks=None):
|
|
128
|
+
"""Yield each new filing as a dict, as soon as EDGAR publishes it."""
|
|
129
|
+
q = urllib.parse.urlencode({"formTypes": ",".join(form_types or []), "ciks": ",".join(map(str, ciks or []))})
|
|
130
|
+
req = urllib.request.Request(f"{self.base}/v1/stream?{q}", headers={"Authorization": f"Bearer {self.key}"})
|
|
131
|
+
with urllib.request.urlopen(req) as r:
|
|
132
|
+
for line in r:
|
|
133
|
+
if line.startswith(b"data: "):
|
|
134
|
+
yield json.loads(line[6:])
|
|
135
|
+
|
|
136
|
+
def add_webhook(self, url, form_types=None, ciks=None):
|
|
137
|
+
return self._req("POST", "/v1/webhooks", body={"url": url, "formTypes": form_types or [], "ciks": ciks or []})
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
DATASETS = {
|
|
141
|
+
"director_compensation": "director-compensation", "executive_compensation_governance": "executive-compensation-governance",
|
|
142
|
+
"severance_change_in_control": "severance-change-in-control", "board_oversight": "board-oversight",
|
|
143
|
+
"shareholder_rights": "shareholder-rights", "esg_oversight": "esg-oversight", "compliance_signals": "compliance-signals",
|
|
144
|
+
"vote_standards": "vote-standards", "shareholder_proposals_proxy_contests": "shareholder-proposals-proxy-contests",
|
|
145
|
+
"insider_5pct_holder_ownership": "insider-5pct-holder-ownership", "fund_reorganizations": "fund-reorganizations",
|
|
146
|
+
"audit_reports_financial_statements": "audit-reports/financial-statements", "audit_reports_icfr": "audit-reports/icfr",
|
|
147
|
+
"enforcement_actions": "sec-enforcement-actions",
|
|
148
|
+
}
|
|
149
|
+
for _name, _path in DATASETS.items(): # sf.board_oversight(ticker="AAPL") and so on
|
|
150
|
+
setattr(Client, _name, lambda self, _p=_path, **filters: self.dataset(_p, **filters))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: secfeed
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the SecFeed SEC EDGAR API
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://secfeedapi.com
|
|
7
|
+
Project-URL: Documentation, https://secfeedapi.com/docs/
|
|
8
|
+
Keywords: sec,edgar,filings,xbrl,13f,insider trading,api
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# secfeed (Python)
|
|
13
|
+
|
|
14
|
+
Client for the [SecFeed](https://secfeedapi.com) SEC EDGAR API. No dependencies, Python 3.8+.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install secfeed
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from secfeed import Client
|
|
22
|
+
|
|
23
|
+
sf = Client("sf_...") # free key at https://secfeedapi.com
|
|
24
|
+
sf.filings('ticker:AAPL AND formType:"10-K"', size=5)
|
|
25
|
+
sf.insider_trades(ticker="NVDA", code="P")
|
|
26
|
+
sf.financial_statements("AAPL")
|
|
27
|
+
sf.events_8k(item="5.02")
|
|
28
|
+
for filing in sf.stream(form_types=["8-K"]):
|
|
29
|
+
print(filing["companyName"], filing["formType"])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Every endpoint in the [API docs](https://secfeedapi.com/docs/) has a method. Errors raise `SecFeedError`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
secfeed
|
secfeed-0.1.0/setup.cfg
ADDED