sec_query 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Rakefile +1 -1
- data/lib/sec_query.rb +14 -9
- data/lib/sec_query/entity.rb +285 -291
- data/lib/sec_query/filing.rb +45 -40
- data/lib/sec_query/relationship.rb +67 -58
- data/lib/sec_query/sec_uri.rb +98 -0
- data/lib/sec_query/transaction.rb +99 -82
- data/lib/sec_query/version.rb +3 -1
- data/sec_query.gemspec +17 -15
- data/spec/sec_query_spec.rb +84 -64
- data/spec/sec_uri_spec.rb +37 -0
- data/spec/spec_helper.rb +6 -1
- data/spec/support/vcr.rb +10 -0
- metadata +58 -6
- data/Gemfile.lock +0 -30
- data/lib/.DS_Store +0 -0
- data/lib/sec_query/.DS_Store +0 -0
data/lib/sec_query/filing.rb
CHANGED
@@ -1,45 +1,50 @@
|
|
1
|
-
|
2
|
-
class Filing
|
3
|
-
attr_accessor :cik, :title, :summary, :link, :term, :date, :file_id
|
4
|
-
def initialize(filing)
|
5
|
-
@cik = filing[:cik]
|
6
|
-
@title = filing[:title]
|
7
|
-
@summary = filing[:summary]
|
8
|
-
@link = filing[:link]
|
9
|
-
@term = filing[:term]
|
10
|
-
@date = filing[:date]
|
11
|
-
@file_id = filing[:file_id]
|
12
|
-
end
|
13
|
-
|
14
|
-
|
15
|
-
def self.find(entity, start, count, limit)
|
1
|
+
# encoding: UTF-8
|
16
2
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
query_more = false;
|
24
|
-
for entry in entries
|
25
|
-
query_more = true;
|
26
|
-
filing={}
|
27
|
-
filing[:cik] = entity[:cik]
|
28
|
-
filing[:title] = (entry/:title).innerHTML
|
29
|
-
filing[:summary] = (entry/:summary).innerHTML
|
30
|
-
filing[:link] = (entry/:link)[0].get_attribute("href")
|
31
|
-
filing[:term] = (entry/:category)[0].get_attribute("term")
|
32
|
-
filing[:date] = (entry/:updated).innerHTML
|
33
|
-
filing[:file_id] = (entry/:id).innerHTML.split("=").last
|
3
|
+
module SecQuery
|
4
|
+
# => SecQuery::Filing
|
5
|
+
# SecQuery::Filing requests and parses filings for any given SecQuery::Entity
|
6
|
+
class Filing
|
7
|
+
COLUMNS = :cik, :title, :summary, :link, :term, :date, :file_id
|
8
|
+
attr_accessor(*COLUMNS)
|
34
9
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
else
|
40
|
-
return entity
|
41
|
-
end
|
42
|
-
end
|
10
|
+
def initialize(filing)
|
11
|
+
COLUMNS.each do |column|
|
12
|
+
instance_variable_set("@#{ column }", filing[column])
|
13
|
+
end
|
43
14
|
end
|
44
15
|
|
16
|
+
def self.find(entity, start, count, limit)
|
17
|
+
start ||= 0
|
18
|
+
count ||= 80
|
19
|
+
url = SecURI.browse_edgar_uri({
|
20
|
+
action: :getcompany,
|
21
|
+
CIK: entity[:cik],
|
22
|
+
output: :atom,
|
23
|
+
count: count,
|
24
|
+
start: start
|
25
|
+
})
|
26
|
+
response = Entity.query(url)
|
27
|
+
doc = Hpricot::XML(response)
|
28
|
+
entries = doc.search(:entry)
|
29
|
+
query_more = false
|
30
|
+
entries.each do |entry|
|
31
|
+
query_more = true
|
32
|
+
filing = {}
|
33
|
+
filing[:cik] = entity[:cik]
|
34
|
+
filing[:title] = (entry/:title).innerHTML
|
35
|
+
filing[:summary] = (entry/:summary).innerHTML
|
36
|
+
filing[:link] = (entry/:link)[0].get_attribute('href')
|
37
|
+
filing[:term] = (entry/:category)[0].get_attribute('term')
|
38
|
+
filing[:date] = (entry/:updated).innerHTML
|
39
|
+
filing[:file_id] = (entry/:id).innerHTML.split('=').last
|
40
|
+
|
41
|
+
entity[:filings] << Filing.new(filing)
|
42
|
+
end
|
43
|
+
if (query_more && limit.nil?) || (query_more && !limit)
|
44
|
+
Filing.find(entity, start + count, count, limit)
|
45
|
+
else
|
46
|
+
return entity
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
45
50
|
end
|
@@ -1,63 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module SecQuery
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
return @relationships
|
47
|
-
else
|
48
|
-
return false
|
49
|
-
end
|
4
|
+
# => SecQuery::Relationship
|
5
|
+
# Relationships are Owner / Issuer Relationships between Entities,
|
6
|
+
# forged by Transactions.
|
7
|
+
class Relationship
|
8
|
+
COLUMNS = :name, :position, :cik
|
9
|
+
attr_accessor(*COLUMNS, :date)
|
10
|
+
|
11
|
+
def initialize(relationship)
|
12
|
+
COLUMNS.each do |column|
|
13
|
+
instance_variable_set("@#{ column }", relationship[column])
|
14
|
+
end
|
15
|
+
date = relationship[:date].split('-')
|
16
|
+
@date = Time.utc(date[0], date[1], date[2].to_i)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(entity)
|
20
|
+
@relationships = []
|
21
|
+
|
22
|
+
if entity[:doc]
|
23
|
+
doc = entity[:doc]
|
24
|
+
elsif entity[:cik]
|
25
|
+
doc = Entity.document(entity[:cik])[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
type = 'Ownership Reports for Issuers:'
|
29
|
+
lines = doc.search('//table').search('//td').search("b[text()*='"+type+"']")
|
30
|
+
if lines.empty?
|
31
|
+
type = 'Ownership Reports from:'
|
32
|
+
lines = doc.search('//table').search('//td').search("b[text()*='"+type+"']")
|
33
|
+
end
|
34
|
+
|
35
|
+
return false if lines.empty?
|
36
|
+
|
37
|
+
relationship = {}
|
38
|
+
lines = lines[0].parent.search('//table')[0].search('//tr')
|
39
|
+
lines.each do |line|
|
40
|
+
link = line.search('//a')[0]
|
41
|
+
if link.innerHTML != 'Owner' && link.innerHTML != 'Issuer'
|
42
|
+
relationship[:name] = link.innerHTML
|
43
|
+
relationship[:cik] = line.search('//td')[1].search('//a').innerHTML
|
44
|
+
relationship[:date] = line.search('//td')[2].innerHTML
|
45
|
+
relationship[:position] = line.search('//td')[3].innerHTML
|
46
|
+
@relationships << Relationship.new(relationship)
|
50
47
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
48
|
+
end
|
49
|
+
@relationships
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.print(relationships)
|
53
|
+
if relationships
|
54
|
+
puts "\n\t#{ relationships[1] }\n"
|
55
|
+
printf("\t%-30s %-10s %-40s %-10s\n\n",
|
56
|
+
'Entity',
|
57
|
+
'CIK',
|
58
|
+
'Position',
|
59
|
+
'Date')
|
60
|
+
issuer[:relationships].each do |relationship|
|
61
|
+
printf("\t%-30s %-10s %-40s %-10s\n",
|
62
|
+
relationship.name,
|
63
|
+
relationship.cik,
|
64
|
+
relationship.position,
|
65
|
+
relationship.date)
|
61
66
|
end
|
67
|
+
else
|
68
|
+
puts 'No relationships'
|
69
|
+
end
|
62
70
|
end
|
71
|
+
end
|
63
72
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'debugger'
|
3
|
+
|
4
|
+
module SecQuery
|
5
|
+
class SecURI
|
6
|
+
attr_accessor :host, :scheme, :path, :query_values
|
7
|
+
|
8
|
+
def self.browse_edgar_uri(args = nil)
|
9
|
+
build_with_path('/browse-edgar', args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.ownership_display_uri(args = nil)
|
13
|
+
build_with_path('/own-disp', args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.build_with_path(path, args)
|
17
|
+
instance = SecURI.new
|
18
|
+
instance.path += path
|
19
|
+
return instance if args.nil?
|
20
|
+
options = send("handle_#{ args.class.to_s.underscore }_args", args)
|
21
|
+
instance.query_values = options
|
22
|
+
instance
|
23
|
+
end
|
24
|
+
|
25
|
+
private_class_method :build_with_path
|
26
|
+
|
27
|
+
def self.handle_string_args(string_arg)
|
28
|
+
options = {}
|
29
|
+
# Uhhhg. I hate using Float here. HACK
|
30
|
+
begin Float(string_arg)
|
31
|
+
options[:CIK] = string_arg
|
32
|
+
rescue
|
33
|
+
if string_arg.length <= 4
|
34
|
+
options[:CIK] = string_arg
|
35
|
+
else
|
36
|
+
options[:company] = string_arg.gsub(/[(,?!\''"":.)]/, '')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
options
|
40
|
+
end
|
41
|
+
|
42
|
+
private_class_method :handle_string_args
|
43
|
+
|
44
|
+
def self.handle_hash_args(hash_arg)
|
45
|
+
options = hash_arg
|
46
|
+
if hash_arg[:symbol] || hash_arg[:cik]
|
47
|
+
options[:CIK] = (hash_arg[:symbol] || hash_arg[:cik])
|
48
|
+
return options
|
49
|
+
end
|
50
|
+
options[:company] = company_name_from_hash_args(hash_arg)
|
51
|
+
options
|
52
|
+
end
|
53
|
+
|
54
|
+
private_class_method :handle_hash_args
|
55
|
+
|
56
|
+
def self.company_name_from_hash_args(args)
|
57
|
+
return "#{ args[:last] } #{ args[:first] }" if args[:first] && args[:last]
|
58
|
+
return args[:name].gsub(/[(,?!\''"":.)]/, '') if args[:name]
|
59
|
+
end
|
60
|
+
|
61
|
+
private_class_method :company_name_from_hash_args
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
self.host = 'www.sec.gov'
|
65
|
+
self.scheme = 'http'
|
66
|
+
self.path = 'cgi-bin'
|
67
|
+
end
|
68
|
+
|
69
|
+
def []=(key, value)
|
70
|
+
query_values[key] = value
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def output_atom
|
75
|
+
query_values.merge!(output: 'atom')
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
uri.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_str
|
84
|
+
to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def uri
|
90
|
+
Addressable::URI.new(
|
91
|
+
host: host,
|
92
|
+
scheme: scheme,
|
93
|
+
path: path,
|
94
|
+
query_values: query_values
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -1,89 +1,106 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module SecQuery
|
2
|
-
|
4
|
+
# => SecQuery::Transactions
|
5
|
+
# SecQuery filings for any given SecQuery::Entity instance.
|
6
|
+
class Transaction
|
7
|
+
STR_COLUMNS = :code, :form, :type, :modes, :owner_cik, :security_name,
|
8
|
+
:deemed, :exercise, :nature, :derivative, :exercised,
|
9
|
+
:reporting_owner
|
3
10
|
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(transaction)
|
7
|
-
@filing_number = transaction[:form].split("/")[-2][0..19]
|
8
|
-
@code = transaction[:code]
|
9
|
-
if transaction[:date] != nil and transaction[:date] != "-"
|
10
|
-
date = transaction[:date].split("-")
|
11
|
-
@date = Time.utc(date[0],date[1],date[2])
|
12
|
-
end
|
13
|
-
@reporting_owner = transaction[:reporting_owner]
|
14
|
-
@form = transaction[:form]
|
15
|
-
@type = transaction[:type]
|
16
|
-
@modes = transaction[:modes]
|
17
|
-
@shares = transaction[:shares].to_f
|
18
|
-
@price = transaction[:price].gsub("$", "").to_f
|
19
|
-
@owned = transaction[:owned].to_f
|
20
|
-
@number = transaction[:number].to_i
|
21
|
-
@owner_cik = transaction[:owner_cik]
|
22
|
-
@security_name = transaction[:security_name]
|
23
|
-
@deemed = transaction[:deemed]
|
24
|
-
@exercise = transaction[:exercise]
|
25
|
-
@nature = transaction[:nature]
|
26
|
-
@derivative = transaction[:derivative]
|
27
|
-
@underlying_1 = transaction[:underlying_1].to_f
|
28
|
-
@exercised = transaction[:exercised]
|
29
|
-
@underlying_2 = transaction[:underlying_2].to_f
|
30
|
-
if transaction[:expires] != nil;
|
31
|
-
expires = transaction[:expires].split("-")
|
32
|
-
@expires = Time.utc(expires[0],expires[1],expires[2].to_i)
|
33
|
-
end
|
34
|
-
@underlying_3 = transaction[:underlying_3].to_f
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.find(entity, start, count, limit)
|
11
|
+
FLOAT_COLUMNS = :shares, :owned, :underlying_1, :underlying_2,
|
12
|
+
:underlying_3
|
38
13
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
14
|
+
attr_accessor(*STR_COLUMNS, *FLOAT_COLUMNS, :filing_number, :date, :price,
|
15
|
+
:owned, :number, :expires)
|
16
|
+
|
17
|
+
def initialize(transaction)
|
18
|
+
@number = transaction[:number].to_i
|
19
|
+
@price = transaction[:price].gsub('$', '').to_f
|
20
|
+
@filing_number = transaction[:form].split('/')[-2][0..19]
|
21
|
+
setup_columns(transaction)
|
22
|
+
setup_date(transaction)
|
23
|
+
setup_expires(transaction)
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_columns(transaction)
|
27
|
+
STR_COLUMNS.each do |column|
|
28
|
+
instance_variable_set("@#{ column }", transaction[column])
|
29
|
+
end
|
30
|
+
FLOAT_COLUMNS.each do |column|
|
31
|
+
instance_variable_set("@#{ column }", transaction[column].to_f)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_date(transaction)
|
36
|
+
if transaction[:date] && transaction[:date] != '-'
|
37
|
+
date = transaction[:date].split('-')
|
38
|
+
@date = Time.utc(date[0], date[1], date[2])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def setup_expires(transaction)
|
43
|
+
if transaction[:expires]
|
44
|
+
expires = transaction[:expires].split('-')
|
45
|
+
@expires = Time.utc(expires[0], expires[1], expires[2].to_i)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find(entity, start, count, limit)
|
50
|
+
start ||= 0
|
51
|
+
count ||= 80
|
52
|
+
url = SecURI.ownership_display_uri({
|
53
|
+
action: "get#{entity[:type]}",
|
54
|
+
CIK: entity[:cik],
|
55
|
+
start: start,
|
56
|
+
count: count
|
57
|
+
})
|
58
|
+
response = Entity.query(url)
|
59
|
+
doc = Hpricot(response)
|
60
|
+
trans = doc.search("//td[@width='40%']")[0].parent.parent.search('//tr')
|
61
|
+
i = start
|
62
|
+
query_more = false
|
63
|
+
trans.each do |tran|
|
64
|
+
td = tran.search('//td')
|
65
|
+
if td[2] && td[1].innerHTML != 'Exercise'
|
66
|
+
query_more = true
|
67
|
+
unless td[0].empty?
|
68
|
+
transaction = {}
|
69
|
+
transaction[:code] = td[0].innerHTML
|
70
|
+
transaction[:date] = td[1].innerHTML
|
71
|
+
transaction[:reporting_owner] = td[2].innerHTML
|
72
|
+
transaction[:form] = td[3].innerHTML
|
73
|
+
transaction[:type] = td[4].innerHTML
|
74
|
+
transaction[:modes] = td[5].innerHTML
|
75
|
+
transaction[:shares] = td[6].innerHTML
|
76
|
+
transaction[:price] = td[7].innerHTML
|
77
|
+
transaction[:owned] = td[8].innerHTML
|
78
|
+
transaction[:number] = td[9].innerHTML
|
79
|
+
transaction[:owner_cik] = td[10].innerHTML
|
80
|
+
transaction[:security_name] = td[11].innerHTML
|
81
|
+
transaction[:deemed] = td[12].innerHTML
|
82
|
+
n_td = trans[i + 1].search('//td') if trans[i + 1]
|
83
|
+
if n_td && n_td.count == 7 && n_td[0].innerHTML.empty?
|
84
|
+
transaction[:exercise] = n_td[1].innerHTML
|
85
|
+
transaction[:nature] = n_td[2].innerHTML
|
86
|
+
transaction[:derivative] = n_td[3].innerHTML
|
87
|
+
transaction[:underlying_1] = n_td[4].innerHTML
|
88
|
+
transaction[:exercised] = n_td[5].innerHTML
|
89
|
+
transaction[:underlying_2] = n_td[6].innerHTML
|
90
|
+
transaction[:expires] = n_td[7].innerHTML
|
91
|
+
transaction[:underlying_3] = n_td[8].innerHTML
|
86
92
|
end
|
93
|
+
entity[:transactions] << Transaction.new(transaction)
|
94
|
+
end
|
87
95
|
end
|
96
|
+
i += 1
|
97
|
+
end
|
98
|
+
|
99
|
+
if (query_more && limit.nil?) || (query_more && !limit)
|
100
|
+
Transaction.find(entity, start + count, count, limit)
|
101
|
+
else
|
102
|
+
return entity
|
103
|
+
end
|
88
104
|
end
|
105
|
+
end
|
89
106
|
end
|