Автор: Александр Кургузкин (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: а самому написать слабо? 🙂
Конв: ну попробую)