# Copyright 2007, 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::LaguerreFilter; use 5.010; use strict; use warnings; use Carp; use List::Util qw(min max); use Locale::TextDomain ('App-Chart'); use base 'App::Chart::Series::Indicator'; use App::Chart::Series::Calculation; use App::Chart::Series::Derived::EMA; use App::Chart::Series::Derived::EMAx2; # http://www.mesasoftware.com/technicalpapers.htm # http://www.mesasoftware.com/Papers/TIME%20WARP.pdf # Paper by John Elhers. # # http://www.mesasoftware.com/seminars.htm # http://www.mesasoftware.com/Seminars/TradeStation%20World%2005.pdf # http://www.mesasoftware.com/Seminars/Seminars/TSWorld05.ppt # (View the powerpoint with google.) # Summary by John Ehlers of several of his and other averages. # * A Laguerre filter warps time in the filter coefficients # - Enables extreme smoothing with just a few filter terms # * A NonLinear Laguerre filter measures the difference between the # current price and the last computed filter output. # - Objective is to drive this "error" to zero # - The "error", normalized to the error range over a selected period # is the alpha of the Laguerre filter # sub longname { __('Laguerre Filter') } sub shortname { __('Laguerre') } sub manual { __p('manual-node','Laguerre Filter') } use constant { type => 'average', parameter_info => [ { name => __('Alpha'), key => 'laguerre_filter_alpha', type => 'float', minimum => 0, maximum => 1, default => 0.2, decimals => 2, step => 0.1 } ], }; sub new { my ($class, $parent, $alpha) = @_; $alpha //= parameter_info()->[0]->{'default'}; ($alpha >= 0 && $alpha <= 1.0) || croak "Laguerre Filter bad alpha: $alpha"; return $class->SUPER::new (parent => $parent, parameters => [ $alpha ], arrays => { values => [] }, array_aliases => { }); } sub proc { my ($class, $alpha) = @_; $alpha = max (0.00001, min (0.99999, $alpha)); my $proc_for_alpha = $class->proc_for_alpha(); return sub { my ($value) = @_; return $proc_for_alpha->($value, $alpha); }; } sub proc_for_alpha { my ($class) = @_; my $a_sum = 0; my $a_div = 0; my ($a_prev, $a_pdiv); my $b_sum = 0; my $b_div = 0; my ($b_prev, $b_pdiv); my $c_sum = 0; my $c_div = 0; my ($c_prev, $c_pdiv); my $d_sum = 0; my $d_div = 0; return sub { my ($value, $alpha) = @_; my $f = 1 - $alpha; $a_prev = $a_sum; $a_pdiv = $a_div; $a_sum = $value * $alpha + $a_sum * $f; $a_div = $alpha + $a_div * $f; $b_prev = $b_sum; $b_pdiv = $b_div; $b_sum = $a_prev + ($b_sum - $a_sum) * $f; $b_div = $a_pdiv + ($b_div - $a_div) * $f; $c_prev = $c_sum; $c_pdiv = $c_div; $c_sum = $b_prev + ($c_sum - $b_sum) * $f; $c_div = $b_pdiv + ($c_div - $b_div) * $f; $d_sum = $c_prev + ($d_sum - $c_sum) * $f; $d_div = $c_pdiv + ($d_div - $c_div) * $f; return ($a_sum/$a_div + 2 * $b_sum/$b_div + 2 * $c_sum/$c_div + $d_sum/$d_div) / 6.0; }; } # warmup_count() gives a fixed amount, based on the worst-case EMA alphas # all the slowest possible. It ends up being 1656 which is hugely more than # needed in practice. # # warmup_count_for_position() calculates a value on actual data, working # backwards. In practice it's as little as about 100. # sub warmup_count { my ($self_or_class, $alpha) = @_; $alpha = max (0.00001, min (0.99999, $alpha)); my $f = 1 - $alpha; return App::Chart::Series::Derived::EMAx2::bsearch_first_true (sub { my ($i) = @_; return (laguerre_omitted($f,$i) <= App::Chart::Series::Derived::EMA::WARMUP_OMITTED_FRACTION); }, App::Chart::Series::Derived::EMA::alpha_to_N($alpha)); } # see devel/ema-omitted.pl sub laguerre_omitted { my ($f, $k) = @_; return $f ** ($k-2) * ((1/36*$k**2 + -1/36)*$k + ($f # f^($k-1) * ((1/4*$k + 1/4)*$k + ($f # f^$k * ((((-1/12*$k + -1/6)*$k + 3/4)*$k + 5/6) + ($f # f^($k+1) * (((-1/2*$k + -1)*$k + 1/2) + ($f # f^($k+2) * ((((1/12*$k + 1/3)*$k + -5/12)*$k + -2/3) + ($f # f^($k+3) * (((1/4*$k + 3/4)*$k + 1/2) + ($f # f^($k+4) * ((((-1/36*$k + -1/6)*$k + -11/36)*$k + -1/6) ))))))))))))); } 1; __END__ # =head1 NAME # # App::Chart::Series::Derived::LaguerreFilter -- Laguerre Filter moving average # # =head1 SYNOPSIS # # my $series = $parent->LaguerreFilter($alpha); # # =head1 DESCRIPTION # # ... # # =head1 SEE ALSO # # L, L # # =cut