Choosing a stock market data provider is the major question in financial application development. There are two possible solutions: to make a contract to for receive data from one of the stock exchanges, or to use public information. To make the right choice you should consider all the positive and negative sides. Thus, the contract guarantees you uninterrupted access to information and technical support. Public data that are available for free, impose some restrictions: information with a significant delay in time, the absence of any safeguards on the accessibility of information that could lead to inoperability application using this data.

One of the possible ways to write applications that don't have the critical nature, using the public financial data available on the Yahoo! Finance internet portal, - a service from Yahoo! that provides financial information. It is the top financial news and research website in the United States, with more than 23 million visitors in February 2010, according to comScore.

Yahoo! Finance offers information including stock quotes, stock exchange rates, corporate press releases and financial reports, and popular message boards for discussing a company's prospects and stock valuation. It also offers some hosted tools for personal finance management. Yahoo! Finance Worldwide offers similar portals localized to assorted large countries in South America, Europe, and Asia.

Historical data

Any analysis of economic data comes to finding patterns in the past. To reveal these patterns on the market, you will need historical data on securities. Fortunately, Yahoo! Finance provides this information.

The following listing shows the code requesting the information from Yahoo! Finance. The method creates a query then use the StringTokenizer, reads the information in a CSV format from the stream.

/*
 * CDDL HEADER START
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2011 Andrey Pudov. All rights reserved.
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 * Copyright 2011 Andrey Pudov.  All rights reserved.
 * Use is subject to license terms.
 *
 * Contributor(s):
 *
 * Portions Copyrighted 2011 Andrey Pudov.
 *
 */
private static final int CONNECT_TIME_SECONDS = 2;
private static final int READ_TIME_SECONDS    = 3;
private static final String historicURL
        = "http://ichart.finance.yahoo.com/table.csv";
public java.util.List<Historic> getHistorical(Symbol symbol,
                                              java.util.Date start,
                                              java.util.Date end,
                                              Interval ival) {
    java.util.List<Historic> historic = new java.util.ArrayList<>(20);
    java.net.URL           url;
    java.net.URLConnection connection;
    /* a - Month number, starting with 0 for January. */
    String a = Integer.toString(Integer.parseInt(
            new java.text.SimpleDateFormat("MM").format(start)) - 1);
    /* b - Day number, eg, 1 for the first of the month. */
    String b = Integer.toString(Integer.parseInt(
            new java.text.SimpleDateFormat("dd").format(start)));
    /* c - Year. */
    String c = Integer.toString(Integer.parseInt(
            new java.text.SimpleDateFormat("yyyy").format(start)));
    /* d - Month number, starting with 0 for January. */
    String d = Integer.toString(Integer.parseInt(
            new java.text.SimpleDateFormat("MM").format(end)) - 1);
    /* e - Day number, eg, 1 for the first of the month. */
    String e = Integer.toString(Integer.parseInt(
            new java.text.SimpleDateFormat("dd").format(end)));
    /* f - Year. */
    String f = Integer.toString(Integer.parseInt(
            new java.text.SimpleDateFormat("yyyy").format(end)));
    /* frequency of historical prices */
    String g;
    switch (ival) {
        case DAYLY:
            g = "d";
            break;
        case WEEKLY:
            g = "w";
            break;
        case MONTHLY:
            g = "m";
            break;
        default:
            g = "d";
            break;
    }
    try {
        url = new java.net.URL(historicURL + "?s=" + symbol.getCode()
                               + "&d=" + d + "&e=" + e + "&f=" + f + "&g="
                               + g +"&a=" + a + "&b=" + b + "&c=" + c
                               + "&ignore=.csv");
        connection = url.openConnection();
        connection.setConnectTimeout(CONNECT_TIME_SECONDS * 1000);
        connection.setReadTimeout(READ_TIME_SECONDS * 1000);
        connection.setDoInput(true);
        connection.setUseCaches(false);
        try (java.io.BufferedReader br = new java.io.BufferedReader(
                new java.io.InputStreamReader(connection.getInputStream()))) {
            Historic historicValue;
            java.util.StringTokenizer st;
            String s;
            /* read title - Date,Open,High,Low,Close,Volume,Adj Close */
            br.readLine();
            while ((s = br.readLine()) != null) {
                st = new java.util.StringTokenizer(s, ",");
                java.util.Date date = new java.util.Date(
                        new java.text.SimpleDateFormat("yyyy-MM-dd").parse(
                            st.nextToken()).getTime());
                double open     = Double.parseDouble(st.nextToken());
                double high     = Double.parseDouble(st.nextToken());
                double low      = Double.parseDouble(st.nextToken());
                double close    = Double.parseDouble(st.nextToken());
                double volume   = Double.parseDouble(st.nextToken());
                double adjClose = Double.parseDouble(st.nextToken());
                historic.add(new Historic(date, open, high, low, close,
                                          volume, adjClose));
            }
        }
    } catch (java.net.MalformedURLException
             | java.io.IOException
             | java.text.ParseException ex) {
        LOG.warning(ex.toString());
    }
    return java.util.Collections.unmodifiableList(historic);
}
          

