# Copyright 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::Series::Derived::Volume; use 5.008; use strict; use warnings; use Carp; use List::Util qw(min max); use Locale::TextDomain ('App-Chart'); use base 'App::Chart::Series::Indicator'; use constant DEBUG => 0; sub longname { __('Volume') } *shortname = \&longname; # FIXME # sub manual { __p('manual-node','Volume and Open Interest') } use constant { type => 'indicator', units => 'volume', priority => 10, minimum => 0, decimals => 0, # no decimals normally default_linestyle => 'Bars', }; sub new { my ($class, $parent) = @_; my $p = $parent->array('volumes') || croak "No volumes in series '",$parent->name,"'"; return $class->SUPER::new (parent => $parent, arrays => { values => $p }); } sub fill_part { my ($self, $lo, $hi) = @_; my $parent = $self->{'parent'}; $parent->fill_part ($lo, $hi); } # Return (LOWER UPPER) which is a suggested initial Y-axis page range to # show for dates LO to HI. This is for use with trading volumes and also # open-interest. # # The page size is meant to keep average to high volume days visible in the # window, but leave abnormally high days off the top of the screen so as not # to make the average ones come out tiny. # # The rule is to look for the 98% fractile, meaning a level which covers 98% # of the values in the LO to HI range, then an extra 1.5x that much. Making # UPPER just the 0.98 fractile alone would guarantee two or three points off # the top of the window even if they're only modestly bigger than the # average, hence going up to 1.5x that point. # # # ENHANCE-ME: If LO to HI is small then maybe the 98% should be reduced a # bit. If the width is less than 50 (or is it 25?) dates then 98% ends up # meaning every data point. # sub initial_range { my ($self, $lo, $hi) = @_; if (DEBUG) { print "Volume initial range\n"; } $lo = max ($lo, 0); $hi = max ($hi, 0); $self->fill ($lo, $hi); my $values = $self->values_array; my @v = grep{defined} @{$values}[$lo..$hi]; if (defined (my $symbol = $self->symbol)) { my $latest = App::Chart::Latest->get($symbol); my $timebase = $self->timebase; if (defined $latest->{'volume'} && defined (my $last_iso = $latest->{'last_date'})) { my $last_t = $timebase->from_iso_floor ($last_iso); if ($last_t >= $lo && $last_t <= $hi) { push @v, $latest->{'volume'}; } } } if (! @v) { return; # no non-undef values at all } # ENHANCE-ME: the top few values can probably be found without a full sort my $lastval = $v[-1]; @v = sort {$a <=> $b} @v; return (0, max (0, $lastval, # always fit last value $v[$#v * 0.98], # fractile 1.5 * List::Util::sum (@v) / scalar @v)); # 1.5*mean } 1; __END__ # =head1 NAME # # App::Chart::Series::Derived::Volume -- trading volume series # # =head1 SYNOPSIS # # my $vol = $series->Volume; # # =head1 CLASS HIERARCHY # # App::Chart::Series # App::Chart::Series::Derived::Volume # # =head1 DESCRIPTION # # A C series picks out just the volumes # from a series. Usually this will be a database series # (C), but anything with a C array can # be used. A Volumes series allows those volume values to be passed into # other series calculations or displays. # # =head1 SEE ALSO # # L, L # # =cut