sec_edgar 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []