File Coverage

blib/lib/App/Chart/Series/Derived/LaguerreFilter.pm
Criterion Covered Total %
statement 20 20 100.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 27 27 100.0


line stmt bran cond sub pod time code
1             # Copyright 2007, 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::LaguerreFilter;
18 1     1   355 use 5.010;
  1         3  
19 1     1   4 use strict;
  1         2  
  1         17  
20 1     1   4 use warnings;
  1         2  
  1         19  
21 1     1   4 use Carp;
  1         1  
  1         45  
22 1     1   5 use List::Util qw(min max);
  1         1  
  1         64  
23 1     1   304 use Locale::TextDomain ('App-Chart');
  1         18191  
  1         5  
24              
25 1     1   5294 use base 'App::Chart::Series::Indicator';
  1         3  
  1         305  
26             use App::Chart::Series::Calculation;
27             use App::Chart::Series::Derived::EMA;
28             use App::Chart::Series::Derived::EMAx2;
29              
30             # http://www.mesasoftware.com/technicalpapers.htm
31             # http://www.mesasoftware.com/Papers/TIME%20WARP.pdf
32             # Paper by John Elhers.
33             #
34             # http://www.mesasoftware.com/seminars.htm
35             # http://www.mesasoftware.com/Seminars/TradeStation%20World%2005.pdf
36             # http://www.mesasoftware.com/Seminars/Seminars/TSWorld05.ppt
37             # (View the powerpoint with google.)
38             # Summary by John Ehlers of several of his and other averages.
39             # * A Laguerre filter warps time in the filter coefficients
40             # - Enables extreme smoothing with just a few filter terms
41             # * A NonLinear Laguerre filter measures the difference between the
42             # current price and the last computed filter output.
43             # - Objective is to drive this "error" to zero
44             # - The "error", normalized to the error range over a selected period
45             # is the alpha of the Laguerre filter
46             #
47              
48              
49             sub longname { __('Laguerre Filter') }
50             sub shortname { __('Laguerre') }
51             sub manual { __p('manual-node','Laguerre Filter') }
52              
53             use constant
54             { type => 'average',
55             parameter_info => [ { name => __('Alpha'),
56             key => 'laguerre_filter_alpha',
57             type => 'float',
58             minimum => 0,
59             maximum => 1,
60             default => 0.2,
61             decimals => 2,
62             step => 0.1 } ],
63             };
64              
65             sub new {
66             my ($class, $parent, $alpha) = @_;
67              
68             $alpha //= parameter_info()->[0]->{'default'};
69             ($alpha >= 0 && $alpha <= 1.0) || croak "Laguerre Filter bad alpha: $alpha";
70              
71             return $class->SUPER::new
72             (parent => $parent,
73             parameters => [ $alpha ],
74             arrays => { values => [] },
75             array_aliases => { });
76             }
77              
78             sub proc {
79             my ($class, $alpha) = @_;
80             $alpha = max (0.00001, min (0.99999, $alpha));
81             my $proc_for_alpha = $class->proc_for_alpha();
82             return sub {
83             my ($value) = @_;
84             return $proc_for_alpha->($value, $alpha);
85             };
86             }
87              
88             sub proc_for_alpha {
89             my ($class) = @_;
90              
91             my $a_sum = 0;
92             my $a_div = 0;
93             my ($a_prev, $a_pdiv);
94              
95             my $b_sum = 0;
96             my $b_div = 0;
97             my ($b_prev, $b_pdiv);
98              
99             my $c_sum = 0;
100             my $c_div = 0;
101             my ($c_prev, $c_pdiv);
102              
103             my $d_sum = 0;
104             my $d_div = 0;
105              
106             return sub {
107             my ($value, $alpha) = @_;
108             my $f = 1 - $alpha;
109              
110             $a_prev = $a_sum;
111             $a_pdiv = $a_div;
112             $a_sum = $value * $alpha + $a_sum * $f;
113             $a_div = $alpha + $a_div * $f;
114              
115             $b_prev = $b_sum;
116             $b_pdiv = $b_div;
117             $b_sum = $a_prev + ($b_sum - $a_sum) * $f;
118             $b_div = $a_pdiv + ($b_div - $a_div) * $f;
119              
120             $c_prev = $c_sum;
121             $c_pdiv = $c_div;
122             $c_sum = $b_prev + ($c_sum - $b_sum) * $f;
123             $c_div = $b_pdiv + ($c_div - $b_div) * $f;
124              
125             $d_sum = $c_prev + ($d_sum - $c_sum) * $f;
126             $d_div = $c_pdiv + ($d_div - $c_div) * $f;
127              
128             return ($a_sum/$a_div
129             + 2 * $b_sum/$b_div
130             + 2 * $c_sum/$c_div
131             + $d_sum/$d_div) / 6.0;
132             };
133             }
134              
135             # warmup_count() gives a fixed amount, based on the worst-case EMA alphas
136             # all the slowest possible. It ends up being 1656 which is hugely more than
137             # needed in practice.
138             #
139             # warmup_count_for_position() calculates a value on actual data, working
140             # backwards. In practice it's as little as about 100.
141             #
142             sub warmup_count {
143             my ($self_or_class, $alpha) = @_;
144              
145             $alpha = max (0.00001, min (0.99999, $alpha));
146             my $f = 1 - $alpha;
147              
148             return App::Chart::Series::Derived::EMAx2::bsearch_first_true
149             (sub {
150             my ($i) = @_;
151             return (laguerre_omitted($f,$i)
152             <= App::Chart::Series::Derived::EMA::WARMUP_OMITTED_FRACTION);
153             },
154             App::Chart::Series::Derived::EMA::alpha_to_N($alpha));
155             }
156              
157             # see devel/ema-omitted.pl
158             sub laguerre_omitted {
159             my ($f, $k) = @_;
160             return
161             $f ** ($k-2)
162             * ((1/36*$k**2 + -1/36)*$k
163             + ($f # f^($k-1)
164             * ((1/4*$k + 1/4)*$k
165             + ($f # f^$k
166             * ((((-1/12*$k + -1/6)*$k + 3/4)*$k + 5/6)
167             + ($f # f^($k+1)
168             * (((-1/2*$k + -1)*$k + 1/2)
169             + ($f # f^($k+2)
170             * ((((1/12*$k + 1/3)*$k + -5/12)*$k + -2/3)
171             + ($f # f^($k+3)
172             * (((1/4*$k + 3/4)*$k + 1/2)
173             + ($f # f^($k+4)
174             * ((((-1/36*$k + -1/6)*$k + -11/36)*$k + -1/6)
175             )))))))))))));
176             }
177              
178             1;
179             __END__
180              
181             # =head1 NAME
182             #
183             # App::Chart::Series::Derived::LaguerreFilter -- Laguerre Filter moving average
184             #
185             # =head1 SYNOPSIS
186             #
187             # my $series = $parent->LaguerreFilter($alpha);
188             #
189             # =head1 DESCRIPTION
190             #
191             # ...
192             #
193             # =head1 SEE ALSO
194             #
195             # L<App::Chart::Series>, L<App::Chart::Series::Derived::EMA>
196             #
197             # =cut