File size: 6,208 Bytes
f5cd2d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.rods.backtestingstrategies.service;

import org.springframework.stereotype.Service;
import yahoofinance.Stock;
import yahoofinance.YahooFinance;
import yahoofinance.histquotes.HistoricalQuote;
import yahoofinance.histquotes.Interval;
import yahoofinance.quotes.stock.StockQuote;
import yahoofinance.quotes.stock.StockStats;

import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

/**
 * Service wrapping the Yahoo Finance API.
 * No API key required. No enforced rate limits.
 */
@Service
public class YahooFinanceService {

    static {
        // Set standard user-agent so Yahoo doesn't reject as bot with 429 Error
        System.setProperty("http.agent",
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36");
    }

    /**
     * Fetch full stock info (quote, stats, dividend)
     */
    public Stock getStock(String symbol) throws IOException {
        return YahooFinance.get(symbol);
    }

    /**
     * Fetch stock with full historical data (default: 1 year, daily)
     */
    public Stock getStockWithHistory(String symbol) throws IOException {
        return YahooFinance.get(symbol, true);
    }

    /**
     * Fetch historical daily data for a custom date range using v8 API (Bypasses
     * 429)
     */
    public List<HistoricalQuote> getHistoricalData(String symbol, Calendar from, Calendar to) throws IOException {
        String urlString = String.format(
                "https://query1.finance.yahoo.com/v8/finance/chart/%s?period1=%d&period2=%d&interval=1d",
                symbol, from.getTimeInMillis() / 1000, to.getTimeInMillis() / 1000);

        return fetchHistoryFromV8(symbol, urlString);
    }

    /**
     * Fetch historical daily data with default lookback (1 year)
     */
    public List<HistoricalQuote> getHistoricalData(String symbol) throws IOException {
        Calendar from = Calendar.getInstance();
        from.add(Calendar.YEAR, -1);
        Calendar to = Calendar.getInstance();
        return getHistoricalData(symbol, from, to);
    }

    private List<HistoricalQuote> fetchHistoryFromV8(String symbol, String urlString) throws IOException {
        java.util.List<HistoricalQuote> history = new java.util.ArrayList<>();
        try {
            java.net.URL url = new java.net.URL(urlString);
            java.net.HttpURLConnection request = (java.net.HttpURLConnection) url.openConnection();
            request.setRequestMethod("GET");
            request.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            request.connect();

            int responseCode = request.getResponseCode();
            if (responseCode == 404)
                return history; // Stock not found

            com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
            com.fasterxml.jackson.databind.JsonNode root = mapper.readTree(request.getInputStream());
            com.fasterxml.jackson.databind.JsonNode result = root.path("chart").path("result").get(0);

            if (result == null || result.isMissingNode())
                return history;

            com.fasterxml.jackson.databind.JsonNode timestampNode = result.path("timestamp");
            com.fasterxml.jackson.databind.JsonNode quoteNode = result.path("indicators").path("quote").get(0);
            com.fasterxml.jackson.databind.JsonNode adjCloseNode = result.path("indicators").path("adjclose").get(0);

            if (timestampNode.isMissingNode() || quoteNode.isMissingNode())
                return history;

            for (int i = 0; i < timestampNode.size(); i++) {
                long timestamp = timestampNode.get(i).asLong();
                java.util.Calendar date = java.util.Calendar.getInstance();
                date.setTimeInMillis(timestamp * 1000);

                java.math.BigDecimal open = getBigDecimal(quoteNode.path("open").get(i));
                java.math.BigDecimal high = getBigDecimal(quoteNode.path("high").get(i));
                java.math.BigDecimal low = getBigDecimal(quoteNode.path("low").get(i));
                java.math.BigDecimal close = getBigDecimal(quoteNode.path("close").get(i));
                java.math.BigDecimal adjClose = adjCloseNode != null && !adjCloseNode.isMissingNode()
                        ? getBigDecimal(adjCloseNode.path("adjclose").get(i))
                        : close;
                long volume = quoteNode.path("volume").get(i) != null ? quoteNode.path("volume").get(i).asLong() : 0L;

                if (close != null) {
                    history.add(new HistoricalQuote(symbol, date, open, low, high, close, adjClose, volume));
                }
            }
        } catch (Exception e) {
            throw new IOException("Failed to fetch custom Yahoo v8 API for " + symbol, e);
        }
        return history;
    }

    private java.math.BigDecimal getBigDecimal(com.fasterxml.jackson.databind.JsonNode node) {
        if (node == null || node.isNull() || node.isMissingNode())
            return null;
        return new java.math.BigDecimal(node.asText());
    }

    /**
     * Fetch multiple stocks at once (single batch request)
     */
    public Map<String, Stock> getMultipleStocks(String[] symbols) throws IOException {
        return YahooFinance.get(symbols);
    }

    /**
     * Validate if a symbol exists on Yahoo Finance
     */
    public boolean isValidSymbol(String symbol) {
        try {
            Stock stock = YahooFinance.get(symbol);
            return stock != null && stock.getQuote() != null && stock.getQuote().getPrice() != null;
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * Get real-time quote data
     */
    public StockQuote getQuote(String symbol) throws IOException {
        Stock stock = YahooFinance.get(symbol);
        return stock.getQuote();
    }

    /**
     * Get stock statistics (PE, EPS, market cap, etc.)
     */
    public StockStats getStats(String symbol) throws IOException {
        Stock stock = YahooFinance.get(symbol);
        return stock.getStats();
    }
}