package main import ( "database/sql" "encoding/json" "fmt" "log" "math/rand" "net/http" "os" "time" ) var seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) // 数据库结构 type Stock struct { ID int `json:"id"` Symbol string `json:"symbol"` Name string `json:"name"` CurrentPrice float64 `json:"current_price"` Change float64 `json:"change"` ChangePercent float64 `json:"change_percent"` MarketCap string `json:"market_cap"` PERatio float64 `json:"pe_ratio"` ROE float64 `json:"roe"` DebtRatio float64 `json:"debt_ratio"` UpdateTime string `json:"update_time"` } type KLineData struct { Symbol string `json:"symbol"` Date string `json:"date"` Open float64 `json:"open"` High float64 `json:"high"` Low float64 `json:"low"` Close float64 `json:"close"` Volume int64 `json:"volume"` } type TechnicalIndicator struct { ID int `json:"id"` Symbol string `json:"symbol"` Date string `json:"date"` RSI float64 `json:"rsi"` MACD float64 `json:"macd"` MACDSignal float64 `json:"macd_signal"` MA5 float64 `json:"ma5"` MA10 float64 `json:"ma10"` MA20 float64 `json:"ma20"` UpdateTime string `json:"update_time"` } type Financials struct { ID int `json:"id"` Symbol string `json:"symbol"` Revenue float64 `json:"revenue"` Profit float64 `json:"profit"` ROE float64 `json:"roe"` DebtToEquity float64 `json:"debt_to_equity"` UpdateTime string `json:"update_time"` } // 全局变量 var db *sql.DB func main() { var err error db, err = sql.Open("sqlite3", "./stock.db") if err != nil { log.Fatal(err) } defer db.Close() // 初始化数据库 initDB() // 注册路由 http.HandleFunc("/", handleIndex) http.HandleFunc("/api/stocks", handleGetStocks) http.HandleFunc("/api/stock/", handleStockDetail) http.HandleFunc("/api/stock/", handleKLine) http.HandleFunc("/api/stock/", handleIndicators) http.HandleFunc("/api/stock/", handleFinancials) http.HandleFunc("/api/search", handleSearch) http.HandleFunc("/api/refresh/", handleRefresh) // 启动服务器 port := os.Getenv("PORT") if port == "" { port = "8080" } log.Printf("🚀 服务器启动: http://localhost:%s", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } func initDB() { query := ` CREATE TABLE IF NOT EXISTS stocks ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT UNIQUE, name TEXT, current_price REAL, change REAL, change_percent REAL, market_cap TEXT, pe_ratio REAL, roe REAL, debt_ratio REAL, update_time TEXT ); CREATE TABLE IF NOT EXISTS klines ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT, date TEXT, open REAL, high REAL, low REAL, close REAL, volume INTEGER, FOREIGN KEY(symbol) REFERENCES stocks(symbol) ); CREATE TABLE IF NOT EXISTS indicators ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT, date TEXT, rsi REAL, macd REAL, macd_signal REAL, ma5 REAL, ma10 REAL, ma20 REAL, update_time TEXT, FOREIGN KEY(symbol) REFERENCES stocks(symbol) ); CREATE TABLE IF NOT EXISTS financials ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT UNIQUE, revenue REAL, profit REAL, roe REAL, debt_to_equity REAL, update_time TEXT ); ` _, err := db.Exec(query) if err != nil { log.Fatal(err) } log.Println("✅ 数据库初始化成功") } // 主页面 func handleIndex(w http.ResponseWriter, r *http.Request) { // 读取HTML模板 content, err := os.ReadFile("templates/index.html") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(content) } // 获取股票列表 func handleGetStocks(w http.ResponseWriter, r *http.Request) { rows, err := db.Query("SELECT * FROM stocks ORDER BY update_time DESC") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var stocks []Stock for rows.Next() { var s Stock err := rows.Scan(&s.ID, &s.Symbol, &s.Name, &s.CurrentPrice, &s.Change, &s.ChangePercent, &s.MarketCap, &s.PERatio, &s.ROE, &s.DebtRatio, &s.UpdateTime) if err != nil { continue } stocks = append(stocks, s) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(stocks) } // 获取股票详情 func handleStockDetail(w http.ResponseWriter, r *http.Request) { symbol := r.URL.Path[len("/api/stock/"):] if symbol == "" { http.Error(w, "股票代码不能为空", http.StatusBadRequest) return } var stock Stock err := db.QueryRow(` SELECT * FROM stocks WHERE symbol = ? `, symbol).Scan(&stock.ID, &stock.Symbol, &stock.Name, &stock.CurrentPrice, &stock.Change, &stock.ChangePercent, &stock.MarketCap, &stock.PERatio, &stock.ROE, &stock.DebtRatio, &stock.UpdateTime) if err != nil { http.Error(w, "股票不存在", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(stock) } // 获取K线数据 func handleKLine(w http.ResponseWriter, r *http.Request) { symbol := r.URL.Path[len("/api/stock/"):] if symbol == "" { http.Error(w, "股票代码不能为空", http.StatusBadRequest) return } rows, err := db.Query(` SELECT date, open, high, low, close, volume FROM klines WHERE symbol = ? ORDER BY date DESC LIMIT 100 `, symbol) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var klines []KLineData for rows.Next() { var k KLineData err := rows.Scan(&k.Date, &k.Open, &k.High, &k.Low, &k.Close, &k.Volume) if err != nil { continue } klines = append(klines, k) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(klines) } // 获取技术指标 func handleIndicators(w http.ResponseWriter, r *http.Request) { symbol := r.URL.Path[len("/api/stock/"):] if symbol == "" { http.Error(w, "股票代码不能为空", http.StatusBadRequest) return } rows, err := db.Query(` SELECT date, rsi, macd, macd_signal, ma5, ma10, ma20 FROM indicators WHERE symbol = ? ORDER BY date DESC LIMIT 100 `, symbol) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var indicators []TechnicalIndicator for rows.Next() { var ind TechnicalIndicator err := rows.Scan(&ind.Date, &ind.RSI, &ind.MACD, &ind.MACDSignal, &ind.MA5, &ind.MA10, &ind.MA20) if err != nil { continue } indicators = append(indicators, ind) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(indicators) } // 获取财务数据 func handleFinancials(w http.ResponseWriter, r *http.Request) { symbol := r.URL.Path[len("/api/stock/"):] if symbol == "" { http.Error(w, "股票代码不能为空", http.StatusBadRequest) return } var f Financials err := db.QueryRow(` SELECT revenue, profit, roe, debt_to_equity FROM financials WHERE symbol = ? `, symbol).Scan(&f.Revenue, &f.Profit, &f.ROE, &f.DebtToEquity) if err != nil { http.Error(w, "财务数据不存在", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(f) } // 搜索股票 func handleSearch(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") if query == "" { http.Error(w, "请输入搜索关键词", http.StatusBadRequest) return } rows, err := db.Query(` SELECT symbol, name, current_price, change_percent FROM stocks WHERE symbol LIKE ? OR name LIKE ? ORDER BY current_price DESC `, "%"+query+"%", "%"+query+"%") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var results []map[string]interface{} for rows.Next() { var symbol, name string var currentPrice, changePercent float64 err := rows.Scan(&symbol, &name, ¤tPrice, &changePercent) if err != nil { continue } results = append(results, map[string]interface{}{ "symbol": symbol, "name": name, "current_price": currentPrice, "change_percent": changePercent, }) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(results) } // 刷新股票数据 func handleRefresh(w http.ResponseWriter, r *http.Request) { symbol := r.URL.Path[len("/api/refresh/"):] if symbol == "" { http.Error(w, "股票代码不能为空", http.StatusBadRequest) return } // 获取yfinance数据 stock := fetchYahooFinance(symbol) // 存储到数据库 storeStockData(stock) storeKLineData(stock) storeIndicatorData(stock) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "message": "数据刷新成功", "symbol": symbol, "price": stock.CurrentPrice, "update_time": time.Now().Format("2006-01-02 15:04:05"), }) } // yfinance API调用(简化版) func fetchYahooFinance(symbol string) Stock { // 模拟数据 return Stock{ Symbol: symbol, Name: getCompanyName(symbol), CurrentPrice: getRandomPrice(), Change: getRandomChange(), ChangePercent: getRandomChangePercent(), MarketCap: getRandomMarketCap(), PERatio: getRandomPERatio(), ROE: getRandomROE(), DebtRatio: getRandomDebtRatio(), UpdateTime: time.Now().Format("2006-01-02 15:04:05"), } } func getCompanyName(symbol string) string { companies := map[string]string{ "AAPL": "Apple Inc.", "MSFT": "Microsoft Corporation", "GOOGL": "Alphabet Inc.", "AMZN": "Amazon.com Inc.", "NVDA": "NVIDIA Corporation", "TSLA": "Tesla Inc.", "META": "Meta Platforms Inc.", "JPM": "JPMorgan Chase & Co.", "V": "Visa Inc.", "JNJ": "Johnson & Johnson", } if name, ok := companies[symbol]; ok { return name } return "Unknown Company" } func getRandomPrice() float64 { return 100 + float64(time.Now().Unix() % 500) } func getRandomChange() float64 { return (randFloat64(0,1) * 10) - 5 } func getRandomChangePercent() float64 { return (randFloat64(0,1) * 10) - 5 } func getRandomMarketCap() string { segments := []string{"B", "T"} return fmt.Sprintf("$%d%s", int(randFloat64(0,1)*1000), segments[rand.Intn(2)]) } func getRandomPERatio() float64 { return 10 + randFloat64(0,1)*40 } func getRandomROE() float64 { return 5 + randFloat64(0,1)*30 } func getRandomDebtRatio() float64 { return 20 + randFloat64(0,1)*80 } func storeStockData(stock Stock) error { _, err := db.Exec(` INSERT INTO stocks (symbol, name, current_price, change, change_percent, market_cap, pe_ratio, roe, debt_ratio, update_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(symbol) DO UPDATE SET current_price = excluded.current_price, change = excluded.change, change_percent = excluded.change_percent, market_cap = excluded.market_cap, pe_ratio = excluded.pe_ratio, roe = excluded.roe, debt_ratio = excluded.debt_ratio, update_time = excluded.update_time `, stock.Symbol, stock.Name, stock.CurrentPrice, stock.Change, stock.ChangePercent, stock.MarketCap, stock.PERatio, stock.ROE, stock.DebtRatio, stock.UpdateTime) return err } func storeKLineData(stock Stock) error { for i := 0; i < 100; i++ { date := time.Now().AddDate(0, 0, -i).Format("2006-01-02") open := stock.CurrentPrice + (randFloat64(0,1)-0.5)*10 high := open + randFloat64(0,1)*5 low := open - randFloat64(0,1)*5 close := open + (randFloat64(0,1)-0.5)*5 volume := int64(randFloat64(0,1) * 1000000) _, err := db.Exec(` INSERT INTO klines (symbol, date, open, high, low, close, volume) VALUES (?, ?, ?, ?, ?, ?, ?) `, stock.Symbol, date, open, high, low, close, volume) if err != nil { return err } } return nil } func storeIndicatorData(stock Stock) error { for i := 0; i < 100; i++ { date := time.Now().AddDate(0, 0, -i).Format("2006-01-02") rsi := 30 + randFloat64(0,1)*40 macd := (randFloat64(0,1) * 10) - 5 macdSignal := macd + (randFloat64(0,1)-0.5)*2 ma5 := stock.CurrentPrice + (randFloat64(0,1)-0.5)*5 ma10 := stock.CurrentPrice + (randFloat64(0,1)-0.5)*8 ma20 := stock.CurrentPrice + (randFloat64(0,1)-0.5)*12 _, err := db.Exec(` INSERT INTO indicators (symbol, date, rsi, macd, macd_signal, ma5, ma10, ma20, update_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, stock.Symbol, date, rsi, macd, macdSignal, ma5, ma10, ma20, time.Now().Format("2006-01-02 15:04:05")) if err != nil { return err } } return nil } func randFloat64(min, max float64) float64 { return min + seededRand.Float64()*(max-min) }