sec_api 1.0.0
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.
- checksums.yaml +7 -0
- data/.devcontainer/Dockerfile +54 -0
- data/.devcontainer/README.md +178 -0
- data/.devcontainer/devcontainer.json +46 -0
- data/.devcontainer/docker-compose.yml +28 -0
- data/.devcontainer/post-create.sh +51 -0
- data/.devcontainer/post-start.sh +44 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +0 -0
- data/LICENSE.txt +21 -0
- data/MIGRATION.md +274 -0
- data/README.md +370 -0
- data/Rakefile +10 -0
- data/config/secapi.yml.example +57 -0
- data/docs/development-guide.md +291 -0
- data/docs/enumerator_pattern_design.md +483 -0
- data/docs/examples/README.md +58 -0
- data/docs/examples/backfill_filings.rb +419 -0
- data/docs/examples/instrumentation.rb +583 -0
- data/docs/examples/query_builder.rb +308 -0
- data/docs/examples/streaming_notifications.rb +491 -0
- data/docs/index.md +244 -0
- data/docs/migration-guide-v1.md +1091 -0
- data/docs/pre-review-checklist.md +145 -0
- data/docs/project-overview.md +90 -0
- data/docs/project-scan-report.json +60 -0
- data/docs/source-tree-analysis.md +190 -0
- data/lib/sec_api/callback_helper.rb +49 -0
- data/lib/sec_api/client.rb +606 -0
- data/lib/sec_api/collections/filings.rb +267 -0
- data/lib/sec_api/collections/fulltext_results.rb +86 -0
- data/lib/sec_api/config.rb +590 -0
- data/lib/sec_api/deep_freezable.rb +42 -0
- data/lib/sec_api/errors/authentication_error.rb +24 -0
- data/lib/sec_api/errors/configuration_error.rb +5 -0
- data/lib/sec_api/errors/error.rb +75 -0
- data/lib/sec_api/errors/network_error.rb +26 -0
- data/lib/sec_api/errors/not_found_error.rb +23 -0
- data/lib/sec_api/errors/pagination_error.rb +28 -0
- data/lib/sec_api/errors/permanent_error.rb +29 -0
- data/lib/sec_api/errors/rate_limit_error.rb +57 -0
- data/lib/sec_api/errors/reconnection_error.rb +34 -0
- data/lib/sec_api/errors/server_error.rb +25 -0
- data/lib/sec_api/errors/transient_error.rb +28 -0
- data/lib/sec_api/errors/validation_error.rb +23 -0
- data/lib/sec_api/extractor.rb +122 -0
- data/lib/sec_api/filing_journey.rb +477 -0
- data/lib/sec_api/mapping.rb +125 -0
- data/lib/sec_api/metrics_collector.rb +411 -0
- data/lib/sec_api/middleware/error_handler.rb +250 -0
- data/lib/sec_api/middleware/instrumentation.rb +186 -0
- data/lib/sec_api/middleware/rate_limiter.rb +541 -0
- data/lib/sec_api/objects/data_file.rb +34 -0
- data/lib/sec_api/objects/document_format_file.rb +45 -0
- data/lib/sec_api/objects/entity.rb +92 -0
- data/lib/sec_api/objects/extracted_data.rb +118 -0
- data/lib/sec_api/objects/fact.rb +147 -0
- data/lib/sec_api/objects/filing.rb +197 -0
- data/lib/sec_api/objects/fulltext_result.rb +66 -0
- data/lib/sec_api/objects/period.rb +96 -0
- data/lib/sec_api/objects/stream_filing.rb +194 -0
- data/lib/sec_api/objects/xbrl_data.rb +356 -0
- data/lib/sec_api/query.rb +423 -0
- data/lib/sec_api/rate_limit_state.rb +130 -0
- data/lib/sec_api/rate_limit_tracker.rb +154 -0
- data/lib/sec_api/stream.rb +841 -0
- data/lib/sec_api/structured_logger.rb +199 -0
- data/lib/sec_api/types.rb +32 -0
- data/lib/sec_api/version.rb +42 -0
- data/lib/sec_api/xbrl.rb +220 -0
- data/lib/sec_api.rb +137 -0
- data/sig/sec_api.rbs +4 -0
- metadata +217 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Query Builder DSL
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates:
|
|
7
|
+
# - Querying filings by ticker symbol(s)
|
|
8
|
+
# - Querying by CIK number
|
|
9
|
+
# - Filtering by form type (domestic and international)
|
|
10
|
+
# - Filtering by date range
|
|
11
|
+
# - Full-text search
|
|
12
|
+
# - Combining multiple filters
|
|
13
|
+
# - Limiting results
|
|
14
|
+
# - Manual pagination with fetch_next_page
|
|
15
|
+
#
|
|
16
|
+
# Prerequisites:
|
|
17
|
+
# - gem install sec_api
|
|
18
|
+
# - Set SECAPI_API_KEY environment variable
|
|
19
|
+
#
|
|
20
|
+
# Usage:
|
|
21
|
+
# ruby docs/examples/query_builder.rb
|
|
22
|
+
|
|
23
|
+
require "sec_api"
|
|
24
|
+
|
|
25
|
+
# Initialize client with API key from environment
|
|
26
|
+
client = SecApi::Client.new(
|
|
27
|
+
api_key: ENV.fetch("SECAPI_API_KEY")
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# =============================================================================
|
|
31
|
+
# SECTION 1: Basic Ticker Queries
|
|
32
|
+
# =============================================================================
|
|
33
|
+
|
|
34
|
+
puts "=" * 60
|
|
35
|
+
puts "SECTION 1: Basic Ticker Queries"
|
|
36
|
+
puts "=" * 60
|
|
37
|
+
|
|
38
|
+
# Single ticker query - returns most recent filings for Apple
|
|
39
|
+
filings = client.query.ticker("AAPL").search
|
|
40
|
+
puts "\nApple filings: #{filings.count} total"
|
|
41
|
+
puts "First filing: #{filings.first.form_type} filed on #{filings.first.filed_at}" if filings.any?
|
|
42
|
+
|
|
43
|
+
# Multiple tickers - query filings for multiple companies at once
|
|
44
|
+
filings = client.query.ticker("AAPL", "TSLA", "GOOGL").search
|
|
45
|
+
puts "\nMultiple tickers: #{filings.count} total filings"
|
|
46
|
+
|
|
47
|
+
# Tickers can also be passed as an array
|
|
48
|
+
tickers = %w[MSFT AMZN META]
|
|
49
|
+
filings = client.query.ticker(*tickers).search
|
|
50
|
+
puts "Tech companies: #{filings.count} total filings"
|
|
51
|
+
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# SECTION 2: CIK Queries
|
|
54
|
+
# =============================================================================
|
|
55
|
+
|
|
56
|
+
puts "\n" + "=" * 60
|
|
57
|
+
puts "SECTION 2: CIK Queries"
|
|
58
|
+
puts "=" * 60
|
|
59
|
+
|
|
60
|
+
# CIK with leading zeros (automatically normalized)
|
|
61
|
+
# Apple's CIK is 0000320193
|
|
62
|
+
filings = client.query.cik("0000320193").search
|
|
63
|
+
puts "\nApple by CIK (with zeros): #{filings.count} filings"
|
|
64
|
+
|
|
65
|
+
# CIK without leading zeros (also works)
|
|
66
|
+
filings = client.query.cik("320193").search
|
|
67
|
+
puts "Apple by CIK (without zeros): #{filings.count} filings"
|
|
68
|
+
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# SECTION 3: Form Type Filtering
|
|
71
|
+
# =============================================================================
|
|
72
|
+
|
|
73
|
+
puts "\n" + "=" * 60
|
|
74
|
+
puts "SECTION 3: Form Type Filtering"
|
|
75
|
+
puts "=" * 60
|
|
76
|
+
|
|
77
|
+
# Single form type - Annual reports only
|
|
78
|
+
filings = client.query.ticker("AAPL").form_type("10-K").search
|
|
79
|
+
puts "\nApple 10-K filings: #{filings.count}"
|
|
80
|
+
|
|
81
|
+
# Multiple form types - Annual and quarterly reports
|
|
82
|
+
filings = client.query.ticker("AAPL").form_type("10-K", "10-Q").search
|
|
83
|
+
puts "Apple 10-K and 10-Q filings: #{filings.count}"
|
|
84
|
+
|
|
85
|
+
# Common SEC forms
|
|
86
|
+
# - 10-K: Annual report
|
|
87
|
+
# - 10-Q: Quarterly report
|
|
88
|
+
# - 8-K: Current report (material events)
|
|
89
|
+
# - 4: Insider trading
|
|
90
|
+
# - 13F: Institutional holdings
|
|
91
|
+
# - DEF 14A: Proxy statement
|
|
92
|
+
|
|
93
|
+
# Material events (8-K filings)
|
|
94
|
+
filings = client.query.ticker("TSLA").form_type("8-K").search
|
|
95
|
+
puts "Tesla 8-K filings: #{filings.count}"
|
|
96
|
+
|
|
97
|
+
# International form types - Foreign private issuers
|
|
98
|
+
# - 20-F: Foreign annual report (like 10-K for non-US companies)
|
|
99
|
+
# - 40-F: Canadian annual report (MJDS program)
|
|
100
|
+
# - 6-K: Foreign current report (like 8-K for non-US companies)
|
|
101
|
+
|
|
102
|
+
# Example: Nomura Holdings (Japanese company)
|
|
103
|
+
filings = client.query.ticker("NMR").form_type("20-F").search
|
|
104
|
+
puts "Nomura 20-F filings: #{filings.count}"
|
|
105
|
+
|
|
106
|
+
# Example: Barrick Gold (Canadian company)
|
|
107
|
+
filings = client.query.ticker("ABX").form_type("40-F").search
|
|
108
|
+
puts "Barrick 40-F filings: #{filings.count}"
|
|
109
|
+
|
|
110
|
+
# Mix domestic and international annual reports
|
|
111
|
+
filings = client.query.form_type("10-K", "20-F", "40-F").limit(100).search
|
|
112
|
+
puts "All annual reports (10-K, 20-F, 40-F): #{filings.count}"
|
|
113
|
+
|
|
114
|
+
# =============================================================================
|
|
115
|
+
# SECTION 4: Date Range Filtering
|
|
116
|
+
# =============================================================================
|
|
117
|
+
|
|
118
|
+
puts "\n" + "=" * 60
|
|
119
|
+
puts "SECTION 4: Date Range Filtering"
|
|
120
|
+
puts "=" * 60
|
|
121
|
+
|
|
122
|
+
# Using string dates (ISO 8601 format: YYYY-MM-DD)
|
|
123
|
+
filings = client.query
|
|
124
|
+
.ticker("AAPL")
|
|
125
|
+
.date_range(from: "2023-01-01", to: "2023-12-31")
|
|
126
|
+
.search
|
|
127
|
+
puts "\nApple 2023 filings: #{filings.count}"
|
|
128
|
+
|
|
129
|
+
# Using Date objects
|
|
130
|
+
require "date"
|
|
131
|
+
filings = client.query
|
|
132
|
+
.ticker("AAPL")
|
|
133
|
+
.date_range(from: Date.new(2022, 1, 1), to: Date.new(2022, 12, 31))
|
|
134
|
+
.search
|
|
135
|
+
puts "Apple 2022 filings: #{filings.count}"
|
|
136
|
+
|
|
137
|
+
# Using Time objects (time portion is ignored, uses date only)
|
|
138
|
+
filings = client.query
|
|
139
|
+
.ticker("AAPL")
|
|
140
|
+
.date_range(from: Time.now - (365 * 24 * 60 * 60), to: Time.now)
|
|
141
|
+
.search
|
|
142
|
+
puts "Apple filings in last year: #{filings.count}"
|
|
143
|
+
|
|
144
|
+
# Using Date.today for dynamic queries
|
|
145
|
+
filings = client.query
|
|
146
|
+
.ticker("AAPL")
|
|
147
|
+
.form_type("10-Q")
|
|
148
|
+
.date_range(from: Date.today - 365, to: Date.today)
|
|
149
|
+
.search
|
|
150
|
+
puts "Apple quarterly reports in last year: #{filings.count}"
|
|
151
|
+
|
|
152
|
+
# =============================================================================
|
|
153
|
+
# SECTION 5: Full-Text Search
|
|
154
|
+
# =============================================================================
|
|
155
|
+
|
|
156
|
+
puts "\n" + "=" * 60
|
|
157
|
+
puts "SECTION 5: Full-Text Search"
|
|
158
|
+
puts "=" * 60
|
|
159
|
+
|
|
160
|
+
# Search for specific keywords in filing content
|
|
161
|
+
filings = client.query.search_text("merger acquisition").search
|
|
162
|
+
puts "\nFilings mentioning 'merger acquisition': #{filings.count}"
|
|
163
|
+
|
|
164
|
+
# Combined with ticker filter
|
|
165
|
+
filings = client.query
|
|
166
|
+
.ticker("AAPL")
|
|
167
|
+
.form_type("8-K")
|
|
168
|
+
.search_text("acquisition")
|
|
169
|
+
.search
|
|
170
|
+
puts "Apple 8-K filings mentioning 'acquisition': #{filings.count}"
|
|
171
|
+
|
|
172
|
+
# Search for specific company mentions across all filings
|
|
173
|
+
filings = client.query
|
|
174
|
+
.search_text("artificial intelligence")
|
|
175
|
+
.form_type("10-K")
|
|
176
|
+
.limit(50)
|
|
177
|
+
.search
|
|
178
|
+
puts "10-K filings mentioning 'artificial intelligence': #{filings.count}"
|
|
179
|
+
|
|
180
|
+
# =============================================================================
|
|
181
|
+
# SECTION 6: Combining Multiple Filters
|
|
182
|
+
# =============================================================================
|
|
183
|
+
|
|
184
|
+
puts "\n" + "=" * 60
|
|
185
|
+
puts "SECTION 6: Combining Multiple Filters"
|
|
186
|
+
puts "=" * 60
|
|
187
|
+
|
|
188
|
+
# Complex query: Apple's annual reports from 2020-2023
|
|
189
|
+
filings = client.query
|
|
190
|
+
.ticker("AAPL")
|
|
191
|
+
.form_type("10-K")
|
|
192
|
+
.date_range(from: "2020-01-01", to: "2023-12-31")
|
|
193
|
+
.search
|
|
194
|
+
puts "\nApple 10-K filings 2020-2023: #{filings.count}"
|
|
195
|
+
|
|
196
|
+
# All major tech companies, annual reports, recent years
|
|
197
|
+
filings = client.query
|
|
198
|
+
.ticker("AAPL", "MSFT", "GOOGL", "AMZN", "META")
|
|
199
|
+
.form_type("10-K", "10-Q")
|
|
200
|
+
.date_range(from: "2022-01-01", to: Date.today.to_s)
|
|
201
|
+
.search
|
|
202
|
+
puts "Big Tech quarterly/annual reports since 2022: #{filings.count}"
|
|
203
|
+
|
|
204
|
+
# International + domestic, with search, limited results
|
|
205
|
+
filings = client.query
|
|
206
|
+
.form_type("10-K", "20-F")
|
|
207
|
+
.search_text("climate risk")
|
|
208
|
+
.date_range(from: "2023-01-01", to: "2023-12-31")
|
|
209
|
+
.limit(100)
|
|
210
|
+
.search
|
|
211
|
+
puts "Annual reports mentioning 'climate risk' in 2023: #{filings.count}"
|
|
212
|
+
|
|
213
|
+
# =============================================================================
|
|
214
|
+
# SECTION 7: Limiting Results
|
|
215
|
+
# =============================================================================
|
|
216
|
+
|
|
217
|
+
puts "\n" + "=" * 60
|
|
218
|
+
puts "SECTION 7: Limiting Results"
|
|
219
|
+
puts "=" * 60
|
|
220
|
+
|
|
221
|
+
# Default is 50 results per page
|
|
222
|
+
filings = client.query.ticker("AAPL").search
|
|
223
|
+
puts "\nDefault limit (50): #{filings.to_a.size} filings returned"
|
|
224
|
+
|
|
225
|
+
# Custom limit - get exactly what you need
|
|
226
|
+
filings = client.query.ticker("AAPL").limit(10).search
|
|
227
|
+
puts "Limited to 10: #{filings.to_a.size} filings returned"
|
|
228
|
+
|
|
229
|
+
# Limit 1 - get just the most recent filing
|
|
230
|
+
filing = client.query
|
|
231
|
+
.ticker("TSLA")
|
|
232
|
+
.form_type("10-K")
|
|
233
|
+
.limit(1)
|
|
234
|
+
.search
|
|
235
|
+
.first
|
|
236
|
+
puts "Tesla's latest 10-K: #{filing.filed_at}" if filing
|
|
237
|
+
|
|
238
|
+
# =============================================================================
|
|
239
|
+
# SECTION 8: Manual Pagination with fetch_next_page
|
|
240
|
+
# =============================================================================
|
|
241
|
+
|
|
242
|
+
puts "\n" + "=" * 60
|
|
243
|
+
puts "SECTION 8: Manual Pagination"
|
|
244
|
+
puts "=" * 60
|
|
245
|
+
|
|
246
|
+
# First page of results
|
|
247
|
+
filings = client.query
|
|
248
|
+
.ticker("AAPL")
|
|
249
|
+
.form_type("10-K", "10-Q", "8-K")
|
|
250
|
+
.limit(10)
|
|
251
|
+
.search
|
|
252
|
+
|
|
253
|
+
puts "\nPage 1: #{filings.to_a.size} filings (total: #{filings.count})"
|
|
254
|
+
puts "Has more pages? #{filings.has_more?}"
|
|
255
|
+
|
|
256
|
+
# Fetch subsequent pages manually
|
|
257
|
+
page_number = 1
|
|
258
|
+
while filings.has_more? && page_number < 3 # Limit to 3 pages for demo
|
|
259
|
+
filings = filings.fetch_next_page
|
|
260
|
+
page_number += 1
|
|
261
|
+
puts "Page #{page_number}: #{filings.to_a.size} filings"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# =============================================================================
|
|
265
|
+
# SECTION 9: Working with Filing Objects
|
|
266
|
+
# =============================================================================
|
|
267
|
+
|
|
268
|
+
puts "\n" + "=" * 60
|
|
269
|
+
puts "SECTION 9: Working with Filing Objects"
|
|
270
|
+
puts "=" * 60
|
|
271
|
+
|
|
272
|
+
# Get a filing and explore its attributes
|
|
273
|
+
filing = client.query
|
|
274
|
+
.ticker("AAPL")
|
|
275
|
+
.form_type("10-K")
|
|
276
|
+
.limit(1)
|
|
277
|
+
.search
|
|
278
|
+
.first
|
|
279
|
+
|
|
280
|
+
if filing
|
|
281
|
+
puts "\nFiling Details:"
|
|
282
|
+
puts " Ticker: #{filing.ticker}"
|
|
283
|
+
puts " Company: #{filing.company_name}"
|
|
284
|
+
puts " CIK: #{filing.cik}"
|
|
285
|
+
puts " Form Type: #{filing.form_type}"
|
|
286
|
+
puts " Filed At: #{filing.filed_at}"
|
|
287
|
+
puts " Accession Number: #{filing.accession_number}"
|
|
288
|
+
puts " Link to SEC: #{filing.filing_details_url}"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Use Enumerable methods on the collection
|
|
292
|
+
filings = client.query.ticker("AAPL").limit(20).search
|
|
293
|
+
|
|
294
|
+
# Filter to specific form types
|
|
295
|
+
ten_ks = filings.select { |f| f.form_type == "10-K" }
|
|
296
|
+
puts "\nFiltered to 10-K: #{ten_ks.size} filings"
|
|
297
|
+
|
|
298
|
+
# Map to get just tickers
|
|
299
|
+
form_types = filings.map(&:form_type).uniq
|
|
300
|
+
puts "Unique form types: #{form_types.join(", ")}"
|
|
301
|
+
|
|
302
|
+
# Find first matching a condition
|
|
303
|
+
first_8k = filings.find { |f| f.form_type == "8-K" }
|
|
304
|
+
puts "First 8-K: #{first_8k.filed_at}" if first_8k
|
|
305
|
+
|
|
306
|
+
puts "\n" + "=" * 60
|
|
307
|
+
puts "Examples completed successfully!"
|
|
308
|
+
puts "=" * 60
|