博客 / 詳情

返回

DeepSeek印度股票數據源 Java 對接文檔

📋 文檔概述

本文檔詳細介紹如何使用 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;
    }
}

📞 技術支持

如果在使用過程中遇到問題,可以通過以下方式獲取幫助:

  1. 查看日誌: 啓用DEBUG級別日誌查看詳細請求信息
  2. 檢查網絡: 確保可以正常訪問 api.stocktv.top
  3. 驗證API Key: 確認API Key有效且具有相應權限
  4. 聯繫支持: 通過官方渠道獲取技術支持
user avatar guizimo 頭像 jidongdehai_co4lxh 頭像 dragonir 頭像 codepencil 頭像 iymxpc3k 頭像 ni_5e1946a1c2171 頭像 qianxiaqingkong 頭像 jjyin 頭像 anran758 頭像 pulangte 頭像 xiaoemo_5df065196d684 頭像 xrkffgg 頭像
25 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.