Uname: Linux web3.us.cloudlogin.co 5.10.226-xeon-hst #2 SMP Fri Sep 13 12:28:44 UTC 2024 x86_64
Software: Apache
PHP version: 8.1.31 [ PHP INFO ] PHP os: Linux
Server Ip: 162.210.96.117
Your Ip: 18.219.168.245
User: edustar (269686) | Group: tty (888)
Safe Mode: OFF
Disable Function:
NONE

name : USFedBonds.pm
#    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 2 of the License, 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

package Finance::Quote::USFedBonds;

use strict;
use warnings;

use constant DEBUG => $ENV{DEBUG};
use if DEBUG, 'Smart::Comments';

use LWP::UserAgent;
use Web::Scraper;
use String::Util qw(trim);
use DateTime;

our $VERSION = '1.52'; # VERSION
my $TREASURY_MAINURL = ("http://www.treasurydirect.gov/");
my $TREASURY_URL = ($TREASURY_MAINURL."indiv/tools/");

sub methods {
    return (usfedbonds => \&treasury);
}

sub labels {
    my @labels = qw/method source name symbol currency last date isodate nav price/;
    return (usfedbonds => \@labels);
}

sub treasury {
  my $quoter  = shift;
  my @symbols = @_;
  my $ua      = $quoter->user_agent();
  my %info;

  my %month; 
  @month{qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/} = (1 .. 12);
  @month{qw/January February March April May June July August September October November December/} = (1 .. 12);

  my $root  = 'https://www.treasurydirect.gov/indiv/tools/';
  my $url   = $root . 'tools_savingsbondvalues_historical.htm';
  my $reply = $ua->get($url);

  ### [<now>] Fetched: $url
  my $data = scraper {
    process '//*[@id="content"]/p[2]/a', 'links[]' => {'name' => 'TEXT', 'href' => '@href'};
  };

  my $result = $data->scrape($reply);

  # Build a map from "YYYYMM" of redemption to URL
  my %map;
  foreach my $link (@{$result->{links}}) {
    if ($link->{name} =~ /^(?<startmonth>[A-Za-z]+)\s+(?<startyear>[0-9]{4}).*?(?<endmonth>[A-Za-z]+)\s+(?<endyear>[0-9]{4})$/) {
      my $start = DateTime->new(year => $+{startyear}, month => $month{$+{startmonth}});
      my $end   = DateTime->new(year => $+{endyear}, month => $month{$+{endmonth}});
      
      while ($start <= $end) {
        my $key    = $start->strftime('%Y%m');
        $map{$key} = $link->{href};

        $start->add(months => 1); 
      }
    }
  }

  my %cache;

  foreach my $symbol (@_) {
    eval {
      die "$symbol does not match expected format" unless $symbol =~ /^(?<series>[IENS])(?<issueyear>[0-9]{4})(?<issuemonth>[0-9]{2})[.](?<redemptionyear>[0-9]{4})(?<redemptionmonth>[0-9]{2})$/;
     
      my $issuemonth      = $+{issuemonth};
      my $redemptionmonth = $+{redemptionmonth};
      my $redemptionyear  = $+{redemptionyear};

      my $redemption = $+{redemptionyear} . $+{redemptionmonth};
      my $url        = $map{$redemption};
      my $row_index  = $+{series} . $+{redemptionyear} . $+{redemptionmonth} . $+{issueyear};

      # row index is [series(1) + redemption year(4) + redemption month(2)](7) + issue year(4) 
      unless (exists $cache{$url}) {
        my %table = map {$_->[0] . $_->[1] => $_}
          map {[unpack("A7A4A6A6A6A6A6A6A6A6A6A6A6A6", $_)]}
            split /\n/, $ua->get($url)->content();
        $cache{$url} = \%table;
      }
      
      my $row       = $cache{$url}->{$row_index};

      ### Looking for : $symbol, $redemption
      ### url         : $url
      ### row         : $row_index

      die "value not found for $symbol" unless $row->[1 + $issuemonth] =~ /[0-9]{6}/;
      
      $info{$symbol, "price"} = sprintf('%.2f', $row->[1 + $issuemonth]/100);
      $info{$symbol, "currency"} = "USD";

      $quoter->store_date(\%info, $symbol, {usdate => "$redemptionmonth/01/$redemptionyear"});

      $info{$symbol, "success"} = 1;
    };

    if ($@) {
      my $error = "USFedBonds failed: $@";
      $info{$symbol, 'success'}  = 0;
      $info{$symbol, 'errormsg'} = trim($error);
    }
  }
 
  return wantarray() ? %info : \%info;
}

1;

__END__

=head1 NAME

Finance::Quote::USFedBonds - Get US Federal Bond redemption values from http://www.treasurydirect.gov

=head1 SYNOPSIS

    use Finance::Quote;

    $q    = Finance::Quote->new();
    %info = $q->fetch('usfedbonds', 'E197001.200606');

=head1 DESCRIPTION

Access redemption values for US Federal Bonds from the treasury.

Bonds should be identified in the following manner:

SERIES(1)         : I/E/N/S

ISSUEDATE(6)      : YYYYMM

SEPERATOR(1)      : "."

REDEMPTIONDATE(6) : YYYYMM

e.g. E200101.200501


=head1 LABELS RETURNED

price, date, isodate

=head1 TERMS & CONDITIONS

Use of www.treasurydirect.gov is governed by any terms & conditions of that site.

Finance::Quote is released under the GNU General Public License, version 2,
which explicitly carries a "No Warranty" clause.


=cut
© 2025 GrazzMean