thirteen_f 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +27 -20
- data/lib/thirteen_f.rb +3 -1
- data/lib/thirteen_f/entity.rb +105 -0
- data/lib/thirteen_f/filing.rb +25 -90
- data/lib/thirteen_f/position.rb +1 -5
- data/lib/thirteen_f/search.rb +10 -26
- data/lib/thirteen_f/search_hit.rb +31 -0
- data/lib/thirteen_f/sec_request.rb +44 -0
- data/lib/thirteen_f/version.rb +1 -1
- data/thirteen_f.gemspec +6 -5
- metadata +14 -12
- data/lib/thirteen_f/company.rb +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2eebea964d2903739ae01ced769bfc61a0df3df5115d027760ff41fa348573a1
|
4
|
+
data.tar.gz: 2b0a2f7f48a25d1eed4238acdb6371333c4b85435f06880dcfdaf87087454ecd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55ad096c2c2100ef187a154f1def213a643317ec42cfa7146f69b1fccd7d336a2c1445cb1e1a01f2dfc6c7d14875a430af627dad16f8c83c972c991c72756035
|
7
|
+
data.tar.gz: 9745d3a78b459759d50933bd2ee5c167bf4b28a0fc7991aae8e8d38ec187b9a6d600a864b7f10d186a147354fde62a1306ee59ed7be92e7d3548d4c30598f28d
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -39,40 +39,38 @@ Or install it yourself as:
|
|
39
39
|
|
40
40
|
```ruby
|
41
41
|
search = ThirteenF::Search.new('Berkshire Hathaway')
|
42
|
-
search.
|
43
|
-
|
42
|
+
search.get_results
|
43
|
+
|
44
|
+
result = search.results.first
|
45
|
+
result.get_entity
|
46
|
+
entity = result.entity
|
44
47
|
```
|
45
48
|
|
46
|
-
###
|
49
|
+
### Entities
|
47
50
|
|
48
51
|
```ruby
|
49
52
|
cik_number = '0001061768'
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
company.cik # type: String | ex: "0001067983"
|
60
|
-
company.name # type: String | ex: "BERKSHIRE HATHAWAY INC"
|
61
|
-
company.state_or_country # type: String | ex: "NE"
|
53
|
+
entity = ThirteenF::Entity.from_cik cik_number
|
54
|
+
|
55
|
+
entity.most_recent_filing
|
56
|
+
entity.get_most_recent_positions
|
57
|
+
entity.most_recent_positions # returns positions from the most recent 13F filing
|
58
|
+
|
59
|
+
entity.cik # type: String | ex: "0001067983"
|
60
|
+
entity.name # type: String | ex: "BERKSHIRE HATHAWAY INC"
|
61
|
+
entity.state_or_country # type: String | ex: "NE"
|
62
62
|
```
|
63
63
|
|
64
64
|
### Filings
|
65
65
|
|
66
66
|
```ruby
|
67
|
-
|
68
|
-
filing
|
69
|
-
filing.company
|
67
|
+
filing = entity.filings.first
|
68
|
+
filing.entity
|
70
69
|
filing.get_positions
|
71
|
-
filing.positions # returns the US public securities held by the
|
70
|
+
filing.positions # returns the US public securities held by the entity at the
|
72
71
|
# time of the period of the report
|
73
72
|
|
74
73
|
filing.index_url # type: String
|
75
|
-
filing.response_status # type: String | ex: "200 OK"
|
76
74
|
filing.period_of_report # type: Date or nil
|
77
75
|
filing.time_accepted # type: DateTime or nil
|
78
76
|
filing.table_html_url # type: String or nil
|
@@ -101,6 +99,15 @@ position.other_managers # type: String or nil | ex: "1,4,11"
|
|
101
99
|
position.voting_authority # type: Hash | ex: { sole: 19994970, shared: 0, none: 0 }
|
102
100
|
```
|
103
101
|
|
102
|
+
### Net Securities
|
103
|
+
```ruby
|
104
|
+
```
|
105
|
+
|
106
|
+
### CUSIP Securities
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
```
|
110
|
+
|
104
111
|
## Development
|
105
112
|
|
106
113
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
data/lib/thirteen_f.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'thirteen_f/sec_request'
|
4
|
+
require "thirteen_f/entity"
|
4
5
|
require "thirteen_f/search"
|
6
|
+
require "thirteen_f/search_hit"
|
5
7
|
require "thirteen_f/filing"
|
6
8
|
require "thirteen_f/position"
|
7
9
|
require "thirteen_f/net_position"
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thirteen_f/sec_request'
|
4
|
+
|
5
|
+
class ThirteenF
|
6
|
+
class Entity
|
7
|
+
attr_reader :cik, :name, :tickers, :exchanges,
|
8
|
+
:sic, :sic_description,
|
9
|
+
:fiscal_year_end,
|
10
|
+
:business_state_or_country,
|
11
|
+
:filings, :most_recent_positions
|
12
|
+
|
13
|
+
BASE_URL = 'https://www.sec.gov'
|
14
|
+
|
15
|
+
def self.from_cik(cik)
|
16
|
+
entity_url = "https://data.sec.gov/submissions/CIK#{cik}.json"
|
17
|
+
new SecRequest.get entity_url
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(sec_entity)
|
21
|
+
@name = sec_entity[:name]
|
22
|
+
@cik = cik_from_id sec_entity[:cik]
|
23
|
+
@tickers = sec_entity[:tickers]
|
24
|
+
@exchanges = sec_entity[:exchanges]
|
25
|
+
@sic = sec_entity[:sic]
|
26
|
+
@sic_description = sec_entity[:sicDescription]
|
27
|
+
@fiscal_year_end = sec_entity[:fiscalYearEnd]
|
28
|
+
@business_state_or_country = sec_entity[:addresses][:business][:stateOrCountry]
|
29
|
+
@filings = thirteen_f_filing_data sec_entity[:filings][:recent]
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def human_fiscal_year_end
|
35
|
+
Time.strptime(@fiscal_year_end, '%m%d').strftime('%B %d')
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_most_recent_positions
|
39
|
+
most_recent_filing.get_positions
|
40
|
+
@most_recent_positions = most_recent_filing.positions
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def most_recent_filing
|
45
|
+
filings.select(&:period_of_report).max_by(&:period_of_report)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sec_filings_page_url
|
49
|
+
"#{BASE_URL}/cgi-bin/browse-edgar?CIK=#{cik}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def thirteen_f_filings_url(count: 10)
|
53
|
+
"#{sec_filings_page_url}&type=13f&count=#{count}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.sec_url_from_cik(cik)
|
57
|
+
"#{BASE_URL}/cgi-bin/browse-edgar?CIK=#{cik}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def thirteen_f_urls(count: 10)
|
61
|
+
response = HTTP.get thirteen_f_filings_url(count: count)
|
62
|
+
page = Nokogiri::HTML response.to_s
|
63
|
+
page.search('#documentsbutton').map do |btn|
|
64
|
+
next nil unless btn.parent.previous.previous.text.include?('13F-HR')
|
65
|
+
"#{BASE_URL + btn.attributes['href'].value}"
|
66
|
+
end.compact
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def cik_from_id(id)
|
71
|
+
id.prepend('0') until id.length >= 10
|
72
|
+
id
|
73
|
+
end
|
74
|
+
|
75
|
+
def thirteen_f_filing_data(filings_data)
|
76
|
+
indexes = thirteen_f_indexes(filings_data)
|
77
|
+
indexes.map do |index|
|
78
|
+
columnar_data = [
|
79
|
+
filings_data[:accessionNumber][index],
|
80
|
+
filings_data[:filingDate][index],
|
81
|
+
filings_data[:reportDate][index],
|
82
|
+
filings_data[:acceptanceDateTime][index],
|
83
|
+
filings_data[:act][index],
|
84
|
+
filings_data[:form][index],
|
85
|
+
filings_data[:fileNumber][index],
|
86
|
+
filings_data[:filmNumber][index],
|
87
|
+
filings_data[:items][index],
|
88
|
+
filings_data[:size][index],
|
89
|
+
filings_data[:isXBRL][index],
|
90
|
+
filings_data[:isInlineXBRL][index],
|
91
|
+
filings_data[:primaryDocument][index],
|
92
|
+
filings_data[:primaryDocDescription][index]
|
93
|
+
]
|
94
|
+
Filing.new self, columnar_data
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def thirteen_f_indexes(filings_data)
|
99
|
+
filings_data[:form].each_with_index.map do |form, i|
|
100
|
+
form.start_with?('13F') ? i : nil
|
101
|
+
end.compact
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
data/lib/thirteen_f/filing.rb
CHANGED
@@ -1,80 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'http'
|
4
3
|
require 'date'
|
5
4
|
|
6
5
|
class ThirteenF
|
7
6
|
class Filing
|
8
|
-
attr_reader :index_url, :
|
7
|
+
attr_reader :entity, :index_url, :report_date, :time_accepted, :form_type,
|
8
|
+
:table_html_url, :table_xml_url,
|
9
9
|
:cover_page_html_url, :cover_page_xml_url, :complete_text_file_url,
|
10
|
-
:
|
10
|
+
:positions
|
11
11
|
|
12
|
-
|
12
|
+
alias period_of_report report_date
|
13
13
|
|
14
|
-
|
15
|
-
redo_count = 0
|
16
|
-
urls.map do |index_url|
|
17
|
-
response = HTTP.get index_url
|
18
|
-
sleep 0.33
|
19
|
-
if response.status == 200
|
20
|
-
redo_count = 0
|
21
|
-
attributes = set_attributes(response, index_url, company)
|
22
|
-
new(**attributes)
|
23
|
-
else
|
24
|
-
redo_count += 1
|
25
|
-
redo unless redo_count > 1
|
26
|
-
attributes = bad_response_attributes(response, index_url, company)
|
27
|
-
new(**attributes)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
14
|
+
BASE_URL = 'https://www.sec.gov'
|
31
15
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
true
|
40
|
-
else
|
41
|
-
false
|
42
|
-
end
|
16
|
+
def initialize(entity, columnar_data)
|
17
|
+
@entity = entity
|
18
|
+
@index_url = assemble_index_url columnar_data[0]
|
19
|
+
@report_date = Date.parse columnar_data[2]
|
20
|
+
@time_accepted = DateTime.parse columnar_data[3]
|
21
|
+
@form_type = columnar_data[5]
|
22
|
+
true
|
43
23
|
end
|
44
24
|
|
45
|
-
def
|
46
|
-
|
47
|
-
table_html_url: nil, table_xml_url: nil,
|
48
|
-
cover_page_html_url: nil, cover_page_xml_url: nil)
|
49
|
-
@company = company
|
50
|
-
@response_status = response_status
|
51
|
-
@index_url = index_url
|
52
|
-
@table_html_url = table_html_url
|
53
|
-
@table_xml_url = table_xml_url
|
54
|
-
@cover_page_html_url = cover_page_html_url
|
55
|
-
@cover_page_xml_url = cover_page_xml_url
|
56
|
-
@complete_text_file_url = complete_text_file_url
|
57
|
-
@period_of_report = period_of_report
|
58
|
-
@time_accepted = time_accepted
|
59
|
-
true
|
25
|
+
def assemble_index_url(accession_number)
|
26
|
+
"#{BASE_URL}/Archives/edgar/data/#{entity.cik}/#{accession_number.delete('-')}/#{accession_number}-index.htm"
|
60
27
|
end
|
61
28
|
|
62
29
|
def get_positions
|
63
|
-
|
30
|
+
set_attributes_from_index_url unless table_xml_url
|
64
31
|
@positions = Position.from_xml_filing self
|
65
32
|
true
|
66
33
|
end
|
67
34
|
|
35
|
+
def set_attributes_from_index_url
|
36
|
+
return unless index_url
|
37
|
+
response = SecRequest.get index_url, response_type: :html
|
38
|
+
assign_attributes(**set_attributes(response))
|
39
|
+
end
|
40
|
+
|
68
41
|
private
|
69
|
-
def
|
70
|
-
page = Nokogiri::HTML response.to_s
|
42
|
+
def set_attributes(page)
|
71
43
|
table_links = page.search('table.tableFile')[0].search('a')
|
72
44
|
attributes = Hash.new
|
73
|
-
attributes[:company] = company
|
74
|
-
attributes[:response_status] = response.status.to_s
|
75
|
-
attributes[:index_url] = index_url
|
76
|
-
attributes[:period_of_report] = get_period_of_report page
|
77
|
-
attributes[:time_accepted] = get_time_accepted page
|
78
45
|
attributes[:complete_text_file_url] = "#{BASE_URL + table_links[-1].attributes['href'].value}"
|
79
46
|
if table_links.count == 5
|
80
47
|
attributes = xml_present(attributes, table_links)
|
@@ -82,23 +49,7 @@ class ThirteenF
|
|
82
49
|
attributes
|
83
50
|
end
|
84
51
|
|
85
|
-
def
|
86
|
-
period_header_div = page.search('div.infoHead').find do |div|
|
87
|
-
div.text.include?('Period of Report')
|
88
|
-
end
|
89
|
-
period_string = period_header_div.next.next.text.strip
|
90
|
-
Date.parse period_string
|
91
|
-
end
|
92
|
-
|
93
|
-
def self.get_time_accepted(page)
|
94
|
-
accepted_header_div = page.search('div.infoHead').find do |div|
|
95
|
-
div.text.include?('Accepted')
|
96
|
-
end
|
97
|
-
accepted_string = accepted_header_div.next.next.text.strip
|
98
|
-
DateTime.parse accepted_string
|
99
|
-
end
|
100
|
-
|
101
|
-
def self.xml_present(attributes, table_links)
|
52
|
+
def xml_present(attributes, table_links)
|
102
53
|
attributes[:table_html_url] = "#{BASE_URL + table_links[2].attributes['href'].value}"
|
103
54
|
attributes[:table_xml_url] = "#{BASE_URL + table_links[3].attributes['href'].value}"
|
104
55
|
attributes[:cover_page_html_url] = "#{BASE_URL + table_links[0].attributes['href'].value}"
|
@@ -106,30 +57,14 @@ class ThirteenF
|
|
106
57
|
attributes
|
107
58
|
end
|
108
59
|
|
109
|
-
def
|
110
|
-
|
111
|
-
attributes[:company] = company
|
112
|
-
attributes[:response_status] = response.status.to_s
|
113
|
-
attributes[:index_url] = index_url
|
114
|
-
attributes[:period_of_report] = nil
|
115
|
-
attributes[:time_accepted] = nil
|
116
|
-
attributes[:complete_text_file_url] = nil
|
117
|
-
attributes
|
118
|
-
end
|
119
|
-
|
120
|
-
def assign_attributes(response_status:, index_url:, company:,
|
121
|
-
complete_text_file_url:, period_of_report:,
|
122
|
-
time_accepted:, table_html_url: nil, table_xml_url:
|
123
|
-
nil, cover_page_html_url: nil,
|
60
|
+
def assign_attributes(complete_text_file_url:, table_html_url: nil,
|
61
|
+
table_xml_url: nil, cover_page_html_url: nil,
|
124
62
|
cover_page_xml_url: nil)
|
125
|
-
@response_status = response_status
|
126
63
|
@table_html_url = table_html_url
|
127
64
|
@table_xml_url = table_xml_url
|
128
65
|
@cover_page_html_url = cover_page_html_url
|
129
66
|
@cover_page_xml_url = cover_page_xml_url
|
130
67
|
@complete_text_file_url = complete_text_file_url
|
131
|
-
@period_of_report = period_of_report
|
132
|
-
@time_accepted = time_accepted
|
133
68
|
true
|
134
69
|
end
|
135
70
|
end
|
data/lib/thirteen_f/position.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'http'
|
4
|
-
|
5
3
|
class ThirteenF
|
6
4
|
class Position
|
7
5
|
attr_reader :name_of_issuer, :title_of_class, :cusip, :value_in_thousands,
|
@@ -14,9 +12,7 @@ class ThirteenF
|
|
14
12
|
end
|
15
13
|
|
16
14
|
def self.from_xml_url(table_xml_url, filing: nil)
|
17
|
-
|
18
|
-
xml_doc = Nokogiri::XML response.to_s
|
19
|
-
xml_doc.remove_namespaces!
|
15
|
+
xml_doc = SecRequest.get table_xml_url, response_type: :xml
|
20
16
|
xml_doc.search("//infoTable").map do |info_table|
|
21
17
|
position = new filing: filing
|
22
18
|
position.attributes_from_info_table(info_table)
|
data/lib/thirteen_f/search.rb
CHANGED
@@ -1,42 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'http'
|
4
|
-
require 'nokogiri'
|
5
|
-
|
6
3
|
class ThirteenF
|
7
4
|
class Search
|
8
|
-
attr_reader :
|
5
|
+
attr_reader :results, :search_params
|
9
6
|
|
10
|
-
|
11
|
-
SEARCH_URL = "#{BASE_URL}/cgi-bin/browse-edgar"
|
7
|
+
SEARCH_URL = 'https://efts.sec.gov/LATEST/search-index'
|
12
8
|
|
13
|
-
def initialize(search_string
|
14
|
-
@search_params = [SEARCH_URL,
|
15
|
-
company: search_string,
|
16
|
-
count: count
|
17
|
-
}]
|
9
|
+
def initialize(search_string)
|
10
|
+
@search_params = [SEARCH_URL, { keysTyped: search_string, narrow: true }]
|
18
11
|
end
|
19
12
|
|
20
|
-
def
|
21
|
-
response =
|
22
|
-
|
23
|
-
@companies = configure_search_results response
|
24
|
-
else
|
25
|
-
raise 'SEC results are not available right now'
|
26
|
-
end
|
13
|
+
def get_results
|
14
|
+
response = SecRequest.post(*search_params)
|
15
|
+
@results = configure_search_results response
|
27
16
|
true
|
28
17
|
end
|
29
18
|
|
30
19
|
private
|
31
20
|
def configure_search_results(response)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
if column_count == 3
|
36
|
-
company_rows.search('br').each { |n| n.replace("\n") }
|
37
|
-
Company.from_sec_search_rows company_rows
|
38
|
-
elsif column_count == 5
|
39
|
-
Company.from_company_page page
|
21
|
+
if response.dig(:hits, :hits)
|
22
|
+
SearchHit.from_search_hits response.dig(:hits, :hits)
|
23
|
+
else []
|
40
24
|
end
|
41
25
|
end
|
42
26
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ThirteenF
|
4
|
+
class SearchHit
|
5
|
+
attr_reader :cik, :name, :entity
|
6
|
+
|
7
|
+
def self.from_search_hits(hits)
|
8
|
+
hits.map { |hit| new hit }
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(sec_hit)
|
12
|
+
@cik = cik_from_id sec_hit[:_id]
|
13
|
+
@name = sec_hit[:_source][:entity]
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_entity
|
18
|
+
entity_url = "https://data.sec.gov/submissions/CIK#{cik}.json"
|
19
|
+
response = SecRequest.get entity_url
|
20
|
+
@entity = Entity.new response
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def cik_from_id(id)
|
26
|
+
id.prepend('0') until id.length >= 10
|
27
|
+
id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'http'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
class ThirteenF
|
5
|
+
class SecRequest
|
6
|
+
HEADERS = {
|
7
|
+
'User-Agent' => 'ThirteenF/v0.5.0 (Open Source Ruby Gem)',
|
8
|
+
'Accept-Encoding' => 'gzip, deflate',
|
9
|
+
'Accept' => '*/*'
|
10
|
+
}
|
11
|
+
|
12
|
+
def self.get(url, response_type: :json)
|
13
|
+
response = HTTP.use(:auto_inflate).headers(HEADERS).get(url)
|
14
|
+
handle_response response, response_type: response_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.post(url, json)
|
18
|
+
response = HTTP.use(:auto_inflate).headers(HEADERS).post(url, json: json)
|
19
|
+
handle_response response
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.handle_response(response, response_type: :json)
|
23
|
+
case response.status
|
24
|
+
when 200, 201, 202, 203, 204, 206
|
25
|
+
handle_response_type response.to_s, response_type
|
26
|
+
else
|
27
|
+
raise "Request failed with response #{response.status}, request url: #{response.uri.to_s}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.handle_response_type(body, response_type)
|
32
|
+
case response_type
|
33
|
+
when :html
|
34
|
+
Nokogiri::HTML body
|
35
|
+
when :json
|
36
|
+
JSON.parse body, symbolize_names: true
|
37
|
+
when :xml
|
38
|
+
xml_doc = Nokogiri::XML body
|
39
|
+
xml_doc.remove_namespaces!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/lib/thirteen_f/version.rb
CHANGED
data/thirteen_f.gemspec
CHANGED
@@ -3,7 +3,7 @@ require_relative 'lib/thirteen_f/version'
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "thirteen_f"
|
5
5
|
spec.version = ThirteenF::VERSION
|
6
|
-
spec.authors = ["
|
6
|
+
spec.authors = ["Savannah Fischer"]
|
7
7
|
spec.email = ["savannah.fischer@hey.com"]
|
8
8
|
|
9
9
|
spec.summary = %q{A ruby interface for S.E.C. 13F Data.}
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
filing data. The SEC is the U.S. Securities and Exchange Commission. 13F
|
13
13
|
filings are disclosures large institutional investors in public securites have
|
14
14
|
to provide and make public every quarter. It is a great way to follow what
|
15
|
-
different investors have been doing.}
|
15
|
+
different investors have been doing in US regulated equity markets.}
|
16
16
|
spec.homepage = "https://github.com/savfischer/thirteen_f"
|
17
17
|
spec.license = "MIT"
|
18
18
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
@@ -32,7 +32,8 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.add_development_dependency "minitest"
|
34
34
|
spec.add_development_dependency "pry"
|
35
|
-
|
36
|
-
spec.
|
37
|
-
spec.
|
35
|
+
|
36
|
+
spec.add_runtime_dependency "http", ">= 5.0"
|
37
|
+
spec.add_runtime_dependency "nokogiri", ">= 1.10"
|
38
|
+
spec.add_runtime_dependency "pdf-reader", ">= 2.2"
|
38
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thirteen_f
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Savannah Fischer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -44,48 +44,48 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '5'
|
47
|
+
version: '5.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '5'
|
54
|
+
version: '5.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: nokogiri
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '1.10'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '1.10'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pdf-reader
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '2.2'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '2.2'
|
83
83
|
description: |-
|
84
84
|
thirteen_f lets you easily search and retrieve SEC 13F
|
85
85
|
filing data. The SEC is the U.S. Securities and Exchange Commission. 13F
|
86
86
|
filings are disclosures large institutional investors in public securites have
|
87
87
|
to provide and make public every quarter. It is a great way to follow what
|
88
|
-
different investors have been doing.
|
88
|
+
different investors have been doing in US regulated equity markets.
|
89
89
|
email:
|
90
90
|
- savannah.fischer@hey.com
|
91
91
|
executables: []
|
@@ -103,12 +103,14 @@ files:
|
|
103
103
|
- bin/console
|
104
104
|
- bin/setup
|
105
105
|
- lib/thirteen_f.rb
|
106
|
-
- lib/thirteen_f/company.rb
|
107
106
|
- lib/thirteen_f/cusip_securities.rb
|
107
|
+
- lib/thirteen_f/entity.rb
|
108
108
|
- lib/thirteen_f/filing.rb
|
109
109
|
- lib/thirteen_f/net_position.rb
|
110
110
|
- lib/thirteen_f/position.rb
|
111
111
|
- lib/thirteen_f/search.rb
|
112
|
+
- lib/thirteen_f/search_hit.rb
|
113
|
+
- lib/thirteen_f/sec_request.rb
|
112
114
|
- lib/thirteen_f/version.rb
|
113
115
|
- thirteen_f.gemspec
|
114
116
|
homepage: https://github.com/savfischer/thirteen_f
|
@@ -133,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
135
|
- !ruby/object:Gem::Version
|
134
136
|
version: '0'
|
135
137
|
requirements: []
|
136
|
-
rubygems_version: 3.
|
138
|
+
rubygems_version: 3.2.15
|
137
139
|
signing_key:
|
138
140
|
specification_version: 4
|
139
141
|
summary: A ruby interface for S.E.C. 13F Data.
|
data/lib/thirteen_f/company.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'http'
|
4
|
-
|
5
|
-
class ThirteenF
|
6
|
-
class Company
|
7
|
-
attr_reader :cik, :name, :state_or_country, :filings, :most_recent_holdings
|
8
|
-
|
9
|
-
BASE_URL = 'https://www.sec.gov'
|
10
|
-
|
11
|
-
def self.from_sec_search_rows(rows)
|
12
|
-
rows.map do |row|
|
13
|
-
raise "Bad row" unless row.search('td').count == 3
|
14
|
-
cells = row.search('td')
|
15
|
-
cik = cells.first.text
|
16
|
-
name = parse_name cells[1]
|
17
|
-
state_or_country = cells.last.text
|
18
|
-
new cik, name, state_or_country
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.from_company_page(page)
|
23
|
-
arr = page.search('.companyName').text.split('CIK#:')
|
24
|
-
name = arr.first.strip
|
25
|
-
cik = arr.last.strip.split(' ').first
|
26
|
-
state_or_country = page.search('.identInfo a').first.text
|
27
|
-
Array.new 1, new(cik, name, state_or_country)
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.from_cik(cik)
|
31
|
-
response = HTTP.get sec_url_from_cik(cik)
|
32
|
-
return false unless response.status == 200
|
33
|
-
page = Nokogiri::HTML response.to_s
|
34
|
-
from_company_page(page).first
|
35
|
-
end
|
36
|
-
|
37
|
-
def initialize(cik, name, state_or_country)
|
38
|
-
@cik = cik
|
39
|
-
@name = name
|
40
|
-
@state_or_country = state_or_country
|
41
|
-
true
|
42
|
-
end
|
43
|
-
|
44
|
-
def get_most_recent_holdings
|
45
|
-
get_filings unless filings
|
46
|
-
most_recent_filing.get_positions
|
47
|
-
@most_recent_holdings = most_recent_filing.positions
|
48
|
-
true
|
49
|
-
end
|
50
|
-
|
51
|
-
def most_recent_filing
|
52
|
-
filings.select(&:period_of_report).max_by(&:period_of_report)
|
53
|
-
end
|
54
|
-
|
55
|
-
def get_filings(count: 10)
|
56
|
-
@filings = Filing.from_index_urls thirteen_f_urls(count: count), self
|
57
|
-
true
|
58
|
-
end
|
59
|
-
|
60
|
-
def sec_filings_page_url
|
61
|
-
"#{BASE_URL}/cgi-bin/browse-edgar?CIK=#{cik}"
|
62
|
-
end
|
63
|
-
|
64
|
-
def thirteen_f_filings_url(count: 10)
|
65
|
-
"#{sec_filings_page_url}&type=13f&count=#{count}"
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.sec_url_from_cik(cik)
|
69
|
-
"#{BASE_URL}/cgi-bin/browse-edgar?CIK=#{cik}"
|
70
|
-
end
|
71
|
-
|
72
|
-
def thirteen_f_urls(count: 10)
|
73
|
-
response = HTTP.get thirteen_f_filings_url(count: count)
|
74
|
-
page = Nokogiri::HTML response.to_s
|
75
|
-
page.search('#documentsbutton').map do |btn|
|
76
|
-
next nil unless btn.parent.previous.previous.text.include?('13F-HR')
|
77
|
-
"#{BASE_URL + btn.attributes['href'].value}"
|
78
|
-
end.compact
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
def self.parse_name(name_cell)
|
83
|
-
name_cell.text.split("\n").first
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|