├── 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 |
2 |
3 |
4 |
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 |
2 |
9 |
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 |
2 |
3 |
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 |
2 |
3 |
DSL策略测试器
4 |
5 |
6 |
{{ result }}
7 |
8 |
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 |
2 |
3 |
符合策略的股票列表
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
2 |
8 |
9 |
10 |
41 |
--------------------------------------------------------------------------------
/frontend/src/views/WatchlistView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
自选股
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ scope.row.symbol }}
11 |
12 |
13 |
14 |
15 |
16 |
17 | 移除
18 |
19 |
20 |
21 |
22 |
23 |
24 |
52 |
--------------------------------------------------------------------------------
/frontend/src/views/BoardView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
按板块浏览
4 |
5 |
6 |
7 |
8 |
{{ selected }}
9 |
10 |
11 |
12 |
13 |
14 |
15 | 加入自选
16 |
17 |
18 |
19 |
20 |
21 |
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 |
2 |
3 |
股票池
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ scope.row.code }}
13 |
14 |
15 |
16 |
17 | {{ scope.row.name }}
18 |
19 |
20 |
21 |
22 |
23 | 加入自选
24 |
25 |
26 |
27 |
28 |
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 |
2 |
3 |
策略编辑器(内测)
4 |
5 |
6 |
11 |
12 |
13 |
14 |
15 |
16 |
运行结果(命中列表)
17 |
执行中...
18 |
21 |
{{ err }}
22 |
耗时: {{ duration }} ms
23 |
24 |
25 |
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 |
2 |
3 |
动态策略列表
4 |
新增策略
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 编辑
15 | 删除
16 | 测试
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 取消
31 | 保存
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
测试结果:
52 |
{{ testResult }}
53 |
54 |
55 | 关闭
56 | 运行
57 |
58 |
59 |
60 |
61 |
62 |
148 |
149 |
--------------------------------------------------------------------------------
/frontend/src/components/TimelineChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
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 |
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 |
2 |
3 |
个股详情 - {{ stock.code || symbol }}
4 |
加载中...
5 |
6 |
代码: {{ stock.code }}
7 |
名称: {{ stock.name }}
8 |
现价: {{ stock.trade }}
9 |
板块: {{ stock.board }}
10 |
11 |
12 |
27 |
28 |
34 |
38 |
39 |
40 |
41 |
42 |
43 |
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 |
--------------------------------------------------------------------------------