в Алготрейдинг

Тянем дневные данные с Yahoo.Finance: код на Java

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

Конв: ну попробую)