# Bendigo Stock Exchange (BSX) setups. # Copyright 2005, 2006, 2007, 2008, 2009, 2010 Kevin Ryde # This file is part of Chart. # # Chart is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software # Foundation; either version 3, or (at your option) any later version. # # Chart is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with Chart. If not, see . package App::Chart::Suffix::BEN; use strict; use warnings; use Locale::TextDomain ('App-Chart'); use App::Chart; use App::Chart::Sympred; use App::Chart::TZ; use App::Chart::Weblink; # Victoria is the same as NSW these days, but let's not assume that rare # outbreak of interstate cooperation will be permanent (in the past the # dates to begin or end daylight savings have varied). # my $timezone_bendigo = App::Chart::TZ->new (name => __('Bendigo'), choose => [ 'Australia/Melbourne' ], fallback => 'EST-10'); my $pred = App::Chart::Sympred::Suffix->new ('.BEN'); $timezone_bendigo->setup_for_symbol ($pred); # App::Chart::setup_source_help # ($pred, __p('manual-node','Bendigo Stock Exchange')); #------------------------------------------------------------------------------ # weblink - company info App::Chart::Weblink->new (pred => $pred, name => __('BSX _Company Information'), desc => __('Open web browser at the Bendigo Stock Exchange information page for this company'), proc => sub { my ($symbol) = @_; return 'http://www.bsx.com.au/markets_pricesresearch_com.asp?security=' . secode_for_symbol ($symbol); }); # weblink_message __('Fetching BSX symbol codes ...') #----------------------------------------------------------------------------- # security codes # # The home page # # http://www.bsx.com.au # # has a pull-down menu of symbols, with the code numbers to be used for # quotes and trade history. # # The cache here saves the code numbers for one day (the current trading # day, bendigo time). This ensures we don't download every time, but still # have a fresh set of codes, in case there's additions or perhaps changes. # # Thought was given to a more sophisticated caching scheme, for instance use # an old code and if the quote turns out to be for something else and the # cache isn't fresh then refill it and get the quote again. But for the # data download it'd be a case of checking the name against the database # name, since there's no symbol on that page, except that for the initial # download when there's no database name yet the cache would have to be # pre-freshened. This seems unnecessarily complicated to merely avoid # downloading one page, especially when it's compressed to about 3kbytes if # we've got gzip or zlib. # return option code string, or undef if unknown sub secode_for_symbol { my ($symbol) = @_; require App::Chart::Pagebits; my $h = App::Chart::Pagebits::get (name => __('BSX stock numbers'), url => 'http://www.bsx.com.au', key => 'bendigo-codes', freq_days => 1, timezone => $timezone_bendigo, parse => \&secodes_parse); return $h->{$symbol}; } # return hash reference { $symbol => $code, ... } sub secodes_parse { my ($content) = @_; my %hash; while ($content =~ m{}g) { $hash{"$2.BEN"} = $1; } return \%hash; } 1; __END__ #------------------------------------------------------------------------------ # This uses the quotes pages like # # http://www.bsx.com.au/markets_pricesresearch_pri.asp?security=20 # # with the security code number from the symbols menu on the home page. # # www.bsx.com.au sends gzipped data, which is good since it compresses # about 19kbytes of javascript junk down to about 3kbytes sent. # # The trade history pages like # # http://www.bsx.com.au/markets_pricesresearch_tra.asp?security=1 # # also have a current bid/offer and show the date/time of the last trade. # Not sure if there's much value showing open/high/low/volume from trading # that could be weeks ago. # return list (ADATE TIME) for latest quote # # the BSX system takes changes only during 9:30am-11:30am weekdays (order # entry 9:30 to 11, then trading 11 to 11:30), so outside those hours lock # to 11:30am # # http://www.bsx.com.au/markets_aboutbsxmarkets_tra.asp # sub quote_date_time { # (tm->adate-time-within (localtime (current-time) (timezone-bendigo)) # #,(hms->seconds 9 30 0) # #,(hms->seconds 11 30 0))) } sub bendigo_quote { sub quote_parse { my ($resp) = @_; my $content = $resp->decoded_content (raise_error => 1); my @data = (); my $h = { source => __PACKAGE__, resp => $resp, date_format => 'dmy', data => \@data }; require HTML::TableExtract; my $te = HTML::TableExtract->new (headers => ['code', 'bid', 'offer', 'last'], keep_html => 1); $te->parse($content); my ($ts) = $te->tables || die 'BSX: quote columns not found'; $te = HTML::TableExtract->new (depth => $ts->depth, count => $ts->count + 1); $te->tables || die 'BSX: reparse table not found'; # (receive-list (quote-adate quote-time) # (bendigo-quote-adate-time) # # eg:

