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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85b773091adc251303e4acb8b07239b42a2c99984a94d7c541f838c37eaacb79
4
- data.tar.gz: aea7834cfc3c0c0194cbd8eedcc9ae6ba6f564d60f4360a8e29bea4cd2f73d26
3
+ metadata.gz: 2eebea964d2903739ae01ced769bfc61a0df3df5115d027760ff41fa348573a1
4
+ data.tar.gz: 2b0a2f7f48a25d1eed4238acdb6371333c4b85435f06880dcfdaf87087454ecd
5
5
  SHA512:
6
- metadata.gz: ea5c9d380b2606b32c2efd9d3a3ed6cc5b452e03e7f6e74dd5075132d594326bd83ba6ca672cb393172446ecea23d8b3b6b1128106c6b99b36119d9a4ce154c8
7
- data.tar.gz: 7da64251c953225cbb0c32b32c1d87496c848bdf896ad3ddbe67579c7f21d419502d29548da4e22231357478c2660805e66ec6dc92445e1ba3c9355c6bcffde3
6
+ metadata.gz: 55ad096c2c2100ef187a154f1def213a643317ec42cfa7146f69b1fccd7d336a2c1445cb1e1a01f2dfc6c7d14875a430af627dad16f8c83c972c991c72756035
7
+ data.tar.gz: 9745d3a78b459759d50933bd2ee5c167bf4b28a0fc7991aae8e8d38ec187b9a6d600a864b7f10d186a147354fde62a1306ee59ed7be92e7d3548d4c30598f28d
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ Gemfile.lock
15
15
  *.swp
16
16
  *.swo
17
17
 
18
+ .DS_Store
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
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"
@@ -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
+
@@ -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,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
+
@@ -1,3 +1,3 @@
1
1
  class ThirteenF
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
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,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
- 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.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.0
4
+ version: 0.5.0
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: 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: '0'
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: '0'
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: '0'
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: '0'
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.1.4
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.
@@ -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
-