sec_edgar 0.0.1

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.
@@ -0,0 +1,121 @@
1
+ module SecEdgar
2
+ TRANSACTION_CODES = {
3
+ 'P' => 'Open market or private purchase of non-derivative or derivative security',
4
+ 'S' => 'Open market or private sale of non-derivative or derivative security6',
5
+ 'V' => 'Transaction voluntarily reported earlier than required',
6
+ 'A' => 'Grant, award or other acquisition pursuant to Rule 16b-3(d)',
7
+ 'D' => 'Disposition to the issuer of issuer equity securities pursuant to Rule 16b-3(e)',
8
+ 'F' => 'Payment of exercise price or tax liability by delivering or withholding securities incident to the receipt, exercise',
9
+ 'I' => 'Discretionary transaction in accordance with Rule 16b-3(f) resulting in acquisition or disposition of issuer',
10
+ 'M' => 'Exercise or conversion of derivative security exempted pursuant to Rule 16b-3',
11
+ 'C' => 'Conversion of derivative security',
12
+ 'E' => 'Expiration of short derivative position',
13
+ 'H' => 'Expiration (or cancellation) of long derivative position with value received',
14
+ 'O' => 'Exercise of out-of-the-money derivative security',
15
+ 'X' => 'Exercise of in-the-money or at-the-money derivative security',
16
+ 'G' => 'Bona fide gift',
17
+ 'L' => 'Small acquisition under Rule 16a-6',
18
+ 'W' => 'Acquisition or disposition by will or the laws of descent and distribution',
19
+ 'Z' => 'Deposit into or withdrawal from voting trust',
20
+ 'J' => 'Other acquisition or disposition (describe transaction)',
21
+ 'K' => 'Transaction in equity swap or instrument with similar characteristics',
22
+ 'U' => 'Disposition pursuant to a tender of shares in a change of control transaction'
23
+ }
24
+
25
+ OwnershipDocument = Struct.new(
26
+ :schema_version,
27
+ :document_type,
28
+ :period_of_report,
29
+ :not_subject_to_section_16,
30
+ :issuer_name,
31
+ :issuer_cik,
32
+ :issuer_trading_symbol,
33
+ :owner_cik,
34
+ :owner_name,
35
+ :owner_address,
36
+ :is_director,
37
+ :is_officer,
38
+ :is_ten_percent_owner,
39
+ :is_other,
40
+ :officer_title,
41
+ :transactions,
42
+ :derivative_transactions,
43
+ :footnotes
44
+ ) do
45
+
46
+ def sum_shares
47
+ @sum ||= begin
48
+ sum = 0
49
+ all_trans.each do |t|
50
+ if t.acquired_or_disposed_code == 'A'
51
+ sum += t.shares
52
+ else
53
+ sum -= t.shares
54
+ end
55
+ end
56
+ sum
57
+ end
58
+ end
59
+
60
+ def dollar_volume
61
+ @dollar_volume ||= begin
62
+ sum = 0
63
+ all_trans.each do |t|
64
+ if t.acquired_or_disposed_code == 'A'
65
+ sum += t.shares * t.price_per_share
66
+ else
67
+ sum -= t.shares * t.price_per_share
68
+ end
69
+ end
70
+ sum
71
+ end
72
+ end
73
+
74
+ def total_shares
75
+ @total ||= all_trans.map(&:shares).inject(:+)
76
+ end
77
+
78
+ def all_trans
79
+ trans + derivative_trans
80
+ end
81
+
82
+ def trans
83
+ transactions || []
84
+ end
85
+
86
+ def derivative_trans
87
+ derivative_transactions || []
88
+ end
89
+
90
+ SecEdgar::TRANSACTION_CODES.keys.each do |code|
91
+ define_method("per_code_#{ code.downcase }".to_sym) do
92
+ return 0 if total_shares == 0
93
+ all_trans.select do |t|
94
+ t.code.upcase == code
95
+ end.tap do |t|
96
+ return 0 if t.empty?
97
+ end.map do |t|
98
+ t.shares
99
+ end.inject(:+) * 100.0 / total_shares
100
+ end
101
+ end
102
+
103
+ def sum_shares_after
104
+ return all_trans.map(&:shares_after).max if sum_shares > 0
105
+ trans.map(&:shares_after).min || 0
106
+ end
107
+
108
+ def as_json(*)
109
+ tran_codes = {}
110
+
111
+ SecEdgar::TRANSACTION_CODES.keys.each do |code|
112
+ name = "per_code_#{ code.downcase }".to_sym
113
+ tran_codes[name] = send(name)
114
+ end
115
+
116
+ super
117
+ .merge(sum_shares: sum_shares, sum_shares_after: sum_shares_after)
118
+ .merge(tran_codes)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,132 @@
1
+ require 'fileutils'
2
+
3
+ module SecEdgar
4
+ PollFiling = Struct.new(
5
+ :cik,
6
+ :company_name,
7
+ :type,
8
+ :date_str,
9
+ :ftp_link,
10
+ :client,
11
+ :title,
12
+ :term,
13
+ :file_id) do
14
+ def date
15
+ Date.parse(date_str)
16
+ end
17
+
18
+ def link
19
+ "http://www.sec.gov/Archives/#{ ftp_link }"
20
+ end
21
+
22
+ def content(&error_blk)
23
+ @content ||= client.fetch(ftp_link, nil)
24
+ rescue RestClient::ResourceNotFound => e
25
+ puts "404 Resource Not Found: Bad link #{ self.link }"
26
+ if block_given?
27
+ error_blk.call(e, self)
28
+ else
29
+ raise e
30
+ end
31
+ end
32
+ end
33
+
34
+ class Poll
35
+ attr_reader :client, :year, :quarter, :local_base
36
+
37
+ def initialize(year = 1993, qtr = 1, client = SecEdgar::FtpClient.instance, local_base = "./assets")
38
+ @client = client
39
+ @year = year
40
+ @quarter = qtr
41
+ @local_base = local_base
42
+ end
43
+
44
+ def self.all(from_year, to_year, &blk)
45
+ polls_by_year(from_year, to_year).each do |poll|
46
+ poll.form4s.each do |filing|
47
+ blk.call(filing)
48
+ end
49
+ end
50
+ end
51
+
52
+ def self.polls_by_year(from_year, to_year)
53
+ polls = []
54
+ (from_year..to_year).each do |yr|
55
+ (1..4).each do |qtr|
56
+ polls << self.new(yr, qtr)
57
+ end
58
+ end
59
+ polls
60
+ end
61
+
62
+ def to_s
63
+ "<Poll y: #{ year } q: #{ quarter }>"
64
+ end
65
+
66
+ def index
67
+ unless File.exist?(local_file)
68
+ puts "fetching from sec #{ to_s }..."
69
+ fetch
70
+ end
71
+ File.read(local_file)
72
+ end
73
+
74
+ def form4s
75
+ filings.select do |line|
76
+ line =~ form4_regexp
77
+ end.map do |f|
78
+ begin
79
+ PollFiling.new(*f.split("|"), client)
80
+ rescue => e
81
+ puts line
82
+ raise e
83
+ end
84
+ end
85
+ end
86
+
87
+ def filings
88
+ index.scrub.split("\n").select do |line|
89
+ line =~ filing_regexp
90
+ end
91
+ end
92
+
93
+ def local_file
94
+ "#{ local_dir }/master.idx"
95
+ end
96
+
97
+ private
98
+
99
+ def form4_regexp
100
+ /^(\d+\|.*\|4\|.*.txt)$/i
101
+ end
102
+
103
+ def filing_regexp
104
+ /^(\d+\|.*\|.*.txt)$/i
105
+ end
106
+
107
+ def remote
108
+ "/edgar/full-index/#{ year }/QTR#{ quarter }/master.idx"
109
+ end
110
+
111
+ def local_dir
112
+ path = "#{ local_base }/#{ year.to_s }/QTR#{ quarter }"
113
+ FileUtils.mkdir_p(path)
114
+ path
115
+ end
116
+
117
+ def fetch
118
+ client.fetch(remote, local_file)
119
+ rescue => e
120
+ puts "SecEdgar::Poll#fetch_quarter failed for #{ year }, QTR#{ quarter }"
121
+ puts "local: #{ local_file }"
122
+ puts "remote: #{ remote }"
123
+ puts e.message
124
+ ensure
125
+ verify_download
126
+ end
127
+
128
+ def verify_download
129
+ File.delete(local_file) unless File.size?(local_file)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: UTF-8
2
+
3
+ module SecEdgar
4
+ class SecURI
5
+ attr_accessor :host, :scheme, :path, :query_values
6
+
7
+ def self.browse_edgar_uri(args = nil)
8
+ build_with_path('/browse-edgar', args)
9
+ end
10
+
11
+ def self.for_date(date)
12
+ instance = SecURI.new
13
+ instance.scheme = 'ftp'
14
+ instance.host = 'ftp.sec.gov'
15
+ instance.path = "edgar/daily-index/#{ date.to_sec_uri_format }"
16
+ instance
17
+ end
18
+
19
+ def self.ownership_display_uri(args = nil)
20
+ build_with_path('/own-disp', args)
21
+ end
22
+
23
+ def self.build_with_path(path, args)
24
+ instance = SecURI.new
25
+ instance.path += path
26
+ return instance if args.nil?
27
+ options = send("handle_#{ args.class.to_s.underscore }_args", args)
28
+ instance.query_values = options
29
+ instance
30
+ end
31
+
32
+ def self.handle_string_args(string_arg)
33
+ options = {}
34
+ begin Float(string_arg)
35
+ options[:CIK] = string_arg
36
+ rescue
37
+ if string_arg.length <= 4
38
+ options[:CIK] = string_arg
39
+ else
40
+ options[:company] = string_arg.gsub(/[(,?!\''"":.)]/, '')
41
+ end
42
+ end
43
+ options
44
+ end
45
+
46
+ private_class_method :handle_string_args
47
+
48
+ def self.handle_hash_args(hash_arg)
49
+ options = hash_arg
50
+ if hash_arg[:symbol] || hash_arg[:cik]
51
+ options[:CIK] = (hash_arg[:symbol] || hash_arg[:cik])
52
+ return options
53
+ end
54
+ options[:company] = company_name_from_hash_args(hash_arg)
55
+ options
56
+ end
57
+
58
+ private_class_method :handle_hash_args
59
+
60
+ def self.company_name_from_hash_args(args)
61
+ return "#{ args[:last] } #{ args[:first] }" if args[:first] && args[:last]
62
+ return args[:name].gsub(/[(,?!\''"":.)]/, '') if args[:name]
63
+ end
64
+
65
+ private_class_method :company_name_from_hash_args
66
+
67
+ def initialize
68
+ self.host = 'www.sec.gov'
69
+ self.scheme = 'http'
70
+ self.path = 'cgi-bin'
71
+ end
72
+
73
+ def []=(key, value)
74
+ query_values[key] = value
75
+ self
76
+ end
77
+
78
+ def output_atom
79
+ query_values.merge!(output: 'atom')
80
+ self
81
+ end
82
+
83
+ def to_s
84
+ uri.to_s
85
+ end
86
+
87
+ def to_str
88
+ to_s
89
+ end
90
+
91
+ private
92
+
93
+ def uri
94
+ Addressable::URI.new(
95
+ host: host,
96
+ scheme: scheme,
97
+ path: path,
98
+ query_values: query_values
99
+ )
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,20 @@
1
+ module SecEdgar
2
+ Transaction = Struct.new(
3
+ :acquired_or_disposed_code,
4
+ :nature_of_ownership,
5
+ :code,
6
+ :shares,
7
+ :security_title,
8
+ :direct_or_indirect_code,
9
+ :form_type,
10
+ :equity_swap_involved,
11
+ :transaction_date,
12
+ :shares_after,
13
+ :price_per_share
14
+ ) do
15
+ def holdings_change
16
+ return 0 if shares_after == 0
17
+ shares.fdiv(shares_after)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ class String
2
+ def blank?
3
+ /^\s*$/.match(self)
4
+ end
5
+
6
+ def underscore
7
+ self.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+ end
14
+
15
+ class Date
16
+ def quarter
17
+ ((month / 3.0) - 0.1).floor + 1
18
+ end
19
+
20
+ def to_sec_uri_format
21
+ today = Date.today
22
+ if today.quarter == quarter && today.year == year
23
+ "company.#{ strftime("%Y%m%d") }.idx"
24
+ else
25
+ "#{ year }/QTR#{ quarter }/company.#{ strftime("%Y%m%d") }.idx"
26
+ end
27
+ end
28
+ end
data/lib/sec_edgar.rb ADDED
@@ -0,0 +1,26 @@
1
+ module SecEdgar; end
2
+
3
+ require 'nokogiri'
4
+ require 'singleton'
5
+ require 'net/ftp'
6
+ require 'date'
7
+ require 'addressable/uri'
8
+ require 'open-uri'
9
+ require 'rest-client'
10
+ require 'rss'
11
+
12
+ require_relative './sec_edgar/utils'
13
+ require_relative './sec_edgar/sec_uri'
14
+ require_relative './sec_edgar/entity'
15
+ require_relative './sec_edgar/basic_stats'
16
+ require_relative './sec_edgar/address'
17
+ require_relative './sec_edgar/ftp_client'
18
+ require_relative './sec_edgar/filing'
19
+ require_relative './sec_edgar/footnote'
20
+ require_relative './sec_edgar/derivative_transaction'
21
+ require_relative './sec_edgar/transaction'
22
+ require_relative './sec_edgar/filing_persister'
23
+ require_relative './sec_edgar/filing_parser'
24
+ require_relative './sec_edgar/officer_title'
25
+ require_relative './sec_edgar/ownership_document'
26
+ require_relative './sec_edgar/poll'
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sec_edgar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - CJ Avilla
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.6.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.8.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: addressable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.3.8
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.3.8
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.2.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.2.0
69
+ description: Tool for querying the SEC database
70
+ email: cjavilla@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/assets/1996/QTR1/master.idx
76
+ - lib/assets/1996/QTR2/master.idx
77
+ - lib/assets/1996/QTR3/master.idx
78
+ - lib/assets/1996/QTR4/master.idx
79
+ - lib/sec_edgar.rb
80
+ - lib/sec_edgar/address.rb
81
+ - lib/sec_edgar/basic_stats.rb
82
+ - lib/sec_edgar/derivative_transaction.rb
83
+ - lib/sec_edgar/entity.rb
84
+ - lib/sec_edgar/filing.rb
85
+ - lib/sec_edgar/filing_parser.rb
86
+ - lib/sec_edgar/filing_persister.rb
87
+ - lib/sec_edgar/filing_updater.rb
88
+ - lib/sec_edgar/footnote.rb
89
+ - lib/sec_edgar/ftp_client.rb
90
+ - lib/sec_edgar/nil_ownership_document.rb
91
+ - lib/sec_edgar/officer_title.rb
92
+ - lib/sec_edgar/ownership4Document.xsd.xml
93
+ - lib/sec_edgar/ownershipDocumentCommon.xsd.xml
94
+ - lib/sec_edgar/ownership_document.rb
95
+ - lib/sec_edgar/poll.rb
96
+ - lib/sec_edgar/sec_uri.rb
97
+ - lib/sec_edgar/transaction.rb
98
+ - lib/sec_edgar/utils.rb
99
+ homepage: http://rubygems.org/gems/sec_edgar
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.4.5
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: SEC filings finder
123
+ test_files: []