thirteen_f 0.1.2 → 0.2.1

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: c49261f579858881f09bf7f929b5e26ab270a3212f04d873ced78c8ff2cca5a7
4
- data.tar.gz: 7357540f69f1cd5e3dd754463d362e1f8efccebf23d2a1add14d9c8a26226049
3
+ metadata.gz: 97448bdbcdaea2e3d0e690584c24503c7d9e65c0140441ae8afa037b6a0d96b0
4
+ data.tar.gz: 4aaf17e842ee4b83693d1977fc3a36036adbdc4a65a2208b44191b0dc4237373
5
5
  SHA512:
6
- metadata.gz: 990cfd15c836e9337df3d364feb813a47e2fd20b7a1269cf622b564c52e296a2fbef56d91b84f7e530237ca3594290ad05c8dbd748abb30df4ab1164e5379858
7
- data.tar.gz: 2f120c6e8afc60a95b8be94358e8aa3f03ee5f19e7e87a37451a44408618146d75ffff8684986b22dcbe0ab2b040c863c2e9d73b557b89f8d1d1fafdd82770d2
6
+ metadata.gz: eacc83d2d8aaf67caff1807e5e4368e6c954ac5f4a59758c020241b56e53d018c430b2c7874d56a2761a8da0ad288f01fcba32ff8ab60dd651a86d822f3d4759
7
+ data.tar.gz: 483483c8d663da64483efa03a8292308f2425e201071155c87810d715e8d54470b6fb39ca643db6b9a96f06be1a23c58815458e726017318b88b5c7821ef0812
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ThirteenF
2
2
 
3
- A ruby interface for S.E.C. 13-F Data. There is a lot of great finance and
3
+ A ruby interface for S.E.C. 13F Data. There is a lot of great finance and
4
4
  investing data that is available for free, but few developer tools that let
5
5
  us interact with the data outside of commercial platforms. This library
6
6
  aims to remedy a small piece of this by providing a robust API to search and
@@ -12,6 +12,11 @@ Reports. What is a 13F Report? Please visit
12
12
  This library is meant to serve a lightweight API which developers can enhance
13
13
  via a persistence layer and/or UI in their own applications.
14
14
 
15
+ At this time, I am sure there are a number of edge cases the gem does
16
+ not handle. Also it only imports 13F's submitted as XML files, which only goes
17
+ back to 2013. It is a low priority for myself to robustly parse the text files
18
+ provided before this time.
19
+
15
20
  ## Installation
16
21
 
17
22
  Add this line to your application's Gemfile:
@@ -30,7 +35,65 @@ Or install it yourself as:
30
35
 
31
36
  ## Usage
32
37
 
33
- TODO: Write usage instructions here
38
+ ### Search
39
+
40
+ ```ruby
41
+ search = ThirteenF::Search.new('Berkshire Hathaway')
42
+ search = ThirteenF::Search.new('BERKSHIRE HATHAWAY INC')
43
+ search.get_companies
44
+ search.companies
45
+ ```
46
+
47
+ ### Companies
48
+
49
+ ```ruby
50
+ company = search.companies.first
51
+ company.get_filings # grabs 10 13F filings by default which is the minimum
52
+ company.get_filings(count: 20) # can supply an optional count keyword arg to get more filings
53
+ company.get_most_recent_holdings
54
+ company.most_recent_holdings # returns positions from more recent 13F filing
55
+
56
+ company.cik # type: String | ex: "0001067983"
57
+ company.name # type: String | ex: "BERKSHIRE HATHAWAY INC"
58
+ company.state_or_country # type: String | ex: "NE"
59
+ ```
60
+
61
+ ### Filings
62
+
63
+ ```ruby
64
+ company.get_filings
65
+ filing = company.filings.first
66
+ filing.company
67
+ filing.get_positions
68
+ filing.positions # returns the US public securities held by the company at the
69
+ # time of the period of the report
70
+
71
+ filing.index_url # type: String
72
+ filing.response_status # type: String | ex: "200 OK"
73
+ filing.period_of_report # type: Date or nil
74
+ filing.time_accepted # type: DateTime or nil
75
+ filing.table_html_url # type: String or nil
76
+ filing.table_xml_url # type: String or nil
77
+ filing.cover_page_html_url # String or nil
78
+ ```
79
+
80
+ ### Positions
81
+
82
+ ```ruby
83
+ position = filing.positions.first
84
+ position.filing
85
+
86
+ position.name_of_issuer # type: String | ex: "EBAY INC"
87
+ position.title_of_class # type: String | ex: "COM"
88
+ position.cusip # type: String | ex: "278642103"
89
+ position.value_in_thousands # type: Integer | ex: 722018
90
+ position.shares_or_principal_amount # type: Integer | ex: 19994970
91
+ position.shares_or_principal_amount_type # type: String | ex: "SH"
92
+ position.put_or_call # type: String or nil | ex: "PUT"
93
+ position.investment_discretion # type: String | ex: "DFND"
94
+ position.other_managers # type: String or nil | ex: "1,4,11"
95
+ position.voting_authority # type: Hash | ex: { sole: 19994970, shared: 0, none: 0 }
96
+ ```
34
97
 