The method uses a class «Historic» which implements the storage of historical records that containe the following fields:

  • date — the calendar date
  • OHRC
    • open - the first price of a security at the beginning of a trading day.
    • high - the day's highest price of a security that has changed hands between a buyer and a seller.
    • low - the day's lowest price of a security that has changed hands between a buyer and a seller.
    • close - the final bid and ask price of a security at the end of a trading session.
  • volume - he amount of trading sustained in a security or in the entire market during a given period. Especially heavy volume may indicate that important news has just been announced or is expected.
  • adjClose - provides the closing price for the requested day, week, or month, adjusted for all applicable splits and dividend distributions. Data is adjusted using appropriate split and dividend multipliers.
public class Historic  {
   
    private java.util.Date date;
    private double         open;
    private double         high;
    private double         low;
    private double         close;
    private double         volume;
    private double         adjClose;
   
    public Historic(java.util.Date date, double open, double high, double low,
                    double close, double volume, double adjClose) {
        super();
       
        this.date     = date;
        this.open     = open;
        this.high     = high;
        this.low      = low;
        this.close    = close;
        this.volume   = volume;
        this.adjClose = adjClose;
    }
   
    public long getDate() {
        return date.getTime();
    }
   
    public double getOpen() {
        return open;
    }
   
    public double getHigh() {
        return high;
    }
   
    public double getLow() {
        return low;
    }
   
    public double getClose() {
        return close;
    }
   
    public double getVolume() {
        return volume;
    }
   
    public double getAdjClose() {
        return adjClose;
    }
   
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(20);
        return sb.append("Date:       "
                ).append(new java.text.SimpleDateFormat(
                    "dd-MMM-yy").format(date)
                ).append("\tOpen: ").append(String.format("%1$.2f", open)
                ).append("\tHigh: ").append(String.format("%1$.2f", high)
                ).append("\tLow: ").append(String.format("%1$.2f", low)
                ).append("\tClose: ").append(String.format("%1$.2f", close)
                ).append("\tVolume: ").append(String.format("%1$.2f", volume)
                ).append("\tAdjClose: ").append(String.format("%1$.2f", adjClose)
                ).toString();
    }
   
    @Override
    public int hashCode() {
        return this.date.hashCode() + Double.valueOf(open).hashCode()
               + Double.valueOf(high).hashCode()
               + Double.valueOf(low).hashCode()
               + Double.valueOf(close).hashCode()
               + Double.valueOf(volume).hashCode()
               + Double.valueOf(adjClose).hashCode();
    }
   
}
          

In the same way you need a class “Symbol”, containing information on security and enumeration “Interval”, indicating the sampling interval of data.

public class Symbol {
   
    private String code;
    private String description;
   
    public Symbol(String code, String description) {
        super();
       
        this.code = code;
        this.description = description;
    }
   
    public String getCode() {
        return this.code;
    }
    public String getDescription() {
        return this.description;
    }
   
    @Override
    public int hashCode() {
        return this.code.hashCode();
    }
    @Override
    public boolean equals(Object o) {
        Symbol symbol;
        if ((o instanceof Symbol)) {
            symbol = (Symbol) o;
        } else {
            return false;
        }
        return this.code.equals(symbol.getCode());
    }
   
}
          
public enum Interval {
    DAYLY, WEEKLY, MONTHLY
}
          

Example

java.util.List<Historic>  historic = getHistorical(new Symbol(
                        "GOOG", null),
                    new java.util.GregorianCalendar(
                            2010, java.util.Calendar.JANUARY, 01).getTime(),
                    new java.util.GregorianCalendar(
                            2010, java.util.Calendar.DECEMBER, 01).getTime(),
                    Interval.DAYLY);
       
for (Historic value : historic) {
    System.out.println(value);
}
          

This code shows the information as follows (volume reduced):

Date: 01-Dec-10 Open: 563.00 High: 571.57 Low: 562.40 Close: 564.35 Volume: 3754100.00 AdjClose: 564.35
Date: 30-Nov-10 Open: 574.32 High: 574.32 Low: 553.31 Close: 555.71 Volume: 7117400.00 AdjClose: 555.71
Date: 29-Nov-10 Open: 589.17 High: 589.80 Low: 579.95 Close: 582.11 Volume: 2859700.00 AdjClose: 582.11
...
Date: 05-Jan-10 Open: 627.18 High: 627.84 Low: 621.54 Close: 623.99 Volume: 3004700.00 AdjClose: 623.99
Date: 04-Jan-10 Open: 626.95 High: 629.51 Low: 624.24 Close: 626.75 Volume: 1956200.00 AdjClose: 626.75