Switched to Alpha Avantage API, created test.

This commit is contained in:
Erik C. Thauvin 2019-04-08 01:40:28 -07:00
parent 4b0ef573bc
commit df6567ad1c
2 changed files with 172 additions and 50 deletions

View file

@ -31,13 +31,20 @@
*/
package net.thauvin.erik.mobibot.modules;
import com.Ostermiller.util.CSVParser;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.ErrorMessage;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.PrivateMessage;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
/**
* The StockQuote module.
@ -47,19 +54,86 @@ import java.io.IOException;
* @since 1.0
*/
public final class StockQuote extends AbstractModule {
static final String ALPHAVANTAGE_API_KEY_PROP = "alphavantage-api-key";
// The Alpha Advantage URL.
private static final String ALAPHADVANTAGE_URL = "https://www.alphavantage.co/query?function=GLOBAL_QUOTE";
/**
* The quote command.
*/
public static final String STOCK_CMD = "stock";
// The Yahoo! stock quote URL.
private static final String YAHOO_URL = "http://finance.yahoo.com/d/quotes.csv?&f=snl1d1t1c1oghv&e=.csv&s=";
private static final String STOCK_CMD = "stock";
/**
* Creates a new {@link StockQuote} instance.
*/
public StockQuote() {
commands.add(STOCK_CMD);
properties.put(ALPHAVANTAGE_API_KEY_PROP, "");
}
/**
* Get a stock quote.
*
* @param symbol The stock symbol.
* @return The stock quote.
* @throws ModuleException If an errors occurs.
*/
static ArrayList<Message> getQuote(String symbol, String apiKey) throws ModuleException {
final String debugMessage = "getQuote(" + symbol + ')';
final ArrayList<Message> messages = new ArrayList<>();
final OkHttpClient client = new OkHttpClient();
final Request request =
new Request.Builder().url(ALAPHADVANTAGE_URL + "&symbol=" + symbol + "&apikey=" + apiKey).build();
try {
final Response response = client.newCall(request).execute();
final JSONObject json = new JSONObject(response.body().string());
try {
final String info = json.getString("Information");
if (!info.isEmpty()) {
throw new ModuleException(debugMessage, Utils.unescapeXml(info));
}
} catch (JSONException ignore) {
// Do nothing.
}
try {
final String error = json.getString("Error Message");
if (!error.isEmpty()) {
throw new ModuleException(debugMessage, Utils.unescapeXml(error));
}
} catch (JSONException ignore) {
// Do nothing.
}
final JSONObject quote = json.getJSONObject("Global Quote");
if (quote.isEmpty()) {
messages.add(new ErrorMessage("Invalid symbol."));
return messages;
}
messages.add(
new PublicMessage("Symbol: " + Utils.unescapeXml(quote.getString("01. symbol"))));
messages.add(new PublicMessage(" Price: " + Utils.unescapeXml(quote.getString("05. price"))));
messages.add(new PublicMessage(" Previous: "
+ Utils.unescapeXml(quote.getString("08. previous close"))));
messages.add(new PrivateMessage(" Open: " + Utils.unescapeXml(quote.getString("02. open"))));
messages.add(new PrivateMessage(" High: " + Utils.unescapeXml(quote.getString("03. high"))));
messages.add(new PrivateMessage(" Low: " + Utils.unescapeXml(quote.getString("04. low"))));
messages.add(new PrivateMessage(" Volume: " + Utils.unescapeXml(quote.getString("06. volume"))));
messages.add(new PrivateMessage(" Latest: "
+ Utils.unescapeXml(quote.getString("07. latest trading day"))));
messages.add(new PrivateMessage(" Change: " + Utils.unescapeXml(quote.getString("09. change"))
+ " [" + Utils.unescapeXml(quote.getString("10. change percent")) + ']'));
} catch (IOException e) {
throw new ModuleException(debugMessage, "An error has occurred retrieving a stock quote.", e);
}
return messages;
}
/**
@ -80,60 +154,27 @@ public final class StockQuote extends AbstractModule {
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To retrieve a stock quote:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + STOCK_CMD + " <symbol[.country code]>"));
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + STOCK_CMD + " <symbol>"));
}
/**
* Returns the specified stock quote from Yahoo!
* Returns the specified stock quote from Alpha Advantage.
*/
private void run(final Mobibot bot, final String sender, final String symbol) {
try {
final OkHttpClient client = new OkHttpClient();
final Request request = new Request.Builder().url(YAHOO_URL + symbol.toUpperCase()).build();
final Response response = client.newCall(request).execute();
final String[][] lines = CSVParser.parse(response.body().string());
if (lines.length > 0) {
final String[] quote = lines[0];
if (quote.length > 0) {
if ((quote.length > 3) && (!"N/A".equalsIgnoreCase(quote[3]))) {
bot.send(bot.getChannel(), "Symbol: " + quote[0] + " [" + quote[1] + ']');
if (quote.length > 5) {
bot.send(bot.getChannel(), "Last Trade: " + quote[2] + " (" + quote[5] + ')');
final ArrayList<Message> messages =
getQuote(symbol, properties.get(ALPHAVANTAGE_API_KEY_PROP));
for (Message msg : messages) {
if (msg.isPrivate() || msg.isError()) {
bot.send(sender, msg.getMessage());
} else {
bot.send(bot.getChannel(), "Last Trade: " + quote[2]);
bot.send(bot.getChannel(), msg.getMessage());
}
}
if (quote.length > 4) {
bot.send(sender, "Time: " + quote[3] + ' ' + quote[4]);
}
if (quote.length > 6 && !"N/A".equalsIgnoreCase(quote[6])) {
bot.send(sender, "Open: " + quote[6]);
}
if (quote.length > 7 && !"N/A".equalsIgnoreCase(quote[7]) && !"N/A".equalsIgnoreCase(quote[8])) {
bot.send(sender, "Day's Range: " + quote[7] + " - " + quote[8]);
}
if (quote.length > 9 && !"0".equalsIgnoreCase(quote[9])) {
bot.send(sender, "Volume: " + quote[9]);
}
} else {
bot.send(sender, "Invalid ticker symbol.");
}
} else {
bot.send(sender, "No values returned.");
}
} else {
bot.send(sender, "No data returned.");
}
} catch (NullPointerException | IOException e) {
bot.getLogger().debug("Unable to retrieve stock quote for: " + symbol, e);
bot.send(sender, "An error has occurred retrieving a stock quote: " + e.getMessage());
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, "An error has occurred retrieving a stock quote.");
}
}
}

View file

@ -0,0 +1,81 @@
/*
* StockQuoteTest.java
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.msg.Message;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The <code>StockQuoteTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
*/
public class StockQuoteTest {
@Test
public void testGetQuote() throws ModuleException, IOException {
String apiKey = System.getenv("ALPHA_ADVANTAGE");
if (apiKey == null) {
final Path localProps = Paths.get("local.properties");
if (Files.exists(localProps)) {
try (final InputStream stream = Files.newInputStream(localProps)) {
final Properties p = new Properties();
p.load(stream);
apiKey = p.getProperty(StockQuote.ALPHAVANTAGE_API_KEY_PROP);
}
}
}
ArrayList<Message> messages = StockQuote.getQuote("AAPL", apiKey);
assertThat(messages).as("response not empty").isNotEmpty();
assertThat(messages.get(0).getMessage()).as("same stock symbol").contains("AAPL");
messages = StockQuote.getQuote("012", apiKey);
assertThat(messages.get(0).isError()).as("invalid symbol error").isTrue();
}
@Test
public void testStockQuoteImpl() {
AbstractModuleTest.testAbstractModule(new StockQuote());
}
}