Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/main/java/com/crazzyghost/alphavantage/AlphaVantage.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.crazzyghost.alphavantage.fundamentaldata.FundamentalData;
import com.crazzyghost.alphavantage.indicator.Indicator;
import com.crazzyghost.alphavantage.marketstatus.MarketStatus;
import com.crazzyghost.alphavantage.news.News;
import com.crazzyghost.alphavantage.search.Search;
import com.crazzyghost.alphavantage.sector.Sector;
import com.crazzyghost.alphavantage.technicalindicator.TechnicalIndicator;
Expand Down Expand Up @@ -173,5 +174,8 @@ public Search search() {
return new Search(config);
}


//News symbols
public News News() {
return new News(config);
}
}
123 changes: 123 additions & 0 deletions src/main/java/com/crazzyghost/alphavantage/news/News.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.crazzyghost.alphavantage.news;

import com.crazzyghost.alphavantage.AlphaVantageException;
import com.crazzyghost.alphavantage.Config;
import com.crazzyghost.alphavantage.Fetcher;
import com.crazzyghost.alphavantage.UrlExtractor;
import com.crazzyghost.alphavantage.news.request.NewsRequest;
import com.crazzyghost.alphavantage.news.response.NewsResponse;
import com.crazzyghost.alphavantage.parser.Parser;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.jetbrains.annotations.NotNull;

