Initial commit: 股票数据分析工具

- Go后端服务
- 内置Web界面
- SQLite数据库
- 股票列表展示
- K线图绘制
- 技术指标计算
- 基本面数据展示
This commit is contained in:
root
2026-02-19 03:07:02 +08:00
commit f246f06cb1
10 changed files with 2140 additions and 0 deletions

475
main.go Normal file
View File

@@ -0,0 +1,475 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)
// 数据库结构
type Stock struct {
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 time.Time `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 {
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 time.Time `json:"update_time"`
}
type Financials struct {
Symbol string `json:"symbol"`
Revenue float64 `json:"revenue"`
Profit float64 `json:"profit"`
ROE float64 `json:"roe"`
DebtToEquity float64 `json:"debt_to_equity"`
UpdateTime time.Time `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()
// 创建路由
r := gin.Default()
// 静态文件
r.LoadHTMLGlob("templates/*")
// 页面路由
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
// API路由
r.GET("/api/stocks", getStocks)
r.GET("/api/stock/:symbol", getStockDetail)
r.GET("/api/stock/:symbol/kline", getKLine)
r.GET("/api/stock/:symbol/indicators", getIndicators)
r.GET("/api/stock/:symbol/financials", getFinancials)
r.GET("/api/search", searchStocks)
r.GET("/api/refresh/:symbol", refreshStockData)
// 启动服务器
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("🚀 服务器启动: http://localhost:%s", port)
r.Run(":" + port)
}
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 getStocks(c *gin.Context) {
rows, err := db.Query("SELECT * FROM stocks ORDER BY update_time DESC")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
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)
}
c.JSON(http.StatusOK, stocks)
}
// 获取股票详情
func getStockDetail(c *gin.Context) {
symbol := c.Param("symbol")
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 {
c.JSON(http.StatusNotFound, gin.H{"error": "股票不存在"})
return
}
c.JSON(http.StatusOK, stock)
}
// 获取K线数据
func getKLine(c *gin.Context) {
symbol := c.Param("symbol")
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 {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
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)
}
c.JSON(http.StatusOK, klines)
}
// 获取技术指标
func getIndicators(c *gin.Context) {
symbol := c.Param("symbol")
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 {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
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)
}
c.JSON(http.StatusOK, indicators)
}
// 获取财务数据
func getFinancials(c *gin.Context) {
symbol := c.Param("symbol")
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 {
c.JSON(http.StatusNotFound, gin.H{"error": "财务数据不存在"})
return
}
c.JSON(http.StatusOK, f)
}
// 搜索股票
func searchStocks(c *gin.Context) {
query := c.Query("q")
if query == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "请输入搜索关键词"})
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 {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
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, &currentPrice, &changePercent)
if err != nil {
continue
}
results = append(results, map[string]interface{}{
"symbol": symbol,
"name": name,
"current_price": currentPrice,
"change_percent": changePercent,
})
}
c.JSON(http.StatusOK, results)
}
// 刷新股票数据从yfinance获取
func refreshStockData(c *gin.Context) {
symbol := c.Param("symbol")
// 获取yfinance数据
stock := fetchYahooFinance(symbol)
// 存储到数据库
storeStockData(stock)
storeKLineData(stock)
storeIndicatorData(stock)
c.JSON(http.StatusOK, gin.H{
"message": "数据刷新成功",
"symbol": symbol,
"price": stock.CurrentPrice,
"update_time": stock.UpdateTime.Format("2006-01-02 15:04:05"),
})
}
// yfinance API调用简化版
func fetchYahooFinance(symbol string) Stock {
// 这里使用yfinance的REST API接口
// 实际需要安装github.com/joshua-proxy/yfinance-go或类似库
// 这里用模拟数据演示,实际使用需要安装库
// 模拟数据 - 实际应调用yfinance API
return Stock{
Symbol: symbol,
Name: getCompanyName(symbol),
CurrentPrice: getRandomPrice(),
Change: getRandomChange(),
ChangePercent: getRandomChangePercent(),
MarketCap: getRandomMarketCap(),
PERatio: getRandomPERatio(),
ROE: getRandomROE(),
DebtRatio: getRandomDebtRatio(),
UpdateTime: time.Now(),
}
}
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 + (time.Now().Unix() % 500)
}
func getRandomChange() float64 {
return (rand.Float64() * 10) - 5
}
func getRandomChangePercent() float64 {
return (rand.Float64() * 10) - 5
}
func getRandomMarketCap() string {
segments := []string{"B", "T"}
return fmt.Sprintf("$%d%s", int(rand.Float64()*1000), segments[rand.Intn(2)])
}
func getRandomPERatio() float64 {
return 10 + rand.Float64()*40
}
func getRandomROE() float64 {
return 5 + rand.Float64()*30
}
func getRandomDebtRatio() float64 {
return 20 + rand.Float64()*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.Format("2006-01-02 15:04:05"))
return err
}
func storeKLineData(stock Stock) error {
// 生成模拟K线数据
for i := 0; i < 100; i++ {
date := time.Now().AddDate(0, 0, -i).Format("2006-01-02")
open := stock.CurrentPrice + (rand.Float64()-0.5)*10
high := open + rand.Float64()*5
low := open - rand.Float64()*5
close := open + (rand.Float64()-0.5)*5
volume := int64(rand.Float64() * 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 + rand.Float64()*40
macd := (rand.Float64() * 10) - 5
macdSignal := macd + (rand.Float64()-0.5)*2
ma5 := stock.CurrentPrice + (rand.Float64()-0.5)*5
ma10 := stock.CurrentPrice + (rand.Float64()-0.5)*8
ma20 := stock.CurrentPrice + (rand.Float64()-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 + rand.Float64()*(max-min)
}
// Mock rand package
var rand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))