App-Chart

 view release on metacpan or  search on metacpan

lib/App/Chart/Barchart.pm  view on Meta::CPAN

#-----------------------------------------------------------------------------
# symbol munging

my @commodity_mung;

sub commodity_mung {
  my ($pred, %table) = @_;
  push @commodity_mung, [ $pred, \%table ];
}

sub symbol_to_barchart {
  my ($symbol) = @_;
  foreach my $elem (@commodity_mung) {
    if ($elem->[0]->match ($symbol)) {
      my $commodity = App::Chart::symbol_commodity ($symbol);
      my $barchart = $elem->[1]->{$commodity};
      if (defined $barchart) {
        $symbol =~ s/^\Q$commodity/$barchart/;
      }
      last;
    }
  }
  $symbol =~ s/.[^.]*$//;
  return $symbol;
}


#------------------------------------------------------------------------------
# latest
#
# The intraday commodity quotes pages are used, like oats
#
#     http://www2.barchart.com/ifutpage.asp?code=BSTK&sym=O
#
# which is about 35 kbytes each.  An alternative would be the combined
# pages like all grains
#
#     http://www2.barchart.com/mktcom.asp?code=BSTK&section=grains
#
# which has the front month or two of various at about 50kbytes the lot.

App::Chart::LatestHandler->new
  (pred => $latest_pred,
   proc => \&latest_download,
   max_symbols => 1);

sub latest_download {
  my ($symbol_list) = @_;

  my $symbol = $symbol_list->[0];
  my $commodity = App::Chart::symbol_commodity ($symbol);
  my $suffix    = App::Chart::symbol_suffix    ($symbol);
  my $barchart_commodity = symbol_to_barchart ("$commodity$suffix");

  App::Chart::Download::status
      (__x('Barchart quote {symbol} ({barchart_commodity})',
           symbol => "$commodity$suffix",
           barchart_commodity => $barchart_commodity));

  my $url = 'http://www2.barchart.com/ifutpage.asp?code=BSTK&sym='
    . URI::Escape::uri_escape ($barchart_commodity);

  my $resp = App::Chart::Download->get ($url,
                                       cookie_jar => cookie_jar(),
                                       referer => $url);
  my $h = ifutpage_parse ($commodity, $suffix, $resp);
  App::Chart::Download::write_latest_group ($h);
}

sub ifutpage_parse {
  my ($commodity, $suffix, $resp) = @_;
  my $content = $resp->decoded_content (raise_error => 1);

  my @data = ();
  my $h = { source      => __PACKAGE__,
            resp        => $resp,
            front_month => 1,
            data        => \@data };

  # eg. "   <B>CRUDE OIL</B> Delayed Futures -20:10 - Sunday, 19 June"
  #     "   <B>SIMEX NIKKEI 225</B> Delayed Futures -18:20 - Tuesday, 12 December</td>"
  #     "   <B>OATS   </B> Daily Futures -     Friday, 20 April                 </td>
  $content =~ m{([^<>\r\n]+) *</B> Delayed Futures *- *([0-9]+:[0-9]+) *- *[A-Za-z]+, ([0-9]+ [A-Za-z]+)}is
    or die 'Barchart: ifutpage name/date/time not matched';
  my $name = $1;
  my $head_time = $2;
  my $head_date = $3;
  ### ifutpage head name: $name
  ### $head_time
  ### $head_date
  require App::Chart::Suffix::NZ;
  $head_date = App::Chart::Suffix::NZ::dm_str_to_nearest_iso ($head_date);

  require HTML::TableExtract;

  my $te = HTML::TableExtract->new
    (headers => ['Contract', 'Last', 'Change', 'Open', 'High', 'Low', 'Time']);
  $te->parse($content);
  if (! $te->tables) { die 'Barchart: ifutpage price columns not matched'; }

  foreach my $row ($te->rows) {
    ### ifutpage row: $row

    my ($month, $last, $change, $open, $high, $low, $time) = @$row;

    # eg. "August '05 ( CLQ05 )"
    $month =~ /\( *([^ )]+) *\)/p
      or die 'Barchart: ifutpage month form not recognised';
    my $month_name = ${^PREMATCH}; # "August '05"
    my $bar_symbol = $1;           # "CLQ05"
    my $MYY = substr ($bar_symbol, -3);
    my $symbol = $commodity . $MYY . $suffix;
    $month_name = App::Chart::collapse_whitespace ($month_name);
    my $month_iso =App::Chart::Download::Decode_Date_EU_to_iso("1 $month_name");

    #     # subtract normal exchange delay to get quote time
    #     (set! quote-time (- quote-time (barchart-symbol-delay symbol)))
    #     (if (negative? quote-time)
    # 	(begin
    # 	  (set! quote-time (+ quote-time 86400))
    # 	  (set! quote-adate (1- quote-adate))))

lib/App/Chart/Barchart.pm  view on Meta::CPAN

  if ($year) {
    $year += 1900;
  } else {
    $year = App::Chart::Download::month_to_nearest_year ($mon+1);
  }
  return App::Chart::ymd_to_iso ($year, $mon+1, $mday);
}


