thirteen_f 0.4.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85b773091adc251303e4acb8b07239b42a2c99984a94d7c541f838c37eaacb79
4
- data.tar.gz: aea7834cfc3c0c0194cbd8eedcc9ae6ba6f564d60f4360a8e29bea4cd2f73d26
3
+ metadata.gz: 89b2921abd3ae773a2d34c3ddb1551eea71708ecaa4218608a520e84a5e3120d
4
+ data.tar.gz: abc9e19a98b164ce1b72a6da225639209b09cef3837ae2ce33f4f9c9c8b96290
5
5
  SHA512:
6
- metadata.gz: ea5c9d380b2606b32c2efd9d3a3ed6cc5b452e03e7f6e74dd5075132d594326bd83ba6ca672cb393172446ecea23d8b3b6b1128106c6b99b36119d9a4ce154c8
7
- data.tar.gz: 7da64251c953225cbb0c32b32c1d87496c848bdf896ad3ddbe67579c7f21d419502d29548da4e22231357478c2660805e66ec6dc92445e1ba3c9355c6bcffde3
6
+ metadata.gz: 8fa4cd7362ab9fb3e68b4e1b1657d9de45e763e5d75eb76327391a84c87904d86dfdb65cfa51b9424fdf1215b0e360058e50cbeb738308048f9f0a8304b0c79a
7
+ data.tar.gz: ae1ed7ffca8fb036fbff8cd7419e6f9ef656063587322e23936c682ebdbe324375203c3b9a1d87ee88c9bb83cfedc7af88ad5191da303d6d773d61b500261653
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ Gemfile.lock
15
15
  *.swp
16
16
  *.swo
17
17
 
18
+ .DS_Store
data/Gemfile.lock CHANGED
@@ -1,44 +1,42 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thirteen_f (0.4.0)
5
- http (>= 5)
6
- nokogiri
7
- pdf-reader
4
+ thirteen_f (0.5.1)
5
+ http (>= 5.0)
6
+ nokogiri (>= 1.10)
7
+ pdf-reader (>= 2.2)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
12
  Ascii85 (1.1.0)
13
- addressable (2.7.0)
13
+ addressable (2.8.0)
14
14
  public_suffix (>= 2.0.2, < 5.0)
15
15
  afm (0.2.2)
16
16
  coderay (1.1.3)
17
17
  domain_name (0.5.20190701)
18
18
  unf (>= 0.0.5, < 1.0.0)
19
- ffi (1.15.0)
19
+ ffi (1.15.3)
20
20
  ffi-compiler (1.0.1)
21
21
  ffi (>= 1.0.0)
22
22
  rake
23
23
  hashery (2.1.2)
24
- http (5.0.0)
24
+ http (5.0.1)
25
25
  addressable (~> 2.3)
26
26
  http-cookie (~> 1.0)
27
27
  http-form_data (~> 2.2)
28
- llhttp-ffi (~> 0.0.1)
29
- http-cookie (1.0.3)
28
+ llhttp-ffi (~> 0.3.0)
29
+ http-cookie (1.0.4)
30
30
  domain_name (~> 0.5)
31
31
  http-form_data (2.3.0)
32
- llhttp-ffi (0.0.1)
32
+ llhttp-ffi (0.3.1)
33
33
  ffi-compiler (~> 1.0)
34
34
  rake (~> 13.0)
35
35
  method_source (1.0.0)
36
- mini_portile2 (2.5.1)
37
36
  minitest (5.14.4)
38
- nokogiri (1.11.4)
39
- mini_portile2 (~> 2.5.0)
37
+ nokogiri (1.11.7-x86_64-darwin)
40
38
  racc (~> 1.4)
41
- pdf-reader (2.4.2)
39
+ pdf-reader (2.5.0)
42
40
  Ascii85 (~> 1.0)
43
41
  afm (~> 0.2.1)
44
42
  hashery (~> 2.0)
@@ -65,4 +63,4 @@ DEPENDENCIES
65
63
  thirteen_f!
66
64
 
67
65
  BUNDLED WITH
68
- 2.1.4
66
+ 2.2.15
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.get_companies
43
- search.companies
42
+ search.get_results
43
+
44
+ result = search.results.first
45
+ result.get_entity
46
+ entity = result.entity
44
47
  ```
45
48
 
46
- ### Companies
49
+ ### Entities
47
50
 
48
51
  ```ruby
49
52
  cik_number = '0001061768'
