SEDI “is Canada’s on-line, browser-based service for the filing and viewing of insider reports as required by various provincial securities rules and regulations.”
The Canadian requirement for insiders to report transactions and holdings in publicly traded companies is governed by REGULATION 55-104 RESPECTING INSIDER REPORTING REQUIREMENTS AND EXEMPTIONS.
Experimenting with the SEDI site and attempting to extract a complete listing of “reporting insiders” for select Canadian Financial Institutions
, I encountered the following difficulty when selecting “A” through “Z” for a given issuer:
It seems that it is necessary to restrict queries (“narrow you search”) to each letter of the alphabet in order to draw reports one-by-one. This would be a slow and tedious process to get at the data in aggregate!
The solution (again!) is to apply a combination of Watir and Nokogiri through the following Ruby script. WATIR drives the browser and Nokogiri for parsing (lightening fast!!) the data on the pages for creating the the following select “insider” lists:
- Bank of Montreal
- Royal Bank of Canada
- Toronto Dominion Bank
- Bank of Nova Scotia
- Canadian Imperial Bank of Commerce
- National Bank of Canada
Now the analytics can begin!
require 'rubygems' require 'watir-webdriver' require 'nokogiri' ary_of_members = Array.new rows = Array.new ### Defines Array Class for HTML Table output ### class Array def to_cells(tag) self.map { |c| "<#{tag}>#{c}</#{tag}>" }.join end end
file = File.open('./SEDI_Insiders.html' browser.select_list(:name=>'ALPHA_RANGE_FROM').option(:text=>letter).select browser.select_list(:name=>'ALPHA_RANGE_TO').option(:text=>letter).select browser.input(:name=>'Search').click if browser.text.include? "Error: The system found no results matching the selected search criteria combination." else browser.table(:xpath, '/html/body/table[1]/tbody/tr[3]/td/table/tbody/tr/td/table[9]')[0][0].link.click doc = Nokogiri::HTML.parse(browser.html) i = 0 t = 0 doc.xpath("//table").each do |table| if doc.at_xpath("//table[#{i}]//tr//td[1]").to_s =~ /Insider Name/ insider_name = doc.at_xpath("//table[#{i}]//tr//td[2]").text.strip #Insider Name ceased_to_b_insider = doc.at_xpath("//table[#{i+2}]//tr//td[2]").text.strip #Ceased to be Insider? ceased_to_b_insider.gsub!(/Not Applicable/,'') until doc.at_xpath("//table[#{i+4+t}]//tr/td[2]").to_s !~ /\d{4}-\d{2}-\d{2}/ date = doc.at_xpath("//table[#{i+4+t}]//tr/td[2]").text.strip share_class = doc.at_xpath("//table[#{i+4+t}]//tr/td[3]").text.strip #Share Class reg_holder = doc.at_xpath("//table[#{i+4+t}]//tr/td[4]").text.strip #Reg. Holder nbr_shares = doc.at_xpath("//table[#{i+4+t}]//tr/td[5]").text.strip #Nbr or Shares #'cleans' HTML if reg_holder !~ /\w+/ reg_holder = () end rows << {"Insider Name" => insider_name, "Ceased Insider?" => ceased_to_b_insider, "Date" => date, "Share Class" => share_class, "Registered Holder" => reg_holder, "Nbr of Shares" => nbr_shares} #Insider Name & Ceased to be Insider - just once insider_name = () ceased_to_b_insider = () t+=2 end t = 0 end i+=1 end end end ### Rolls HTML Table output ### headers = "<tr>#{rows[0].keys.to_cells('th')}</tr>" cells = rows.map do |row| "<tr>#{row.values.to_cells('td')}</tr>" end.join("\n ") table = "<table border=\"1\"> #{headers} #{cells} </table>" file.puts table, "w") browser = Watir::Browser.new :chrome ('A'..'Z').each do |letter| browser.goto 'https://www.sedi.ca/sedi/SVTReportsAccessController?menukey=15.03.00&locale=en_CA' browser.radio(:value => 'SVTIIBIselectIssuer').set browser.input(:name=>'Next').click browser.text_field(:name=>'ISSUER_SEARCH_VALUE').set "Bank of Montreal"