#-----------------------------------------------------------------------------
# 5-day download
#
# This uses the rolling 5-day quote pages like
#
#     http://www.barchart.com/detailedquote/futures/CLZ16
#
use constant FIVEDAY_URL_BASE =>
  'http://www.barchart.com/detailedquote/futures/';
#
# which has daily open, high, low and close, volume.
#
# Going back only five days isn't good for much, but it's better than
# nothing and regular updates accumulate enough for a short-term picture.

App::Chart::DownloadHandler->new
  (name            => __('Barchart'),
   pred            => $fiveday_pred,
   available_tdate => \&fiveday_available_tdate,
   proc            => \&fiveday_download,
   max_symbols     => 1);

# latest data available from barchart 5-day quote page
# the support page says the daily pages update at 5pm US central, try 5pm
# local for the 5-day
sub fiveday_available_tdate {
  return App::Chart::Download::weekday_tdate_after_time
    (17,0, App::Chart::TZ->chicago, -1);
}

sub fiveday_download {
  my ($symbol_list) = @_;

  my $symbol = $symbol_list->[0];
  if (App::Chart::symbol_is_front ($symbol)) { return; }

  # my $commodity = App::Chart::symbol_commodity ($symbol);
  my $suffix    = App::Chart::symbol_suffix    ($symbol);
  my $barchart_symbol = symbol_to_barchart ($symbol);

  if ($barchart_symbol.$suffix eq $symbol) {
    App::Chart::Download::status
        (__x('Barchart five day {symbol}',
             symbol => $symbol));
  } else {
    App::Chart::Download::status
        (__x('Barchart five day {symbol} ({barchart_symbol})',
             symbol => $symbol,
             barchart_symbol => $barchart_symbol));
  }

  my $url = FIVEDAY_URL_BASE . URI::Escape::uri_escape ($barchart_symbol);

  my $resp = App::Chart::Download->get ($url,
                                       cookie_jar => cookie_jar(),
                                       referer => $url);
  my $h = fiveday_parse ($symbol, $resp);
  App::Chart::Download::write_daily_group ($h);
}

sub fiveday_parse {
  my ($symbol, $resp) = @_;

  my @data;
  my $h = { source          => __PACKAGE__,
            prefer_decimals => 2,
            date_format     => 'mdy',
            data            => \@data };

  my $content = $resp->decoded_content (raise_error => 1);

  # message in table when no info for given symbol (eg. an old month/year)
  if ($content =~ /In order to form an extended quote/) {
    return $h;
  }

  # eg. <h1 class="fl" id="symname">Crude Oil WTI December 2016 (CLZ16)</h1>
  #
  $content =~ m{<h1[^>]* id="symname">(([^<\r\n]*?) [a-z]+ [0-9]+)}si
    or die "Barchart fiveday heading not matched";
  my $name = $1;
  ### $name

  require HTML::TableExtract;
  my $te = HTML::TableExtract->new
    (headers => ['Date', 'Open', 'High', 'Low', 'Last', 'Volume']);
  $te->parse($content);
  my ($ts) = $te->tables
    or die 'Barchart: fiveday price columns not matched';

  foreach my $row ($ts->rows) {
    ### fiveday row: $row
    my ($date, $open, $high, $low, $close, $volume) = @$row;

    $open  = dash_frac_to_decimals ($open);
    $high  = dash_frac_to_decimals ($high);
    $low   = dash_frac_to_decimals ($low);
    $close = dash_frac_to_decimals ($close);

    push @data, { symbol  => $symbol,
                  name    => $name,
                  date    => $date,
                  open    => $open,
                  high    => $high,
                  low     => $low,
                  close   => $close,
                  volume  => $volume,
                };
  }
  return $h;
}