50
- company = ThirteenF::Company.from_cik cik_number
51
-
52
- company = search.companies.first
53
- company.get_filings # grabs 10 13F filings by default which is the minimum
54
- company.get_filings(count: 20) # can supply an optional count keyword arg to get more filings
55
- company.most_recent_filing
56
- company.get_most_recent_holdings
57
- company.most_recent_holdings # returns positions from the most recent 13F filing
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
- company.get_filings
68
- filing = company.filings.first
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 company at 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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'http'
4
3
  require 'date'
5
4
  require 'open-uri'
6
5
  require 'pdf-reader'
@@ -12,9 +11,7 @@ class ThirteenF
12
11
 
13
12
  def self.all_file_locations
14
13
  index_url = "#{BASE_URL}/divisions/investment/13flists.htm"
15
- response = HTTP.get index_url
16
- return false unless response.status == 200
17
- page = Nokogiri::HTML response.to_s
14
+ page = SecRequest.get index_url, response_type: :html
18
15
  a_tags = page.search('a').select do |a_tag|
19
16
  href = a_tag.attributes['href']&.value.to_s
20
17
  href.include?('13flist') && href.include?('.pdf')
@@ -24,9 +21,7 @@ class ThirteenF
24
21
 
25
22
  def self.most_recent_list
26
23
  index_url = "#{BASE_URL}/divisions/investment/13flists.htm"
27
- response = HTTP.get index_url
28
- return false unless response.status == 200
29
- page = Nokogiri::HTML response.to_s
24
+ page = SecRequest.get index_url, response_type: :html
30
25
  a_tag = page.search('a').find { |a| a.text.include?('Current List') }
31
26
  file_location = "#{BASE_URL + a_tag.attributes['href'].value}"
32
27
  new file_location
@@ -0,0 +1,88 @@
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
+ private
49
+ def cik_from_id(id)
50
+ id.prepend('0') until id.length >= 10
51
+ id
52
+ end
53
+
54
+ COLUMN_KEYS = %i(
55
+ accessionNumber
56
+ filingDate
57
+ reportDate
58
+ acceptanceDateTime
59
+ act
60
+ form
61
+ fileNumber
62
+ filmNumber
63
+ items
64
+ size
65
+ isXBRL
66
+ isInlineXBRL
67
+ primaryDocument
68
+ primaryDocDescription
69
+ )
70
+
71
+ def thirteen_f_filing_data(filings_data)
72
+ indexes = thirteen_f_indexes(filings_data)
73
+ indexes.map do |index|
74
+ columnar_data = COLUMN_KEYS.map do |key|
75
+ filings_data[key][index]
76
+ end
77
+ Filing.new self, columnar_data
78
+ end
79
+ end
80
+
81
+ def thirteen_f_indexes(filings_data)
82
+ filings_data[:form].each_with_index.map do |form, i|
83
+ form.start_with?('13F') ? i : nil
84
+ end.compact
85
+ end
86
+ end
87
+ end
88
+
@@ -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, :table_html_url, :table_xml_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
- :period_of_report, :time_accepted, :response_status, :company, :positions
10
+ :positions
11
11
 
12
- BASE_URL = 'https://www.sec.gov'
12
+ alias period_of_report report_date
13
13
 
14
- def self.from_index_urls(urls, company)
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 reset_attributes_from_index_url
33
- return unless index_url
34
- response = HTTP.get index_url
35
- sleep 0.33
36
- if response.status == 200
37
- attributes = self.class.set_attributes(response, index_url)
38
- assign_attributes(**attributes)
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 initialize(response_status:, index_url:, company:,
46
- complete_text_file_url:, period_of_report:, time_accepted:,
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
- return false unless period_of_report
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 self.set_attributes(response, index_url, company)
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 self.get_period_of_report(page)
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 self.bad_response_attributes(response, index_url, company)
110
- attributes = Hash.new
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
@@ -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
- response = HTTP.get table_xml_url
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)
@@ -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 :companies, :search_params
5
+ attr_reader :results, :search_params
9
6
 
