Finance-QuoteHist-1.29/0000755000175000017500000000000013563020544013441 5ustar sisksiskFinance-QuoteHist-1.29/Makefile.PL0000644000175000017500000000150212453543532015415 0ustar sisksiskuse ExtUtils::MakeMaker; my %prereq_pm = ( 'Date::Manip' => 0, 'LWP::UserAgent' => 0, 'HTTP::Request' => 0, 'HTML::TableExtract' => 2.07, 'MIME::Base64' => 0, 'Text::CSV' => 0, ); eval "use Text::CSV_XS"; if ($@) { print STDERR <<__MSG; Note: This is not required, but installing Text::CSV_XS on your system will speed up the parsing of quote data in CSV format. A C compiler is necessary, however. In the meantime we will use Text::CSV_PP. __MSG } else { # why enforce the dependency if we already know it's present? Well, in # the future, we might want to enforce a version dependency here. # $prereq_pm{'Text::CSV_XS'} = 0; } WriteMakefile( NAME => 'Finance-QuoteHist', VERSION_FROM => 'lib/Finance/QuoteHist.pm', PREREQ_PM => \%prereq_pm, ); Finance-QuoteHist-1.29/t/0000755000175000017500000000000013563020544013704 5ustar sisksiskFinance-QuoteHist-1.29/t/30_splits.t0000644000175000017500000000174112453545261015721 0ustar sisksiskuse strict; my $tcount; BEGIN { $tcount = 4 } use Test::More tests => $tcount; use FindBin; use lib $FindBin::RealBin; use testload; SKIP: { skip("split (no connect)", $tcount) unless network_ok(); for my $src (sources()) { SKIP: { my($m, $sym, $start, $end, $dat) = basis($src, 'split'); next unless $m; skip("(dev only) split $src test", 2) unless DEV_TESTS || $src eq GOLDEN_CHILD; eval "use $m"; my %parms = ( class => $m ); split_cmp( $sym, $start, $end, "direct split ($src)", $dat, %parms ); } } } sub split_cmp { @_ >= 5 or die "Problem with args\n"; my($symbol, $start_date, $end_date, $label, $dat, %parms) = @_; my $q = new_quotehist($symbol, $start_date, $end_date, %parms); my @rows = $q->splits; cmp_ok(scalar @rows, '==', scalar @$dat, "$label (rows)"); for my $i (0 .. $#rows) { $rows[$i] = join(':', @{$rows[$i]}); } is_deeply(\@rows, $dat, "$label (content)"); } Finance-QuoteHist-1.29/t/10_quotes.t0000644000175000017500000000233713554076261015725 0ustar sisksiskuse FindBin; use lib $FindBin::RealBin; use testload; my $tcount; BEGIN { $tcount = 8 } use Test::More tests => $tcount; use FindBin; use lib $FindBin::RealBin; use testload; SKIP: { skip("quotes (no connect)", $tcount) unless network_ok(); for my $src (sources()) { for my $gran (granularities($src)) { SKIP: { skip("(dev only) $src-$gran test", 2) unless DEV_TESTS || $src eq GOLDEN_CHILD; my($m, $sym, $start, $end, $dat) = basis($src, 'quote', $gran); next unless $m; eval "use $m"; my %parms = ( class => $m, granularity => $gran ); quote_cmp( $sym, $start, $end, "direct quotes ($src:$gran)", $dat, %parms ); } } } } sub quote_cmp { @_ >= 5 or die "Problem with args\n"; my($symbol, $start_date, $end_date, $label, $dat, %parms) = @_; my $q = new_quotehist($symbol, $start_date, $end_date, %parms); my @rows = $q->quotes; cmp_ok(scalar @rows, '==', scalar @$dat, "$label (rows)"); for my $i (0 .. $#rows) { # drop adjusted and volume, too variable for testing pop @{$rows[$i]} while @{$rows[$i]} > 6; $rows[$i] = join(':', @{$rows[$i]}); } is_deeply(\@rows, $dat, "$label (content)"); } Finance-QuoteHist-1.29/t/20_dividends.t0000644000175000017500000000176712453534725016366 0ustar sisksiskuse strict; my $tcount; BEGIN { $tcount = 4 } use Test::More tests => $tcount; use FindBin; use lib $FindBin::RealBin; use testload; SKIP: { skip("dividend (no connect)", $tcount) unless network_ok(); for my $src (sources()) { SKIP: { my($m, $sym, $start, $end, $dat) = basis($src, 'dividend'); next unless $m; skip("(dev only) dividend $src test", 2) unless DEV_TESTS || $src eq GOLDEN_CHILD; eval "use $m"; my %parms = ( class => $m ); dividend_cmp( $sym, $start, $end, "direct dividend ($src)", $dat, %parms ); } } } sub dividend_cmp { @_ >= 5 or die "Problem with args\n"; my($symbol, $start_date, $end_date, $label, $dat, %parms) = @_; my $q = new_quotehist($symbol, $start_date, $end_date, %parms); my @rows = $q->dividends; cmp_ok(scalar @rows, '==', scalar @$dat, "$label (rows)"); for my $i (0 .. $#rows) { $rows[$i] = join(':', @{$rows[$i]}); } is_deeply(\@rows, $dat, "$label (content)"); } Finance-QuoteHist-1.29/t/05_csv.t0000644000175000017500000000254512453534725015206 0ustar sisksiskmy $test_count; BEGIN { $test_count = 161 } use strict; use Test::More tests => $test_count; use FindBin; use lib $FindBin::RealBin; use testload; my @headers = ( 'Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Adj. Close*'); my($pp_present, $xs_present); eval { require Text::CSV_PP }; $pp_present = !$@; eval { require Text::CSV_XS }; $xs_present = !$@; my $each_count = ($test_count - 1)/2; SKIP: { my $class = 'Text::CSV_PP'; skip "$class not installed", $each_count unless $pp_present; use_ok($class); my $cp = $class->new; csv_parse($cp, csv_content()); } SKIP: { my $class = 'Text::CSV_XS'; skip "$class not installed", $each_count unless $xs_present; use_ok($class); my $cp = $class->new; csv_parse($cp, csv_content()); } ok($pp_present || $xs_present, "csv parsing class present"); ### sub csv_parse { my($cp, $str) = @_; my @rows = split(/\s*\n\s*/, $str); chomp @rows; my $first_line = shift @rows; ok($cp->parse($first_line), "header parse"); my @fields = $cp->fields; cmp_ok(scalar @fields, '==', scalar @headers, "header field count"); foreach (0 .. $#headers) { cmp_ok($fields[$_], 'eq', $headers[$_], "header field match"); } foreach my $line (@rows) { ok($cp->parse($line), 'line parse'); my @fields = $cp->fields; cmp_ok(scalar @fields, '==', 7, "line field count"); } } Finance-QuoteHist-1.29/t/00_basic.t0000644000175000017500000000043112453775511015457 0ustar sisksiskuse FindBin; use lib $FindBin::RealBin; use testload; my $tcount; BEGIN { my @mods = modules(); $tcount = scalar @mods + 2; } use Test::More tests => $tcount; BEGIN { use_ok('Finance::QuoteHist'); use_ok('Finance::QuoteHist::Generic'); use_ok($_) foreach modules(); } Finance-QuoteHist-1.29/t/dat/0000755000175000017500000000000013563020544014454 5ustar sisksiskFinance-QuoteHist-1.29/t/dat/dividend_plain.dat0000644000175000017500000000043413554074432020125 0ustar sisksiskFinance::QuoteHist INTC,2011/01/01,2013/10/01 INTC:2011/02/03:0.181 INTC:2011/05/04:0.181 INTC:2011/08/03:0.21 INTC:2011/11/03:0.21 INTC:2012/02/03:0.21 INTC:2012/05/03:0.21 INTC:2012/08/03:0.225 INTC:2012/11/05:0.225 INTC:2013/02/05:0.225 INTC:2013/05/03:0.225 INTC:2013/08/05:0.225 Finance-QuoteHist-1.29/t/dat/quote_daily_plain.dat0000644000175000017500000004123313554074427020664 0ustar sisksiskFinance::QuoteHist IBM,2012/07/01,2013/07/01 IBM:2012/07/02:196.3600:197.2000:194.8500:195.8300:2827000:151.6017 IBM:2012/07/03:195.4600:196.3400:194.9100:195.9300:1450400:151.6791 IBM:2012/07/05:194.8800:196.8500:193.6300:195.2900:2690200:151.1836 IBM:2012/07/06:193.9200:193.9400:189.7400:191.4100:4952900:148.1799 IBM:2012/07/09:190.7600:191.0000:188.0500:189.6700:3988100:146.8329 IBM:2012/07/10:190.3000:191.1400:185.6000:186.2600:4690300:144.1931 IBM:2012/07/11:186.2200:187.3600:183.5100:185.2500:5456100:143.4112 IBM:2012/07/12:184.2500:184.3900:181.8500:183.0900:4931300:141.7390 IBM:2012/07/13:183.4600:186.3300:183.0300:186.0100:3933000:143.9995 IBM:2012/07/16:185.5800:186.1000:184.5800:184.7900:3144400:143.0551 IBM:2012/07/17:185.7300:186.2900:183.2000:183.6500:5158600:142.1725 IBM:2012/07/18:184.1500:188.5900:183.5500:188.2500:8019500:145.7336 IBM:2012/07/19:193.4000:196.8500:192.9700:195.3400:10395400:151.2224 IBM:2012/07/20:194.0900:194.9000:192.1700:192.4500:4789700:148.9851 IBM:2012/07/23:189.7800:191.3000:188.2000:190.8300:3904500:147.7309 IBM:2012/07/24:190.9200:191.3200:188.5600:190.3400:3597100:147.3516 IBM:2012/07/25:190.3100:192.7700:189.3200:191.0800:3833800:147.9244 IBM:2012/07/26:193.4900:194.9500:192.5700:193.9500:3282900:150.1463 IBM:2012/07/27:195.1000:197.4100:193.9500:196.3900:4177300:152.0352 IBM:2012/07/30:196.3200:197.8400:195.9200:196.6800:2787000:152.2597 IBM:2012/07/31:196.5000:197.5800:195.8800:195.9800:3507000:151.7178 IBM:2012/08/01:196.9600:197.8500:194.7200:195.1800:2559300:151.0985 IBM:2012/08/02:194.1600:196.6000:193.0200:194.4500:2812600:150.5334 IBM:2012/08/03:196.4800:198.9500:196.1600:198.5200:3278100:153.6842 IBM:2012/08/06:198.7600:199.9400:198.5200:198.7600:2337800:153.8699 IBM:2012/08/07:199.4300:200.8800:198.8000:199.9300:3209400:154.7757 IBM:2012/08/08:198.2700:199.6900:198.1600:199.0300:2068300:154.7368 IBM:2012/08/09:198.6200:199.4600:197.8900:198.4200:2160100:154.2626 IBM:2012/08/10:197.8700:199.3700:197.2400:199.2900:2600500:154.9389 IBM:2012/08/13:198.8800:199.9800:197.7900:199.0100:2418300:154.7213 IBM:2012/08/14:198.8800:199.3300:197.7200:198.2900:2562300:154.1615 IBM:2012/08/15:198.9000:199.3300:197.9200:198.4000:2111800:154.2470 IBM:2012/08/16:198.7700:201.3200:198.1200:200.8400:2729500:156.1441 IBM:2012/08/17:201.0800:202.0000:200.6600:201.2200:2551800:156.4395 IBM:2012/08/20:200.6900:201.1300:200.0300:200.5000:2336000:155.8796 IBM:2012/08/21:200.9900:201.0000:198.3700:198.6500:3111300:154.4414 IBM:2012/08/22:198.6400:198.6500:196.9000:197.2500:3296100:153.3530 IBM:2012/08/23:197.0400:197.3700:195.4400:195.7000:2757400:152.1479 IBM:2012/08/24:194.9600:198.1100:194.2000:197.7700:2639500:153.7573 IBM:2012/08/27:197.9600:198.3000:195.6100:195.6900:2498800:152.1401 IBM:2012/08/28:195.5600:196.1100:194.5000:194.8700:2539200:151.5026 IBM:2012/08/29:195.1300:196.0400:194.9000:195.0800:2141400:151.6659 IBM:2012/08/30:194.7700:195.4700:193.1800:193.3700:2740900:150.3364 IBM:2012/08/31:194.3100:195.9500:193.4600:194.8500:3193300:151.4870 IBM:2012/09/04:196.6100:197.1700:193.2500:194.5400:4514400:151.2460 IBM:2012/09/05:194.4100:195.8500:193.8900:195.0400:3312500:151.6348 IBM:2012/09/06:196.2600:199.4600:196.1100:199.1000:3931700:154.7913 IBM:2012/09/07:199.1200:199.5000:198.0800:199.5000:3413700:155.1022 IBM:2012/09/10:199.3900:201.8200:198.7300:200.9500:4208000:156.2295 IBM:2012/09/11:200.5500:203.4600:200.5100:203.2700:3910600:158.0332 IBM:2012/09/12:203.5200:204.6500:202.9600:203.7700:3284000:158.4219 IBM:2012/09/13:204.3800:206.8100:203.4600:206.3600:3879600:160.4356 IBM:2012/09/14:206.2400:207.6500:206.0500:206.8100:4057100:160.7854 IBM:2012/09/17:205.5500:207.9900:205.5500:207.1500:3275100:161.0498 IBM:2012/09/18:206.8800:207.8800:206.4800:207.0700:2476100:160.9875 IBM:2012/09/19:207.5500:207.5700:206.0100:206.4300:2605600:160.4900 IBM:2012/09/20:205.8500:206.9900:205.3000:206.1800:3739100:160.2956 IBM:2012/09/21:207.3600:207.9400:205.9200:205.9800:12771500:160.1401 IBM:2012/09/24:205.0200:206.0600:204.9000:205.2900:3511100:159.6037 IBM:2012/09/25:205.6000:207.3200:204.9500:204.9800:3345200:159.3627 IBM:2012/09/26:205.0500:205.9800:203.9000:204.0000:3423300:158.6008 IBM:2012/09/27:204.3700:206.9000:204.1400:205.9100:2771900:160.0857 IBM:2012/09/28:205.6600:208.3200:205.3100:207.4500:4910900:161.2830 IBM:2012/10/01:208.0100:211.7500:207.9400:210.4700:5456900:163.6309 IBM:2012/10/02:210.9600:211.4700:208.5000:209.8400:3321000:163.1411 IBM:2012/10/03:209.9400:211.3000:209.3000:210.5100:3192300:163.6620 IBM:2012/10/04:210.2800:210.9700:208.5300:210.3900:2957500:163.5687 IBM:2012/10/05:211.1500:211.7900:210.0600:210.5900:2858400:163.7242 IBM:2012/10/08:210.0200:210.6400:209.2100:209.8200:1982600:163.1255 IBM:2012/10/09:209.9500:210.7400:207.7000:207.9900:3580400:161.7029 IBM:2012/10/10:207.8200:208.2300:205.0500:205.8200:4024500:160.0157 IBM:2012/10/11:206.5700:207.2700:205.6300:205.7600:2910000:159.9691 IBM:2012/10/12:205.6400:207.9600:205.5600:207.8000:2712000:161.5551 IBM:2012/10/15:208.8800:209.4000:207.8200:208.9300:3060900:162.4336 IBM:2012/10/16:209.9400:211.0000:209.4700:211.0000:5632100:164.0430 IBM:2012/10/17:201.2100:203.4100:198.5500:200.6300:12673900:155.9807 IBM:2012/10/18:199.9100:200.1400:194.0700:194.9600:9295900:151.5726 IBM:2012/10/19:195.2900:196.0800:193.1800:193.3600:6620300:150.3287 IBM:2012/10/22:193.7100:194.6800:192.4400:194.4000:4402500:151.1372 IBM:2012/10/23:193.5300:194.0000:190.9400:191.2500:4427400:148.6882 IBM:2012/10/24:192.4400:193.2900:190.5600:190.7200:3738000:148.2762 IBM:2012/10/25:191.4100:192.4400:190.7100:191.6000:3249600:148.9603 IBM:2012/10/26:191.4900:193.8700:191.4700:193.2700:4006500:150.2587 IBM:2012/10/31:194.8000:196.4100:193.6300:194.5300:6052300:151.2383 IBM:2012/11/01:194.6800:197.8900:194.5500:197.1500:3758800:153.2752 IBM:2012/11/02:197.5300:198.0000:193.2900:193.4300:4260100:150.3831 IBM:2012/11/05:192.3600:194.9700:192.0100:194.1400:2736200:150.9351 IBM:2012/11/06:195.2600:196.7500:194.6800:195.0700:3281000:151.6581 IBM:2012/11/07:193.3600:193.6800:191.1600:191.1600:4746200:149.2687 IBM:2012/11/08:191.4000:191.8100:190.0900:190.1000:3542200:148.4410 IBM:2012/11/09:189.8500:191.6400:188.8800:189.6400:3671600:148.0817 IBM:2012/11/12:190.0300:190.7700:187.4100:189.2500:3067400:147.7772 IBM:2012/11/13:188.3900:190.8800:188.2500:188.3200:3406700:147.0510 IBM:2012/11/14:189.1400:189.2700:185.2800:185.5100:4404400:144.8568 IBM:2012/11/15:185.8300:187.2000:184.7800:185.8500:3411700:145.1223 IBM:2012/11/16:186.3300:187.5600:185.1000:186.9400:4660800:145.9735 IBM:2012/11/19:188.6200:190.3900:188.3000:190.3500:3588900:148.6362 IBM:2012/11/20:189.8800:190.1100:188.2500:189.2000:3170200:147.7382 IBM:2012/11/21:189.3000:190.8900:188.9500:190.2900:5189600:148.5893 IBM:2012/11/23:191.0000:193.4900:190.8000:193.4900:3877600:151.0881 IBM:2012/11/26:192.4500:193.3700:191.7700:192.8800:5210300:150.6118 IBM:2012/11/27:192.0800:193.0500:191.0000:191.2300:3290900:149.3233 IBM:2012/11/28:190.9800:192.0400:189.2700:191.9800:3603600:149.9090 IBM:2012/11/29:192.7500:192.9000:190.2000:191.5300:4077900:149.5576 IBM:2012/11/30:191.7500:192.0000:189.5000:190.0700:4936400:148.4175 IBM:2012/12/03:190.7600:191.3000:188.3600:189.4800:3349600:147.9569 IBM:2012/12/04:189.7100:190.7500:189.0200:189.3600:3990700:147.8632 IBM:2012/12/05:189.0100:189.4600:186.9400:188.6500:4202100:147.3087 IBM:2012/12/06:189.1700:190.1800:188.4100:189.7000:3216600:148.1286 IBM:2012/12/07:190.1400:192.2000:190.1100:191.9500:4091300:149.8856 IBM:2012/12/10:192.1700:193.5400:191.6500:192.6200:3438500:150.4087 IBM:2012/12/11:193.1800:194.8000:193.1500:194.2000:4144000:151.6425 IBM:2012/12/12:194.6000:194.7500:192.4600:192.9500:4241900:150.6664 IBM:2012/12/13:192.1800:193.0500:191.3700:191.9900:3623200:149.9168 IBM:2012/12/14:191.6900:193.4000:191.2600:191.7600:3826900:149.7372 IBM:2012/12/17:191.7600:193.9100:191.7600:193.6200:3792400:151.1896 IBM:2012/12/18:193.8500:195.9200:193.8400:195.6900:4125900:152.8060 IBM:2012/12/19:195.4600:196.4500:195.0000:195.0800:4270500:152.3296 IBM:2012/12/20:194.4700:194.9500:193.1600:194.7700:4184500:152.0876 IBM:2012/12/21:193.1000:194.5000:191.5700:193.4200:8225300:151.0334 IBM:2012/12/24:193.0500:193.8300:192.0200:192.4000:1631200:150.2370 IBM:2012/12/26:192.2100:193.5300:190.9500:191.9500:2776500:149.8856 IBM:2012/12/27:191.5300:193.2800:190.7000:192.7100:3967600:150.4790 IBM:2012/12/28:191.1100:192.2200:189.8300:189.8300:3536600:148.2301 IBM:2012/12/31:189.2300:191.6800:188.8400:191.5500:4684900:149.5732 IBM:2013/01/02:194.0900:196.3500:193.8000:196.3500:4234100:153.3213 IBM:2013/01/03:195.6700:196.2900:194.4400:195.2700:3644700:152.4780 IBM:2013/01/04:194.1900:194.4600:192.7800:193.9900:3380200:151.4785 IBM:2013/01/07:193.4000:193.7800:192.3400:193.1400:2862300:150.8148 IBM:2013/01/08:192.9200:193.3000:191.6000:192.8700:3026900:150.6040 IBM:2013/01/09:193.4800:193.4900:191.6500:192.3200:3212000:150.1745 IBM:2013/01/10:192.6500:192.9600:191.2800:192.8800:3608100:150.6118 IBM:2013/01/11:194.1500:195.0000:192.9000:194.4500:3880400:151.8377 IBM:2013/01/14:192.8200:193.2800:191.7500:192.6200:4172200:150.4087 IBM:2013/01/15:191.3100:192.7300:190.3900:192.5000:4172100:150.3150 IBM:2013/01/16:192.0000:193.1800:191.3500:192.5900:2962900:150.3853 IBM:2013/01/17:193.8500:194.4600:193.2400:193.6500:3881600:151.2130 IBM:2013/01/18:194.0300:195.0000:193.8000:194.4700:4559600:151.8533 IBM:2013/01/22:194.3600:196.0800:194.0100:196.0800:7172300:153.1105 IBM:2013/01/23:203.5000:208.5800:203.3600:204.7200:12530500:159.8571 IBM:2013/01/24:203.9100:205.0600:203.0800:204.4200:4447200:159.6228 IBM:2013/01/25:204.4500:205.1800:204.1300:204.9700:3358900:160.0523 IBM:2013/01/28:204.8500:206.2200:204.2900:204.9300:2823700:160.0211 IBM:2013/01/29:204.3400:205.7300:203.6400:203.9000:3617100:159.2167 IBM:2013/01/30:203.6900:204.8800:203.1900:203.5200:2998400:158.9201 IBM:2013/01/31:203.3200:204.4700:202.9600:203.0700:3091400:158.5687 IBM:2013/02/01:204.6500:205.3500:203.8400:205.1800:3370700:160.2163 IBM:2013/02/04:204.1900:205.0200:203.5700:203.7900:3188800:159.1309 IBM:2013/02/05:204.3100:204.7500:202.5100:202.7900:3636900:158.3501 IBM:2013/02/06:200.3900:201.2900:199.5600:201.0200:3624200:157.6286 IBM:2013/02/07:200.6200:200.9100:198.6800:199.7400:3076700:156.6249 IBM:2013/02/08:199.9700:202.0900:199.6800:201.6800:2893300:158.1461 IBM:2013/02/11:200.9800:201.9500:199.7500:200.1600:2944700:156.9543 IBM:2013/02/12:200.0100:200.7400:199.0200:200.0400:2461800:156.8602 IBM:2013/02/13:200.6500:200.9500:199.5700:200.0900:2169800:156.8994 IBM:2013/02/14:199.7300:200.3200:199.2600:199.6500:3294200:156.5544 IBM:2013/02/15:199.9800:201.2500:199.8200:200.9800:3627800:157.5973 IBM:2013/02/19:200.6000:201.8900:200.2200:200.3200:2998300:157.0797 IBM:2013/02/20:200.6200:201.7200:198.8600:199.3100:3715400:156.2877 IBM:2013/02/21:198.6300:199.0700:198.1100:198.3300:3922900:155.5193 IBM:2013/02/22:199.2300:201.0900:198.8400:201.0900:3107900:157.6835 IBM:2013/02/25:201.6700:202.4900:197.5100:197.5100:3844800:154.8763 IBM:2013/02/26:198.6300:199.9000:197.8400:199.1400:3391600:156.1544 IBM:2013/02/27:198.8900:202.7500:198.6000:202.3300:4185100:158.6559 IBM:2013/02/28:202.1800:203.1200:200.7900:200.8300:4689200:157.4796 IBM:2013/03/01:200.6500:202.9400:199.3600:202.9100:3308300:159.1106 IBM:2013/03/04:202.5900:205.1900:202.5500:205.1900:3693400:160.8985 IBM:2013/03/05:205.8600:207.7000:205.6900:206.5300:3807800:161.9493 IBM:2013/03/06:207.0300:208.4900:206.6600:208.3800:3594800:163.3999 IBM:2013/03/07:208.2900:209.6000:208.2400:209.4200:3884400:164.2154 IBM:2013/03/08:209.8500:210.7400:209.4300:210.3800:3700500:164.9682 IBM:2013/03/11:210.0400:210.2000:209.0400:210.0800:3049700:164.7330 IBM:2013/03/12:209.4000:210.7300:209.0900:210.5500:3591600:165.1015 IBM:2013/03/13:210.2000:212.3600:209.7700:212.0600:3355900:166.2856 IBM:2013/03/14:212.1500:215.8600:212.1500:215.8000:5505500:169.2183 IBM:2013/03/15:215.3800:215.9000:213.4100:214.9200:7930300:168.5283 IBM:2013/03/18:212.9000:214.5000:212.6400:213.2100:3006000:167.1874 IBM:2013/03/19:214.1300:215.1200:211.8300:213.4400:3198600:167.3677 IBM:2013/03/20:214.7600:215.8200:214.3000:215.0600:3019000:168.6380 IBM:2013/03/21:212.9600:213.0000:210.1100:212.2600:5830400:166.4424 IBM:2013/03/22:212.2100:213.1700:211.6200:212.0800:3031500:166.3013 IBM:2013/03/25:212.5400:212.8100:210.0500:210.7400:3242500:165.2505 IBM:2013/03/26:211.7700:212.5000:211.5000:212.3600:2300300:166.5208 IBM:2013/03/27:210.9600:212.1600:210.1000:210.8900:3223400:165.3681 IBM:2013/03/28:209.8300:213.4400:209.7400:213.3000:3749900:167.2579 IBM:2013/04/01:212.8000:213.5000:211.2500:212.3800:2144500:166.5365 IBM:2013/04/02:212.9200:214.7100:211.5700:214.3600:3116800:168.0891 IBM:2013/04/03:214.3200:214.8900:212.6300:212.6600:3128800:166.7561 IBM:2013/04/04:212.7700:212.9300:210.0400:211.3100:3709500:165.6975 IBM:2013/04/05:209.1000:209.8400:206.3400:209.4100:4148000:164.2076 IBM:2013/04/08:209.0700:209.3200:207.3300:209.3200:2351200:164.1370 IBM:2013/04/09:209.5600:210.0000:208.6100:209.2200:2577300:164.0587 IBM:2013/04/10:211.8600:212.5100:210.4700:212.0000:3524200:166.2385 IBM:2013/04/11:211.5000:213.0900:210.8500:212.9200:3655600:166.9599 IBM:2013/04/12:210.7900:211.4900:209.7800:211.3800:3165400:165.7524 IBM:2013/04/15:210.4400:211.6900:209.2600:209.2600:4218100:164.0900 IBM:2013/04/16:210.8400:212.0000:209.2000:212.0000:2859500:166.2385 IBM:2013/04/17:210.5300:211.0900:209.5000:209.6700:3269800:164.4115 IBM:2013/04/18:210.1100:210.2500:206.1500:207.1500:6502000:162.4354 IBM:2013/04/19:195.7400:196.5000:189.7600:190.0000:18847000:148.9874 IBM:2013/04/22:191.1500:191.3800:187.6800:187.8300:9845400:147.2858 IBM:2013/04/23:189.2700:192.5500:188.6100:191.6100:6296100:150.2498 IBM:2013/04/24:192.1600:192.5000:191.0900:191.7100:3693900:150.3283 IBM:2013/04/25:192.6900:195.1700:192.3200:193.9500:4650900:152.0847 IBM:2013/04/26:194.1600:194.7100:193.2500:194.3100:3490300:152.3671 IBM:2013/04/29:194.7800:199.6800:194.6500:199.1500:5932400:156.1623 IBM:2013/04/30:199.1300:202.5900:197.3500:202.5400:7539900:158.8206 IBM:2013/05/01:201.8700:202.1700:199.2000:199.6300:4899100:156.5387 IBM:2013/05/02:200.1200:202.4300:199.7500:202.3900:3945100:158.7029 IBM:2013/05/03:203.9400:205.3200:203.7500:204.5100:4492700:160.3653 IBM:2013/05/06:203.7900:203.9900:201.5200:202.7800:4826500:159.0087 IBM:2013/05/07:202.8100:203.7300:201.6500:203.6300:3451000:159.6753 IBM:2013/05/08:202.9400:204.8500:202.5100:204.8200:3601700:161.3612 IBM:2013/05/09:204.6900:205.0000:202.7200:203.2400:3542300:160.1164 IBM:2013/05/10:203.3700:204.5300:202.8200:204.4700:3279200:161.0855 IBM:2013/05/13:204.1800:204.4700:202.2200:202.4700:3648400:159.5098 IBM:2013/05/14:202.0900:203.6700:202.0800:203.2100:3699700:160.0928 IBM:2013/05/15:202.2500:203.6800:202.0400:203.3200:4028100:160.1795 IBM:2013/05/16:204.0000:206.5900:204.0000:204.6900:4507000:161.2587 IBM:2013/05/17:205.2500:209.5000:204.9900:208.4400:5704100:164.2131 IBM:2013/05/20:208.0200:209.1500:207.4200:207.6000:3614500:163.5513 IBM:2013/05/21:207.2400:209.4800:207.0000:208.6500:2982900:164.3785 IBM:2013/05/22:208.5000:210.1500:206.1200:206.9900:5186800:163.0707 IBM:2013/05/23:205.4800:208.6100:205.1300:206.1600:5179100:162.4169 IBM:2013/05/24:204.6900:206.4300:204.4200:205.7200:3295800:162.0702 IBM:2013/05/28:207.4400:208.5900:207.1600:207.7800:4011500:163.6931 IBM:2013/05/29:206.2200:208.4200:206.0400:207.9200:3439400:163.8034 IBM:2013/05/30:206.9700:210.9900:206.9700:209.3600:4399700:164.9379 IBM:2013/05/31:208.5900:211.9800:208.0200:208.0200:4949000:163.8822 IBM:2013/06/03:208.2500:210.0500:207.3300:208.9500:3322000:164.6149 IBM:2013/06/04:208.6000:209.0000:205.0800:206.1900:3598300:162.4405 IBM:2013/06/05:205.1600:206.2900:202.4000:202.7400:4215400:159.7225 IBM:2013/06/06:203.0000:204.2000:201.4700:203.8000:3679400:160.5576 IBM:2013/06/07:204.8500:206.3500:204.1100:206.3500:2953400:162.5666 IBM:2013/06/10:206.9700:206.9800:204.9000:205.0200:2652300:161.5188 IBM:2013/06/11:203.1700:205.9500:202.5100:203.9800:2814400:160.6994 IBM:2013/06/12:204.5900:205.2300:200.5100:201.2000:3409800:158.5093 IBM:2013/06/13:201.0100:204.4000:200.3400:203.7700:2899500:160.5339 IBM:2013/06/14:203.9700:204.7400:201.8100:202.2000:2804500:159.2971 IBM:2013/06/17:203.4400:205.1700:202.5500:203.0400:3219900:159.9588 IBM:2013/06/18:203.0200:206.0900:202.8700:204.8700:3277800:161.4006 IBM:2013/06/19:204.4400:205.0300:201.9300:201.9400:2846100:159.0923 IBM:2013/06/20:200.6700:201.7000:197.2800:197.3500:4514800:155.4762 IBM:2013/06/21:198.5000:198.5200:193.5400:195.4600:8914800:153.9872 IBM:2013/06/24:193.9900:194.9100:191.3400:193.5400:4356200:152.4746 IBM:2013/06/25:195.3100:195.5900:193.1500:194.9800:3846200:153.6090 IBM:2013/06/26:195.9700:196.2300:194.5000:194.8600:3320200:153.5145 IBM:2013/06/27:196.0500:196.9000:195.2900:195.6500:3514200:154.1368 IBM:2013/06/28:191.7300:192.6600:188.4100:191.1100:10055800:150.5602 IBM:2013/07/01:192.1500:193.8000:191.1200:191.2800:4442400:150.6941 Finance-QuoteHist-1.29/t/dat/split_plain.dat0000644000175000017500000000010013554074430017456 0ustar sisksiskFinance::QuoteHist NKE,2009/01/01,2013/10/01 NKE:2012/12/26:1:2 Finance-QuoteHist-1.29/t/dat/dividend_yahoo.dat0000644000175000017500000000044313554074442020142 0ustar sisksiskFinance::QuoteHist::Yahoo INTC,2011/01/01,2013/10/01 INTC:2011/02/03:0.181 INTC:2011/05/04:0.181 INTC:2011/08/03:0.21 INTC:2011/11/03:0.21 INTC:2012/02/03:0.21 INTC:2012/05/03:0.21 INTC:2012/08/03:0.225 INTC:2012/11/05:0.225 INTC:2013/02/05:0.225 INTC:2013/05/03:0.225 INTC:2013/08/05:0.225 Finance-QuoteHist-1.29/t/dat/split_yahoo.dat0000644000175000017500000000010713554074441017503 0ustar sisksiskFinance::QuoteHist::Yahoo NKE,2009/01/01,2013/10/01 NKE:2012/12/26:1:2 Finance-QuoteHist-1.29/t/dat/quote_monthly_yahoo.dat0000644000175000017500000000166613554074437021277 0ustar sisksiskFinance::QuoteHist::Yahoo IBM,2012/07/01,2013/07/01 IBM:2012/07/01:196.3600:197.8400:181.8500:195.9800:91516500:151.7178 IBM:2012/08/01:196.9600:202.0000:193.0200:194.8500:60653700:150.8430 IBM:2012/09/01:196.6100:208.3200:193.2500:207.4500:77341400:161.2830 IBM:2012/10/01:208.0100:211.7900:190.5600:194.5300:96155000:151.2383 IBM:2012/11/01:194.6800:198.0000:184.7800:190.0700:81892500:147.7708 IBM:2012/12/01:190.7600:196.4500:186.9400:191.5500:79320200:149.5732 IBM:2013/01/01:194.0900:208.5800:190.3900:203.0700:87636600:158.5687 IBM:2013/02/01:204.6500:205.3500:197.5100:200.8300:64144100:156.8195 IBM:2013/03/01:200.6500:215.9000:199.3600:213.3000:76023800:167.2579 IBM:2013/04/01:212.8000:214.8900:187.6800:202.5400:108666600:158.8206 IBM:2013/05/01:201.8700:211.9800:199.2000:208.0200:90683600:163.1176 IBM:2013/06/01:208.2500:210.0500:188.4100:191.1100:80215000:150.5602 IBM:2013/07/01:192.1500:200.9400:190.2600:195.0400:88079400:153.6563 Finance-QuoteHist-1.29/t/dat/quote_daily_yahoo.dat0000644000175000017500000004124213554074434020676 0ustar sisksiskFinance::QuoteHist::Yahoo IBM,2012/07/01,2013/07/01 IBM:2012/07/02:196.3600:197.2000:194.8500:195.8300:2827000:151.6017 IBM:2012/07/03:195.4600:196.3400:194.9100:195.9300:1450400:151.6791 IBM:2012/07/05:194.8800:196.8500:193.6300:195.2900:2690200:151.1836 IBM:2012/07/06:193.9200:193.9400:189.7400:191.4100:4952900:148.1799 IBM:2012/07/09:190.7600:191.0000:188.0500:189.6700:3988100:146.8329 IBM:2012/07/10:190.3000:191.1400:185.6000:186.2600:4690300:144.1931 IBM:2012/07/11:186.2200:187.3600:183.5100:185.2500:5456100:143.4112 IBM:2012/07/12:184.2500:184.3900:181.8500:183.0900:4931300:141.7390 IBM:2012/07/13:183.4600:186.3300:183.0300:186.0100:3933000:143.9995 IBM:2012/07/16:185.5800:186.1000:184.5800:184.7900:3144400:143.0551 IBM:2012/07/17:185.7300:186.2900:183.2000:183.6500:5158600:142.1725 IBM:2012/07/18:184.1500:188.5900:183.5500:188.2500:8019500:145.7336 IBM:2012/07/19:193.4000:196.8500:192.9700:195.3400:10395400:151.2224 IBM:2012/07/20:194.0900:194.9000:192.1700:192.4500:4789700:148.9851 IBM:2012/07/23:189.7800:191.3000:188.2000:190.8300:3904500:147.7309 IBM:2012/07/24:190.9200:191.3200:188.5600:190.3400:3597100:147.3516 IBM:2012/07/25:190.3100:192.7700:189.3200:191.0800:3833800:147.9244 IBM:2012/07/26:193.4900:194.9500:192.5700:193.9500:3282900:150.1463 IBM:2012/07/27:195.1000:197.4100:193.9500:196.3900:4177300:152.0352 IBM:2012/07/30:196.3200:197.8400:195.9200:196.6800:2787000:152.2597 IBM:2012/07/31:196.5000:197.5800:195.8800:195.9800:3507000:151.7178 IBM:2012/08/01:196.9600:197.8500:194.7200:195.1800:2559300:151.0985 IBM:2012/08/02:194.1600:196.6000:193.0200:194.4500:2812600:150.5334 IBM:2012/08/03:196.4800:198.9500:196.1600:198.5200:3278100:153.6842 IBM:2012/08/06:198.7600:199.9400:198.5200:198.7600:2337800:153.8699 IBM:2012/08/07:199.4300:200.8800:198.8000:199.9300:3209400:154.7757 IBM:2012/08/08:198.2700:199.6900:198.1600:199.0300:2068300:154.7368 IBM:2012/08/09:198.6200:199.4600:197.8900:198.4200:2160100:154.2626 IBM:2012/08/10:197.8700:199.3700:197.2400:199.2900:2600500:154.9389 IBM:2012/08/13:198.8800:199.9800:197.7900:199.0100:2418300:154.7213 IBM:2012/08/14:198.8800:199.3300:197.7200:198.2900:2562300:154.1615 IBM:2012/08/15:198.9000:199.3300:197.9200:198.4000:2111800:154.2470 IBM:2012/08/16:198.7700:201.3200:198.1200:200.8400:2729500:156.1441 IBM:2012/08/17:201.0800:202.0000:200.6600:201.2200:2551800:156.4395 IBM:2012/08/20:200.6900:201.1300:200.0300:200.5000:2336000:155.8796 IBM:2012/08/21:200.9900:201.0000:198.3700:198.6500:3111300:154.4414 IBM:2012/08/22:198.6400:198.6500:196.9000:197.2500:3296100:153.3530 IBM:2012/08/23:197.0400:197.3700:195.4400:195.7000:2757400:152.1479 IBM:2012/08/24:194.9600:198.1100:194.2000:197.7700:2639500:153.7573 IBM:2012/08/27:197.9600:198.3000:195.6100:195.6900:2498800:152.1401 IBM:2012/08/28:195.5600:196.1100:194.5000:194.8700:2539200:151.5026 IBM:2012/08/29:195.1300:196.0400:194.9000:195.0800:2141400:151.6659 IBM:2012/08/30:194.7700:195.4700:193.1800:193.3700:2740900:150.3364 IBM:2012/08/31:194.3100:195.9500:193.4600:194.8500:3193300:151.4870 IBM:2012/09/04:196.6100:197.1700:193.2500:194.5400:4514400:151.2460 IBM:2012/09/05:194.4100:195.8500:193.8900:195.0400:3312500:151.6348 IBM:2012/09/06:196.2600:199.4600:196.1100:199.1000:3931700:154.7913 IBM:2012/09/07:199.1200:199.5000:198.0800:199.5000:3413700:155.1022 IBM:2012/09/10:199.3900:201.8200:198.7300:200.9500:4208000:156.2295 IBM:2012/09/11:200.5500:203.4600:200.5100:203.2700:3910600:158.0332 IBM:2012/09/12:203.5200:204.6500:202.9600:203.7700:3284000:158.4219 IBM:2012/09/13:204.3800:206.8100:203.4600:206.3600:3879600:160.4356 IBM:2012/09/14:206.2400:207.6500:206.0500:206.8100:4057100:160.7854 IBM:2012/09/17:205.5500:207.9900:205.5500:207.1500:3275100:161.0498 IBM:2012/09/18:206.8800:207.8800:206.4800:207.0700:2476100:160.9875 IBM:2012/09/19:207.5500:207.5700:206.0100:206.4300:2605600:160.4900 IBM:2012/09/20:205.8500:206.9900:205.3000:206.1800:3739100:160.2956 IBM:2012/09/21:207.3600:207.9400:205.9200:205.9800:12771500:160.1401 IBM:2012/09/24:205.0200:206.0600:204.9000:205.2900:3511100:159.6037 IBM:2012/09/25:205.6000:207.3200:204.9500:204.9800:3345200:159.3627 IBM:2012/09/26:205.0500:205.9800:203.9000:204.0000:3423300:158.6008 IBM:2012/09/27:204.3700:206.9000:204.1400:205.9100:2771900:160.0857 IBM:2012/09/28:205.6600:208.3200:205.3100:207.4500:4910900:161.2830 IBM:2012/10/01:208.0100:211.7500:207.9400:210.4700:5456900:163.6309 IBM:2012/10/02:210.9600:211.4700:208.5000:209.8400:3321000:163.1411 IBM:2012/10/03:209.9400:211.3000:209.3000:210.5100:3192300:163.6620 IBM:2012/10/04:210.2800:210.9700:208.5300:210.3900:2957500:163.5687 IBM:2012/10/05:211.1500:211.7900:210.0600:210.5900:2858400:163.7242 IBM:2012/10/08:210.0200:210.6400:209.2100:209.8200:1982600:163.1255 IBM:2012/10/09:209.9500:210.7400:207.7000:207.9900:3580400:161.7029 IBM:2012/10/10:207.8200:208.2300:205.0500:205.8200:4024500:160.0157 IBM:2012/10/11:206.5700:207.2700:205.6300:205.7600:2910000:159.9691 IBM:2012/10/12:205.6400:207.9600:205.5600:207.8000:2712000:161.5551 IBM:2012/10/15:208.8800:209.4000:207.8200:208.9300:3060900:162.4336 IBM:2012/10/16:209.9400:211.0000:209.4700:211.0000:5632100:164.0430 IBM:2012/10/17:201.2100:203.4100:198.5500:200.6300:12673900:155.9807 IBM:2012/10/18:199.9100:200.1400:194.0700:194.9600:9295900:151.5726 IBM:2012/10/19:195.2900:196.0800:193.1800:193.3600:6620300:150.3287 IBM:2012/10/22:193.7100:194.6800:192.4400:194.4000:4402500:151.1372 IBM:2012/10/23:193.5300:194.0000:190.9400:191.2500:4427400:148.6882 IBM:2012/10/24:192.4400:193.2900:190.5600:190.7200:3738000:148.2762 IBM:2012/10/25:191.4100:192.4400:190.7100:191.6000:3249600:148.9603 IBM:2012/10/26:191.4900:193.8700:191.4700:193.2700:4006500:150.2587 IBM:2012/10/31:194.8000:196.4100:193.6300:194.5300:6052300:151.2383 IBM:2012/11/01:194.6800:197.8900:194.5500:197.1500:3758800:153.2752 IBM:2012/11/02:197.5300:198.0000:193.2900:193.4300:4260100:150.3831 IBM:2012/11/05:192.3600:194.9700:192.0100:194.1400:2736200:150.9351 IBM:2012/11/06:195.2600:196.7500:194.6800:195.0700:3281000:151.6581 IBM:2012/11/07:193.3600:193.6800:191.1600:191.1600:4746200:149.2687 IBM:2012/11/08:191.4000:191.8100:190.0900:190.1000:3542200:148.4410 IBM:2012/11/09:189.8500:191.6400:188.8800:189.6400:3671600:148.0817 IBM:2012/11/12:190.0300:190.7700:187.4100:189.2500:3067400:147.7772 IBM:2012/11/13:188.3900:190.8800:188.2500:188.3200:3406700:147.0510 IBM:2012/11/14:189.1400:189.2700:185.2800:185.5100:4404400:144.8568 IBM:2012/11/15:185.8300:187.2000:184.7800:185.8500:3411700:145.1223 IBM:2012/11/16:186.3300:187.5600:185.1000:186.9400:4660800:145.9735 IBM:2012/11/19:188.6200:190.3900:188.3000:190.3500:3588900:148.6362 IBM:2012/11/20:189.8800:190.1100:188.2500:189.2000:3170200:147.7382 IBM:2012/11/21:189.3000:190.8900:188.9500:190.2900:5189600:148.5893 IBM:2012/11/23:191.0000:193.4900:190.8000:193.4900:3877600:151.0881 IBM:2012/11/26:192.4500:193.3700:191.7700:192.8800:5210300:150.6118 IBM:2012/11/27:192.0800:193.0500:191.0000:191.2300:3290900:149.3233 IBM:2012/11/28:190.9800:192.0400:189.2700:191.9800:3603600:149.9090 IBM:2012/11/29:192.7500:192.9000:190.2000:191.5300:4077900:149.5576 IBM:2012/11/30:191.7500:192.0000:189.5000:190.0700:4936400:148.4175 IBM:2012/12/03:190.7600:191.3000:188.3600:189.4800:3349600:147.9569 IBM:2012/12/04:189.7100:190.7500:189.0200:189.3600:3990700:147.8632 IBM:2012/12/05:189.0100:189.4600:186.9400:188.6500:4202100:147.3087 IBM:2012/12/06:189.1700:190.1800:188.4100:189.7000:3216600:148.1286 IBM:2012/12/07:190.1400:192.2000:190.1100:191.9500:4091300:149.8856 IBM:2012/12/10:192.1700:193.5400:191.6500:192.6200:3438500:150.4087 IBM:2012/12/11:193.1800:194.8000:193.1500:194.2000:4144000:151.6425 IBM:2012/12/12:194.6000:194.7500:192.4600:192.9500:4241900:150.6664 IBM:2012/12/13:192.1800:193.0500:191.3700:191.9900:3623200:149.9168 IBM:2012/12/14:191.6900:193.4000:191.2600:191.7600:3826900:149.7372 IBM:2012/12/17:191.7600:193.9100:191.7600:193.6200:3792400:151.1896 IBM:2012/12/18:193.8500:195.9200:193.8400:195.6900:4125900:152.8060 IBM:2012/12/19:195.4600:196.4500:195.0000:195.0800:4270500:152.3296 IBM:2012/12/20:194.4700:194.9500:193.1600:194.7700:4184500:152.0876 IBM:2012/12/21:193.1000:194.5000:191.5700:193.4200:8225300:151.0334 IBM:2012/12/24:193.0500:193.8300:192.0200:192.4000:1631200:150.2370 IBM:2012/12/26:192.2100:193.5300:190.9500:191.9500:2776500:149.8856 IBM:2012/12/27:191.5300:193.2800:190.7000:192.7100:3967600:150.4790 IBM:2012/12/28:191.1100:192.2200:189.8300:189.8300:3536600:148.2301 IBM:2012/12/31:189.2300:191.6800:188.8400:191.5500:4684900:149.5732 IBM:2013/01/02:194.0900:196.3500:193.8000:196.3500:4234100:153.3213 IBM:2013/01/03:195.6700:196.2900:194.4400:195.2700:3644700:152.4780 IBM:2013/01/04:194.1900:194.4600:192.7800:193.9900:3380200:151.4785 IBM:2013/01/07:193.4000:193.7800:192.3400:193.1400:2862300:150.8148 IBM:2013/01/08:192.9200:193.3000:191.6000:192.8700:3026900:150.6040 IBM:2013/01/09:193.4800:193.4900:191.6500:192.3200:3212000:150.1745 IBM:2013/01/10:192.6500:192.9600:191.2800:192.8800:3608100:150.6118 IBM:2013/01/11:194.1500:195.0000:192.9000:194.4500:3880400:151.8377 IBM:2013/01/14:192.8200:193.2800:191.7500:192.6200:4172200:150.4087 IBM:2013/01/15:191.3100:192.7300:190.3900:192.5000:4172100:150.3150 IBM:2013/01/16:192.0000:193.1800:191.3500:192.5900:2962900:150.3853 IBM:2013/01/17:193.8500:194.4600:193.2400:193.6500:3881600:151.2130 IBM:2013/01/18:194.0300:195.0000:193.8000:194.4700:4559600:151.8533 IBM:2013/01/22:194.3600:196.0800:194.0100:196.0800:7172300:153.1105 IBM:2013/01/23:203.5000:208.5800:203.3600:204.7200:12530500:159.8571 IBM:2013/01/24:203.9100:205.0600:203.0800:204.4200:4447200:159.6228 IBM:2013/01/25:204.4500:205.1800:204.1300:204.9700:3358900:160.0523 IBM:2013/01/28:204.8500:206.2200:204.2900:204.9300:2823700:160.0211 IBM:2013/01/29:204.3400:205.7300:203.6400:203.9000:3617100:159.2167 IBM:2013/01/30:203.6900:204.8800:203.1900:203.5200:2998400:158.9201 IBM:2013/01/31:203.3200:204.4700:202.9600:203.0700:3091400:158.5687 IBM:2013/02/01:204.6500:205.3500:203.8400:205.1800:3370700:160.2163 IBM:2013/02/04:204.1900:205.0200:203.5700:203.7900:3188800:159.1309 IBM:2013/02/05:204.3100:204.7500:202.5100:202.7900:3636900:158.3501 IBM:2013/02/06:200.3900:201.2900:199.5600:201.0200:3624200:157.6286 IBM:2013/02/07:200.6200:200.9100:198.6800:199.7400:3076700:156.6249 IBM:2013/02/08:199.9700:202.0900:199.6800:201.6800:2893300:158.1461 IBM:2013/02/11:200.9800:201.9500:199.7500:200.1600:2944700:156.9543 IBM:2013/02/12:200.0100:200.7400:199.0200:200.0400:2461800:156.8602 IBM:2013/02/13:200.6500:200.9500:199.5700:200.0900:2169800:156.8994 IBM:2013/02/14:199.7300:200.3200:199.2600:199.6500:3294200:156.5544 IBM:2013/02/15:199.9800:201.2500:199.8200:200.9800:3627800:157.5973 IBM:2013/02/19:200.6000:201.8900:200.2200:200.3200:2998300:157.0797 IBM:2013/02/20:200.6200:201.7200:198.8600:199.3100:3715400:156.2877 IBM:2013/02/21:198.6300:199.0700:198.1100:198.3300:3922900:155.5193 IBM:2013/02/22:199.2300:201.0900:198.8400:201.0900:3107900:157.6835 IBM:2013/02/25:201.6700:202.4900:197.5100:197.5100:3844800:154.8763 IBM:2013/02/26:198.6300:199.9000:197.8400:199.1400:3391600:156.1544 IBM:2013/02/27:198.8900:202.7500:198.6000:202.3300:4185100:158.6559 IBM:2013/02/28:202.1800:203.1200:200.7900:200.8300:4689200:157.4796 IBM:2013/03/01:200.6500:202.9400:199.3600:202.9100:3308300:159.1106 IBM:2013/03/04:202.5900:205.1900:202.5500:205.1900:3693400:160.8985 IBM:2013/03/05:205.8600:207.7000:205.6900:206.5300:3807800:161.9493 IBM:2013/03/06:207.0300:208.4900:206.6600:208.3800:3594800:163.3999 IBM:2013/03/07:208.2900:209.6000:208.2400:209.4200:3884400:164.2154 IBM:2013/03/08:209.8500:210.7400:209.4300:210.3800:3700500:164.9682 IBM:2013/03/11:210.0400:210.2000:209.0400:210.0800:3049700:164.7330 IBM:2013/03/12:209.4000:210.7300:209.0900:210.5500:3591600:165.1015 IBM:2013/03/13:210.2000:212.3600:209.7700:212.0600:3355900:166.2856 IBM:2013/03/14:212.1500:215.8600:212.1500:215.8000:5505500:169.2183 IBM:2013/03/15:215.3800:215.9000:213.4100:214.9200:7930300:168.5283 IBM:2013/03/18:212.9000:214.5000:212.6400:213.2100:3006000:167.1874 IBM:2013/03/19:214.1300:215.1200:211.8300:213.4400:3198600:167.3677 IBM:2013/03/20:214.7600:215.8200:214.3000:215.0600:3019000:168.6380 IBM:2013/03/21:212.9600:213.0000:210.1100:212.2600:5830400:166.4424 IBM:2013/03/22:212.2100:213.1700:211.6200:212.0800:3031500:166.3013 IBM:2013/03/25:212.5400:212.8100:210.0500:210.7400:3242500:165.2505 IBM:2013/03/26:211.7700:212.5000:211.5000:212.3600:2300300:166.5208 IBM:2013/03/27:210.9600:212.1600:210.1000:210.8900:3223400:165.3681 IBM:2013/03/28:209.8300:213.4400:209.7400:213.3000:3749900:167.2579 IBM:2013/04/01:212.8000:213.5000:211.2500:212.3800:2144500:166.5365 IBM:2013/04/02:212.9200:214.7100:211.5700:214.3600:3116800:168.0891 IBM:2013/04/03:214.3200:214.8900:212.6300:212.6600:3128800:166.7561 IBM:2013/04/04:212.7700:212.9300:210.0400:211.3100:3709500:165.6975 IBM:2013/04/05:209.1000:209.8400:206.3400:209.4100:4148000:164.2076 IBM:2013/04/08:209.0700:209.3200:207.3300:209.3200:2351200:164.1370 IBM:2013/04/09:209.5600:210.0000:208.6100:209.2200:2577300:164.0587 IBM:2013/04/10:211.8600:212.5100:210.4700:212.0000:3524200:166.2385 IBM:2013/04/11:211.5000:213.0900:210.8500:212.9200:3655600:166.9599 IBM:2013/04/12:210.7900:211.4900:209.7800:211.3800:3165400:165.7524 IBM:2013/04/15:210.4400:211.6900:209.2600:209.2600:4218100:164.0900 IBM:2013/04/16:210.8400:212.0000:209.2000:212.0000:2859500:166.2385 IBM:2013/04/17:210.5300:211.0900:209.5000:209.6700:3269800:164.4115 IBM:2013/04/18:210.1100:210.2500:206.1500:207.1500:6502000:162.4354 IBM:2013/04/19:195.7400:196.5000:189.7600:190.0000:18847000:148.9874 IBM:2013/04/22:191.1500:191.3800:187.6800:187.8300:9845400:147.2858 IBM:2013/04/23:189.2700:192.5500:188.6100:191.6100:6296100:150.2498 IBM:2013/04/24:192.1600:192.5000:191.0900:191.7100:3693900:150.3283 IBM:2013/04/25:192.6900:195.1700:192.3200:193.9500:4650900:152.0847 IBM:2013/04/26:194.1600:194.7100:193.2500:194.3100:3490300:152.3671 IBM:2013/04/29:194.7800:199.6800:194.6500:199.1500:5932400:156.1623 IBM:2013/04/30:199.1300:202.5900:197.3500:202.5400:7539900:158.8206 IBM:2013/05/01:201.8700:202.1700:199.2000:199.6300:4899100:156.5387 IBM:2013/05/02:200.1200:202.4300:199.7500:202.3900:3945100:158.7029 IBM:2013/05/03:203.9400:205.3200:203.7500:204.5100:4492700:160.3653 IBM:2013/05/06:203.7900:203.9900:201.5200:202.7800:4826500:159.0087 IBM:2013/05/07:202.8100:203.7300:201.6500:203.6300:3451000:159.6753 IBM:2013/05/08:202.9400:204.8500:202.5100:204.8200:3601700:161.3612 IBM:2013/05/09:204.6900:205.0000:202.7200:203.2400:3542300:160.1164 IBM:2013/05/10:203.3700:204.5300:202.8200:204.4700:3279200:161.0855 IBM:2013/05/13:204.1800:204.4700:202.2200:202.4700:3648400:159.5098 IBM:2013/05/14:202.0900:203.6700:202.0800:203.2100:3699700:160.0928 IBM:2013/05/15:202.2500:203.6800:202.0400:203.3200:4028100:160.1795 IBM:2013/05/16:204.0000:206.5900:204.0000:204.6900:4507000:161.2587 IBM:2013/05/17:205.2500:209.5000:204.9900:208.4400:5704100:164.2131 IBM:2013/05/20:208.0200:209.1500:207.4200:207.6000:3614500:163.5513 IBM:2013/05/21:207.2400:209.4800:207.0000:208.6500:2982900:164.3785 IBM:2013/05/22:208.5000:210.1500:206.1200:206.9900:5186800:163.0707 IBM:2013/05/23:205.4800:208.6100:205.1300:206.1600:5179100:162.4169 IBM:2013/05/24:204.6900:206.4300:204.4200:205.7200:3295800:162.0702 IBM:2013/05/28:207.4400:208.5900:207.1600:207.7800:4011500:163.6931 IBM:2013/05/29:206.2200:208.4200:206.0400:207.9200:3439400:163.8034 IBM:2013/05/30:206.9700:210.9900:206.9700:209.3600:4399700:164.9379 IBM:2013/05/31:208.5900:211.9800:208.0200:208.0200:4949000:163.8822 IBM:2013/06/03:208.2500:210.0500:207.3300:208.9500:3322000:164.6149 IBM:2013/06/04:208.6000:209.0000:205.0800:206.1900:3598300:162.4405 IBM:2013/06/05:205.1600:206.2900:202.4000:202.7400:4215400:159.7225 IBM:2013/06/06:203.0000:204.2000:201.4700:203.8000:3679400:160.5576 IBM:2013/06/07:204.8500:206.3500:204.1100:206.3500:2953400:162.5666 IBM:2013/06/10:206.9700:206.9800:204.9000:205.0200:2652300:161.5188 IBM:2013/06/11:203.1700:205.9500:202.5100:203.9800:2814400:160.6994 IBM:2013/06/12:204.5900:205.2300:200.5100:201.2000:3409800:158.5093 IBM:2013/06/13:201.0100:204.4000:200.3400:203.7700:2899500:160.5339 IBM:2013/06/14:203.9700:204.7400:201.8100:202.2000:2804500:159.2971 IBM:2013/06/17:203.4400:205.1700:202.5500:203.0400:3219900:159.9588 IBM:2013/06/18:203.0200:206.0900:202.8700:204.8700:3277800:161.4006 IBM:2013/06/19:204.4400:205.0300:201.9300:201.9400:2846100:159.0923 IBM:2013/06/20:200.6700:201.7000:197.2800:197.3500:4514800:155.4762 IBM:2013/06/21:198.5000:198.5200:193.5400:195.4600:8914800:153.9872 IBM:2013/06/24:193.9900:194.9100:191.3400:193.5400:4356200:152.4746 IBM:2013/06/25:195.3100:195.5900:193.1500:194.9800:3846200:153.6090 IBM:2013/06/26:195.9700:196.2300:194.5000:194.8600:3320200:153.5145 IBM:2013/06/27:196.0500:196.9000:195.2900:195.6500:3514200:154.1368 IBM:2013/06/28:191.7300:192.6600:188.4100:191.1100:10055800:150.5602 IBM:2013/07/01:192.1500:193.8000:191.1200:191.2800:4442400:150.6941 Finance-QuoteHist-1.29/t/dat/csv.dat0000644000175000017500000000326612453534725015760 0ustar sisksiskDate,Open,High,Low,Close,Volume,Adj. Close* 20-Jul-05,83.20,84.96,82.99,84.60,9323200,84.60 19-Jul-05,84.72,85.11,83.70,83.70,14149700,83.70 18-Jul-05,81.99,83.94,81.68,81.81,8705600,81.81 15-Jul-05,82.43,82.75,81.52,82.38,7644100,82.38 14-Jul-05,82.00,82.67,82.00,82.42,8546200,82.42 13-Jul-05,81.16,81.75,81.16,81.45,11340000,81.45 12-Jul-05,79.20,80.49,79.18,80.04,10945500,80.04 11-Jul-05,79.40,79.52,78.52,78.96,7186700,78.96 8-Jul-05,77.38,79.52,77.14,79.30,13440500,79.30 7-Jul-05,75.15,77.53,75.00,77.38,10757200,77.38 6-Jul-05,74.80,76.15,74.40,75.81,8009300,75.81 5-Jul-05,74.38,74.97,74.16,74.79,5181800,74.79 1-Jul-05,74.30,75.33,74.30,74.67,4353100,74.67 30-Jun-05,74.80,75.49,74.07,74.20,6127000,74.20 29-Jun-05,75.26,75.68,74.62,74.73,5519100,74.73 28-Jun-05,73.83,75.30,73.45,75.30,8188800,75.30 27-Jun-05,74.01,74.77,73.50,73.88,6489800,73.88 24-Jun-05,75.17,75.40,74.00,74.01,10792100,74.01 23-Jun-05,76.81,76.97,75.06,75.41,7766700,75.41 22-Jun-05,76.83,77.49,76.67,77.23,5646300,77.23 21-Jun-05,76.70,77.00,76.11,76.41,5339100,76.41 20-Jun-05,76.03,76.98,75.57,76.55,3745600,76.55 17-Jun-05,77.70,77.73,76.38,76.39,8593800,76.39 16-Jun-05,76.40,77.25,76.31,77.05,7840000,77.05 15-Jun-05,75.70,76.50,75.15,76.30,7103600,76.30 14-Jun-05,75.05,75.43,74.73,74.89,4314900,74.89 13-Jun-05,74.50,75.93,74.45,75.05,5715700,75.05 10-Jun-05,74.75,75.05,74.10,74.77,4895800,74.77 9-Jun-05,74.58,75.47,74.23,74.93,4423200,74.93 8-Jun-05,75.04,75.40,74.63,74.80,4280000,74.80 7-Jun-05,75.00,76.09,75.00,75.04,5226600,75.04 6-Jun-05,75.80,75.90,74.92,75.00,5978600,75.00 3-Jun-05,77.06,77.10,75.74,75.79,6149900,75.79 2-Jun-05,76.75,77.39,76.68,77.35,4025600,77.35 1-Jun-05,75.57,77.50,75.57,76.84,7380600,76.84 Finance-QuoteHist-1.29/t/dat/quote_weekly_yahoo.dat0000644000175000017500000000730213554074436021075 0ustar sisksiskFinance::QuoteHist::Yahoo IBM,2012/07/01,2013/07/01 IBM:2012/06/25:192.4800:195.8100:188.9000:195.5800:18637100:151.4082 IBM:2012/07/02:196.3600:197.2000:189.7400:191.4100:11920500:148.1799 IBM:2012/07/09:190.7600:191.1400:181.8500:186.0100:22998800:143.9995 IBM:2012/07/16:185.5800:196.8500:183.2000:192.4500:31507600:148.9851 IBM:2012/07/23:189.7800:197.4100:188.2000:196.3900:18795600:152.0352 IBM:2012/07/30:196.3200:198.9500:193.0200:198.5200:14944000:153.6842 IBM:2012/08/06:198.7600:200.8800:197.2400:199.2900:12376100:154.2802 IBM:2012/08/13:198.8800:202.0000:197.7200:201.2200:12373700:156.4395 IBM:2012/08/20:200.6900:201.1300:194.2000:197.7700:14140300:153.7573 IBM:2012/08/27:197.9600:198.3000:193.1800:194.8500:13113600:151.4870 IBM:2012/09/03:196.6100:199.5000:193.2500:199.5000:15172300:155.1022 IBM:2012/09/10:199.3900:207.6500:198.7300:206.8100:19339300:160.7854 IBM:2012/09/17:205.5500:207.9900:205.3000:205.9800:24867400:160.1401 IBM:2012/09/24:205.0200:208.3200:203.9000:207.4500:17962400:161.2830 IBM:2012/10/01:208.0100:211.7900:207.9400:210.5900:17786100:163.7242 IBM:2012/10/08:210.0200:210.7400:205.0500:207.8000:15209500:161.5551 IBM:2012/10/15:208.8800:211.0000:193.1800:193.3600:37283100:150.3287 IBM:2012/10/22:193.7100:194.6800:190.5600:193.2700:19824000:150.2587 IBM:2012/10/29:194.8000:198.0000:193.2900:193.4300:14071200:150.3831 IBM:2012/11/05:192.3600:196.7500:188.8800:189.6400:17977200:147.4365 IBM:2012/11/12:190.0300:190.8800:184.7800:186.9400:18951000:145.9735 IBM:2012/11/19:188.6200:193.4900:188.2500:193.4900:15826300:151.0881 IBM:2012/11/26:192.4500:193.3700:189.2700:190.0700:21119100:148.4175 IBM:2012/12/03:190.7600:192.2000:186.9400:191.9500:18850300:149.8856 IBM:2012/12/10:192.1700:194.8000:191.2600:191.7600:19274500:149.7372 IBM:2012/12/17:191.7600:196.4500:191.5700:193.4200:24598600:151.0334 IBM:2012/12/24:193.0500:193.8300:189.8300:189.8300:11911900:148.2301 IBM:2012/12/31:189.2300:196.3500:188.8400:193.9900:15943900:151.4785 IBM:2013/01/07:193.4000:195.0000:191.2800:194.4500:16589700:151.8377 IBM:2013/01/14:192.8200:195.0000:190.3900:194.4700:19748400:151.8533 IBM:2013/01/21:194.3600:208.5800:194.0100:204.9700:27508900:160.0523 IBM:2013/01/28:204.8500:206.2200:202.9600:205.1800:15901300:160.2163 IBM:2013/02/04:204.1900:205.0200:198.6800:201.6800:16419900:157.4833 IBM:2013/02/11:200.9800:201.9500:199.0200:200.9800:14498300:157.5973 IBM:2013/02/18:200.6000:201.8900:198.1100:201.0900:13744500:157.6835 IBM:2013/02/25:201.6700:203.1200:197.5100:202.9100:19419000:159.1106 IBM:2013/03/04:202.5900:210.7400:202.5500:210.3800:18680900:164.9682 IBM:2013/03/11:210.0400:215.9000:209.0400:214.9200:23433000:168.5283 IBM:2013/03/18:212.9000:215.8200:210.1100:212.0800:18085500:166.3013 IBM:2013/03/25:212.5400:213.4400:209.7400:213.3000:12516100:167.2579 IBM:2013/04/01:212.8000:214.8900:206.3400:209.4100:16247600:164.2076 IBM:2013/04/08:209.0700:213.0900:207.3300:211.3800:15273700:165.7524 IBM:2013/04/15:210.4400:212.0000:189.7600:190.0000:35696400:148.9874 IBM:2013/04/22:191.1500:195.1700:187.6800:194.3100:27976600:152.3671 IBM:2013/04/29:194.7800:205.3200:194.6500:204.5100:26809200:160.3653 IBM:2013/05/06:203.7900:205.0000:201.5200:204.4700:18700700:160.3339 IBM:2013/05/13:204.1800:209.5000:202.0400:208.4400:21587300:164.2131 IBM:2013/05/20:208.0200:210.1500:204.4200:205.7200:20259100:162.0702 IBM:2013/05/27:207.4400:211.9800:206.0400:208.0200:16799600:163.8822 IBM:2013/06/03:208.2500:210.0500:201.4700:206.3500:17768500:162.5666 IBM:2013/06/10:206.9700:206.9800:200.3400:202.2000:14580500:159.2971 IBM:2013/06/17:203.4400:206.0900:193.5400:195.4600:22773400:153.9872 IBM:2013/06/24:193.9900:196.9000:188.4100:191.1100:25092600:150.5602 IBM:2013/07/01:192.1500:195.1600:190.2600:194.9300:12378100:153.5696 Finance-QuoteHist-1.29/t/testload.pm0000644000175000017500000000727713123017016016066 0ustar sisksiskpackage testload; use strict; use Test::More; use File::Spec; use LWP::UserAgent; use HTTP::Request; use Finance::QuoteHist; use constant DEV_TESTS => $ENV{FQH_DEV_TESTS}; use constant GOLDEN_CHILD => 'yahoo'; use vars qw( @ISA @EXPORT ); require Exporter; @ISA = qw(Exporter); @EXPORT = qw( network_ok new_quotehist modules all_modules sources modes granularities basis csv_content GOLDEN_CHILD DEV_TESTS ); my($Dat_Dir, $Mod_Dir); BEGIN { my($vol, $dir, $file) = File::Spec->splitpath(__FILE__); my @parts = File::Spec->splitdir($dir); pop @parts while @parts && $parts[-1] ne 't'; my $ddir = File::Spec->catdir(@parts, 'dat'); $Dat_Dir = File::Spec->catpath($vol, $ddir, ''); pop @parts; my $mdir = File::Spec->catdir(@parts, 'lib', 'Finance', 'QuoteHist'); $Mod_Dir = File::Spec->catpath($vol, $mdir, ''); } my $csv_txt; my $csv_file = "$Dat_Dir/csv.dat"; open(F, '<', $csv_file) or die "problem reading $csv_file : $!"; $csv_txt = join('', ); close(F); sub csv_content { $csv_txt } my(%Modules, %Files); for my $f (glob("$Dat_Dir/*.dat")) { my($vol, $dir, $label) = File::Spec->splitpath($f); $label =~ s/\.dat$//; next unless $label =~ /^(quote|dividend|split)_/; open(F, '<', $f) or die "problem reading $f : $!"; my @lines = ; chomp @lines; close(F); my $class = shift @lines; ++$Modules{$class}; my($sym, $start, $end) = split(/,/, shift @lines); if ($1 eq 'quote') { my($mode, $gran, $source) = split(/_/, $label); if ($lines[0] =~ tr/:/:/ > 5) { # drop adjusted and volume, they've proven to be too # variable for testing for my $i (0 .. $#lines) { my @line = split(/:/, $lines[$i]); pop @line while @line > 6; $lines[$i] = join(':', @line); } } $Files{$source}{$mode}{$gran} = [$class, $sym, $start, $end, \@lines]; } else { my($mode, $source) = split(/_/, $label); $Files{$source}{$mode} = [$class, $sym, $start, $end, \@lines]; } } my $Network_Up; sub network_ok { if (! defined $Network_Up) { my %ua_parms; if ($ENV{http_proxy}) { $ua_parms{env_proxy} = 1; } my $ua = LWP::UserAgent->new(%ua_parms) or die "Problem creating user agent\n"; #my $request = HTTP::Request->new('HEAD', 'https://finance.yahoo.com') my $request = HTTP::Request->new('HEAD', 'https://www.google.com') or die "Problem creating http request object\n"; my $response = $ua->request($request, @_); $Network_Up = $response->is_redirect || $response->is_success; if (!$Network_Up) { print STDERR "Problem with net fetch: ", $response->status_line, "\n"; } } $Network_Up; } sub new_quotehist { my($symbols, $start_date, $end_date, %parms) = @_; my $class = $parms{class} || 'Finance::QuoteHist'; delete $parms{class}; $class->new( symbols => $symbols, start_date => $start_date, end_date => $end_date, auto_proxy => 1, %parms, ); } sub modules { sort keys %Modules } sub sources { sort keys %Files } sub modes { my $src = shift || return; my $h = $Files{$src} || return; sort keys %$h; } sub granularities { my $src = shift || return; my $h = $Files{$src}{quote} || return; sort keys %$h; } sub basis { my($src, $mode, $gran) = @_; my $basis; if ($mode eq 'quote') { $basis = $Files{$src}{$mode}{$gran}; } else { $basis = $Files{$src}{$mode}; } return unless $basis; @$basis; } sub all_modules { my %mods; for my $f (glob "$Mod_Dir/*.pm") { my($vol, $dir, $base) = File::Spec->splitpath($f); $base =~ s/\.pm$//; next if $base eq 'Generic'; $mods{lc($base)} = "Finance::QuoteHist::$base"; } $mods{plain} = "Finance::QuoteHist"; wantarray ? %mods : \%mods; } 1; Finance-QuoteHist-1.29/MANIFEST0000644000175000017500000000140313563020544014570 0ustar sisksiskREADME Changes LICENSE MANIFEST Makefile.PL lib/Finance/QuoteHist.pm lib/Finance/QuoteHist/Generic.pm lib/Finance/QuoteHist/Yahoo.pm lib/Finance/QuoteHist/Google.pm lib/Finance/QuoteHist/DailyFinance.pm t/00_basic.t t/05_csv.t t/10_quotes.t t/20_dividends.t t/30_splits.t t/testload.pm t/dat/csv.dat t/dat/quote_daily_dailyfinance.dat t/dat/quote_daily_google.dat t/dat/quote_daily_plain.dat t/dat/quote_daily_yahoo.dat t/dat/quote_weekly_google.dat t/dat/quote_weekly_yahoo.dat t/dat/quote_monthly_yahoo.dat t/dat/dividend_yahoo.dat t/dat/dividend_plain.dat t/dat/split_plain.dat t/dat/split_yahoo.dat META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Finance-QuoteHist-1.29/META.yml0000664000175000017500000000116013563020544014712 0ustar sisksisk--- abstract: unknown author: - unknown build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' license: unknown meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Finance-QuoteHist no_index: directory: - t - inc requires: Date::Manip: '0' HTML::TableExtract: '2.07' HTTP::Request: '0' LWP::UserAgent: '0' MIME::Base64: '0' Text::CSV: '0' version: '1.29' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Finance-QuoteHist-1.29/Changes0000644000175000017500000001733713563020422014742 0ustar sisksiskRevision history for Perl extension Finance::QuoteHist. 1.29 Wed Nov 13 10:27:10 EST 2019 - Fixed (harmless) dependency on google module 1.28 Wed Oct 23 12:11:32 EDT 2019 - Updated yahoo test data again - Retired Finance::QuoteHist::Google (no data) - Retired Finance::QuoteHist::DailyFinance (no data) 1.27 Tue Jul 18 10:46:49 EDT 2017 - Updated yahoo test data 1.26 Tue Jun 27 14:19:13 EDT 2017 - Fixed Yahoo module to handle the site's obfuscation - updated test data - removed defunct StockNod 1.24 Mon Aug 3 15:55:29 EDT 2015 - removed some deprecated Date::Manip config statements - removed outmoded fetchers 1.23 Thu May 21 11:48:11 EDT 2015 - fixed divident/split entanglement for yahoo - patched quotes test to properly skip tests without network connectivity (RT #104358) 1.22 Mon Apr 20 14:15:32 EDT 2015 - fixed dividend/split entanglement on yahoo rt.cpan.org #103759 1.21 Fri Jan 9 11:25:10 EST 2015 - fixed splits on yahoo - removed BusinessWeek 1.20 Tue Oct 8 14:54:48 EDT 2013 - added DailyFinance - removed QuoteMedia, MSN, Investopedia 1.19 Mon Aug 29 11:08:08 EDT 2011 - fixed test data window (1.17 was a little *too* recent for reliable monthly tests) - 1.18 skipped, CPAN version appeasement 1.17 Fri Aug 26 19:31:05 EDT 2011 - Updated tests with more recent data 1.16 Thu Sep 30 01:00:27 EDT 2010 - Fixed tests so that they no longer rely upon historical adjusted quotes since these appear to change every now and then whenever Yahoo decides to tweak their algorithm. - typo fix 1.14 Mon Jun 7 18:36:30 EDT 2010 - Fixed several syntax issues that were tripping up -w - Fixed the utf8 Byte Order Mark issues with google csv - Added modules for Investopedia.com and StockNod.com - Expanded site-specific tests (for development mostly) - Test data updated, docs refreshed and tweaked 1.12 Mon Aug 3 18:40:15 EDT 2009 - Fixed various site-specific modules - F::Q::Yahoo_AU is deprecated in favor of F::Q::Yahoo::Australia (though the old package is still around for backwards compat) - moved CSV class discovery to package level (thanks to Edmond Shum) - fixed the 'unsigned integer' problem with volumes (thanks to 'o kite') - fixed some undef warnings and typos (thanks to Jim Miner) 1.11 Mon Jun 25 19:21:21 EDT 2007 - Added Google and Yahoo_AU - Rewrote BusinessWeek, decodes from javascript channels now, since they changed how their site operates. No more intraday quotes though. 1.10 Tue Mar 13 13:57:07 EDT 2007 - Tweaked Yahoo test data yet again (they seem to be rounding off their volumes now) - Fixed undefined error for symbols with no closing (thanks to Terry Gliedt) - Added BusinessWeek to lineup, including intraday functionality. - Flattened champion delegation. - Added labels() documentation - Implemented user-defined row filtering callback (thanks to Manoj Bhatti for the suggestion) - Added 'shares' as possible label for 'vol' (thanks again to Manoj) 1.09 Thu Jun 8 16:19:57 EDT 2006 - Fixed ^DJI test data (again) since yahoo keeps changing how they represent volume (thanks to Ivo Welch) 1.08 Sat May 6 15:16:11 EDT 2006 - Fixed ^DJI test data for quote tests (Yahoo switched to the volume for each anchor date rather than aggregate volumes) - splits() now returns ref properly in scalar context for F::QH::Yahoo. Thanks to Raymond de Leon for spotting. 1.07 Thu Mar 16 00:44:24 EST 2006 - Internal restructure to date iterators where appropriate. - Doc fix for MSN. - Various minor edge cases fixed. 1.06 Fri Mar 10 15:59:05 EST 2006 - Added MSN quote source. - Fixed some edge cases involving null-queries on splits. - Various minor fixes, including proper business-day calculations for end of month dates on montly resolutions, plus proper newline stripping (chomp didn't work on MSN csv). 1.05 Tue Feb 28 16:47:57 EST 2006 - Splits now have no dependencies on dividend queries - Daily, Weekly, Monthly granularities available - Restored quote_source(), dividend_source(), and split_source() methods - More tests added 1.04 Fri Jan 6 12:18:59 EST 2006 - Compensated for yahoo symbol switch from ^DJA to ^DJI, which was causing some tests to fail. - Updated quotemedia url. 1.03 Wed Nov 2 16:52:30 EST 2005 - Minor bug fixes - Forced date calcs to GMT to avoid some cygwin/win errors - Added auto_proxy, enabled during tests so that if $ENV{HTTP_PROXY} is present $ua->env_proxy will be automatically invoked - Tests will skip if network connectivity cannot be established 1.02 Wed Aug 3 14:46:43 EDT 2005 - Fixed Date::Manip requirement that was inadvertently dropped. 1.01 Thu Jul 21 13:29:54 EDT 2005 - Added conditional support for Text::CSV_PP in cases where Text::CSV_XS is not installed - Split out tests 1.00 Thu Jun 23 16:25:53 EDT 2005 - Reworked Yahoo! queries for new interface - Added quotes from QuoteMedia (they supply SiliconInvestor, so that was dropped) - Date constraints relaxed. No end date defaults to 'today', no start date defaults to inception. Having neither date grabs entire history. ! Internal API changed from symbolic dereferencing to supplying closures and iterators for particular targets and parse modes. 0.33 - Broke up Yahoo! csv queries into blocks of 1000, as apparently Yahoo! sometimes puts a cap on these sort of queries (thanks to Jay Strauss) 0.32 Thu Aug 14 13:37:36 EDT 2003 - Fixed csv parsing for Yahoo dividend data after they fixed their format - Updated test data 0.31 Thu Jan 16 19:20:31 GMT 2003 - Rolled back some 5.6 specific syntax 0.30 Fri Nov 8 16:47:07 EST 2002 - adjusted volume fix (column labeling snafu) - extraneous status messages fixed for split extrations - added filtered split and filtered dividend tests 0.29 Thu Oct 17 04:36:05 EDT 2002 - Patched Yahoo URL interface since they switched to January = 00 rather than 01. 0.28 Thu Feb 28 08:23:08 CST 2002 - Updated Yahoo interface, incorporating new changes. - added proxy_env parameter and method to ease proxy handling via LWP::UserAgent - Added WallStreetCity.pm (basic source) - Removed FinancialWeb.pm (apparently defunct) - Removed MotleyFool.pm (no longer offers tabular data, plus login now required for historical info) 0.25 Mon Jan 8 19:35:00 CST 2001 - Doc cleanup 0.24 Wed Nov 29 02:28:59 CST 2000 - Replaced FinancialWeb with SiliconInvestor in order to provide information on defunct ticker symbols -- FinancialWeb ceased coverage of these symbols. 0.23 Thu Nov 2 15:19:00 CST 2000 - Fixed cache bug with Yahoo::source_type() - Documentation revisions and fixes 0.21 Wed Sep 13 13:25:35 CDT 2000 - Splits and dividends added - Much faster queries, primarily due to taking advantage of Yahoo and CSV-formatted data, when available - Supports grabbing non-adjusted data, as well as notification when transitioning to a site that cannot provide non-adjusted data - Supports auto-adjusting of non-adjusted quotes for tables that include an adjusted column (like Yahoo in HTML mode) - Information pedigree support -- i.e., from which site did the quote/split/dividend data come for a particular ticker symbol - Revised interface, although the old one is mostly still supported (read the docs!) - Defunct ticker symbols still supported (quotes only, no split/dividend) 0.01 Wed Feb 2 18:24:07 CST 2000 - Initial version. Finance-QuoteHist-1.29/META.json0000664000175000017500000000206613563020544015070 0ustar sisksisk{ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Finance-QuoteHist", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Date::Manip" : "0", "HTML::TableExtract" : "2.07", "HTTP::Request" : "0", "LWP::UserAgent" : "0", "MIME::Base64" : "0", "Text::CSV" : "0" } } }, "release_status" : "stable", "version" : "1.29", "x_serialization_backend" : "JSON::PP version 2.27400_02" } Finance-QuoteHist-1.29/README0000644000175000017500000000536612453534725014343 0ustar sisksiskFinance-QuoteHist ----------------- The Finance-QuoteHist bundle is several modules designed to fetch historical stock quotes from the web: Finance::QuoteHist ------------------ Top level aggregator that will select a default lineup of site instances from which to retrieve quotes. Other than the default lineup, this module behaves identically to whichever site-specific module is first in the lineup. See below for site-specific modules. Finance::QuoteHist::Generic --------------------------- The real workhorse of the bundle, a sub class of LWP::UserAgent. Site-specific modules are sub classes of this module. INSTALLATION You install HTML-Element-Extended, as you would install any perl library, by running these commands: perl Makefile.PL make make test make install DOCUMENTATION POD style documentation is included with each module. This is normally converted to a manual page and installed as part of the "make install" process. You should also be able to use the 'perldoc' utility to extract and read documentation from the module file directly. See Changes for recent changes. SUPPORT The source code is tracked on GitHub: https://github.com/mojotoad/Finance-QuoteHist Problems can be reported there. Questions and comments can also be directed to Matt Sisk AVAILABILITY In addition to GitHub, the library is available from CPAN: http://www.cpan.org/authors/id/M/MS/MSISK/ DISCLAIMER The data returned from these modules is in no way guaranteed, nor are the developers responsible in any way for how this data (or lack thereof) is used. The interface is based on URLs and page layouts that might change at any time. Even though these modules are designed to be adaptive under these circumstances, they will at some point probably be unable to retrieve data unless fixed or provided with new parameters. Furthermore, the data from these web sites is usually not even guaranteed by the web sites themselves, and oftentimes is acquired elsewhere. See the documentation for each site-specific module for more information regarding the disclaimer for that site. ACKNOWLEDGEMENTS Thanks to Jim Miner for his generous efforts testing this module. Thanks to Josh Woodward for some pointers on the Yahoo! interface. Thanks to Jacob Anawalt for pointers on the Yahoo! interface and suggestions on open-ended date queries. Thanks to Paul McDermott and Jay Strauss for patches. Thanks to Manoj Bhatti, Bernd Hacker, Christian von Engelbrechten, Gary Nielson, Mike Brown, Bill Stephenson, and Robert S. Weigel for their bug-spotting. COPYRIGHT Copyright (c) 1999-2013 Matthew P. Sisk. All rights reserved. All wrongs revenged. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Finance-QuoteHist-1.29/LICENSE0000644000175000017500000004343612453534725014470 0ustar sisksiskThis is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program 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 1, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Finance-QuoteHist-1.29/lib/0000755000175000017500000000000013563020544014207 5ustar sisksiskFinance-QuoteHist-1.29/lib/Finance/0000755000175000017500000000000013563020544015552 5ustar sisksiskFinance-QuoteHist-1.29/lib/Finance/QuoteHist/0000755000175000017500000000000013563020544017477 5ustar sisksiskFinance-QuoteHist-1.29/lib/Finance/QuoteHist/Yahoo.pm0000644000175000017500000002025413554073523021124 0ustar sisksiskpackage Finance::QuoteHist::Yahoo; use strict; use vars qw(@ISA $VERSION); use Carp; $VERSION = "1.26"; use Finance::QuoteHist::Generic; @ISA = qw(Finance::QuoteHist::Generic); use Date::Manip; # https://query1.finance.yahoo.com/v7/finance/download/IBM?period1=1495391410&period2=1498069810&interval=1d&events=history&crumb=bB6k340lPXt # https://query1.finance.yahoo.com/v7/finance/download/IBM?period1=993096000&period2=1498017600&interval=1wk&events=history&crumb=bB6k340lPXt # https://query1.finance.yahoo.com/v7/finance/download/IBM?period1=993096000&period2=1498017600&interval=1mo&events=history&crumb=bB6k340lPXt # # Dividends: # https://query1.finance.yahoo.com/v7/finance/download/IBM?period1=993096000&period2=1498017600&interval=1d&events=div&crumb=bB6k340lPXt # # Splits: # https://query1.finance.yahoo.com/v7/finance/download/NKE?period1=993096000&period2=1498017600&interval=1d&events=split&crumb=bB6k340lPXt sub new { my $that = shift; my $class = ref($that) || $that; my %parms = @_; $parms{parse_mode} = 'csv'; $parms{ua_params} ||= {}; $parms{ua_params}{cookie_jar} ||= {}; my $self = __PACKAGE__->SUPER::new(%parms); bless $self, $class; # set initial cookie (the cookie crumbs are hashed out of this) # https://finance.yahoo.com/quote/IBM/history my $ticker = $parms{symbols}; $ticker = $ticker->[0] if ref $ticker eq 'ARRAY'; my $html = $self->fetch("https://finance.yahoo.com/quote/$ticker/history"); open(F, ">/tmp/hmm.html"); print F $html; close(F); # extract the cookie crumb my %crumbs; for my $c ($html =~ /"crumb"\s*:\s*"([^"]+)"/g) { next if $c =~ /[{}]/; $c =~ s/\\u002F/\//; ++$crumbs{$c}; } my $crumb = ''; my $max = 0; for my $c (keys %crumbs) { if ($crumbs{$c} >= $max) { $crumb = $c; $max = $crumbs{$c}; } } $self->{crumb} = $crumb; $self; } sub granularities { qw( daily weekly monthly ) } sub labels { my $self = shift; my %parms = @_; my $target_mode = $parms{target_mode} || $self->target_mode; my @labels; if ($target_mode eq 'split') { @labels = qw( date stock ); } else { @labels = $self->SUPER::labels(%parms); push(@labels, 'adj') if $target_mode eq 'quote'; } @labels; } sub url_maker { my($self, %parms) = @_; my $target_mode = $parms{target_mode} || $self->target_mode; my $parse_mode = $parms{parse_mode} || $self->parse_mode; # *always* block unknown target mode and parse mode combinations for # cascade to work properly! return undef unless $target_mode eq 'quote' || $target_mode eq 'split' || $target_mode eq 'dividend'; return undef unless $parse_mode eq 'html' || $parse_mode eq 'csv'; my $granularity = lc($parms{granularity} || $self->granularity); my $grain = 'd'; $granularity =~ /^\s*(\w)/; $grain = $1 if $1 eq 'w' || $1 eq 'm'; my($ticker, $start_date, $end_date) = @parms{qw(symbol start_date end_date)}; $start_date ||= $self->start_date; $end_date ||= $self->end_date; if ($start_date && $end_date && $start_date gt $end_date) { ($start_date, $end_date) = ($end_date, $start_date); } my $host = "query1.finance.yahoo.com"; my $base_url = "https://$host/v7/finance/download/$ticker?"; my @base_parms; if ($start_date) { my($y, $m, $d) = $self->ymd($start_date); my $ts = Date_SecsSince1970($m, $d, $y, 0, 0, 0); push(@base_parms, "period1=$ts"); } if ($end_date) { my($y, $m, $d) = $self->ymd($end_date); my $ts = Date_SecsSince1970($m, $d, $y, 0, 0, 0); $ts += 24*60*60; push(@base_parms, "period2=$ts"); } my $interval = "1d"; if ($grain eq 'w') { $interval = "1wk"; } elsif ($grain eq 'm') { $interval = "1mo"; } push(@base_parms, "interval=$interval"); if ($target_mode eq "quote") { push(@base_parms, "events=history"); } elsif ($target_mode eq "dividend") { push(@base_parms, "events=div"); } elsif ($target_mode eq "split") { push(@base_parms, "events=split"); } push(@base_parms, "crumb=" . $self->{crumb}); my @urls = $base_url . join('&', @base_parms); return sub { pop @urls }; } 1; __END__ =head1 NAME Finance::QuoteHist::Yahoo - Site-specific subclass for retrieving historical stock quotes. =head1 SYNOPSIS use Finance::QuoteHist::Yahoo; $q = new Finance::QuoteHist::Yahoo ( symbols => [qw(IBM UPS AMZN)], start_date => '01/01/2009', end_date => 'today', ); # Values foreach $row ($q->quotes()) { ($symbol, $date, $open, $high, $low, $close, $volume) = @$row; ... } # Splits foreach $row ($q->splits()) { ($symbol, $date, $post, $pre) = @$row; } # Dividends foreach $row ($q->dividends()) { ($symbol, $date, $dividend) = @$row; } =head1 DESCRIPTION Finance::QuoteHist::Yahoo is a subclass of Finance::QuoteHist::Generic, specifically tailored to read historical quotes, dividends, and splits from the Yahoo web site (I). For quotes and dividends, Yahoo can return data quickly in CSV format. Both of these can also be extracted from HTML tables. Splits are extracted from the HTML of the 'Basic Chart' page for that ticker. There are no date range restrictions on CSV queries for quotes and dividends. For HTML queries, Yahoo takes arbitrary date ranges as arguments, but breaks results into pages of 66 entries. Please see L for more details on usage and available methods. If you just want to get historical quotes and are not interested in the details of how it is done, check out L. =head1 METHODS The basic user interface consists of three methods, as seen in the example above. Those methods are: =over =item quotes() Returns a list of rows (or a reference to an array containing those rows, if in scalar context). Each row contains the B, B, B, B, B, B, and B for that date. =item dividends() Returns a list of rows (or a reference to an array containing those rows, if in scalar context). Each row contains the B, B, and amount of the B, in that order. =item splits() Returns a list of rows (or a reference to an array containing those rows, if in scalar context). Each row contains the B, B, B split shares, and B
 split shares, in that order.

=back

The following methods override methods provided by the
Finance::QuoteHist::Generic module; more of this was necessary than is
typical for a basic query site due to the variety of query types and
data formats available on Yahoo.

=over

=item url_maker()

Returns a subroutine reference tailored for the current target mode and
parsing mode. The routine is an iterator that will produce all necessary
URLs on repeated invocations necessary to complete a query.

=item extractors()

Returns a hash of subroutine references that attempt to extract embedded
values (dividends or splits) within the results from a larger query.

=item labels()

Includes the 'adj' column.

=back

=head1 REQUIRES

Finance::QuoteHist::Generic

=head1 DISCLAIMER

The data returned from these modules is in no way guaranteed, nor are
the developers responsible in any way for how this data (or lack
thereof) is used. The interface is based on URLs and page layouts that
might change at any time. Even though these modules are designed to be
adaptive under these circumstances, they will at some point probably
be unable to retrieve data unless fixed or provided with new
parameters. Furthermore, the data from these web sites is usually not
even guaranteed by the web sites themselves, and oftentimes is
acquired elsewhere.

If you would like to know more, check out the terms of service from
Yahoo!, which can be found here:

  http://docs.yahoo.com/info/terms/

If you still have concerns, then use another site-specific historical
quote instance, or none at all.

Above all, play nice.

=head1 AUTHOR

Matthew P. Sisk, EFE

=head1 COPYRIGHT

Copyright (c) 2000-2017 Matthew P. Sisk. All rights reserved. All wrongs
revenged. This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.

=head1 SEE ALSO

Finance::QuoteHist::Generic(3), Finance::QuoteHist(3), perl(1).

=cut
Finance-QuoteHist-1.29/lib/Finance/QuoteHist/Generic.pm0000644000175000017500000013743113124524473021425 0ustar  sisksiskpackage Finance::QuoteHist::Generic;

# http://www.stanford.edu/dept/OOD/RESEARCH/top-ten-faq/how_do_i_find_an_historical_st.html
#
# Shortcut: Use adjusted close price
#
# For the mathematically inclined, one shortcut to determining the value
# after splits is to use the adjusted close price from the historical
# quote tool. For June 2, 1997, it lists a market close price of 33.13
# and an adjusted close price of 1.38. Divide 33.13 by 1.38 and you come
# up with 24.007. Multiply by 1,000 and you come pretty close to the
# 24,000 share figure determined above. Or you could divide 1.38 by
# 33.13, which gives you 0.041654. Divide $33,130 by 0.041654, and you
# get $795K, which is very close to the $808K figure above.

use strict;
use Carp;

use vars qw($VERSION);
$VERSION = "1.22";

use LWP::UserAgent;
use HTTP::Request;
use Date::Manip;

my $CSV_XS_Class = 'Text::CSV_XS';
my $CSV_PP_Class = 'Text::CSV_PP';
my $CSV_Class = $CSV_XS_Class;
eval "use $CSV_Class";
if ($@) {
  $CSV_Class = $CSV_PP_Class;
  eval "use $CSV_Class";
  croak "Could not load either $CSV_XS_Class or $CSV_PP_Class : $@\n" if $@;
}

my $HTE_CLASS;
my $HTE_Class = 'HTML::TableExtract';
sub HTML_CLASS {
  if (!$HTE_CLASS) {
    eval "use $HTE_Class";
    croak $@ if $@;
    $HTE_CLASS = $HTE_Class;
  }
  $HTE_CLASS;
}

my $Default_Target_Mode = 'quote';
my $Default_Parse_Mode  = 'html';
my $Default_Granularity = 'daily';
my $Default_Vol_Pat = qr(vol|shares)i;

my %Default_Labels;
$Default_Labels{quote}{$Default_Parse_Mode} =
  [qw( date open high low close ), $Default_Vol_Pat];
$Default_Labels{dividend}{$Default_Parse_Mode} =
  [qw( date div )];
$Default_Labels{'split'}{$Default_Parse_Mode} =
  [qw( date post pre )];
$Default_Labels{intraday}{$Default_Parse_Mode} =
  [qw( date time high low close ), $Default_Vol_Pat];

my @Scalar_Flags = qw(
  verbose
  quiet
  zthresh
  quote_precision
  attempts
  adjusted
  has_non_adjusted
  env_proxy
  debug
  parse_mode
  target_mode
  granularity
  auto_proxy
  row_filter
  ua_params
);
my $SF_pat = join('|', @Scalar_Flags);

my @Array_Flags = qw(
  symbols
  lineup
);
my $AF_pat = join('|', @Array_Flags);

my @Hash_Flags = qw( ua_params );
my $HF_pat = join('|', @Hash_Flags);

sub new {
  my $that  = shift;
  my $class = ref($that) || $that;
  my(%parms, $k, $v);
  while (($k,$v) = splice(@_, 0, 2)) {
    if ($k eq 'start_date' || $k eq 'end_date' && $v !~ /^\s*$/) {
      $parms{$k} = __PACKAGE__->date_standardize($v);
    }
    elsif ($k =~ /^$AF_pat$/o) {
      if (UNIVERSAL::isa($v, 'ARRAY')) {
        $parms{$k} = $v;
      }
      elsif (ref $v) {
        croak "$k must be passed as an array ref or single-entry string\n";
      }
      else {
        $parms{$k} = [$v];
      }
    }
    elsif ($k =~ /^$HF_pat$/o) {
      if (UNIVERSAL::isa($v, 'HASH')) {
        $parms{$k} = $v;
      }
      else {
        croak "$k must be passed as a hash ref\n";
      }
    }
    elsif ($k eq 'row_filter') {
      croak "$k must be sub ref\n" unless UNIVERSAL::isa($v, 'CODE');
      $parms{$k} = $v;
    }
    elsif ($k =~ /^$SF_pat$/o) {
      $parms{$k} = $v;
    }
  }
  $parms{end_date} ||= __PACKAGE__->date_standardize('today');
  $parms{symbols} or croak "Symbol list required\n";

  my $start_date = delete $parms{start_date};
  my $end_date   = delete $parms{end_date};
  my $symbols    = delete $parms{symbols};

  # Defaults
  $parms{zthresh}          = 30 unless $parms{zthresh};
  $parms{attempts}         = 3  unless $parms{attempts};
  $parms{adjusted}         = 1  unless exists  $parms{adjusted};
  $parms{has_non_adjusted} = 0  unless defined $parms{has_non_adjusted};
  $parms{quote_precision}  = 4  unless defined $parms{quote_precision};
  $parms{auto_proxy}       = 1  unless exists  $parms{auto_proxy};
  $parms{debug}            = 0  unless defined $parms{debug};

  my $self = \%parms;
  bless $self, $class;

  my $ua_params = $parms{ua_params} || {};
  if ($parms{env_proxy}) {
    $ua_params->{env_proxy} = 1;
  }
  elsif ($parms{auto_proxy}) {
    $ua_params->{env_proxy} = 1 if $ENV{http_proxy};
  }
  $self->{ua} ||= LWP::UserAgent->new(%$ua_params);

  if ($self->granularity !~ /^d/i) {
    $start_date = $self->snap_start_date($start_date);
    $end_date   = $self->snap_end_date($end_date);
  }

  $self->start_date($start_date);
  $self->end_date($end_date);
  $self->symbols(@$symbols);

  # These are used for constructing method names for target types.
  $self->{target_order} = [qw(quote split dividend)];
  grep($self->{targets}{$_} = "${_}s", @{$self->{target_order}});

  $self;
}

### User interface stubs

sub quotes    { shift->getter(target_mode => 'quote')->()    }
sub dividends { shift->getter(target_mode => 'dividend')->() }
sub splits    { shift->getter(target_mode => 'split')->()    }
sub intraday  { shift->getter(target_mode => 'intraday')->() }

*intraday_quotes = *intraday;

sub target_worthy {
  my $self = shift;
  my %parms = @_;
  my $target_mode = $parms{target_mode} || $self->target_mode;
  my $parse_mode  = $parms{parse_mode}  || $self->parse_mode;
  # forcing url_maker into a boolean role here, using a dummy symbol
  my $capable = $self->url_maker(
    %parms,
    target_mode => $target_mode,
    parse_mode  => $parse_mode,
    symbol      => 'waggledance',
  );
  my $worthy = $capable && UNIVERSAL::isa($capable, 'CODE');
  if ($self->{verbose}) {
    print STDERR "Seeing if ", ref $self,
                 " can get ($target_mode, $parse_mode) : ",
                 $worthy ? "yes\n" : "no\n";
  }
  $worthy;
}

sub granularities { qw( daily ) }

### Data retrieval

sub ua {
  my $self = shift;
  @_ ? $self->{ua} = shift : $self->{ua};
}

sub fetch {
  # HTTP::Request and LWP::UserAgent Wrangler
  my($self, $request) = splice(@_, 0, 2);
  $request or croak "Request or URL required\n";

  if (! ref $request || ! $request->isa('HTTP::Request')) {
    $request = HTTP::Request->new(GET => $request);
  }

  my $trys = $self->{attempts};
  my $response = $self->ua->request($request, @_);
  $self->{_lwp_success} = 0;
  while (! $response->is_success) {
    last unless $trys;
    $self->{_lwp_status}  = $response->status_line;
    print STDERR "Bad fetch",
       $response->is_error ? ' (' . $response->status_line . '), ' : ', ',
       "trying again...\n" if $self->{debug};
    $response = $self->ua->request($request, @_);
    --$trys;
  }
  $self->{_lwp_success} = $response->is_success;
  return undef unless $response->is_success;
  print STDERR 'Fetch complete. (' . length($response->content) . " chars)\n"
    if $self->{verbose};
  $response->content;
}

sub getter {
  # closure factory to get results for a particular target_mode and
  # parse_mode
  my $self = shift;

  my %parms = @_;
  my $target_mode = $parms{target_mode} || $self->target_mode;
  my $parse_mode  = $parms{parse_mode}  || $self->parse_mode;
  my @column_labels = $self->labels(
    %parms, target_mode => $target_mode, parse_mode  => $parse_mode
  );
  my %extractors = $self->extractors(
    %parms, target_mode => $target_mode, parse_mode  => $parse_mode
  );

  # return our closure
  sub {
    my @symbols = @_ ? @_ : $self->symbols;

    my @rows;

    # cache check
    my @not_seen;
    foreach my $symbol (@symbols) {
      my @r = $self->result_rows($target_mode, $symbol);
      if (@r) {
        push(@rows, @r);
      }
      else {
        push(@not_seen, $symbol);
      }
    }
    return @rows unless @not_seen;

    my $original_target_mode = $self->target_mode;
    my $original_parse_mode  = $self->parse_mode;

    $self->target_mode($target_mode);
    $self->parse_mode($parse_mode);

    my $dcol = $self->label_column('date');
    my(%empty_fetch, %saw_good_rows);
    my $last_data = '';

    my $target_worthy = $self->target_worthy(
      %parms,
      target_mode => $target_mode,
      parse_mode  => $parse_mode
    );
    if (!$target_worthy) {
      # make sure and empty @symbols
      ++$empty_fetch{$_} while $_ = pop @symbols;
    }

    SYMBOL: foreach my $s (@symbols) {
      my $urlmaker = $self->url_maker(
        target_mode => $target_mode,
        parse_mode  => $parse_mode,
        symbol      => $s,
      );
      UNIVERSAL::isa($urlmaker, 'CODE') or croak "urlmaker not a code ref.\n";
      my $so_far_so_good = 0;
      URL: while (my $url = $urlmaker->()) {
        if ($empty_fetch{$s}) {
          print STDERR ref $self,
             " passing on $s ($target_mode) for now, empty fetch\n"
             if $self->{verbose};
          last URL;
        }

        if ($self->{verbose}) {
          my $uri = $url;
          $uri = $url->uri if UNIVERSAL::isa($url, 'HTTP::Request');
          print STDERR "Processing ($s:$target_mode) $uri\n";
        }

        # We're a bit more persistent with quotes. It is more suspicious
        # if we get no quote rows, but it is nevertheless possible.
        my $trys = $target_mode eq 'quote' ? $self->{attempts} : 1;
        my $initial_trys = $trys;
        my($data, $rows) = ('', []);
        do {
          print STDERR "$s Trying ($target_mode) again due to no rows...\n"
            if $self->{verbose} && $trys != $initial_trys;
          if (!($data = $self->{url_cache}{$url})) {
            $data = $self->fetch($url);
            if (my $pre_parser = $self->pre_parser) {
              $data = $pre_parser->(
                $data,
                target_mode => $target_mode,
                parse_mode  => $parse_mode,
              );
            }
          }
          # make sure our url_maker hasn't sent us into a twister
          if ($data && $data eq $last_data) {
            print STDERR "Redundant data fetch, assuming end of URLs.\n"
              if $self->{verbose};
            last URL;
          }
          else {
            $last_data = defined $data ? $data : '';
          }
          $rows = $self->rows($self->parser->($data));
          last URL if $so_far_so_good && !@$rows;
          --$trys;
        } while !@$rows && $trys && $self->{_lwp_success};
        $so_far_so_good = 1;

        if ($target_mode ne 'quote' || $target_mode ne 'intraday') {
          # We are not very stubborn about dividends and splits right
          # now. This is because we cannot prove a successful negative
          # (i.e., say there were no dividends or splits over the time
          # period...or perhaps there were, but it is a defunct
          # symbol...whatever...quotes should always be present unless
          # they are defunct, which is dealt with later.
          if (!$self->{_lwp_success} || !$data) {
            ++$empty_fetch{$s};
            @$rows = ();
          }
          elsif ($self->{_lwp_success} && !@$rows) {
            ++$empty_fetch{$s};
          }
        }

        # Raw cache
        $self->{url_cache}{$url} = $data;
  
        # Extraction filters. This is an opportunity to extract rows
        # that are not what we are looking for, but contain valuable
        # information nevertheless. An example of this would be the
        # split and dividend rows you see in Yahoo HTML quote output. An
        # extraction filter method should expect an array ref as an
        # argument, representing a single row, and should return another
        # array ref with extracted output. If there is a return value,
        # then this row will be filtered from the primary output.
        my(%extractions, $ecount, $rc);
        $rc = @$rows;
        if (%extractors) {
          my(@filtered, $row);
          while ($row = pop(@$rows)) {
            my $erow;
            foreach my $mode (sort keys %extractors) {
              $extractions{$mode} ||= [];
              my $em = $extractors{$mode};
              if ($erow = $em->($row)) {
                print STDERR "$s extract ($mode) got $s, ",
                   join(', ', @$erow), "\n" if $self->{verbose};
                push(@{$extractions{$mode}}, [@$erow]);
                ++$ecount;
                last;
              }
            }
            push(@filtered, $row) unless $erow;
          }
          if ($self->{verbose} && $ecount) {
            print STDERR "$s Trimmed to ",$rc - $ecount,
               " rows after $ecount extractions.\n";
          }
          $rows = \@filtered;
        }

        if ($extractions{$target_mode}) {
          $rows = [@{$extractions{$target_mode}}];
          print STDERR "Coopted to ", scalar @$rows,
            " rows after $target_mode extraction redundancy.\n"
            if $self->{verbose};
        }

        if (@$rows) {
          # Normalization steps

          if ($target_mode eq 'split') {
            if (@{$rows->[0]} == 2) {
              foreach (@$rows) {
                if ($_->[-1] =~ /(split\s+)?(\d+)\D+(\d+)/is) {
                  splice(@$_, -1, 1, $2, $3);
                }
              }
            }
          }

          # Saving the rounding operations until after the adjust
          # routine is deliberate since we don't want to be auto-
          # adjusting pre-rounded numbers.
          $self->number_normalize_rows($rows);
  
          # Do the same for the extraction rows, plus store the
          # extracted rows
          foreach my $mode (keys %extractions) {
            # _store_results splices each row...don't do it twice
            next if $mode eq $target_mode;
            $self->target_mode($mode);
            $self->number_normalize_rows($extractions{$mode});
            $self->_target_source($mode, $s, ref $self);
            $self->_store_results($mode, $s, $dcol, $extractions{$mode});
          }
          # restore original target mode
          $self->target_mode($target_mode);
  
          if ($target_mode eq 'quote' || $target_mode eq 'intraday') {
            my $count = @$rows;
            @$rows = grep($self->is_quote_row($_) &&
                          $self->row_not_seen($s, $_), @$rows);
            if ($self->{verbose}) {
              if ($count == @$rows) {
                print STDERR "$s Retained $count rows\n";
              }
              else {
                print STDERR "$s Retained $count raw rows, trimmed to ",
                  scalar @$rows, " rows due to noise\n";
              }
            }
  
          }
          if ($target_mode eq 'quote') {
            # zcount is an attempt to capture null values; if there are
            # too many we assume there is something wrong with the
            # remote data
            my $close_col = $self->label_column('close');
            my($zcount, $hcount) = (0,0);
            foreach (@$rows) {
              foreach (@$_) {
                # Sometimes N/A appears
                s%^\s*N/A\s*$%%;
              }
              my $q = $_->[$close_col];
              if (defined $q && $q =~ /\d+/) { ++$hcount }
              else                            { ++$zcount }
            }
            my $pct = $hcount ? 100 * $zcount / ($zcount + $hcount) : 100;
            if (!$trys || $pct >= $self->{zthresh}) {
              ++$empty_fetch{$s} unless $saw_good_rows{$s};
            }
            else {
              # For defunct symbols, we could conceivably get quotes
              # over a date range that contains blocks of time where the
              # ticker was actively traded, as well as blocks of time
              # where the ticker doesn't exist. If we got good data over
              # some of the blocks, then we take note of it so we don't
              # toss the whole set of queries for this symbol.
              ++$saw_good_rows{$s};
            }
            $self->precision_normalize_rows($rows)
              if @$rows && $self->{quote_precision};
          }

          last URL if !$ecount && !@$rows;
          $self->_store_results($target_mode, $s, $dcol, $rows) if @$rows;
          $self->_target_source($target_mode, $s, ref $self);
        }
      }
    }

    $self->_store_empty_fetches([keys %empty_fetch]);
  
    # Check for bad fetches. If we failed on some symbols, punt them to
    # our champion class.
    if (%empty_fetch) {
      my @bad_symbols = $self->empty_fetches;
      my @champion_classes = $self->lineup;
      while (@champion_classes && @bad_symbols) {
        print STDERR "Bad fetch for ", join(',', @bad_symbols), "\n"
          if $self->{verbose} && $target_worthy;
        my $champion =
          $self->_summon_champion(shift @champion_classes, @bad_symbols);
        next unless $champion &&
                    $champion->target_worthy(target_mode => $target_mode);
        print STDERR ref $champion, ", my hero!\n" if $self->{verbose};
        # Hail Mary
        $champion->getter(target_mode => $target_mode)->();
        # Our champion was the source for these symbols (including
        # extracted info).
        foreach my $mode ($champion->result_modes) {
          foreach my $symbol ($champion->result_symbols($mode)) {
            $self->_target_source($mode, $symbol, ref $champion);
            $self->_copy_results($mode, $symbol,
                                 $champion->results($mode, $symbol));
          }
        }
        @bad_symbols = $champion->empty_fetches;
      }
      if (@bad_symbols && !$self->{quiet}) {
        print STDERR "WARNING: Could not fetch $target_mode for some symbols (",join(', ', @bad_symbols), "). Abandoning request for these symbols.";
        if ($target_mode ne 'quote') {
          print STDERR " Don't worry, though, we were looking for ${target_mode}s. These are less likely to exist compared to quotes.";
        }
        if ($self->{_lwp_status}) {
          print STDERR "\n\nLast status: $self->{_lwp_status}\n";
        }
        print STDERR "\n";
      }
    }
  
    $self->target_mode($original_target_mode);
    $self->parse_mode($original_parse_mode);

    @rows = $self->result_rows($target_mode);
    if ($self->{verbose}) {
      print STDERR "Class ", ref $self, " returning ", scalar @rows,
         " composite rows.\n";
    }

    # Return the loot.
    wantarray ? @rows : \@rows;
  };
}

sub _store_results {
  my($self, $mode, $symbol, $dcol, $rows) = @_;
  foreach my $row (@$rows) {
    my $date = splice(@$row, $dcol, 1);
    $self->{results}{$mode}{$symbol}{$date} = $row;
  }
}

sub _copy_results {
  my($self, $mode, $symbol, $results) = @_;
  foreach my $date (sort keys %$results) {
    $self->{results}{$mode}{$symbol}{$date} = [@{$results->{$date}}];
  }
}

sub result_rows {
  my($self, $target_mode, @symbols) = @_;
  $target_mode ||= $self->target_mode;
  @symbols = $self->result_symbols($target_mode) unless @symbols;
  my @rows;
  foreach my $symbol (@symbols) {
    my $results = $self->results($target_mode, $symbol);
    foreach my $date (sort keys %$results) {
      push(@rows, [$symbol, $date, @{$results->{$date}}]);
    }
  }
  sort { $a->[1] cmp $b->[1] } @rows;
}

sub _store_empty_fetches {
  my $self = shift;
  my $ref = shift || [];
  @$ref = sort @$ref;
  $self->{empty_fetches} = $ref;
}

sub empty_fetches {
  my $self = shift;
  return () unless $self->{empty_fetches};
  @{$self->{empty_fetches}} 
}

sub extractors { () }

sub rows {
  my($self, $rows) = @_;
  return wantarray ? () : [] unless $rows;
  my $rc = @$rows;
  print STDERR "Got $rc raw rows\n" if $self->{verbose};

  # Load user filter if present
  my $row_filter = $self->row_filter;

  # Prep the rows
  foreach my $row (@$rows) {
    $row_filter->($row) if $row_filter;
    foreach (@$row) {
      # Zap leading and trailing white space
      next unless defined;
      s/^\s+//; s/\s+$//;
    }
  }
  # Pass only rows with a valid date that is in range (and store the
  # processed value while we are at it)
  my $target_mode = $self->target_mode;
  my @date_rows;
  my $dcol = $self->label_column('date');
  my $tcol = $self->label_column('time') if $target_mode eq 'intraday';
  my $r;
  while($r = pop @$rows) {
    my $date = $r->[$dcol];
    if ($target_mode eq 'intraday') {
      my $time = splice(@$r, $tcol, 1);
      $date = join('', $date, $time);
    }
    $date = $self->date_normalize($date);
    unless ($date) {
      print STDERR "Reject row (no date): '$r->[$dcol]'\n" if $self->{verbose};
      next;
    }
    next unless $self->date_in_range($date);
    $r->[$dcol] = $date;
    push(@date_rows, $r);
  }

  print STDERR "Trimmed to ", scalar @date_rows, " applicable date rows\n"
    if $self->{verbose} && @date_rows != $rc;

  return wantarray ? @date_rows : \@date_rows;
}

### Adjustment triggers and manipulation

sub adjuster {
  # In order to be an adjuster, it must first be enabled. In addition,
  # there has to be a column specified as the adjusted value. This is
  # not as generic as I would like it, but so far it's just for
  # Yahoo...it should work for any site with "adj" in the column
  # label...this column should be the adjusted closing value.
  my $self = shift;
  return 0 if !$self->{adjusted};
  foreach ($self->labels) {
    return 1 if /adj/i;
  }
  0;
}

sub adjusted { shift->{adjusted} ? 1 : 0 }

### Bulk manipulation filters

sub date_normalize_rows {
  # Place dates into a consistent format, courtesy of Date::Manip
  my($self, $rows, $dcol) = @_;
  $dcol = $self->label_column('date') unless defined $dcol;
  foreach my $row (@$rows) {
    $row->[$dcol] = $self->date_normalize($row->[$dcol]);
  }
  $rows;
}

sub date_normalize {
  my($self, $date) = @_;
  return unless $date;
  my $normal_date;
  if ($self->granularity =~ /^m/ && $date =~ m{^\s*(\D+)[-/]+(\d{2,})\s*$}) {
    my($m, $y) = ($1, $2);
    $y += 1900 if length $y == 2;
    $normal_date = ParseDate($m =~ /^\d+$/ ? "$y/$m/01" : "$m 01 $y");
  }
  else {
    $normal_date = ParseDate($date);
  }
  $normal_date or return undef;
  return $normal_date if $self->target_mode eq 'intraday';
  join('/', $self->ymd($normal_date));
}

sub snap_start_date {
  my($self, $date) = @_;
  my $g = $self->granularity;
  if ($g =~ /^(m|w)/i) {
    if ($1 eq 'm') {
      my($dom) = UnixDate($date, '%d') - 1;
      $date = DateCalc($date, "- $dom days") if $dom;
    }
    else {
      my $dow = Date_DayOfWeek(UnixDate($date, '%m', '%d', '%Y')) - 1;
      $date = DateCalc($date, "- $dow days") if $dow;
    }
  }
  $date;
}

sub snap_end_date {
  my($self, $date) = @_;
  my $g = $self->granularity;
  if ($g =~ /^(m|w)/i) {
    if ($1 eq 'm') {
      my($m, $y) = UnixDate($date, '%m', '%Y');
      my $last = Date_DaysInMonth($m, $y);
      $date = ParseDateString("$y$m$last");
    }
    else {
      my $dow = Date_DayOfWeek(UnixDate($date, '%m', '%d', '%Y')) - 1;
      $date = DateCalc($date, "+ " . (6 - $dow) . ' days')
        unless $dow == 6;
    }
  }
  $date;
}

sub number_normalize_rows {
  # Strip non-numeric noise from numeric fields
  my($self, $rows, $dcol) = @_;
  $dcol = $self->label_column('date') unless defined $dcol;
  # filtered rows might not have same columns
  my @cols = grep($_ != $dcol, 0 .. $#{$rows->[0]});
  foreach my $row (@$rows) {
    s/[^\d\.]//go foreach @{$row}[@cols];
  }
  $rows;
}

sub precision_normalize_rows {
  # Round off numeric fields, if requested (%.4f by default). Volume
  # is the exception -- we just round that into an integer. This
  # should probably only be called for 'quote' targets because it
  # knows details about where the numbers of interest reside.
  my($self, $rows) = @_;
  my $target_mode = $self->target_mode;
  croak "precision_normalize invoked in '$target_mode' mode rather than 'quote'  or 'intraday' mode.\n"
    unless $target_mode eq 'quote' || $target_mode eq 'intraday';
  my @columns;
  if ($target_mode ne 'intraday') {
    @columns = $self->label_column(qw(open high low close));
    push(@columns, $self->label_column('adj')) if $self->adjuster;
  }
  else {
    @columns = $self->label_column(qw(high low close));
  }
  my $vol_col = $self->label_column($Default_Vol_Pat);
  foreach my $row (@$rows) {
    $row->[$_] = sprintf("%.$self->{quote_precision}f", $row->[$_])
      foreach @columns;
    $row->[$vol_col] = int $row->[$vol_col];
  }
  $rows;
}

### Single row filters

sub is_quote_row {
  my($self, $row, $dcol) = @_;
  ref $row or croak "Row ref required\n";
  # Skip date in first field
  $dcol = $self->label_column('date') unless defined $dcol;
  foreach (0 .. $#$row) {
    next if $_ == $dcol;
    next if $row->[$_] =~ /^\s*$/;
    if ($row->[$_] !~ /^\s*\$*[\d\.,]+\s*$/) {
      return 0;
    }
  }
  1;
}

sub row_not_seen {
  my($self, $symbol, $row, $dcol) = @_;
  ref $row or croak "Row ref required\n";
  $symbol or croak "ticker symbol required\n";
  my $mode = $self->target_mode;
  my $res = $self->{results}{$mode} or return 1;
  my $mres = $res->{$symbol} or return 1;
  $dcol = $self->label_column('date') unless defined $dcol;
  $mres->{$row->[$dcol]} or return 1;
  return 0;
}

sub date_in_range {
  my $self = shift;
  my $date = shift;
  $date = $self->date_standardize($date) or return undef;
  return 0 if $self->{start_date} && $date lt $self->{start_date};
  return 0 if $self->{end_date}   && $date gt $self->{end_date};
  1;
}

### Label and label mapping/extraction management

sub default_target_mode { $Default_Target_Mode }
sub default_parse_mode  { $Default_Parse_Mode  }
sub default_granularity { $Default_Granularity }

sub set_label_pattern {
  my $self = shift;
  my %parms = @_;
  my $target_mode = $parms{target_mode} || $self->target_mode;
  my $parse_mode  = $parms{parse_mode}  || $self->parse_mode;
  my $label = $parms{label};
  croak "Column label required\n" unless $label;
  my $l2p = $self->{_label_pat}{$target_mode}{$parse_mode} ||= {};
  my $p2l = $self->{_pat_label}{$target_mode}{$parse_mode} ||= {};
  my $pattern = $parms{pattern};
  if ($pattern) {
    $l2p->{$label} = $pattern;
    delete $self->{label_map};
    delete $self->{pattern_map};
  }
  my $pat = $l2p->{$label} ||= ($label =~ $Default_Vol_Pat ?
                                  qr/\s*$label/i : qr/^\s*$label/i);
  $p2l->{$pat} ||= $label;
  $pat;
}

sub label_pattern {
  my $self = shift;
  my $target_mode = $self->target_mode;
  my $parse_mode  = $self->parse_mode;
  my $label = shift;
  croak "column label required\n" unless $label;
  my $l2p = $self->{_label_pat}{$target_mode}{$parse_mode} ||= {};
  my $pat = $l2p->{$label} || $self->set_label_pattern(label => $label);
  $pat;
}

sub label_column {
  my $self = shift;
  my @cols;
  if (!$self->{label_map}) {
    delete $self->{pattern_map};
    my @labels = $self->labels;
    foreach my $i (0 .. $#labels) {
      $self->{label_map}{$labels[$i]} = $i;
    }
  }
  foreach (@_) {
    croak "Unknown label '$_'\n" unless exists $self->{label_map}{$_};
    push(@cols, $self->{label_map}{$_});
  }
  unless (wantarray) {
    croak "multiple columns in scalar context\n" if @cols > 1;
    return $cols[0];
  }
  @cols;
}

sub pattern_column {
  my $self = shift;
  if (!$self->{pattern_map}) {
    my @patterns = $self->patterns;
    foreach my $i (0 .. $#patterns) {
      $self->{pattern_map}{$patterns[$i]} = $i;
    }
  }
  return unless @_;
  my $pattern = shift;
  croak "Unknown pattern '$pattern'\n" unless $self->{_pat_map}{$pattern};
  $self->{pattern_map{$pattern}};
}

sub pattern_map {
  my $self = shift;
  $self->pattern_column unless $self->{pattern_map};
  $self->{pattern_map};
}

sub label_map {
  my $self = shift;
  $self->label_column unless $self->{label_map};
  $self->{label_map};
}

sub pattern_label {
  my $self = shift;
  my %parms = @_;
  my $target_mode = $parms{target_mode} || $self->target_mode;
  my $parse_mode  = $parms{parse_mode}  || $self->parse_mode;
  my $pat = $parms{pattern} or croak "pattern required for label lookup\n";
  my $p2l = $self->{_pat_label}{$target_mode}{$parse_mode} ||= {};
  my $label = $p2l->{$pat};
  unless (defined $label) {
    delete $parms{pattern};
    $self->set_label_pattern(%parms, label => $_) foreach $self->labels;
  }
  $label;
}

sub patterns {
  my $self = shift;
  my %parms = @_;
  $parms{target_mode} ||= $self->target_mode;
  $parms{parse_mode}  ||= $self->parse_mode;
  map($self->label_pattern($_), $self->labels(%parms));
}

sub columns {
  my $self = shift;
  my %parms = @_;
  $parms{target_mode} ||= $self->target_mode;
  $parms{parse_mode}  ||= $self->parse_mode;
  $self->label_column($self->labels(%parms));
}

sub default_labels {
  my $self = shift;
  my %parms = @_;
  my $target_mode = $parms{target_mode} || $self->target_mode;
  my $tm = $Default_Labels{$target_mode};
  unless ($tm) {
    $tm = $Default_Labels{$self->default_target_mode};
  }
  my $parse_mode = $parms{parse_mode} || $self->parse_mode;
  my $labels = $tm->{$parse_mode};
  unless ($labels) {
    $labels = $tm->{$self->default_parse_mode};
  }
  @$labels;
}

sub labels {
  my $self  = shift;
  my %parms = @_;
  my $target_mode = $parms{target_mode} || $self->target_mode;
  my $parse_mode  = $parms{parse_mode}  || $self->parse_mode;
  my $tm = $self->{_labels}{$target_mode};
  if ($parms{labels} || ! $tm->{$parse_mode}) {
    delete $self->{label_map};
    delete $self->{pattern_map};
  }
  $tm->{$parse_mode} = $parms{labels} if $parms{labels};
  my $labels = $tm->{$parse_mode} ||= [$self->default_labels(
                                         target_mode => $target_mode,
                                         parse_mode  => $parse_mode)];
  @$labels;
}

sub parse_mode {
  my $self = shift;
  if (@_) {
    $self->{parse_mode} = shift;
  }
  $self->{parse_mode} || $self->default_parse_mode;
}

sub target_mode {
  my $self = shift;
  if (@_) {
    $self->{target_mode} = shift;
  }
  $self->{target_mode} || $self->default_target_mode;
}

sub granularity {
  my $self = shift;
  if (@_) {
    $self->{granularity} = shift;
  }
  $self->{granularity} || $self->default_granularity;
}

sub lineup {
  my $self = shift;
  $self->{lineup} = \@_ if @_;
  return unless $self->{lineup};
  @{$self->{lineup}};
}

### Parser methods

sub pre_parser {
  my($self, %parms) = @_;
  my $parse_mode = $parms{parse_mode} || $self->parse_mode;
  my $method = "${parse_mode}_pre_parser";
  return unless $self->can($method);
  $self->$method(%parms, parse_mode => $parse_mode);
}

sub parser {
  my($self, %parms) = @_;
  my $parse_mode = $parms{parse_mode} || $self->parse_mode;
  my $make_parser = "${parse_mode}_parser";
  $self->$make_parser(%parms, parse_mode => $parse_mode);
}

sub html_parser {
  # HTML::TableExtract supports automatic column reordering.
  my $self = shift;
  my $class = HTML_CLASS;
  my @labels = $self->labels(@_);
  my @patterns = $self->patterns(@_);
  my(%pat_map, %label_map);
  $pat_map{$patterns[$_]} = $_ foreach 0 .. $#patterns;
  $label_map{$labels[$_]} = $_ foreach 0 .. $#labels;
  $self->pattern_map(\%pat_map);
  $self->label_map(\%label_map);
  sub {
    my $data = shift;
    my $html_string;
    if (ref $data) {
      local($/);
      $html_string = <$data>;
    }
    else {
      $html_string = $data;
    }
    my %te_parms = (
      headers => \@patterns,
      automap => 1,
    );
    $te_parms{debug} = $self->{debug} if $self->{debug} > 2;
    my $te = $class->new(%te_parms) or croak "Problem creating $class\n";
    $te->parse($html_string);
    $te->eof;
    my $ts = $te->first_table_found;
    [ $ts ? $ts->rows() : ()];
  }
}

sub csv_parser {
  # Text::CSV_XS doesn't column slice or re-order, so we do.
  my $self = shift;
  my @patterns = $self->patterns(@_);
  sub {
    my $data = shift;
    return [] unless defined $data;
    my @csv_lines = ref $data ? <$data> : split("\n", $data);
    # BOM squad (byte order mark, as csv from google tends to be)
    if ($csv_lines[0] =~ s/^\xEF\xBB\xBF//) {
      for my $i (0 .. $#csv_lines) {
        utf8::decode($csv_lines[$i]);
      }
    }
    # might be unix, windows, or mac style newlines
    s/\s+$// foreach @csv_lines;
    return [] if !@csv_lines || $csv_lines[0] =~ /(no data)|error/i;
    # attempt to get rid of comments at front of csv data
    while (@csv_lines) {
      last if $csv_lines[0] =~ /date/i || $csv_lines[0] =~ /\d+$/;
      print STDERR "CSV reject line: $csv_lines[0]\n" if $self->{verbose};
      shift @csv_lines;
    }
    my $first_line = $csv_lines[0];
    my $sep_char = $first_line =~ /date\s*(\S)/i ? $1 : ',';
    my $cp = $CSV_Class->new({sep_char => $sep_char, binary => 1})
      or croak "Problem creating $CSV_Class\n";
    my @pat_slice;
    if ($first_line =~ /date/i) {
      # derive column detection and ordering
      $cp->parse($first_line) or croak ("Problem parsing (" .
        $cp->error_input . ") : " . $cp->error_diag . "\n");
      my @headers = $cp->fields;
      my @pats = @patterns;
      my @labels = map($self->pattern_label(pattern => $_), @patterns);
      my(%pat_map, %label_map);
      HEADER: for my $i (0 .. $#headers) {
        last unless @pats;
        my $header = $headers[$i];
        for my $pi (0 .. $#pats) {
          my $pat = $pats[$pi];
          if ($header =~ /$pat/) {
            my $label = $labels[$pi];
            splice(@pats,   $pi, 1);
            splice(@labels, $pi, 1);
            $pat_map{$pat} = $i;
            $label_map{$label} = $i;
            next HEADER;
          }
        }
      }
      shift @csv_lines;
      @pat_slice = map($pat_map{$_}, @patterns);
    }
    else {
      # no header row, trust natural order and presence
      @pat_slice = 0 .. $#patterns;
    }
    my @rows;
    foreach my $line (@csv_lines) {
      $cp->parse($line) or next;
      my @fields = $cp->fields;
      push(@rows, [@fields[@pat_slice]]);
    }
    \@rows;
  };
}

### Accessors, generators

sub start_date {
  my $self = shift;
  if (@_) {
    my $start_date = shift;
    my $clear = @_ ? shift : 1;
    $self->clear_cache if $clear;
    $self->{start_date} = defined $start_date ?
      $self->date_standardize($start_date) : undef;
  }
  $self->{start_date};
}

sub end_date {
  my $self = shift;
  if (@_) {
    my $end_date = shift;
    my $clear = @_ ? shift : 1;
    $self->clear_cache if $clear;
    $self->{end_date} = defined $end_date ?
      $self->date_standardize($end_date) : undef;
  }
  $self->{end_date};
}

sub date_standardize {
  my($self, @dates) = @_;
  return unless @dates;
  foreach (@dates) {
    $_ = ParseDate($_) or Carp::confess "Could not parse date '$_'\n";
    s/\d\d:.*//;
  }
  @dates > 1 ? @dates : ($dates[0]);
}

sub mydates {
  my $self = shift;
  $self->dates($self->{start_date}, $self->{end_date});
}

sub dates {
  my($self, $sdate, $edate) = @_;
  $sdate && $edate or croak "Start date and end date strings required\n";
  my($sd, $ed) = sort($self->date_standardize($sdate, $edate));
  my @dates;
  push(@dates, $sd) if Date_IsWorkDay($sd);
  my $cd = $self->date_standardize(Date_NextWorkDay($sd, 1));
  while ($cd <= $ed) {
    push(@dates, $cd);
    $cd = $self->date_standardize(Date_NextWorkDay($cd));
  }
  @dates;
}

sub symbols {
  my($self, @symbols) = @_;
  if (@symbols) {
    my %seen;
    grep(++$seen{$_}, grep(uc $_, @symbols));
    $self->{symbols} = [sort keys %seen];
    $self->clear_cache;
  }
  @{$self->{symbols}};
}

sub successors {
  my $self = shift;
  @{$self->{successors}};
}

sub clear_cache {
  my $self = shift;
  delete $self->{url_cache};
  delete $self->{results};
  1;
}

sub result_modes {
  my $self = shift;
  return () unless $self->{results};
  sort keys %{$self->{results}};
}

sub result_symbols {
  my($self, $target_mode) = @_;
  $target_mode ||= $self->target_mode;
  return () unless $self->{sources}{$target_mode};
  sort keys %{$self->{results}{$target_mode}};
}

sub results {
  my($self, $target_mode, $symbol) = @_;
  $self->{results}{$target_mode}{$symbol};
}

sub quote_source    { shift->source(shift, 'quote')    }
sub dividend_source { shift->source(shift, 'dividend') }
sub split_source    { shift->source(shift, 'split')    }
sub intraday_source { shift->source(shift, 'intraday') }

sub row_filter { shift->{row_filter} }

sub source {
  my($self, $symbol, $target_mode) = @_;
  croak "Ticker symbol required\n" unless $symbol;
  $target_mode ||= $self->target_mode;
  $self->{sources}{$target_mode}{$symbol} || '';
}

sub _target_source {
  my($self, $target_mode, $symbol, $source) = @_;
  croak "Target mode required\n"   unless $target_mode;
  croak "Ticker symbol required\n" unless $symbol;
  $symbol = uc $symbol;
  if ($source) {
    $self->{sources}{$target_mode}{$symbol} = $source;
  }
  $self->{sources}{$target_mode}{$symbol};
}

###

sub _summon_champion {
  # Instantiate the next class in line if this class failed in
  # fetching any quotes. Make sure and pass along the remaining
  # champions to the new champion.
  my($self, $champion_class, @bad_symbols) = @_;
  return undef unless ref $self->{lineup} && @{$self->{lineup}};
  print STDERR "Loading $champion_class\n" if $self->{verbose};
  eval "require $champion_class;";
  die $@ if $@;
  my $champion = $champion_class->new
    (
     symbols    => [@bad_symbols],
     start_date => $self->{start_date},
     end_date   => $self->{end_date},
     adjusted   => $self->{adjusted},
     verbose    => $self->{verbose},
     lineup     => [],
    );
  $champion;
}

### Toolbox

sub save_query    { shift->_save_restore_query(1) }
sub restore_query { shift->_save_restore_query(0) }
sub _save_restore_query {
  my($self, $save) = @_;
  $save = 1 unless defined $save;
  foreach (qw(parse_mode target_mode start_date end_date granularity quiet)) {
    my $qstr = "_query_$_";
    if ($save) {
      $self->{$qstr} = $self->{$_};
    }
    else {
      $self->{$_} = $self->{$qstr} if exists $self->{$qstr};
    }
  }
  $self;
}

sub ymd {
  my $self = shift;
  my @res = $_[0] =~ /^\s*(\d{4})(\d{2})(\d{2})/o;
  shift =~ /^\s*(\d{4})(\d{2})(\d{2})/o;
}

sub date_iterator {
  my $self = shift;
  my %parms = @_;
  my $start_date = $parms{start_date};
  my $end_date   = $parms{end_date} || 'today';
  my $increment  = $parms{increment};
  my $units      = $parms{units} || 'days';
  $increment && $increment > 0 or croak "Increment > 0 required\n";
  $start_date = ParseDate($start_date) if $start_date;
  $end_date   = ParseDate($end_date)   if $end_date;
  if ($start_date && $start_date gt $end_date) {
    ($start_date, $end_date) = ($end_date, $start_date);
  }
  my($low_date, $high_date);
  $high_date = $end_date;
  sub {
    return () unless $end_date;
    $low_date = DateCalc($high_date, "- $increment $units");
    if ($start_date && $low_date lt $start_date) {
      $low_date = $start_date;
      undef $start_date;
      undef $end_date;
      return () if $low_date eq $high_date;
    }
    my @date_pair = ($low_date, $high_date);
    $high_date = $low_date;
    @date_pair;
  }
}

1;

__END__

=head1 NAME

Finance::QuoteHist::Generic - Base class for retrieving historical stock quotes.

=head1 SYNOPSIS

  package Finance::QuoteHist::MyFavoriteSite;
  use strict;
  use vars qw(@ISA);
  use Finance::QuoteHist::Generic;
  @ISA = qw(Finance::QuoteHist::Generic);

  sub url_maker {
    # This method returns a code reference for a routine that, upon
    # repeated invocation, will provide however many URLs are necessary
    # to fully obtain the historical data for a given target mode and
    # parsing mode.
  }

=head1 DESCRIPTION

This is the base class for retrieving historical stock quotes. It is
built around LWP::UserAgent. Page results are currently parsed as either
CSV or HTML tables.

In order to actually retrieve historical stock quotes, this class should
be subclassed and tailored to a particular web site. In particular, the
C factory method should be overridden, which provides a
code reference to a routine that provides however many URLs are
necessary to retrieve the data over a list of symbols within the given
date range, for a particular target (quotes, dividends, splits).
Different sites have different formats and different limitations on how
many quotes are returned for each query. See Finance::QuoteHist::Yahoo
for an example of how to do this.

For more complicated sites, such as Yahoo, overriding additional methods
might be necessary for dealing with things such as splits and dividends.

=head1 METHODS

=over

=item new()

Returns a new Finance::QuoteHist::Generic object.  Valid attributes
are:

=over

=item start_date

=item end_date

Specify the date range from which you would like historical quotes.
These dates get parsed by the C method in Date::Manip, so
see L for more information on valid date strings. They
are quite flexible, and include such strings as '1 year ago'. Date
boundaries can also be dynamically set with methods of the same
name. The absence of a start date means go to the beginning of the
history. The absence of an end date means go up to the most recent
historical date. The absence of both means grab everything.

=item symbols

Indicates which ticker symbols to include in the search for historical
quotes. Passed either as a string (for single ticker) or an array ref
for multiple tickers.

=item granularity

Returns rows at 'daily', 'weekly', or 'monthly' levels of granularity.
Defaults to 'daily'.

=item attempts

Sets how persistently the module tries to retrieve the quotes. There are
two places this will manifest. First, if there are what appear to be
network errors, this many network connections are attempted for that
URL. Secondly, for quotes only, if pages were successfully retrieved,
but they contained no quotes, this number of attempts are made to
retrieve a document with data. Sometimes sites will report a temporary
internal error via HTML, and if it is truly transitory this will usually
get around it. The default is 3.

=item lineup

Passed as an array reference (or scalar for single class), this list
indicates which Finance::QuoteHist::Generic sub classes should be
invoked in the event this class fails in its attempt to retrieve
historical quotes. In the event of failure, the first class in this list
is invoked with the same parameters as the original class, and the
remaining classes are passed as the lineup to the new class. This sets
up a daisy chain of redundancy in the event a particular site is hosed.
See L to see an example of how this is done in a
top level invocation of these modules. This list is empty by default.

=item quote_precision

Sets the number of decimal places to which quote values are rounded.
This might be of particular significance if there is auto-adjustment
taking place (which is only under particular circumstances
currently...see L). Setting this to 0 will
disable the rounding behavior, returning the quote values as they
appear on the sites (assuming no auto-adjustment has taken place). The
default is 4.

=item row_filter

When provided a subroutine reference, the routine is invoked with an
array reference for each raw row retrieved from the quote source. This
allows user-defined filtering or formatting for the items of each
row. This routine is invoked before any built-in routines are called
on the row. The array must be modified directly rather than returned
as a value. Use sparingly since the built-in filtering and
normalizing routines do expect each row to more or less look like
historical stock data. Rearranging the order of the columns in each row
is contraindicated.

=item env_proxy

When set, instructs the underlying LWP::UserAgent to load proxy
configuration information from environment variables. See the C
method and L for more information.

=item auto_proxy

Same as env_proxy, but tests first to see if $ENV{http_proxy} is
present.

=item verbose

When set, many status messages are printed to STDERR indicating
progression through URLs and lineup invocations.

=item quiet

When set, certain failure messages are suppressed from appearing on
STDERR. These messages would normally appear regardless the setting of
the C flag.

=back

=back

The following methods are the primary user interface methods; methods
of interest to developers wishing to make their own site-specific
instance of this module will find information on overriding methods
further below.

=over

=item quotes()

Retrieves historical quotes for all provided symbols over the specified
date range. Depending on context, returns either a list of rows or an
array reference to the same list of rows.

=item dividends()

=item splits()

If available, retrieves dividend or split information for all provided
symbols over the specified date range. If there are no site-specific
subclassed modules in the B capable of getting dividends or
splits, the user will be notified on STDERR unless the B flag was
requested during object creation.

=item start_date(date_string)

=item end_date(date_string)

Set the date boundaries of all queries. The B is
interpreted by the Date::Manip module. The absence of a start date means
retrieve back to the beginning of that ticker's history. The absence of
an end date means retrieve up to the latest date in the history.

=item clear_cache()

When results are gathered for a particular date range, whether they be
via direct query or incidental extraction, they are cached. This cache
is cleared by invoking this method directly, by resetting the boundary
dates of the query, or by changing the C setting.

=item quote_source(ticker_symbol)

=item dividend_source(ticker_symbol)

=item split_source(ticker_symbol)

After query, these methods can be used to find out which particular
subclass in the B fulfilled the corresponding request for a
particular ticker symbol.

=back

The following methods are the primary methods of interest for developers
wishing to make a site-specific subclass. The url_maker() factory is
typically all that is necessary.

=over

=item url_maker()

Returns a subroutine reference that serves as an iterrator for producing
URLs based on target and parse mode. Repeated calls to this routine
produce subsequent URLs in the sequence.

=item extractors()

For a particular target mode and parse mode, returns a hash containing
code references to extraction routines for the remaining targets. For
example, for target 'quote' in parse mode 'html' there might be
extractor routines for both 'dividend' and 'split'.

=item ua()

Accessor method for the LWP::UserAgent object used to process
HTTP::Request for individual URLs. This can be handy for such things as
configuring proxy access for the underlying user agent. Example:

 # Manual configuration
 $qh1->ua->proxy(['http'], 'http://proxy.sn.no:8001/');

 # Load from environment variables
 $qh2->ua->env_proxy();

See L for more information on the capabilities
of that module.

=back

The following are potentially useful for calling within methods
overridden above:

=over

=item parse_mode($parse_mode)

Set the current parsing mode. Currently parsers are available for html
and csv.

=item target_mode($target_mode)

Return the current target mode.

=item dates($start_date, $end_date)

Returns a list of business days between and including the provided
boundary dates. If no arguments are provided, B and
B default to the currently specified date range.

=item labels(%parms)

Used to override the default labels for a given target mode and parse
mode. Takes the following named parameters:

=over

=item target_mode

Can currently be 'quote', 'dividend', or 'split'. Default is 'quote'.

=item parse mode

Can currently be 'csv' or 'html'. The default is typically 'csv' but
might vary depending on the quote source.

=item labels

The following are the default labels. Text entries convert to case-
insensitive regular expressions):

  target_mode
  -------------------------------------------------------
  quote    => ['date','open','high','low','close',qr(vol|shares)i]
  dividend => ['date','div']
  split    => ['date','post','pre']

=back

=back

=head1 DISCLAIMER

The data returned from these modules is in no way guaranteed, nor are
the developers responsible in any way for how this data (or lack
thereof) is used. The interface is based on URLs and page layouts that
might change at any time. Even though these modules are designed to be
adaptive under these circumstances, they will at some point probably be
unable to retrieve data unless fixed or provided with new parameters.
Furthermore, the data from these web sites is usually not even
guaranteed by the web sites themselves, and oftentimes is acquired
elsewhere. See the documentation for each site-specific module for more
information regarding the disclaimer for that site.

Above all, play nice.

=head1 AUTHOR

Matthew P. Sisk, EFE

=head1 COPYRIGHT

Copyright (c) 2000-2017 Matthew P. Sisk. All rights reserved. All wrongs
revenged. This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

Finance::QuoteHist(3), HTML::TableExtract(3), Date::Manip(3),
perlmodlib(1), perl(1).

=cut
Finance-QuoteHist-1.29/lib/Finance/QuoteHist.pm0000644000175000017500000001162613563020435020042 0ustar  sisksiskpackage Finance::QuoteHist;

# Simple aggregator for Finance::QuoteHist::Generic instances,
# the primary function of which is to specify the order in which
# to try the modules upon failure.

use strict;
use vars qw($VERSION $AUTOLOAD);
use Carp;

use Finance::QuoteHist::Generic;

$VERSION = '1.29';

my @DEFAULT_ENGINES = qw(
  Finance::QuoteHist::Yahoo
);

sub new {
  my $class = shift;
  my %parms = @_;
  if (!$parms{lineup}) {
    $parms{lineup} = [@DEFAULT_ENGINES];
  }
  elsif (! ref $parms{lineup}) {
    $parms{lineup} = [$parms{lineup}];
  }
  elsif (ref $parms{lineup} ne 'ARRAY') {
    croak "Lineup must be passed as an array ref or single-entry string\n";
  }

  # Instantiate the first, pass the rest as champions to the first
  my $first = shift @{$parms{lineup}};

  eval "require $first";
  croak $@ if $@;

  my $self = $first->new(%parms);

  $self;
}

sub default_lineup { @DEFAULT_ENGINES }

sub granularities { Finance::QuoteHist::Generic->granularities }

1;
__END__

=head1 NAME

Finance::QuoteHist - Perl module for fetching historical stock quotes.

=head1 SYNOPSIS

  use Finance::QuoteHist;
  $q = Finance::QuoteHist->new
     (
      symbols    => [qw(IBM UPS AMZN)],
      start_date => '01/01/2009', # or '1 year ago', see Date::Manip
      end_date   => 'today',
     );

  # Quotes
  foreach $row ($q->quotes()) {
    ($symbol, $date, $open, $high, $low, $close, $volume) = @$row;
    ...
  }

  # Splits
  foreach $row ($q->splits()) {
     ($symbol, $date, $post, $pre) = @$row;
  }

  # Dividends
  foreach $row ($q->dividends()) {
     ($symbol, $date, $dividend) = @$row;
  }

  # Culprit
  $fetch_class = $q->quote_source('IBM');

=head1 DESCRIPTION

Finance::QuoteHist is a top level interface for fetching historical
stock quotes from the web.

It is actually a front end to modules based on
Finance::QuoteHist::Generic, the main difference being that it has a
default I of web sites from which to attempt quote retrieval. If
the prospect of mixing data from multiple sites seems scary to you, then
use one of the site-specific modules directly.

Unless otherwise defined via the I attribute, this module will
select a I for you, the default being:

    Finance::QutoeHist::Yahoo
    Finance::QutoeHist::Google

Once instantiated, this module behaves identically to the first module
in the I, sharing all of that module's methods.

Most queries will likely be handled by the first module in the lineup.
If the site is down for some reason, or perhaps that site does not
provide quotes for defunct ticker symbols, then the other sites in the
lineup will be attempted.

See L for gory details on all of the
parameters and methods this module accepts. The basic interface is
noted below.

=head1 METHODS

The basic user interface consists of several methods, as seen in the
example above. Those methods are:

=over

=item quotes()

Returns a list of rows (or a reference to an array containing those
rows, if in scalar context). Each row contains the B, B,
B, B, B, B, and B for that date.
Optionally, if non-adjusted values were requested, their will be an
extra element at the end of the row for the B closing price.

=item dividends()

Returns a list of rows (or a reference to an array containing those
rows, if in scalar context). Each row contains the B and amount of
the B, in that order.

=item splits()

Returns a list of rows (or a reference to an array containing those
rows, if in scalar context). Each row contains the B, B
split shares, and B
 split shares, in that order.

=item source($ticker, $target)

Each of these methods displays which site-specific class actually
retrieved the information, if any, for a particular ticker symbol and
target such as 'quote' (default), 'dividend', or 'split'.

=back

=head1 DISCLAIMER

The data returned from these modules is in no way guaranteed, nor are
the developers responsible in any way for how this data (or lack
thereof) is used. The interface is based on URLs and page layouts that
might change at any time. Even though these modules are designed to be
adaptive under these circumstances, they will at some point probably be
unable to retrieve data unless fixed or provided with new parameters.
Furthermore, the data from these web sites is usually not even
guaranteed by the web sites themselves, and oftentimes is acquired
elsewhere. See the documentation for each site-specific module for more
information regarding the disclaimer for that site.

=head1 AUTHOR

Matthew P. Sisk, EFE

=head1 COPYRIGHT

Copyright (c) 2000-2017 Matthew P. Sisk. All rights reserved. All wrongs
revenged. This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

Finance::QuoteHist::Generic(3), Finance::QuoteHist::Yahoo(3),
Finance::QuoteHist::QuoteMedia(3), perl(1).

=cut