Автор: Александр Кургузкин (mehanizator).
Сервис исторических данных на yahoo.finance имеет то преимущество, что расчитывает для данных adjusted цену, то есть создает “поправленную” цену с учетом дивидендов и сплитов. Напишем же код, который тянет дневные данные по нужному нам тикеру.
Во-первых, на вкладке “Historical prices” в конце таблицы есть ссылка “Download to Spreadsheet” – тянем и разбираем данные.
Однако там могут отсутствовать последний день или даже два последних дня – пришлось с этим столкнуться. Причем в веб-таблице они есть. Поэтому для надежности разбираем еще и веб-таблицу.
В исторических данных отсутствуют данные за текущий день, поэтому забираем по ссылке “Summary”/”Download data (delayed)” в колонке справа. Следует иметь в виду, что они в большинстве случаев не риалтайм, а с задержкой.
Ниже представлен Java код объекта YahooData, который делает все это. Каждый бар дневок представлен объектом класса YBar, данные хранятся в карте Map<Date, YBar>. В объекте YahooData есть функция toFile(), которая скидывает данные в файл. В функции main() – пример использования. Сторонние библиотеки не требуются.
package com.cognitum.research.data;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class YahooData {
public static class YBar {
public final Date date;
public final double open;
public final double high;
public final double low;
public final double close;
public final double volume;
public final double adjusted;
public YBar(Date date, double open, double high, double low, double close, double volume, double adjusted) {
this.date = date;
this.open = open;
this.high = high;
this.low = low;
this.close = close;
this.volume = volume;
this.adjusted = adjusted;
}
@Override
public String toString() {
return String.format(Locale.US, “%s,%f,%f,%f,%f,%f,%f”,
dateFormat.format(date), open, high, low, close, volume, adjusted);
}
}
public final static SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyyMMdd”);
public final Map<Date, YBar> bars = new TreeMap<>();
public YahooData(String ticker) {
String encodedTicker = ticker;
try {
encodedTicker = URLEncoder.encode(ticker, “UTF-8”);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// GETTING DATA FROM “DOWNLOAD HISTORICAL DATA” SOURCE
System.out.println(“getting data from the “download to spreadsheet” source…”);
final Calendar tm = Calendar.getInstance();
tm.add(Calendar.DATE, 1);
final String url = String.format(
“http://ichart.finance.yahoo.com/table.csv?s=%s&d=%d&e=%d&f=%d&g=d&a=&b=&c=&ignore=.csv&tmstmp=%d”,
encodedTicker, tm.get(Calendar.MONTH), tm.get(Calendar.DATE), tm.get(Calendar.YEAR), tm.getTimeInMillis());
final SimpleDateFormat df = new SimpleDateFormat(“yyyy-MM-dd”);
try {
for (String s : getUrlStrings(url)) {
try {
final String[] ss = s.trim().split(“,”);
final Date d = df.parse(ss[0]);
final double open = Double.valueOf(ss[1]);
final double high = Double.valueOf(ss[2]);
final double low = Double.valueOf(ss[3]);
final double close = Double.valueOf(ss[4]);
final double volume = Double.valueOf(ss[5]);
final double adjusted = Double.valueOf(ss[6]);
bars.put(d, new YBar(d, open, high, low, close, volume, adjusted));
} catch (Exception e) {
System.out.println(“cannot parse ” + s);
}
}
} catch (Exception e) {
System.out.println(“cannot get data from ” + url);
}
// GETTING DATA FROM THE WEB TABLE
System.out.println(“getting data from the web table…”);
final String url2 = String.format(“http://finance.yahoo.com/q/hp?s=%s+Historical+Prices”, encodedTicker);
final SimpleDateFormat df2 = new SimpleDateFormat(“MMM d, yyyy”);
try {
final StringBuilder sb = new StringBuilder();
for (String s : getUrlStrings(url2)) {
sb.append(s.trim());
}
final Pattern p = Pattern.compile(“<tr>” +
“<td class.*?>(.*?)</td>” +
“<td .*?>(.*?)</td>” +
“<td .*?>(.*?)</td>” +
“<td .*?>(.*?)</td>” +
“<td .*?>(.*?)</td>” +
“<td .*?>(.*?)</td>” +
“<td .*?>(.*?)</td>” +
“</tr>”);
final Matcher m = p.matcher(sb.toString());
while (m.find()) {
try {
final Date dt = df2.parse(m.group(1));
final double open = Double.valueOf(m.group(2).replace(“,”, “”));
final double high = Double.valueOf(m.group(3).replace(“,”, “”));
final double low = Double.valueOf(m.group(4).replace(“,”, “”));
final double close = Double.valueOf(m.group(5).replace(“,”, “”));
final double volume = Double.valueOf(m.group(6).replace(“,”, “”));
final double adjusted = Double.valueOf(m.group(7).replace(“,”, “”));
bars.put(dt, new YBar(dt, open, high, low, close, volume, adjusted));
} catch (Exception e) {
System.out.println(“cannot parse string: ” + m.group());
}
}
} catch (Exception e) {
System.out.println(“cannot get data from ” + url2);
}
// GETTING LAST DATA FROM THE MAIN TICKER PAGE
System.out.println(“getting last data from the main ticker page…”);
final String url3 = String.format(“http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sl1d1t1c1ohgv&e=.csv”, encodedTicker);
final SimpleDateFormat df3 = new SimpleDateFormat(“MM/dd/yyyy”);
try {
final String currentString = getUrlStrings(url3).get(0).trim();
try {
final String[] ss = currentString.split(“,”);
final Date d = df3.parse(ss[2].replace(“””, “”));
final double open = Double.valueOf(ss[5]);
final double high = Double.valueOf(ss[6]);
final double low = Double.valueOf(ss[7]);
final double close = Double.valueOf(ss[1]);
final double volume = Double.valueOf(ss[8]);
if (!bars.containsKey(d)) bars.put(d, new YBar(d, open, high, low, close, volume, close));
} catch (Exception e) {
System.out.println(“cannot parse ” + currentString);
}
} catch (Exception e) {
System.out.println(“cannot get data from ” + url3);
}
}
public void toFile(String fileName) {
try (final BufferedWriter bw = new BufferedWriter(new FileWriter(fileName))) {
for (YBar b : bars.values()) bw.write(b.toString() + “n”);
} catch (IOException e) {
e.printStackTrace();
}
}
public static List<String> getUrlStrings(String urlString) {
final List<String> list = new ArrayList<>();
try (final BufferedReader br = new BufferedReader(new InputStreamReader(
new URL(urlString).openConnection().getInputStream()))) {
String inputLine;
while ((inputLine = br.readLine()) != null) list.add(inputLine);
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
public static void main(String[] args) {
final YahooData data = new YahooData(“SPY”);
System.out.println(“downloaded bars: ” + data.bars.size());
data.toFile(“c:/test/bars/yahoo-data-test.txt”);
}
}
Комментарии:
armag: В качестве разделителей date, ohlc использовать бы лучше “;”, ибо у меня скачался файл вида:
20131119,179,330000,179,870000,178,720000,179,030000,89417600,000000,179,030000
Хотя возможно зависит от локализации =
armag: Угу, в конструктор добавить смену локализации на US, получаем :
20131119,179.330000,179.870000,178.720000,179.030000,89417600.000000,179.030000
mehanizator: ага, заменить все “String.format(” на “String.format(Locale.US,”
armag: Зачем изобретать велосипед?
Locale.setDefault(Locale.US);
Удачи!
Конв: спасибо за код!
ps:
а final’ы везде (немутабельность?) – это для ускорения работы программы, или просто выдерживается строгость программирования для избежания каких-либо ошибок?
mehanizator: второе. иммутабельность это хорошо 🙂
Конв: а как/где валютки брать?
у них нет вкладки historical prices 🙁
mehanizator: на финаме, например
Конв: а финам дату/время нормирует по Москве или оставляет оригинальную?
mehanizator: а какая дата/время является оригинальной для валют? 🙂
он может ставить московское время.
Конв: угу, увидел там галку “московское время”, спасиб
Конв: а случайно нет проги, чтобы вытянуть список всех тикеров?:
http://finance.yahoo.com/lookup/stocks;_ylt=AmEOpLu4lx26ODK3l4cLzCjxVax_;_ylu=X3oDMTFiM3RzMzF1BHBvcwMyBHNlYwN5ZmlTeW1ib2xMb29rdXBSZXN1bHRzBHNsawNzdG9ja3M-?s=*&t=S&b=0&m=US
mehanizator: а самому написать слабо? 🙂
Конв: ну попробую)