Initial commit: 可转债轮动分析系统
- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换) - 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制) - 增量同步:支持 -incr/-since 参数按存档日期增量入库 - 策略回测:净值曲线/10指标/基准对比,含基本面过滤 - 用户认证:用户名密码 + 邮箱验证码双登录方式 - 前端:三Tab SPA(实时行情/策略回测/数据管理)
This commit is contained in:
102
cmd/fundamentals/main.go
Normal file
102
cmd/fundamentals/main.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// fundamentals 是正股基本面数据入库命令:
|
||||
//
|
||||
// go run ./cmd/fundamentals [-db 库路径]
|
||||
//
|
||||
// 从 convert_bond 表读取所有正股代码,通过东财 clist 接口批量拉取
|
||||
// 基本面数据(ROE/资产负债率/净利润/ST状态),写入 stock_fundamental 表。
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"stock-analysis/internal/datasource/eastmoney"
|
||||
"stock-analysis/internal/storage/sqlite"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbPath := flag.String("db", defaultDBPath(), "SQLite 数据库文件路径")
|
||||
flag.Parse()
|
||||
|
||||
store, err := sqlite.Open(*dbPath)
|
||||
if err != nil {
|
||||
fatal("打开数据库失败: ", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 1. 获取所有正股代码。
|
||||
stock2bond, err := store.GetStockBondMap(ctx)
|
||||
if err != nil {
|
||||
fatal("获取正股映射失败: ", err)
|
||||
}
|
||||
if len(stock2bond) == 0 {
|
||||
fmt.Println("convert_bond 表为空,请先执行 go run ./cmd/ingest -all 入库转债数据")
|
||||
return
|
||||
}
|
||||
|
||||
stockCodes := make([]string, 0, len(stock2bond))
|
||||
for code := range stock2bond {
|
||||
stockCodes = append(stockCodes, code)
|
||||
}
|
||||
fmt.Printf("正股数量:%d\n", len(stockCodes))
|
||||
|
||||
// 2. 分批拉取基本面数据(每批最多 200 只)。
|
||||
client := eastmoney.NewClient()
|
||||
const batchSize = 200
|
||||
|
||||
var totalSaved, totalFail int
|
||||
for i := 0; i < len(stockCodes); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(stockCodes) {
|
||||
end = len(stockCodes)
|
||||
}
|
||||
batch := stockCodes[i:end]
|
||||
|
||||
funds, err := client.FetchStockFundamentals(ctx, batch)
|
||||
if err != nil {
|
||||
fmt.Printf("批次 %d-%d 拉取失败: %v\n", i, end-1, err)
|
||||
totalFail += len(batch)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range funds {
|
||||
f.UpdateDate = time.Now().Format("2006-01-02")
|
||||
if err := store.SaveStockFundamental(ctx, f); err != nil {
|
||||
fmt.Printf(" %s %s: 保存失败 %v\n", f.Code, f.Name, err)
|
||||
totalFail++
|
||||
continue
|
||||
}
|
||||
totalSaved++
|
||||
}
|
||||
|
||||
fmt.Printf("批次 %d-%d: 拉取 %d 只, 已保存 %d\n", i+1, end, len(batch), totalSaved)
|
||||
|
||||
// 控制请求频率。
|
||||
if end < len(stockCodes) {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n基本面入库完成:成功 %d 只,失败 %d 只(库: %s)\n", totalSaved, totalFail, *dbPath)
|
||||
}
|
||||
|
||||
// defaultDBPath 返回默认数据库路径。
|
||||
func defaultDBPath() string {
|
||||
local := os.Getenv("LOCALAPPDATA")
|
||||
if local == "" {
|
||||
local = os.TempDir()
|
||||
}
|
||||
return filepath.Join(local, "stock-analysis", "stock.db")
|
||||
}
|
||||
|
||||
// fatal 打印错误并退出。
|
||||
func fatal(msg string, err error) {
|
||||
fmt.Fprintln(os.Stderr, msg, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user