# Copyright 2007, 2008, 2009, 2010, 2011 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::Series::Database;
use 5.010;
use strict;
use warnings;
use Carp;
use Scalar::Util;
use App::Chart::Database;
use App::Chart::DBI;
use base 'App::Chart::Series::OHLCVI';
our $VERSION = 246;
use constant DEBUG => 0;
# %cache is keyed by symbol string, with value a App::Chart::Series::Database
# object. The value reference is weakened so it becomes undef when
# otherwise unused.
#
our %cache = ();
sub _purge_cache_on_data_changed {
my ($symbol_hash) = @_;
if (DEBUG) {
print "data-changed, purge series: ",
join (', ', grep {exists $cache{$_}} keys %$symbol_hash),"\n";
}
delete @cache{keys %$symbol_hash}; # hash slice
}
use constant::defer _init_cache => sub {
App::Chart::chart_dirbroadcast()->connect_first
('data-changed', \&_purge_cache_on_data_changed);
return;
};
sub new {
my ($class, $symbol) = @_;
if (DEBUG) { print "Series new $symbol\n"; }
my $self = $cache{$symbol};
if ($self) { return $self; }
my $base = App::Chart::DBI->read_single
('SELECT date FROM daily WHERE symbol=? ORDER BY date ASC LIMIT 1',
$symbol);
if (! $base) {
require App::Chart::TZ;
my $timezone = App::Chart::TZ->for_symbol ($symbol);
$base = $timezone->iso_date;
}
if (DEBUG) { print " base $base\n"; }
require App::Chart::Timebase::Days;
my $timebase = App::Chart::Timebase::Days->new_from_iso ($base);
$self = $class->SUPER::new (symbol => $symbol,
timebase => $timebase);
# lose any cache entries which have gone undef through weaks destroyed
delete @cache{grep {! $cache{$_}} keys %cache};
# add new entry
_init_cache();
$cache{$symbol} = $self;
Scalar::Util::weaken ($cache{$symbol});
return $self;
}
sub hi {
my ($self) = @_;
if (! exists $self->{'hi'}) {
if (DEBUG) { print "Series hi for $self->{'symbol'}\n"; }
my $date = App::Chart::DBI->read_single
('SELECT date FROM daily WHERE symbol=? ORDER BY date DESC LIMIT 1',
$self->{'symbol'});
if (DEBUG) { print " iso ",$date//'undef',"\n"; }
my $timebase = $self->{'timebase'};
$self->{'hi'} = ($date
? $timebase->from_iso_floor ($date)
: 0);
if (DEBUG) { print " hi=$self->{'hi'}\n"; }
}
return $self->{'hi'};
}
sub fill_part {
my ($self, $lo, $hi) = @_;
if (DEBUG) { print "Database $self->{'symbol'} fill_part $lo $hi\n"; }
my $dbh = App::Chart::DBI->instance;
my $timebase = $self->{'timebase'};
# date descending so first store pre-extends the respective arrays
my $sth = $dbh->prepare_cached
('SELECT date, open, high, low, close, volume, openint
FROM daily WHERE (symbol=? AND (date BETWEEN ? AND ?))
ORDER BY date DESC');
my $aref = $dbh->selectall_arrayref ($sth, undef,
$self->{'symbol'},
$timebase->to_iso ($lo),
$timebase->to_iso ($hi));
$sth->finish;
my $opens = $self->array('opens');
my $highs = $self->array('highs');
my $lows = $self->array('lows');
my $closes = $self->array('closes');
my $volumes = $self->array('volumes');
my $openints = $self->array('openints');
foreach my $row (@$aref) {
my $i = $timebase->from_iso_floor ($row->[0]);
next if ($i < 0);
if (defined $row->[1]) { $opens->[$i] = $row->[1]; }
if (defined $row->[2]) { $highs->[$i] = $row->[2]; }
if (defined $row->[3]) { $lows->[$i] = $row->[3]; }
if (defined $row->[4]) { $closes->[$i] = $row->[4]; }
if (defined $row->[5]) { $volumes->[$i] = $row->[5]; }
if (defined $row->[6]) { $openints->[$i] = $row->[6]; }
}
}
sub name {
my ($self) = @_;
return $self->symbol; # in Series.pm
}
# {
# $self->{'symbol'}
#
# if (! exists $self->{'name'}) {
# $self->{'name'} = App::Chart::Database->symbol_name ($self->{'symbol'});
# }
# return $self->{'name'}
# }
sub symbol_name {
my ($self) = @_;
return App::Chart::Database->symbol_name ($self->{'symbol'});
}
sub decimals {
my ($self) = @_;
if (! exists $self->{'decimals'}) {
$self->{'decimals'}
= App::Chart::Database->symbol_decimals ($self->{'symbol'});
}
return $self->{'decimals'};
}
sub dividends {
my ($self) = @_;
return ($self->{'dividends'} ||= do {
my $dbh = App::Chart::DBI->instance;
my $sth = $dbh->prepare_cached
('SELECT ex_date, type, amount, imputation, qualifier, note
FROM dividend WHERE symbol=? ORDER BY ex_date ASC');
my $aref = $dbh->selectall_arrayref ($sth, {Slice=>{}}, $self->{'symbol'});
my $timebase = $self->{'timebase'};
foreach my $div (@$aref) {
foreach my $date (qw(ex_date record_date pay_date)) {
my $iso = $div->{$date} or next;
my $t = $timebase->from_iso_floor ($iso);
$div->{$date.'_t'} = $t;
}
}
$aref
});
}
sub splits {
my ($self) = @_;
return ($self->{'splits'} ||= do {
my $dbh = App::Chart::DBI->instance;
my $sth = $dbh->prepare_cached
('SELECT date, new, old, note
FROM split WHERE symbol=? ORDER BY date ASC');
my $aref = $dbh->selectall_arrayref ($sth, {Slice=>{}}, $self->{'symbol'});
my $timebase = $self->{'timebase'};
foreach my $div (@$aref) {
my $iso = $div->{'date'} or next;
my $t = $timebase->from_iso_floor ($iso);
$div->{'date_t'} = $t;
}
$aref
});
}
sub annotations {
my ($self) = @_;
return ($self->{'annotations'} ||= do {
if (DEBUG) { print "Series::Database read annotations ",
$self->{'symbol'},"\n"; }
my $dbh = App::Chart::DBI->instance;
my $sth = $dbh->prepare_cached
('SELECT id, date, note FROM annotation
WHERE symbol=? ORDER BY date ASC');
my $aref = $dbh->selectall_arrayref ($sth, {Slice=>{}}, $self->{'symbol'});
my $timebase = $self->{'timebase'};
foreach my $ann (@$aref) {
my $iso = $ann->{'date'} or next;
my $t = $timebase->from_iso_floor ($iso);
$ann->{'date_t'} = $t;
}
$aref
});
}
sub Alerts_arrayref {
my ($series) = @_;
return ($series->{__PACKAGE__.'.array'} ||= do {
my $symbol = $series->symbol || '';
require App::Chart::DBI;
my $dbh = App::Chart::DBI->instance;
my $sth = $dbh->prepare_cached ('SELECT * FROM alert WHERE symbol=?');
my $aref = $dbh->selectall_arrayref ($sth, {Slice=>{}}, $symbol);
$sth->finish;
if (@$aref) {
require App::Chart::Annotation;
foreach my $elem (@$aref) {
bless $elem, 'App::Chart::Annotation::Alert';
}
}
$aref;
});
}
1;
__END__
=for stopwords OHLCVI
=head1 NAME
App::Chart::Series::Database -- symbol data series from database
=head1 SYNOPSIS
use App::Chart::Series::Database;
my $series = App::Chart::Series::Database->new ('BHP.AX');
=head1 CLASS HIERARCHY
App::Chart::Series
App::Chart::Series::OHLCVI
App::Chart::Series::Database
=head1 FUNCTIONS
=over 4
=item C<< $series = App::Chart::Series::Database->new ($symbol) >>
Return a series object which is the OHLCVI data for C<$symbol> from the
database.
If the database changes then C should be called again to get a new
object with new values, date range, etc.
=back
=head1 SEE ALSO
L
=head1 HOME PAGE
L
=head1 LICENCE
Copyright 2008, 2009, 2010, 2011 Kevin Ryde
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; see the file F. Failing that, see
L.
=cut