File Coverage

blib/lib/App/Chart/Series/Derived/DMI.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 2006, 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::DMI;
18 1     1   427 use 5.010;
  1         3  
19 1     1   4 use strict;
  1         2  
  1         18  
20 1     1   4 use warnings;
  1         1  
  1         28  
21 1     1   4 use Carp;
  1         2  
  1         54  
22 1     1   5 use List::Util qw(min max);
  1         1  
  1         54  
23 1     1   307 use Locale::TextDomain ('App-Chart');
  1         17880  
  1         10  
24              
25 1     1   6024 use base 'App::Chart::Series::Indicator';
  1         2  
  1         369  
26             use App::Chart::Series::Derived::ATR;
27             use App::Chart::Series::Derived::EMA;
28              
29             # http://www.incrediblecharts.com/indicators/directional_movement.php
30             #
31              
32             sub longname { __('DMI - Directional Movement Index') }
33             sub shortname { __('DMI') }
34             sub manual { __p('manual-node','Directional Movement Index') }
35              
36             use constant
37             { type => 'indicator',
38             units => 'dmi',
39             minimum => 0,
40             parameter_info => [ { name => __('Days'),
41             key => 'dmi_days',
42             type => 'float',
43             minimum => 1,
44             default => 14,
45             decimals => 0,
46             step => 1 } ],
47             line_colours => { plus => App::Chart::UP_COLOUR(),
48             minus => App::Chart::DOWN_COLOUR() },
49             };
50              
51             sub new {
52             my ($class, $parent, $N) = @_;
53              
54             $N //= parameter_info()->[0]->{'default'};
55             ($N > 0) || croak "DMI bad N: $N";
56              
57             return $class->SUPER::new
58             (parent => $parent,
59             parameters => [ $N ],
60             arrays => { plus => [],
61             minus => [] },
62             array_aliases => { values => 'plus' });
63             }
64             *warmup_count = \&App::Chart::Series::Derived::ATR::warmup_count; # EMA(W)+1
65              
66             sub proc {
67             my ($class_or_self, $N) = @_;
68             my $W = App::Chart::Series::Derived::EMA::N_from_Wilder_N ($N);
69             my $dm_proc = dm_proc();
70             my $ema_plus_proc = App::Chart::Series::Derived::EMA->proc($W);
71             my $ema_minus_proc = App::Chart::Series::Derived::EMA->proc($W);
72             my $atr_proc = App::Chart::Series::Derived::ATR->proc($N);
73              
74             return sub {
75             my ($high, $low, $close) = @_;
76             $high //= $close;
77             $low //= $close;
78              
79             my $atr = $atr_proc->($high, $low, $close);
80             my ($dm_plus, $dm_minus) = $dm_proc->($high, $low);
81              
82             if (! defined $dm_plus) { return; }
83             my $di_plus = $ema_plus_proc->($dm_plus);
84             my $di_minus = $ema_minus_proc->($dm_minus);
85              
86             if ($atr == 0) { return; }
87             return (100 * $di_plus / $atr,
88             100 * $di_minus / $atr);
89             };
90             }
91             sub dm_proc {
92             my ($class_or_self) = @_;
93             my ($prev_high, $prev_low);
94              
95             return sub {
96             my ($high, $low) = @_;
97              
98             my ($dm_plus, $dm_minus);
99             if (defined $prev_high) {
100             $dm_plus = max (0, $high - $prev_high);
101             $dm_minus = max (0, $prev_low - $low);
102              
103             # zap the smaller of the two, or if equal zap both
104             if ($dm_plus > $dm_minus) {
105             $dm_minus = 0;
106             } elsif ($dm_plus < $dm_minus) {
107             $dm_plus = 0;
108             } else {
109             $dm_plus = $dm_minus = 0;
110             }
111             }
112             $prev_high = $high;
113             $prev_low = $low;
114             return ($dm_plus, $dm_minus);
115             };
116             }
117              
118             sub fill_part {
119             my ($self, $lo, $hi) = @_;
120             my $parent = $self->{'parent'};
121              
122             my $warmup_count = $self->warmup_count_for_position ($lo);
123             my $start = $parent->find_before ($lo, $warmup_count);
124             $parent->fill ($lo, $hi);
125             my $p = $parent->values_array;
126             my $ph = $parent->array('highs') || $p;
127             my $pl = $parent->array('lows') || $p;
128              
129             my $s_plus = $self->array('plus');
130             my $s_minus = $self->array('minus');
131             $hi = min ($hi, $#$p);
132             if ($#$s_plus < $hi) { $#$s_plus = $hi; } # pre-extend
133             if ($#$s_minus < $hi) { $#$s_minus = $hi; } # pre-extend
134              
135             my $proc = $self->proc(@{$self->{'parameters'}});
136              
137             foreach my $i ($start .. $lo-1) {
138             my $value = $p->[$i] // next;
139             $proc->($ph->[$i], $pl->[$i], $value);
140             }
141             foreach my $i ($lo .. $hi) {
142             my $value = $p->[$i] // next;
143             ($s_plus->[$i], $s_minus->[$i]) = $proc->($ph->[$i], $pl->[$i], $value);
144             }
145             }
146              
147             1;
148             __END__
149              
150             # =head1 NAME
151             #
152             # App::Chart::Series::Derived::DMI -- directional movement index
153             #
154             # =head1 SYNOPSIS
155             #
156             # my $series = $parent->DMI($N);
157             #
158             # =head1 DESCRIPTION
159             #
160             # ...
161             #
162             # =head1 SEE ALSO
163             #
164             # L<App::Chart::Series>
165             #
166             # =cut