35
98
  ## Development
36
99
 
@@ -37,7 +37,13 @@ class ThirteenF
37
37
  def get_most_recent_holdings
38
38
  get_filings unless filings
39
39
  most_recent_filing = filings.select(&:period_of_report).max_by(&:period_of_report)
40
- @most_recent_holdings = Position.from_xml_filing most_recent_filing
40
+ most_recent_filing.get_positions
41
+ @most_recent_holdings = most_recent_filing.positions
42
+ true
43
+ end
44
+
45
+ def get_filings(count: 10)
46
+ @filings = Filing.from_index_urls thirteen_f_urls(count: count), self
41
47
  true
42
48
  end
43
49
 
@@ -45,15 +51,10 @@ class ThirteenF
45
51
  "#{BASE_URL}/cgi-bin/browse-edgar?CIK=#{cik}"
46
52
  end
47
53
 
48
- def thirteen_f_filings_url(count: 100)
54
+ def thirteen_f_filings_url(count: 10)
49
55
  "#{sec_filings_page_url}&type=13f&count=#{count}"
50
56
  end
51
57
 
52
- def get_filings(count: 100)
53
- @filings = Filing.from_index_urls thirteen_f_urls(count: count)
54
- true
55
- end
56
-
57
58
  private
58
59
  def self.parse_name(name_cell)
59
60
  name_cell.text.split("\n").first
@@ -7,23 +7,23 @@ class ThirteenF
7
7
  class Filing
8
8
  attr_reader :index_url, :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
10
+ :period_of_report, :time_accepted, :response_status, :company, :positions
11
11
 
12
12
  BASE_URL = 'https://www.sec.gov'
13
13
 
14
- def self.from_index_urls(urls)
14
+ def self.from_index_urls(urls, company)
15
15
  redo_count = 0
16
16
  urls.map do |index_url|
17
17
  response = HTTP.get index_url
18
18
  sleep 0.33
19
19
  if response.status == 200
20
20
  redo_count = 0
21
- attributes = set_attributes(response, index_url)
21
+ attributes = set_attributes(response, index_url, company)
22
22
  new(**attributes)
23
23
  else
24
24
  redo_count += 1
25
25
  redo unless redo_count > 1
26
- attributes = bad_response_attributes(response, index_url)
26
+ attributes = bad_response_attributes(response, index_url, company)
27
27
  new(**attributes)
28
28
  end
29
29
  end
@@ -42,10 +42,11 @@ class ThirteenF
42
42
  end
43
43
  end
44
44
 
45
- def initialize(response_status:, index_url:, complete_text_file_url:,
46
- period_of_report:, time_accepted:, table_html_url: nil,
47
- table_xml_url: nil, cover_page_html_url: nil,
48
- cover_page_xml_url: nil)
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
49
50
  @response_status = response_status
50
51
  @index_url = index_url
51
52
  @table_html_url = table_html_url
@@ -58,11 +59,18 @@ class ThirteenF
58
59
  true
59
60
  end
60
61
 
62
+ def get_positions
63
+ return false unless period_of_report
64
+ @positions = Position.from_xml_filing self
65
+ true
66
+ end
67
+
61
68
  private
62
- def self.set_attributes(response, index_url)
69
+ def self.set_attributes(response, index_url, company)
63
70
  page = Nokogiri::HTML response.to_s
64
71
  table_links = page.search('table.tableFile')[0].search('a')
65
72
  attributes = Hash.new
73
+ attributes[:company] = company
66
74
  attributes[:response_status] = response.status.to_s
67
75
  attributes[:index_url] = index_url
68
76
  attributes[:period_of_report] = get_period_of_report page
@@ -98,8 +106,9 @@ class ThirteenF
98
106
  attributes
99
107
  end
100
108
 
101
- def self.bad_response_attributes(response, index_url)
109
+ def self.bad_response_attributes(response, index_url, company)
102
110
  attributes = Hash.new
111
+ attributes[:company] = company
103
112
  attributes[:response_status] = response.status.to_s
104
113
  attributes[:index_url] = index_url
105
114
  attributes[:period_of_report] = nil
@@ -108,12 +117,12 @@ class ThirteenF
108
117
  attributes
109
118
  end
110
119
 
