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,3 @@
1
+ module SecEdgar
2
+ Address = Struct.new(:street1, :street2, :city, :state, :zip, :state_description)
3
+ end
@@ -0,0 +1,26 @@
1
+ module SecEdgar
2
+ module BasicStats
3
+ extend self
4
+
5
+ def precision(tp, fp)
6
+ tp.fdiv(tp + fp)
7
+ end
8
+
9
+ def recall(tp, fn)
10
+ tp.fdiv(tp + fn)
11
+ end
12
+
13
+ def f_beta(tp, fp, fn, beta)
14
+ p = precision(tp, fp)
15
+ r = recall(tp, fn)
16
+ b2 = beta ** 2
17
+ (1 + b2) * ((p * r).fdiv((b2 * p) + r))
18
+ end
19
+
20
+ def f1(tp, fp, fn)
21
+ p = precision(tp, fp)
22
+ r = recall(tp, fn)
23
+ 2 * ((p * r).fdiv(p + r))
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module SecEdgar
2
+ DerivativeTransaction = Struct.new(
3
+ :acquired_or_disposed_code,
4
+ :conversion_or_exercise_price,
5
+ :deemed_execution_date,
6
+ :exercise_date,
7
+ :expiration_date,
8
+ :underlying_security_title,
9
+ :underlying_security_shares,
10
+ :nature_of_ownership,
11
+ :code,
12
+ :shares,
13
+ :security_title,
14
+ :direct_or_indirect_code,
15
+ :form_type,
16
+ :equity_swap_involved,
17
+ :transaction_date,
18
+ :shares_after,
19
+ :price_per_share
20
+ )
21
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: UTF-8
2
+ module SecEdgar
3
+ class Entity
4
+ COLUMNS = [
5
+ :cik,
6
+ :name,
7
+ :mailing_address,
8
+ :business_address,
9
+ :assigned_sic,
10
+ :assigned_sic_desc,
11
+ :assigned_sic_href,
12
+ :assitant_director,
13
+ :cik_href,
14
+ :formerly_names,
15
+ :state_location,
16
+ :state_location_href,
17
+ :state_of_incorporation
18
+ ]
19
+
20
+ attr_accessor(*COLUMNS)
21
+
22
+ def initialize(entity)
23
+ COLUMNS.each do |column|
24
+ instance_variable_set("@#{ column }", entity[column.to_s])
25
+ end
26
+ end
27
+
28
+ def filings
29
+ Filing.find(@cik)
30
+ end
31
+
32
+ def transactions
33
+ Transaction.find(@cik)
34
+ end
35
+
36
+ def self.query(url)
37
+ RestClient.get(url) do |response, request, result, &block|
38
+ case response.code
39
+ when 200
40
+ return response
41
+ else
42
+ response.return!(request, result, &block)
43
+ end
44
+ end
45
+ end
46
+
47
+ # def self.find(entity_args)
48
+ # temp = {}
49
+ # temp[:url] = SecURI.browse_edgar_uri(entity_args)
50
+ # temp[:url][:action] = :getcompany
51
+ # response = query(temp[:url].output_atom.to_s)
52
+ # document = Nokogiri::HTML(response)
53
+ # xml = document.xpath("//feed/company-info")
54
+ # Entity.new(parse(xml))
55
+ # end
56
+ #
57
+ # def self.parse(xml)
58
+ # content = Hash.from_xml(xml.to_s)
59
+ # if content['company_info'].present?
60
+ # content = content['company_info']
61
+ # content['name'] = content.delete('conformed_name')
62
+ # if content['formerly_names'].present?
63
+ # content['formerly_names'] = content.delete('formerly_names')['names']
64
+ # end
65
+ # content['addresses']['address'].each do |address|
66
+ # content["#{address['type']}_address"] = address
67
+ # end
68
+ # return content
69
+ # else
70
+ # return {}
71
+ # end
72
+ # end
73
+ end
74
+ end
@@ -0,0 +1,171 @@
1
+ # encoding: UTF-8
2
+
3
+ module SecEdgar
4
+ class Filing
5
+ COLUMNS = [:cik, :title, :summary, :link, :term, :date, :file_id]
6
+
7
+ attr_accessor(*COLUMNS)
8
+
9
+ def initialize(filing)
10
+ COLUMNS.each do |column|
11
+ instance_variable_set("@#{ column }", filing[column])
12
+ end
13
+ end
14
+
15
+ def self.fetch(uri, &blk)
16
+ open(uri) do |rss|
17
+ parse_rss(rss, &blk)
18
+ end
19
+ end
20
+
21
+ def self.recent(options = {}, &blk)
22
+ start = options.fetch(:start, 0)
23
+ count = options.fetch(:count, 100)
24
+ limit = options.fetch(:limit, 100)
25
+ limited_count = [limit - start, count].min
26
+ fetch(uri_for_recent(start, limited_count), &blk)
27
+ start += count
28
+ return if start >= limit
29
+ recent({ start: start, count: count, limit: limit }, &blk)
30
+ rescue OpenURI::HTTPError => e
31
+ puts e
32
+ return
33
+ end
34
+
35
+ def self.for_cik(cik, options = {}, &blk)
36
+ start = options.fetch(:start, 0)
37
+ count = options.fetch(:count, 100)
38
+ limit = options.fetch(:limit, 100)
39
+ fetch(uri_for_cik(cik, start, count), &blk)
40
+ start += count
41
+ return if start >= limit
42
+ for_cik(cik, { start: start, count: count, limit: limit }, &blk)
43
+ rescue OpenURI::HTTPError
44
+ return
45
+ end
46
+
47
+ def self.for_date(date, &blk)
48
+ ftp = Net::FTP.new('ftp.sec.gov')
49
+ ftp.login
50
+ file_name = ftp.nlst("edgar/daily-index/#{ date.to_sec_uri_format }*")[0]
51
+ ftp.close
52
+ open("ftp://ftp.sec.gov/#{ file_name }") do |file|
53
+ if file_name[-2..-1] == 'gz'
54
+ gz_reader = Zlib::GzipReader.new(file)
55
+ gz_reader.rewind
56
+ filings_for_index(gz_reader).each(&blk)
57
+ else
58
+ filings_for_index(file).each(&blk)
59
+ end
60
+ end
61
+ rescue Net::FTPTempError
62
+ end
63
+
64
+ def self.filings_for_index(index)
65
+ [].tap do |filings|
66
+ content_section = false
67
+ index.each_line do |row|
68
+ content_section = true if row.include?('-------------')
69
+ next if !content_section || row.include?('------------')
70
+ filing = filing_for_index_row(row)
71
+ filings << filing unless filing.nil?
72
+ end
73
+ end
74
+ end
75
+
76
+ def self.filing_for_index_row(row)
77
+ data = row.split(/ /).reject(&:blank?).map(&:strip)
78
+ data = row.split(/ /).reject(&:blank?).map(&:strip) if data.count == 4
79
+ data[1].gsub!('/ADV', '')
80
+ data.delete_at(1) if data[1][0] == '/'
81
+ return nil unless Regexp.new(/\d{8}/).match(data[3])
82
+ unless data[4][0..3] == 'http'
83
+ data[4] = "http://www.sec.gov/Archives/#{ data[4] }"
84
+ end
85
+ Filing.new(
86
+ term: data[1],
87
+ cik: data[2],
88
+ date: Date.parse(data[3]),
89
+ link: data[4]
90
+ )
91
+ end
92
+
93
+ def self.uri_for_recent(start = 0, count = 100)
94
+ SecURI.browse_edgar_uri(
95
+ action: :getcurrent,
96
+ owner: :include,
97
+ output: :atom,
98
+ start: start,
99
+ count: count
100
+ )
101
+ end
102
+
103
+ def self.uri_for_cik(cik, start = 0, count = 100)
104
+ SecURI.browse_edgar_uri(
105
+ action: :getcompany,
106
+ owner: :include,
107
+ output: :atom,
108
+ start: start,
109
+ count: count,
110
+ CIK: cik
111
+ )
112
+ end
113
+
114
+ def self.parse_rss(rss, &blk)
115
+ feed = RSS::Parser.parse(rss, false)
116
+ feed.entries.each do |entry|
117
+ filing = Filing.new(
118
+ cik: entry.title.content.match(/\((\w{10})\)/)[1],
119
+ file_id: entry.id.content.split('=').last,
120
+ term: entry.category.term,
121
+ title: entry.title.content,
122
+ summary: entry.summary.content,
123
+ date: DateTime.parse(entry.updated.content.to_s),
124
+ link: entry.link.href.gsub('-index.htm', '.txt')
125
+ )
126
+ blk.call(filing)
127
+ end
128
+ end
129
+
130
+ # def self.find(cik, start = 0, count = 80)
131
+ # temp = {}
132
+ # temp[:url] = SecURI.browse_edgar_uri({cik: cik})
133
+ # temp[:url][:action] = :getcompany
134
+ # temp[:url][:start] = start
135
+ # temp[:url][:count] = count
136
+ # response = Entity.query(temp[:url].output_atom.to_s)
137
+ # document = Nokogiri::HTML(response)
138
+ # parse(cik, document)
139
+ # end
140
+
141
+ def self.parse(cik, document)
142
+ filings = []
143
+ if document.xpath('//content').to_s.length > 0
144
+ document.xpath('//content').each do |e|
145
+ if e.xpath('//content/accession-nunber').to_s.length > 0
146
+ content = Hash.from_xml(e.to_s)['content']
147
+ content[:cik] = cik
148
+ content[:file_id] = content.delete('accession_nunber')
149
+ content[:date] = content.delete('filing_date')
150
+ content[:link] = content.delete('filing_href')
151
+ content[:term] = content.delete('filing_type')
152
+ content[:title] = content.delete('form_name')
153
+ filings << Filing.new(content)
154
+ end
155
+ end
156
+ end
157
+ filings
158
+ end
159
+
160
+ def content(&error_blk)
161
+ @content ||= RestClient.get(self.link)
162
+ rescue RestClient::ResourceNotFound => e
163
+ puts "404 Resource Not Found: Bad link #{ self.link }"
164
+ if block_given?
165
+ error_blk.call(e, self)
166
+ else
167
+ raise e
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,216 @@
1
+ module SecEdgar
2
+ class FilingParser
3
+ attr_reader :xsd_uri
4
+
5
+ def initialize(filing)
6
+ @xsd_uri = 'lib/sec4/ownership4Document.xsd.xml'
7
+ @filing = filing
8
+ @content = filing.content
9
+ end
10
+
11
+ def content
12
+ if @content.start_with?('-----BEGIN PRIVACY-ENHANCED MESSAGE-----')
13
+ @content = strip_privacy_wrapper(@content)
14
+ end
15
+ @content
16
+ end
17
+
18
+ def doc
19
+ @doc ||= OwnershipDocument.new
20
+ end
21
+
22
+ def parse(&error_blk)
23
+ if block_given? && !xml_valid?
24
+ error_blk.call(xml_errors)
25
+ puts "Error: returning NilObjectDocument #{ @filing.link }"
26
+ return NilOwnershipDocument.new
27
+ end
28
+
29
+ footnotes | transactions | derivative_transactions # eager init
30
+ parse_doc(xml_doc)
31
+ parse_issuer(xml_doc.xpath('//issuer'))
32
+ parse_owner(xml_doc.xpath('//reportingOwner'))
33
+ parse_non_derivative_table(xml_doc.xpath('//nonDerivativeTable'))
34
+ parse_derivative_table(xml_doc.xpath('//derivativeTable'))
35
+ parse_footnotes(xml_doc.xpath('//footnotes'))
36
+ doc
37
+ end
38
+
39
+ def footnotes
40
+ doc.footnotes ||= []
41
+ end
42
+
43
+ def transactions
44
+ doc.transactions ||= []
45
+ end
46
+
47
+ def derivative_transactions
48
+ doc.derivative_transactions ||= []
49
+ end
50
+
51
+ def xml_filing
52
+ Nokogiri::XML(xml_doc.to_xml)
53
+ end
54
+
55
+ private
56
+
57
+ def strip_privacy_wrapper(raw)
58
+ lines = raw.split("\n")
59
+ lines.shift until lines.first.start_with?('<SEC-DOCUMENT>')
60
+ lines.pop if lines.last.start_with?('-----END PRIVACY-ENHANCED MESSAGE--')
61
+ lines.join("\n")
62
+ end
63
+
64
+ def parse_transaction(el)
65
+ transaction = Transaction.new
66
+ transaction.security_title = el.xpath('securityTitle').text.strip
67
+ transaction.transaction_date = Date.parse(el.xpath('transactionDate').text)
68
+
69
+ parse_transaction_amounts(transaction, el.xpath('transactionAmounts'))
70
+ parse_transaction_ownership_nature(transaction, el.xpath('ownershipNature'))
71
+ parse_transaction_coding(transaction, el.xpath('transactionCoding'))
72
+
73
+ # post transaction amounts
74
+ transaction.shares_after = el
75
+ .xpath('postTransactionAmounts/sharesOwnedFollowingTransaction/value')
76
+ .text
77
+ .to_f
78
+ transactions << transaction
79
+ end
80
+
81
+ def parse_footnote(el)
82
+ footnote = Footnote.new
83
+ footnote.content = el.text.strip
84
+ footnote.id = el.attribute("id").value
85
+ footnotes << footnote
86
+ end
87
+
88
+ def parse_derivative_transaction(el)
89
+ transaction = DerivativeTransaction.new
90
+
91
+ transaction.security_title = el.xpath('securityTitle').text.strip
92
+ transaction.transaction_date = Date.parse(el.xpath('transactionDate').text)
93
+ transaction.conversion_or_exercise_price = el.xpath('conversionOrExercisePrice').text.to_f
94
+
95
+ unless (expiration_date = el.xpath('expirationDate/value').text).blank?
96
+ transaction.expiration_date = Date.parse(expiration_date)
97
+ end
98
+
99
+ unless (exercise_date = el.xpath('exerciseDate/value').text).blank?
100
+ transaction.exercise_date = Date.parse(exercise_date)
101
+ end
102
+
103
+ parse_transaction_amounts(transaction, el.xpath('transactionAmounts'))
104
+ parse_transaction_ownership_nature(transaction, el.xpath('ownershipNature'))
105
+ parse_transaction_coding(transaction, el.xpath('transactionCoding'))
106
+ parse_transaction_underlying(transaction, el.xpath('underlyingSecurity'))
107
+
108
+ # post transaction amounts
109
+ transaction.shares_after = el
110
+ .xpath('postTransactionAmounts/sharesOwnedFollowingTransaction/value')
111
+ .text
112
+ .to_f
113
+ derivative_transactions << transaction
114
+ end
115
+
116
+ def parse_transaction_underlying(transaction, el)
117
+ transaction.underlying_security_title = el.xpath('underlyingSecurityTitle/value').text
118
+ transaction.underlying_security_shares =
119
+ el.xpath('underlyingSecurityShares/value').text.to_f
120
+ end
121
+
122
+ def parse_non_derivative_table(el)
123
+ el.xpath('//nonDerivativeTransaction').each do |transaction_el|
124
+ parse_transaction(transaction_el)
125
+ end
126
+ end
127
+
128
+ def parse_derivative_table(el)
129
+ el.xpath('//derivativeTransaction').each do |transaction_el|
130
+ parse_derivative_transaction(transaction_el)
131
+ end
132
+ end
133
+
134
+ def parse_footnotes(el)
135
+ el.xpath('//footnote').each do |note_el|
136
+ parse_footnote(note_el)
137
+ end
138
+ end
139
+
140
+ def parse_transaction_amounts(transaction, el)
141
+ transaction.acquired_or_disposed_code =
142
+ el.xpath('transactionAcquiredDisposedCode/value').text
143
+ transaction.shares =
144
+ el.xpath('transactionShares/value').text.to_f
145
+ transaction.price_per_share =
146
+ el.xpath('transactionPricePerShare/value').text.to_f
147
+ end
148
+
149
+ def parse_transaction_coding(transaction, el)
150
+ transaction.code = el.xpath('transactionCode').text
151
+ transaction.form_type = el.xpath('transactionFormType').text
152
+ transaction.equity_swap_involved = el.xpath('equitySwapInvolved').text == '1'
153
+ end
154
+
155
+ def parse_transaction_ownership_nature(transaction, el)
156
+ transaction.nature_of_ownership = el.xpath('natureOfOwnership/value').text
157
+ transaction.direct_or_indirect_code = el.xpath('directOrIndirectOwnership/value').text
158
+ end
159
+
160
+ def parse_doc(el)
161
+ doc.schema_version = el.xpath('//schemaVersion').text
162
+ doc.document_type = el.xpath('//documentType').text
163
+ doc.not_subject_to_section_16 =
164
+ (el.xpath('//notSubjectToSection16').text == '1' ||
165
+ el.xpath('//notSubjectToSection16').text == 'true')
166
+ doc.period_of_report = Date.parse(el.xpath('//periodOfReport').text)
167
+ rescue ArgumentError => e
168
+ puts "parse_doc error: #{ el.inspect }"
169
+ puts e
170
+ raise e
171
+ end
172
+
173
+ def parse_issuer(el)
174
+ doc.issuer_cik = el.xpath('//issuerCik').text
175
+ doc.issuer_name = el.xpath('//issuerName').text
176
+ doc.issuer_trading_symbol = el.xpath('//issuerTradingSymbol').text
177
+ end
178
+
179
+ def parse_owner(el)
180
+ doc.owner_cik = el.xpath('//rptOwnerCik').text
181
+ doc.owner_name = el.xpath('//rptOwnerName').text
182
+ doc.is_director = el.xpath('//isDirector').text == '1'
183
+ doc.is_ten_percent_owner = el.xpath('//isTenPercentOwner').text == '1'
184
+ doc.is_other = el.xpath('//isOther').text == '1'
185
+ doc.is_officer =
186
+ (el.xpath('//isOfficer').text == '1' ||
187
+ el.xpath('//isOfficer').text.downcase == 'true')
188
+ doc.officer_title = el.xpath('//officerTitle').text
189
+
190
+ address = Address.new
191
+ address.street1 = el.xpath('//rptOwnerStreet1').text
192
+ address.street2 = el.xpath('//rptOwnerStreet2').text
193
+ address.city = el.xpath('//rptOwnerCity').text
194
+ address.state = el.xpath('//rptOwnerState').text
195
+ address.zip = el.xpath('//rptOwnerZipCode').text
196
+ address.state_description = el.xpath('//rptOwnerStateDescription').text
197
+ doc.owner_address = address
198
+ end
199
+
200
+ def xml_doc
201
+ Nokogiri::XML(content).xpath('//ownershipDocument')
202
+ end
203
+
204
+ def xml_schema
205
+ @schema ||= Nokogiri::XML::Schema(IO.read(xsd_uri))
206
+ end
207
+
208
+ def xml_valid?
209
+ xml_errors.empty? && !content.blank?
210
+ end
211
+
212
+ def xml_errors
213
+ @errors ||= xml_schema.validate(xml_filing)
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,53 @@
1
+ # module SecEdgar
2
+ # class FilingPersister
3
+ # attr_reader :filing
4
+ #
5
+ # def initialize(filing)
6
+ # @filing = filing
7
+ # end
8
+ #
9
+ # def doc
10
+ # @doc ||= RawFiling.for_filing(filing).parsed
11
+ # end
12
+ #
13
+ # def persist!
14
+ # return form if form = Form4.find_by(link: filing.link)
15
+ # doc_json = doc.to_json
16
+ # return if doc_json == '{}'
17
+ #
18
+ # form = Form4.new(
19
+ # cik: filing.cik,
20
+ # title: filing.title,
21
+ # link: filing.link,
22
+ # term: filing.term,
23
+ # date: filing.date,
24
+ # file_id: filing.file_id,
25
+ # dollar_volume: doc.dollar_volume,
26
+ # document: { d: doc_json }
27
+ # )
28
+ # form.company = Company.where(cik: doc.issuer_cik).first_or_initialize
29
+ # form.company.update_attributes(
30
+ # name: doc.issuer_name,
31
+ # ticker: doc.issuer_trading_symbol.upcase
32
+ # )
33
+ #
34
+ # form.insider = Insider.where(cik: doc.owner_cik).first_or_initialize
35
+ # form.insider.update_attributes(
36
+ # name: doc.owner_name[0, 254]
37
+ # )
38
+ #
39
+ # form.day_traded_price = form.company.price_on(form.date)
40
+ # form.day_traded_volume = form.company.volume_on(form.date)
41
+ # form.plus_3_months_price = form.company.price_on(form.date + 3.months)
42
+ # form.plus_6_months_price = form.company.price_on(form.date + 6.months)
43
+ # form.plus_12_months_price = form.company.price_on(form.date + 12.months)
44
+ # # need to find a more detailed data source
45
+ # # form.price_to_earnings = Company.price_to_earnings_on(form.date)
46
+ # # form.price_to_book = Company.price_to_book_on(form.date)
47
+ #
48
+ # form.doc = doc
49
+ # form.save!
50
+ # form
51
+ # end
52
+ # end
53
+ # end
@@ -0,0 +1,52 @@
1
+ module SecEdgar
2
+ class FilingUpdater
3
+ attr_reader :form
4
+
5
+ def initialize(form)
6
+ @form = form
7
+ end
8
+
9
+ def doc
10
+ @doc ||= RawFiling.for_form(form).parsed
11
+ end
12
+
13
+ def update
14
+ doc_json = doc.to_json
15
+ return if doc_json == '{}'
16
+
17
+ form.update({
18
+ dollar_volume: doc.dollar_volume,
19
+ document: { d: doc_json }
20
+ })
21
+
22
+ unless form.company
23
+ form.company = Company.where(cik: doc.issuer_cik).first_or_initialize
24
+ form.company.update_attributes(
25
+ name: doc.issuer_name,
26
+ ticker: doc.issuer_trading_symbol.upcase
27
+ )
28
+ end
29
+
30
+ unless form.insider
31
+ form.insider = Insider.where(cik: doc.owner_cik).first_or_initialize
32
+ form.insider.update_attributes(
33
+ name: doc.owner_name[0, 254]
34
+ )
35
+ end
36
+
37
+ dt = Date.parse(form.date)
38
+ form.day_traded_price = form.company.price_on(dt)
39
+ form.day_traded_volume = form.company.volume_on(dt)
40
+ form.plus_3_months_price = form.company.price_on(dt + 3.months)
41
+ form.plus_6_months_price = form.company.price_on(dt + 6.months)
42
+ form.plus_12_months_price = form.company.price_on(dt + 12.months)
43
+ # need to find a more detailed data source
44
+ # form.price_to_earnings = Company.price_to_earnings_on(form.date)
45
+ # form.price_to_book = Company.price_to_book_on(form.date)
46
+
47
+ form.doc = doc
48
+ form.save!
49
+ form
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module SecEdgar
2
+ Footnote = Struct.new(:id, :content)
3
+ end
@@ -0,0 +1,35 @@
1
+ module SecEdgar
2
+ class FtpClient
3
+ include Singleton
4
+
5
+ def initialize
6
+ @ftp = Net::FTP.new
7
+ @ftp.passive = true
8
+ connect
9
+ login
10
+
11
+ at_exit do
12
+ puts "Closing Sec4::FtpClient..."
13
+ @ftp.close
14
+ end
15
+ end
16
+
17
+ def fetch(remote_url, local_url)
18
+ @ftp.getbinaryfile(remote_url, local_url)
19
+ end
20
+
21
+ def connect
22
+ @ftp.connect('ftp.sec.gov', 21)
23
+ rescue => e
24
+ puts "Sec4::FtpClient connection failed"
25
+ puts e.message
26
+ end
27
+
28
+ def login
29
+ @ftp.login
30
+ rescue => e
31
+ puts "Sec4::FtpClient login failed"
32
+ puts e.message
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ module Sec4
2
+ class NilOwnershipDocument
3
+ def document_type
4
+ '4'
5
+ end
6
+
7
+ def issuer_trading_symbol
8
+ 'NULL'
9
+ end
10
+
11
+ def is_director
12
+ false
13
+ end
14
+
15
+ def is_officer
16
+ false
17
+ end
18
+
19
+ def is_owner
20
+ false
21
+ end
22
+
23
+ def is_ten_percent_owner
24
+ false
25
+ end
26
+
27
+ def officer_title
28
+ ''
29
+ end
30
+
31
+ def transactions
32
+ []
33
+ end
34
+
35
+ def derivative_transactions
36
+ []
37
+ end
38
+ end
39
+ end