10
- BASE_URL = 'https://www.sec.gov'
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, count: 100)
14
- @search_params = [SEARCH_URL, params: {
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 get_companies
21
- response = HTTP.get(*search_params)
22
- if response.status == 200
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
- page = Nokogiri::HTML response.to_s
33
- company_rows = page.search('table.tableFile2 > tr')[1..-1]
34
- column_count = company_rows.first.search('td').count
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,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ThirteenF
4
+ class SearchHit
5
+ attr_reader :cik, :name, :entity, :sec_page_url
6
+
7
+ def self.from_search_hits(hits)
8
+ hits.map { |hit| new hit }
9
+ end
10
+
11
+ BASE_URL = 'https://www.sec.gov'
12
+
13
+ def initialize(sec_hit)
14
+ @cik = cik_from_id sec_hit[:_id]
15
+ @name = sec_hit[:_source][:entity]
16
+ @sec_page_url = "#{BASE_URL}/edgar/browse/?CIK=#{cik}&owner=exclude"
17
+ true
18
+ end
19
+
20
+ def get_entity
21
+ entity_url = "https://data.sec.gov/submissions/CIK#{cik}.json"
22
+ response = SecRequest.get entity_url
23
+ @entity = Entity.new response
24
+ true
25
+ end
26
+
27
+ private
28
+ def cik_from_id(id)
29
+ id.prepend('0') until id.length >= 10
30
+ id
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,61 @@
1
+ require 'http'
2
+ require 'nokogiri'
3
+
4
+ class ThirteenF
5
+ class SecRequest
6
+ DATA_HEADERS = {
7
+ 'User-Agent' => 'ThirteenF/v0.5.0 (Open Source Ruby Gem) savannah.fischer@hey.com',
8
+ 'Host' => 'data.sec.gov',
9
+ 'Accept-Encoding' => 'gzip, deflate'
10
+ }
11
+
12
+ WWW_HEADERS = {
13
+ 'User-Agent' => 'ThirteenF/v0.5.0 (Open Source Ruby Gem) savannah.fischer@hey.com',
14
+ 'Host' => 'www.sec.gov'
15
+ }
16
+
17
+ EFTS_HEADERS = {
18
+ 'User-Agent' => 'S Fischer sfischer@fischercompany.com',
19
+ 'Accept-Encoding' => 'gzip, deflate',
20
+ 'Host' => 'efts.sec.gov'
21
+ }
22
+
23
+ def self.get(url, response_type: :json)
24
+ case response_type
25
+ when :json
26
+ response = HTTP.use(:auto_inflate).headers(DATA_HEADERS).get(url)
27
+ handle_response response, response_type: response_type
28
+ else
29
+ response = HTTP.use(:auto_inflate).headers(WWW_HEADERS).get(url)
30
+ handle_response response, response_type: response_type
31
+ end
32
+ end
33
+
34
+ def self.post(url, json)
35
+ response = HTTP.use(:auto_inflate).headers(EFTS_HEADERS).post(url, json: json)
36
+ handle_response response
37
+ end
38
+
39
+ def self.handle_response(response, response_type: :json)
40
+ case response.status
41
+ when 200, 201, 202, 203, 204, 206
42
+ handle_response_type response.to_s, response_type
43
+ else
44
+ raise "Request failed with response #{response.status}, request url: #{response.uri.to_s}"
45
+ end
46
+ end
47
+
48
+ def self.handle_response_type(body, response_type)
49
+ case response_type
50
+ when :html
51
+ Nokogiri::HTML body
52
+ when :json
53
+ JSON.parse body, symbolize_names: true
54
+ when :xml
55
+ xml_doc = Nokogiri::XML body
56
+ xml_doc.remove_namespaces!
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -1,3 +1,3 @@
1
1
  class ThirteenF
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.2"
3
3
  end
data/lib/thirteen_f.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thirteen_f/company"
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"
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 = ["savfischer"]
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,10 +12,10 @@ 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
- spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
18
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0")
19
19
 
20
20
  spec.metadata["homepage_uri"] = spec.homepage
21
21
  spec.metadata["source_code_uri"] = "https://github.com/savfischer/thirteen_f"
@@ -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
- spec.add_dependency "http", ">= 5"
36
- spec.add_dependency "nokogiri"
37
- spec.add_dependency "pdf-reader"
35
+
36
+ spec.add_runtime_dependency "http", ">= 5.0"
37
+ spec.add_runtime_dependency "nokogiri", ">= 1.13"
38
+ spec.add_runtime_dependency "pdf-reader", ">= 2.10"
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.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
- - savfischer
7
+ - Savannah Fischer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-18 00:00:00.000000000 Z
11
+ date: 2022-06-12 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: '0'
61
+ version: '1.13'
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: '0'
68
+ version: '1.13'
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: '0'
75
+ version: '2.10'
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: '0'
82
+ version: '2.10'
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
@@ -126,14 +128,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
128
  requirements:
127
129
  - - ">="
128
130
  - !ruby/object:Gem::Version
129
- version: 2.6.0
131
+ version: '3.0'
130
132
  required_rubygems_version: !ruby/object:Gem::Requirement
131
133
  requirements:
132
134
  - - ">="
133
135
  - !ruby/object:Gem::Version
134
136
  version: '0'
135
137
  requirements: []
136
- rubygems_version: 3.1.4
138
+ rubygems_version: 3.3.7
137
139
  signing_key:
138
140
  specification_version: 4
139
141
  summary: A ruby interface for S.E.C. 13F Data.
@@ -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
-