📋 文檔概述
本文檔詳細介紹如何使用 Java 語言對接 StockTV 印度股票數據源,包含完整的代碼示例、數據模型、異常處理等。
🚀 快速開始
環境要求
- JDK 8+
- Maven 3.6+
- 網絡連接(可訪問
api.stocktv.top)
項目依賴
<!-- pom.xml -->
<dependencies>
<!-- HTTP客户端 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- JSON處理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- 日誌框架 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Lombok(可選) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
</dependencies>
🏗️ 核心架構
項目結構
src/main/java/com/stocktv/india/
├── config/
│ └── StockTVConfig.java
├── model/
│ ├── Stock.java
│ ├── Index.java
│ ├── KLine.java
│ └── ApiResponse.java
├── client/
│ ├── StockTVHttpClient.java
│ └── StockTVWebSocketClient.java
├── service/
│ └── IndiaStockService.java
└── demo/
└── IndiaStockDemo.java
📦 核心代碼實現
1. 配置類
package com.stocktv.india.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
/**
* StockTV API 配置類
*/
public class StockTVConfig {
// API 基礎配置
public static final String BASE_URL = "https://api.stocktv.top";
public static final String WS_URL = "wss://ws-api.stocktv.top/connect";
// 印度市場配置
public static final int INDIA_COUNTRY_ID = 14;
public static final int NSE_EXCHANGE_ID = 46;
public static final int BSE_EXCHANGE_ID = 74;
// API Key
private final String apiKey;
// HTTP 客户端和JSON處理器
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public StockTVConfig(String apiKey) {
this.apiKey = apiKey;
this.httpClient = HttpClients.createDefault();
this.objectMapper = new ObjectMapper();
// 配置ObjectMapper
this.objectMapper.findAndRegisterModules();
}
// Getter方法
public String getApiKey() { return apiKey; }
public CloseableHttpClient getHttpClient() { return httpClient; }
public ObjectMapper getObjectMapper() { return objectMapper; }
}
2. 數據模型類
股票數據模型
package com.stocktv.india.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 印度股票數據模型
*/
@Data
public class Stock {
@JsonProperty("id")
private Long id;
@JsonProperty("symbol")
private String symbol;
@JsonProperty("name")
private String name;
@JsonProperty("last")
private BigDecimal lastPrice;
@JsonProperty("chg")
private BigDecimal change;
@JsonProperty("chgPct")
private BigDecimal changePercent;
@JsonProperty("high")
private BigDecimal high;
@JsonProperty("low")
private BigDecimal low;
@JsonProperty("volume")
private Long volume;
@JsonProperty("open")
private Boolean isOpen;
@JsonProperty("exchangeId")
private Integer exchangeId;
@JsonProperty("countryId")
private Integer countryId;
@JsonProperty("time")
private Long timestamp;
@JsonProperty("fundamentalMarketCap")
private BigDecimal marketCap;
@JsonProperty("fundamentalRevenue")
private String revenue;
// 技術指標
@JsonProperty("technicalDay")
private String technicalDay;
@JsonProperty("technicalHour")
private String technicalHour;
@JsonProperty("technicalWeek")
private String technicalWeek;
@JsonProperty("technicalMonth")
private String technicalMonth;
// 性能指標
@JsonProperty("performanceDay")
private BigDecimal performanceDay;
@JsonProperty("performanceWeek")
private BigDecimal performanceWeek;
@JsonProperty("performanceMonth")
private BigDecimal performanceMonth;
@JsonProperty("performanceYtd")
private BigDecimal performanceYtd;
/**
* 獲取格式化的時間
*/
public LocalDateTime getFormattedTime() {
return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
}
/**
* 獲取交易所名稱
*/
public String getExchangeName() {
if (NSE_EXCHANGE_ID == exchangeId) {
return "NSE";
} else if (BSE_EXCHANGE_ID == exchangeId) {
return "BSE";
}
return "Unknown";
}
}
// 常量接口
interface ExchangeConstants {
int NSE_EXCHANGE_ID = 46;
int BSE_EXCHANGE_ID = 74;
}
K線數據模型
package com.stocktv.india.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* K線數據模型
*/
@Data
public class KLine {
@JsonProperty("time")
private Long timestamp;
@JsonProperty("open")
private BigDecimal open;
@JsonProperty("high")
private BigDecimal high;
@JsonProperty("low")
private BigDecimal low;
@JsonProperty("close")
private BigDecimal close;
@JsonProperty("volume")
private Long volume;
@JsonProperty("vo")
private BigDecimal turnover;
/**
* 獲取格式化的時間
*/
public LocalDateTime getFormattedTime() {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
}
/**
* 計算振幅
*/
public BigDecimal getAmplitude() {
if (open == null || open.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return high.subtract(low).divide(open, 4, BigDecimal.ROUND_HALF_UP);
}
}
指數數據模型
package com.stocktv.india.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 指數數據模型
*/
@Data
public class Index {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String name;
@JsonProperty("symbol")
private String symbol;
@JsonProperty("last")
private BigDecimal lastPrice;
@JsonProperty("chg")
private BigDecimal change;
@JsonProperty("chgPct")
private BigDecimal changePercent;
@JsonProperty("high")
private BigDecimal high;
@JsonProperty("low")
private BigDecimal low;
@JsonProperty("isOpen")
private Boolean isOpen;
@JsonProperty("time")
private Long timestamp;
@JsonProperty("flag")
private String countryFlag;
/**
* 獲取格式化的時間
*/
public LocalDateTime getFormattedTime() {
return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
}
}
API響應包裝類
package com.stocktv.india.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* API通用響應包裝類
*/
@Data
public class ApiResponse<T> {
@JsonProperty("code")
private Integer code;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private T data;
/**
* 判斷請求是否成功
*/
public boolean isSuccess() {
return code != null && code == 200;
}
}
/**
* 股票列表響應包裝類
*/
@Data
class StockListResponse {
@JsonProperty("records")
private List<Stock> records;
@JsonProperty("total")
private Integer total;
@JsonProperty("current")
private Integer current;
@JsonProperty("pages")
private Integer pages;
@JsonProperty("size")
private Integer size;
}
3. HTTP客户端實現
package com.stocktv.india.client;
import com.fasterxml.jackson.core.type.TypeReference;
import com.stocktv.india.config.StockTVConfig;
import com.stocktv.india.model.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
/**
* StockTV HTTP API客户端
*/
public class StockTVHttpClient {
private static final Logger logger = LoggerFactory.getLogger(StockTVHttpClient.class);
private final StockTVConfig config;
private final CloseableHttpClient httpClient;
public StockTVHttpClient(StockTVConfig config) {
this.config = config;
this.httpClient = config.getHttpClient();
}
/**
* 獲取印度股票列表
*
* @param pageSize 每頁數量
* @param page 頁碼
* @return 股票列表
*/
public List<Stock> getIndiaStocks(Integer pageSize, Integer page) throws IOException, URISyntaxException {
URI uri = new URIBuilder(config.BASE_URL + "/stock/stocks")
.addParameter("countryId", String.valueOf(StockTVConfig.INDIA_COUNTRY_ID))
.addParameter("pageSize", String.valueOf(pageSize))
.addParameter("page", String.valueOf(page))
.addParameter("key", config.getApiKey())
.build();
ApiResponse<StockListResponse> response = executeGetRequest(uri,
new TypeReference<ApiResponse<StockListResponse>>() {});
if (response.isSuccess()) {
logger.info("成功獲取 {} 條印度股票數據", response.getData().getRecords().size());
return response.getData().getRecords();
} else {
throw new RuntimeException("API請求失敗: " + response.getMessage());
}
}
/**
* 查詢單個股票
*
* @param pid 股票PID
* @param symbol 股票代碼
* @param name 股票名稱
* @return 股票列表
*/
public List<Stock> queryStock(Long pid, String symbol, String name) throws IOException, URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(config.BASE_URL + "/stock/queryStocks")
.addParameter("key", config.getApiKey());
if (pid != null) {
uriBuilder.addParameter("id", String.valueOf(pid));
}
if (symbol != null) {
uriBuilder.addParameter("symbol", symbol);
}
if (name != null) {
uriBuilder.addParameter("name", name);
}
URI uri = uriBuilder.build();
ApiResponse<List<Stock>> response = executeGetRequest(uri,
new TypeReference<ApiResponse<List<Stock>>>() {});
if (response.isSuccess()) {
logger.info("股票查詢成功,返回 {} 條記錄", response.getData().size());
return response.getData();
} else {
throw new RuntimeException("股票查詢失敗: " + response.getMessage());
}
}
/**
* 批量查詢多個股票
*
* @param pids 股票PID列表
* @return 股票列表
*/
public List<Stock> getStocksByPids(List<Long> pids) throws IOException, URISyntaxException {
if (pids == null || pids.isEmpty()) {
throw new IllegalArgumentException("股票PID列表不能為空");
}
String pidsStr = String.join(",", pids.stream().map(String::valueOf).toArray(String[]::new));
URI uri = new URIBuilder(config.BASE_URL + "/stock/stocksByPids")
.addParameter("key", config.getApiKey())
.addParameter("pids", pidsStr)
.build();
ApiResponse<List<Stock>> response = executeGetRequest(uri,
new TypeReference<ApiResponse<List<Stock>>>() {});
if (response.isSuccess()) {
logger.info("批量查詢成功,返回 {} 條記錄", response.getData().size());
return response.getData();
} else {
throw new RuntimeException("批量查詢失敗: " + response.getMessage());
}
}
/**
* 獲取印度主要指數
*
* @return 指數列表
*/
public List<Index> getIndiaIndices() throws IOException, URISyntaxException {
URI uri = new URIBuilder(config.BASE_URL + "/stock/indices")
.addParameter("countryId", String.valueOf(StockTVConfig.INDIA_COUNTRY_ID))
.addParameter("key", config.getApiKey())
.build();
ApiResponse<List<Index>> response = executeGetRequest(uri,
new TypeReference<ApiResponse<List<Index>>>() {});
if (response.isSuccess()) {
logger.info("成功獲取 {} 個印度指數", response.getData().size());
return response.getData();
} else {
throw new RuntimeException("獲取指數失敗: " + response.getMessage());
}
}
/**
* 獲取K線數據
*
* @param pid 股票PID
* @param interval 時間間隔
* @return K線數據列表
*/
public List<KLine> getKLineData(Long pid, String interval) throws IOException, URISyntaxException {
if (pid == null) {
throw new IllegalArgumentException("股票PID不能為空");
}
URI uri = new URIBuilder(config.BASE_URL + "/stock/kline")
.addParameter("pid", String.valueOf(pid))
.addParameter("interval", interval)
.addParameter("key", config.getApiKey())
.build();
ApiResponse<List<KLine>> response = executeGetRequest(uri,
new TypeReference<ApiResponse<List<KLine>>>() {});
if (response.isSuccess()) {
logger.info("成功獲取股票 {} 的K線數據,共 {} 條", pid, response.getData().size());
return response.getData();
} else {
throw new RuntimeException("獲取K線數據失敗: " + response.getMessage());
}
}
/**
* 獲取漲跌排行榜
*
* @param type 排行榜類型
* @return 股票列表
*/
public List<Stock> getUpDownList(Integer type) throws IOException, URISyntaxException {
URI uri = new URIBuilder(config.BASE_URL + "/stock/updownList")
.addParameter("countryId", String.valueOf(StockTVConfig.INDIA_COUNTRY_ID))
.addParameter("type", String.valueOf(type))
.addParameter("key", config.getApiKey())
.build();
ApiResponse<List<Stock>> response = executeGetRequest(uri,
new TypeReference<ApiResponse<List<Stock>>>() {});
if (response.isSuccess()) {
String typeName = getRankingTypeName(type);
logger.info("成功獲取{},共 {} 條記錄", typeName, response.getData().size());
return response.getData();
} else {
throw new RuntimeException("獲取排行榜失敗: " + response.getMessage());
}
}
/**
* 獲取IPO新股日曆
*
* @param type IPO類型
* @return IPO列表
*/
public List<Object> getIpoList(Integer type) throws IOException, URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(config.BASE_URL + "/stock/getIpo")
.addParameter("countryId", String.valueOf(StockTVConfig.INDIA_COUNTRY_ID))
.addParameter("key", config.getApiKey());
if (type != null) {
uriBuilder.addParameter("type", String.valueOf(type));
}
URI uri = uriBuilder.build();
ApiResponse<List<Object>> response = executeGetRequest(uri,
new TypeReference<ApiResponse<List<Object>>>() {});
if (response.isSuccess()) {
logger.info("成功獲取IPO數據,共 {} 條", response.getData().size());
return response.getData();
} else {
throw new RuntimeException("獲取IPO數據失敗: " + response.getMessage());
}
}
/**
* 通用GET請求執行方法
*/
private <T> T executeGetRequest(URI uri, TypeReference<T> typeReference) throws IOException {
HttpGet request = new HttpGet(uri);
logger.debug("執行API請求: {}", uri);
try (CloseableHttpResponse response = httpClient.execute(request)) {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (statusCode != 200) {
throw new IOException("HTTP請求失敗,狀態碼: " + statusCode);
}
logger.debug("API響應: {}", responseBody);
return config.getObjectMapper().readValue(responseBody, typeReference);
}
}
/**
* 獲取排行榜類型名稱
*/
private String getRankingTypeName(Integer type) {
switch (type) {
case 1: return "漲幅榜";
case 2: return "跌幅榜";
case 3: return "漲停榜";
case 4: return "跌停榜";
default: return "排行榜";
}
}
/**
* 關閉HTTP客户端
*/
public void close() throws IOException {
if (httpClient != null) {
httpClient.close();
}
}
}
4. 服務層封裝
package com.stocktv.india.service;
import com.stocktv.india.client.StockTVHttpClient;
import com.stocktv.india.config.StockTVConfig;
import com.stocktv.india.model.Index;
import com.stocktv.india.model.KLine;
import com.stocktv.india.model.Stock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.stream.Collectors;
/**
* 印度股票數據服務
*/
public class IndiaStockService {
private static final Logger logger = LoggerFactory.getLogger(IndiaStockService.class);
private final StockTVHttpClient httpClient;
private final StockTVConfig config;
public IndiaStockService(String apiKey) {
this.config = new StockTVConfig(apiKey);
this.httpClient = new StockTVHttpClient(config);
}
/**
* 獲取Nifty 50成分股
*/
public List<Stock> getNifty50Stocks() {
try {
// 實際應用中應該根據Nifty 50成分股列表查詢
List<Stock> stocks = httpClient.getIndiaStocks(50, 1);
logger.info("獲取Nifty 50成分股成功,共 {} 只股票", stocks.size());
return stocks;
} catch (Exception e) {
logger.error("獲取Nifty 50成分股失敗", e);
throw new RuntimeException("獲取Nifty 50成分股失敗", e);
}
}
/**
* 獲取印度主要指數
*/
public List<Index> getMajorIndices() {
try {
List<Index> indices = httpClient.getIndiaIndices();
logger.info("獲取印度主要指數成功,共 {} 個指數", indices.size());
return indices;
} catch (Exception e) {
logger.error("獲取印度指數失敗", e);
throw new RuntimeException("獲取印度指數失敗", e);
}
}
/**
* 查詢特定股票
*/
public Stock getStockBySymbol(String symbol) {
try {
List<Stock> stocks = httpClient.queryStock(null, symbol, null);
if (stocks.isEmpty()) {
throw new RuntimeException("未找到股票: " + symbol);
}
logger.info("查詢股票 {} 成功", symbol);
return stocks.get(0);
} catch (Exception e) {
logger.error("查詢股票失敗: " + symbol, e);
throw new RuntimeException("查詢股票失敗: " + symbol, e);
}
}
/**
* 批量查詢股票
*/
public List<Stock> getStocksBySymbols(List<String> symbols) {
try {
// 這裏需要先將symbol轉換為pid,簡化處理直接查詢
List<Stock> result = symbols.stream()
.map(this::getStockBySymbol)
.collect(Collectors.toList());
logger.info("批量查詢 {} 只股票成功", symbols.size());
return result;
} catch (Exception e) {
logger.error("批量查詢股票失敗", e);
throw new RuntimeException("批量查詢股票失敗", e);
}
}
/**
* 獲取股票K線數據
*/
public List<KLine> getStockKLine(Long pid, String interval) {
try {
List<KLine> klines = httpClient.getKLineData(pid, interval);
logger.info("獲取股票 {} 的K線數據成功,共 {} 條", pid, klines.size());
return klines;
} catch (Exception e) {
logger.error("獲取K線數據失敗: pid=" + pid, e);
throw new RuntimeException("獲取K線數據失敗: pid=" + pid, e);
}
}
/**
* 獲取漲幅榜
*/
public List<Stock> getGainers() {
try {
List<Stock> gainers = httpClient.getUpDownList(1);
logger.info("獲取漲幅榜成功,共 {} 只股票", gainers.size());
return gainers;
} catch (Exception e) {
logger.error("獲取漲幅榜失敗", e);
throw new RuntimeException("獲取漲幅榜失敗", e);
}
}
/**
* 獲取跌幅榜
*/
public List<Stock> getLosers() {
try {
List<Stock> losers = httpClient.getUpDownList(2);
logger.info("獲取跌幅榜成功,共 {} 只股票", losers.size());
return losers;
} catch (Exception e) {
logger.error("獲取跌幅榜失敗", e);
throw new RuntimeException("獲取跌幅榜失敗", e);
}
}
/**
* 獲取IPO數據
*/
public List<Object> getUpcomingIPOs() {
try {
List<Object> ipos = httpClient.getIpoList(1); // 1表示未上市
logger.info("獲取IPO數據成功,共 {} 個", ipos.size());
return ipos;
} catch (Exception e) {
logger.error("獲取IPO數據失敗", e);
throw new RuntimeException("獲取IPO數據失敗", e);
}
}
/**
* 關閉服務
*/
public void close() {
try {
httpClient.close();
logger.info("IndiaStockService已關閉");
} catch (Exception e) {
logger.error("關閉服務時發生錯誤", e);
}
}
}
5. 使用示例
package com.stocktv.india.demo;
import com.stocktv.india.model.Index;
import com.stocktv.india.model.KLine;
import com.stocktv.india.model.Stock;
import com.stocktv.india.service.IndiaStockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* 印度股票數據使用示例
*/
public class IndiaStockDemo {
private static final Logger logger = LoggerFactory.getLogger(IndiaStockDemo.class);
public static void main(String[] args) {
// 替換為您的實際 API Key
String apiKey = "您的API_KEY";
IndiaStockService stockService = new IndiaStockService(apiKey);
try {
logger.info("=== StockTV 印度股票數據演示程序開始 ===");
// 1. 獲取印度主要指數
demonstrateIndices(stockService);
// 2. 查詢特定股票
demonstrateStockQuery(stockService);
// 3. 獲取Nifty 50成分股示例
demonstrateNifty50(stockService);
// 4. 獲取K線數據
demonstrateKLineData(stockService);
// 5. 獲取排行榜
demonstrateRankings(stockService);
logger.info("=== 演示程序執行完成 ===");
} catch (Exception e) {
logger.error("演示程序執行失敗", e);
} finally {
// 關閉服務
stockService.close();
}
}
/**
* 演示指數數據獲取
*/
private static void demonstrateIndices(IndiaStockService stockService) {
logger.info("\n1. 印度主要指數");
List<Index> indices = stockService.getMajorIndices();
indices.forEach(index -> {
String trend = index.getChange().doubleValue() >= 0 ? "📈" : "📉";
logger.info("{} {}: {}{} ({}{}%)",
trend, index.getName(), index.getLastPrice(),
index.getChange().doubleValue() >= 0 ? "↑" : "↓",
index.getChange().doubleValue() >= 0 ? "+" : "",
index.getChangePercent());
});
}
/**
* 演示股票查詢
*/
private static void demonstrateStockQuery(IndiaStockService stockService) {
logger.info("\n2. 查詢特定股票");
// 查詢Reliance Industries
Stock reliance = stockService.getStockBySymbol("RELIANCE");
printStockInfo(reliance, "Reliance Industries");
// 查詢TCS
Stock tcs = stockService.getStockBySymbol("TCS");
printStockInfo(tcs, "Tata Consultancy Services");
}
/**
* 演示Nifty 50成分股
*/
private static void demonstrateNifty50(IndiaStockService stockService) {
logger.info("\n3. Nifty 50成分股(示例)");
List<Stock> niftyStocks = stockService.getNifty50Stocks();
// 顯示前10只股票
niftyStocks.stream().limit(10).forEach(stock -> {
String trend = stock.getChangePercent().doubleValue() >= 0 ? "🟢" : "🔴";
logger.info("{} {}: ₹{} ({}{}%) - {}",
trend, stock.getSymbol(), stock.getLastPrice(),
stock.getChangePercent().doubleValue() >= 0 ? "+" : "",
stock.getChangePercent(), stock.getName());
});
}
/**
* 演示K線數據獲取
*/
private static void demonstrateKLineData(IndiaStockService stockService) {
logger.info("\n4. K線數據示例");
Stock reliance = stockService.getStockBySymbol("RELIANCE");
if (reliance != null) {
List<KLine> kLines = stockService.getStockKLine(reliance.getId(), "P1D");
logger.info("Reliance Industries 近期日K線數據:");
kLines.stream().limit(5).forEach(kLine -> {
logger.info("時間: {}, 開: ₹{}, 高: ₹{}, 低: ₹{}, 收: ₹{}, 成交量: {}",
kLine.getFormattedTime(), kLine.getOpen(),
kLine.getHigh(), kLine.getLow(), kLine.getClose(),
kLine.getVolume());
});
}
}
/**
* 演示排行榜功能
*/
private static void demonstrateRankings(IndiaStockService stockService) {
logger.info("\n5. 市場排行榜");
// 獲取漲幅榜
List<Stock> gainers = stockService.getGainers();
logger.info("📈 今日漲幅榜(前5):");
gainers.stream().limit(5).forEach(stock -> {
logger.info(" {}: ₹{} (+{}%) - {}",
stock.getSymbol(), stock.getLastPrice(),
stock.getChangePercent(), stock.getName());
});
// 獲取跌幅榜
List<Stock> losers = stockService.getLosers();
logger.info("📉 今日跌幅榜(前5):");
losers.stream().limit(5).forEach(stock -> {
logger.info(" {}: ₹{} ({}%) - {}",
stock.getSymbol(), stock.getLastPrice(),
stock.getChangePercent(), stock.getName());
});
}
/**
* 打印股票信息
*/
private static void printStockInfo(Stock stock, String description) {
if (stock != null) {
String status = stock.getIsOpen() ? "🟢 交易中" : "🔴 已收盤";
String trend = stock.getChangePercent().doubleValue() >= 0 ? "📈" : "📉";
logger.info("{} {} - {}", trend, description, status);
logger.info(" 代碼: {} | 價格: ₹{}", stock.getSymbol(), stock.getLastPrice());
logger.info(" 漲跌: ₹{} ({}{}%)", stock.getChange(),
stock.getChangePercent().doubleValue() >= 0 ? "+" : "",
stock.getChangePercent());
logger.info(" 最高: ₹{} | 最低: ₹{} | 成交量: {}",
stock.getHigh(), stock.getLow(), stock.getVolume());
if (stock.getTechnicalDay() != null) {
logger.info(" 技術指標: {}", getTechnicalIndicatorName(stock.getTechnicalDay()));
}
}
}
/**
* 獲取技術指標中文名稱
*/
private static String getTechnicalIndicatorName(String indicator) {
switch (indicator) {
case "strong_buy": return "強烈買入";
case "buy": return "買入";
case "neutral": return "中性";
case "sell": return "賣出";
case "strong_sell": return "強烈賣出";
default: return indicator;
}
}
}
🎯 高級功能
自定義股票監控器
package com.stocktv.india.advanced;
import com.stocktv.india.model.Stock;
import com.stocktv.india.service.IndiaStockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 股票價格監控器
*/
public class StockPriceMonitor {
private static final Logger logger = LoggerFactory.getLogger(StockPriceMonitor.class);
private final IndiaStockService stockService;
private final ScheduledExecutorService scheduler;
private final Map<String, Stock> lastPrices;
private final Set<String> monitoredSymbols;
public StockPriceMonitor(String apiKey) {
this.stockService = new IndiaStockService(apiKey);
this.scheduler = Executors.newScheduledThreadPool(1);
this.lastPrices = new HashMap<>();
this.monitoredSymbols = new HashSet<>();
}
/**
* 添加監控股票
*/
public void addStock(String symbol) {
monitoredSymbols.add(symbol);
logger.info("添加監控股票: {}", symbol);
}
/**
* 開始監控
*/
public void startMonitoring() {
logger.info("開始股票價格監控...");
scheduler.scheduleAtFixedRate(this::checkPrices, 0, 60, TimeUnit.SECONDS);
}
/**
* 停止監控
*/
public void stopMonitoring() {
scheduler.shutdown();
stockService.close();
logger.info("股票價格監控已停止");
}
/**
* 檢查價格變化
*/
private void checkPrices() {
try {
List<Stock> currentStocks = stockService.getStocksBySymbols(
new ArrayList<>(monitoredSymbols));
for (Stock currentStock : currentStocks) {
String symbol = currentStock.getSymbol();
Stock lastStock = lastPrices.get(symbol);
if (lastStock != null) {
// 檢查價格變化
double priceChange = currentStock.getLastPrice().subtract(lastStock.getLastPrice()).doubleValue();
double percentChange = currentStock.getChangePercent().doubleValue();
// 價格預警邏輯
if (Math.abs(percentChange) > 2.0) {
logger.warn("🚨 股票 {} 價格波動超過2%: {}%",
symbol, percentChange);
}
// 技術指標變化
if (!Objects.equals(currentStock.getTechnicalDay(), lastStock.getTechnicalDay())) {
logger.info("📊 股票 {} 技術指標變化: {} → {}",
symbol, lastStock.getTechnicalDay(), currentStock.getTechnicalDay());
}
}
// 更新最後價格
lastPrices.put(symbol, currentStock);
}
} catch (Exception e) {
logger.error("價格監控執行失敗", e);
}
}
/**
* 獲取監控報告
*/
public void printMonitoringReport() {
logger.info("=== 股票監控報告 ===");
lastPrices.values().forEach(stock -> {
String trend = stock.getChangePercent().doubleValue() >= 0 ? "🟢" : "🔴";
logger.info("{} {}: ₹{} ({}{}%)",
trend, stock.getSymbol(), stock.getLastPrice(),
stock.getChangePercent().doubleValue() >= 0 ? "+" : "",
stock.getChangePercent());
});
}
}
⚠️ 注意事項
1. 錯誤處理最佳實踐
/**
* 自定義異常類
*/
class StockTVException extends RuntimeException {
public StockTVException(String message) {
super(message);
}
public StockTVException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* 重試機制
*/
class RetryableStockService {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
public Stock getStockWithRetry(String symbol) {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
IndiaStockService service = new IndiaStockService("API_KEY");
return service.getStockBySymbol(symbol);
} catch (Exception e) {
retries++;
if (retries == MAX_RETRIES) {
throw new StockTVException("獲取股票數據失敗,已達到最大重試次數", e);
}
try {
Thread.sleep(RETRY_DELAY_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new StockTVException("重試過程被中斷", ie);
}
}
}
throw new StockTVException("未知錯誤");
}
}
2. 性能優化建議
/**
* 數據緩存示例
*/
class StockDataCache {
private final Map<String, Stock> stockCache = new ConcurrentHashMap<>();
private final Map<Long, List<KLine>> klineCache = new ConcurrentHashMap<>();
private final long cacheTimeoutMs = 5 * 60 * 1000; // 5分鐘緩存
public Stock getCachedStock(String symbol, IndiaStockService service) {
Stock cached = stockCache.get(symbol);
if (cached != null &&
System.currentTimeMillis() - cached.getTimestamp() * 1000 < cacheTimeoutMs) {
return cached;
}
Stock fresh = service.getStockBySymbol(symbol);
stockCache.put(symbol, fresh);
return fresh;
}
}
📞 技術支持
如果在使用過程中遇到問題,可以通過以下方式獲取幫助:
- 查看日誌: 啓用DEBUG級別日誌查看詳細請求信息
- 檢查網絡: 確保可以正常訪問
api.stocktv.top - 驗證API Key: 確認API Key有效且具有相應權限
- 聯繫支持: 通過官方渠道獲取技術支持