Capilano Honey Limited
TRADING HALT

# (m (regexp-exec #,(regexp "Blue.*.*red[^>]*>([^<]+)" regexp/icase regexp/newline) body)) # (note (and m (match:substring m 1)))) foreach my $row ($te->rows) { my ($code, $bid, $offer, $last) = @$row; $code =~ /(.*)\(([^)]+)\)/ or next; my $name = $1; my $symbol = "$2.BEN"; push @data, { symbol => $symbol, name => $name, bid => $bid, offer => $offer, last => $last }; #:quote-adate quote-adate #:quote-time quote-time #:note note } } (define (bendigo-latest-get symbol-list extra-list proc) (for-each (lambda (symbol) (define option (symbol->option symbol)) (if (not option) (proc (list (latest-new-unknown symbol))) (receive (headers body) (http-request (string-append "http://www.bsx.com.au/markets_pricesresearch_pri.asp?security=" option) #:want-ok #t) (proc (body->latest-list body))))) symbol-list)) (latest-handler! #:selector bendigo-symbol? #:handler bendigo-latest-get #:adate-time bendigo-quote-adate-time) ;;----------------------------------------------------------------------------- ;; download ;; ;; This uses the "view all" trade history pages like ;; ;; http://www.bsx.com.au/markets_pricesresearch_tra_popup.asp?security=20 ;; ;; with the security number from the home page menu (cached above). ;; ;; This history has each trade individually, but we collapse that to daily ;; open/high/low/close. ;; Eg: ;; Capilano Honey Limited
;;
Trade History

;; Capilano Honey Limited
XD
;;
Trade History

;; ;; Or when halted: ;; Capilano Honey Limited
TRADING HALT

;;
Trade History

;; ;; match 1 is company name ;; (define history-regexp "([^<]+)[^\n]*\n[^\n]*
Trade History") (define (bendigo-process-download symbol url headers body) (define commodity (chart-symbol-sans-dot symbol)) (let* ((m (must-match (string-match history-regexp body))) (name (match:substring m 1)) (row-list (html-table-rows body ;; skip empty table, the second is wanted (string-contains-after-ci body "tdate date) #:commodity commodity #:sessions (reverse! (map second row-list)) #:volume (apply + (map! string->number-err (map first row-list)))))) (partition-equal-adjacent third row-list))))) ;; latest download data available ;; guess today available from 6pm ;; (define (bendigo-available-tdate) (tdate-today-after 18 0 (timezone-melbourne))) (define (bendigo-download symbol-list) (for-each (lambda (symbol) (download-status (_ "BSX") (_ "data") symbol) (let ((option (symbol->option symbol))) (if (not option) (download-message (string-append (_ "BSX") ": " (_ "unknown symbol") " " symbol)) (let ((url (string-append "http://www.bsx.com.au/markets_pricesresearch_tra_popup.asp?security=" option))) (receive (headers body) (http-request url #:want-ok #t) (bendigo-process-download symbol url headers body)))))) symbol-list)) (define (bendigo-backto want-tdate symbol-list) (download-message (string-append (_ "BSX") ": " (_ "no further historical data available")))) (download-handler! #:selector bendigo-symbol? #:update bendigo-download #:avail-proc bendigo-available-tdate #:backto bendigo-backto) 1; __END__