lib/App/Chart/Barchart.pm  view on Meta::CPAN

#          E      576 by 360
#          B      612 by 360
#          C      720 by 432
#          D      864 by 504
#     sly=N       linear
#         L       log
#         Y       fit available space
#     late=
#     ch1=011     OHLC [default]
#         012     close-only
#         013     candlestick
#         ...
#     ov1=
#     ch2=
#     ov2=
#     code=BSTKIC IC for interactive chart
#     vol=y/n     volume (not avail for 5min)

foreach my $n (1, 2, 5) {
  App::Chart::IntradayHandler->new
      (pred => $intraday_pred,
       proc => \&intraday_url,
       mode => "${n}d",
       name => __nx('_{n} Day',
                    '_{n} Days',
                    $n,
                    n => $n));
}
App::Chart::IntradayHandler->new
  (pred => $intraday_pred,
   proc => \&intraday_url,
   mode => 'daily',
   name => __('_Daily 2 Months'));
App::Chart::IntradayHandler->new
  (pred => $intraday_pred,
   proc => \&intraday_url,
   mode => 'daily',
   name => __('_Daily 1 Year'));

# 5 minute, linear scale

my %intraday_mode_to_data = ('1d'    => '&p=I&d=L',
                             '2d'    => '&p=I&d=O',
                             '3d'    => '&p=I&d=M',
                             '4d'    => '&p=I&d=H',
                             '5d'    => '&p=I&d=X',
                             'daily2m' => '&p=DO&d=L',
                             'daily1y' => '&p=DO&d=X');

#                              '7d'    => '&data=Z60&den=MEDHIGH',
#                              'daily' => '&data=A');

sub intraday_url {
  my ($self, $symbol, $mode) = @_;

  App::Chart::Download::status
      (__x('Barchart intraday page {symbol} {mode}',
           symbol => $symbol,
           mode   => $mode));
  my $url = 'http://www.barchart.com/chart.php?sym='
    . URI::Escape::uri_escape (symbol_to_barchart ($symbol))
      . $intraday_mode_to_data{$mode};
  App::Chart::Download::verbose_message ("Intraday page", $url);

  my $jar = cookie_jar();
  my $resp = App::Chart::Download->get ($url,
                                        cookie_jar => $jar,
                                        referer => $url);
  return (intraday_resp_to_url ($resp, $symbol),
          cookie_jar => $jar,
          referer => $url);
}
# separate func for offline testing ...
sub intraday_resp_to_url {
  my ($resp, $symbol) = @_;
  my $content = $resp->decoded_content (raise_error => 1);

  require HTML::LinkExtor;
  my $parser = HTML::LinkExtor->new(undef, $resp->base);
  $parser->parse($content);
  $parser->eof;

  # eg. 

  # </map><img src="/cache/bde71ebe23ddac66f2d25081b1b5f953.png"
  # must match some of the link target name since there's other images in
  # the page
  foreach my $link ($parser->links) {
    my ($tag, %attr) = @$link;
    $tag eq 'img' or next;
    my $url = $attr{'src'};
    index ($url, '/cache/') >= 0 or next;
    ### $url
    return URI->new_abs($url,$resp->base)->as_string;
  }

  if ($content =~ /Could not find any symbols/i) {
    die __x("No such symbol {symbol}\n",
            symbol => $symbol);
  } else {
    die 'Barchart Customer: Intraday page not matched';
  }
}

1;
__END__





# ---------------------------------------------------------------------------
# @node Barchart, Finance Quote, Data Sources, Data Sources
# @section Barchart
# @cindex @code{barchart.com}
# 
# @uref{http://www.barchart.com}
# 
# Barchart provides the following for various futures exchanges around the
# world,
# 



( run in 0.929 second using v1.01-cache-2.11-cpan-39bf76dae61 )