mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
feat(stock):添加美股数据支持
- 新增 StockInfoUS 模型用于存储美股信息 - 实现 IsUSTradingTime 函数判断美股交易时间 - 修改 MonitorStockPrices 函数以支持美股数据 - 更新前端股票组件以适配美股数据 - 优化后端 API 以支持美股实时数据获取和解析
This commit is contained in:
parent
7b3bad4102
commit
fdca30ce3a
45
app.go
45
app.go
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/duke-git/lancet/v2/convertor"
|
"github.com/duke-git/lancet/v2/convertor"
|
||||||
"github.com/duke-git/lancet/v2/mathutil"
|
"github.com/duke-git/lancet/v2/mathutil"
|
||||||
"github.com/duke-git/lancet/v2/slice"
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
|
"github.com/duke-git/lancet/v2/strutil"
|
||||||
"github.com/getlantern/systray"
|
"github.com/getlantern/systray"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
@ -104,9 +105,8 @@ func (a *App) domReady(ctx context.Context) {
|
|||||||
ticker := time.NewTicker(time.Second * time.Duration(interval))
|
ticker := time.NewTicker(time.Second * time.Duration(interval))
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if isTradingTime(time.Now()) || IsHKTradingTime(time.Now()) {
|
MonitorStockPrices(a)
|
||||||
MonitorStockPrices(a)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -229,6 +229,35 @@ func IsHKTradingTime(date time.Time) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsUSTradingTime 判断当前时间是否在美股交易时间内
|
||||||
|
func IsUSTradingTime(date time.Time) bool {
|
||||||
|
// 获取美国东部时区
|
||||||
|
est, err := time.LoadLocation("America/New_York")
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("加载时区失败: %s", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将当前时间转换为美国东部时间
|
||||||
|
estTime := date.In(est)
|
||||||
|
|
||||||
|
// 判断是否是周末
|
||||||
|
weekday := estTime.Weekday()
|
||||||
|
if weekday == time.Saturday || weekday == time.Sunday {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取小时和分钟
|
||||||
|
hour, minute, _ := estTime.Clock()
|
||||||
|
|
||||||
|
// 判断是否在9:30到16:00之间
|
||||||
|
if (hour == 9 && minute >= 30) || (hour >= 10 && hour < 16) || (hour == 16 && minute == 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func MonitorStockPrices(a *App) {
|
func MonitorStockPrices(a *App) {
|
||||||
dest := &[]data.FollowedStock{}
|
dest := &[]data.FollowedStock{}
|
||||||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||||||
@ -244,6 +273,16 @@ func MonitorStockPrices(a *App) {
|
|||||||
|
|
||||||
stockInfos := GetStockInfos(*dest...)
|
stockInfos := GetStockInfos(*dest...)
|
||||||
for _, stockInfo := range *stockInfos {
|
for _, stockInfo := range *stockInfos {
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
total += stockInfo.ProfitAmountToday
|
total += stockInfo.ProfitAmountToday
|
||||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||||
if stockInfo.PrePrice != price {
|
if stockInfo.PrePrice != price {
|
||||||
|
@ -13,3 +13,7 @@ func TestIsHKTradingTime(t *testing.T) {
|
|||||||
f := IsHKTradingTime(time.Now())
|
f := IsHKTradingTime(time.Now())
|
||||||
t.Log(f)
|
t.Log(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsUSTradingTime(t *testing.T) {
|
||||||
|
t.Log(IsUSTradingTime(time.Now()))
|
||||||
|
}
|
||||||
|
@ -2,12 +2,14 @@ package data
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/duke-git/lancet/v2/strutil"
|
"github.com/duke-git/lancet/v2/strutil"
|
||||||
"go-stock/backend/db"
|
"go-stock/backend/db"
|
||||||
"go-stock/backend/logger"
|
"go-stock/backend/logger"
|
||||||
"go-stock/backend/models"
|
"go-stock/backend/models"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -166,3 +168,143 @@ func TestHk(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateUSName(t *testing.T) {
|
||||||
|
db.Init("../../data/stock.db")
|
||||||
|
us := &[]models.StockInfoUS{}
|
||||||
|
db.Dao.Model(&models.StockInfoUS{}).Where("name = ?", "").Order("RANDOM()").Find(us)
|
||||||
|
|
||||||
|
for _, us := range *us {
|
||||||
|
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(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
crawlerAPI = crawlerAPI.NewCrawler(ctx, crawlerBaseInfo)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://stock.finance.sina.com.cn/usstock/quotes/%s.html", us.Code[:len(us.Code)-3])
|
||||||
|
logger.SugaredLogger.Infof("url: %s", url)
|
||||||
|
//waitVisible := "span.quote_title_name"
|
||||||
|
waitVisible := "div.hq_title > h1"
|
||||||
|
|
||||||
|
htmlContent, ok := crawlerAPI.GetHtml(url, waitVisible, 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())
|
||||||
|
}
|
||||||
|
name := ""
|
||||||
|
document.Find(waitVisible).Each(func(i int, selection *goquery.Selection) {
|
||||||
|
name = strutil.RemoveNonPrintable(selection.Text())
|
||||||
|
name = strutil.SplitAndTrim(name, " ", "")[0]
|
||||||
|
logger.SugaredLogger.Infof("股票名称-:%s", name)
|
||||||
|
})
|
||||||
|
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", us.Code).Updates(map[string]interface{}{
|
||||||
|
"name": name,
|
||||||
|
"full_name": name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestUS(t *testing.T) {
|
||||||
|
db.Init("../../data/stock.db")
|
||||||
|
bytes, err := os.ReadFile("../../build/us.json")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
crawlerAPI := CrawlerApi{}
|
||||||
|
crawlerBaseInfo := CrawlerBaseInfo{
|
||||||
|
Name: "TestCrawler",
|
||||||
|
Description: "Test Crawler Description",
|
||||||
|
BaseUrl: "https://quote.eastmoney.com",
|
||||||
|
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)
|
||||||
|
|
||||||
|
tick := &Tick{}
|
||||||
|
json.Unmarshal(bytes, &tick)
|
||||||
|
for i, datum := range tick.Data {
|
||||||
|
logger.SugaredLogger.Infof("datum: %d, %+v", i, datum)
|
||||||
|
name := ""
|
||||||
|
|
||||||
|
//https://quote.eastmoney.com/us/AAPL.html
|
||||||
|
//https://stock.finance.sina.com.cn/usstock/quotes/goog.html
|
||||||
|
//url := fmt.Sprintf("https://stock.finance.sina.com.cn/usstock/quotes/%s.html", strings.ReplaceAll(datum.C, ".US", ""))
|
||||||
|
////waitVisible := "span.quote_title_name"
|
||||||
|
//waitVisible := "div.hq_title > h1"
|
||||||
|
//
|
||||||
|
//htmlContent, ok := crawlerAPI.GetHtml(url, waitVisible, 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(waitVisible).Each(func(i int, selection *goquery.Selection) {
|
||||||
|
// name = strutil.RemoveNonPrintable(selection.Text())
|
||||||
|
// name = strutil.SplitAndTrim(name, " ", "")[0]
|
||||||
|
// logger.SugaredLogger.Infof("股票名称-:%s", name)
|
||||||
|
//})
|
||||||
|
|
||||||
|
us := &models.StockInfoUS{
|
||||||
|
Code: datum.C + ".US",
|
||||||
|
EName: datum.N,
|
||||||
|
FullName: datum.N,
|
||||||
|
Name: name,
|
||||||
|
Exchange: datum.E,
|
||||||
|
Type: datum.T,
|
||||||
|
}
|
||||||
|
db.Dao.Create(us)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUSSINA(t *testing.T) {
|
||||||
|
//https://finance.sina.com.cn/stock/usstock/sector.shtml#cm
|
||||||
|
crawlerAPI := CrawlerApi{}
|
||||||
|
crawlerBaseInfo := CrawlerBaseInfo{
|
||||||
|
Name: "TestCrawler",
|
||||||
|
Description: "Test Crawler Description",
|
||||||
|
BaseUrl: "https://quote.eastmoney.com",
|
||||||
|
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)
|
||||||
|
|
||||||
|
html, ok := crawlerAPI.GetHtml("https://finance.sina.com.cn/stock/usstock/sector.shtml#cm", "div#data", false)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
document, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Error(err.Error())
|
||||||
|
}
|
||||||
|
document.Find("div#data > table >tbody >tr").Each(func(i int, selection *goquery.Selection) {
|
||||||
|
tr := selection.Text()
|
||||||
|
logger.SugaredLogger.Infof("tr: %s", tr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tick struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data []struct {
|
||||||
|
C string `json:"c"`
|
||||||
|
N string `json:"n"`
|
||||||
|
T string `json:"t"`
|
||||||
|
E string `json:"e"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
@ -449,6 +449,9 @@ func SearchGuShiTongStockInfo(stock string, crawlTimeOut int64) *[]string {
|
|||||||
if strutil.HasPrefixAny(stock, []string{"SZ", "SH", "sh", "sz"}) {
|
if strutil.HasPrefixAny(stock, []string{"SZ", "SH", "sh", "sz"}) {
|
||||||
url = "https://gushitong.baidu.com/stock/ab-" + RemoveAllNonDigitChar(stock)
|
url = "https://gushitong.baidu.com/stock/ab-" + RemoveAllNonDigitChar(stock)
|
||||||
}
|
}
|
||||||
|
if strutil.HasPrefixAny(stock, []string{"us", "US", "gb_", "gb"}) {
|
||||||
|
url = "https://gushitong.baidu.com/stock/us-" + strings.Replace(stock, "gb_", "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
logger.SugaredLogger.Infof("SearchGuShiTongStockInfo搜索股票-%s: %s", stock, url)
|
logger.SugaredLogger.Infof("SearchGuShiTongStockInfo搜索股票-%s: %s", stock, url)
|
||||||
actions := []chromedp.Action{
|
actions := []chromedp.Action{
|
||||||
@ -483,6 +486,10 @@ func GetFinancialReports(stockCode string, crawlTimeOut int64) *[]string {
|
|||||||
stockCode = strings.ReplaceAll(stockCode, "hk", "")
|
stockCode = strings.ReplaceAll(stockCode, "hk", "")
|
||||||
stockCode = strings.ReplaceAll(stockCode, "HK", "")
|
stockCode = strings.ReplaceAll(stockCode, "HK", "")
|
||||||
}
|
}
|
||||||
|
if strutil.HasPrefixAny(stockCode, []string{"us", "gb_"}) {
|
||||||
|
stockCode = strings.ReplaceAll(stockCode, "us", "")
|
||||||
|
stockCode = strings.ReplaceAll(stockCode, "gb_", "")
|
||||||
|
}
|
||||||
|
|
||||||
// 创建一个 chromedp 上下文
|
// 创建一个 chromedp 上下文
|
||||||
timeoutCtx, timeoutCtxCancel := context.WithTimeout(context.Background(), time.Duration(crawlTimeOut)*time.Second)
|
timeoutCtx, timeoutCtxCancel := context.WithTimeout(context.Background(), time.Duration(crawlTimeOut)*time.Second)
|
||||||
|
@ -25,5 +25,6 @@ func TestGetTopNewsList(t *testing.T) {
|
|||||||
func TestSearchGuShiTongStockInfo(t *testing.T) {
|
func TestSearchGuShiTongStockInfo(t *testing.T) {
|
||||||
SearchGuShiTongStockInfo("hk01810", 60)
|
SearchGuShiTongStockInfo("hk01810", 60)
|
||||||
SearchGuShiTongStockInfo("sh600745", 60)
|
SearchGuShiTongStockInfo("sh600745", 60)
|
||||||
|
SearchGuShiTongStockInfo("gb_goog", 60)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -283,11 +283,24 @@ func (receiver StockDataApi) GetStockBaseInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) {
|
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) {
|
||||||
|
|
||||||
|
codes := slice.JoinFunc(StockCodes, ",", func(s string) string {
|
||||||
|
if strings.HasPrefix(s, "us") {
|
||||||
|
s = strings.Replace(s, "us", "gb_", 1)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "US") {
|
||||||
|
s = strings.Replace(s, "US", "gb_", 1)
|
||||||
|
}
|
||||||
|
return strings.ToLower(s)
|
||||||
|
})
|
||||||
|
|
||||||
|
url := fmt.Sprintf(sinaStockUrl, time.Now().Unix(), codes)
|
||||||
|
//logger.SugaredLogger.Infof("GetStockCodeRealTimeData %s", url)
|
||||||
resp, err := receiver.client.R().
|
resp, err := receiver.client.R().
|
||||||
SetHeader("Host", "hq.sinajs.cn").
|
SetHeader("Host", "hq.sinajs.cn").
|
||||||
SetHeader("Referer", "https://finance.sina.com.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").
|
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(), slice.Join(StockCodes, ",")))
|
Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SugaredLogger.Error(err.Error())
|
logger.SugaredLogger.Error(err.Error())
|
||||||
return &[]StockInfo{}, err
|
return &[]StockInfo{}, err
|
||||||
@ -391,6 +404,9 @@ func (receiver StockDataApi) GetStockList(key string) []StockBasic {
|
|||||||
var result3 []models.StockInfoHK
|
var result3 []models.StockInfoHK
|
||||||
db.Dao.Model(&models.StockInfoHK{}).Where("name like ? or code like ?", "%"+key+"%", "%"+key+"%").Find(&result3)
|
db.Dao.Model(&models.StockInfoHK{}).Where("name like ? or code like ?", "%"+key+"%", "%"+key+"%").Find(&result3)
|
||||||
|
|
||||||
|
var result4 []models.StockInfoUS
|
||||||
|
db.Dao.Model(&models.StockInfoUS{}).Where("name like ? or code like ?", "%"+key+"%", "%"+key+"%").Find(&result4)
|
||||||
|
|
||||||
for _, item := range result2 {
|
for _, item := range result2 {
|
||||||
result = append(result, StockBasic{
|
result = append(result, StockBasic{
|
||||||
TsCode: item.TsCode,
|
TsCode: item.TsCode,
|
||||||
@ -409,6 +425,14 @@ func (receiver StockDataApi) GetStockList(key string) []StockBasic {
|
|||||||
Market: "HK",
|
Market: "HK",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
for _, item := range result4 {
|
||||||
|
result = append(result, StockBasic{
|
||||||
|
TsCode: item.Code,
|
||||||
|
Name: item.Name,
|
||||||
|
Fullname: item.Name,
|
||||||
|
Market: "US",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -436,6 +460,9 @@ func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
|||||||
if strutil.ContainsAny(datas[0], []string{"hq_str_hk"}) {
|
if strutil.ContainsAny(datas[0], []string{"hq_str_hk"}) {
|
||||||
result, err = ParseHKStockData(datas)
|
result, err = ParseHKStockData(datas)
|
||||||
}
|
}
|
||||||
|
if strutil.ContainsAny(datas[0], []string{"hq_str_gb"}) {
|
||||||
|
result, err = ParseUSStockData(datas)
|
||||||
|
}
|
||||||
|
|
||||||
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
||||||
marshal, err := json.Marshal(result)
|
marshal, err := json.Marshal(result)
|
||||||
@ -452,6 +479,65 @@ func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
|||||||
return stockInfo, nil
|
return stockInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseUSStockData(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) < 30 {
|
||||||
|
return nil, fmt.Errorf("invalid data format")
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
谷歌, 0
|
||||||
|
170.2100, 1 现价
|
||||||
|
-2.57, 2 涨跌幅
|
||||||
|
2025-02-28 09:38:50, 3 时间
|
||||||
|
-4.4900, 4 涨跌额
|
||||||
|
175.9400, 5 今日开盘价
|
||||||
|
176.5900, 6 区间
|
||||||
|
169.7520, 7 区间
|
||||||
|
208.7000, 8 52周区间
|
||||||
|
130.9500, 9 52周区间
|
||||||
|
25930485, 10 成交量
|
||||||
|
17083496, 11 10日均量
|
||||||
|
2074859900000, 12 市值
|
||||||
|
8.13, 13 每股收益
|
||||||
|
20.940000 , 14 市盈率
|
||||||
|
0.00, 15
|
||||||
|
0.00, 16
|
||||||
|
0.20, 17
|
||||||
|
0.00, 18
|
||||||
|
12190000000, 19
|
||||||
|
71, 20
|
||||||
|
170.2000, 21 盘后
|
||||||
|
-0.01, 22
|
||||||
|
-0.01, 23
|
||||||
|
Feb 27 07:59PM EST, 24
|
||||||
|
Feb 27 04:00PM EST, 25
|
||||||
|
174.7000, 26 前收盘
|
||||||
|
2917444, 27
|
||||||
|
1, 28
|
||||||
|
2025, 29
|
||||||
|
4456143849.0000, 30
|
||||||
|
176.1200, 31
|
||||||
|
163.7039, 32
|
||||||
|
496605933.1411, 33
|
||||||
|
170.2100, 34 现价
|
||||||
|
174.7000 35 前收盘
|
||||||
|
*/
|
||||||
|
result["股票代码"] = code
|
||||||
|
result["股票名称"] = parts[0]
|
||||||
|
result["今日开盘价"] = parts[5]
|
||||||
|
result["昨日收盘价"] = parts[26]
|
||||||
|
result["今日最高价"] = parts[6]
|
||||||
|
result["今日最低价"] = parts[7]
|
||||||
|
result["当前价格"] = parts[1]
|
||||||
|
result["日期"] = strutil.SplitAndTrim(parts[3], " ", "")[0]
|
||||||
|
result["时间"] = strutil.SplitAndTrim(parts[3], " ", "")[1]
|
||||||
|
logger.SugaredLogger.Infof("美股股票数据解析完成: %v", result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParseHKStockData(datas []string) (map[string]string, error) {
|
func ParseHKStockData(datas []string) (map[string]string, error) {
|
||||||
code := strings.Split(datas[0], "hq_str_")[1]
|
code := strings.Split(datas[0], "hq_str_")[1]
|
||||||
result := make(map[string]string)
|
result := make(map[string]string)
|
||||||
|
@ -26,14 +26,15 @@ func TestGetTelegraph(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetFinancialReports(t *testing.T) {
|
func TestGetFinancialReports(t *testing.T) {
|
||||||
GetFinancialReports("sz000802", 30)
|
//GetFinancialReports("sz000802", 30)
|
||||||
//GetFinancialReports("hk00927", 30)
|
//GetFinancialReports("hk00927", 30)
|
||||||
|
GetFinancialReports("gb_aapl", 30)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTelegraphSearch(t *testing.T) {
|
func TestGetTelegraphSearch(t *testing.T) {
|
||||||
//url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram"
|
//url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram"
|
||||||
messages := SearchStockInfo("新 希 望", "telegram", 30)
|
messages := SearchStockInfo("谷歌", "telegram", 30)
|
||||||
for _, message := range *messages {
|
for _, message := range *messages {
|
||||||
logger.SugaredLogger.Info(message)
|
logger.SugaredLogger.Info(message)
|
||||||
}
|
}
|
||||||
@ -82,7 +83,7 @@ func TestParseFullSingleStockData(t *testing.T) {
|
|||||||
SetHeader("Host", "hq.sinajs.cn").
|
SetHeader("Host", "hq.sinajs.cn").
|
||||||
SetHeader("Referer", "https://finance.sina.com.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").
|
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(), "sh600584,sz000938,hk01810,hk00856"))
|
Get(fmt.Sprintf(sinaStockUrl, time.Now().Unix(), "sh600584,sz000938,hk01810,hk00856,gb_aapl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SugaredLogger.Error(err.Error())
|
logger.SugaredLogger.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ var SensitiveWords = strings.Split("戊边、戍边、戌边、边防、李鹏
|
|||||||
func ReplaceSensitiveWords(text string) string {
|
func ReplaceSensitiveWords(text string) string {
|
||||||
for _, word := range SensitiveWords {
|
for _, word := range SensitiveWords {
|
||||||
if strings.Contains(text, word) {
|
if strings.Contains(text, word) {
|
||||||
text = strings.ReplaceAll(text, word, "*")
|
text = strings.ReplaceAll(text, word, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
|
@ -176,6 +176,21 @@ func (receiver StockInfoHK) TableName() string {
|
|||||||
return "stock_base_info_hk"
|
return "stock_base_info_hk"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StockInfoUS struct {
|
||||||
|
gorm.Model
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
EName string `json:"eName"`
|
||||||
|
Exchange string `json:"exchange"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (receiver StockInfoUS) TableName() string {
|
||||||
|
return "stock_base_info_us"
|
||||||
|
}
|
||||||
|
|
||||||
type Resp struct {
|
type Resp struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
@ -10,12 +10,12 @@ import {
|
|||||||
} from '../wailsjs/runtime'
|
} from '../wailsjs/runtime'
|
||||||
import {h, ref} from "vue";
|
import {h, ref} from "vue";
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink } from 'vue-router'
|
||||||
import {darkTheme, NIcon, NText,} from 'naive-ui'
|
import {darkTheme, NGradientText, NIcon, NText,} from 'naive-ui'
|
||||||
import {
|
import {
|
||||||
SettingsOutline,
|
SettingsOutline,
|
||||||
ReorderTwoOutline,
|
ReorderTwoOutline,
|
||||||
ExpandOutline,
|
ExpandOutline,
|
||||||
PowerOutline, LogoGithub, MoveOutline, WalletOutline, StarOutline,
|
PowerOutline, LogoGithub, MoveOutline, WalletOutline, StarOutline, AlarmOutline, SparklesOutline,
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
const content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎')
|
const content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎')
|
||||||
const isFullscreen = ref(false)
|
const isFullscreen = ref(false)
|
||||||
@ -36,7 +36,7 @@ const menuOptions = ref([
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ default: () => '我的自选',}
|
{ default: () => '股票自选',}
|
||||||
),
|
),
|
||||||
key: 'stock',
|
key: 'stock',
|
||||||
icon: renderIcon(StarOutline),
|
icon: renderIcon(StarOutline),
|
||||||
@ -49,6 +49,29 @@ const menuOptions = ref([
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () =>
|
||||||
|
h(
|
||||||
|
NGradientText,
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
style: {
|
||||||
|
'text-decoration': 'line-through',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => '基金自选' }
|
||||||
|
),
|
||||||
|
key: 'fund',
|
||||||
|
icon: renderIcon(SparklesOutline),
|
||||||
|
children:[
|
||||||
|
{
|
||||||
|
label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '敬请期待!'}),
|
||||||
|
key: 'realtimeProfit',
|
||||||
|
show: realtimeProfit.value,
|
||||||
|
icon: renderIcon(AlarmOutline),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () =>
|
||||||
h(
|
h(
|
||||||
|
@ -465,7 +465,7 @@ function search(code,name){
|
|||||||
//window.open("https://www.cls.cn/stock?code="+code)
|
//window.open("https://www.cls.cn/stock?code="+code)
|
||||||
//window.open("https://quote.eastmoney.com/"+code+".html")
|
//window.open("https://quote.eastmoney.com/"+code+".html")
|
||||||
//window.open("https://finance.sina.com.cn/realstock/company/"+code+"/nc.shtml")
|
//window.open("https://finance.sina.com.cn/realstock/company/"+code+"/nc.shtml")
|
||||||
window.open("https://www.iwencai.com/unifiedwap/result?w="+code)
|
window.open("https://www.iwencai.com/unifiedwap/result?w="+name)
|
||||||
//window.open("https://www.iwencai.com/chat/?question="+code)
|
//window.open("https://www.iwencai.com/chat/?question="+code)
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
@ -486,16 +486,34 @@ function showFenshi(code,name){
|
|||||||
data.code=code
|
data.code=code
|
||||||
data.name=name
|
data.name=name
|
||||||
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
|
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
|
||||||
|
|
||||||
|
if(code.startsWith('hk')){
|
||||||
|
data.fenshiURL='http://image.sinajs.cn/newchart/hk_stock/min/'+data.code.replace("hk","")+'.gif'+"?t="+Date.now()
|
||||||
|
}
|
||||||
|
if(code.startsWith('gb_')){
|
||||||
|
data.fenshiURL='http://image.sinajs.cn/newchart/usstock/min/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
modalShow2.value=true
|
modalShow2.value=true
|
||||||
}
|
}
|
||||||
function showK(code,name){
|
function showK(code,name){
|
||||||
data.code=code
|
data.code=code
|
||||||
data.name=name
|
data.name=name
|
||||||
data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now()
|
data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now()
|
||||||
|
if(code.startsWith('hk')){
|
||||||
|
data.kURL='http://image.sinajs.cn/newchart/hk_stock/daily/'+data.code.replace("hk","")+'.gif'+"?t="+Date.now()
|
||||||
|
}
|
||||||
|
if(code.startsWith('gb_')){
|
||||||
|
data.kURL='http://image.sinajs.cn/newchart/usstock/daily/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now()
|
||||||
|
}
|
||||||
|
//https://image.sinajs.cn/newchart/usstock/daily/dji.gif
|
||||||
|
//https://image.sinajs.cn/newchart/hk_stock/daily/06030.gif?1740729404273
|
||||||
modalShow3.value=true
|
modalShow3.value=true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){
|
function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){
|
||||||
|
|
||||||
if(formModel.sort){
|
if(formModel.sort){
|
||||||
|
1
main.go
1
main.go
@ -59,6 +59,7 @@ func main() {
|
|||||||
db.Dao.AutoMigrate(&data.Settings{})
|
db.Dao.AutoMigrate(&data.Settings{})
|
||||||
db.Dao.AutoMigrate(&models.AIResponseResult{})
|
db.Dao.AutoMigrate(&models.AIResponseResult{})
|
||||||
db.Dao.AutoMigrate(&models.StockInfoHK{})
|
db.Dao.AutoMigrate(&models.StockInfoHK{})
|
||||||
|
db.Dao.AutoMigrate(&models.StockInfoUS{})
|
||||||
|
|
||||||
if stocksBin != nil && len(stocksBin) > 0 {
|
if stocksBin != nil && len(stocksBin) > 0 {
|
||||||
go initStockData()
|
go initStockData()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user