thirteen_f 0.4.0 → 0.5.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 +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
|
-
|