mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
- 新增 NewsPush 函数用于推送市场资讯 - 在 App.vue 中添加新闻推送的事件监听 - 在 settings 中增加启用新闻推送的选项 - 修改 README.md,添加实时市场资讯信息提醒的更新说明
1155 lines
35 KiB
Go
1155 lines
35 KiB
Go
//go:build windows
|
||
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"go-stock/backend/data"
|
||
"go-stock/backend/db"
|
||
"go-stock/backend/logger"
|
||
"go-stock/backend/models"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/PuerkitoBio/goquery"
|
||
"github.com/coocood/freecache"
|
||
"github.com/duke-git/lancet/v2/convertor"
|
||
"github.com/duke-git/lancet/v2/mathutil"
|
||
"github.com/duke-git/lancet/v2/slice"
|
||
"github.com/duke-git/lancet/v2/strutil"
|
||
"github.com/energye/systray"
|
||
"github.com/go-resty/resty/v2"
|
||
"github.com/go-toast/toast"
|
||
"github.com/robfig/cron/v3"
|
||
"github.com/wailsapp/wails/v2/pkg/options"
|
||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||
"golang.org/x/sys/windows/registry"
|
||
)
|
||
|
||
// App struct
|
||
type App struct {
|
||
ctx context.Context
|
||
cache *freecache.Cache
|
||
cron *cron.Cron
|
||
cronEntrys map[string]cron.EntryID
|
||
}
|
||
|
||
// NewApp creates a new App application struct
|
||
func NewApp() *App {
|
||
cacheSize := 512 * 1024
|
||
cache := freecache.NewCache(cacheSize)
|
||
c := cron.New(cron.WithSeconds())
|
||
c.Start()
|
||
return &App{
|
||
cache: cache,
|
||
cron: c,
|
||
cronEntrys: make(map[string]cron.EntryID),
|
||
}
|
||
}
|
||
|
||
// startup is called at application startup
|
||
func (a *App) startup(ctx context.Context) {
|
||
defer PanicHandler()
|
||
runtime.EventsOn(ctx, "frontendError", func(optionalData ...interface{}) {
|
||
logger.SugaredLogger.Errorf("Frontend error: %v\n", optionalData)
|
||
})
|
||
logger.SugaredLogger.Infof("Version:%s", Version)
|
||
// Perform your setup here
|
||
a.ctx = ctx
|
||
|
||
// 创建系统托盘
|
||
//systray.RunWithExternalLoop(func() {
|
||
// onReady(a)
|
||
//}, func() {
|
||
// onExit(a)
|
||
//})
|
||
runtime.EventsOn(ctx, "updateSettings", func(optionalData ...interface{}) {
|
||
logger.SugaredLogger.Infof("updateSettings : %v\n", optionalData)
|
||
config := &data.Settings{}
|
||
setMap := optionalData[0].(map[string]interface{})
|
||
|
||
// 将 map 转换为 JSON 字节切片
|
||
jsonData, err := json.Marshal(setMap)
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("Marshal error:%s", err.Error())
|
||
return
|
||
}
|
||
// 将 JSON 字节切片解析到结构体中
|
||
err = json.Unmarshal(jsonData, config)
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("Unmarshal error:%s", err.Error())
|
||
return
|
||
}
|
||
|
||
logger.SugaredLogger.Infof("updateSettings config:%+v", config)
|
||
if config.DarkTheme {
|
||
runtime.WindowSetBackgroundColour(ctx, 27, 38, 54, 1)
|
||
runtime.WindowSetDarkTheme(ctx)
|
||
} else {
|
||
runtime.WindowSetBackgroundColour(ctx, 255, 255, 255, 1)
|
||
runtime.WindowSetLightTheme(ctx)
|
||
}
|
||
runtime.WindowReloadApp(ctx)
|
||
|
||
})
|
||
go systray.Run(func() {
|
||
onReady(a)
|
||
}, func() {
|
||
onExit(a)
|
||
})
|
||
|
||
logger.SugaredLogger.Infof(" application startup Version:%s", Version)
|
||
}
|
||
|
||
func (a *App) CheckUpdate() {
|
||
releaseVersion := &models.GitHubReleaseVersion{}
|
||
_, err := resty.New().R().
|
||
SetResult(releaseVersion).
|
||
Get("https://api.github.com/repos/ArvinLovegood/go-stock/releases/latest")
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("get github release version error:%s", err.Error())
|
||
return
|
||
}
|
||
logger.SugaredLogger.Infof("releaseVersion:%+v", releaseVersion.TagName)
|
||
if releaseVersion.TagName != Version {
|
||
tag := &models.Tag{}
|
||
_, err = resty.New().R().
|
||
SetResult(tag).
|
||
Get("https://api.github.com/repos/ArvinLovegood/go-stock/git/ref/tags/" + releaseVersion.TagName)
|
||
if err == nil {
|
||
releaseVersion.Tag = *tag
|
||
}
|
||
commit := &models.Commit{}
|
||
_, err = resty.New().R().
|
||
SetResult(commit).
|
||
Get(tag.Object.Url)
|
||
if err == nil {
|
||
releaseVersion.Commit = *commit
|
||
}
|
||
|
||
go runtime.EventsEmit(a.ctx, "updateVersion", releaseVersion)
|
||
}
|
||
}
|
||
|
||
// domReady is called after front-end resources have been loaded
|
||
func (a *App) domReady(ctx context.Context) {
|
||
defer PanicHandler()
|
||
|
||
if stocksBin != nil && len(stocksBin) > 0 {
|
||
go runtime.EventsEmit(a.ctx, "loadingMsg", "检查A股基础信息...")
|
||
go initStockData(a.ctx)
|
||
}
|
||
|
||
if stocksBinHK != nil && len(stocksBinHK) > 0 {
|
||
go runtime.EventsEmit(a.ctx, "loadingMsg", "检查港股基础信息...")
|
||
go initStockDataHK(a.ctx)
|
||
}
|
||
|
||
if stocksBinUS != nil && len(stocksBinUS) > 0 {
|
||
go runtime.EventsEmit(a.ctx, "loadingMsg", "检查美股基础信息...")
|
||
go initStockDataUS(a.ctx)
|
||
}
|
||
updateBasicInfo()
|
||
|
||
// Add your action here
|
||
//定时更新数据
|
||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||
go func() {
|
||
interval := config.RefreshInterval
|
||
if interval <= 0 {
|
||
interval = 1
|
||
}
|
||
//ticker := time.NewTicker(time.Second * time.Duration(interval))
|
||
//defer ticker.Stop()
|
||
//for range ticker.C {
|
||
// MonitorStockPrices(a)
|
||
//}
|
||
id, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval), func() {
|
||
MonitorStockPrices(a)
|
||
})
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
|
||
} else {
|
||
a.cronEntrys["MonitorStockPrices"] = id
|
||
}
|
||
entryID, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
|
||
news := data.NewMarketNewsApi().GetNewTelegraph(30)
|
||
if config.EnablePushNews {
|
||
go a.NewsPush(news)
|
||
}
|
||
go runtime.EventsEmit(a.ctx, "newTelegraph", news)
|
||
})
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
|
||
} else {
|
||
a.cronEntrys["GetNewTelegraph"] = entryID
|
||
}
|
||
|
||
entryIDSina, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
|
||
news := data.NewMarketNewsApi().GetSinaNews(30)
|
||
if config.EnablePushNews {
|
||
go a.NewsPush(news)
|
||
}
|
||
go runtime.EventsEmit(a.ctx, "newSinaNews", news)
|
||
})
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
|
||
} else {
|
||
a.cronEntrys["newSinaNews"] = entryIDSina
|
||
}
|
||
}()
|
||
|
||
//刷新基金净值信息
|
||
go func() {
|
||
//ticker := time.NewTicker(time.Second * time.Duration(60))
|
||
//defer ticker.Stop()
|
||
//for range ticker.C {
|
||
// MonitorFundPrices(a)
|
||
//}
|
||
if config.EnableFund {
|
||
id, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", 60), func() {
|
||
MonitorFundPrices(a)
|
||
})
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
|
||
} else {
|
||
a.cronEntrys["MonitorFundPrices"] = id
|
||
}
|
||
}
|
||
|
||
}()
|
||
|
||
if config.EnableNews {
|
||
//go func() {
|
||
// ticker := time.NewTicker(time.Second * time.Duration(60))
|
||
// defer ticker.Stop()
|
||
// for range ticker.C {
|
||
// telegraph := refreshTelegraphList()
|
||
// if telegraph != nil {
|
||
// go runtime.EventsEmit(a.ctx, "telegraph", telegraph)
|
||
// }
|
||
// }
|
||
//
|
||
//}()
|
||
|
||
id, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", 60), func() {
|
||
telegraph := refreshTelegraphList()
|
||
if telegraph != nil {
|
||
go runtime.EventsEmit(a.ctx, "telegraph", telegraph)
|
||
}
|
||
})
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
|
||
} else {
|
||
a.cronEntrys["refreshTelegraphList"] = id
|
||
}
|
||
|
||
go runtime.EventsEmit(a.ctx, "telegraph", refreshTelegraphList())
|
||
}
|
||
go MonitorStockPrices(a)
|
||
if config.EnableFund {
|
||
go MonitorFundPrices(a)
|
||
go data.NewFundApi().AllFund()
|
||
}
|
||
//检查新版本
|
||
go func() {
|
||
a.CheckUpdate()
|
||
a.cron.AddFunc("30 05 8,12,20 * * *", func() {
|
||
logger.SugaredLogger.Errorf("Checking for updates...")
|
||
a.CheckUpdate()
|
||
})
|
||
|
||
}()
|
||
|
||
//检查谷歌浏览器
|
||
//go func() {
|
||
// f := checkChromeOnWindows()
|
||
// if !f {
|
||
// go runtime.EventsEmit(a.ctx, "warnMsg", "谷歌浏览器未安装,ai分析功能可能无法使用")
|
||
// }
|
||
//}()
|
||
|
||
//检查Edge浏览器
|
||
//go func() {
|
||
// path, e := checkEdgeOnWindows()
|
||
// if !e {
|
||
// go runtime.EventsEmit(a.ctx, "warnMsg", "Edge浏览器未安装,ai分析功能可能无法使用")
|
||
// } else {
|
||
// logger.SugaredLogger.Infof("Edge浏览器已安装,路径为: %s", path)
|
||
// }
|
||
//}()
|
||
followList := data.NewStockDataApi().GetFollowList(0)
|
||
for _, follow := range *followList {
|
||
if follow.Cron == nil || *follow.Cron == "" {
|
||
continue
|
||
}
|
||
entryID, err := a.cron.AddFunc(*follow.Cron, a.AddCronTask(follow))
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("添加自动分析任务失败:%s cron=%s entryID:%v", follow.Name, *follow.Cron, entryID)
|
||
continue
|
||
}
|
||
a.cronEntrys[follow.StockCode] = entryID
|
||
}
|
||
logger.SugaredLogger.Infof("domReady-cronEntrys:%+v", a.cronEntrys)
|
||
|
||
}
|
||
|
||
func (a *App) NewsPush(news *[]models.Telegraph) {
|
||
for _, telegraph := range *news {
|
||
//if telegraph.IsRed {
|
||
go runtime.EventsEmit(a.ctx, "newsPush", telegraph)
|
||
go data.NewAlertWindowsApi("go-stock", telegraph.Source+" "+telegraph.Time, telegraph.Content, string(icon)).SendNotification()
|
||
//}
|
||
}
|
||
}
|
||
|
||
func (a *App) AddCronTask(follow data.FollowedStock) func() {
|
||
return func() {
|
||
go runtime.EventsEmit(a.ctx, "warnMsg", "开始自动分析"+follow.Name+"_"+follow.StockCode)
|
||
ai := data.NewDeepSeekOpenAi(a.ctx)
|
||
msgs := ai.NewChatStream(follow.Name, follow.StockCode, "", nil)
|
||
var res strings.Builder
|
||
|
||
chatId := ""
|
||
question := ""
|
||
for msg := range msgs {
|
||
if msg["extraContent"] != nil {
|
||
res.WriteString(msg["extraContent"].(string) + "\n")
|
||
}
|
||
if msg["content"] != nil {
|
||
res.WriteString(msg["content"].(string))
|
||
}
|
||
if msg["chatId"] != nil {
|
||
chatId = msg["chatId"].(string)
|
||
}
|
||
if msg["question"] != nil {
|
||
question = msg["question"].(string)
|
||
}
|
||
}
|
||
data.NewDeepSeekOpenAi(a.ctx).SaveAIResponseResult(follow.StockCode, follow.Name, res.String(), chatId, question)
|
||
go runtime.EventsEmit(a.ctx, "warnMsg", "AI分析完成:"+follow.Name+"_"+follow.StockCode)
|
||
|
||
}
|
||
}
|
||
|
||
func refreshTelegraphList() *[]string {
|
||
url := "https://www.cls.cn/telegraph"
|
||
response, err := resty.New().R().
|
||
SetHeader("Referer", "https://www.cls.cn/").
|
||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
|
||
Get(fmt.Sprintf(url))
|
||
if err != nil {
|
||
return &[]string{}
|
||
}
|
||
//logger.SugaredLogger.Info(string(response.Body()))
|
||
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
||
if err != nil {
|
||
return &[]string{}
|
||
}
|
||
var telegraph []string
|
||
document.Find("div.telegraph-content-box").Each(func(i int, selection *goquery.Selection) {
|
||
//logger.SugaredLogger.Info(selection.Text())
|
||
telegraph = append(telegraph, selection.Text())
|
||
})
|
||
return &telegraph
|
||
}
|
||
|
||
// isTradingDay 判断是否是交易日
|
||
func isTradingDay(date time.Time) bool {
|
||
weekday := date.Weekday()
|
||
// 判断是否是周末
|
||
if weekday == time.Saturday || weekday == time.Sunday {
|
||
return false
|
||
}
|
||
// 这里可以添加具体的节假日判断逻辑
|
||
// 例如:判断是否是春节、国庆节等
|
||
return true
|
||
}
|
||
|
||
// isTradingTime 判断是否是交易时间
|
||
func isTradingTime(date time.Time) bool {
|
||
if !isTradingDay(date) {
|
||
return false
|
||
}
|
||
|
||
hour, minute, _ := date.Clock()
|
||
|
||
// 判断是否在9:15到11:30之间
|
||
if (hour == 9 && minute >= 15) || (hour == 10) || (hour == 11 && minute <= 30) {
|
||
return true
|
||
}
|
||
|
||
// 判断是否在13:00到15:00之间
|
||
if (hour == 13) || (hour == 14) || (hour == 15 && minute <= 0) {
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// IsHKTradingTime 判断当前时间是否在港股交易时间内
|
||
func IsHKTradingTime(date time.Time) bool {
|
||
hour, minute, _ := date.Clock()
|
||
|
||
// 开市前竞价时段:09:00 - 09:30
|
||
if (hour == 9 && minute >= 0) || (hour == 9 && minute <= 30) {
|
||
return true
|
||
}
|
||
|
||
// 上午持续交易时段:09:30 - 12:00
|
||
if (hour == 9 && minute > 30) || (hour >= 10 && hour < 12) || (hour == 12 && minute == 0) {
|
||
return true
|
||
}
|
||
|
||
// 下午持续交易时段:13:00 - 16:00
|
||
if (hour == 13 && minute >= 0) || (hour >= 14 && hour < 16) || (hour == 16 && minute == 0) {
|
||
return true
|
||
}
|
||
|
||
// 收市竞价交易时段:16:00 - 16:10
|
||
if (hour == 16 && minute >= 0) || (hour == 16 && minute <= 10) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// IsUSTradingTime 判断当前时间是否在美股交易时间内
|
||
func IsUSTradingTime(date time.Time) bool {
|
||
// 获取美国东部时区
|
||
est, err := time.LoadLocation("America/New_York")
|
||
var estTime time.Time
|
||
if err != nil {
|
||
estTime = date.Add(time.Hour * -12)
|
||
} else {
|
||
// 将当前时间转换为美国东部时间
|
||
estTime = date.In(est)
|
||
}
|
||
|
||
// 判断是否是周末
|
||
weekday := estTime.Weekday()
|
||
if weekday == time.Saturday || weekday == time.Sunday {
|
||
return false
|
||
}
|
||
|
||
// 获取小时和分钟
|
||
hour, minute, _ := estTime.Clock()
|
||
|
||
// 判断是否在4:00 AM到9:30 AM之间(盘前)
|
||
if (hour == 4) || (hour == 5) || (hour == 6) || (hour == 7) || (hour == 8) || (hour == 9 && minute < 30) {
|
||
return true
|
||
}
|
||
|
||
// 判断是否在9:30 AM到4:00 PM之间(盘中)
|
||
if (hour == 9 && minute >= 30) || (hour >= 10 && hour < 16) || (hour == 16 && minute == 0) {
|
||
return true
|
||
}
|
||
|
||
// 判断是否在4:00 PM到8:00 PM之间(盘后)
|
||
if (hour == 16 && minute > 0) || (hour >= 17 && hour < 20) || (hour == 20 && minute == 0) {
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
func MonitorFundPrices(a *App) {
|
||
dest := &[]data.FollowedFund{}
|
||
db.Dao.Model(&data.FollowedFund{}).Find(dest)
|
||
for _, follow := range *dest {
|
||
_, err := data.NewFundApi().CrawlFundBasic(follow.Code)
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("获取基金基本信息失败,基金代码:%s,错误信息:%s", follow.Code, err.Error())
|
||
continue
|
||
}
|
||
data.NewFundApi().CrawlFundNetEstimatedUnit(follow.Code)
|
||
data.NewFundApi().CrawlFundNetUnitValue(follow.Code)
|
||
}
|
||
}
|
||
|
||
func MonitorStockPrices(a *App) {
|
||
dest := &[]data.FollowedStock{}
|
||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||
total := float64(0)
|
||
//for _, follow := range *dest {
|
||
// stockData := getStockInfo(follow)
|
||
// total += stockData.ProfitAmountToday
|
||
// price, _ := convertor.ToFloat(stockData.Price)
|
||
// if stockData.PrePrice != price {
|
||
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
||
// }
|
||
//}
|
||
|
||
stockInfos := GetStockInfos(*dest...)
|
||
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
|
||
price, _ := convertor.ToFloat(stockInfo.Price)
|
||
|
||
if stockInfo.PrePrice != price {
|
||
//logger.SugaredLogger.Infof("-----------sz------------股票代码: %s, 股票名称: %s, 股票价格: %s,盘前盘后:%s", stockInfo.Code, stockInfo.Name, stockInfo.Price, stockInfo.BA)
|
||
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
||
}
|
||
|
||
}
|
||
if total != 0 {
|
||
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||
systray.SetTooltip(title)
|
||
}
|
||
|
||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||
//runtime.WindowSetTitle(a.ctx, title)
|
||
|
||
}
|
||
func GetStockInfos(follows ...data.FollowedStock) *[]data.StockInfo {
|
||
stockInfos := make([]data.StockInfo, 0)
|
||
stockCodes := make([]string, 0)
|
||
for _, follow := range follows {
|
||
if strutil.HasPrefixAny(follow.StockCode, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||
continue
|
||
}
|
||
if strutil.HasPrefixAny(follow.StockCode, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||
continue
|
||
}
|
||
if strutil.HasPrefixAny(follow.StockCode, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||
continue
|
||
}
|
||
stockCodes = append(stockCodes, follow.StockCode)
|
||
}
|
||
stockData, _ := data.NewStockDataApi().GetStockCodeRealTimeData(stockCodes...)
|
||
for _, info := range *stockData {
|
||
v, ok := slice.FindBy(follows, func(idx int, follow data.FollowedStock) bool {
|
||
if strutil.HasPrefixAny(follow.StockCode, []string{"US", "us"}) {
|
||
return strings.ToLower(strings.Replace(follow.StockCode, "us", "gb_", 1)) == info.Code
|
||
}
|
||
|
||
return follow.StockCode == info.Code
|
||
})
|
||
if ok {
|
||
addStockFollowData(v, &info)
|
||
stockInfos = append(stockInfos, info)
|
||
}
|
||
}
|
||
return &stockInfos
|
||
}
|
||
func getStockInfo(follow data.FollowedStock) *data.StockInfo {
|
||
stockCode := follow.StockCode
|
||
stockDatas, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
||
if err != nil || len(*stockDatas) == 0 {
|
||
return &data.StockInfo{}
|
||
}
|
||
stockData := (*stockDatas)[0]
|
||
addStockFollowData(follow, &stockData)
|
||
return &stockData
|
||
}
|
||
|
||
func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
|
||
stockData.PrePrice = follow.Price //上次当前价格
|
||
stockData.Sort = follow.Sort
|
||
stockData.CostPrice = follow.CostPrice //成本价
|
||
stockData.CostVolume = follow.Volume //成本量
|
||
stockData.AlarmChangePercent = follow.AlarmChangePercent
|
||
stockData.AlarmPrice = follow.AlarmPrice
|
||
stockData.Groups = follow.Groups
|
||
|
||
//当前价格
|
||
price, _ := convertor.ToFloat(stockData.Price)
|
||
//当前价格为0 时 使用卖一价格作为当前价格
|
||
if price == 0 {
|
||
price, _ = convertor.ToFloat(stockData.A1P)
|
||
}
|
||
//当前价格依然为0 时 使用买一报价作为当前价格
|
||
if price == 0 {
|
||
price, _ = convertor.ToFloat(stockData.B1P)
|
||
}
|
||
|
||
//昨日收盘价
|
||
preClosePrice, _ := convertor.ToFloat(stockData.PreClose)
|
||
|
||
//当前价格依然为0 时 使用昨日收盘价为当前价格
|
||
if price == 0 {
|
||
price = preClosePrice
|
||
}
|
||
|
||
//今日最高价
|
||
highPrice, _ := convertor.ToFloat(stockData.High)
|
||
if highPrice == 0 {
|
||
highPrice, _ = convertor.ToFloat(stockData.Open)
|
||
}
|
||
|
||
//今日最低价
|
||
lowPrice, _ := convertor.ToFloat(stockData.Low)
|
||
if lowPrice == 0 {
|
||
lowPrice, _ = convertor.ToFloat(stockData.Open)
|
||
}
|
||
//开盘价
|
||
//openPrice, _ := convertor.ToFloat(stockData.Open)
|
||
|
||
if price > 0 && preClosePrice > 0 {
|
||
stockData.ChangePrice = mathutil.RoundToFloat(price-preClosePrice, 2)
|
||
stockData.ChangePercent = mathutil.RoundToFloat(mathutil.Div(price-preClosePrice, preClosePrice)*100, 3)
|
||
}
|
||
if highPrice > 0 && preClosePrice > 0 {
|
||
stockData.HighRate = mathutil.RoundToFloat(mathutil.Div(highPrice-preClosePrice, preClosePrice)*100, 3)
|
||
}
|
||
if lowPrice > 0 && preClosePrice > 0 {
|
||
stockData.LowRate = mathutil.RoundToFloat(mathutil.Div(lowPrice-preClosePrice, preClosePrice)*100, 3)
|
||
}
|
||
if follow.CostPrice > 0 && follow.Volume > 0 {
|
||
if price > 0 {
|
||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(price-follow.CostPrice, follow.CostPrice)*100, 3)
|
||
stockData.ProfitAmount = mathutil.RoundToFloat((price-follow.CostPrice)*float64(follow.Volume), 2)
|
||
stockData.ProfitAmountToday = mathutil.RoundToFloat((price-preClosePrice)*float64(follow.Volume), 2)
|
||
} else {
|
||
//未开盘时当前价格为昨日收盘价
|
||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(preClosePrice-follow.CostPrice, follow.CostPrice)*100, 3)
|
||
stockData.ProfitAmount = mathutil.RoundToFloat((preClosePrice-follow.CostPrice)*float64(follow.Volume), 2)
|
||
// 未开盘时,今日盈亏为 0
|
||
stockData.ProfitAmountToday = 0
|
||
}
|
||
|
||
}
|
||
|
||
//logger.SugaredLogger.Debugf("stockData:%+v", stockData)
|
||
if follow.Price != price && price > 0 {
|
||
go db.Dao.Model(follow).Where("stock_code = ?", follow.StockCode).Updates(map[string]interface{}{
|
||
"price": price,
|
||
})
|
||
}
|
||
}
|
||
|
||
// beforeClose is called when the application is about to quit,
|
||
// either by clicking the window close button or calling runtime.Quit.
|
||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||
defer PanicHandler()
|
||
|
||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||
Type: runtime.QuestionDialog,
|
||
Title: "go-stock",
|
||
Message: "确定关闭吗?",
|
||
Buttons: []string{"确定"},
|
||
Icon: icon,
|
||
CancelButton: "取消",
|
||
})
|
||
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||
return false
|
||
}
|
||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||
if dialog == "No" {
|
||
return true
|
||
} else {
|
||
systray.Quit()
|
||
a.cron.Stop()
|
||
return false
|
||
}
|
||
}
|
||
|
||
// shutdown is called at application termination
|
||
func (a *App) shutdown(ctx context.Context) {
|
||
defer PanicHandler()
|
||
// Perform your teardown here
|
||
//os.Exit(0)
|
||
logger.SugaredLogger.Infof("application shutdown Version:%s", Version)
|
||
}
|
||
|
||
// Greet returns a greeting for the given name
|
||
func (a *App) Greet(stockCode string) *data.StockInfo {
|
||
//stockInfo, _ := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
||
|
||
follow := &data.FollowedStock{
|
||
StockCode: stockCode,
|
||
}
|
||
db.Dao.Model(follow).Where("stock_code = ?", stockCode).Preload("Groups").Preload("Groups.GroupInfo").First(follow)
|
||
stockInfo := getStockInfo(*follow)
|
||
return stockInfo
|
||
}
|
||
|
||
func (a *App) Follow(stockCode string) string {
|
||
return data.NewStockDataApi().Follow(stockCode)
|
||
}
|
||
|
||
func (a *App) UnFollow(stockCode string) string {
|
||
return data.NewStockDataApi().UnFollow(stockCode)
|
||
}
|
||
|
||
func (a *App) GetFollowList(groupId int) *[]data.FollowedStock {
|
||
return data.NewStockDataApi().GetFollowList(groupId)
|
||
}
|
||
|
||
func (a *App) GetStockList(key string) []data.StockBasic {
|
||
return data.NewStockDataApi().GetStockList(key)
|
||
}
|
||
|
||
func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int64) string {
|
||
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
|
||
}
|
||
|
||
func (a *App) SetAlarmChangePercent(val, alarmPrice float64, stockCode string) string {
|
||
return data.NewStockDataApi().SetAlarmChangePercent(val, alarmPrice, stockCode)
|
||
}
|
||
func (a *App) SetStockSort(sort int64, stockCode string) {
|
||
data.NewStockDataApi().SetStockSort(sort, stockCode)
|
||
}
|
||
func (a *App) SendDingDingMessage(message string, stockCode string) string {
|
||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||
if ttl > 0 {
|
||
return ""
|
||
}
|
||
err := a.cache.Set([]byte(stockCode), []byte("1"), 60*5)
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||
return ""
|
||
}
|
||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||
}
|
||
|
||
// SendDingDingMessageByType msgType 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
|
||
func (a *App) SendDingDingMessageByType(message string, stockCode string, msgType int) string {
|
||
|
||
if strutil.HasPrefixAny(stockCode, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||
return "非A股交易时间"
|
||
}
|
||
if strutil.HasPrefixAny(stockCode, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||
return "非港股交易时间"
|
||
}
|
||
if strutil.HasPrefixAny(stockCode, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||
return "非美股交易时间"
|
||
}
|
||
|
||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||
//logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||
if ttl > 0 {
|
||
return ""
|
||
}
|
||
err := a.cache.Set([]byte(stockCode), []byte("1"), getMsgTypeTTL(msgType))
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||
return ""
|
||
}
|
||
stockInfo := &data.StockInfo{}
|
||
db.Dao.Model(stockInfo).Where("code = ?", stockCode).First(stockInfo)
|
||
go data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification()
|
||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||
}
|
||
|
||
func (a *App) NewChatStream(stock, stockCode, question string, sysPromptId *int) {
|
||
msgs := data.NewDeepSeekOpenAi(a.ctx).NewChatStream(stock, stockCode, question, sysPromptId)
|
||
for msg := range msgs {
|
||
runtime.EventsEmit(a.ctx, "newChatStream", msg)
|
||
}
|
||
runtime.EventsEmit(a.ctx, "newChatStream", "DONE")
|
||
}
|
||
|
||
func (a *App) SaveAIResponseResult(stockCode, stockName, result, chatId, question string) {
|
||
data.NewDeepSeekOpenAi(a.ctx).SaveAIResponseResult(stockCode, stockName, result, chatId, question)
|
||
}
|
||
func (a *App) GetAIResponseResult(stock string) *models.AIResponseResult {
|
||
return data.NewDeepSeekOpenAi(a.ctx).GetAIResponseResult(stock)
|
||
}
|
||
|
||
func (a *App) GetVersionInfo() *models.VersionInfo {
|
||
return &models.VersionInfo{
|
||
Version: Version,
|
||
Icon: GetImageBase(icon),
|
||
Alipay: GetImageBase(alipay),
|
||
Wxpay: GetImageBase(wxpay),
|
||
Content: VersionCommit,
|
||
}
|
||
}
|
||
|
||
// checkChromeOnWindows 在 Windows 系统上检查谷歌浏览器是否安装
|
||
func checkChromeOnWindows() bool {
|
||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||
if err != nil {
|
||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
defer key.Close()
|
||
}
|
||
defer key.Close()
|
||
_, _, err = key.GetValue("Path", nil)
|
||
return err == nil
|
||
}
|
||
|
||
// checkEdgeOnWindows 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
||
func checkEdgeOnWindows() (string, bool) {
|
||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||
if err != nil {
|
||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||
if err != nil {
|
||
return "", false
|
||
}
|
||
defer key.Close()
|
||
}
|
||
defer key.Close()
|
||
path, _, err := key.GetStringValue("Path")
|
||
if err != nil {
|
||
return "", false
|
||
}
|
||
return path, true
|
||
}
|
||
|
||
func GetImageBase(bytes []byte) string {
|
||
return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(bytes)
|
||
}
|
||
|
||
func GenNotificationMsg(stockInfo *data.StockInfo) string {
|
||
Price, err := convertor.ToFloat(stockInfo.Price)
|
||
if err != nil {
|
||
Price = 0
|
||
}
|
||
PreClose, err := convertor.ToFloat(stockInfo.PreClose)
|
||
if err != nil {
|
||
PreClose = 0
|
||
}
|
||
var RF float64
|
||
if PreClose > 0 {
|
||
RF = mathutil.RoundToFloat(((Price-PreClose)/PreClose)*100, 2)
|
||
}
|
||
|
||
return "[" + stockInfo.Name + "] " + stockInfo.Price + " " + convertor.ToString(RF) + "% " + stockInfo.Date + " " + stockInfo.Time
|
||
}
|
||
|
||
// msgType : 1 涨跌报警(5分钟);2 股价报警(30分钟) 3 成本价报警(30分钟)
|
||
func getMsgTypeTTL(msgType int) int {
|
||
switch msgType {
|
||
case 1:
|
||
return 60 * 5
|
||
case 2:
|
||
return 60 * 30
|
||
case 3:
|
||
return 60 * 30
|
||
default:
|
||
return 60 * 5
|
||
}
|
||
}
|
||
|
||
func getMsgTypeName(msgType int) string {
|
||
switch msgType {
|
||
case 1:
|
||
return "涨跌报警"
|
||
case 2:
|
||
return "股价报警"
|
||
case 3:
|
||
return "成本价报警"
|
||
default:
|
||
return "未知类型"
|
||
}
|
||
}
|
||
|
||
func onExit(a *App) {
|
||
// 清理操作
|
||
logger.SugaredLogger.Infof("systray onExit")
|
||
//systray.Quit()
|
||
//runtime.Quit(a.ctx)
|
||
}
|
||
|
||
func onReady(a *App) {
|
||
|
||
// 初始化操作
|
||
logger.SugaredLogger.Infof("systray onReady")
|
||
systray.SetIcon(icon2)
|
||
systray.SetTitle("go-stock")
|
||
systray.SetTooltip("go-stock 股票行情实时获取")
|
||
// 创建菜单项
|
||
show := systray.AddMenuItem("显示", "显示应用程序")
|
||
show.Click(func() {
|
||
//logger.SugaredLogger.Infof("显示应用程序")
|
||
runtime.WindowShow(a.ctx)
|
||
})
|
||
hide := systray.AddMenuItem("隐藏", "隐藏应用程序")
|
||
hide.Click(func() {
|
||
//logger.SugaredLogger.Infof("隐藏应用程序")
|
||
runtime.WindowHide(a.ctx)
|
||
})
|
||
systray.AddSeparator()
|
||
mQuitOrig := systray.AddMenuItem("退出", "退出应用程序")
|
||
mQuitOrig.Click(func() {
|
||
//logger.SugaredLogger.Infof("退出应用程序")
|
||
runtime.Quit(a.ctx)
|
||
})
|
||
systray.SetOnRClick(func(menu systray.IMenu) {
|
||
menu.ShowMenu()
|
||
//logger.SugaredLogger.Infof("SetOnRClick")
|
||
})
|
||
systray.SetOnClick(func(menu systray.IMenu) {
|
||
//logger.SugaredLogger.Infof("SetOnClick")
|
||
menu.ShowMenu()
|
||
})
|
||
systray.SetOnDClick(func(menu systray.IMenu) {
|
||
menu.ShowMenu()
|
||
//logger.SugaredLogger.Infof("SetOnDClick")
|
||
})
|
||
}
|
||
|
||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
||
//logger.SugaredLogger.Infof("UpdateConfig:%+v", settings)
|
||
if settings.RefreshInterval > 0 {
|
||
if entryID, exists := a.cronEntrys["MonitorStockPrices"]; exists {
|
||
a.cron.Remove(entryID)
|
||
}
|
||
id, _ := a.cron.AddFunc(fmt.Sprintf("@every %ds", settings.RefreshInterval), func() {
|
||
//logger.SugaredLogger.Infof("MonitorStockPrices:%s", time.Now())
|
||
MonitorStockPrices(a)
|
||
})
|
||
a.cronEntrys["MonitorStockPrices"] = id
|
||
}
|
||
|
||
return data.NewSettingsApi(settings).UpdateConfig()
|
||
}
|
||
|
||
func (a *App) GetConfig() *data.Settings {
|
||
return data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||
}
|
||
|
||
func (a *App) ExportConfig() string {
|
||
config := data.NewSettingsApi(&data.Settings{}).Export()
|
||
file, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
|
||
Title: "导出配置文件",
|
||
CanCreateDirectories: true,
|
||
DefaultFilename: "config.json",
|
||
})
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("导出配置文件失败:%s", err.Error())
|
||
return err.Error()
|
||
}
|
||
err = os.WriteFile(file, []byte(config), 0644)
|
||
if err != nil {
|
||
logger.SugaredLogger.Errorf("导出配置文件失败:%s", err.Error())
|
||
return err.Error()
|
||
}
|
||
return "导出成功:" + file
|
||
}
|
||
func getScreenResolution() (int, int, error) {
|
||
//user32 := syscall.NewLazyDLL("user32.dll")
|
||
//getSystemMetrics := user32.NewProc("GetSystemMetrics")
|
||
//
|
||
//width, _, _ := getSystemMetrics.Call(0)
|
||
//height, _, _ := getSystemMetrics.Call(1)
|
||
|
||
return int(1366), int(768), nil
|
||
}
|
||
|
||
func (a *App) ShareAnalysis(stockCode, stockName string) string {
|
||
//http://go-stock.sparkmemory.top:16688/upload
|
||
res := data.NewDeepSeekOpenAi(a.ctx).GetAIResponseResult(stockCode)
|
||
if res != nil && len(res.Content) > 100 {
|
||
analysisTime := res.CreatedAt.Format("2006/01/02")
|
||
logger.SugaredLogger.Infof("%s analysisTime:%s", res.CreatedAt, analysisTime)
|
||
response, err := resty.New().SetHeader("ua-x", "go-stock").R().SetFormData(map[string]string{
|
||
"text": res.Content,
|
||
"stockCode": stockCode,
|
||
"stockName": stockName,
|
||
"analysisTime": analysisTime,
|
||
}).Post("http://go-stock.sparkmemory.top:16688/upload")
|
||
if err != nil {
|
||
return err.Error()
|
||
}
|
||
return response.String()
|
||
} else {
|
||
return "分析结果异常"
|
||
}
|
||
}
|
||
|
||
func (a *App) GetfundList(key string) []data.FundBasic {
|
||
return data.NewFundApi().GetFundList(key)
|
||
}
|
||
func (a *App) GetFollowedFund() []data.FollowedFund {
|
||
return data.NewFundApi().GetFollowedFund()
|
||
}
|
||
func (a *App) FollowFund(fundCode string) string {
|
||
return data.NewFundApi().FollowFund(fundCode)
|
||
}
|
||
func (a *App) UnFollowFund(fundCode string) string {
|
||
return data.NewFundApi().UnFollowFund(fundCode)
|
||
}
|
||
func (a *App) SaveAsMarkdown(stockCode, stockName string) string {
|
||
res := data.NewDeepSeekOpenAi(a.ctx).GetAIResponseResult(stockCode)
|
||
if res != nil && len(res.Content) > 100 {
|
||
analysisTime := res.CreatedAt.Format("2006-01-02_15_04_05")
|
||
file, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
|
||
Title: "保存为Markdown",
|
||
DefaultFilename: fmt.Sprintf("%s[%s]AI分析结果_%s.md", stockName, stockCode, analysisTime),
|
||
Filters: []runtime.FileFilter{
|
||
{
|
||
DisplayName: "Markdown",
|
||
Pattern: "*.md;*.markdown",
|
||
},
|
||
},
|
||
})
|
||
if err != nil {
|
||
return err.Error()
|
||
}
|
||
err = os.WriteFile(file, []byte(res.Content), 0644)
|
||
return "已保存至:" + file
|
||
}
|
||
return "分析结果异常,无法保存。"
|
||
}
|
||
|
||
func (a *App) GetPromptTemplates(name, promptType string) *[]models.PromptTemplate {
|
||
return data.NewPromptTemplateApi().GetPromptTemplates(name, promptType)
|
||
}
|
||
func (a *App) AddPrompt(prompt models.Prompt) string {
|
||
promptTemplate := models.PromptTemplate{
|
||
ID: prompt.ID,
|
||
Content: prompt.Content,
|
||
Name: prompt.Name,
|
||
Type: prompt.Type,
|
||
}
|
||
return data.NewPromptTemplateApi().AddPrompt(promptTemplate)
|
||
}
|
||
func (a *App) DelPrompt(id uint) string {
|
||
return data.NewPromptTemplateApi().DelPrompt(id)
|
||
}
|
||
func (a *App) SetStockAICron(cronText, stockCode string) {
|
||
data.NewStockDataApi().SetStockAICron(cronText, stockCode)
|
||
if strutil.HasPrefixAny(stockCode, []string{"gb_"}) {
|
||
stockCode = strings.ToUpper(stockCode)
|
||
stockCode = strings.Replace(stockCode, "gb_", "us", 1)
|
||
stockCode = strings.Replace(stockCode, "GB_", "us", 1)
|
||
}
|
||
if entryID, exists := a.cronEntrys[stockCode]; exists {
|
||
a.cron.Remove(entryID)
|
||
}
|
||
follow := data.NewStockDataApi().GetFollowedStockByStockCode(stockCode)
|
||
id, _ := a.cron.AddFunc(cronText, a.AddCronTask(follow))
|
||
a.cronEntrys[stockCode] = id
|
||
|
||
}
|
||
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
|
||
notification := toast.Notification{
|
||
AppID: "go-stock",
|
||
Title: "go-stock",
|
||
Message: "程序已经在运行了",
|
||
Icon: "",
|
||
Duration: "short",
|
||
Audio: toast.Default,
|
||
}
|
||
err := notification.Push()
|
||
if err != nil {
|
||
logger.SugaredLogger.Error(err)
|
||
}
|
||
time.Sleep(time.Second * 3)
|
||
}
|
||
|
||
func (a *App) AddGroup(group data.Group) string {
|
||
ok := data.NewStockGroupApi(db.Dao).AddGroup(group)
|
||
if ok {
|
||
return "添加成功"
|
||
} else {
|
||
return "添加失败"
|
||
}
|
||
}
|
||
func (a *App) GetGroupList() []data.Group {
|
||
return data.NewStockGroupApi(db.Dao).GetGroupList()
|
||
}
|
||
|
||
func (a *App) GetGroupStockList(groupId int) []data.GroupStock {
|
||
return data.NewStockGroupApi(db.Dao).GetGroupStockByGroupId(groupId)
|
||
}
|
||
|
||
func (a *App) AddStockGroup(groupId int, stockCode string) string {
|
||
ok := data.NewStockGroupApi(db.Dao).AddStockGroup(groupId, stockCode)
|
||
if ok {
|
||
return "添加成功"
|
||
} else {
|
||
return "添加失败"
|
||
}
|
||
}
|
||
|
||
func (a *App) RemoveStockGroup(code, name string, groupId int) string {
|
||
ok := data.NewStockGroupApi(db.Dao).RemoveStockGroup(code, name, groupId)
|
||
if ok {
|
||
return "移除成功"
|
||
} else {
|
||
return "移除失败"
|
||
}
|
||
}
|
||
|
||
func (a *App) RemoveGroup(groupId int) string {
|
||
ok := data.NewStockGroupApi(db.Dao).RemoveGroup(groupId)
|
||
if ok {
|
||
return "移除成功"
|
||
} else {
|
||
return "移除失败"
|
||
}
|
||
}
|
||
|
||
func (a *App) GetStockKLine(stockCode, stockName string, days int64) *[]data.KLineData {
|
||
return data.NewStockDataApi().GetHK_KLineData(stockCode, "day", days)
|
||
}
|
||
|
||
func (a *App) GetStockMinutePriceLineData(stockCode, stockName string) map[string]any {
|
||
res := make(map[string]any, 4)
|
||
priceData, date := data.NewStockDataApi().GetStockMinutePriceData(stockCode)
|
||
res["priceData"] = priceData
|
||
res["date"] = date
|
||
res["stockName"] = stockName
|
||
res["stockCode"] = stockCode
|
||
return res
|
||
}
|
||
|
||
func (a *App) GetStockCommonKLine(stockCode, stockName string, days int64) *[]data.KLineData {
|
||
return data.NewStockDataApi().GetCommonKLineData(stockCode, "day", days)
|
||
}
|
||
|
||
func (a *App) GetTelegraphList(source string) *[]*models.Telegraph {
|
||
telegraphs := data.NewMarketNewsApi().GetTelegraphList(source)
|
||
return telegraphs
|
||
}
|
||
|
||
func (a *App) ReFleshTelegraphList(source string) *[]*models.Telegraph {
|
||
data.NewMarketNewsApi().GetNewTelegraph(30)
|
||
data.NewMarketNewsApi().GetSinaNews(30)
|
||
telegraphs := data.NewMarketNewsApi().GetTelegraphList(source)
|
||
return telegraphs
|
||
}
|
||
|
||
func (a *App) GlobalStockIndexes() map[string]any {
|
||
return data.NewMarketNewsApi().GlobalStockIndexes(30)
|
||
}
|
||
|
||
func (a *App) SummaryStockNews(question string, sysPromptId *int) {
|
||
msgs := data.NewDeepSeekOpenAi(a.ctx).NewSummaryStockNewsStream(question, sysPromptId)
|
||
for msg := range msgs {
|
||
runtime.EventsEmit(a.ctx, "summaryStockNews", msg)
|
||
}
|
||
runtime.EventsEmit(a.ctx, "summaryStockNews", "DONE")
|
||
}
|
||
func (a *App) GetIndustryRank(sort string, cnt int) []any {
|
||
res := data.NewMarketNewsApi().GetIndustryRank(sort, cnt)
|
||
return res["data"].([]any)
|
||
}
|
||
func (a *App) GetIndustryMoneyRankSina(fenlei, sort string) []map[string]any {
|
||
res := data.NewMarketNewsApi().GetIndustryMoneyRankSina(fenlei, sort)
|
||
return res
|
||
}
|
||
func (a *App) GetMoneyRankSina(sort string) []map[string]any {
|
||
res := data.NewMarketNewsApi().GetMoneyRankSina(sort)
|
||
return res
|
||
}
|
||
|
||
func (a *App) GetStockMoneyTrendByDay(stockCode string, days int) []map[string]any {
|
||
res := data.NewMarketNewsApi().GetStockMoneyTrendByDay(stockCode, days)
|
||
slice.Reverse(res)
|
||
return res
|
||
}
|