mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
feat(stock):添加香港股票数据支持
- 新增 StockInfoHK模型用于存储香港股票基本信息- 实现香港股票数据的爬取和解析功能 - 更新数据库初始化逻辑,支持香港股票数据导入 - 修改股票价格信息获取接口,支持香港股票 - 优化股票数据解析逻辑,适配香港股票数据格式
This commit is contained in:
parent
4c249f0806
commit
a6f17c632e
@ -30,12 +30,14 @@
|
||||
## 🧩 功能开发计划
|
||||
| 功能说明 | 状态 | 备注 |
|
||||
|-----------------|----|---------------------------------------------------------------------------------------------------------|
|
||||
| 港股支持 | 🚧 | 港股数据支持 |
|
||||
| 港股支持 | ✅ | 港股数据支持 |
|
||||
| 多轮对话 | ✅ | AI分析后可继续对话提问 |
|
||||
| 自定义AI分析提问模板 | ✅ | 可配置的提问模板 [v2025.2.12.7-alpha](https://github.com/ArvinLovegood/go-stock/releases/tag/v2025.2.12.7-alpha) |
|
||||
| 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 |
|
||||
|
||||
## 👀 更新日志
|
||||
### 2025.02.22 港股数据支持
|
||||
|
||||
### 2025.02.16 AI分析后可继续对话提问
|
||||
- [v2025.2.16.1-alpha](https://github.com/ArvinLovegood/go-stock/releases/tag/v2025.2.16.1-alpha)
|
||||
|
||||
|
@ -2,9 +2,12 @@ package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -116,3 +119,50 @@ func TestGetHtmlWithActions(t *testing.T) {
|
||||
}
|
||||
//logger.SugaredLogger.Infof("htmlContent:%s", htmlContent)
|
||||
}
|
||||
|
||||
func TestHk(t *testing.T) {
|
||||
//https://stock.finance.sina.com.cn/hkstock/quotes/00001.html
|
||||
db.Init("../../data/stock.db")
|
||||
hks := &[]models.StockInfoHK{}
|
||||
db.Dao.Model(&models.StockInfoHK{}).Limit(1).Find(hks)
|
||||
|
||||
crawlerAPI := CrawlerApi{}
|
||||
crawlerBaseInfo := CrawlerBaseInfo{
|
||||
Name: "TestCrawler",
|
||||
Description: "Test Crawler Description",
|
||||
BaseUrl: "https://stock.finance.sina.com.cn",
|
||||
Headers: map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"},
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute)
|
||||
defer cancel()
|
||||
crawlerAPI = crawlerAPI.NewCrawler(ctx, crawlerBaseInfo)
|
||||
|
||||
for _, hk := range *hks {
|
||||
logger.SugaredLogger.Infof("hk: %+v", hk)
|
||||
url := fmt.Sprintf("https://stock.finance.sina.com.cn/hkstock/quotes/%s.html", strings.ReplaceAll(hk.Code, ".HK", ""))
|
||||
htmlContent, ok := crawlerAPI.GetHtml(url, "#stock_cname", true)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
//logger.SugaredLogger.Infof("htmlContent: %s", htmlContent)
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
}
|
||||
document.Find("#stock_cname").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Infof("股票名称-:%s", text)
|
||||
})
|
||||
|
||||
document.Find("#mts_stock_hk_price").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Infof("股票名称-现价: %s", text)
|
||||
})
|
||||
|
||||
document.Find(".deta_hqContainer >.deta03 li").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Infof("股票名称-%s: %s", "", text)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -442,6 +442,14 @@ func SearchGuShiTongStockInfo(stock string, crawlTimeOut int64) *[]string {
|
||||
Headers: map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"},
|
||||
})
|
||||
url := "https://gushitong.baidu.com/stock/ab-" + RemoveAllNonDigitChar(stock)
|
||||
|
||||
if strutil.HasPrefixAny(stock, []string{"HK", "hk"}) {
|
||||
url = "https://gushitong.baidu.com/stock/hk-" + RemoveAllNonDigitChar(stock)
|
||||
}
|
||||
if strutil.HasPrefixAny(stock, []string{"SZ", "SH", "sh", "sz"}) {
|
||||
url = "https://gushitong.baidu.com/stock/ab-" + RemoveAllNonDigitChar(stock)
|
||||
}
|
||||
|
||||
logger.SugaredLogger.Infof("SearchGuShiTongStockInfo搜索股票-%s: %s", stock, url)
|
||||
actions := []chromedp.Action{
|
||||
chromedp.Navigate(url),
|
||||
@ -471,6 +479,11 @@ func SearchGuShiTongStockInfo(stock string, crawlTimeOut int64) *[]string {
|
||||
}
|
||||
|
||||
func GetFinancialReports(stockCode string, crawlTimeOut int64) *[]string {
|
||||
if strutil.HasPrefixAny(stockCode, []string{"HK", "hk"}) {
|
||||
stockCode = strings.ReplaceAll(stockCode, "hk", "")
|
||||
stockCode = strings.ReplaceAll(stockCode, "HK", "")
|
||||
}
|
||||
|
||||
// 创建一个 chromedp 上下文
|
||||
timeoutCtx, timeoutCtxCancel := context.WithTimeout(context.Background(), time.Duration(crawlTimeOut)*time.Second)
|
||||
defer timeoutCtxCancel()
|
||||
|
@ -21,3 +21,9 @@ func TestNewDeepSeekOpenAiConfig(t *testing.T) {
|
||||
func TestGetTopNewsList(t *testing.T) {
|
||||
GetTopNewsList(30)
|
||||
}
|
||||
|
||||
func TestSearchGuShiTongStockInfo(t *testing.T) {
|
||||
SearchGuShiTongStockInfo("hk01810", 60)
|
||||
SearchGuShiTongStockInfo("sh600745", 60)
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
@ -387,6 +388,9 @@ func (receiver StockDataApi) GetStockList(key string) []StockBasic {
|
||||
var result2 []IndexBasic
|
||||
db.Dao.Model(&IndexBasic{}).Where("market in ?", []string{"SSE", "SZSE"}).Where("name like ? or ts_code like ?", "%"+key+"%", "%"+key+"%").Find(&result2)
|
||||
|
||||
var result3 []models.StockInfoHK
|
||||
db.Dao.Model(&models.StockInfoHK{}).Where("name like ? or code like ?", "%"+key+"%", "%"+key+"%").Find(&result3)
|
||||
|
||||
for _, item := range result2 {
|
||||
result = append(result, StockBasic{
|
||||
TsCode: item.TsCode,
|
||||
@ -397,6 +401,14 @@ func (receiver StockDataApi) GetStockList(key string) []StockBasic {
|
||||
ListDate: item.ListDate,
|
||||
})
|
||||
}
|
||||
for _, item := range result3 {
|
||||
result = append(result, StockBasic{
|
||||
TsCode: item.Code,
|
||||
Name: item.Name,
|
||||
Fullname: item.Name,
|
||||
Market: "HK",
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@ -416,6 +428,73 @@ func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
||||
if len(datas) < 2 {
|
||||
return nil, fmt.Errorf("invalid data format")
|
||||
}
|
||||
var result map[string]string
|
||||
var err error
|
||||
if strutil.ContainsAny(datas[0], []string{"hq_str_sz", "hq_str_sh"}) {
|
||||
result, err = ParseSHSZStockData(datas)
|
||||
}
|
||||
if strutil.ContainsAny(datas[0], []string{"hq_str_hk"}) {
|
||||
result, err = ParseHKStockData(datas)
|
||||
}
|
||||
|
||||
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
||||
marshal, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stockInfo := &StockInfo{}
|
||||
err = json.Unmarshal(marshal, &stockInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//logger.SugaredLogger.Infof("股票数据解析完成stockInfo: %+v", stockInfo)
|
||||
|
||||
return stockInfo, nil
|
||||
}
|
||||
|
||||
func ParseHKStockData(datas []string) (map[string]string, error) {
|
||||
code := strings.Split(datas[0], "hq_str_")[1]
|
||||
result := make(map[string]string)
|
||||
parts := strutil.SplitAndTrim(datas[1], ",", "\"")
|
||||
//parts := strings.Split(data, ",")
|
||||
if len(parts) < 19 {
|
||||
return nil, fmt.Errorf("invalid data format")
|
||||
}
|
||||
/*
|
||||
XIAOMI-W, 0
|
||||
小米集团-W, 1 股票名称
|
||||
50.050, 2 今日开盘价
|
||||
49.150, 3 昨日收盘价
|
||||
51.950, 4 今日最高价
|
||||
49.700, 5 今日最低价
|
||||
51.700, 6 当前价格
|
||||
2.550, 7 涨跌额
|
||||
5.188, 8 涨跌幅
|
||||
51.65000, 9
|
||||
51.70000, 10
|
||||
15770408249, 11 成交额
|
||||
308362585, 12 成交量
|
||||
0.000, 13
|
||||
0.000, 14
|
||||
51.950, 15 52周最高
|
||||
12.560, 16 52周最低
|
||||
2025/02/21, 17
|
||||
16:08 18
|
||||
*/
|
||||
result["股票代码"] = code
|
||||
result["股票名称"] = parts[1]
|
||||
result["今日开盘价"] = parts[2]
|
||||
result["昨日收盘价"] = parts[3]
|
||||
result["今日最高价"] = parts[4]
|
||||
result["今日最低价"] = parts[5]
|
||||
result["当前价格"] = parts[6]
|
||||
result["日期"] = parts[17]
|
||||
result["时间"] = parts[18]
|
||||
logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ParseSHSZStockData(datas []string) (map[string]string, error) {
|
||||
code := strings.Split(datas[0], "hq_str_")[1]
|
||||
result := make(map[string]string)
|
||||
parts := strutil.SplitAndTrim(datas[1], ",", "\"")
|
||||
@ -482,19 +561,7 @@ func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
||||
result["卖五报价"] = parts[29]
|
||||
result["日期"] = parts[30]
|
||||
result["时间"] = parts[31]
|
||||
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
||||
marshal, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stockInfo := &StockInfo{}
|
||||
err = json.Unmarshal(marshal, &stockInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//logger.SugaredLogger.Infof("股票数据解析完成stockInfo: %+v", stockInfo)
|
||||
|
||||
return stockInfo, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type IndexBasic struct {
|
||||
@ -519,6 +586,63 @@ func (IndexBasic) TableName() string {
|
||||
}
|
||||
|
||||
func SearchStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
|
||||
|
||||
if strutil.HasPrefixAny(stockCode, []string{"HK", "hk"}) {
|
||||
return getHKStockPriceInfo(stockCode, crawlTimeOut)
|
||||
}
|
||||
if strutil.HasPrefixAny(stockCode, []string{"SZ", "SH", "sh", "sz"}) {
|
||||
return getSHSZStockPriceInfo(stockCode, crawlTimeOut)
|
||||
}
|
||||
return &[]string{}
|
||||
}
|
||||
|
||||
func getHKStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
|
||||
var messages []string
|
||||
crawlerAPI := CrawlerApi{}
|
||||
crawlerBaseInfo := CrawlerBaseInfo{
|
||||
Name: "SinaCrawler",
|
||||
Description: "SinaCrawler Crawler Description",
|
||||
BaseUrl: "https://stock.finance.sina.com.cn",
|
||||
Headers: map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"},
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(crawlTimeOut)*time.Second)
|
||||
defer cancel()
|
||||
crawlerAPI = crawlerAPI.NewCrawler(ctx, crawlerBaseInfo)
|
||||
|
||||
url := fmt.Sprintf("https://stock.finance.sina.com.cn/hkstock/quotes/%s.html", strings.ReplaceAll(stockCode, "hk", ""))
|
||||
htmlContent, ok := crawlerAPI.GetHtml(url, ".deta_hqContainer >.deta03 ", true)
|
||||
if !ok {
|
||||
return &[]string{}
|
||||
}
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
}
|
||||
stockName := ""
|
||||
stockPrice := ""
|
||||
document.Find("#stock_cname").Each(func(i int, selection *goquery.Selection) {
|
||||
stockName = strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Infof("股票名称-:%s", stockName)
|
||||
})
|
||||
|
||||
document.Find("#mts_stock_hk_price").Each(func(i int, selection *goquery.Selection) {
|
||||
stockPrice = strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Infof("股票名称-现价: %s", stockPrice)
|
||||
})
|
||||
|
||||
messages = append(messages, fmt.Sprintf("%s现价%s", stockName, stockPrice))
|
||||
|
||||
document.Find(".deta_hqContainer >.deta03 li").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Infof("股票名称-%s: %s", stockName, text)
|
||||
messages = append(messages, text)
|
||||
})
|
||||
|
||||
return &messages
|
||||
}
|
||||
|
||||
func getSHSZStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
|
||||
var messages []string
|
||||
url := "https://www.cls.cn/stock?code=" + stockCode
|
||||
// 创建一个 chromedp 上下文
|
||||
timeoutCtx, timeoutCtxCancel := context.WithTimeout(context.Background(), time.Duration(crawlTimeOut)*time.Second)
|
||||
@ -570,7 +694,7 @@ func SearchStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &[]string{}
|
||||
}
|
||||
var messages []string
|
||||
|
||||
document.Find("div.quote-text-border,span.quote-price").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Info(text)
|
||||
|
@ -24,7 +24,9 @@ func TestGetTelegraph(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFinancialReports(t *testing.T) {
|
||||
GetFinancialReports("sz000802", 30)
|
||||
//GetFinancialReports("sz000802", 30)
|
||||
GetFinancialReports("hk00927", 30)
|
||||
|
||||
}
|
||||
|
||||
func TestGetTelegraphSearch(t *testing.T) {
|
||||
@ -41,7 +43,8 @@ func TestSearchStockInfoByCode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSearchStockPriceInfo(t *testing.T) {
|
||||
SearchStockPriceInfo("sh600745", 30)
|
||||
SearchStockPriceInfo("hk00927", 30)
|
||||
SearchStockPriceInfo("sh600859", 30)
|
||||
}
|
||||
|
||||
func TestParseFullSingleStockData(t *testing.T) {
|
||||
@ -49,7 +52,7 @@ func TestParseFullSingleStockData(t *testing.T) {
|
||||
SetHeader("Host", "hq.sinajs.cn").
|
||||
SetHeader("Referer", "https://finance.sina.com.cn/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
|
||||
Get(fmt.Sprintf(sinaStockUrl, time.Now().Unix(), "sh600859,sh600745"))
|
||||
Get(fmt.Sprintf(sinaStockUrl, time.Now().Unix(), "sh600859,sz000034,hk01810,hk00856"))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
}
|
||||
@ -57,6 +60,11 @@ func TestParseFullSingleStockData(t *testing.T) {
|
||||
strs := strutil.SplitEx(data, "\n", true)
|
||||
for _, str := range strs {
|
||||
logger.SugaredLogger.Info(str)
|
||||
stockData, err := ParseFullSingleStockData(str)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger.SugaredLogger.Infof("%+#v", stockData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package data
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
@ -30,10 +31,12 @@ func (receiver TushareApi) GetDaily(tsCode, startDate, endDate string, crawlTime
|
||||
logger.SugaredLogger.Debugf("tushare daily request: ts_code=%s, start_date=%s, end_date=%s", tsCode, startDate, endDate)
|
||||
fields := "ts_code,trade_date,open,high,low,close,pre_close,change,pct_chg,vol,amount"
|
||||
resp := &TushareStockBasicResponse{}
|
||||
stockType := getStockType(tsCode)
|
||||
logger.SugaredLogger.Debugf("tushare daily request: %s", stockType)
|
||||
_, err := receiver.client.SetTimeout(time.Duration(crawlTimeOut)*time.Second).R().
|
||||
SetHeader("content-type", "application/json").
|
||||
SetBody(&TushareRequest{
|
||||
ApiName: "daily",
|
||||
ApiName: stockType,
|
||||
Token: receiver.config.TushareToken,
|
||||
Params: map[string]any{
|
||||
"ts_code": tsCode,
|
||||
@ -64,3 +67,13 @@ func (receiver TushareApi) GetDaily(tsCode, startDate, endDate string, crawlTime
|
||||
logger.SugaredLogger.Debugf("tushare response: %s", res)
|
||||
return res
|
||||
}
|
||||
|
||||
func getStockType(code string) string {
|
||||
if strutil.HasSuffixAny(code, []string{"SZ", "SH", "sh", "sz"}) {
|
||||
return "daily"
|
||||
}
|
||||
if strutil.HasSuffixAny(code, []string{"HK", "hk"}) {
|
||||
return "hk_daily"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
func TestGetDaily(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
tushareApi := NewTushareApi(getConfig())
|
||||
res := tushareApi.GetDaily("000802.SZ", "20250101", "20250217", 30)
|
||||
res := tushareApi.GetDaily("00927.HK", "20250101", "20250217", 30)
|
||||
t.Log(res)
|
||||
|
||||
}
|
||||
|
@ -163,6 +163,19 @@ func (receiver VersionInfo) TableName() string {
|
||||
return "version_info"
|
||||
}
|
||||
|
||||
type StockInfoHK struct {
|
||||
gorm.Model
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"fullName"`
|
||||
EName string `json:"eName"`
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
}
|
||||
|
||||
func (receiver StockInfoHK) TableName() string {
|
||||
return "stock_base_info_hk"
|
||||
}
|
||||
|
||||
type Resp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
|
49
backend/models/models_test.go
Normal file
49
backend/models/models_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/2/22 16:09
|
||||
// @Desc
|
||||
// -----------------------------------------------------------------------------------
|
||||
type StockInfoHKResp struct {
|
||||
Code int `json:"code"`
|
||||
Status string `json:"status"`
|
||||
StockInfos *[]StockInfoData `json:"data"`
|
||||
}
|
||||
|
||||
type StockInfoData struct {
|
||||
C string `json:"c"`
|
||||
N string `json:"n"`
|
||||
T string `json:"t"`
|
||||
E string `json:"e"`
|
||||
}
|
||||
|
||||
func TestStockInfoHK(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
db.Dao.AutoMigrate(&StockInfoHK{})
|
||||
bs, _ := os.ReadFile("../../build/hk.json")
|
||||
v := &StockInfoHKResp{}
|
||||
err := json.Unmarshal(bs, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hks := &[]StockInfoHK{}
|
||||
for i, data := range *v.StockInfos {
|
||||
logger.SugaredLogger.Infof("第%d条数据: %+v", i, data)
|
||||
hk := &StockInfoHK{
|
||||
Code: strutil.PadStart(data.C, 5, "0") + ".HK",
|
||||
EName: data.N,
|
||||
}
|
||||
*hks = append(*hks, *hk)
|
||||
}
|
||||
db.Dao.Create(&hks)
|
||||
|
||||
}
|
15192
build/hk.json
Normal file
15192
build/hk.json
Normal file
File diff suppressed because it is too large
Load Diff
27843
build/stock_base_info_hk.json
Normal file
27843
build/stock_base_info_hk.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -675,7 +675,7 @@ async function saveAsWord() {
|
||||
<p>
|
||||
<a href="https://github.com/ArvinLovegood/go-stock">
|
||||
AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警推送,市场整体/个股情绪分析,K线技术指标分析等。数据全部保留在本地。支持DeepSeek,OpenAI, Ollama,LMStudio,AnythingLLM,硅基流动,火山方舟,阿里云百炼等平台或模型。
|
||||
</a></p>>
|
||||
</a></p>
|
||||
`
|
||||
// landscape就是横着的,portrait是竖着的,默认是竖屏portrait。
|
||||
const blob = await asBlob(value, { orientation: 'portrait' })
|
||||
|
25
main.go
25
main.go
@ -41,6 +41,9 @@ var wxpay []byte
|
||||
//go:embed build/stock_basic.json
|
||||
var stocksBin []byte
|
||||
|
||||
//go:embed build/stock_base_info_hk.json
|
||||
var stocksBinHK []byte
|
||||
|
||||
//go:generate cp -R ./data ./build/bin
|
||||
|
||||
var Version string
|
||||
@ -55,10 +58,15 @@ func main() {
|
||||
db.Dao.AutoMigrate(&data.IndexBasic{})
|
||||
db.Dao.AutoMigrate(&data.Settings{})
|
||||
db.Dao.AutoMigrate(&models.AIResponseResult{})
|
||||
db.Dao.AutoMigrate(&models.StockInfoHK{})
|
||||
|
||||
if stocksBin != nil && len(stocksBin) > 0 {
|
||||
go initStockData()
|
||||
}
|
||||
if stocksBinHK != nil && len(stocksBinHK) > 0 {
|
||||
go initStockDataHK()
|
||||
}
|
||||
|
||||
updateBasicInfo()
|
||||
|
||||
// Create an instance of the app structure
|
||||
@ -162,6 +170,23 @@ func main() {
|
||||
|
||||
}
|
||||
|
||||
func initStockDataHK() {
|
||||
var count int64
|
||||
db.Dao.Model(&models.StockInfoHK{}).Count(&count)
|
||||
if count > 0 {
|
||||
return
|
||||
}
|
||||
var v []models.StockInfoHK
|
||||
err := json.Unmarshal(stocksBinHK, &v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, item := range v {
|
||||
db.Dao.Model(&models.StockInfoHK{}).Create(&item)
|
||||
}
|
||||
log.Printf("init stock data hk %d", len(v))
|
||||
}
|
||||
|
||||
func updateBasicInfo() {
|
||||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
if config.UpdateBasicInfoOnStart {
|
||||
|
Loading…
x
Reference in New Issue
Block a user