File Coverage

blib/lib/App/Chart/Series/Derived/VWMA.pm
Criterion Covered Total %
statement 23 23 100.0
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 31 31 100.0


line stmt bran cond sub pod time code
1             # Copyright 2008, 2009, 2010 Kevin Ryde
2              
3             # This file is part of Chart.
4             #
5             # Chart is free software; you can redistribute it and/or modify it under the
6             # terms of the GNU General Public License as published by the Free Software
7             # Foundation; either version 3, or (at your option) any later version.
8             #
9             # Chart is distributed in the hope that it will be useful, but WITHOUT ANY
10             # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11             # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12             # details.
13             #
14             # You should have received a copy of the GNU General Public License along
15             # with Chart. If not, see <http://www.gnu.org/licenses/>.
16              
17             package App::Chart::Series::Derived::VWMA;
18 1     1   24415 use 5.010;
  1         4  
19 1     1   5 use strict;
  1         18  
  1         18  
20 1     1   4 use warnings;
  1         2  
  1         26  
21 1     1   5 use Carp;
  1         2  
  1         46  
22 1     1   6 use List::Util qw(min max);
  1         2  
  1         78  
23 1     1   5 use Locale::TextDomain ('App-Chart');
  1         2  
  1         8  
24 1     1   458 use Math::Trig ();
  1         9496  
  1         26  
25              
26 1     1   8 use base 'App::Chart::Series::Indicator';
  1         2  
  1         388  
27              
28             use constant DEBUG => 0;
29              
30             sub longname { __('VWMA - Volume Weighted') }
31             sub shortname { __('VWMA') }
32             sub manual { __p('manual-node','Volume Weighted Moving Average') }
33              
34             use constant
35             { type => 'average',
36             parameter_info => [ { name => __('Days'),
37             key => 'vwma_days',
38             type => 'integer',
39             minimum => 1,
40             default => 20 } ],
41             };
42              
43             sub new {
44             if (DEBUG >= 2) { require Data::Dumper;
45             print "VWMA->new ",Data::Dumper::Dumper(\@_); }
46             my ($class, $parent, $N) = @_;
47              
48             $parent->array('volumes')
49             or croak "No volumes in series '",$parent->name,"'";
50              
51             $N //= parameter_info()->[0]->{'default'};
52             ($N > 0) || croak "VWMA bad N: $N";
53              
54             return $class->SUPER::new
55             (parent => $parent,
56             N => $N,
57             parameters => [ $N ],
58             arrays => { values => [] },
59             array_aliases => { });
60             }
61              
62             sub fill_part {
63             my ($self, $lo, $hi) = @_;
64             my $parent = $self->{'parent'};
65             my $N = $self->{'N'};
66              
67             my $start = $parent->find_before ($lo, $N-1);
68             $parent->fill ($start, $hi);
69             my $s = $self->values_array;
70             my $pp = $parent->values_array;
71             my $pv = $parent->array('volumes');
72              
73             $hi = min ($hi, $#$pp);
74             if ($#$s < $hi) { $#$s = $hi; } # pre-extend
75              
76             # $a[0] is the newest point, $a[1] the prev, through to $a[$N-1]
77             my @a;
78             my @v;
79              
80             my $total_volume = 0;
81             my $total_prod = 0;
82             my $pos = 0;
83             foreach my $i ($start .. $lo-1) {
84             my $value = $pp->[$i] // next;
85             my $volume = $pv->[$i] // 0;
86             $total_volume += ($v[$pos] = $volume);
87             $total_prod += ($a[$pos++] = $value * $volume);
88             }
89             my $count = $pos;
90             if (DEBUG) { print " warmed to total_volume=$total_volume count=$count\n";
91             require Data::Dumper;
92             print Data::Dumper->Dump([\@v,\@a],['v','a']); }
93              
94             foreach my $i ($lo .. $hi) {
95             my $value = $pp->[$i] // next;
96             my $volume = $pv->[$i] // 0;
97              
98             if ($count < $N) {
99             $count++;
100             } else {
101             # drop old
102             $total_volume -= $v[$pos];
103             $total_prod -= $a[$pos];
104             }
105             # add new
106             $total_volume += ($v[$pos] = $volume);
107             $total_prod += ($a[$pos++] = $value * $volume);
108             if ($pos >= $N) { $pos = 0; }
109              
110             if ($total_volume != 0) {
111             $s->[$i] = $total_prod / $total_volume;
112             }
113             }
114             }
115              
116             1;
117             __END__
118              
119             # =head1 NAME
120             #
121             # App::Chart::Series::Derived::VWMA -- volume weighted moving average
122             #
123             # =head1 SYNOPSIS
124             #
125             # my $series = $parent->VWMA($N);
126             #
127             # =head1 DESCRIPTION
128             #
129             # ...
130             #
131             # =head1 SEE ALSO
132             #
133             # L<App::Chart::Series>, L<App::Chart::Series::Derived::Volume>
134             #
135             # =cut