Update on July 2nd 2017
Unfortunately, the API described in this article has been changed and now it requires a session cookie to access the data in CSV format. I guess the guys on Yahoo do not like to display their data without having the chance of displaying their ads...Still, you might download the data manually (example URL here).
In the meantime, I´m looking for an alternate source of prices, I´ll keep you posted.
Summary
In a previous article, we defined how to get new stock quotations as soon as they were published in Google Finance (of course, after a 20-minute delay, this is a free service after all).
However, it was hard to study this data without having any historical data to compare with, so we defined a manual process to load some historical prices.
This manual process is very inefficient, though. You need to do it manually and there is a data gap between the last file update and the current quotations.
Solution: Yahoo finance historical quotations
We know that this service is not suitable for "real-time" quotations in case of the Spanish stock exchange, however, it is perfectly good for getting historical quotations during a period of time and offering more information such as:
- Volume
- Open, close, high and low prices
- Statistical data, such as mobile means, etc.
API Description
There seems to be no documentation for this interface at all (or at least, I have not been able to find it), however, I found some information about it buried in this 2009 blog article.
Basically, you need to compose a URL with the stock ticker and some time delimiters, for instance:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
http://ichart.finance.yahoo.com/table.csv?s=OHI&a=01&b=19&c=2010&d=01&e=19&f=2013&g=d&ignore=.csv | |
#s=OHI will retrieve the Stock OHI (Omega Healthcare Invertors - NYSE) | |
#a=Day number of the start of the time range | |
#b=Month number of the start of the time range | |
#c=Year number of the start of the time range | |
#d=Day number of the end of the time range | |
#e=Month number of the end of the time range | |
#f=Year number of the end of the time range |
New Spring integration flow
As I had to replace the logic for reading the old static files, I created a new flow defined entirely in a separated XML. Basically what it does is:
- Reads from the application settings which stocks are we going to retrieve.
- Reads the time range to recover.
- Performs one request per Stock to the specific URL.
- Parses and converts the received CSV to our internal format.
- Publishes the new POJOs in to the topic so the consumer can use this information seamlessly (it does not really care how the POJOs were built as long as the contract is respected).
Application.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
stokker.target.stocks=SAN.MC,MAP.MC,REP.MC,IBE.MC,OHI,REE.MC,ENAG.MC,BME.MC,ABE.MC,ACS.MC | |
stokker.historicaldata.start.year=2012 | |
stokker.historicaldata.start.month=1 | |
stokker.historicaldata.start.day=1 |
HistoricalCSVRequestCreator
This class will receive a signal from Spring ApplicationContext as soon it is initialised (as it implements the interface ApplicationListener), it will parse the property containing the stocks to be retrieved and finally it will inject one message in the downstream channel per stock (Note that the ticker code is set as message header).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.vferrer.stokker.feeder.csv; | |
// Imports ommitted | |
@SuppressWarnings("rawtypes") | |
@Component | |
public class HistoricalCSVRequestCreator implements ApplicationListener { | |
private List<String> tickers; | |
@Autowired | |
@Qualifier("historical.trigger.split.channel") | |
private MessageChannel channel; | |
@Value("${stokker.target.stocks}") | |
private String tickersConfig; | |
@PostConstruct | |
public void init() | |
{ | |
tickers = Arrays.asList(tickersConfig.split(",")); | |
} | |
@Override | |
public void onApplicationEvent(ApplicationEvent event) { | |
if (!(event instanceof ContextRefreshedEvent)) | |
{ | |
return; | |
} | |
System.out.println("Building request for tickers: " + tickersConfig); | |
for (String ticker : tickers) { | |
MessageBuilder<String> builder = MessageBuilder.withPayload(""); | |
builder.setHeaderIfAbsent("ticker", ticker); | |
channel.send(builder.build()); | |
} | |
} | |
} |
HTTP Outbound Gateway
This is the XML configuration for the component that will perform the request. Note how the target ticket is retrieved from the message header ticker and how the range of time is taken from the properties file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<int-http:outbound-gateway id="historicalQuotationsHttpGateway" | |
url="http://ichart.finance.yahoo.com/table.csv?s={ticker}&a={startmonth}&b={startday}&c={startyear}&g=ds" | |
http-method="GET" | |
expected-response-type="java.lang.String" | |
charset="UTF-8" | |
reply-timeout="5000" | |
reply-channel="historical.raw.quotations.channel" | |
request-channel="historical.trigger.split.channel"> | |
<int-http:uri-variable name="ticker" expression="headers.get('ticker')"/> | |
<int-http:uri-variable name="startday" expression="${stokker.historicaldata.start.day}"/> | |
<int-http:uri-variable name="startmonth" expression="${stokker.historicaldata.start.month}"/> | |
<int-http:uri-variable name="startyear" expression="${stokker.historicaldata.start.year}"/> | |
</int-http:outbound-gateway> |
As usual you can find the code in this GitHub repository. Feedback is always welcome!
Related articles
- Live Stock data as input for your Spring application
- Implement a Spring Integration flow with Spring Boot
- Integrate the application with ElasticSearch
- Represent and search your data with Kibana
- Deploy your Spring Boot microservice in a Docker container