111
- def assign_attributes(response_status:, index_url:, complete_text_file_url:,
112
- period_of_report:, time_accepted:, table_html_url: nil,
113
- table_xml_url: nil, cover_page_html_url: nil,
114
- cover_page_xml_url: nil)
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,
124
+ cover_page_xml_url: nil)
115
125
  @response_status = response_status
116
- @index_url = index_url
117
126
  @table_html_url = table_html_url
118
127
  @table_xml_url = table_xml_url
119
128
  @cover_page_html_url = cover_page_html_url
@@ -9,7 +9,7 @@ class ThirteenF
9
9
  :investment_discretion, :other_managers, :voting_authority, :filing
10
10
 
11
11
  def self.from_xml_filing(filing)
12
- return unless filing.table_xml_url
12
+ return nil unless filing.table_xml_url
13
13
  response = HTTP.get filing.table_xml_url
14
14
  xml_doc = Nokogiri::XML response.to_s
15
15
  xml_doc.search('infoTable').map do |info_table|
@@ -27,9 +27,9 @@ class ThirteenF
27
27
  @name_of_issuer = info_table.search('nameOfIssuer').text
28
28
  @title_of_class = info_table.search('titleOfClass').text
29
29
  @cusip = info_table.search('cusip').text
30
- @value_in_thousands = info_table.search('value').text
30
+ @value_in_thousands = to_integer(info_table.search('value').text)
31
31
  @shares_or_principal_amount_type = info_table.search('sshPrnamtType').text
32
- @shares_or_principal_amount = info_table.search('sshPrnamt').text
32
+ @shares_or_principal_amount = to_integer(info_table.search('sshPrnamt').text)
33
33
 
34
34
  not_found = info_table.search('putCall').count == 0
35
35
  @put_or_call = info_table.search('putCall').text unless not_found
@@ -37,11 +37,16 @@ class ThirteenF
37
37
  @investment_discretion = info_table.search('investmentDiscretion').text
38
38
  @other_managers = info_table.search('otherManager').text
39
39
  @voting_authority = {
40
- sole: info_table.search('Sole').text,
41
- shared: info_table.search('Shared').text,
42
- none: info_table.search('None').text
40
+ sole: to_integer(info_table.search('Sole').text),
41
+ shared: to_integer(info_table.search('Shared').text),
42
+ none: to_integer(info_table.search('None').text)
43
43
  }
44
44
  end
45
+
46
+ private
47
+ def to_integer(text)
48
+ text.delete(',').to_i
49
+ end
45
50
  end
46
51
  end
47
52
 
@@ -1,3 +1,3 @@
1
1
  class ThirteenF
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.1"
3
3
  end
data/thirteen_f.gemspec CHANGED
@@ -6,13 +6,13 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["fordfischer"]
7
7
  spec.email = ["fordfischer07@gmail.com"]
8
8
 
9
- spec.summary = %q{A ruby interface for S.E.C. 13-F Data.}
9
+ spec.summary = %q{A ruby interface for S.E.C. 13F Data.}
10
10
 
11
- spec.description = %q{thirteen_f lets you easily interact with SEC 13-F
12
- filing data in all the ways we think normal human beings might want to. The
13
- SEC is the U.S. Securities and Exchange Commission. 13-F filings are
14
- disclosures large institutional investors in public securites have to provide
15
- and make public every quarter.}
11
+ spec.description = %q{thirteen_f lets you easily search and retrieve SEC 13F
12
+ filing data. The SEC is the U.S. Securities and Exchange Commission. 13F
13
+ filings are disclosures large institutional investors in public securites have
14
+ to provide and make public every quarter. It is a great way to follow what
15
+ different investors have been doing.}
16
16
  spec.homepage = "https://github.com/fordfischer/thirteen_f"
17
17
  spec.license = "MIT"
18
18
  spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thirteen_f
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - fordfischer
@@ -67,11 +67,11 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: |-
70
- thirteen_f lets you easily interact with SEC 13-F
71
- filing data in all the ways we think normal human beings might want to. The
72
- SEC is the U.S. Securities and Exchange Commission. 13-F filings are
73
- disclosures large institutional investors in public securites have to provide
74
- and make public every quarter.
70
+ thirteen_f lets you easily search and retrieve SEC 13F
71
+ filing data. The SEC is the U.S. Securities and Exchange Commission. 13F
72
+ filings are disclosures large institutional investors in public securites have
73
+ to provide and make public every quarter. It is a great way to follow what
74
+ different investors have been doing.
75
75
  email:
76
76
  - fordfischer07@gmail.com
77
77
  executables: []
@@ -120,5 +120,5 @@ requirements: []
120
120
  rubygems_version: 3.1.2
121
121
  signing_key:
122
122
  specification_version: 4
123
- summary: A ruby interface for S.E.C. 13-F Data.
123
+ summary: A ruby interface for S.E.C. 13F Data.
124
124
  test_files: []