├── frontend ├── src │ ├── styles │ │ └── dark │ │ │ └── css-vars.css │ ├── App.vue │ ├── main.js │ ├── components │ │ ├── NavBar.vue │ │ ├── StockChart.vue │ │ ├── StockKlineModal.vue │ │ └── TimelineChart.vue │ ├── views │ │ ├── DSLTester.vue │ │ ├── ResultView.vue │ │ ├── WatchlistView.vue │ │ ├── BoardView.vue │ │ ├── StockPoolView.vue │ │ ├── StrategyEditor.vue │ │ ├── StrategyListView.vue │ │ └── StockDetailView.vue │ └── router │ │ └── index.js ├── index.html ├── package.json ├── vite.config.js └── yarn.lock ├── yarn.lock ├── .gitignore ├── predict ├── onnx_output_style.py ├── onnx_laber_out.py ├── main.go ├── predict_onnx.go ├── train_lightgbm.py └── data │ └── stock_history.csv ├── backend ├── strategy │ ├── macd_strategy.go │ ├── composite_strategy.go │ ├── ma_strategy.go │ ├── dsl_strategy.go │ └── strategy.go ├── config │ ├── config.yaml │ └── config.go ├── fetcher │ ├── indicator.go │ ├── fetcher.go │ └── stock_list.go ├── realtime │ ├── client.go │ ├── hub.go │ └── fetcher.go ├── scheduler │ └── scheduler.go ├── web │ ├── server.go │ ├── handlers_stocks.go │ └── handlers_strategy.go ├── storage │ ├── strategy_store.go │ └── db.go ├── strategyexec │ └── exec.go └── main.go ├── stockapi └── main.go ├── go.mod ├── .github └── copilot-instructions.md ├── go.sum └── README.md /frontend/src/styles/dark/css-vars.css: -------------------------------------------------------------------------------- 1 | html.dark { 2 | /* 自定义深色背景颜色 */ 3 | --el-bg-color: #626aef; 4 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | /results 4 | /frontend/node_modules 5 | /bin 6 | /backend/stock.db 7 | /backend/stock.db-shm 8 | /backend/stock.db-wal 9 | -------------------------------------------------------------------------------- /predict/onnx_output_style.py: -------------------------------------------------------------------------------- 1 | import onnx 2 | model = onnx.load("model/lgb_stock_model.onnx") 3 | 4 | for o in model.graph.output: 5 | print(o.name, o.type.tensor_type.elem_type) -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 9 | -------------------------------------------------------------------------------- /predict/onnx_laber_out.py: -------------------------------------------------------------------------------- 1 | import onnx 2 | 3 | model = onnx.load("model/lgb_stock_model.onnx") 4 | print("Inputs:") 5 | for i in model.graph.input: 6 | print(" ", i.name) 7 | print("Outputs:") 8 | for o in model.graph.output: 9 | print(" ", o.name) 10 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stock Analyzer 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import 'element-plus/dist/index.css' 5 | import 'element-plus/theme-chalk/dark/css-vars.css' 6 | import './styles/dark/css-vars.css' 7 | 8 | const app = createApp(App) 9 | app.use(router) 10 | app.mount('#app') 11 | -------------------------------------------------------------------------------- /frontend/src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /backend/strategy/macd_strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import "go-stock-analyzer/backend/storage" 4 | 5 | type MACDStrategy struct{} 6 | 7 | func NewMACDStrategy() *MACDStrategy { return &MACDStrategy{} } 8 | 9 | func (s *MACDStrategy) Name() string { return "MACD" } 10 | 11 | func (s *MACDStrategy) Match(code string, klines []storage.KLine) bool { 12 | if len(klines) < 2 { 13 | return false 14 | } 15 | prev := klines[len(klines)-2] 16 | last := klines[len(klines)-1] 17 | return prev.DIF < prev.DEA && last.DIF > last.DEA 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/StockChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stock-frontend", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "axios": "^1.5.0", 11 | "echarts": "^5.4.0", 12 | "element-plus": "^2.11.4", 13 | "vue": "^3.3.4", 14 | "vue-router": "^4.2.2" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^4.2.3", 18 | "unplugin-auto-import": "^20.2.0", 19 | "unplugin-vue-components": "^29.1.0", 20 | "vite": "^5.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/views/DSLTester.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | -------------------------------------------------------------------------------- /backend/strategy/composite_strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import "go-stock-analyzer/backend/storage" 4 | 5 | type CompositeStrategy struct { 6 | HoldDays int 7 | } 8 | 9 | func NewCompositeStrategy(holdDays int) *CompositeStrategy { 10 | return &CompositeStrategy{HoldDays: holdDays} 11 | } 12 | 13 | func (s *CompositeStrategy) Name() string { return "Composite" } 14 | 15 | func (s *CompositeStrategy) Match(code string, klines []storage.KLine) bool { 16 | if len(klines) < s.HoldDays+2 { 17 | return false 18 | } 19 | ma := NewMAStrategy(20, s.HoldDays) 20 | macd := NewMACDStrategy() 21 | return ma.Match(code, klines) && macd.Match(code, klines) 22 | } 23 | -------------------------------------------------------------------------------- /backend/config/config.yaml: -------------------------------------------------------------------------------- 1 | db_path: "backend/stock.db" 2 | kline_days: 120 3 | update_hour: 12 4 | update_minute: 56 5 | combination: "all" 6 | # worker pool defaults for startup watchlist KLine fetch 7 | worker_concurrency: 5 8 | worker_retries: 3 9 | worker_delay_ms: 200 10 | worker_backoff_ms: 500 11 | watchlist_kline_days: 300 12 | strategies: 13 | - name: "MA" 14 | enabled: true 15 | params: 16 | ma: 20 17 | hold_days: 3 18 | - name: "MACD" 19 | enabled: true 20 | - name: "Composite" 21 | enabled: true 22 | params: 23 | hold_days: 3 24 | - name: "DSL" 25 | enabled: true 26 | params: 27 | expr: "close > ma20 AND macd_dif > macd_dea" 28 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import Components from 'unplugin-vue-components/vite' 4 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 5 | import AutoImport from 'unplugin-auto-import/vite' 6 | 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | AutoImport({ 11 | resolvers: [ElementPlusResolver()], 12 | }), 13 | Components({ 14 | resolvers: [ElementPlusResolver()], 15 | }), 16 | ], 17 | server: { proxy: { '/api': 'http://localhost:8080' } }, 18 | CSS: { 19 | preprocessorOptions: { 20 | scss: { api: 'modern-compiler' } 21 | } 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /frontend/src/views/ResultView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 25 | -------------------------------------------------------------------------------- /backend/strategy/ma_strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import "go-stock-analyzer/backend/storage" 4 | 5 | type MAStrategy struct { 6 | MA int 7 | HoldDays int 8 | } 9 | 10 | func NewMAStrategy(ma, holdDays int) *MAStrategy { 11 | return &MAStrategy{MA: ma, HoldDays: holdDays} 12 | } 13 | 14 | func (s *MAStrategy) Name() string { return "MA" } 15 | 16 | func (s *MAStrategy) Match(code string, klines []storage.KLine) bool { 17 | if len(klines) < s.HoldDays { 18 | return false 19 | } 20 | for i := len(klines) - s.HoldDays; i < len(klines); i++ { 21 | k := klines[i] 22 | var maValue float64 23 | switch s.MA { 24 | case 5: 25 | maValue = k.MA5 26 | case 10: 27 | maValue = k.MA10 28 | case 20: 29 | maValue = k.MA20 30 | case 30: 31 | maValue = k.MA30 32 | default: 33 | maValue = k.MA20 34 | } 35 | if k.Close < maValue { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import ResultView from '../views/ResultView.vue' 3 | import DSLTester from '../views/DSLTester.vue' 4 | import StockPoolView from '../views/StockPoolView.vue' 5 | import StockDetailView from '../views/StockDetailView.vue' 6 | import BoardView from '../views/BoardView.vue' 7 | import WatchlistView from '../views/WatchlistView.vue' 8 | import StrategyEditor from '../views/StrategyEditor.vue' 9 | import StrategyListView from '../views/StrategyListView.vue' 10 | 11 | const routes = [ 12 | { path: '/', component: BoardView }, 13 | { path: '/dsl', component: DSLTester }, 14 | { path: '/stocks', component: StockPoolView }, 15 | { path: '/stocks/:symbol', component: StockDetailView }, 16 | { path: '/watchlist', component: WatchlistView }, 17 | { path: '/strategy', component: StrategyEditor }, 18 | { path: '/result', component: ResultView }, 19 | { path: '/strategies', component: StrategyListView }, 20 | ] 21 | 22 | export default createRouter({ history: createWebHistory(), routes }) 23 | -------------------------------------------------------------------------------- /backend/fetcher/indicator.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | func CalcMA(values []float64, n int) float64 { 4 | if len(values) < n { 5 | return 0 6 | } 7 | sum := 0.0 8 | for i := len(values) - n; i < len(values); i++ { 9 | sum += values[i] 10 | } 11 | 12 | return sum / float64(n) 13 | } 14 | 15 | func CalcEMASequence(values []float64, period int) []float64 { 16 | n := len(values) 17 | out := make([]float64, n) 18 | if n == 0 { 19 | return out 20 | } 21 | mult := 2.0 / float64(period+1) 22 | out[0] = values[0] 23 | for i := 1; i < n; i++ { 24 | out[i] = (values[i]-out[i-1])*mult + out[i-1] 25 | } 26 | return out 27 | } 28 | 29 | func CalcMACD(values []float64) (dif, dea, macd float64) { 30 | if len(values) == 0 { 31 | return 0, 0, 0 32 | } 33 | ema12 := CalcEMASequence(values, 12) 34 | ema26 := CalcEMASequence(values, 26) 35 | difSeries := make([]float64, len(values)) 36 | for i := range values { 37 | difSeries[i] = ema12[i] - ema26[i] 38 | } 39 | deaSeries := CalcEMASequence(difSeries, 9) 40 | last := len(values) - 1 41 | dif = difSeries[last] 42 | dea = deaSeries[last] 43 | macd = 2 * (dif - dea) 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /backend/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | 7 | "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type StrategyConfig struct { 11 | Name string `yaml:"name"` 12 | Enabled bool `yaml:"enabled"` 13 | Params map[string]interface{} `yaml:"params"` 14 | } 15 | 16 | type Config struct { 17 | DBPath string `yaml:"db_path"` 18 | KLineDays int `yaml:"kline_days"` 19 | UpdateHour int `yaml:"update_hour"` 20 | UpdateMinute int `yaml:"update_minute"` 21 | Combination string `yaml:"combination"` 22 | Strategies []StrategyConfig `yaml:"strategies"` 23 | // Worker pool settings for startup watchlist KLine fetch 24 | WorkerConcurrency int `yaml:"worker_concurrency"` 25 | WorkerRetries int `yaml:"worker_retries"` 26 | WorkerDelayMs int `yaml:"worker_delay_ms"` 27 | WorkerBackoffMs int `yaml:"worker_backoff_ms"` 28 | WatchlistKlineDays int `yaml:"watchlist_kline_days"` 29 | } 30 | 31 | var Cfg Config 32 | 33 | func LoadConfig(path string) { 34 | data, err := ioutil.ReadFile(path) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | if err := yaml.Unmarshal(data, &Cfg); err != nil { 39 | log.Fatal(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /backend/realtime/client.go: -------------------------------------------------------------------------------- 1 | package realtime 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | type Client struct { 11 | conn *websocket.Conn 12 | send chan []byte 13 | subs map[string]bool 14 | } 15 | 16 | // 处理客户端消息 17 | func (c *Client) readPump() { 18 | defer func() { 19 | h.unregister <- c 20 | c.conn.Close() 21 | }() 22 | for { 23 | _, msg, err := c.conn.ReadMessage() 24 | if err != nil { 25 | log.Println("read error:", err) 26 | break 27 | } 28 | var req struct { 29 | Action string `json:"action"` 30 | Symbols []string `json:"symbols"` 31 | } 32 | if err := json.Unmarshal(msg, &req); err != nil { 33 | continue 34 | } 35 | if req.Action == "subscribe" { 36 | for _, s := range req.Symbols { 37 | c.subs[s] = true 38 | } 39 | } else if req.Action == "unsubscribe" { 40 | for _, s := range req.Symbols { 41 | delete(c.subs, s) 42 | } 43 | } 44 | } 45 | } 46 | 47 | // 向客户端发送消息 48 | func (c *Client) writePump() { 49 | for { 50 | msg, ok := <-c.send 51 | if !ok { 52 | _ = c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 53 | return 54 | } 55 | if err := c.conn.WriteMessage(websocket.TextMessage, msg); err != nil { 56 | return 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/strategy/dsl_strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go-stock-analyzer/backend/storage" 7 | 8 | "github.com/Knetic/govaluate" 9 | ) 10 | 11 | type DSLStrategy struct { 12 | Expr string 13 | } 14 | 15 | func NewDSLStrategy(expr string) *DSLStrategy { 16 | return &DSLStrategy{Expr: expr} 17 | } 18 | 19 | func (s *DSLStrategy) Name() string { return "DSL" } 20 | 21 | func (s *DSLStrategy) Match(code string, klines []storage.KLine) bool { 22 | if len(klines) == 0 { 23 | return false 24 | } 25 | last := klines[len(klines)-1] 26 | 27 | parameters := map[string]interface{}{ 28 | "close": last.Close, 29 | "open": last.Open, 30 | "high": last.High, 31 | "low": last.Low, 32 | "volume": last.Volume, 33 | "ma5": last.MA5, 34 | "ma10": last.MA10, 35 | "ma20": last.MA20, 36 | "ma30": last.MA30, 37 | "macd_dif": last.DIF, 38 | "macd_dea": last.DEA, 39 | "macd_hist": last.MACD, 40 | } 41 | 42 | expr, err := govaluate.NewEvaluableExpression(s.Expr) 43 | if err != nil { 44 | fmt.Println("dsl parse error:", err) 45 | return false 46 | } 47 | res, err := expr.Evaluate(parameters) 48 | if err != nil { 49 | fmt.Println("dsl eval error:", err) 50 | return false 51 | } 52 | pass, ok := res.(bool) 53 | if !ok { 54 | return false 55 | } 56 | return pass 57 | } 58 | -------------------------------------------------------------------------------- /stockapi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "io/ioutil" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // GetStocksHandler 获取股票数据 11 | func GetStocksHandler(c *gin.Context) { 12 | // 获取请求中的股票代码 13 | symbol := c.Query("symbol") 14 | if symbol == "" { 15 | c.JSON(http.StatusBadRequest, gin.H{"error": "symbol is required"}) 16 | return 17 | } 18 | 19 | // 拼接请求URL 20 | url := fmt.Sprintf("http://127.0.0.1:18080/api/public/stock_zh_a_hist?symbol=%s", symbol) 21 | 22 | // 发起HTTP请求 23 | resp, err := http.Get(url) 24 | if err != nil { 25 | c.JSON(http.StatusInternalServerError, gin.H{"error": "无法连接到股票数据源:" + err.Error()}) 26 | return 27 | } 28 | defer resp.Body.Close() 29 | 30 | // 如果AKTools返回的状态码不是200,则返回错误 31 | if resp.StatusCode != http.StatusOK { 32 | c.JSON(resp.StatusCode, gin.H{"error": fmt.Sprintf("无法获取数据,状态码:%d", resp.StatusCode)}) 33 | return 34 | } 35 | 36 | // 读取返回的Body内容 37 | body, err := ioutil.ReadAll(resp.Body) 38 | if err != nil { 39 | c.JSON(http.StatusInternalServerError, gin.H{"error": "无法读取股票数据:" + err.Error()}) 40 | return 41 | } 42 | 43 | // 将获取到的数据返回 44 | c.Data(http.StatusOK, "application/json", body) 45 | } 46 | 47 | func main() { 48 | // 创建Gin引擎 49 | r := gin.Default() 50 | 51 | // 注册路由 52 | r.GET("/api/stocks", GetStocksHandler) 53 | 54 | // 启动服务器 55 | r.Run(":8088") 56 | } 57 | -------------------------------------------------------------------------------- /predict/main.go: -------------------------------------------------------------------------------- 1 | // predict.go 2 | package main 3 | 4 | import ( 5 | "encoding/csv" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/dmitryikh/leaves" 12 | ) 13 | 14 | // 简单的特征处理函数,与训练保持一致 15 | func extractFeatures(record []string) ([]float64, error) { 16 | // 假设 CSV 格式与训练时一致: Date,Open,High,Low,Close,Volume,ma5,ma10,rsi,return 17 | // 我们只取与训练相同的特征列 18 | feats := make([]float64, 9) 19 | for i := 1; i <= 9; i++ { 20 | v, err := strconv.ParseFloat(record[i], 64) 21 | if err != nil { 22 | return nil, err 23 | } 24 | feats[i-1] = v 25 | } 26 | return feats, nil 27 | } 28 | 29 | func main() { 30 | // 1. 加载 LightGBM 模型 31 | model, err := leaves.LGBMModelFromFile("model/lgb_stock_model.txt") 32 | if err != nil { 33 | log.Fatalf("加载模型失败: %v", err) 34 | } 35 | defer model.Free() 36 | 37 | // 2. 读取最新样本数据(例如 data/latest.csv) 38 | file, err := os.Open("data/latest.csv") 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | defer file.Close() 43 | 44 | reader := csv.NewReader(file) 45 | _, _ = reader.Read() // 跳过header 46 | 47 | record, _ := reader.Read() 48 | feats, err := extractFeatures(record) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // 3. 预测 54 | pred := model.PredictSingle(feats, 0) 55 | fmt.Printf("上涨概率: %.4f\n", pred) 56 | if pred > 0.5 { 57 | fmt.Println("👉 预测:明日上涨") 58 | } else { 59 | fmt.Println("👎 预测:明日下跌") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /backend/realtime/hub.go: -------------------------------------------------------------------------------- 1 | package realtime 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | ) 7 | 8 | type Hub struct { 9 | clients map[*Client]bool 10 | broadcast chan []byte 11 | register chan *Client 12 | unregister chan *Client 13 | mu sync.RWMutex 14 | } 15 | 16 | var h = &Hub{ 17 | clients: make(map[*Client]bool), 18 | broadcast: make(chan []byte, 256), 19 | register: make(chan *Client), 20 | unregister: make(chan *Client), 21 | } 22 | 23 | func RunHub() { 24 | for { 25 | select { 26 | case client := <-h.register: 27 | h.mu.Lock() 28 | h.clients[client] = true 29 | h.mu.Unlock() 30 | case client := <-h.unregister: 31 | h.mu.Lock() 32 | if _, ok := h.clients[client]; ok { 33 | delete(h.clients, client) 34 | close(client.send) 35 | } 36 | h.mu.Unlock() 37 | case msg := <-h.broadcast: 38 | var q Quote 39 | if err := json.Unmarshal(msg, &q); err != nil { 40 | // broadcast raw if cannot parse 41 | h.mu.RLock() 42 | for c := range h.clients { 43 | select { 44 | case c.send <- msg: 45 | default: 46 | close(c.send) 47 | delete(h.clients, c) 48 | } 49 | } 50 | h.mu.RUnlock() 51 | continue 52 | } 53 | h.mu.RLock() 54 | for c := range h.clients { 55 | if c.subs[q.Code] { 56 | select { 57 | case c.send <- msg: 58 | default: 59 | close(c.send) 60 | delete(h.clients, c) 61 | } 62 | } 63 | } 64 | h.mu.RUnlock() 65 | } 66 | } 67 | } 68 | 69 | func Broadcast(msg []byte) { 70 | h.broadcast <- msg 71 | } 72 | -------------------------------------------------------------------------------- /frontend/src/components/StockKlineModal.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 41 | -------------------------------------------------------------------------------- /frontend/src/views/WatchlistView.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 52 | -------------------------------------------------------------------------------- /frontend/src/views/BoardView.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 53 | -------------------------------------------------------------------------------- /backend/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "go-stock-analyzer/backend/config" 8 | "go-stock-analyzer/backend/fetcher" 9 | "go-stock-analyzer/backend/storage" 10 | "go-stock-analyzer/backend/strategy" 11 | ) 12 | 13 | // StartDailyTask 启动每日定时任务协程 14 | func StartDailyTask() { 15 | go func() { 16 | for { 17 | now := time.Now() 18 | next := time.Date(now.Year(), now.Month(), now.Day(), config.Cfg.UpdateHour, config.Cfg.UpdateMinute, 0, 0, now.Location()) 19 | if now.After(next) { 20 | next = next.Add(24 * time.Hour) 21 | } 22 | sleep := time.Until(next) 23 | log.Printf("Scheduler sleeping until %v\n", next) 24 | time.Sleep(sleep) 25 | runAnalysis() 26 | } 27 | }() 28 | } 29 | // 执行每日分析任务 30 | func runAnalysis() { 31 | log.Println("Daily analysis start: reading watchlist") 32 | watch, err := storage.GetWatchlist() 33 | if err != nil { 34 | log.Println("get watchlist error:", err) 35 | return 36 | } 37 | if len(watch) == 0 { 38 | log.Println("watchlist empty, nothing to analyze") 39 | return 40 | } 41 | 42 | var symbols []string 43 | for _, w := range watch { 44 | symbols = append(symbols, w.Symbol) 45 | } 46 | log.Printf("Analyzing %d watched stocks\n", len(symbols)) 47 | 48 | for _, sym := range symbols { 49 | klines, err := fetcher.FetchKLine(sym, config.Cfg.KLineDays) 50 | if err != nil { 51 | log.Printf("fetch kline %s error: %v", sym, err) 52 | continue 53 | } 54 | 55 | if err := storage.SaveKLines(sym, klines); err != nil { 56 | log.Printf("save kline %s error: %v", sym, err) 57 | } 58 | } 59 | // 运行所有策略 60 | strategy.RunAll(symbols) 61 | log.Println("Daily analysis finished") 62 | } 63 | -------------------------------------------------------------------------------- /predict/predict_onnx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | ort "github.com/yalue/onnxruntime_go" 8 | ) 9 | 10 | func main() { 11 | // 1️⃣ 设置 ONNX Runtime 共享库路径 12 | ort.SetSharedLibraryPath("/home/sun/onnxruntimearm/lib/libonnxruntime.so.1.22.0") 13 | 14 | // 2️⃣ 初始化环境 15 | if err := ort.InitializeEnvironment(); err != nil { 16 | log.Fatal("Failed to initialize ONNX Runtime environment:", err) 17 | } 18 | defer ort.DestroyEnvironment() 19 | 20 | // 3️⃣ 构造输入数据 21 | inputData := []float32{12.36, 12.58, 12.20, 12.40, 5600000} 22 | inputShape := ort.NewShape(1, 9) // batch=1, 特征数=9 23 | inputTensor, err := ort.NewTensor(inputShape, inputData) 24 | if err != nil { 25 | log.Fatal("Failed to create input tensor:", err) 26 | } 27 | defer inputTensor.Destroy() 28 | 29 | // 4️⃣ 创建输出 Tensor 30 | outputShape := ort.NewShape(1) // 二分类输出 [batch_size] 31 | outputTensor, err := ort.NewEmptyTensor[int64](outputShape) 32 | if err != nil { 33 | log.Fatal("Failed to create output tensor:", err) 34 | } 35 | defer outputTensor.Destroy() 36 | 37 | // 5️⃣ 创建 AdvancedSession 38 | session, err := ort.NewAdvancedSession( 39 | "./predict/model/lgb_stock_model.onnx", // ONNX 模型路径 40 | []string{"input"}, // 输入节点名(Python 查看 sess.get_inputs()[0].name) 41 | []string{"label"}, // 输出节点名(Python 查看 sess.get_outputs()[0].name) 42 | []ort.Value{inputTensor}, 43 | []ort.Value{outputTensor}, 44 | nil, 45 | ) 46 | if err != nil { 47 | log.Fatal("Failed to create session:", err) 48 | } 49 | defer session.Destroy() 50 | 51 | // 6️⃣ 执行推理 52 | if err := session.Run(); err != nil { 53 | log.Fatal("Inference failed:", err) 54 | } 55 | 56 | // 7️⃣ 获取输出结果 57 | pred := outputTensor.GetData() 58 | fmt.Println("预测结果:", pred) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/views/StockPoolView.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 53 | -------------------------------------------------------------------------------- /backend/strategy/strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go-stock-analyzer/backend/config" 7 | "go-stock-analyzer/backend/storage" 8 | ) 9 | type Strategy interface { 10 | Name() string 11 | Match(code string, klines []storage.KLine) bool 12 | } 13 | 14 | func strategyFromConfig(sc config.StrategyConfig) (Strategy, error) { 15 | switch sc.Name { 16 | case "MA": 17 | ma := 20 18 | hd := 3 19 | if v, ok := sc.Params["ma"]; ok { 20 | switch t := v.(type) { 21 | case int: 22 | ma = t 23 | case float64: 24 | ma = int(t) 25 | } 26 | } 27 | if v, ok := sc.Params["hold_days"]; ok { 28 | switch t := v.(type) { 29 | case int: 30 | hd = t 31 | case float64: 32 | hd = int(t) 33 | } 34 | } 35 | return NewMAStrategy(ma, hd), nil 36 | case "MACD": 37 | return NewMACDStrategy(), nil 38 | case "Composite": 39 | hd := 3 40 | if v, ok := sc.Params["hold_days"]; ok { 41 | switch t := v.(type) { 42 | case int: 43 | hd = t 44 | case float64: 45 | hd = int(t) 46 | } 47 | } 48 | return NewCompositeStrategy(hd), nil 49 | case "DSL": 50 | expr := "" 51 | if v, ok := sc.Params["expr"]; ok { 52 | if s, ok2 := v.(string); ok2 { 53 | expr = s 54 | } 55 | } 56 | return NewDSLStrategy(expr), nil 57 | default: 58 | return nil, fmt.Errorf("unknown strategy: %s", sc.Name) 59 | } 60 | } 61 | 62 | func RunAll(stocks []string) { 63 | for _, sc := range config.Cfg.Strategies { 64 | if !sc.Enabled { 65 | continue 66 | } 67 | strat, err := strategyFromConfig(sc) 68 | if err != nil { 69 | continue 70 | } 71 | for _, code := range stocks { 72 | klines, err := storage.LoadKLines(code, config.Cfg.KLineDays) 73 | if err != nil || len(klines) == 0 { 74 | continue 75 | } 76 | if strat.Match(code, klines) { 77 | last := klines[len(klines)-1] 78 | storage.SaveResult(code, last.Date, strat.Name()) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go-stock-analyzer 2 | 3 | go 1.25.1 4 | 5 | require ( 6 | github.com/Knetic/govaluate v3.0.0+incompatible 7 | github.com/gin-gonic/gin v1.11.0 8 | github.com/gorilla/websocket v1.5.3 9 | github.com/mattn/go-sqlite3 v1.14.32 10 | github.com/traefik/yaegi v0.16.1 11 | github.com/yalue/onnxruntime_go v1.21.0 12 | gopkg.in/yaml.v2 v2.4.0 13 | ) 14 | 15 | require ( 16 | github.com/bytedance/sonic v1.14.0 // indirect 17 | github.com/bytedance/sonic/loader v0.3.0 // indirect 18 | github.com/cloudwego/base64x v0.1.6 // indirect 19 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 20 | github.com/gin-contrib/sse v1.1.0 // indirect 21 | github.com/go-playground/locales v0.14.1 // indirect 22 | github.com/go-playground/universal-translator v0.18.1 // indirect 23 | github.com/go-playground/validator/v10 v10.27.0 // indirect 24 | github.com/goccy/go-json v0.10.2 // indirect 25 | github.com/goccy/go-yaml v1.18.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/klauspost/cpuid/v2 v2.3.0 // indirect 28 | github.com/leodido/go-urn v1.4.0 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 31 | github.com/modern-go/reflect2 v1.0.2 // indirect 32 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 33 | github.com/quic-go/qpack v0.5.1 // indirect 34 | github.com/quic-go/quic-go v0.54.0 // indirect 35 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 36 | github.com/ugorji/go/codec v1.3.0 // indirect 37 | go.uber.org/mock v0.5.0 // indirect 38 | golang.org/x/arch v0.20.0 // indirect 39 | golang.org/x/crypto v0.40.0 // indirect 40 | golang.org/x/mod v0.25.0 // indirect 41 | golang.org/x/net v0.42.0 // indirect 42 | golang.org/x/sync v0.16.0 // indirect 43 | golang.org/x/sys v0.35.0 // indirect 44 | golang.org/x/text v0.27.0 // indirect 45 | golang.org/x/tools v0.34.0 // indirect 46 | google.golang.org/protobuf v1.36.9 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /backend/fetcher/fetcher.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "go-stock-analyzer/backend/storage" 13 | ) 14 | 15 | // 获取指定股票的日 K 线数据并计算常用指标(MA, MACD) 16 | func FetchKLine(symbol string, days int) ([]storage.KLine, error) { 17 | url := fmt.Sprintf("http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol=%s&scale=240&ma=no&datalen=%d", symbol, days) 18 | client := &http.Client{Timeout: 15 * time.Second} 19 | resp, err := client.Get(url) 20 | if err != nil { 21 | return nil, err 22 | } 23 | body, _ := io.ReadAll(resp.Body) 24 | resp.Body.Close() 25 | text := strings.TrimSpace(string(body)) 26 | if text == "" || text == "null" { 27 | return nil, fmt.Errorf("empty kline for %s", symbol) 28 | } 29 | var raw []struct { 30 | Day string `json:"day"` 31 | Open string `json:"open"` 32 | High string `json:"high"` 33 | Low string `json:"low"` 34 | Close string `json:"close"` 35 | Volume string `json:"volume"` 36 | } 37 | if err := json.Unmarshal([]byte(text), &raw); err != nil { 38 | return nil, err 39 | } 40 | klines := make([]storage.KLine, 0, len(raw)) 41 | closes := make([]float64, 0, len(raw)) 42 | for _, r := range raw { 43 | open, _ := strconv.ParseFloat(r.Open, 64) 44 | high, _ := strconv.ParseFloat(r.High, 64) 45 | low, _ := strconv.ParseFloat(r.Low, 64) 46 | closep, _ := strconv.ParseFloat(r.Close, 64) 47 | vol, _ := strconv.ParseFloat(r.Volume, 64) 48 | k := storage.KLine{ 49 | Code: symbol, 50 | Date: r.Day, 51 | Open: open, 52 | High: high, 53 | Low: low, 54 | Close: closep, 55 | Volume: vol, 56 | } 57 | klines = append(klines, k) 58 | closes = append(closes, closep) 59 | } 60 | // 计算指标 61 | for i := range klines { 62 | sub := closes[:i+1] 63 | klines[i].MA5 = CalcMA(sub, 5) 64 | klines[i].MA10 = CalcMA(sub, 10) 65 | klines[i].MA20 = CalcMA(sub, 20) 66 | klines[i].MA30 = CalcMA(sub, 30) 67 | dif, dea, macd := CalcMACD(sub) 68 | klines[i].DIF = dif 69 | klines[i].DEA = dea 70 | klines[i].MACD = macd 71 | } 72 | return klines, nil 73 | } 74 | -------------------------------------------------------------------------------- /frontend/src/views/StrategyEditor.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 77 | -------------------------------------------------------------------------------- /backend/fetcher/stock_list.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go-stock-analyzer/backend/storage" 7 | "io" 8 | "net/http" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // 新浪财经返回的股票条目 14 | type sinaItem struct { 15 | Symbol string `json:"symbol"` 16 | Code string `json:"code"` 17 | Name string `json:"name"` 18 | Trade string `json:"trade"` 19 | } 20 | 21 | // 板块分类 22 | func classifyBoard(symbol, code string) string { 23 | // symbol like "sh600000" or "sz000001" 24 | if strings.HasPrefix(symbol, "sh") { 25 | if strings.HasPrefix(code, "688") { 26 | return "科创板" 27 | } 28 | // treat other sh as 上证主板 if starts with 6 29 | if strings.HasPrefix(code, "6") { 30 | return "上证主板" 31 | } 32 | } 33 | if strings.HasPrefix(symbol, "sz") { 34 | if strings.HasPrefix(code, "300") || strings.HasPrefix(code, "301") { 35 | return "创业板" 36 | } 37 | if strings.HasPrefix(code, "000") { 38 | return "深证主板" 39 | } 40 | } 41 | return "" 42 | } 43 | 44 | // FetchAllStocks 从新浪财经抓取沪深A股列表并分类为四大板块 45 | func FetchAllStocks() ([]storage.StockInfo, error) { 46 | var all []storage.StockInfo 47 | page := 1 48 | pageSize := 200 49 | 50 | client := &http.Client{Timeout: 15 * time.Second} 51 | for { 52 | url := fmt.Sprintf("http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?page=%d&num=%d&sort=symbol&asc=1&node=hs_a", page, pageSize) 53 | resp, err := client.Get(url) 54 | if err != nil { 55 | // network error - return so caller can decide 56 | return nil, err 57 | } 58 | body, _ := io.ReadAll(resp.Body) 59 | resp.Body.Close() 60 | text := strings.TrimSpace(string(body)) 61 | text = strings.ReplaceAll(text, "'", "\"") 62 | if text == "[]" || text == "" { 63 | break 64 | } 65 | var items []sinaItem 66 | if err := json.Unmarshal([]byte(text), &items); err != nil { 67 | // if unmarshal fails, stop fetching 68 | break 69 | } 70 | if len(items) == 0 { 71 | break 72 | } 73 | for _, it := range items { 74 | board := classifyBoard(it.Symbol, it.Code) 75 | if board == "" { 76 | continue 77 | } 78 | trade := 0.0 79 | // parse trade float safely 80 | fmt.Sscanf(it.Trade, "%f", &trade) 81 | s := storage.StockInfo{ 82 | Symbol: it.Symbol, 83 | Code: it.Code, 84 | Name: it.Name, 85 | Market: strings.ToUpper(it.Symbol[:2]), 86 | Board: board, 87 | Trade: trade, 88 | } 89 | all = append(all, s) 90 | } 91 | // next page 92 | page++ 93 | // sleep a bit to be polite 94 | time.Sleep(200 * time.Millisecond) 95 | // safety cap 96 | if page > 50 { 97 | break 98 | } 99 | } 100 | return all, nil 101 | } 102 | -------------------------------------------------------------------------------- /predict/train_lightgbm.py: -------------------------------------------------------------------------------- 1 | # train_lightgbm.py 2 | import os 3 | import sys 4 | import pandas as pd 5 | import numpy as np 6 | import joblib 7 | import lightgbm as lgb 8 | from sklearn.model_selection import train_test_split 9 | from sklearn.metrics import accuracy_score 10 | 11 | # ✅ 关键:用 onnxmltools 转换 LightGBM 模型 12 | from onnxmltools import convert_lightgbm 13 | from onnxmltools.utils import save_model 14 | from onnxmltools.convert.common.data_types import FloatTensorType 15 | 16 | def make_features(df): 17 | df = df.copy() 18 | df["return"] = df["Close"].pct_change() 19 | df["ma5"] = df["Close"].rolling(5).mean() 20 | df["ma10"] = df["Close"].rolling(10).mean() 21 | delta = df["Close"].diff() 22 | up = delta.clip(lower=0).rolling(14).mean() 23 | down = (-delta.clip(upper=0)).rolling(14).mean() 24 | df["rsi"] = 100 - 100 / (1 + (up / (down + 1e-9))) 25 | df = df.dropna().reset_index(drop=True) 26 | return df 27 | 28 | def main(csv_path="data/stock_history.csv"): 29 | if not os.path.exists(csv_path): 30 | raise SystemExit(f"数据文件不存在: {csv_path}") 31 | 32 | df = pd.read_csv(csv_path) 33 | df = df.sort_values("Date").reset_index(drop=True) 34 | df = make_features(df) 35 | 36 | df["label"] = (df["Close"].shift(-1) > df["Close"]).astype(int) 37 | df = df.dropna().reset_index(drop=True) 38 | 39 | feature_cols = ["Open", "High", "Low", "Close", "Volume", "ma5", "ma10", "rsi", "return"] 40 | X = df[feature_cols].astype(float) 41 | y = df["label"].astype(int) 42 | 43 | X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=False, test_size=0.2) 44 | 45 | # ✅ 使用 LightGBM 原生训练接口 46 | train_data = lgb.Dataset(X_train, label=y_train) 47 | valid_data = lgb.Dataset(X_test, label=y_test) 48 | 49 | params = { 50 | "objective": "binary", 51 | "metric": "binary_error", 52 | "learning_rate": 0.05, 53 | "num_leaves": 31, 54 | "verbose": -1, 55 | } 56 | 57 | model = lgb.train(params, train_data, valid_sets=[valid_data], num_boost_round=200) 58 | 59 | # 验证准确率 60 | y_pred = (model.predict(X_test) > 0.5).astype(int) 61 | acc = accuracy_score(y_test, y_pred) 62 | print(f"✅ Test accuracy: {acc:.4f}") 63 | 64 | os.makedirs("model", exist_ok=True) 65 | 66 | # 保存 LightGBM 模型(文本格式 + Booster) 67 | model.save_model("model/lgb_stock_model.txt") 68 | joblib.dump(model, "model/lgb_stock_model.joblib") 69 | print("✅ 模型保存完成") 70 | 71 | # 2) 初始化 input 类型 72 | n_features = X_train.shape[1] 73 | initial_types = [("float_input", FloatTensorType([None, n_features]))] 74 | 75 | # 3) 转换 LightGBM Booster 为 ONNX 76 | onnx_model = convert_lightgbm( 77 | model, 78 | name="LGBMClassifier", 79 | initial_types=initial_types, 80 | target_opset=15 81 | ) 82 | 83 | # 4) 保存 ONNX 84 | onnx_path = "model/lgb_stock_model.onnx" 85 | save_model(onnx_model, onnx_path) 86 | print(f"✅ 导出 ONNX 成功 -> {onnx_path}") 87 | 88 | if __name__ == "__main__": 89 | csv = sys.argv[1] if len(sys.argv) > 1 else "data/stock_history.csv" 90 | main(csv) 91 | -------------------------------------------------------------------------------- /backend/web/server.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go-stock-analyzer/backend/realtime" 7 | "io/ioutil" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func RunServer() { 16 | r := gin.Default() 17 | 18 | // API endpoints 19 | r.GET("/api/stocks", GetStocksHandler) 20 | r.GET("/api/watchlist", GetWatchlistHandler) 21 | r.POST("/api/watchlist/add", AddWatchlistHandler) 22 | r.DELETE("/api/watchlist/remove", RemoveWatchlistHandler) 23 | r.GET("/api/kline", GetKLineHandler) 24 | r.GET("/api/timeline", GetTimelineHandler) 25 | r.GET("/api/is_market_open", IsMarketOpenHandler) 26 | 27 | r.GET("/api/strategy/list", ListStrategiesHandler) 28 | r.POST("/api/strategy/run", RunStrategyHandler) 29 | r.PUT("/api/strategy/:id", UpdateStrategyHandler) 30 | r.DELETE("/api/strategy/:id", DeleteStrategyHandler) 31 | r.POST("/api/strategy", SaveStrategyHandler) 32 | 33 | // websocket endpoint for realtime 34 | r.GET("/ws/realtime", func(c *gin.Context) { 35 | realtime.HandleWebSocket(c.Writer, c.Request) 36 | }) 37 | 38 | // static frontend (optional) 39 | r.Static("/static", "../frontend/dist") 40 | r.GET("/", func(c *gin.Context) { 41 | c.String(http.StatusOK, "Go-stock-analyzer backend") 42 | }) 43 | 44 | go realtime.RunHub() 45 | r.Run(":8080") 46 | } 47 | 48 | // 新浪分时接口返回结构 49 | // [{"day":"2024-06-14","time":"09:30","price":"10.23","volume":"1234"}, ...] 50 | type SinaTimeline struct { 51 | Time string `json:"time"` 52 | Price string `json:"price"` 53 | Volume string `json:"volume"` 54 | } 55 | type Timeline struct { 56 | Time string `json:"time"` 57 | Price float64 `json:"price"` 58 | Volume float64 `json:"volume"` 59 | } 60 | 61 | func GetTimelineHandler(c *gin.Context) { 62 | symbol := c.Query("symbol") 63 | if symbol == "" { 64 | c.JSON(http.StatusBadRequest, gin.H{"error": "symbol required"}) 65 | return 66 | } 67 | url := fmt.Sprintf("http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol=%s&scale=5&datalen=48", symbol) 68 | resp, err := http.Get(url) 69 | if err != nil { 70 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 71 | return 72 | } 73 | defer resp.Body.Close() 74 | body, err := ioutil.ReadAll(resp.Body) 75 | if err != nil { 76 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 77 | return 78 | } 79 | str := string(body) 80 | str = strings.ReplaceAll(str, "'", "\"") 81 | if !strings.HasPrefix(str, "[") { 82 | c.JSON(http.StatusOK, []Timeline{}) 83 | return 84 | } 85 | // 新浪5分钟K线结构 86 | type SinaKLine struct { 87 | Day string `json:"day"` 88 | Open string `json:"open"` 89 | High string `json:"high"` 90 | Low string `json:"low"` 91 | Close string `json:"close"` 92 | Volume string `json:"volume"` 93 | } 94 | var klines []SinaKLine 95 | if err := json.Unmarshal([]byte(str), &klines); err != nil { 96 | c.JSON(http.StatusOK, []Timeline{}) 97 | return 98 | } 99 | out := make([]Timeline, 0, len(klines)) 100 | for _, k := range klines { 101 | price, _ := strconv.ParseFloat(k.Close, 64) 102 | volume, _ := strconv.ParseFloat(k.Volume, 64) 103 | // 只取时间部分 104 | t := k.Day 105 | if len(t) >= 8 { 106 | // 兼容 "2025-09-30 09:35:00" 或 "09:35:00" 107 | parts := strings.Split(t, " ") 108 | if len(parts) == 2 { 109 | t = parts[1] 110 | } 111 | } 112 | out = append(out, Timeline{Time: t, Price: price, Volume: volume}) 113 | } 114 | c.JSON(http.StatusOK, out) 115 | } 116 | -------------------------------------------------------------------------------- /backend/storage/strategy_store.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "time" 5 | "fmt" 6 | ) 7 | 8 | // Strategy 持久化策略结构 9 | type Strategy struct { 10 | ID int64 `json:"id"` 11 | Name string `json:"name"` 12 | Desc string `json:"description"` 13 | Code string `json:"code"` 14 | Author string `json:"author"` 15 | CreatedAt time.Time `json:"created_at"` 16 | UpdatedAt time.Time `json:"updated_at"` 17 | } 18 | 19 | // InitStrategyTable 在 InitDB 后可调用(或合并到 InitDB) 20 | func InitStrategyTable() error { 21 | _, err := db.Exec(`CREATE TABLE IF NOT EXISTS strategies ( 22 | id INTEGER PRIMARY KEY AUTOINCREMENT, 23 | name TEXT, 24 | description TEXT, 25 | code TEXT, 26 | author TEXT, 27 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 28 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 29 | )`) 30 | if err != nil { 31 | return err 32 | } 33 | _, err = db.Exec(`CREATE TABLE IF NOT EXISTS strategy_runs ( 34 | id INTEGER PRIMARY KEY AUTOINCREMENT, 35 | strategy_id INTEGER, 36 | target TEXT, 37 | matches_count INTEGER, 38 | duration_ms INTEGER, 39 | err TEXT, 40 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 41 | )`) 42 | return err 43 | } 44 | 45 | // SaveStrategy 保存策略并返回 id 46 | func SaveStrategyDB(s *Strategy) (int64, error) { 47 | res, err := db.Exec(`INSERT INTO strategies(name, description, code, author, created_at, updated_at) VALUES(?,?,?,?,?,?)`, 48 | s.Name, s.Desc, s.Code, s.Author, time.Now(), time.Now()) 49 | if err != nil { 50 | return 0, err 51 | } 52 | return res.LastInsertId() 53 | } 54 | 55 | // UpdateStrategy 更新策略 56 | func UpdateStrategyDB(s *Strategy) error { 57 | _, err := db.Exec(`UPDATE strategies SET name=?, description=?, code=?, author=?, updated_at=? WHERE id=?`, 58 | s.Name, s.Desc, s.Code, s.Author, time.Now(), s.ID) 59 | return err 60 | } 61 | 62 | // GetStrategyDB 63 | func GetStrategyDB(id int64) (*Strategy, error) { 64 | row := db.QueryRow(`SELECT id,name,description,code,author,created_at,updated_at FROM strategies WHERE id=?`, id) 65 | var s Strategy 66 | var created, updated string 67 | if err := row.Scan(&s.ID, &s.Name, &s.Desc, &s.Code, &s.Author, &created, &updated); err != nil { 68 | return nil, err 69 | } 70 | s.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", created) 71 | s.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updated) 72 | return &s, nil 73 | } 74 | 75 | // ListStrategiesDB 76 | func ListStrategiesDB() ([]Strategy, error) { 77 | rows, err := db.Query(`SELECT id,name,description,author,created_at,updated_at FROM strategies ORDER BY created_at DESC`) 78 | if err != nil { 79 | fmt.Println("ListStrategiesDB error:", err) 80 | return nil, err 81 | } 82 | defer rows.Close() 83 | var out []Strategy 84 | for rows.Next() { 85 | var s Strategy 86 | var created, updated string 87 | if err := rows.Scan(&s.ID, &s.Name, &s.Desc, &s.Author, &created, &updated); err != nil { 88 | return nil, err 89 | } 90 | s.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", created) 91 | s.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updated) 92 | out = append(out, s) 93 | } 94 | return out, nil 95 | } 96 | 97 | // SaveStrategyRunLog 保存一次运行记录(optional) 98 | func SaveStrategyRunLog(strategyID int64, target string, matchesCount int, durationMs int64, errStr string) error { 99 | _, err := db.Exec(`INSERT INTO strategy_runs(strategy_id, target, matches_count, duration_ms, err, created_at) VALUES(?,?,?,?,?,?)`, 100 | strategyID, target, matchesCount, durationMs, errStr, time.Now()) 101 | return err 102 | } 103 | 104 | // DeleteStrategyDB 根据 id 删除策略 105 | func DeleteStrategyDB(id int64) error { 106 | _, err := db.Exec(`DELETE FROM strategies WHERE id=?`, id) 107 | return err 108 | } 109 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | ## 快速概览(对 AI 代理) 2 | 3 | 这是一个轻量的 A 股数据抓取、指标计算、策略回测/调度与实时推送的全栈演示工程。 4 | 主要语言:Go(后端) + Vue/Vite(前端)。数据库为 SQLite(单文件,位于 `backend/stock.db`,路径可由 `backend/config/config.yaml` 配置)。 5 | 6 | 下面的要点足够让一个自动化编码代理快速定位修改点并安全运行项目。 7 | 8 | --- 9 | 10 | ## 一句话架构梳理 11 | 12 | - 启动入口:`backend/main.go`。顺序:加载 config -> 初始化 DB -> 若股票表不足则调用 `fetcher.FetchAllStocks()` -> 启动 realtime poll -> 启动每日 scheduler -> 启动 web 服务(Gin,:8080)。 13 | - 数据流:fetcher (Sina API) -> storage (SQLite) -> scheduler/strategy -> web(realtime + HTTP API) -> frontend (Vue)。 14 | 15 | --- 16 | 17 | ## 运行 / 构建(最常用操作) 18 | 19 | - 在仓库根目录运行后端(Go 环境要求): 20 | - 开发直接:`go run ./backend`(以 repo 根为工作目录,保证相对路径 `backend/config/config.yaml` 可读) 21 | - 构建:`go build -o bin/backend ./backend`,然后执行 `./bin/backend` 22 | - 注意:项目依赖 `github.com/mattn/go-sqlite3`,该包需要 CGO(本地 C 编译器,如 gcc)启用。若构建失败,请在开发环境(WSL/Unix)安装 `build-essential` 并确保 `CGO_ENABLED=1`。 23 | 24 | - 前端(开发模式): 25 | - 进入 `frontend`,安装依赖并启动:`npm install`,`npm run dev`(vite)。 26 | - 生产构建后端可通过 `backend/web` 的静态路由 `r.Static("/static", "../frontend/dist")` 提供静态资源,需将 `frontend/dist` 放在 `backend` 相对路径下。 27 | 28 | --- 29 | 30 | ## 关键目录与文件(快速定位) 31 | 32 | - `backend/main.go`:程序入口,说明了启动顺序和默认阈值(例如少于 100 条股票会触发全量抓取)。 33 | - `backend/config/config.yaml`、`backend/config/config.go`:运行时配置(DB 路径、kline 天数、调度时间、策略开关)。 34 | - `backend/fetcher/*`:所有外部数据抓取逻辑(Sina 股票列表与 K 线),并在抓取后计算简单指标(MA、MACD)。示例:`FetchAllStocks()`、`FetchKLine()`。 35 | - `backend/storage/db.go`:SQLite schema、CRUD、核心约定(表名:stocks、watchlist、kline、results)。注意:使用 `INSERT OR REPLACE` 做 upsert,PRAGMA 已做性能优化(WAL、synchronous NORMAL)。 36 | - `backend/scheduler/scheduler.go`:按 `config.Cfg.UpdateHour/Minute` 定时拉取 K 线并触发 `strategy.RunAll`。 37 | - `backend/realtime/*`:WebSocket hub(`/ws/realtime`),以及 polling/broadcast 逻辑,客户端按 `Quote.Code` 订阅。 38 | - `backend/web/server.go`:HTTP API 路由(例如 `/api/stocks`, `/api/watchlist`, `/api/kline`)以及启动 Gin 服务。 39 | - `backend/strategy/*`:策略实现(MA、MACD、DSL、Composite)。DSL 使用 `github.com/Knetic/govaluate` 解析表达式。 40 | 41 | --- 42 | 43 | ## 项目特有约定与模式(对修改行为很重要) 44 | 45 | - 符号格式:项目使用 `symbol`(例如 `sz000001` / `sh600000`)与 `code`(纯数字代码)并区分 `market`(前缀)与 `board`(通过 `classifyBoard` 分配为上证主板/深证主板/创业板/科创板)。修改 symbol 相关逻辑请同时检查 `fetcher` 和 `storage`。 46 | - Watchlist 驱动:调度器与 realtime 的 polling 都以 `storage.GetWatchlist()` 返回的 `symbol` 列表为准。改变被分析或推送的股票请修改 watchlist 表或对应 API 调用。 47 | - 数据保存:K 线、股票信息通过 `INSERT OR REPLACE` 持久化,schema 在 `storage.InitDB` 中定义。直接修改表结构时请同时更新 InitDB。 48 | - 外部 API 偶发性:fetcher 直接请求新浪接口并做简单容错(空判断、单引号转双引号等),网络或解析错误是常见失败点,建议在修改时尽量保持宽容的解析逻辑。 49 | 50 | --- 51 | 52 | ## 集成点与外部依赖(需要注意的地方) 53 | 54 | - 外部数据源:新浪财经相关接口(股票列表与 K 线)。抓取代码:`backend/fetcher/stock_list.go`, `backend/fetcher/fetcher.go`。 55 | - 数据库:SQLite 文件 `backend/stock.db`(默认)。使用 sqlite3 驱动需要 CGO,CI/容器环境需装 gcc。调试时可以直接用 `sqlite3 backend/stock.db` 查看表。 56 | - Web/Realtime:后端提供 REST + WebSocket(Gin + gorilla/websocket)。前端使用 axios + WebSocket 连接 `/ws/realtime` 推送。 57 | - 策略 DSL:`backend/strategy/dsl_strategy.go` 使用 `govaluate`,策略参数来自 `config.yaml`,策略结果通过 `storage.SaveResult` 写入 `results` 表。 58 | 59 | --- 60 | 61 | ## 典型修改场景示例(针对 AI 代理) 62 | 63 | - 增加一个新的 HTTP API:在 `backend/web/server.go` 上添加路由,编写 handler(参考现有 `GetStocksHandler`),如需 DB 访问使用 `storage` 包导出的函数。 64 | - 修改抓取逻辑的容错:在 `backend/fetcher/*` 中增强 JSON 解析或对 API 速限的退避,注意保持 `FetchAllStocks()` 的分页与 `classifyBoard()` 的符号规则。 65 | - 添加新策略:实现 `strategy` 包下新文件,实现与现有策略相同的注册/调用方式(参考 `RunAll` 调用)。策略输出需调用 `storage.SaveResult`。 66 | 67 | --- 68 | 69 | ## 调试与诊断提示 70 | 71 | - 运行时日志:后端大量使用 `log.Printf` 输出关键步骤(初始化、抓取失败、保存失败等),可直接在终端观察。 72 | - 数据层验证:用 `sqlite3 backend/stock.db` 或任意 SQLite 浏览器查看 `stocks` / `kline` 表。 73 | - 快速触发分析:最简单方式是启动整个后端(`go run ./backend`),scheduler 会在配置时间触发;要立即手动触发,可以临时调用 `strategy.RunAll`(修改代码后编译运行)或把 `StartDailyTask` 的逻辑抽成可复用函数并调用。 74 | 75 | --- 76 | 77 | 如果本文件中有任何不清楚或需要更多细节(例如 CI、容器化建议、或某些文件的具体行为),请告诉我你想补充的用例,我会基于仓库代码迭代更新此文件。 78 | -------------------------------------------------------------------------------- /backend/strategyexec/exec.go: -------------------------------------------------------------------------------- 1 | package strategyexec 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "time" 8 | 9 | "go-stock-analyzer/backend/storage" 10 | 11 | "github.com/traefik/yaegi/interp" 12 | "github.com/traefik/yaegi/stdlib" 13 | ) 14 | 15 | // 执行器配置 16 | type ExecConfig struct { 17 | TotalTimeout time.Duration // 总超时 18 | PerSymbolTimeout time.Duration // 每只股票执行超时,可二次控制 19 | } 20 | 21 | // 默认配置(可修改) 22 | var DefaultExecConfig = ExecConfig{ 23 | TotalTimeout: 30 * time.Second, 24 | PerSymbolTimeout: 800 * time.Millisecond, 25 | } 26 | 27 | // ExecuteStrategy 用用户 code 在 symbols 列表上执行 Match 函数。 28 | // - code: 用户提供的源码字符串,必须定义 `func Match(symbol string, klines []map[string]interface{}) bool` 29 | // - symbols: 如 ["sz000001", "sh600000"] 30 | // - loadKlines: 由调用方提供加载函数 (symbol, days) -> []storage.KLine 31 | func ExecuteStrategy(code string, symbols []string, klineDays int, loadKlines func(string, int) ([]storage.KLine, error), cfg ExecConfig) ([]string, error) { 32 | if cfg.TotalTimeout == 0 { 33 | cfg = DefaultExecConfig 34 | } 35 | // 创建解释器 36 | i := interp.New(interp.Options{}) 37 | // 允许基础 stdlib(你可以选择禁用某些包,但为方便演示使用 stdlib) 38 | i.Use(stdlib.Symbols) 39 | 40 | // Prepare a wrapper source that will expose Match as global symbol. 41 | // We will evaluate the user's code directly. 42 | if _, err := i.Eval(code); err != nil { 43 | return nil, fmt.Errorf("compile error: %w", err) 44 | } 45 | 46 | // 获取 Match 符号 47 | v, err := i.Eval("Match") 48 | if err != nil { 49 | return nil, fmt.Errorf("Match function not found in code: %w", err) 50 | } 51 | matchVal := v.Interface() 52 | matchFunc := reflect.ValueOf(matchVal) 53 | if matchFunc.Kind() != reflect.Func { 54 | return nil, fmt.Errorf("Match is not a function") 55 | } 56 | 57 | ctx, cancel := context.WithTimeout(context.Background(), cfg.TotalTimeout) 58 | defer cancel() 59 | 60 | matched := []string{} 61 | start := time.Now() 62 | 63 | for _, sym := range symbols { 64 | select { 65 | case <-ctx.Done(): 66 | return matched, fmt.Errorf("total timeout reached") 67 | default: 68 | } 69 | 70 | // load klines from caller-provided loader 71 | klines, err := loadKlines(sym, klineDays) 72 | if err != nil { 73 | // skip on load error 74 | continue 75 | } 76 | // convert []storage.KLine -> []map[string]interface{} 77 | arg := make([]map[string]interface{}, 0, len(klines)) 78 | for _, k := range klines { 79 | m := map[string]interface{}{ 80 | "Date": k.Date, 81 | "Open": k.Open, 82 | "High": k.High, 83 | "Low": k.Low, 84 | "Close": k.Close, 85 | "Volume": k.Volume, 86 | } 87 | arg = append(arg, m) 88 | } 89 | 90 | // prepare call with a per-symbol timeout 91 | ch := make(chan bool, 1) 92 | errCh := make(chan error, 1) 93 | go func(sym string, arg []map[string]interface{}) { 94 | defer func() { 95 | // recover from panics in user code 96 | if r := recover(); r != nil { 97 | errCh <- fmt.Errorf("panic in user code: %v", r) 98 | } 99 | }() 100 | // Call matchFunc(symbol, arg) 101 | in := []reflect.Value{reflect.ValueOf(sym), reflect.ValueOf(arg)} 102 | out := matchFunc.Call(in) 103 | if len(out) == 0 { 104 | errCh <- fmt.Errorf("Match must return bool") 105 | return 106 | } 107 | ok, okCast := out[0].Interface().(bool) 108 | if !okCast { 109 | errCh <- fmt.Errorf("Match return is not bool") 110 | return 111 | } 112 | ch <- ok 113 | }(sym, arg) 114 | 115 | select { 116 | case ok := <-ch: 117 | if ok { 118 | matched = append(matched, sym) 119 | } 120 | case e := <-errCh: 121 | // log and skip symbol 122 | _ = e // can log 123 | continue 124 | case <-time.After(cfg.PerSymbolTimeout): 125 | // per-symbol timeout, skip 126 | continue 127 | case <-ctx.Done(): 128 | return matched, fmt.Errorf("total timeout reached") 129 | } 130 | } 131 | 132 | _ = start // could measure duration 133 | return matched, nil 134 | } 135 | -------------------------------------------------------------------------------- /backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | 8 | "go-stock-analyzer/backend/config" 9 | "go-stock-analyzer/backend/fetcher" 10 | "go-stock-analyzer/backend/realtime" 11 | "go-stock-analyzer/backend/scheduler" 12 | "go-stock-analyzer/backend/storage" 13 | "go-stock-analyzer/backend/web" 14 | ) 15 | 16 | func main() { 17 | // 加载配置 18 | config.LoadConfig("backend/config/config.yaml") 19 | 20 | // init db 21 | if err := storage.InitDB(config.Cfg.DBPath); err != nil { 22 | log.Fatalf("init db failed: %v", err) 23 | } 24 | // 每次启动校验板块股票列表是否有更新(首次启动会初始化) 25 | log.Println("checking stock list updates from Sina...") 26 | if list, err := fetcher.FetchAllStocks(); err != nil { 27 | log.Printf("fetch all stocks failed: %v", err) 28 | } else { 29 | // compare counts; if different或更新则 upsert 到 DB 30 | n, err := storage.CountStocksByBoards() 31 | if err != nil { 32 | log.Printf("count stocks error: %v", err) 33 | } 34 | log.Printf("existing stocks in DB (boards of interest): %d; fetched: %d", n, len(list)) 35 | // 如果数量不一致,尝试保存(INSERT OR REPLACE 会做 upsert) 36 | if len(list) == 0 { 37 | log.Println("fetched stock list empty, skipping save") 38 | } else if n != len(list) { 39 | log.Println("stock list changed or first-run — saving fetched list...") 40 | if err := storage.SaveStocks(list); err != nil { 41 | log.Printf("save stocks failed: %v", err) 42 | } else { 43 | log.Printf("saved %d stocks", len(list)) 44 | } 45 | } else { 46 | log.Println("stock list appears unchanged; no save needed") 47 | } 48 | } 49 | // 启动时加载自选股的 K 线数据并保存 50 | // load watchlist (自选股) 51 | watch, _ := storage.GetWatchlist() 52 | symbols := []string{} 53 | for _, w := range watch { 54 | symbols = append(symbols, w.Symbol) 55 | } 56 | log.Printf("symbols: %d\n", len(symbols)) 57 | // 为自选股抓取 300 天日 K 线并保存(后台异步执行以不阻塞 web 启动) 58 | if len(symbols) > 0 { 59 | go func(syms []string) { 60 | conc := config.Cfg.WorkerConcurrency 61 | if conc <= 0 { 62 | conc = 5 63 | } 64 | retries := config.Cfg.WorkerRetries 65 | if retries <= 0 { 66 | retries = 3 67 | } 68 | delayMs := config.Cfg.WorkerDelayMs 69 | if delayMs <= 0 { 70 | delayMs = 200 71 | } 72 | backoffMs := config.Cfg.WorkerBackoffMs 73 | if backoffMs <= 0 { 74 | backoffMs = 500 75 | } 76 | days := config.Cfg.WatchlistKlineDays 77 | if days <= 0 { 78 | days = 300 79 | } 80 | 81 | jobs := make(chan string, len(syms)) 82 | var wg sync.WaitGroup 83 | 84 | for i := 0; i < conc; i++ { 85 | wg.Add(1) 86 | go func(id int) { 87 | defer wg.Done() 88 | clientDelay := time.Duration(delayMs) * time.Millisecond 89 | for sym := range jobs { 90 | var lastErr error 91 | for attempt := 1; attempt <= retries; attempt++ { 92 | klines, err := fetcher.FetchKLine(sym, days) 93 | log.Printf("worker %d: fetched kline for %s (attempt %d/%d): %d days, err=%v", id, sym, attempt, retries, len(klines), err) 94 | if err == nil { 95 | if err := storage.SaveKLines(sym, klines); err != nil { 96 | lastErr = err 97 | } else { 98 | // saved successfully 99 | lastErr = nil 100 | break 101 | } 102 | } else { 103 | lastErr = err 104 | // backoff 105 | time.Sleep(time.Duration(attempt*backoffMs) * time.Millisecond) 106 | } 107 | } 108 | if lastErr != nil { 109 | log.Printf("startup: failed to fetch/save kline for %s: %v", sym, lastErr) 110 | } 111 | time.Sleep(clientDelay) 112 | } 113 | }(i) 114 | } 115 | 116 | for _, s := range syms { 117 | jobs <- s 118 | } 119 | close(jobs) 120 | wg.Wait() 121 | }(symbols) 122 | } 123 | // if no watchlist, poll a few top symbols from DB 124 | if len(symbols) == 0 { 125 | // get some example symbols 126 | stocks, _, _ := storage.QueryStocks("", "", 0, 50) 127 | for _, s := range stocks { 128 | symbols = append(symbols, s.Symbol) 129 | } 130 | } 131 | 132 | realtime.StartPolling(symbols, 2*time.Second) 133 | 134 | // start daily scheduler (15:00) 135 | scheduler.StartDailyTask() 136 | 137 | // run web server (blocks) 138 | log.Println("starting web server :8080") 139 | web.RunServer() 140 | } 141 | -------------------------------------------------------------------------------- /backend/web/handlers_stocks.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "go-stock-analyzer/backend/fetcher" 5 | "go-stock-analyzer/backend/realtime" 6 | "go-stock-analyzer/backend/storage" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // 查询是否开始交易 15 | // GET /api/is_market_open?code=sz000001 16 | func IsMarketOpenHandler(c *gin.Context) { 17 | code := strings.TrimSpace(c.Query("code")) 18 | open := realtime.IsMarketOpen(code) 19 | c.JSON(http.StatusOK, gin.H{"is_open": open}) 20 | } 21 | 22 | // GET /api/stocks?q=&board=&page=&size= 23 | func GetStocksHandler(c *gin.Context) { 24 | q := strings.TrimSpace(c.Query("q")) 25 | board := strings.TrimSpace(c.Query("board")) 26 | pageStr := c.DefaultQuery("page", "1") 27 | sizeStr := c.DefaultQuery("size", "50") 28 | page, _ := strconv.Atoi(pageStr) 29 | size, _ := strconv.Atoi(sizeStr) 30 | if page < 1 { 31 | page = 1 32 | } 33 | if size < 1 || size > 500 { 34 | size = 50 35 | } 36 | offset := (page - 1) * size 37 | list, total, err := storage.QueryStocks(q, board, offset, size) 38 | if err != nil { 39 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 40 | return 41 | } 42 | c.JSON(http.StatusOK, gin.H{"total": total, "list": list}) 43 | } 44 | 45 | // Watchlist handlers 46 | func GetWatchlistHandler(c *gin.Context) { 47 | list, err := storage.GetWatchlist() 48 | if err != nil { 49 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 50 | return 51 | } 52 | c.JSON(http.StatusOK, list) 53 | } 54 | 55 | func AddWatchlistHandler(c *gin.Context) { 56 | var body struct { 57 | Symbol string `json:"symbol"` 58 | Name string `json:"name"` 59 | } 60 | if err := c.ShouldBindJSON(&body); err != nil { 61 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"}) 62 | return 63 | } 64 | if body.Symbol == "" { 65 | c.JSON(http.StatusBadRequest, gin.H{"error": "symbol required"}) 66 | return 67 | } 68 | if err := storage.AddToWatchlist(body.Symbol, body.Name); err != nil { 69 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 70 | return 71 | } 72 | c.JSON(http.StatusOK, gin.H{"msg": "ok"}) 73 | } 74 | 75 | func RemoveWatchlistHandler(c *gin.Context) { 76 | symbol := c.Query("symbol") 77 | if symbol == "" { 78 | c.JSON(http.StatusBadRequest, gin.H{"error": "symbol required"}) 79 | return 80 | } 81 | if err := storage.RemoveFromWatchlist(symbol); err != nil { 82 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 83 | return 84 | } 85 | c.JSON(http.StatusOK, gin.H{"msg": "removed"}) 86 | } 87 | 88 | // GET /api/kline?symbol=sz000001&datalen=120 89 | func GetKLineHandler(c *gin.Context) { 90 | symbol := c.Query("symbol") 91 | if symbol == "" { 92 | c.JSON(http.StatusBadRequest, gin.H{"error": "symbol required"}) 93 | return 94 | } 95 | datalenStr := c.DefaultQuery("datalen", "120") 96 | datalen, _ := strconv.Atoi(datalenStr) 97 | if datalen <= 0 { 98 | datalen = 120 99 | } 100 | // If symbol is in watchlist (自选股) -> try to load from DB (these are persisted at startup with 300 days) 101 | watch, _ := storage.GetWatchlist() 102 | isWatch := false 103 | for _, w := range watch { 104 | if w.Symbol == symbol { 105 | isWatch = true 106 | break 107 | } 108 | } 109 | if isWatch { 110 | // load from DB; datalen may be <= stored days (we store 300 days at startup) 111 | klines, err := storage.LoadKLines(symbol, datalen) 112 | if err != nil { 113 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 114 | return 115 | } 116 | // if DB returns fewer than requested (e.g., first time), try fetch and save 117 | if len(klines) < datalen { 118 | fetched, err := fetcher.FetchKLine(symbol, datalen) 119 | if err == nil && len(fetched) > 0 { 120 | _ = storage.SaveKLines(symbol, fetched) 121 | // return fetched (most up-to-date) 122 | c.JSON(http.StatusOK, fetched) 123 | return 124 | } 125 | } 126 | c.JSON(http.StatusOK, klines) 127 | return 128 | } 129 | 130 | // Non-watch symbols: fetch on-the-fly from remote and do NOT persist (only return datalen, default 120) 131 | klines, err := fetcher.FetchKLine(symbol, datalen) 132 | if err != nil { 133 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 134 | return 135 | } 136 | c.JSON(http.StatusOK, klines) 137 | } 138 | -------------------------------------------------------------------------------- /backend/realtime/fetcher.go: -------------------------------------------------------------------------------- 1 | package realtime 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/gorilla/websocket" 14 | ) 15 | 16 | var IsMarketOpen = func(code string) bool { 17 | // 简单判断是否在交易时间内(9:30-11:30, 13:00-15:00) 18 | now := time.Now() 19 | weekday := now.Weekday() 20 | if weekday == time.Saturday || weekday == time.Sunday { 21 | return false 22 | } 23 | hour := now.Hour() 24 | minute := now.Minute() 25 | if ((hour == 9 && minute < 30) || (hour < 9) || (hour == 11 && minute > 30)) { 26 | return false 27 | } 28 | if ((hour == 12) || (hour > 15)) { 29 | return false 30 | } 31 | 32 | quotes, err := FetchSinaQuotes([]string{code}) // 试探性请求,确保行情接口可用 33 | if err != nil || len(quotes) == 0 { 34 | return false 35 | } 36 | currDate := now.Format("2006-01-02") 37 | has := strings.HasPrefix(quotes[0].Time, currDate+" ") 38 | if (has && quotes[0].Price != 0) { 39 | return true 40 | } 41 | 42 | return false 43 | } 44 | 45 | type Quote struct { 46 | Code string `json:"code"` 47 | Name string `json:"name"` 48 | Price float64 `json:"price"` 49 | PrevClose float64 `json:"prev_close"` 50 | Open float64 `json:"open"` 51 | High float64 `json:"high"` 52 | Low float64 `json:"low"` 53 | Volume int64 `json:"volume"` 54 | Time string `json:"time"` 55 | } 56 | 57 | var Snapshot = struct { 58 | m map[string]Quote 59 | mu sync.RWMutex 60 | }{m: make(map[string]Quote)} 61 | 62 | func HandleWebSocket(w http.ResponseWriter, r *http.Request) { 63 | upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }} 64 | conn, err := upgrader.Upgrade(w, r, nil) 65 | if err != nil { 66 | return 67 | } 68 | client := &Client{conn: conn, send: make(chan []byte, 256), subs: make(map[string]bool)} 69 | h.register <- client 70 | go client.writePump() 71 | go client.readPump() 72 | } 73 | 74 | func FetchSinaQuotes(codes []string) ([]Quote, error) { 75 | url := "http://hq.sinajs.cn/list=" + strings.Join(codes, ",") 76 | req, _ := http.NewRequest("GET", url, nil) 77 | req.Header.Add("Referer", "http://finance.sina.com.cn/") 78 | c := &http.Client{} 79 | resp, err := c.Do(req) 80 | if err != nil { 81 | log.Println("realtime poll error:", err) 82 | return nil, err 83 | } 84 | body, _ := io.ReadAll(resp.Body) 85 | resp.Body.Close() 86 | 87 | var quotes []Quote 88 | 89 | lines := strings.Split(string(body), ";") 90 | for _, line := range lines { 91 | line = strings.TrimSpace(line) 92 | if line == "" { 93 | continue 94 | } 95 | eq := strings.Index(line, "=") 96 | if eq < 0 { 97 | continue 98 | } 99 | left := line[:eq] 100 | start := strings.LastIndex(left, "hq_str_") 101 | if start < 0 { 102 | continue 103 | } 104 | code := left[start+7:] 105 | qstart := strings.Index(line, "\"") 106 | qend := strings.LastIndex(line, "\"") 107 | if qstart < 0 || qend <= qstart { 108 | continue 109 | } 110 | body := line[qstart+1 : qend] 111 | fields := strings.Split(body, ",") 112 | if len(fields) < 6 { 113 | continue 114 | } 115 | name := fields[0] 116 | open := parseFloat(fields[1]) 117 | prev := parseFloat(fields[2]) 118 | price := parseFloat(fields[3]) 119 | high := parseFloat(fields[4]) 120 | low := parseFloat(fields[5]) 121 | var vol int64 = 0 122 | if len(fields) > 8 { 123 | vol = parseInt64(fields[8]) 124 | } 125 | tstr := "" 126 | if len(fields) >= 32 { 127 | tstr = fields[30] + " " + fields[31] 128 | } 129 | q := Quote{Code: code, Name: name, Price: price, PrevClose: prev, Open: open, High: high, Low: low, Volume: vol, Time: tstr} 130 | quotes = append(quotes, q) 131 | } 132 | return quotes, nil 133 | } 134 | // 广播消息给所有客户端 135 | func StartPolling(symbols []string, interval time.Duration) { 136 | go func() { 137 | ticker := time.NewTicker(interval) 138 | defer ticker.Stop() 139 | for { 140 | for i := 0; i < len(symbols); i += 60 { 141 | j := i + 60 142 | if j > len(symbols) { 143 | j = len(symbols) 144 | } 145 | batch := symbols[i:j] 146 | quotes, err := FetchSinaQuotes(batch) 147 | if err != nil { 148 | log.Println("realtime poll error:", err) 149 | continue 150 | } 151 | parseAndBroadcast(quotes) 152 | } 153 | <-ticker.C 154 | } 155 | }() 156 | } 157 | 158 | // 广播消息给所有客户端 159 | func parseAndBroadcast(quotes []Quote) { 160 | for _, quote := range quotes { 161 | code := quote.Code 162 | Snapshot.mu.Lock() 163 | Snapshot.m[code] = quote 164 | Snapshot.mu.Unlock() 165 | b, _ := json.Marshal(quote) 166 | Broadcast(b) 167 | } 168 | } 169 | 170 | func parseFloat(s string) float64 { 171 | v, _ := strconv.ParseFloat(strings.TrimSpace(s), 64) 172 | return v 173 | } 174 | func parseInt64(s string) int64 { 175 | v, _ := strconv.ParseInt(strings.TrimSpace(s), 10, 64) 176 | return v 177 | } 178 | -------------------------------------------------------------------------------- /backend/web/handlers_strategy.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | 11 | "go-stock-analyzer/backend/storage" 12 | "go-stock-analyzer/backend/strategyexec" 13 | ) 14 | 15 | // POST /api/strategy 保存策略 16 | func SaveStrategyHandler(c *gin.Context) { 17 | var body struct { 18 | Name string `json:"name"` 19 | Desc string `json:"description"` 20 | Code string `json:"code"` 21 | } 22 | if err := c.ShouldBindJSON(&body); err != nil { 23 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"}) 24 | return 25 | } 26 | // SaveStrategyDB 需要 storage 层函数访问 db 27 | id, err := storage.SaveStrategyDB(&storage.Strategy{ 28 | Name: body.Name, Desc: body.Desc, Code: body.Code, 29 | }) 30 | if err != nil { 31 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 32 | return 33 | } 34 | c.JSON(http.StatusOK, gin.H{"id": id}) 35 | } 36 | 37 | // POST /api/strategy/run 运行策略 38 | // body: { "id": optional, "code": optional, "target": "watchlist"|"board:上证主板"|"all" } 39 | func RunStrategyHandler(c *gin.Context) { 40 | var body struct { 41 | ID int64 `json:"id"` 42 | Code string `json:"code"` 43 | Target string `json:"target"` 44 | Days int `json:"days"` 45 | } 46 | if err := c.ShouldBindJSON(&body); err != nil { 47 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"}) 48 | return 49 | } 50 | code := body.Code 51 | if body.ID != 0 && code == "" { 52 | // load from DB 53 | s, err := storage.GetStrategyDB(body.ID) 54 | if err != nil { 55 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 56 | return 57 | } 58 | code = s.Code 59 | } 60 | if code == "" { 61 | c.JSON(http.StatusBadRequest, gin.H{"error": "no code provided"}) 62 | return 63 | } 64 | // determine symbols based on target 65 | symbols := []string{} 66 | if body.Target == "" || body.Target == "watchlist" { 67 | wl, _ := storage.GetWatchlist() // implement GetWatchlistDB that uses storage db 68 | for _, w := range wl { 69 | symbols = append(symbols, w.Symbol) 70 | } 71 | } else if strings.HasPrefix(body.Target, "board:") { 72 | board := strings.TrimPrefix(body.Target, "board:") 73 | list, _, _ := storage.QueryStocks("", board, 0, 10000) 74 | for _, s := range list { 75 | symbols = append(symbols, s.Symbol) 76 | } 77 | } else if body.Target == "all" { 78 | list, _, _ := storage.QueryStocks("", "", 0, 1000000) 79 | for _, s := range list { 80 | symbols = append(symbols, s.Symbol) 81 | } 82 | } else { 83 | // fallback: treat target as comma-separated symbols 84 | symbols = strings.Split(body.Target, ",") 85 | } 86 | 87 | if len(symbols) == 0 { 88 | c.JSON(http.StatusOK, gin.H{"matches": []string{}}) 89 | return 90 | } 91 | 92 | // loader function: use storage.LoadKLines (package-level) - be careful with days default 93 | days := body.Days 94 | if days <= 0 { 95 | days = 120 96 | } 97 | loader := func(sym string, d int) ([]storage.KLine, error) { 98 | return storage.LoadKLines(sym, d) 99 | } 100 | 101 | start := time.Now() 102 | matches, err := strategyexec.ExecuteStrategy(code, symbols, days, loader, strategyexec.DefaultExecConfig) 103 | duration := time.Since(start) 104 | // Save run log optionally (if ID provided) 105 | if body.ID != 0 { 106 | _ = storage.SaveStrategyRunLog(body.ID, body.Target, len(matches), duration.Milliseconds(), "") 107 | } 108 | if err != nil { 109 | c.JSON(http.StatusOK, gin.H{"matches": matches, "error": err.Error(), "duration_ms": duration.Milliseconds()}) 110 | return 111 | } 112 | c.JSON(http.StatusOK, gin.H{"matches": matches, "duration_ms": duration.Milliseconds()}) 113 | } 114 | 115 | // GET /api/strategy/list 查询所有策略 116 | func ListStrategiesHandler(c *gin.Context) { 117 | list, err := storage.ListStrategiesDB() 118 | if err != nil { 119 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 120 | return 121 | } 122 | c.JSON(http.StatusOK, gin.H{"list": list}) 123 | } 124 | 125 | // PUT /api/strategy/:id 修改策略 126 | func UpdateStrategyHandler(c *gin.Context) { 127 | var body struct { 128 | Name string `json:"name"` 129 | Desc string `json:"description"` 130 | Code string `json:"code"` 131 | Author string `json:"author"` 132 | } 133 | idStr := c.Param("id") 134 | if idStr == "" { 135 | c.JSON(http.StatusBadRequest, gin.H{"error": "missing id"}) 136 | return 137 | } 138 | id, err := strconv.ParseInt(idStr, 10, 64) 139 | if err != nil { 140 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) 141 | return 142 | } 143 | if err := c.ShouldBindJSON(&body); err != nil { 144 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"}) 145 | return 146 | } 147 | s := &storage.Strategy{ 148 | ID: id, 149 | Name: body.Name, 150 | Desc: body.Desc, 151 | Code: body.Code, 152 | Author: body.Author, 153 | } 154 | if err := storage.UpdateStrategyDB(s); err != nil { 155 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 156 | return 157 | } 158 | c.JSON(http.StatusOK, gin.H{"id": id}) 159 | } 160 | 161 | // DELETE /api/strategy/:id 删除策略 162 | func DeleteStrategyHandler(c *gin.Context) { 163 | idStr := c.Param("id") 164 | if idStr == "" { 165 | c.JSON(http.StatusBadRequest, gin.H{"error": "missing id"}) 166 | return 167 | } 168 | id, err := strconv.ParseInt(idStr, 10, 64) 169 | if err != nil { 170 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) 171 | return 172 | } 173 | if err := storage.DeleteStrategyDB(id); err != nil { 174 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 175 | return 176 | } 177 | c.JSON(http.StatusOK, gin.H{"id": id}) 178 | } 179 | -------------------------------------------------------------------------------- /frontend/src/views/StrategyListView.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 148 | 149 | -------------------------------------------------------------------------------- /frontend/src/components/TimelineChart.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 238 | 239 | -------------------------------------------------------------------------------- /backend/storage/db.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "time" 7 | 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | var db *sql.DB 12 | 13 | // 股票基本信息 14 | type StockInfo struct { 15 | Symbol string `json:"symbol"` 16 | Code string `json:"code"` 17 | Name string `json:"name"` 18 | Market string `json:"market"` 19 | Board string `json:"board"` 20 | Trade float64 `json:"trade"` 21 | } 22 | // KLine 日 K 线数据及指标 23 | type KLine struct { 24 | Code string `json:"code"` 25 | Date string `json:"date"` 26 | Open float64 `json:"open"` 27 | High float64 `json:"high"` 28 | Low float64 `json:"low"` 29 | Close float64 `json:"close"` 30 | Volume float64 `json:"volume"` 31 | MA5 float64 `json:"ma5"` 32 | MA10 float64 `json:"ma10"` 33 | MA20 float64 `json:"ma20"` 34 | MA30 float64 `json:"ma30"` 35 | DIF float64 `json:"dif"` 36 | DEA float64 `json:"dea"` 37 | MACD float64 `json:"macd"` 38 | } 39 | 40 | // 初始化数据库连接和表 41 | func InitDB(path string) error { 42 | var err error 43 | db, err = sql.Open("sqlite3", path) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | _, _ = db.Exec("PRAGMA journal_mode = WAL;") 49 | _, _ = db.Exec("PRAGMA synchronous = NORMAL;") 50 | 51 | // 股票基本信息表 52 | stocksSQL := `CREATE TABLE IF NOT EXISTS stocks ( 53 | symbol TEXT PRIMARY KEY, 54 | code TEXT, 55 | name TEXT, 56 | market TEXT, 57 | board TEXT, 58 | trade REAL, 59 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 60 | );` 61 | if _, err = db.Exec(stocksSQL); err != nil { 62 | return err 63 | } 64 | // 自选股表 65 | watchSQL := `CREATE TABLE IF NOT EXISTS watchlist ( 66 | id INTEGER PRIMARY KEY AUTOINCREMENT, 67 | symbol TEXT UNIQUE, 68 | name TEXT, 69 | added_at DATETIME DEFAULT CURRENT_TIMESTAMP 70 | );` 71 | if _, err = db.Exec(watchSQL); err != nil { 72 | return err 73 | } 74 | // K线数据表 75 | klineSQL := `CREATE TABLE IF NOT EXISTS kline ( 76 | code TEXT, date TEXT, open REAL, close REAL, high REAL, low REAL, volume REAL, 77 | ma5 REAL, ma10 REAL, ma20 REAL, ma30 REAL, 78 | dif REAL, dea REAL, macd REAL, 79 | PRIMARY KEY(code,date) 80 | );` 81 | if _, err = db.Exec(klineSQL); err != nil { 82 | return err 83 | } 84 | // 策略结果表 85 | resultsSQL := `CREATE TABLE IF NOT EXISTS results ( 86 | code TEXT, date TEXT, strategy TEXT, 87 | PRIMARY KEY(code,date,strategy) 88 | );` 89 | if _, err = db.Exec(resultsSQL); err != nil { 90 | return err 91 | } 92 | 93 | err = InitStrategyTable() 94 | if err != nil { 95 | return err 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // SaveStocks 批量保存股票基本信息 102 | func SaveStocks(list []StockInfo) error { 103 | tx, err := db.Begin() 104 | if err != nil { 105 | return err 106 | } 107 | stmt, err := tx.Prepare(`INSERT OR REPLACE INTO stocks(symbol,code,name,market,board,trade,updated_at) VALUES(?,?,?,?,?,?,?)`) 108 | if err != nil { 109 | return err 110 | } 111 | defer stmt.Close() 112 | now := time.Now().Format("2006-01-02 15:04:05") 113 | for _, s := range list { 114 | if _, err := stmt.Exec(s.Symbol, s.Code, s.Name, s.Market, s.Board, s.Trade, now); err != nil { 115 | tx.Rollback() 116 | return err 117 | } 118 | } 119 | return tx.Commit() 120 | } 121 | 122 | // CountStocksByBoards 返回指定板块的股票数量 123 | func CountStocksByBoards() (int, error) { 124 | row := db.QueryRow(`SELECT COUNT(*) FROM stocks WHERE board IN ('上证主板','深证主板','创业板','科创板')`) 125 | var n int 126 | if err := row.Scan(&n); err != nil { 127 | return 0, err 128 | } 129 | return n, nil 130 | } 131 | 132 | // QueryStocks 按条件分页查询股票列表 133 | func QueryStocks(keyword, board string, offset, limit int) ([]StockInfo, int, error) { 134 | args := []interface{}{} 135 | where := " WHERE 1=1 " 136 | if board != "" { 137 | where += " AND board = ? " 138 | args = append(args, board) 139 | } else { 140 | where += " AND board IN ('上证主板','深证主板','创业板','科创板') " 141 | } 142 | if keyword != "" { 143 | where += " AND (name LIKE ? OR code LIKE ? OR symbol LIKE ?) " 144 | kw := "%" + keyword + "%" 145 | args = append(args, kw, kw, kw) 146 | } 147 | countSQL := "SELECT COUNT(*) FROM stocks " + where 148 | var total int 149 | if err := db.QueryRow(countSQL, args...).Scan(&total); err != nil { 150 | return nil, 0, err 151 | } 152 | querySQL := "SELECT symbol,code,name,market,board,trade FROM stocks " + where + " ORDER BY code LIMIT ? OFFSET ?" 153 | args = append(args, limit, offset) 154 | rows, err := db.Query(querySQL, args...) 155 | if err != nil { 156 | return nil, 0, err 157 | } 158 | defer rows.Close() 159 | out := []StockInfo{} 160 | for rows.Next() { 161 | var s StockInfo 162 | if err := rows.Scan(&s.Symbol, &s.Code, &s.Name, &s.Market, &s.Board, &s.Trade); err != nil { 163 | return nil, 0, err 164 | } 165 | out = append(out, s) 166 | } 167 | return out, total, nil 168 | } 169 | 170 | // 自选股票 171 | type WatchStock struct { 172 | Symbol string `json:"symbol"` 173 | Name string `json:"name"` 174 | AddedAt string `json:"added_at"` 175 | } 176 | // 添加自选股 177 | func AddToWatchlist(symbol, name string) error { 178 | _, err := db.Exec("INSERT OR IGNORE INTO watchlist(symbol,name) VALUES(?,?)", symbol, name) 179 | return err 180 | } 181 | // 移除自选股 182 | func RemoveFromWatchlist(symbol string) error { 183 | _, err := db.Exec("DELETE FROM watchlist WHERE symbol = ?", symbol) 184 | return err 185 | } 186 | // 获取自选股列表 187 | func GetWatchlist() ([]WatchStock, error) { 188 | rows, err := db.Query("SELECT symbol,name,added_at FROM watchlist ORDER BY added_at DESC") 189 | if err != nil { 190 | return nil, err 191 | } 192 | defer rows.Close() 193 | out := []WatchStock{} 194 | for rows.Next() { 195 | var s WatchStock 196 | if err := rows.Scan(&s.Symbol, &s.Name, &s.AddedAt); err != nil { 197 | return nil, err 198 | } 199 | out = append(out, s) 200 | } 201 | return out, nil 202 | } 203 | 204 | // 保存K线数据 205 | func SaveKLines(code string, klines []KLine) error { 206 | tx, err := db.Begin() 207 | if err != nil { 208 | return err 209 | } 210 | stmt, err := tx.Prepare(`INSERT OR REPLACE INTO kline(code,date,open,close,high,low,volume,ma5,ma10,ma20,ma30,dif,dea,macd) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`) 211 | if err != nil { 212 | return err 213 | } 214 | defer stmt.Close() 215 | for _, k := range klines { 216 | if _, err := stmt.Exec(k.Code, k.Date, k.Open, k.Close, k.High, k.Low, k.Volume, k.MA5, k.MA10, k.MA20, k.MA30, k.DIF, k.DEA, k.MACD); err != nil { 217 | tx.Rollback() 218 | return err 219 | } 220 | } 221 | return tx.Commit() 222 | } 223 | // LoadKLines 加载指定股票的最近 N 天 K 线数据 224 | func LoadKLines(code string, days int) ([]KLine, error) { 225 | rows, err := db.Query("SELECT date,open,high,low,close,volume,ma5,ma10,ma20,ma30,dif,dea,macd FROM kline WHERE code=? ORDER BY date ASC LIMIT ?", code, days) 226 | if err != nil { 227 | return nil, err 228 | } 229 | defer rows.Close() 230 | res := []KLine{} 231 | for rows.Next() { 232 | var k KLine 233 | if err := rows.Scan(&k.Date, &k.Open, &k.High, &k.Low, &k.Close, &k.Volume, &k.MA5, &k.MA10, &k.MA20, &k.MA30, &k.DIF, &k.DEA, &k.MACD); err != nil { 234 | return nil, err 235 | } 236 | k.Code = code 237 | res = append(res, k) 238 | } 239 | return res, nil 240 | } 241 | 242 | // SaveResult 保存自动选股结果 243 | func SaveResult(code, date, strategy string) error { 244 | _, err := db.Exec("INSERT OR REPLACE INTO results(code,date,strategy) VALUES (?,?,?)", code, date, strategy) 245 | return err 246 | } 247 | 248 | func QueryAllBoards() []string { 249 | return []string{"上证主板", "深证主板", "创业板", "科创板"} 250 | } 251 | 252 | // Utility 253 | func ExecSQL(s string) error { 254 | _, err := db.Exec(s) 255 | return err 256 | } 257 | 258 | // For debug: dump count 259 | func DumpCounts() { 260 | var n int 261 | _ = db.QueryRow("SELECT COUNT(*) FROM stocks").Scan(&n) 262 | log.Println("stocks:", n) 263 | _ = db.QueryRow("SELECT COUNT(*) FROM watchlist").Scan(&n) 264 | log.Println("watchlist:", n) 265 | } 266 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= 2 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 3 | github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= 4 | github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= 5 | github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= 6 | github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 7 | github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= 8 | github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 13 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 14 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 15 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 16 | github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= 17 | github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 20 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 21 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 24 | github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= 25 | github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 26 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 27 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 28 | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 29 | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 30 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 31 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 32 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 34 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 35 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 36 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 37 | github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= 38 | github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 39 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 40 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 41 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 42 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 43 | github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= 44 | github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 45 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 46 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 47 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 48 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 49 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 50 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 51 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 52 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 53 | github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= 54 | github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= 55 | github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= 56 | github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= 57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 58 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 59 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 60 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 61 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 62 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 63 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 64 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 65 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 66 | github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E= 67 | github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY= 68 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 69 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 70 | github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= 71 | github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= 72 | github.com/yalue/onnxruntime_go v1.21.0 h1:DdtvfY7OP5gR8mwPDqAOAQckf+KcI30hPNJL8hQaYWI= 73 | github.com/yalue/onnxruntime_go v1.21.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4= 74 | go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 75 | go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 76 | golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= 77 | golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 78 | golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= 79 | golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= 80 | golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= 81 | golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 82 | golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= 83 | golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= 84 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 85 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 86 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 87 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 88 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 89 | golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= 90 | golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= 91 | golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= 92 | golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= 93 | google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= 94 | google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 95 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 96 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 97 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 98 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 99 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 100 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 101 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-stock-analyzer 2 | 3 | 轻量级 A 股数据抓取、指标计算、策略回测/调度与实时推送示例工程。 4 | 5 | 本仓库以 Go 作为后端、Vue(Vite)作为前端,使用 SQLite 做为单文件数据库,演示了从数据抓取到策略执行和 Web / WebSocket 推送的完整流程。适合作为学习、二次开发或构建原型的起点。 6 | 7 | 详细的内容介绍全在微信公众号中。干货持续更新,敬请关注「代码扳手」微信公众号: 8 | image 9 | 10 | ## 目录结构(摘录) 11 | 12 | - `backend/`:后端 Go 服务代码(入口:`backend/main.go`) 13 | - `config/`:配置加载(`config.yaml`, `config.go`) 14 | - `fetcher/`:外部行情抓取逻辑(Sina API)、指标计算 15 | - `storage/`:SQLite 初始化与 CRUD(`db.go`) 16 | - `strategy/`:示例策略(MA、MACD、DSL、Composite) 17 | - `strategyexec/`:动态策略执行引擎(基于 yaegi 解释器) 18 | - `scheduler/`:定时任务调度(拉取 K 线并触发策略) 19 | - `realtime/`:WebSocket Hub 与 polling 广播逻辑 20 | - `web/`:HTTP API 路由与处理器 21 | - `stock.db`:示例 SQLite 数据库文件(运行时生成/更新) 22 | 23 | - `frontend/`:Vue + Vite 前端代码(简单 UI,连接后端 REST + WS) 24 | - `src/`:组件与页面(`StockChart.vue`、`WatchlistView.vue` 等) 25 | 26 | 根目录包含 `go.mod` / `go.sum`,以及 `bin/backend`(构建产物示例)。 27 | 28 | ## 项目概述(一句话) 29 | 30 | 后端定期从新浪等公开接口抓取股票列表和 K 线数据,计算常见技术指标(MA、MACD 等),把结果存入 `SQLite`,同时通过 WebSocket 将实时行情/策略信号推送给前端。前端使用 Vite + Vue 显示行情、K 线与策略回测/结果。 31 | 32 | ## 快速上手(开发环境,推荐使用 WSL / Linux) 33 | 34 | 先决条件:Go 1.18+、Node.js (建议 16+)、npm、gcc(构建 sqlite3 cgo 时需要)。在 Windows 上建议通过 WSL 运行后端以避免 CGO 问题。 35 | 36 | 1) 克隆仓库(已在本 workspace) 37 | 38 | 2) 启动后端(开发模式) 39 | 40 | ```bash 41 | # 进入仓库根目录(WSL) 42 | cd /mnt/d/2025/go-stock-analyzer 43 | # 直接运行(会读取 backend/config/config.yaml) 44 | go run ./backend 45 | ``` 46 | 47 | 说明:后端入口 `backend/main.go` 启动流程:加载配置 -> 初始化 DB -> 若 `stocks` 表条目少于阈值则调用 `fetcher.FetchAllStocks()` 做全量抓取 -> 启动 realtime poll -> 启动每日 scheduler -> 启动 Gin HTTP 服务(默认 :8080)。 48 | 49 | 3) 构建后端二进制 50 | 51 | ```bash 52 | go build -o ./bin/backend ./backend 53 | ./bin/backend 54 | ``` 55 | 56 | 注意:`github.com/mattn/go-sqlite3` 依赖 CGO,构建时需启用 CGO 并在系统安装 C 编译工具链(如 `gcc`)。在 WSL/Ubuntu 上可以使用 `sudo apt update && sudo apt install build-essential`。 57 | 58 | 4) 启动前端(开发模式) 59 | 60 | ```bash 61 | cd frontend 62 | npm install 63 | npm run dev 64 | ``` 65 | 66 | 前端默认会连接后端提供的 REST 和 WebSocket: 67 | - REST 示例:`/api/stocks`, `/api/watchlist`, `/api/kline` 68 | - WebSocket:`/ws/realtime`(客户端按 `Quote.Code` 订阅) 69 | 70 | 如果想让后端提供前端生产静态资源,先在 `frontend` 中执行 `npm run build`,然后把 `dist` 放到 `frontend/dist`,后端在 `backend/web/server.go` 中有将 `../frontend/dist` 暴露为静态文件的路由。 71 | 72 | ## 核心实现要点(开发者速览) 73 | 74 | - 抓取(backend/fetcher) 75 | - `stock_list.go`:抓取并解析股票列表,生成 `symbol`(示例:`sz000001` / `sh600000`) 76 | - `fetcher.go`:按 symbol 拉取 K 线数据并解析,抓取后会计算部分指标(MA/MACD)以便策略使用 77 | 78 | - 存储(backend/storage/db.go) 79 | - 初始化 schema(tables: `stocks`, `kline`, `watchlist`, `results` 等) 80 | - 统一使用 `INSERT OR REPLACE` 做 upsert 81 | - 已为性能做了 PRAGMA 调优(WAL、synchronous NORMAL) 82 | 83 | - 策略(backend/strategy) 84 | - 提供多种策略实现:`ma_strategy.go`, `macd_strategy.go`, `dsl_strategy.go`, `composite_strategy.go` 85 | - DSL 使用 `github.com/Knetic/govaluate` 解析表达式,可在前端或配置里输入简单逻辑表达式进行回测 86 | 87 | - 调度(backend/scheduler/scheduler.go) 88 | - 按 `config.yaml` 中设置的时间周期拉取最新 K 线并触发 `strategy.RunAll` 89 | 90 | - 实时推送(backend/realtime) 91 | - WebSocket Hub(`hub.go`)管理连接,`fetcher.go` 负责轮询并把最新 Quote 广播给订阅的客户端 92 | 93 | ## 常见问题与排查 94 | 95 | - 后端构建失败(通常是 sqlite3/CGO 相关) 96 | - 错误信息通常包含 `gcc`、`cgo` 或 `sqlite3`,在 WSL 上安装 `build-essential` 并确保 `CGO_ENABLED=1` 通常可解决。 97 | 98 | - 数据不够或全量抓取没跑 99 | - 后端在 `InitDB` 时会检查 `stocks` 表条目,若少于配置阈值会调用 `fetcher.FetchAllStocks()`。手动触发可以临时修改或调用相应函数进行测试(开发时直接在 `main.go` 添加触发点)。 100 | 101 | - 外部 API 不稳定 102 | - 抓取模块对 Sina 接口的解析有一定容错(空判断等)。遇到接口变更时优先调整 `backend/fetcher/*` 中的解析逻辑。 103 | 104 | ## 可扩展项 / 下一步建议 105 | 106 | - 增加单元测试与集成测试:当前为演示工程,建议为关键模块(`fetcher`、`storage`、`strategy`)补充自动化测试。 107 | - 把 SQLite 替换为可选的持久层(Postgres / MySQL),并提供迁移脚本。 108 | - 优化抓取并发与限频策略(加入重试/退避、rate limiter)。 109 | - 增强前端交互:策略编辑器、回测可视化、自定义 watchlist 管理。 110 | - 扩展动态策略执行: 111 | - 为策略提供更多内置函数(技术指标、统计函数) 112 | - 支持策略之间的组合与引用 113 | - 添加策略性能分析与优化建议 114 | - 提供更多策略模板与示例 115 | 116 | ## 贡献 117 | 118 | 欢迎 Issue / PR。请保持 commit 小而专注,确保修改伴随必要的说明或测试用例。 119 | 120 | ## 许可证 121 | 122 | 仓库默认无明确 LICENSE 文件 — 如需对外开源请添加合适的 LICENSE(推荐 MIT / Apache-2.0)。 123 | 124 | --- 125 | 126 | 作者:本仓库为示例/教学用途,主要维护者信息见仓库提交历史。 127 | 128 | ## 详细示例 129 | 130 | 下列示例旨在快速帮助你复制环境、调试后端、调用 API 与使用策略 DSL(可直接复制到终端 / 客户端进行测试)。 131 | 132 | ### 后端配置示例(`backend/config/config.yaml`) 133 | 下面是一个合理的配置示例;仓库中的 `config.yaml` 可能略有不同,但字段含义相同: 134 | 135 | ```yaml 136 | # 数据库文件(相对 backend 路径或绝对路径) 137 | db_path: "backend/stock.db" 138 | 139 | # 抓取与调度设置 140 | update_hour: 15 # 每日触发策略的小时(24h) 141 | update_minute: 10 # 每日触发策略的分钟 142 | watchlist_poll_interval_seconds: 5 # 实时推送 polling 间隔(秒) 143 | kline_days: 200 # 抓取历史 K 线天数 144 | 145 | # Web 服务 146 | http_port: 8080 147 | 148 | # 抓取并发与限频(示例) 149 | fetch_workers: 4 150 | fetch_rate_limit_per_second: 2 151 | ``` 152 | 153 | ### 常用环境变量(构建/运行) 154 | 155 | - 在 Windows/WSL 上构建时为 sqlite3 启用 CGO(确保已安装 gcc): 156 | 157 | ```bash 158 | # 在 WSL/Ubuntu 下 159 | export CGO_ENABLED=1 160 | go build -o ./bin/backend ./backend 161 | ``` 162 | 163 | ### REST API 示例 164 | 165 | 1) 获取股票列表(分页/简单示例) 166 | 167 | ```bash 168 | curl -s "http://localhost:8080/api/stocks" 169 | ``` 170 | 171 | 示例响应(JSON): 172 | 173 | ```json 174 | [ 175 | { 176 | "symbol": "sz000001", 177 | "code": "000001", 178 | "name": "平安银行", 179 | "market": "sz", 180 | "board": "深证主板" 181 | }, 182 | ... 183 | ] 184 | ``` 185 | 186 | 2) 获取单支股票的 K 线(按天) 187 | 188 | ```bash 189 | curl -s "http://localhost:8080/api/kline?symbol=sz000001&days=60" 190 | ``` 191 | 192 | 示例响应(结构化,省略部分字段): 193 | 194 | ```json 195 | { 196 | "symbol": "sz000001", 197 | "kline": [ 198 | {"date":"2025-10-01","open":10.5,"high":11.0,"low":10.3,"close":10.9,"volume":1234567,"ma5":10.2,"ma10":9.8}, 199 | ... 200 | ] 201 | } 202 | ``` 203 | 204 | 3) Watchlist 与策略结果 205 | 206 | ```bash 207 | curl -s "http://localhost:8080/api/watchlist" 208 | curl -s "http://localhost:8080/api/results?symbol=sz000001" 209 | ``` 210 | 211 | ### WebSocket(实时推送)示例 212 | 213 | 后端 WebSocket 路径:`ws://localhost:8080/ws/realtime` 214 | 215 | 客户端连接后通常先发送一条订阅消息(这里以 JSON 为例,后端实现会根据 `Quote.Code` 分配订阅) 216 | 217 | 示例订阅消息: 218 | 219 | ```json 220 | { "action": "subscribe", "symbol": "sz000001" } 221 | ``` 222 | 223 | 示例服务器推送(行情/策略信号混合): 224 | 225 | ```json 226 | { 227 | "type": "quote", 228 | "symbol": "sz000001", 229 | "price": 10.92, 230 | "volume": 120000, 231 | "time": "2025-10-05T15:10:02+08:00" 232 | } 233 | 234 | { 235 | "type": "signal", 236 | "symbol": "sz000001", 237 | "strategy": "ma_cross", 238 | "signal": "buy", 239 | "meta": {"ma_short":5,"ma_long":20} 240 | } 241 | ``` 242 | 243 | 注意:前端实现里 `backend/realtime/client.go` 与 `hub.go` 负责订阅管理与广播,实际字段名与格式请以代码为准。 244 | 245 | ### 数据库(SQLite)schema 快照 246 | 247 | 下面给出常见表的简化字段,具体定义在 `backend/storage/db.go` 中(InitDB): 248 | 249 | - `stocks`: 250 | - `symbol` TEXT PRIMARY KEY 251 | - `code` TEXT 252 | - `name` TEXT 253 | - `market` TEXT 254 | - `board` TEXT 255 | 256 | - `kline`:按日/分钟存储(示例为日线) 257 | - `symbol` TEXT 258 | - `date` TEXT (YYYY-MM-DD) 259 | - `open` REAL 260 | - `high` REAL 261 | - `low` REAL 262 | - `close` REAL 263 | - `volume` INTEGER 264 | - `ma5` REAL 265 | - `ma10` REAL 266 | - `macd` REAL 267 | - PRIMARY KEY (`symbol`, `date`) 268 | 269 | - `watchlist`:订阅或关注的 symbol 列表 270 | - `id` INTEGER PRIMARY KEY 271 | - `symbol` TEXT 272 | 273 | - `results`:策略运行结果/信号 274 | - `id` INTEGER PRIMARY KEY 275 | - `symbol` TEXT 276 | - `strategy` TEXT 277 | - `signal` TEXT 278 | - `timestamp` TEXT 279 | - `payload` TEXT (JSON) 280 | 281 | 可以用 `sqlite3 backend/stock.db` 或 `sqlitebrowser` 打开并检查表数据;常用 SQL: 282 | 283 | ```sql 284 | -- 最近 10 条 kline 285 | SELECT * FROM kline WHERE symbol='sz000001' ORDER BY date DESC LIMIT 10; 286 | 287 | -- 查看 watchlist 288 | SELECT * FROM watchlist; 289 | ``` 290 | 291 | ### DSL 策略示例与回测 292 | 293 | 仓库中的 DSL 策略基于 `govaluate` 表达式解析器,示例表达式: 294 | 295 | ``` 296 | ma5 > ma10 && volume > sma(volume, 20) * 1.5 297 | ``` 298 | 299 | 含义:5 日均线上穿 10 日均线,且当前成交量大于 20 日均量的 1.5 倍。 300 | 301 | 回测流程(简化) 302 | 303 | 1. 在前端 DSL 编辑器输入表达式并保存。 304 | 2. 后端 `strategy.RunAll` 会读取 DSL 策略并对 `kline` 数据序列逐日评估表达式(实现细节在 `backend/strategy/dsl_strategy.go`)。 305 | 3. 将命中的信号写入 `results` 表,并(可选)通过 WebSocket 推送给在线客户端。 306 | 307 | 示例:在前端提供回测按钮会触发 API(或在本地调用策略函数)生成回测报告,报告包含命中日期、收益统计与基本可视化数据。 308 | 309 | ### 动态策略执行(Go 源码) 310 | 311 | 除了 DSL 表达式,系统还支持直接执行 Go 源码形式的策略。这种方式更灵活,可以使用完整的 Go 语言特性,适合复杂策略的实现。 312 | 313 | 策略需要实现以下函数签名(在前端编辑器或配置中提供源码): 314 | 315 | ```go 316 | // Match 函数接收股票代码和 K 线数据,返回是否满足策略条件 317 | func Match(symbol string, klines []map[string]interface{}) bool { 318 | // 在这里实现策略逻辑 319 | if len(klines) < 20 { 320 | return false 321 | } 322 | 323 | // 示例:判断最近收盘价是否连续上涨 324 | latest := klines[len(klines)-1]["Close"].(float64) 325 | prev := klines[len(klines)-2]["Close"].(float64) 326 | prevPrev := klines[len(klines)-3]["Close"].(float64) 327 | 328 | return latest > prev && prev > prevPrev 329 | } 330 | ``` 331 | 332 | 系统使用 yaegi 解释器在安全的环境中执行策略代码。主要特点: 333 | 334 | 1. 超时控制 335 | - 总执行超时(默认 30s) 336 | - 每只股票超时(默认 800ms) 337 | 338 | 2. 安全性 339 | - 代码在隔离环境中执行 340 | - 运行时错误不会影响主程序 341 | - panic 会被捕获并跳过当前股票 342 | 343 | 3. 上下文与数据 344 | - K 线数据以 map 形式提供 345 | - 支持访问标准库函数 346 | - 可以在策略中实现技术指标计算 347 | 348 | 使用示例: 349 | 350 | ```go 351 | code := ` 352 | func Match(symbol string, klines []map[string]interface{}) bool { 353 | // 你的策略逻辑 354 | return true 355 | } 356 | ` 357 | matches, err := strategyexec.ExecuteStrategy(code, symbols, 60, storage.GetKLines, strategyexec.DefaultExecConfig) 358 | ``` 359 | 360 | 注意:动态执行会比预编译策略稍慢,建议在回测场景使用,实时信号生成优先使用预编译策略。 361 | 362 | ### 常用调试命令与快速检查 363 | 364 | - 检查后端是否成功监听端口(Linux/WSL): 365 | 366 | ```bash 367 | ss -ltnp | grep 8080 368 | ``` 369 | 370 | - 在没有前端时手动发送一条测试 WebSocket(可用 websocat 或 node) 371 | 372 | ```bash 373 | # 安装 websocat(Linux) 374 | sudo apt install websocat 375 | websocat ws://localhost:8080/ws/realtime 376 | ``` 377 | 378 | - 清理并重建 DB(慎用,会丢数据): 379 | 380 | ```bash 381 | rm -f backend/stock.db 382 | go run ./backend 383 | ``` 384 | 385 | ### 调试与常见错误定位 386 | 387 | - 若遇到 `database is locked`:查看是否有多个进程访问同一 sqlite 文件并发写入,或 WAL 模式未正确生效。 388 | - 若抓取返回空数据:检查外部 API 是否可访问(网络问题或接口变更),可在 `backend/fetcher` 中增加更严格的日志以定位解析失败行。 389 | 390 | ## 总结 391 | 392 | 本文档补充了配置样例、REST/WS 使用样例、数据库 schema 快照、DSL 策略示例与常用调试命令,便于复制运行和快速调试代码。若你希望我把其中的某些示例变成可运行的脚本(例如自动化回测脚本、短小的 Postman 集合或前端 Mock),我可以继续实现并在仓库中添加对应文件。 393 | -------------------------------------------------------------------------------- /frontend/src/views/StockDetailView.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 372 | 373 | 406 | -------------------------------------------------------------------------------- /predict/data/stock_history.csv: -------------------------------------------------------------------------------- 1 | Date,Open,High,Low,Close,Volume 2 | 2023/1/1,100,102,99,101,100000 3 | 2023/1/2,101,103,100,102,110000 4 | 2023/1/3,102,104,101,103,120000 5 | 2023/1/4,103,105,102,104,130000 6 | 2023/1/5,104,106,103,105,140000 7 | 2023/1/6,105,107,104,106,150000 8 | 2023/1/7,106,108,105,107,160000 9 | 2023/1/8,107,109,106,108,170000 10 | 2023/1/9,108,110,107,109,180000 11 | 2023/1/10,109,111,108,110,190000 12 | 2023/1/11,110,112,109,111,200000 13 | 2023/1/12,111,113,110,112,210000 14 | 2023/1/13,112,114,111,113,220000 15 | 2023/1/14,113,115,112,114,230000 16 | 2023/1/15,114,116,113,115,240000 17 | 2023/1/16,115,117,114,116,250000 18 | 2023/1/17,116,118,115,117,260000 19 | 2023/1/18,117,119,116,118,270000 20 | 2023/1/19,118,120,117,119,280000 21 | 2023/1/20,119,121,118,120,290000 22 | 2023/1/21,120,122,119,121,300000 23 | 2023/1/22,121,123,120,122,310000 24 | 2023/1/23,122,124,121,123,320000 25 | 2023/1/24,123,125,122,124,330000 26 | 2023/1/25,124,126,123,125,340000 27 | 2023/1/26,125,127,124,126,350000 28 | 2023/1/27,126,128,125,127,360000 29 | 2023/1/28,127,129,126,128,370000 30 | 2023/1/29,128,130,127,129,380000 31 | 2023/1/30,129,131,128,130,390000 32 | 2023/1/31,130,132,129,131,400000 33 | 2023/2/1,131,133,130,132,410000 34 | 2023/2/2,132,134,131,133,420000 35 | 2023/2/3,133,135,132,134,430000 36 | 2023/2/4,134,136,133,135,440000 37 | 2023/2/5,135,137,134,136,450000 38 | 2023/2/6,136,138,135,137,460000 39 | 2023/2/7,137,139,136,138,470000 40 | 2023/2/8,138,140,137,139,480000 41 | 2023/2/9,139,141,138,140,490000 42 | 2023/2/10,140,142,139,141,500000 43 | 2023/2/11,141,143,140,142,510000 44 | 2023/2/12,142,144,141,143,520000 45 | 2023/2/13,143,145,142,144,530000 46 | 2023/2/14,144,146,143,145,540000 47 | 2023/2/15,145,147,144,146,550000 48 | 2023/2/16,146,148,145,147,560000 49 | 2023/2/17,147,149,146,148,570000 50 | 2023/2/18,148,150,147,149,580000 51 | 2023/2/19,149,151,148,150,590000 52 | 2023/2/20,150,152,149,151,600000 53 | 2023/2/21,151,153,150,152,610000 54 | 2023/2/22,152,154,151,153,620000 55 | 2023/2/23,153,155,152,154,630000 56 | 2023/2/24,154,156,153,155,640000 57 | 2023/2/25,155,157,154,156,650000 58 | 2023/2/26,156,158,155,157,660000 59 | 2023/2/27,157,159,156,158,670000 60 | 2023/2/28,158,160,157,159,680000 61 | 2023/3/1,159,161,158,160,690000 62 | 2023/3/2,160,162,159,161,700000 63 | 2023/3/3,161,163,160,162,710000 64 | 2023/3/4,162,164,161,163,720000 65 | 2023/3/5,163,165,162,164,730000 66 | 2023/3/6,164,166,163,165,740000 67 | 2023/3/7,165,167,164,166,750000 68 | 2023/3/8,166,168,165,167,760000 69 | 2023/3/9,167,169,166,168,770000 70 | 2023/3/10,168,170,167,169,780000 71 | 2023/3/11,169,171,168,170,790000 72 | 2023/3/12,170,172,169,171,800000 73 | 2023/3/13,171,173,170,172,810000 74 | 2023/3/14,172,174,171,173,820000 75 | 2023/3/15,173,175,172,174,830000 76 | 2023/3/16,174,176,173,175,840000 77 | 2023/3/17,175,177,174,176,850000 78 | 2023/3/18,176,178,175,177,860000 79 | 2023/3/19,177,179,176,178,870000 80 | 2023/3/20,178,180,177,179,880000 81 | 2023/3/21,179,181,178,180,890000 82 | 2023/3/22,180,182,179,181,900000 83 | 2023/3/23,181,183,180,182,910000 84 | 2023/3/24,182,184,181,183,920000 85 | 2023/3/25,183,185,182,184,930000 86 | 2023/3/26,184,186,183,185,940000 87 | 2023/3/27,185,187,184,186,950000 88 | 2023/3/28,186,188,185,187,960000 89 | 2023/3/29,187,189,186,188,970000 90 | 2023/3/30,188,190,187,189,980000 91 | 2023/3/31,189,191,188,190,990000 92 | 2023/4/1,190,192,189,191,1000000 93 | 2023/4/2,191,193,190,192,1010000 94 | 2023/4/3,192,194,191,193,1020000 95 | 2023/4/4,193,195,192,194,1030000 96 | 2023/4/5,194,196,193,195,1040000 97 | 2023/4/6,195,197,194,196,1050000 98 | 2023/4/7,196,198,195,197,1060000 99 | 2023/4/8,197,199,196,198,1070000 100 | 2023/4/9,198,200,197,199,1080000 101 | 2023/4/10,199,201,198,200,1090000 102 | 2023/4/11,200,202,199,201,1100000 103 | 2023/4/12,201,203,200,202,1110000 104 | 2023/4/13,202,204,201,203,1120000 105 | 2023/4/14,203,205,202,204,1130000 106 | 2023/4/15,204,206,203,205,1140000 107 | 2023/4/16,205,207,204,206,1150000 108 | 2023/4/17,206,208,205,207,1160000 109 | 2023/4/18,207,209,206,208,1170000 110 | 2023/4/19,208,210,207,209,1180000 111 | 2023/4/20,209,211,208,210,1190000 112 | 2023/4/21,210,212,209,211,1200000 113 | 2023/4/22,211,213,210,212,1210000 114 | 2023/4/23,212,214,211,213,1220000 115 | 2023/4/24,213,215,212,214,1230000 116 | 2023/4/25,214,216,213,215,1240000 117 | 2023/4/26,215,217,214,216,1250000 118 | 2023/4/27,216,218,215,217,1260000 119 | 2023/4/28,217,219,216,218,1270000 120 | 2023/4/29,218,220,217,219,1280000 121 | 2023/4/30,219,221,218,220,1290000 122 | 2023/5/1,220,222,219,221,1300000 123 | 2023/5/2,221,223,220,222,1310000 124 | 2023/5/3,222,224,221,223,1320000 125 | 2023/5/4,223,225,222,224,1330000 126 | 2023/5/5,224,226,223,225,1340000 127 | 2023/5/6,225,227,224,226,1350000 128 | 2023/5/7,226,228,225,227,1360000 129 | 2023/5/8,227,229,226,228,1370000 130 | 2023/5/9,228,230,227,229,1380000 131 | 2023/5/10,229,231,228,230,1390000 132 | 2023/5/11,230,232,229,231,1400000 133 | 2023/5/12,231,233,230,232,1410000 134 | 2023/5/13,232,234,231,233,1420000 135 | 2023/5/14,233,235,232,234,1430000 136 | 2023/5/15,234,236,233,235,1440000 137 | 2023/5/16,235,237,234,236,1450000 138 | 2023/5/17,236,238,235,237,1460000 139 | 2023/5/18,237,239,236,238,1470000 140 | 2023/5/19,238,240,237,239,1480000 141 | 2023/5/20,239,241,238,240,1490000 142 | 2023/5/21,240,242,239,241,1500000 143 | 2023/5/22,241,243,240,242,1510000 144 | 2023/5/23,242,244,241,243,1520000 145 | 2023/5/24,243,245,242,244,1530000 146 | 2023/5/25,244,246,243,245,1540000 147 | 2023/5/26,245,247,244,246,1550000 148 | 2023/5/27,246,248,245,247,1560000 149 | 2023/5/28,247,249,246,248,1570000 150 | 2023/5/29,248,250,247,249,1580000 151 | 2023/5/30,249,251,248,250,1590000 152 | 2023/5/31,250,252,249,251,1600000 153 | 2023/6/1,251,253,250,252,1610000 154 | 2023/6/2,252,254,251,253,1620000 155 | 2023/6/3,253,255,252,254,1630000 156 | 2023/6/4,254,256,253,255,1640000 157 | 2023/6/5,255,257,254,256,1650000 158 | 2023/6/6,256,258,255,257,1660000 159 | 2023/6/7,257,259,256,258,1670000 160 | 2023/6/8,258,260,257,259,1680000 161 | 2023/6/9,259,261,258,260,1690000 162 | 2023/6/10,260,262,259,261,1700000 163 | 2023/6/11,261,263,260,262,1710000 164 | 2023/6/12,262,264,261,263,1720000 165 | 2023/6/13,263,265,262,264,1730000 166 | 2023/6/14,264,266,263,265,1740000 167 | 2023/6/15,265,267,264,266,1750000 168 | 2023/6/16,266,268,265,267,1760000 169 | 2023/6/17,267,269,266,268,1770000 170 | 2023/6/18,268,270,267,269,1780000 171 | 2023/6/19,269,271,268,270,1790000 172 | 2023/6/20,270,272,269,271,1800000 173 | 2023/6/21,271,273,270,272,1810000 174 | 2023/6/22,272,274,271,273,1820000 175 | 2023/6/23,273,275,272,274,1830000 176 | 2023/6/24,274,276,273,275,1840000 177 | 2023/6/25,275,277,274,276,1850000 178 | 2023/6/26,276,278,275,277,1860000 179 | 2023/6/27,277,279,276,278,1870000 180 | 2023/6/28,278,280,277,279,1880000 181 | 2023/6/29,279,281,278,280,1890000 182 | 2023/6/30,280,282,279,281,1900000 183 | 2023/7/1,281,283,280,282,1910000 184 | 2023/7/2,282,284,281,283,1920000 185 | 2023/7/3,283,285,282,284,1930000 186 | 2023/7/4,284,286,283,285,1940000 187 | 2023/7/5,285,287,284,286,1950000 188 | 2023/7/6,286,288,285,287,1960000 189 | 2023/7/7,287,289,286,288,1970000 190 | 2023/7/8,288,290,287,289,1980000 191 | 2023/7/9,289,291,288,290,1990000 192 | 2023/7/10,290,292,289,291,2000000 193 | 2023/7/11,291,293,290,292,2010000 194 | 2023/7/12,292,294,291,293,2020000 195 | 2023/7/13,293,295,292,294,2030000 196 | 2023/7/14,294,296,293,295,2040000 197 | 2023/7/15,295,297,294,296,2050000 198 | 2023/7/16,296,298,295,297,2060000 199 | 2023/7/17,297,299,296,298,2070000 200 | 2023/7/18,298,300,297,299,2080000 201 | 2023/7/19,299,301,298,300,2090000 202 | 2023/7/20,300,302,299,301,2100000 203 | 2023/7/21,301,303,300,302,2110000 204 | 2023/7/22,302,304,301,303,2120000 205 | 2023/7/23,303,305,302,304,2130000 206 | 2023/7/24,304,306,303,305,2140000 207 | 2023/7/25,305,307,304,306,2150000 208 | 2023/7/26,306,308,305,307,2160000 209 | 2023/7/27,307,309,306,308,2170000 210 | 2023/7/28,308,310,307,309,2180000 211 | 2023/7/29,309,311,308,310,2190000 212 | 2023/7/30,310,312,309,311,2200000 213 | 2023/7/31,311,313,310,312,2210000 214 | 2023/8/1,312,314,311,313,2220000 215 | 2023/8/2,313,315,312,314,2230000 216 | 2023/8/3,314,316,313,315,2240000 217 | 2023/8/4,315,317,314,316,2250000 218 | 2023/8/5,316,318,315,317,2260000 219 | 2023/8/6,317,319,316,318,2270000 220 | 2023/8/7,318,320,317,319,2280000 221 | 2023/8/8,319,321,318,320,2290000 222 | 2023/8/9,320,322,319,321,2300000 223 | 2023/8/10,321,323,320,322,2310000 224 | 2023/8/11,322,324,321,323,2320000 225 | 2023/8/12,323,325,322,324,2330000 226 | 2023/8/13,324,326,323,325,2340000 227 | 2023/8/14,325,327,324,326,2350000 228 | 2023/8/15,326,328,325,327,2360000 229 | 2023/8/16,327,329,326,328,2370000 230 | 2023/8/17,328,330,327,329,2380000 231 | 2023/8/18,329,331,328,330,2390000 232 | 2023/8/19,330,332,329,331,2400000 233 | 2023/8/20,331,333,330,332,2410000 234 | 2023/8/21,332,334,331,333,2420000 235 | 2023/8/22,333,335,332,334,2430000 236 | 2023/8/23,334,336,333,335,2440000 237 | 2023/8/24,335,337,334,336,2450000 238 | 2023/8/25,336,338,335,337,2460000 239 | 2023/8/26,337,339,336,338,2470000 240 | 2023/8/27,338,340,337,339,2480000 241 | 2023/8/28,339,341,338,340,2490000 242 | 2023/8/29,340,342,339,341,2500000 243 | 2023/8/30,341,343,340,342,2510000 244 | 2023/8/31,342,344,341,343,2520000 245 | 2023/9/1,343,345,342,344,2530000 246 | 2023/9/2,344,346,343,345,2540000 247 | 2023/9/3,345,347,344,346,2550000 248 | 2023/9/4,346,348,345,347,2560000 249 | 2023/9/5,347,349,346,348,2570000 250 | 2023/9/6,348,350,347,349,2580000 251 | 2023/9/7,349,351,348,350,2590000 252 | 2023/9/8,350,352,349,351,2600000 253 | 2023/9/9,351,353,350,352,2610000 254 | 2023/9/10,352,354,351,353,2620000 255 | 2023/9/11,353,355,352,354,2630000 256 | 2023/9/12,354,356,353,355,2640000 257 | 2023/9/13,355,357,354,356,2650000 258 | 2023/9/14,356,358,355,357,2660000 259 | 2023/9/15,357,359,356,358,2670000 260 | 2023/9/16,358,360,357,359,2680000 261 | 2023/9/17,359,361,358,360,2690000 262 | 2023/9/18,360,362,359,361,2700000 263 | 2023/9/19,361,363,360,362,2710000 264 | 2023/9/20,362,364,361,363,2720000 265 | 2023/9/21,363,365,362,364,2730000 266 | 2023/9/22,364,366,363,365,2740000 267 | 2023/9/23,365,367,364,366,2750000 268 | 2023/9/24,366,368,365,367,2760000 269 | 2023/9/25,367,369,366,368,2770000 270 | 2023/9/26,368,370,367,369,2780000 271 | 2023/9/27,369,371,368,370,2790000 272 | 2023/9/28,370,372,369,371,2800000 273 | 2023/9/29,371,373,370,372,2810000 274 | 2023/9/30,372,374,371,373,2820000 275 | 2023/10/1,373,375,372,374,2830000 276 | 2023/10/2,374,376,373,375,2840000 277 | 2023/10/3,375,377,374,376,2850000 278 | 2023/10/4,376,378,375,377,2860000 279 | 2023/10/5,377,379,376,378,2870000 280 | 2023/10/6,378,380,377,379,2880000 281 | 2023/10/7,379,381,378,380,2890000 282 | 2023/10/8,380,382,379,381,2900000 283 | 2023/10/9,381,383,380,382,2910000 284 | 2023/10/10,382,384,381,383,2920000 285 | 2023/10/11,383,385,382,384,2930000 286 | 2023/10/12,384,386,383,385,2940000 287 | 2023/10/13,385,387,384,386,2950000 288 | 2023/10/14,386,388,385,387,2960000 289 | 2023/10/15,387,389,386,388,2970000 290 | 2023/10/16,388,390,387,389,2980000 291 | 2023/10/17,389,391,388,390,2990000 292 | 2023/10/18,390,392,389,391,3000000 293 | 2023/10/19,391,393,390,392,3010000 294 | 2023/10/20,392,394,391,393,3020000 295 | 2023/10/21,393,395,392,394,3030000 296 | 2023/10/22,394,396,393,395,3040000 297 | 2023/10/23,395,397,394,396,3050000 298 | 2023/10/24,396,398,395,397,3060000 299 | 2023/10/25,397,399,396,398,3070000 300 | 2023/10/26,398,400,397,399,3080000 301 | 2023/10/27,399,401,398,400,3090000 302 | 2023/10/28,400,402,399,401,3100000 303 | 2023/10/29,401,403,400,402,3110000 304 | 2023/10/30,402,404,401,403,3120000 305 | 2023/10/31,403,405,402,404,3130000 306 | 2023/11/1,404,406,403,405,3140000 307 | 2023/11/2,405,407,404,406,3150000 308 | 2023/11/3,406,408,405,407,3160000 309 | 2023/11/4,407,409,406,408,3170000 310 | 2023/11/5,408,410,407,409,3180000 311 | 2023/11/6,409,411,408,410,3190000 312 | 2023/11/7,410,412,409,411,3200000 313 | 2023/11/8,411,413,410,412,3210000 314 | 2023/11/9,412,414,411,413,3220000 315 | 2023/11/10,413,415,412,414,3230000 316 | 2023/11/11,414,416,413,415,3240000 317 | 2023/11/12,415,417,414,416,3250000 318 | 2023/11/13,416,418,415,417,3260000 319 | 2023/11/14,417,419,416,418,3270000 320 | 2023/11/15,418,420,417,419,3280000 321 | 2023/11/16,419,421,418,420,3290000 322 | 2023/11/17,420,422,419,421,3300000 323 | 2023/11/18,421,423,420,422,3310000 324 | 2023/11/19,422,424,421,423,3320000 325 | 2023/11/20,423,425,422,424,3330000 326 | 2023/11/21,424,426,423,425,3340000 327 | 2023/11/22,425,427,424,426,3350000 328 | 2023/11/23,426,428,425,427,3360000 329 | 2023/11/24,427,429,426,428,3370000 330 | 2023/11/25,428,430,427,429,3380000 331 | 2023/11/26,429,431,428,430,3390000 332 | 2023/11/27,430,432,429,431,3400000 333 | 2023/11/28,431,433,430,432,3410000 334 | 2023/11/29,432,434,431,433,3420000 335 | 2023/11/30,433,435,432,434,3430000 336 | 2023/12/1,434,436,433,435,3440000 337 | 2023/12/2,435,437,434,436,3450000 338 | 2023/12/3,436,438,435,437,3460000 339 | 2023/12/4,437,439,436,438,3470000 340 | 2023/12/5,438,440,437,439,3480000 341 | 2023/12/6,439,441,438,440,3490000 342 | 2023/12/7,440,442,439,441,3500000 343 | 2023/12/8,441,443,440,442,3510000 344 | 2023/12/9,442,444,441,443,3520000 345 | 2023/12/10,443,445,442,444,3530000 346 | 2023/12/11,444,446,443,445,3540000 347 | 2023/12/12,445,447,444,446,3550000 348 | 2023/12/13,446,448,445,447,3560000 349 | 2023/12/14,447,449,446,448,3570000 350 | 2023/12/15,448,450,447,449,3580000 351 | 2023/12/16,449,451,448,450,3590000 352 | 2023/12/17,450,452,449,451,3600000 353 | 2023/12/18,451,453,450,452,3610000 354 | 2023/12/19,452,454,451,453,3620000 355 | 2023/12/20,453,455,452,454,3630000 356 | 2023/12/21,454,456,453,455,3640000 357 | 2023/12/22,455,457,454,456,3650000 358 | 2023/12/23,456,458,455,457,3660000 359 | 2023/12/24,457,459,456,458,3670000 360 | 2023/12/25,458,460,457,459,3680000 361 | 2023/12/26,459,461,458,460,3690000 362 | 2023/12/27,460,462,459,461,3700000 363 | 2023/12/28,461,463,460,462,3710000 364 | 2023/12/29,462,464,461,463,3720000 365 | 2023/12/30,463,465,462,464,3730000 366 | 2023/12/31,464,466,463,465,3740000 367 | 2024/1/1,465,467,464,466,3750000 368 | 2024/1/2,466,468,465,467,3760000 369 | 2024/1/3,467,469,466,468,3770000 370 | 2024/1/4,468,470,467,469,3780000 371 | 2024/1/5,469,471,468,470,3790000 372 | 2024/1/6,470,472,469,471,3800000 373 | 2024/1/7,471,473,470,472,3810000 374 | 2024/1/8,472,474,471,473,3820000 375 | 2024/1/9,473,475,472,474,3830000 376 | 2024/1/10,474,476,473,475,3840000 377 | 2024/1/11,475,477,474,476,3850000 378 | 2024/1/12,476,478,475,477,3860000 379 | 2024/1/13,477,479,476,478,3870000 380 | 2024/1/14,478,480,477,479,3880000 381 | 2024/1/15,479,481,478,480,3890000 382 | 2024/1/16,480,482,479,481,3900000 383 | 2024/1/17,481,483,480,482,3910000 384 | 2024/1/18,482,484,481,483,3920000 385 | 2024/1/19,483,485,482,484,3930000 386 | 2024/1/20,484,486,483,485,3940000 387 | 2024/1/21,485,487,484,486,3950000 388 | 2024/1/22,486,488,485,487,3960000 389 | 2024/1/23,487,489,486,488,3970000 390 | 2024/1/24,488,490,487,489,3980000 391 | 2024/1/25,489,491,488,490,3990000 392 | 2024/1/26,490,492,489,491,4000000 393 | 2024/1/27,491,493,490,492,4010000 394 | 2024/1/28,492,494,491,493,4020000 395 | 2024/1/29,493,495,492,494,4030000 396 | 2024/1/30,494,496,493,495,4040000 397 | 2024/1/31,495,497,494,496,4050000 398 | 2024/2/1,496,498,495,497,4060000 399 | 2024/2/2,497,499,496,498,4070000 400 | 2024/2/3,498,500,497,499,4080000 401 | 2024/2/4,499,501,498,500,4090000 402 | 2024/2/5,500,502,499,501,4100000 403 | 2024/2/6,501,503,500,502,4110000 404 | 2024/2/7,502,504,501,503,4120000 405 | 2024/2/8,503,505,502,504,4130000 406 | 2024/2/9,504,506,503,505,4140000 407 | 2024/2/10,505,507,504,506,4150000 408 | 2024/2/11,506,508,505,507,4160000 409 | 2024/2/12,507,509,506,508,4170000 410 | 2024/2/13,508,510,507,509,4180000 411 | 2024/2/14,509,511,508,510,4190000 412 | 2024/2/15,510,512,509,511,4200000 413 | 2024/2/16,511,513,510,512,4210000 414 | 2024/2/17,512,514,511,513,4220000 415 | 2024/2/18,513,515,512,514,4230000 416 | 2024/2/19,514,516,513,515,4240000 417 | 2024/2/20,515,517,514,516,4250000 418 | 2024/2/21,516,518,515,517,4260000 419 | 2024/2/22,517,519,516,518,4270000 420 | 2024/2/23,518,520,517,519,4280000 421 | 2024/2/24,519,521,518,520,4290000 422 | 2024/2/25,520,522,519,521,4300000 423 | 2024/2/26,521,523,520,522,4310000 424 | 2024/2/27,522,524,521,523,4320000 425 | 2024/2/28,523,525,522,524,4330000 426 | 2024/2/29,524,526,523,525,4340000 427 | 2024/3/1,525,527,524,526,4350000 428 | 2024/3/2,526,528,525,527,4360000 429 | 2024/3/3,527,529,526,528,4370000 430 | 2024/3/4,528,530,527,529,4380000 431 | 2024/3/5,529,531,528,530,4390000 432 | 2024/3/6,530,532,529,531,4400000 433 | 2024/3/7,531,533,530,532,4410000 434 | 2024/3/8,532,534,531,533,4420000 435 | 2024/3/9,533,535,532,534,4430000 436 | 2024/3/10,534,536,533,535,4440000 437 | 2024/3/11,535,537,534,536,4450000 438 | 2024/3/12,536,538,535,537,4460000 439 | 2024/3/13,537,539,536,538,4470000 440 | 2024/3/14,538,540,537,539,4480000 441 | 2024/3/15,539,541,538,540,4490000 442 | 2024/3/16,540,542,539,541,4500000 443 | 2024/3/17,541,543,540,542,4510000 444 | 2024/3/18,542,544,541,543,4520000 445 | 2024/3/19,543,545,542,544,4530000 446 | 2024/3/20,544,546,543,545,4540000 447 | 2024/3/21,545,547,544,546,4550000 448 | 2024/3/22,546,548,545,547,4560000 449 | 2024/3/23,547,549,546,548,4570000 450 | 2024/3/24,548,550,547,549,4580000 451 | 2024/3/25,549,551,548,550,4590000 452 | 2024/3/26,550,552,549,551,4600000 453 | 2024/3/27,551,553,550,552,4610000 454 | 2024/3/28,552,554,551,553,4620000 455 | 2024/3/29,553,555,552,554,4630000 456 | 2024/3/30,554,556,553,555,4640000 457 | 2024/3/31,555,557,554,556,4650000 458 | 2024/4/1,556,558,555,557,4660000 459 | 2024/4/2,557,559,556,558,4670000 460 | 2024/4/3,558,560,557,559,4680000 461 | 2024/4/4,559,561,558,560,4690000 462 | 2024/4/5,560,562,559,561,4700000 463 | 2024/4/6,561,563,560,562,4710000 464 | 2024/4/7,562,564,561,563,4720000 465 | 2024/4/8,563,565,562,564,4730000 466 | 2024/4/9,564,566,563,565,4740000 467 | 2024/4/10,565,567,564,566,4750000 468 | 2024/4/11,566,568,565,567,4760000 469 | 2024/4/12,567,569,566,568,4770000 470 | 2024/4/13,568,570,567,569,4780000 471 | 2024/4/14,569,571,568,570,4790000 472 | 2024/4/15,570,572,569,571,4800000 473 | 2024/4/16,571,573,570,572,4810000 474 | 2024/4/17,572,574,571,573,4820000 475 | 2024/4/18,573,575,572,574,4830000 476 | 2024/4/19,574,576,573,575,4840000 477 | 2024/4/20,575,577,574,576,4850000 478 | 2024/4/21,576,578,575,577,4860000 479 | 2024/4/22,577,579,576,578,4870000 480 | 2024/4/23,578,580,577,579,4880000 481 | 2024/4/24,579,581,578,580,4890000 482 | 2024/4/25,580,582,579,581,4900000 483 | 2024/4/26,581,583,580,582,4910000 484 | 2024/4/27,582,584,581,583,4920000 485 | 2024/4/28,583,585,582,584,4930000 486 | 2024/4/29,584,586,583,585,4940000 487 | 2024/4/30,585,587,584,586,4950000 488 | 2024/5/1,586,588,585,587,4960000 489 | 2024/5/2,587,589,586,588,4970000 490 | 2024/5/3,588,590,587,589,4980000 491 | 2024/5/4,589,591,588,590,4990000 492 | 2024/5/5,590,592,589,591,5000000 493 | 2024/5/6,591,593,590,592,5010000 494 | 2024/5/7,592,594,591,593,5020000 495 | 2024/5/8,593,595,592,594,5030000 496 | 2024/5/9,594,596,593,595,5040000 497 | 2024/5/10,595,597,594,596,5050000 498 | 2024/5/11,596,598,595,597,5060000 499 | 2024/5/12,597,599,596,598,5070000 500 | 2024/5/13,598,600,597,599,5080000 501 | 2024/5/14,599,601,598,600,5090000 502 | 2024/5/15,600,602,599,601,5100000 503 | 2024/5/16,601,603,600,602,5110000 504 | 2024/5/17,602,604,601,603,5120000 505 | 2024/5/18,603,605,602,604,5130000 506 | 2024/5/19,604,606,603,605,5140000 507 | 2024/5/20,605,607,604,606,5150000 508 | 2024/5/21,606,608,605,607,5160000 509 | 2024/5/22,607,609,606,608,5170000 510 | 2024/5/23,608,610,607,609,5180000 511 | 2024/5/24,609,611,608,610,5190000 512 | 2024/5/25,610,612,609,611,5200000 513 | 2024/5/26,611,613,610,612,5210000 514 | 2024/5/27,612,614,611,613,5220000 515 | 2024/5/28,613,615,612,614,5230000 516 | 2024/5/29,614,616,613,615,5240000 517 | 2024/5/30,615,617,614,616,5250000 518 | 2024/5/31,616,618,615,617,5260000 519 | 2024/6/1,617,619,616,618,5270000 520 | 2024/6/2,618,620,617,619,5280000 521 | 2024/6/3,619,621,618,620,5290000 522 | 2024/6/4,620,622,619,621,5300000 523 | 2024/6/5,621,623,620,622,5310000 524 | 2024/6/6,622,624,621,623,5320000 525 | 2024/6/7,623,625,622,624,5330000 526 | 2024/6/8,624,626,623,625,5340000 527 | 2024/6/9,625,627,624,626,5350000 528 | 2024/6/10,626,628,625,627,5360000 529 | 2024/6/11,627,629,626,628,5370000 530 | 2024/6/12,628,630,627,629,5380000 531 | 2024/6/13,629,631,628,630,5390000 532 | 2024/6/14,630,632,629,631,5400000 533 | 2024/6/15,631,633,630,632,5410000 534 | 2024/6/16,632,634,631,633,5420000 535 | 2024/6/17,633,635,632,634,5430000 536 | 2024/6/18,634,636,633,635,5440000 537 | 2024/6/19,635,637,634,636,5450000 538 | 2024/6/20,636,638,635,637,5460000 539 | 2024/6/21,637,639,636,638,5470000 540 | 2024/6/22,638,640,637,639,5480000 541 | 2024/6/23,639,641,638,640,5490000 542 | 2024/6/24,640,642,639,641,5500000 543 | 2024/6/25,641,643,640,642,5510000 544 | 2024/6/26,642,644,641,643,5520000 545 | 2024/6/27,643,645,642,644,5530000 546 | 2024/6/28,644,646,643,645,5540000 547 | 2024/6/29,645,647,644,646,5550000 548 | 2024/6/30,646,648,645,647,5560000 549 | 2024/7/1,647,649,646,648,5570000 550 | 2024/7/2,648,650,647,649,5580000 551 | 2024/7/3,649,651,648,650,5590000 552 | 2024/7/4,650,652,649,651,5600000 553 | 2024/7/5,651,653,650,652,5610000 554 | 2024/7/6,652,654,651,653,5620000 555 | 2024/7/7,653,655,652,654,5630000 556 | 2024/7/8,654,656,653,655,5640000 557 | 2024/7/9,655,657,654,656,5650000 558 | 2024/7/10,656,658,655,657,5660000 559 | 2024/7/11,657,659,656,658,5670000 560 | 2024/7/12,658,660,657,659,5680000 561 | 2024/7/13,659,661,658,660,5690000 562 | 2024/7/14,660,662,659,661,5700000 563 | 2024/7/15,661,663,660,662,5710000 564 | 2024/7/16,662,664,661,663,5720000 565 | 2024/7/17,663,665,662,664,5730000 566 | 2024/7/18,664,666,663,665,5740000 567 | 2024/7/19,665,667,664,666,5750000 568 | 2024/7/20,666,668,665,667,5760000 569 | 2024/7/21,667,669,666,668,5770000 570 | 2024/7/22,668,670,667,669,5780000 571 | 2024/7/23,669,671,668,670,5790000 572 | 2024/7/24,670,672,669,671,5800000 573 | 2024/7/25,671,673,670,672,5810000 574 | 2024/7/26,672,674,671,673,5820000 575 | 2024/7/27,673,675,672,674,5830000 576 | 2024/7/28,674,676,673,675,5840000 577 | 2024/7/29,675,677,674,676,5850000 578 | 2024/7/30,676,678,675,677,5860000 579 | 2024/7/31,677,679,676,678,5870000 580 | 2024/8/1,678,680,677,679,5880000 581 | 2024/8/2,679,681,678,680,5890000 582 | 2024/8/3,680,682,679,681,5900000 583 | 2024/8/4,681,683,680,682,5910000 584 | 2024/8/5,682,684,681,683,5920000 585 | 2024/8/6,683,685,682,684,5930000 586 | 2024/8/7,684,686,683,685,5940000 587 | 2024/8/8,685,687,684,686,5950000 588 | 2024/8/9,686,688,685,687,5960000 589 | 2024/8/10,687,689,686,688,5970000 590 | 2024/8/11,688,690,687,689,5980000 591 | 2024/8/12,689,691,688,690,5990000 592 | 2024/8/13,690,692,689,691,6000000 593 | 2024/8/14,691,693,690,692,6010000 594 | 2024/8/15,692,694,691,693,6020000 595 | 2024/8/16,693,695,692,694,6030000 596 | 2024/8/17,694,696,693,695,6040000 597 | 2024/8/18,695,697,694,696,6050000 598 | 2024/8/19,696,698,695,697,6060000 599 | 2024/8/20,697,699,696,698,6070000 600 | 2024/8/21,698,700,697,699,6080000 601 | 2024/8/22,699,701,698,700,6090000 602 | 2024/8/23,700,702,699,701,6100000 603 | 2024/8/24,701,703,700,702,6110000 604 | 2024/8/25,702,704,701,703,6120000 605 | 2024/8/26,703,705,702,704,6130000 606 | 2024/8/27,704,706,703,705,6140000 607 | 2024/8/28,705,707,704,706,6150000 608 | 2024/8/29,706,708,705,707,6160000 609 | 2024/8/30,707,709,706,708,6170000 610 | 2024/8/31,708,710,707,709,6180000 611 | 2024/9/1,709,711,708,710,6190000 612 | 2024/9/2,710,712,709,711,6200000 613 | 2024/9/3,711,713,710,712,6210000 614 | 2024/9/4,712,714,711,713,6220000 615 | 2024/9/5,713,715,712,714,6230000 616 | 2024/9/6,714,716,713,715,6240000 617 | 2024/9/7,715,717,714,716,6250000 618 | 2024/9/8,716,718,715,717,6260000 619 | 2024/9/9,717,719,716,718,6270000 620 | 2024/9/10,718,720,717,719,6280000 621 | 2024/9/11,719,721,718,720,6290000 622 | 2024/9/12,720,722,719,721,6300000 623 | 2024/9/13,721,723,720,722,6310000 624 | 2024/9/14,722,724,721,723,6320000 625 | 2024/9/15,723,725,722,724,6330000 626 | 2024/9/16,724,726,723,725,6340000 627 | 2024/9/17,725,727,724,726,6350000 628 | 2024/9/18,726,728,725,727,6360000 629 | 2024/9/19,727,729,726,728,6370000 630 | 2024/9/20,728,730,727,729,6380000 631 | 2024/9/21,729,731,728,730,6390000 632 | 2024/9/22,730,732,729,731,6400000 633 | 2024/9/23,731,733,730,732,6410000 634 | 2024/9/24,732,734,731,733,6420000 635 | 2024/9/25,733,735,732,734,6430000 636 | 2024/9/26,734,736,733,735,6440000 637 | 2024/9/27,735,737,734,736,6450000 638 | 2024/9/28,736,738,735,737,6460000 639 | 2024/9/29,737,739,736,738,6470000 640 | 2024/9/30,738,740,737,739,6480000 641 | 2024/10/1,739,741,738,740,6490000 642 | 2024/10/2,740,742,739,741,6500000 643 | 2024/10/3,741,743,740,742,6510000 644 | 2024/10/4,742,744,741,743,6520000 645 | 2024/10/5,743,745,742,744,6530000 646 | 2024/10/6,744,746,743,745,6540000 647 | 2024/10/7,745,747,744,746,6550000 648 | 2024/10/8,746,748,745,747,6560000 649 | 2024/10/9,747,749,746,748,6570000 650 | 2024/10/10,748,750,747,749,6580000 651 | 2024/10/11,749,751,748,750,6590000 652 | 2024/10/12,750,752,749,751,6600000 653 | 2024/10/13,751,753,750,752,6610000 654 | 2024/10/14,752,754,751,753,6620000 655 | 2024/10/15,753,755,752,754,6630000 656 | 2024/10/16,754,756,753,755,6640000 657 | 2024/10/17,755,757,754,756,6650000 658 | 2024/10/18,756,758,755,757,6660000 659 | 2024/10/19,757,759,756,758,6670000 660 | 2024/10/20,758,760,757,759,6680000 661 | 2024/10/21,759,761,758,760,6690000 662 | 2024/10/22,760,762,759,761,6700000 663 | 2024/10/23,761,763,760,762,6710000 664 | 2024/10/24,762,764,761,763,6720000 665 | 2024/10/25,763,765,762,764,6730000 666 | 2024/10/26,764,766,763,765,6740000 667 | 2024/10/27,765,767,764,766,6750000 668 | 2024/10/28,766,768,765,767,6760000 669 | 2024/10/29,767,769,766,768,6770000 670 | 2024/10/30,768,770,767,769,6780000 671 | 2024/10/31,769,771,768,770,6790000 672 | 2024/11/1,770,772,769,771,6800000 673 | 2024/11/2,771,773,770,772,6810000 674 | 2024/11/3,772,774,771,773,6820000 675 | 2024/11/4,773,775,772,774,6830000 676 | 2024/11/5,774,776,773,775,6840000 677 | 2024/11/6,775,777,774,776,6850000 678 | 2024/11/7,776,778,775,777,6860000 679 | 2024/11/8,777,779,776,778,6870000 680 | 2024/11/9,778,780,777,779,6880000 681 | 2024/11/10,779,781,778,780,6890000 682 | 2024/11/11,780,782,779,781,6900000 683 | 2024/11/12,781,783,780,782,6910000 684 | 2024/11/13,782,784,781,783,6920000 685 | 2024/11/14,783,785,782,784,6930000 686 | 2024/11/15,784,786,783,785,6940000 687 | 2024/11/16,785,787,784,786,6950000 688 | 2024/11/17,786,788,785,787,6960000 689 | 2024/11/18,787,789,786,788,6970000 690 | 2024/11/19,788,790,787,789,6980000 691 | 2024/11/20,789,791,788,790,6990000 692 | 2024/11/21,790,792,789,791,7000000 693 | 2024/11/22,791,793,790,792,7010000 694 | 2024/11/23,792,794,791,793,7020000 695 | 2024/11/24,793,795,792,794,7030000 696 | 2024/11/25,794,796,793,795,7040000 697 | 2024/11/26,795,797,794,796,7050000 698 | 2024/11/27,796,798,795,797,7060000 699 | 2024/11/28,797,799,796,798,7070000 700 | 2024/11/29,798,800,797,799,7080000 701 | 2024/11/30,799,801,798,800,7090000 702 | 2024/12/1,800,802,799,801,7100000 703 | 2024/12/2,801,803,800,802,7110000 704 | 2024/12/3,802,804,801,803,7120000 705 | 2024/12/4,803,805,802,804,7130000 706 | 2024/12/5,804,806,803,805,7140000 707 | 2024/12/6,805,807,804,806,7150000 708 | 2024/12/7,806,808,805,807,7160000 709 | 2024/12/8,807,809,806,808,7170000 710 | 2024/12/9,808,810,807,809,7180000 711 | 2024/12/10,809,811,808,810,7190000 712 | 2024/12/11,810,812,809,811,7200000 713 | 2024/12/12,811,813,810,812,7210000 714 | 2024/12/13,812,814,811,813,7220000 715 | 2024/12/14,813,815,812,814,7230000 716 | 2024/12/15,814,816,813,815,7240000 717 | 2024/12/16,815,817,814,816,7250000 718 | 2024/12/17,816,818,815,817,7260000 719 | 2024/12/18,817,819,816,818,7270000 720 | 2024/12/19,818,820,817,819,7280000 721 | 2024/12/20,819,821,818,820,7290000 722 | 2024/12/21,820,822,819,821,7300000 723 | 2024/12/22,821,823,820,822,7310000 724 | 2024/12/23,822,824,821,823,7320000 725 | 2024/12/24,823,825,822,824,7330000 726 | 2024/12/25,824,826,823,825,7340000 727 | 2024/12/26,825,827,824,826,7350000 728 | 2024/12/27,826,828,825,827,7360000 729 | 2024/12/28,827,829,826,828,7370000 730 | 2024/12/29,828,830,827,829,7380000 731 | 2024/12/30,829,831,828,830,7390000 732 | 2024/12/31,830,832,829,831,7400000 733 | 2025/1/1,831,833,830,832,7410000 734 | 2025/1/2,832,834,831,833,7420000 735 | 2025/1/3,833,835,832,834,7430000 736 | 2025/1/4,834,836,833,835,7440000 737 | 2025/1/5,835,837,834,836,7450000 738 | 2025/1/6,836,838,835,837,7460000 739 | 2025/1/7,837,839,836,838,7470000 740 | 2025/1/8,838,840,837,839,7480000 741 | 2025/1/9,839,841,838,840,7490000 742 | 2025/1/10,840,842,839,841,7500000 743 | 2025/1/11,841,843,840,842,7510000 744 | 2025/1/12,842,844,841,843,7520000 745 | 2025/1/13,843,845,842,844,7530000 746 | 2025/1/14,844,846,843,845,7540000 747 | 2025/1/15,845,847,844,846,7550000 748 | 2025/1/16,846,848,845,847,7560000 749 | 2025/1/17,847,849,846,848,7570000 750 | 2025/1/18,848,850,847,849,7580000 751 | 2025/1/19,849,851,848,850,7590000 752 | 2025/1/20,850,852,849,851,7600000 753 | 2025/1/21,851,853,850,852,7610000 754 | 2025/1/22,852,854,851,853,7620000 755 | 2025/1/23,853,855,852,854,7630000 756 | 2025/1/24,854,856,853,855,7640000 757 | 2025/1/25,855,857,854,856,7650000 758 | 2025/1/26,856,858,855,857,7660000 759 | 2025/1/27,857,859,856,858,7670000 760 | 2025/1/28,858,860,857,859,7680000 761 | 2025/1/29,859,861,858,860,7690000 762 | 2025/1/30,860,862,859,861,7700000 763 | 2025/1/31,861,863,860,862,7710000 764 | 2025/2/1,862,864,861,863,7720000 765 | 2025/2/2,863,865,862,864,7730000 766 | 2025/2/3,864,866,863,865,7740000 767 | 2025/2/4,865,867,864,866,7750000 768 | 2025/2/5,866,868,865,867,7760000 769 | 2025/2/6,867,869,866,868,7770000 770 | 2025/2/7,868,870,867,869,7780000 771 | 2025/2/8,869,871,868,870,7790000 772 | 2025/2/9,870,872,869,871,7800000 773 | 2025/2/10,871,873,870,872,7810000 774 | 2025/2/11,872,874,871,873,7820000 775 | 2025/2/12,873,875,872,874,7830000 776 | 2025/2/13,874,876,873,875,7840000 777 | 2025/2/14,875,877,874,876,7850000 778 | 2025/2/15,876,878,875,877,7860000 779 | 2025/2/16,877,879,876,878,7870000 780 | 2025/2/17,878,880,877,879,7880000 781 | 2025/2/18,879,881,878,880,7890000 782 | 2025/2/19,880,882,879,881,7900000 783 | 2025/2/20,881,883,880,882,7910000 784 | 2025/2/21,882,884,881,883,7920000 785 | 2025/2/22,883,885,882,884,7930000 786 | 2025/2/23,884,886,883,885,7940000 787 | 2025/2/24,885,887,884,886,7950000 788 | 2025/2/25,886,888,885,887,7960000 789 | 2025/2/26,887,889,886,888,7970000 790 | 2025/2/27,888,890,887,889,7980000 791 | 2025/2/28,889,891,888,890,7990000 792 | 2025/3/1,890,892,889,891,8000000 793 | 2025/3/2,891,893,890,892,8010000 794 | 2025/3/3,892,894,891,893,8020000 795 | 2025/3/4,893,895,892,894,8030000 796 | 2025/3/5,894,896,893,895,8040000 797 | 2025/3/6,895,897,894,896,8050000 798 | 2025/3/7,896,898,895,897,8060000 799 | 2025/3/8,897,899,896,898,8070000 800 | 2025/3/9,898,900,897,899,8080000 801 | 2025/3/10,899,901,898,900,8090000 802 | 2025/3/11,900,902,899,901,8100000 803 | 2025/3/12,901,903,900,902,8110000 804 | 2025/3/13,902,904,901,903,8120000 805 | 2025/3/14,903,905,902,904,8130000 806 | 2025/3/15,904,906,903,905,8140000 807 | 2025/3/16,905,907,904,906,8150000 808 | 2025/3/17,906,908,905,907,8160000 809 | 2025/3/18,907,909,906,908,8170000 810 | 2025/3/19,908,910,907,909,8180000 811 | 2025/3/20,909,911,908,910,8190000 812 | 2025/3/21,910,912,909,911,8200000 813 | 2025/3/22,911,913,910,912,8210000 814 | 2025/3/23,912,914,911,913,8220000 815 | 2025/3/24,913,915,912,914,8230000 816 | 2025/3/25,914,916,913,915,8240000 817 | 2025/3/26,915,917,914,916,8250000 818 | 2025/3/27,916,918,915,917,8260000 819 | 2025/3/28,917,919,916,918,8270000 820 | 2025/3/29,918,920,917,919,8280000 821 | 2025/3/30,919,921,918,920,8290000 822 | 2025/3/31,920,922,919,921,8300000 823 | 2025/4/1,921,923,920,922,8310000 824 | 2025/4/2,922,924,921,923,8320000 825 | 2025/4/3,923,925,922,924,8330000 826 | 2025/4/4,924,926,923,925,8340000 827 | 2025/4/5,925,927,924,926,8350000 828 | 2025/4/6,926,928,925,927,8360000 829 | 2025/4/7,927,929,926,928,8370000 830 | 2025/4/8,928,930,927,929,8380000 831 | 2025/4/9,929,931,928,930,8390000 832 | 2025/4/10,930,932,929,931,8400000 833 | 2025/4/11,931,933,930,932,8410000 834 | 2025/4/12,932,934,931,933,8420000 835 | 2025/4/13,933,935,932,934,8430000 836 | 2025/4/14,934,936,933,935,8440000 837 | 2025/4/15,935,937,934,936,8450000 838 | 2025/4/16,936,938,935,937,8460000 839 | 2025/4/17,937,939,936,938,8470000 840 | 2025/4/18,938,940,937,939,8480000 841 | 2025/4/19,939,941,938,940,8490000 842 | 2025/4/20,940,942,939,941,8500000 843 | 2025/4/21,941,943,940,942,8510000 844 | 2025/4/22,942,944,941,943,8520000 845 | 2025/4/23,943,945,942,944,8530000 846 | 2025/4/24,944,946,943,945,8540000 847 | 2025/4/25,945,947,944,946,8550000 848 | 2025/4/26,946,948,945,947,8560000 849 | 2025/4/27,947,949,946,948,8570000 850 | 2025/4/28,948,950,947,949,8580000 851 | 2025/4/29,949,951,948,950,8590000 852 | 2025/4/30,950,952,949,951,8600000 853 | 2025/5/1,951,953,950,952,8610000 854 | 2025/5/2,952,954,951,953,8620000 855 | 2025/5/3,953,955,952,954,8630000 856 | 2025/5/4,954,956,953,955,8640000 857 | 2025/5/5,955,957,954,956,8650000 858 | 2025/5/6,956,958,955,957,8660000 859 | 2025/5/7,957,959,956,958,8670000 860 | 2025/5/8,958,960,957,959,8680000 861 | 2025/5/9,959,961,958,960,8690000 862 | 2025/5/10,960,962,959,961,8700000 863 | 2025/5/11,961,963,960,962,8710000 864 | 2025/5/12,962,964,961,963,8720000 865 | 2025/5/13,963,965,962,964,8730000 866 | 2025/5/14,964,966,963,965,8740000 867 | 2025/5/15,965,967,964,966,8750000 868 | 2025/5/16,966,968,965,967,8760000 869 | 2025/5/17,967,969,966,968,8770000 870 | 2025/5/18,968,970,967,969,8780000 871 | 2025/5/19,969,971,968,970,8790000 872 | 2025/5/20,970,972,969,971,8800000 873 | 2025/5/21,971,973,970,972,8810000 874 | 2025/5/22,972,974,971,973,8820000 875 | 2025/5/23,973,975,972,974,8830000 876 | 2025/5/24,974,976,973,975,8840000 877 | 2025/5/25,975,977,974,976,8850000 878 | 2025/5/26,976,978,975,977,8860000 879 | 2025/5/27,977,979,976,978,8870000 880 | 2025/5/28,978,980,977,979,8880000 881 | 2025/5/29,979,981,978,980,8890000 882 | 2025/5/30,980,982,979,981,8900000 883 | 2025/5/31,981,983,980,982,8910000 884 | 2025/6/1,982,984,981,983,8920000 885 | 2025/6/2,983,985,982,984,8930000 886 | 2025/6/3,984,986,983,985,8940000 887 | 2025/6/4,985,987,984,986,8950000 888 | 2025/6/5,986,988,985,987,8960000 889 | 2025/6/6,987,989,986,988,8970000 890 | 2025/6/7,988,990,987,989,8980000 891 | 2025/6/8,989,991,988,990,8990000 892 | 2025/6/9,990,992,989,991,9000000 893 | 2025/6/10,991,993,990,992,9010000 894 | 2025/6/11,992,994,991,993,9020000 895 | 2025/6/12,993,995,992,994,9030000 896 | 2025/6/13,994,996,993,995,9040000 897 | 2025/6/14,995,997,994,996,9050000 898 | 2025/6/15,996,998,995,997,9060000 899 | 2025/6/16,997,999,996,998,9070000 900 | 2025/6/17,998,1000,997,999,9080000 901 | 2025/6/18,999,1001,998,1000,9090000 902 | 2025/6/19,1000,1002,999,1001,9100000 903 | 2025/6/20,1001,1003,1000,1002,9110000 904 | 2025/6/21,1002,1004,1001,1003,9120000 905 | 2025/6/22,1003,1005,1002,1004,9130000 906 | 2025/6/23,1004,1006,1003,1005,9140000 907 | 2025/6/24,1005,1007,1004,1006,9150000 908 | 2025/6/25,1006,1008,1005,1007,9160000 909 | 2025/6/26,1007,1009,1006,1008,9170000 910 | 2025/6/27,1008,1010,1007,1009,9180000 911 | 2025/6/28,1009,1011,1008,1010,9190000 912 | 2025/6/29,1010,1012,1009,1011,9200000 913 | 2025/6/30,1011,1013,1010,1012,9210000 914 | 2025/7/1,1012,1014,1011,1013,9220000 915 | 2025/7/2,1013,1015,1012,1014,9230000 916 | 2025/7/3,1014,1016,1013,1015,9240000 917 | 2025/7/4,1015,1017,1014,1016,9250000 918 | 2025/7/5,1016,1018,1015,1017,9260000 919 | 2025/7/6,1017,1019,1016,1018,9270000 920 | 2025/7/7,1018,1020,1017,1019,9280000 921 | 2025/7/8,1019,1021,1018,1020,9290000 922 | 2025/7/9,1020,1022,1019,1021,9300000 923 | 2025/7/10,1021,1023,1020,1022,9310000 924 | 2025/7/11,1022,1024,1021,1023,9320000 925 | 2025/7/12,1023,1025,1022,1024,9330000 926 | 2025/7/13,1024,1026,1023,1025,9340000 927 | 2025/7/14,1025,1027,1024,1026,9350000 928 | 2025/7/15,1026,1028,1025,1027,9360000 929 | 2025/7/16,1027,1029,1026,1028,9370000 930 | 2025/7/17,1028,1030,1027,1029,9380000 931 | 2025/7/18,1029,1031,1028,1030,9390000 932 | 2025/7/19,1030,1032,1029,1031,9400000 933 | 2025/7/20,1031,1033,1030,1032,9410000 934 | 2025/7/21,1032,1034,1031,1033,9420000 935 | 2025/7/22,1033,1035,1032,1034,9430000 936 | 2025/7/23,1034,1036,1033,1035,9440000 937 | 2025/7/24,1035,1037,1034,1036,9450000 938 | 2025/7/25,1036,1038,1035,1037,9460000 939 | 2025/7/26,1037,1039,1036,1038,9470000 940 | 2025/7/27,1038,1040,1037,1039,9480000 941 | 2025/7/28,1039,1041,1038,1040,9490000 942 | 2025/7/29,1040,1042,1039,1041,9500000 943 | 2025/7/30,1041,1043,1040,1042,9510000 944 | 2025/7/31,1042,1044,1041,1043,9520000 945 | 2025/8/1,1043,1045,1042,1044,9530000 946 | 2025/8/2,1044,1046,1043,1045,9540000 947 | 2025/8/3,1045,1047,1044,1046,9550000 948 | 2025/8/4,1046,1048,1045,1047,9560000 949 | 2025/8/5,1047,1049,1046,1048,9570000 950 | 2025/8/6,1048,1050,1047,1049,9580000 951 | 2025/8/7,1049,1051,1048,1050,9590000 952 | 2025/8/8,1050,1052,1049,1051,9600000 953 | 2025/8/9,1051,1053,1050,1052,9610000 954 | 2025/8/10,1052,1054,1051,1053,9620000 955 | 2025/8/11,1053,1055,1052,1054,9630000 956 | 2025/8/12,1054,1056,1053,1055,9640000 957 | 2025/8/13,1055,1057,1054,1056,9650000 958 | 2025/8/14,1056,1058,1055,1057,9660000 959 | 2025/8/15,1057,1059,1056,1058,9670000 960 | 2025/8/16,1058,1060,1057,1059,9680000 961 | 2025/8/17,1059,1061,1058,1060,9690000 962 | 2025/8/18,1060,1062,1059,1061,9700000 963 | 2025/8/19,1061,1063,1060,1062,9710000 964 | 2025/8/20,1062,1064,1061,1063,9720000 965 | 2025/8/21,1063,1065,1062,1064,9730000 966 | 2025/8/22,1064,1066,1063,1065,9740000 967 | 2025/8/23,1065,1067,1064,1066,9750000 968 | 2025/8/24,1066,1068,1065,1067,9760000 969 | 2025/8/25,1067,1069,1066,1068,9770000 970 | 2025/8/26,1068,1070,1067,1069,9780000 971 | 2025/8/27,1069,1071,1068,1070,9790000 972 | 2025/8/28,1070,1072,1069,1071,9800000 973 | 2025/8/29,1071,1073,1070,1072,9810000 974 | 2025/8/30,1072,1074,1071,1073,9820000 975 | 2025/8/31,1073,1075,1072,1074,9830000 976 | 2025/9/1,1074,1076,1073,1075,9840000 977 | 2025/9/2,1075,1077,1074,1076,9850000 978 | 2025/9/3,1076,1078,1075,1077,9860000 979 | 2025/9/4,1077,1079,1076,1078,9870000 980 | 2025/9/5,1078,1080,1077,1079,9880000 981 | 2025/9/6,1079,1081,1078,1080,9890000 982 | 2025/9/7,1080,1082,1079,1081,9900000 983 | 2025/9/8,1081,1083,1080,1082,9910000 984 | 2025/9/9,1082,1084,1081,1083,9920000 985 | 2025/9/10,1083,1085,1082,1084,9930000 986 | 2025/9/11,1084,1086,1083,1085,9940000 987 | 2025/9/12,1085,1087,1084,1086,9950000 988 | 2025/9/13,1086,1088,1085,1087,9960000 989 | 2025/9/14,1087,1089,1086,1088,9970000 990 | 2025/9/15,1088,1090,1087,1089,9980000 991 | 2025/9/16,1089,1091,1088,1090,9990000 992 | 2025/9/17,1090,1092,1089,1091,10000000 993 | 2025/9/18,1091,1093,1090,1092,10010000 994 | 2025/9/19,1092,1094,1091,1093,10020000 995 | 2025/9/20,1093,1095,1092,1094,10030000 996 | 2025/9/21,1094,1096,1093,1095,10040000 997 | 2025/9/22,1095,1097,1094,1096,10050000 998 | 2025/9/23,1096,1098,1095,1097,10060000 999 | 2025/9/24,1097,1099,1096,1098,10070000 1000 | 2025/9/25,1098,1100,1097,1099,10080000 1001 | 2025/9/26,1099,1101,1098,1100,10090000 1002 | 2025/9/27,1100,1102,1099,1101,10100000 1003 | 2025/9/28,1101,1103,1100,1102,10110000 1004 | 2025/9/29,1102,1104,1101,1103,10120000 1005 | 2025/9/30,1103,1105,1102,1104,10130000 1006 | 2025/10/1,1104,1106,1103,1105,10140000 1007 | 2025/10/2,1105,1107,1104,1106,10150000 1008 | 2025/10/3,1106,1108,1105,1107,10160000 1009 | 2025/10/4,1107,1109,1106,1108,10170000 1010 | 2025/10/5,1108,1110,1107,1109,10180000 1011 | 2025/10/6,1109,1111,1108,1110,10190000 1012 | 2025/10/7,1110,1112,1109,1111,10200000 1013 | 2025/10/8,1111,1113,1110,1112,10210000 1014 | 2025/10/9,1112,1114,1111,1113,10220000 1015 | 2025/10/10,1113,1115,1112,1114,10230000 1016 | 2025/10/11,1114,1116,1113,1115,10240000 1017 | 2025/10/12,1115,1117,1114,1116,10250000 1018 | 2025/10/13,1116,1118,1115,1117,10260000 1019 | 2025/10/14,1117,1119,1116,1118,10270000 1020 | 2025/10/15,1118,1120,1117,1119,10280000 1021 | -------------------------------------------------------------------------------- /frontend/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/helper-string-parser@^7.27.1": 6 | version "7.27.1" 7 | resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" 8 | integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== 9 | 10 | "@babel/helper-validator-identifier@^7.27.1": 11 | version "7.27.1" 12 | resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz" 13 | integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== 14 | 15 | "@babel/parser@^7.28.4": 16 | version "7.28.4" 17 | resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz" 18 | integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== 19 | dependencies: 20 | "@babel/types" "^7.28.4" 21 | 22 | "@babel/types@^7.28.4": 23 | version "7.28.4" 24 | resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz" 25 | integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== 26 | dependencies: 27 | "@babel/helper-string-parser" "^7.27.1" 28 | "@babel/helper-validator-identifier" "^7.27.1" 29 | 30 | "@ctrl/tinycolor@^3.4.1": 31 | version "3.6.1" 32 | resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz" 33 | integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== 34 | 35 | "@element-plus/icons-vue@^2.3.1": 36 | version "2.3.2" 37 | resolved "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz" 38 | integrity sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A== 39 | 40 | "@esbuild/aix-ppc64@0.21.5": 41 | version "0.21.5" 42 | resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz" 43 | integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== 44 | 45 | "@esbuild/android-arm64@0.21.5": 46 | version "0.21.5" 47 | resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz" 48 | integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== 49 | 50 | "@esbuild/android-arm@0.21.5": 51 | version "0.21.5" 52 | resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz" 53 | integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== 54 | 55 | "@esbuild/android-x64@0.21.5": 56 | version "0.21.5" 57 | resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz" 58 | integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== 59 | 60 | "@esbuild/darwin-arm64@0.21.5": 61 | version "0.21.5" 62 | resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz" 63 | integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== 64 | 65 | "@esbuild/darwin-x64@0.21.5": 66 | version "0.21.5" 67 | resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz" 68 | integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== 69 | 70 | "@esbuild/freebsd-arm64@0.21.5": 71 | version "0.21.5" 72 | resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz" 73 | integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== 74 | 75 | "@esbuild/freebsd-x64@0.21.5": 76 | version "0.21.5" 77 | resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz" 78 | integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== 79 | 80 | "@esbuild/linux-arm64@0.21.5": 81 | version "0.21.5" 82 | resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz" 83 | integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== 84 | 85 | "@esbuild/linux-arm@0.21.5": 86 | version "0.21.5" 87 | resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz" 88 | integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== 89 | 90 | "@esbuild/linux-ia32@0.21.5": 91 | version "0.21.5" 92 | resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz" 93 | integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== 94 | 95 | "@esbuild/linux-loong64@0.21.5": 96 | version "0.21.5" 97 | resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz" 98 | integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== 99 | 100 | "@esbuild/linux-mips64el@0.21.5": 101 | version "0.21.5" 102 | resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz" 103 | integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== 104 | 105 | "@esbuild/linux-ppc64@0.21.5": 106 | version "0.21.5" 107 | resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz" 108 | integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== 109 | 110 | "@esbuild/linux-riscv64@0.21.5": 111 | version "0.21.5" 112 | resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz" 113 | integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== 114 | 115 | "@esbuild/linux-s390x@0.21.5": 116 | version "0.21.5" 117 | resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz" 118 | integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== 119 | 120 | "@esbuild/linux-x64@0.21.5": 121 | version "0.21.5" 122 | resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz" 123 | integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== 124 | 125 | "@esbuild/netbsd-x64@0.21.5": 126 | version "0.21.5" 127 | resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz" 128 | integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== 129 | 130 | "@esbuild/openbsd-x64@0.21.5": 131 | version "0.21.5" 132 | resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz" 133 | integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== 134 | 135 | "@esbuild/sunos-x64@0.21.5": 136 | version "0.21.5" 137 | resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz" 138 | integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== 139 | 140 | "@esbuild/win32-arm64@0.21.5": 141 | version "0.21.5" 142 | resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz" 143 | integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== 144 | 145 | "@esbuild/win32-ia32@0.21.5": 146 | version "0.21.5" 147 | resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz" 148 | integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== 149 | 150 | "@esbuild/win32-x64@0.21.5": 151 | version "0.21.5" 152 | resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" 153 | integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== 154 | 155 | "@floating-ui/core@^1.7.3": 156 | version "1.7.3" 157 | resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz" 158 | integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== 159 | dependencies: 160 | "@floating-ui/utils" "^0.2.10" 161 | 162 | "@floating-ui/dom@^1.0.1": 163 | version "1.7.4" 164 | resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz" 165 | integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== 166 | dependencies: 167 | "@floating-ui/core" "^1.7.3" 168 | "@floating-ui/utils" "^0.2.10" 169 | 170 | "@floating-ui/utils@^0.2.10": 171 | version "0.2.10" 172 | resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz" 173 | integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== 174 | 175 | "@jridgewell/gen-mapping@^0.3.5": 176 | version "0.3.13" 177 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" 178 | integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== 179 | dependencies: 180 | "@jridgewell/sourcemap-codec" "^1.5.0" 181 | "@jridgewell/trace-mapping" "^0.3.24" 182 | 183 | "@jridgewell/remapping@^2.3.5": 184 | version "2.3.5" 185 | resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" 186 | integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== 187 | dependencies: 188 | "@jridgewell/gen-mapping" "^0.3.5" 189 | "@jridgewell/trace-mapping" "^0.3.24" 190 | 191 | "@jridgewell/resolve-uri@^3.1.0": 192 | version "3.1.2" 193 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" 194 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 195 | 196 | "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": 197 | version "1.5.5" 198 | resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" 199 | integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== 200 | 201 | "@jridgewell/trace-mapping@^0.3.24": 202 | version "0.3.31" 203 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" 204 | integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== 205 | dependencies: 206 | "@jridgewell/resolve-uri" "^3.1.0" 207 | "@jridgewell/sourcemap-codec" "^1.4.14" 208 | 209 | "@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7": 210 | version "2.11.7" 211 | resolved "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz" 212 | integrity sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ== 213 | 214 | "@rollup/rollup-android-arm-eabi@4.52.4": 215 | version "4.52.4" 216 | resolved "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz" 217 | integrity sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA== 218 | 219 | "@rollup/rollup-android-arm64@4.52.4": 220 | version "4.52.4" 221 | resolved "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz" 222 | integrity sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w== 223 | 224 | "@rollup/rollup-darwin-arm64@4.52.4": 225 | version "4.52.4" 226 | resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz" 227 | integrity sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg== 228 | 229 | "@rollup/rollup-darwin-x64@4.52.4": 230 | version "4.52.4" 231 | resolved "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz" 232 | integrity sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw== 233 | 234 | "@rollup/rollup-freebsd-arm64@4.52.4": 235 | version "4.52.4" 236 | resolved "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz" 237 | integrity sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ== 238 | 239 | "@rollup/rollup-freebsd-x64@4.52.4": 240 | version "4.52.4" 241 | resolved "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz" 242 | integrity sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw== 243 | 244 | "@rollup/rollup-linux-arm-gnueabihf@4.52.4": 245 | version "4.52.4" 246 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz" 247 | integrity sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ== 248 | 249 | "@rollup/rollup-linux-arm-musleabihf@4.52.4": 250 | version "4.52.4" 251 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz" 252 | integrity sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q== 253 | 254 | "@rollup/rollup-linux-arm64-gnu@4.52.4": 255 | version "4.52.4" 256 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz" 257 | integrity sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg== 258 | 259 | "@rollup/rollup-linux-arm64-musl@4.52.4": 260 | version "4.52.4" 261 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz" 262 | integrity sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g== 263 | 264 | "@rollup/rollup-linux-loong64-gnu@4.52.4": 265 | version "4.52.4" 266 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz" 267 | integrity sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ== 268 | 269 | "@rollup/rollup-linux-ppc64-gnu@4.52.4": 270 | version "4.52.4" 271 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz" 272 | integrity sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g== 273 | 274 | "@rollup/rollup-linux-riscv64-gnu@4.52.4": 275 | version "4.52.4" 276 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz" 277 | integrity sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg== 278 | 279 | "@rollup/rollup-linux-riscv64-musl@4.52.4": 280 | version "4.52.4" 281 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz" 282 | integrity sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA== 283 | 284 | "@rollup/rollup-linux-s390x-gnu@4.52.4": 285 | version "4.52.4" 286 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz" 287 | integrity sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA== 288 | 289 | "@rollup/rollup-linux-x64-gnu@4.52.4": 290 | version "4.52.4" 291 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz" 292 | integrity sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg== 293 | 294 | "@rollup/rollup-linux-x64-musl@4.52.4": 295 | version "4.52.4" 296 | resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz" 297 | integrity sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw== 298 | 299 | "@rollup/rollup-openharmony-arm64@4.52.4": 300 | version "4.52.4" 301 | resolved "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz" 302 | integrity sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA== 303 | 304 | "@rollup/rollup-win32-arm64-msvc@4.52.4": 305 | version "4.52.4" 306 | resolved "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz" 307 | integrity sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ== 308 | 309 | "@rollup/rollup-win32-ia32-msvc@4.52.4": 310 | version "4.52.4" 311 | resolved "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz" 312 | integrity sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw== 313 | 314 | "@rollup/rollup-win32-x64-gnu@4.52.4": 315 | version "4.52.4" 316 | resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz" 317 | integrity sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ== 318 | 319 | "@rollup/rollup-win32-x64-msvc@4.52.4": 320 | version "4.52.4" 321 | resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz" 322 | integrity sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w== 323 | 324 | "@types/estree@1.0.8", "@types/estree@^1.0.0": 325 | version "1.0.8" 326 | resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" 327 | integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== 328 | 329 | "@types/lodash-es@^4.17.12": 330 | version "4.17.12" 331 | resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz" 332 | integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== 333 | dependencies: 334 | "@types/lodash" "*" 335 | 336 | "@types/lodash@*", "@types/lodash@^4.17.20": 337 | version "4.17.20" 338 | resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz" 339 | integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== 340 | 341 | "@types/web-bluetooth@^0.0.16": 342 | version "0.0.16" 343 | resolved "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz" 344 | integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ== 345 | 346 | "@vitejs/plugin-vue@^4.2.3": 347 | version "4.6.2" 348 | resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz" 349 | integrity sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw== 350 | 351 | "@vue/compiler-core@3.5.22": 352 | version "3.5.22" 353 | resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz" 354 | integrity sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ== 355 | dependencies: 356 | "@babel/parser" "^7.28.4" 357 | "@vue/shared" "3.5.22" 358 | entities "^4.5.0" 359 | estree-walker "^2.0.2" 360 | source-map-js "^1.2.1" 361 | 362 | "@vue/compiler-dom@3.5.22": 363 | version "3.5.22" 364 | resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz" 365 | integrity sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA== 366 | dependencies: 367 | "@vue/compiler-core" "3.5.22" 368 | "@vue/shared" "3.5.22" 369 | 370 | "@vue/compiler-sfc@3.5.22": 371 | version "3.5.22" 372 | resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz" 373 | integrity sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ== 374 | dependencies: 375 | "@babel/parser" "^7.28.4" 376 | "@vue/compiler-core" "3.5.22" 377 | "@vue/compiler-dom" "3.5.22" 378 | "@vue/compiler-ssr" "3.5.22" 379 | "@vue/shared" "3.5.22" 380 | estree-walker "^2.0.2" 381 | magic-string "^0.30.19" 382 | postcss "^8.5.6" 383 | source-map-js "^1.2.1" 384 | 385 | "@vue/compiler-ssr@3.5.22": 386 | version "3.5.22" 387 | resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz" 388 | integrity sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww== 389 | dependencies: 390 | "@vue/compiler-dom" "3.5.22" 391 | "@vue/shared" "3.5.22" 392 | 393 | "@vue/devtools-api@^6.6.4": 394 | version "6.6.4" 395 | resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz" 396 | integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g== 397 | 398 | "@vue/reactivity@3.5.22": 399 | version "3.5.22" 400 | resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz" 401 | integrity sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A== 402 | dependencies: 403 | "@vue/shared" "3.5.22" 404 | 405 | "@vue/runtime-core@3.5.22": 406 | version "3.5.22" 407 | resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz" 408 | integrity sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ== 409 | dependencies: 410 | "@vue/reactivity" "3.5.22" 411 | "@vue/shared" "3.5.22" 412 | 413 | "@vue/runtime-dom@3.5.22": 414 | version "3.5.22" 415 | resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz" 416 | integrity sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww== 417 | dependencies: 418 | "@vue/reactivity" "3.5.22" 419 | "@vue/runtime-core" "3.5.22" 420 | "@vue/shared" "3.5.22" 421 | csstype "^3.1.3" 422 | 423 | "@vue/server-renderer@3.5.22": 424 | version "3.5.22" 425 | resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz" 426 | integrity sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ== 427 | dependencies: 428 | "@vue/compiler-ssr" "3.5.22" 429 | "@vue/shared" "3.5.22" 430 | 431 | "@vue/shared@3.5.22": 432 | version "3.5.22" 433 | resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz" 434 | integrity sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w== 435 | 436 | "@vueuse/core@^9.1.0": 437 | version "9.13.0" 438 | resolved "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz" 439 | integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw== 440 | dependencies: 441 | "@types/web-bluetooth" "^0.0.16" 442 | "@vueuse/metadata" "9.13.0" 443 | "@vueuse/shared" "9.13.0" 444 | vue-demi "*" 445 | 446 | "@vueuse/metadata@9.13.0": 447 | version "9.13.0" 448 | resolved "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz" 449 | integrity sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ== 450 | 451 | "@vueuse/shared@9.13.0": 452 | version "9.13.0" 453 | resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz" 454 | integrity sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw== 455 | dependencies: 456 | vue-demi "*" 457 | 458 | acorn@^8.15.0: 459 | version "8.15.0" 460 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" 461 | integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== 462 | 463 | anymatch@~3.1.2: 464 | version "3.1.3" 465 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 466 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 467 | dependencies: 468 | normalize-path "^3.0.0" 469 | picomatch "^2.0.4" 470 | 471 | async-validator@^4.2.5: 472 | version "4.2.5" 473 | resolved "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz" 474 | integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== 475 | 476 | asynckit@^0.4.0: 477 | version "0.4.0" 478 | resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" 479 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 480 | 481 | axios@^1.5.0: 482 | version "1.12.2" 483 | resolved "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz" 484 | integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw== 485 | dependencies: 486 | follow-redirects "^1.15.6" 487 | form-data "^4.0.4" 488 | proxy-from-env "^1.1.0" 489 | 490 | binary-extensions@^2.0.0: 491 | version "2.3.0" 492 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" 493 | integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== 494 | 495 | braces@~3.0.2: 496 | version "3.0.3" 497 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 498 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 499 | dependencies: 500 | fill-range "^7.1.1" 501 | 502 | call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: 503 | version "1.0.2" 504 | resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" 505 | integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== 506 | dependencies: 507 | es-errors "^1.3.0" 508 | function-bind "^1.1.2" 509 | 510 | chokidar@^3.6.0: 511 | version "3.6.0" 512 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" 513 | integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== 514 | dependencies: 515 | anymatch "~3.1.2" 516 | braces "~3.0.2" 517 | glob-parent "~5.1.2" 518 | is-binary-path "~2.1.0" 519 | is-glob "~4.0.1" 520 | normalize-path "~3.0.0" 521 | readdirp "~3.6.0" 522 | optionalDependencies: 523 | fsevents "~2.3.2" 524 | 525 | combined-stream@^1.0.8: 526 | version "1.0.8" 527 | resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" 528 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 529 | dependencies: 530 | delayed-stream "~1.0.0" 531 | 532 | confbox@^0.1.8: 533 | version "0.1.8" 534 | resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" 535 | integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== 536 | 537 | confbox@^0.2.2: 538 | version "0.2.2" 539 | resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110" 540 | integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== 541 | 542 | csstype@^3.1.3: 543 | version "3.1.3" 544 | resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" 545 | integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== 546 | 547 | dayjs@^1.11.13: 548 | version "1.11.18" 549 | resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz" 550 | integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA== 551 | 552 | debug@^4.4.3: 553 | version "4.4.3" 554 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" 555 | integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== 556 | dependencies: 557 | ms "^2.1.3" 558 | 559 | delayed-stream@~1.0.0: 560 | version "1.0.0" 561 | resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" 562 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 563 | 564 | dunder-proto@^1.0.1: 565 | version "1.0.1" 566 | resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" 567 | integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== 568 | dependencies: 569 | call-bind-apply-helpers "^1.0.1" 570 | es-errors "^1.3.0" 571 | gopd "^1.2.0" 572 | 573 | echarts@^5.4.0: 574 | version "5.6.0" 575 | resolved "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz" 576 | integrity sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA== 577 | dependencies: 578 | tslib "2.3.0" 579 | zrender "5.6.1" 580 | 581 | element-plus@^2.11.4: 582 | version "2.11.4" 583 | resolved "https://registry.npmjs.org/element-plus/-/element-plus-2.11.4.tgz" 584 | integrity sha512-sLq+Ypd0cIVilv8wGGMEGvzRVBBsRpJjnAS5PsI/1JU1COZXqzH3N1UYMUc/HCdvdjf6dfrBy80Sj7KcACsT7w== 585 | dependencies: 586 | "@ctrl/tinycolor" "^3.4.1" 587 | "@element-plus/icons-vue" "^2.3.1" 588 | "@floating-ui/dom" "^1.0.1" 589 | "@popperjs/core" "npm:@sxzz/popperjs-es@^2.11.7" 590 | "@types/lodash" "^4.17.20" 591 | "@types/lodash-es" "^4.17.12" 592 | "@vueuse/core" "^9.1.0" 593 | async-validator "^4.2.5" 594 | dayjs "^1.11.13" 595 | escape-html "^1.0.3" 596 | lodash "^4.17.21" 597 | lodash-es "^4.17.21" 598 | lodash-unified "^1.0.3" 599 | memoize-one "^6.0.0" 600 | normalize-wheel-es "^1.2.0" 601 | 602 | entities@^4.5.0: 603 | version "4.5.0" 604 | resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" 605 | integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== 606 | 607 | es-define-property@^1.0.1: 608 | version "1.0.1" 609 | resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" 610 | integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== 611 | 612 | es-errors@^1.3.0: 613 | version "1.3.0" 614 | resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" 615 | integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== 616 | 617 | es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: 618 | version "1.1.1" 619 | resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" 620 | integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== 621 | dependencies: 622 | es-errors "^1.3.0" 623 | 624 | es-set-tostringtag@^2.1.0: 625 | version "2.1.0" 626 | resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" 627 | integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== 628 | dependencies: 629 | es-errors "^1.3.0" 630 | get-intrinsic "^1.2.6" 631 | has-tostringtag "^1.0.2" 632 | hasown "^2.0.2" 633 | 634 | esbuild@^0.21.3: 635 | version "0.21.5" 636 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz" 637 | integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== 638 | optionalDependencies: 639 | "@esbuild/aix-ppc64" "0.21.5" 640 | "@esbuild/android-arm" "0.21.5" 641 | "@esbuild/android-arm64" "0.21.5" 642 | "@esbuild/android-x64" "0.21.5" 643 | "@esbuild/darwin-arm64" "0.21.5" 644 | "@esbuild/darwin-x64" "0.21.5" 645 | "@esbuild/freebsd-arm64" "0.21.5" 646 | "@esbuild/freebsd-x64" "0.21.5" 647 | "@esbuild/linux-arm" "0.21.5" 648 | "@esbuild/linux-arm64" "0.21.5" 649 | "@esbuild/linux-ia32" "0.21.5" 650 | "@esbuild/linux-loong64" "0.21.5" 651 | "@esbuild/linux-mips64el" "0.21.5" 652 | "@esbuild/linux-ppc64" "0.21.5" 653 | "@esbuild/linux-riscv64" "0.21.5" 654 | "@esbuild/linux-s390x" "0.21.5" 655 | "@esbuild/linux-x64" "0.21.5" 656 | "@esbuild/netbsd-x64" "0.21.5" 657 | "@esbuild/openbsd-x64" "0.21.5" 658 | "@esbuild/sunos-x64" "0.21.5" 659 | "@esbuild/win32-arm64" "0.21.5" 660 | "@esbuild/win32-ia32" "0.21.5" 661 | "@esbuild/win32-x64" "0.21.5" 662 | 663 | escape-html@^1.0.3: 664 | version "1.0.3" 665 | resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" 666 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== 667 | 668 | escape-string-regexp@^5.0.0: 669 | version "5.0.0" 670 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" 671 | integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== 672 | 673 | estree-walker@^2.0.2: 674 | version "2.0.2" 675 | resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" 676 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 677 | 678 | estree-walker@^3.0.3: 679 | version "3.0.3" 680 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" 681 | integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== 682 | dependencies: 683 | "@types/estree" "^1.0.0" 684 | 685 | exsolve@^1.0.7: 686 | version "1.0.7" 687 | resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.7.tgz#3b74e4c7ca5c5f9a19c3626ca857309fa99f9e9e" 688 | integrity sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw== 689 | 690 | fdir@^6.5.0: 691 | version "6.5.0" 692 | resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" 693 | integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== 694 | 695 | fill-range@^7.1.1: 696 | version "7.1.1" 697 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 698 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 699 | dependencies: 700 | to-regex-range "^5.0.1" 701 | 702 | follow-redirects@^1.15.6: 703 | version "1.15.11" 704 | resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" 705 | integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== 706 | 707 | form-data@^4.0.4: 708 | version "4.0.4" 709 | resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz" 710 | integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== 711 | dependencies: 712 | asynckit "^0.4.0" 713 | combined-stream "^1.0.8" 714 | es-set-tostringtag "^2.1.0" 715 | hasown "^2.0.2" 716 | mime-types "^2.1.12" 717 | 718 | fsevents@~2.3.2, fsevents@~2.3.3: 719 | version "2.3.3" 720 | resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" 721 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 722 | 723 | function-bind@^1.1.2: 724 | version "1.1.2" 725 | resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" 726 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 727 | 728 | get-intrinsic@^1.2.6: 729 | version "1.3.0" 730 | resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" 731 | integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== 732 | dependencies: 733 | call-bind-apply-helpers "^1.0.2" 734 | es-define-property "^1.0.1" 735 | es-errors "^1.3.0" 736 | es-object-atoms "^1.1.1" 737 | function-bind "^1.1.2" 738 | get-proto "^1.0.1" 739 | gopd "^1.2.0" 740 | has-symbols "^1.1.0" 741 | hasown "^2.0.2" 742 | math-intrinsics "^1.1.0" 743 | 744 | get-proto@^1.0.1: 745 | version "1.0.1" 746 | resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" 747 | integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== 748 | dependencies: 749 | dunder-proto "^1.0.1" 750 | es-object-atoms "^1.0.0" 751 | 752 | glob-parent@~5.1.2: 753 | version "5.1.2" 754 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 755 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 756 | dependencies: 757 | is-glob "^4.0.1" 758 | 759 | gopd@^1.2.0: 760 | version "1.2.0" 761 | resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" 762 | integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== 763 | 764 | has-symbols@^1.0.3, has-symbols@^1.1.0: 765 | version "1.1.0" 766 | resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" 767 | integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== 768 | 769 | has-tostringtag@^1.0.2: 770 | version "1.0.2" 771 | resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" 772 | integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== 773 | dependencies: 774 | has-symbols "^1.0.3" 775 | 776 | hasown@^2.0.2: 777 | version "2.0.2" 778 | resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" 779 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== 780 | dependencies: 781 | function-bind "^1.1.2" 782 | 783 | is-binary-path@~2.1.0: 784 | version "2.1.0" 785 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 786 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 787 | dependencies: 788 | binary-extensions "^2.0.0" 789 | 790 | is-extglob@^2.1.1: 791 | version "2.1.1" 792 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 793 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 794 | 795 | is-glob@^4.0.1, is-glob@~4.0.1: 796 | version "4.0.3" 797 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 798 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 799 | dependencies: 800 | is-extglob "^2.1.1" 801 | 802 | is-number@^7.0.0: 803 | version "7.0.0" 804 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 805 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 806 | 807 | js-tokens@^9.0.1: 808 | version "9.0.1" 809 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" 810 | integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== 811 | 812 | local-pkg@^1.1.2: 813 | version "1.1.2" 814 | resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.2.tgz#c03d208787126445303f8161619dc701afa4abb5" 815 | integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A== 816 | dependencies: 817 | mlly "^1.7.4" 818 | pkg-types "^2.3.0" 819 | quansync "^0.2.11" 820 | 821 | lodash-es@^4.17.21: 822 | version "4.17.21" 823 | resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" 824 | integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== 825 | 826 | lodash-unified@^1.0.3: 827 | version "1.0.3" 828 | resolved "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz" 829 | integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ== 830 | 831 | lodash@^4.17.21: 832 | version "4.17.21" 833 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" 834 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 835 | 836 | magic-string@^0.30.19: 837 | version "0.30.19" 838 | resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz" 839 | integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== 840 | dependencies: 841 | "@jridgewell/sourcemap-codec" "^1.5.5" 842 | 843 | math-intrinsics@^1.1.0: 844 | version "1.1.0" 845 | resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" 846 | integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== 847 | 848 | memoize-one@^6.0.0: 849 | version "6.0.0" 850 | resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz" 851 | integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== 852 | 853 | mime-db@1.52.0: 854 | version "1.52.0" 855 | resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" 856 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 857 | 858 | mime-types@^2.1.12: 859 | version "2.1.35" 860 | resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" 861 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 862 | dependencies: 863 | mime-db "1.52.0" 864 | 865 | mlly@^1.7.4, mlly@^1.8.0: 866 | version "1.8.0" 867 | resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e" 868 | integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== 869 | dependencies: 870 | acorn "^8.15.0" 871 | pathe "^2.0.3" 872 | pkg-types "^1.3.1" 873 | ufo "^1.6.1" 874 | 875 | ms@^2.1.3: 876 | version "2.1.3" 877 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 878 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 879 | 880 | nanoid@^3.3.11: 881 | version "3.3.11" 882 | resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" 883 | integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== 884 | 885 | normalize-path@^3.0.0, normalize-path@~3.0.0: 886 | version "3.0.0" 887 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 888 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 889 | 890 | normalize-wheel-es@^1.2.0: 891 | version "1.2.0" 892 | resolved "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz" 893 | integrity sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw== 894 | 895 | pathe@^2.0.1, pathe@^2.0.3: 896 | version "2.0.3" 897 | resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" 898 | integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== 899 | 900 | picocolors@^1.1.1: 901 | version "1.1.1" 902 | resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" 903 | integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== 904 | 905 | picomatch@^2.0.4, picomatch@^2.2.1: 906 | version "2.3.1" 907 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 908 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 909 | 910 | picomatch@^4.0.3: 911 | version "4.0.3" 912 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" 913 | integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== 914 | 915 | pkg-types@^1.3.1: 916 | version "1.3.1" 917 | resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" 918 | integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== 919 | dependencies: 920 | confbox "^0.1.8" 921 | mlly "^1.7.4" 922 | pathe "^2.0.1" 923 | 924 | pkg-types@^2.3.0: 925 | version "2.3.0" 926 | resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.3.0.tgz#037f2c19bd5402966ff6810e32706558cb5b5726" 927 | integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== 928 | dependencies: 929 | confbox "^0.2.2" 930 | exsolve "^1.0.7" 931 | pathe "^2.0.3" 932 | 933 | postcss@^8.4.43, postcss@^8.5.6: 934 | version "8.5.6" 935 | resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz" 936 | integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== 937 | dependencies: 938 | nanoid "^3.3.11" 939 | picocolors "^1.1.1" 940 | source-map-js "^1.2.1" 941 | 942 | proxy-from-env@^1.1.0: 943 | version "1.1.0" 944 | resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" 945 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 946 | 947 | quansync@^0.2.11: 948 | version "0.2.11" 949 | resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.11.tgz#f9c3adda2e1272e4f8cf3f1457b04cbdb4ee692a" 950 | integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA== 951 | 952 | readdirp@~3.6.0: 953 | version "3.6.0" 954 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 955 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 956 | dependencies: 957 | picomatch "^2.2.1" 958 | 959 | rollup@^4.20.0: 960 | version "4.52.4" 961 | resolved "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz" 962 | integrity sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ== 963 | dependencies: 964 | "@types/estree" "1.0.8" 965 | optionalDependencies: 966 | "@rollup/rollup-android-arm-eabi" "4.52.4" 967 | "@rollup/rollup-android-arm64" "4.52.4" 968 | "@rollup/rollup-darwin-arm64" "4.52.4" 969 | "@rollup/rollup-darwin-x64" "4.52.4" 970 | "@rollup/rollup-freebsd-arm64" "4.52.4" 971 | "@rollup/rollup-freebsd-x64" "4.52.4" 972 | "@rollup/rollup-linux-arm-gnueabihf" "4.52.4" 973 | "@rollup/rollup-linux-arm-musleabihf" "4.52.4" 974 | "@rollup/rollup-linux-arm64-gnu" "4.52.4" 975 | "@rollup/rollup-linux-arm64-musl" "4.52.4" 976 | "@rollup/rollup-linux-loong64-gnu" "4.52.4" 977 | "@rollup/rollup-linux-ppc64-gnu" "4.52.4" 978 | "@rollup/rollup-linux-riscv64-gnu" "4.52.4" 979 | "@rollup/rollup-linux-riscv64-musl" "4.52.4" 980 | "@rollup/rollup-linux-s390x-gnu" "4.52.4" 981 | "@rollup/rollup-linux-x64-gnu" "4.52.4" 982 | "@rollup/rollup-linux-x64-musl" "4.52.4" 983 | "@rollup/rollup-openharmony-arm64" "4.52.4" 984 | "@rollup/rollup-win32-arm64-msvc" "4.52.4" 985 | "@rollup/rollup-win32-ia32-msvc" "4.52.4" 986 | "@rollup/rollup-win32-x64-gnu" "4.52.4" 987 | "@rollup/rollup-win32-x64-msvc" "4.52.4" 988 | fsevents "~2.3.2" 989 | 990 | scule@^1.3.0: 991 | version "1.3.0" 992 | resolved "https://registry.yarnpkg.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3" 993 | integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g== 994 | 995 | source-map-js@^1.2.1: 996 | version "1.2.1" 997 | resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" 998 | integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== 999 | 1000 | strip-literal@^3.1.0: 1001 | version "3.1.0" 1002 | resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.1.0.tgz#222b243dd2d49c0bcd0de8906adbd84177196032" 1003 | integrity sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg== 1004 | dependencies: 1005 | js-tokens "^9.0.1" 1006 | 1007 | tinyglobby@^0.2.15: 1008 | version "0.2.15" 1009 | resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" 1010 | integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== 1011 | dependencies: 1012 | fdir "^6.5.0" 1013 | picomatch "^4.0.3" 1014 | 1015 | to-regex-range@^5.0.1: 1016 | version "5.0.1" 1017 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1018 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1019 | dependencies: 1020 | is-number "^7.0.0" 1021 | 1022 | tslib@2.3.0: 1023 | version "2.3.0" 1024 | resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz" 1025 | integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== 1026 | 1027 | ufo@^1.6.1: 1028 | version "1.6.1" 1029 | resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" 1030 | integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== 1031 | 1032 | unimport@^5.4.0: 1033 | version "5.4.1" 1034 | resolved "https://registry.yarnpkg.com/unimport/-/unimport-5.4.1.tgz#7c573161ed9a207ef59146dab6d5c7592361a21a" 1035 | integrity sha512-wMZ2JKUCleCK2zfRHeWcbrUHKXOC3SVBYkyn/wTGzh0THX6sT4hSjuKXxKANN4/WMbT6ZPM4JzcDcnhD2x9Bpg== 1036 | dependencies: 1037 | acorn "^8.15.0" 1038 | escape-string-regexp "^5.0.0" 1039 | estree-walker "^3.0.3" 1040 | local-pkg "^1.1.2" 1041 | magic-string "^0.30.19" 1042 | mlly "^1.8.0" 1043 | pathe "^2.0.3" 1044 | picomatch "^4.0.3" 1045 | pkg-types "^2.3.0" 1046 | scule "^1.3.0" 1047 | strip-literal "^3.1.0" 1048 | tinyglobby "^0.2.15" 1049 | unplugin "^2.3.10" 1050 | unplugin-utils "^0.3.0" 1051 | 1052 | unplugin-auto-import@^20.2.0: 1053 | version "20.2.0" 1054 | resolved "https://registry.yarnpkg.com/unplugin-auto-import/-/unplugin-auto-import-20.2.0.tgz#6bf2fc4dba8a596f34ce8c50d635b8433c2157db" 1055 | integrity sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug== 1056 | dependencies: 1057 | local-pkg "^1.1.2" 1058 | magic-string "^0.30.19" 1059 | picomatch "^4.0.3" 1060 | unimport "^5.4.0" 1061 | unplugin "^2.3.10" 1062 | unplugin-utils "^0.3.0" 1063 | 1064 | unplugin-utils@^0.3.0: 1065 | version "0.3.1" 1066 | resolved "https://registry.yarnpkg.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz#ef2873670a6a2a21bd2c9d31307257cc863a709c" 1067 | integrity sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog== 1068 | dependencies: 1069 | pathe "^2.0.3" 1070 | picomatch "^4.0.3" 1071 | 1072 | unplugin-vue-components@^29.1.0: 1073 | version "29.1.0" 1074 | resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-29.1.0.tgz#6064dfd43ebfc2d54fc04d6c391d30c9f66d3360" 1075 | integrity sha512-z/9ACPXth199s9aCTCdKZAhe5QGOpvzJYP+Hkd0GN1/PpAmsu+W3UlRY3BJAewPqQxh5xi56+Og6mfiCV1Jzpg== 1076 | dependencies: 1077 | chokidar "^3.6.0" 1078 | debug "^4.4.3" 1079 | local-pkg "^1.1.2" 1080 | magic-string "^0.30.19" 1081 | mlly "^1.8.0" 1082 | tinyglobby "^0.2.15" 1083 | unplugin "^2.3.10" 1084 | unplugin-utils "^0.3.0" 1085 | 1086 | unplugin@^2.3.10: 1087 | version "2.3.10" 1088 | resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.10.tgz#15e75fec9384743335be7e54e5c88b5c187a3e94" 1089 | integrity sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw== 1090 | dependencies: 1091 | "@jridgewell/remapping" "^2.3.5" 1092 | acorn "^8.15.0" 1093 | picomatch "^4.0.3" 1094 | webpack-virtual-modules "^0.6.2" 1095 | 1096 | vite@^5.3.0: 1097 | version "5.4.20" 1098 | resolved "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz" 1099 | integrity sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g== 1100 | dependencies: 1101 | esbuild "^0.21.3" 1102 | postcss "^8.4.43" 1103 | rollup "^4.20.0" 1104 | optionalDependencies: 1105 | fsevents "~2.3.3" 1106 | 1107 | vue-demi@*: 1108 | version "0.14.10" 1109 | resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz" 1110 | integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg== 1111 | 1112 | vue-router@^4.2.2: 1113 | version "4.5.1" 1114 | resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz" 1115 | integrity sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw== 1116 | dependencies: 1117 | "@vue/devtools-api" "^6.6.4" 1118 | 1119 | vue@^3.3.4: 1120 | version "3.5.22" 1121 | resolved "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz" 1122 | integrity sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ== 1123 | dependencies: 1124 | "@vue/compiler-dom" "3.5.22" 1125 | "@vue/compiler-sfc" "3.5.22" 1126 | "@vue/runtime-dom" "3.5.22" 1127 | "@vue/server-renderer" "3.5.22" 1128 | "@vue/shared" "3.5.22" 1129 | 1130 | webpack-virtual-modules@^0.6.2: 1131 | version "0.6.2" 1132 | resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" 1133 | integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== 1134 | 1135 | zrender@5.6.1: 1136 | version "5.6.1" 1137 | resolved "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz" 1138 | integrity sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag== 1139 | dependencies: 1140 | tslib "2.3.0" 1141 | --------------------------------------------------------------------------------