public final class News implements Fetcher {
private final Config config;
private final NewsRequest.Builder builder;
private SuccessCallback<NewsResponse> successCallback;
private FailureCallback failureCallback;

public News(Config config) {
this.config = config;
this.builder = new NewsRequest.Builder();
}

public News setTickers(String tickers) {
this.builder.tickers(tickers);
return this;
}

public News setTopics(String topics) {
this.builder.topics(topics);
return this;
}

public News setTimeFrom(String timeFrom) {
this.builder.timeFrom(timeFrom);
return this;
}

public News setTimeTo(String timeTo) {
this.builder.timeTo(timeTo);
return this;
}

public News setSort(String sort) {
this.builder.sort(sort);
return this;
}

public News setLimit(int limit) {
this.builder.limit(limit);
return this;
}

public News onSuccess(SuccessCallback<NewsResponse> callback) {
this.successCallback = callback;
return this;
}

public News onFailure(FailureCallback callback) {
this.failureCallback = callback;
return this;
}

public NewsResponse fetchSync() throws AlphaVantageException {

Config.checkNotNullOrKeyEmpty(config);

this.successCallback = null;
this.failureCallback = null;
OkHttpClient client = config.getOkHttpClient();

try (Response response = client.newCall(UrlExtractor.extract(builder.build(), config.getKey()))
.execute()) {
return NewsResponse.of(
Parser.parseJSON(response.body() != null ? response.body().string() : null));
} catch (IOException e) {
throw new AlphaVantageException(e.getMessage());
}
}

@Override
public void fetch() {

Config.checkNotNullOrKeyEmpty(config);

config.getOkHttpClient().newCall(UrlExtractor.extract(builder.build(), config.getKey()))
.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
if (failureCallback != null) {
failureCallback.onFailure(new AlphaVantageException());
}
}

@Override
public void onResponse(@NotNull Call call, @NotNull Response response)
throws IOException {
if (response.isSuccessful()) {
try (ResponseBody body = response.body()) {
NewsResponse newsResponse =
NewsResponse.of(Parser.parseJSON(body != null ? body.string() : null));
if (newsResponse.getErrorMessage() != null && failureCallback != null) {
failureCallback.onFailure(
new AlphaVantageException(newsResponse.getErrorMessage()));
}
if (successCallback != null) {
successCallback.onSuccess(newsResponse);
}
}
} else {
if (failureCallback != null) {
failureCallback.onFailure(new AlphaVantageException());
}
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.crazzyghost.alphavantage.news.request;

import com.crazzyghost.alphavantage.parameters.Function;

public class NewsRequest {

protected Function function;
protected String tickers; // Optional: Filter by tickers
protected String topics; // Optional: Filter by topics
protected String timeFrom; // Optional: Filter by starting time
protected String timeTo; // Optional: Filter by ending time
protected String sort; // Optional: Sorting options
protected int limit; // Optional: Limit of results

protected NewsRequest(Builder builder) {
this.function = builder.function;
this.tickers = builder.tickers;
this.topics = builder.topics;
this.timeFrom = builder.timeFrom;
this.timeTo = builder.timeTo;
this.sort = builder.sort;
this.limit = builder.limit;
}

public static class Builder {

public Function function;
protected String tickers;
protected String topics;
protected String timeFrom;
protected String timeTo;
protected String sort;
protected int limit = 50; // Default limit

public Builder() {
this.function = Function.NEWS_SENTIMENT;
}

public Builder function(Function function) {
this.function = function;
return this;
}

public Builder tickers(String tickers) {
this.tickers = tickers;
return this;
}

public Builder topics(String topics) {
this.topics = topics;
return this;
}

public Builder timeFrom(String timeFrom) {
this.timeFrom = timeFrom;
return this;
}

public Builder timeTo(String timeTo) {
this.timeTo = timeTo;
return this;
}

public Builder sort(String sort) {
this.sort = sort;
return this;
}

public Builder limit(int limit) {
this.limit = limit;
return this;
}

public NewsRequest build() {
return new NewsRequest(this); // Use constructor to create instance
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.crazzyghost.alphavantage.news.response;

import com.crazzyghost.alphavantage.parser.Parser;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class NewsResponse {
private final String errorMessage;
private final List<NewsItem> newsItems;

private NewsResponse(List<NewsItem> newsItems, String error) {
this.errorMessage = error;
this.newsItems = newsItems != null ? newsItems : new ArrayList<>();
}

public static NewsResponse of(Map<String, Object> stringObjectMap) {
Parser<NewsResponse> parser = new NewsParser();
return parser.parse(stringObjectMap);
}

public List<NewsItem> getNewsItems() {
return newsItems;
}

public String getErrorMessage() {
return errorMessage;
}

@Override
public String toString() {
return newsItems.toString();
}

public static class NewsItem {
private final String title;
private final String url;
private final String timePublished;
private final List<String> authors;
private final String summary;
private final String source;
private final double overallSentimentScore;
private final String overallSentimentLabel;
private final List<TickerSentiment> tickerSentiment;

public NewsItem(String title, String url, String timePublished, List<String> authors,
String summary,
String source, double overallSentimentScore, String overallSentimentLabel,
List<TickerSentiment> tickerSentiment) {
this.title = title;
this.url = url;
this.timePublished = timePublished;
this.authors = authors != null ? authors : new ArrayList<>();
this.summary = summary;
this.source = source;
this.overallSentimentScore = overallSentimentScore;
this.overallSentimentLabel = overallSentimentLabel;
this.tickerSentiment = tickerSentiment != null ? tickerSentiment : new ArrayList<>();
}

@Override
public String toString() {
return title + " - " + summary + " (" + overallSentimentLabel + ")";
}

public String getTitle() {
return title;
}

public String getSummary() {
return summary;
}

public String getUrl() {
return url;
}
}

public static class TickerSentiment {
private final String ticker;
private final double relevanceScore;
private final double sentimentScore;
private final String sentimentLabel;

public TickerSentiment(String ticker, double relevanceScore, double sentimentScore,
String sentimentLabel) {
this.ticker = ticker;
this.relevanceScore = relevanceScore;
this.sentimentScore = sentimentScore;
this.sentimentLabel = sentimentLabel;
}

@Override
public String toString() {
return ticker + ": " + sentimentLabel + " (" + sentimentScore + ")";
}
}

public static class NewsParser extends Parser<NewsResponse> {

@SuppressWarnings("unchecked")
@Override
public NewsResponse parse(Map<String, Object> stringObjectMap) {
List<NewsItem> newsItems = new ArrayList<>();
String errorMessage = null;

// Check if the response is empty
if (stringObjectMap.isEmpty()) {
return onParseError("Empty JSON returned by the API, no news items found.");
}

try {
List<Map<String, Object>> feed = (List<Map<String, Object>>) stringObjectMap.get("feed");
if (feed != null) {
for (Map<String, Object> item : feed) {
// Parse the ticker sentiment
List<TickerSentiment> tickerSentiments = new ArrayList<>();
List<Map<String, Object>> tickerSentimentList =
(List<Map<String, Object>>) item.get("ticker_sentiment");
if (tickerSentimentList != null) {
for (Map<String, Object> tickerSentimentMap : tickerSentimentList) {
tickerSentiments.add(new TickerSentiment(
(String) tickerSentimentMap.get("ticker"),
Double.parseDouble(tickerSentimentMap.get("relevance_score").toString()),
Double.parseDouble(tickerSentimentMap.get("ticker_sentiment_score").toString()),
(String) tickerSentimentMap.get("ticker_sentiment_label")
));
}
}

// Create a NewsItem and add it to the list
newsItems.add(new NewsItem(
(String) item.get("title"),
(String) item.get("url"),
(String) item.get("time_published"),
(List<String>) item.get("authors"),
(String) item.get("summary"),
(String) item.get("source"),
Double.parseDouble(item.get("overall_sentiment_score").toString()),
(String) item.get("overall_sentiment_label"),
tickerSentiments
));
}
}
} catch (ClassCastException | NullPointerException e) {
errorMessage = "Error parsing news sentiment results.";
}

return new NewsResponse(newsItems, errorMessage);
}

@Override
public NewsResponse onParseError(String error) {
return new NewsResponse(null, error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,5 @@ public enum Function {
DURABLES,
UNEMPLOYMENT,
NONFARM_PAYROLL,

NEWS_SENTIMENT,
}