Compare commits

...

19 Commits

Author SHA1 Message Date
ArvinLovegood
63d278b9aa feat(stock):加载用户自定义词典进行情感分析(用户可以自己定义词典调整热词权重)
- 在默认词典加载成功后,增加加载用户自定义词典逻辑
- 判断用户词典文件是否存在,避免加载空文件
- 记录用户词典加载成功或失败的日志信息
- 保持原有默认词典加载流程不变
- 确保词典加载错误不会中断程序运行
- 统一词典加载后的日志输出格式
2025-11-22 13:02:33 +08:00
ArvinLovegood
5621d40c71 feat(market):调整词频树图数据计算方式
- 将词频树图中的值计算方式从 Frequency 改为 Frequency * Weight
- 更新 treemap 数据映射逻辑以反映新的权重计算
- 确保前端展示的数据更加准确地反映词汇的重要性
2025-11-22 12:58:41 +08:00
ArvinLovegood
26e9753b94 feat(data):添加十五五规划重点领域关键词到基础词典
- 新增涵盖七个大类的政策关键词汇
- 设置词汇权重范围为310-350,适配政策资讯分词场景
- 包含核心战略方向、科技创新与数字经济等领域术语
- 添加能源与绿色转型相关高频词汇
- 补充高端制造与新兴产业的专业表达
- 增加乡村振兴与农业现代化关键词
- 纳入对外开放与贸易升级术语
- 更新社会民生与公共服务领域用词
2025-11-22 12:55:11 +08:00
ArvinLovegood
b7f6dbd2da feat(data):更新股票情感分析词典并优化测试代码
- 在 base.txt 中新增多个知名美股和中概股词汇,提升分词准确性
- 调整测试代码逻辑,移除随机新闻获取,使用固定文本进行情感分析验证
- 初始化数据库连接和情感分析模块,确保测试环境正常运行
- 添加详细的市场新闻示例文本,增强测试覆盖度和结果可靠性
2025-11-22 08:03:44 +08:00
ArvinLovegood
18dd01b613 feat(data):热词算法优化(最近24小时资讯+去重)
- 新增 GetNews24HoursList 方法,用于获取最近24小时内的新闻数据
- 支持按来源筛选新闻,如“财联社电报”
- 添加内容去重逻辑,避免重复新闻条目
- 自动加载新闻关联的标签信息,并构建主题标签字段
- 优化查询逻辑,提升数据获取效率与准确性
2025-11-22 07:48:00 +08:00
ArvinLovegood
81bb33a135 feat(sentiment):新增带频率权重的情感分析功能
- 新增 AnalyzeSentimentWithFreqWeight 方法,支持词频统计与权重分析
- 扩展前端组件 market.vue,集成词频热力图展示功能
- 更新后端词典库,新增 base.txt 金融专业词汇字典
- 引入 ECharts 实现词频 TreeMap 可视化展示
- 优化情感分析算法,增加对股票名称及行业标签的识别支持
- 完善词频过滤逻辑,去除标点符号与无效字符干扰
- 增加词典初始化方法 InitAnalyzeSentiment,提升分析准确性
2025-11-21 20:17:57 +08:00
ArvinLovegood
9926b61fac fix(data): 调整新闻等级判断逻辑
- 修改 GetLevel 函数中的比较操作符,确保更准确的等级判定
- 将大于等于 "C" 的条件改为严格大于 "C",以符合新的业务需求
2025-11-21 13:35:28 +08:00
ArvinLovegood
5e975b060c fix(backend):偶尔修复闪退BUG
- 添加空数据检查避免nil指针异常
2025-11-21 09:12:54 +08:00
ArvinLovegood
e8f063fd9b feat(news):添加新闻情绪标签显示功能(情绪标签仅供参考)
- 在新闻列表项中新增情绪标签展示
- 根据情绪结果动态设置标签颜色和文本
- 支持看涨、看跌和其他情绪状态的可视化呈现
2025-11-20 19:25:00 +08:00
ArvinLovegood
8b0b53fae7 refactor(data):重构股票数据获取与新闻电报处理逻辑
- 修改 TelegraphList 方法返回类型为 *[]models.Telegraph
- 更新获取新闻电报的调用方式,替换原有接口方法
- 新增 Telegraph 数据创建及标签关联逻辑
- 调整股票基础信息更新策略,采用批量删除后插入
- 移除旧有的增量更新逻辑,提高数据同步效率
- 增加对 Telegraph 标签(subjects)的解析与存储支持
- 修正模型字段注解,移除无效的 gorm 标签配置
- 添加测试函数用于验证股票基础信息同步功能
2025-11-20 18:55:12 +08:00
ArvinLovegood
b29c380055 feat(backend):更新财联社电报爬取功能
- 实现 TelegraphList 方法用于获取财联社电报数据
- 添加 GetLevel 函数判断电报等级是否为重要
- 修改去重逻辑,使用 content 和 time作为唯一标识- 在模型中增加 DataTime 字段以支持时间查询
- 更新测试文件,新增对 TelegraphList 的测试用例- 调整 GetNewsList2 方法,触发 TelegraphList 爬取任务
2025-11-18 17:46:00 +08:00
ArvinLovegood
cf58a707c7 refactor(stock):重构股票数据接口并新增历史资金流向功能
- 调整 import 包顺序,优化代码结构
- 新增 GetStockHistoryMoneyData 方法用于获取历史资金流向
- 移除无用空行,提升代码可读性- 更新 README 文档中的大模型平台链接信息
- 删除测试文件中冗余的日志调试代码行
2025-11-16 11:17:45 +08:00
ArvinLovegood
1ae1bb0116 feat(vip):移除水印内容显示
- 更新窗口标题以包含授权声明
- 移除水印内容显示为空字符串
2025-10-31 18:37:26 +08:00
ArvinLovegood
d8971935ee feat(settings):添加AI智能体功能开关(建议关闭,使用体验不理想)
- 在设置界面新增AI智能体启用开关
- 支持保存和读取AI智能体启用状态
- 更新配置模型以支持新的启用选项
- 动态控制菜单项显示状态
- 同步前后端配置结构以支持新功能
2025-10-31 17:11:17 +08:00
ArvinLovegood
9c68458b81 feat(stockhotmap):添加财联社行情数据标签页
- 在 stockhotmap 组件中新增财联社-行情数据标签页
- 配置嵌入链接指向 https://www.cls.cn/quotation
- 设置高度为 calc(100vh - 252px) 以适配页面布局
2025-10-31 16:54:05 +08:00
ArvinLovegood
b367d1eb40 feat(stockhotmap):启用百度股市通和摸鱼网页标签页
- 取消注释百度股市通标签页,恢复其功能
- 取消注释摸鱼标签页,恢复其功能
-保持其他标签页配置不变
2025-10-29 16:18:29 +08:00
ArvinLovegood
8fe79adbb1 feat(stock):增加股票搜索结果数量并优化搜索条件
- 将随机返回的股票数量从5-10支增加到5-20支
- 更新测试用例中的搜索关键词,提高筛选条件准确性
- 调整搜索逻辑以适应新的数据范围和质量要求
2025-10-29 16:13:30 +08:00
ArvinLovegood
1d81fdba87 chore(deps):指标选股问题
- 将 User-Agent 中的 Firefox 版本从 140.0 更新为 145.0
2025-10-27 18:16:25 +08:00
ArvinLovegood
6aca0e15cc fix(backend): 指标选股问题
- 修改fingerprint和requestId为新的固定值
- 将timestamp设置为动态生成的时间戳
- 更新xcId为新的标识符
- 在测试中增加返回结果的日志输出
2025-10-27 18:15:02 +08:00
37 changed files with 1747362 additions and 121 deletions

View File

@@ -37,11 +37,9 @@
| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 |
| [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 |
| [DeepSeek](https://www.deepseek.com/) | ✅ | deepseek-reasoner,deepseek-chat |
| [大模型聚合平台](https://cloud.siliconflow.cn/i/foufCerk) | ✅ | 如:[302.AI](https://share.302.ai/1KUpfG)[硅基流动](https://cloud.siliconflow.cn/i/foufCerk)[火山方舟](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) |
| [大模型聚合平台](https://cloud.siliconflow.cn/i/foufCerk) | ✅ | 如:[硅基流动](https://cloud.siliconflow.cn/i/foufCerk)[火山方舟](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) |
### <span style="color: #568DF4;">各位亲爱的朋友们,如果您对这个项目感兴趣,请先给我一个<i style="color: #EA2626;">star</i>吧,谢谢!</span>💕
- 302.AI新用户使用邀请码注册即可领取 $1 测试额度![注册链接](https://share.302.ai/1KUpfG)
[//]: # (- 优云智算by UCloud万卡规模4090免费用10小时新人注册另增50万tokens海量热门源项目镜像一键部署[注册链接]&#40;https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock&#41;)
- 火山方舟新用户每个模型注册即送50万tokens[注册链接](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ)
- 硅基流动(siliconflow)注册即送2000万Tokens[注册链接](https://cloud.siliconflow.cn/i/foufCerk)

115
app.go
View File

@@ -428,7 +428,8 @@ func (a *App) domReady(ctx context.Context) {
a.cronEntrys["MonitorStockPrices"] = id
}
entryID, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
news := data.NewMarketNewsApi().GetNewTelegraph(30)
//news := data.NewMarketNewsApi().GetNewTelegraph(30)
news := data.NewMarketNewsApi().TelegraphList(30)
if config.EnablePushNews {
go a.NewsPush(news)
}
@@ -564,65 +565,84 @@ func (a *App) CheckStockBaseInfo(ctx context.Context) {
SetResult(stockBasics).
Get("http://8.134.249.145:18080/go-stock/stock_basic.json")
count := int64(0)
db.Dao.Model(&data.StockBasic{}).Count(&count)
if count == int64(len(*stockBasics)) {
return
}
for _, stock := range *stockBasics {
stockInfo := &data.StockBasic{
TsCode: stock.TsCode,
Name: stock.Name,
Symbol: stock.Symbol,
BKCode: stock.BKCode,
BKName: stock.BKName,
}
db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).First(stockInfo)
if stockInfo.ID == 0 {
db.Dao.Model(&data.StockBasic{}).Create(stockInfo)
} else {
db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Updates(stockInfo)
}
db.Dao.Unscoped().Model(&data.StockBasic{}).Where("1=1").Delete(&data.StockBasic{})
err := db.Dao.CreateInBatches(stockBasics, 400).Error
if err != nil {
logger.SugaredLogger.Errorf("保存StockBasic股票基础信息失败:%s", err.Error())
}
//count := int64(0)
//db.Dao.Model(&data.StockBasic{}).Count(&count)
//if count == int64(len(*stockBasics)) {
// return
//}
//for _, stock := range *stockBasics {
// stockInfo := &data.StockBasic{
// TsCode: stock.TsCode,
// Name: stock.Name,
// Symbol: stock.Symbol,
// BKCode: stock.BKCode,
// BKName: stock.BKName,
// }
// db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).First(stockInfo)
// if stockInfo.ID == 0 {
// db.Dao.Model(&data.StockBasic{}).Create(stockInfo)
// } else {
// db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Updates(stockInfo)
// }
//}
stockHKBasics := &[]models.StockInfoHK{}
resty.New().R().
SetHeader("user", "go-stock").
SetResult(stockHKBasics).
Get("http://8.134.249.145:18080/go-stock/stock_base_info_hk.json")
for _, stock := range *stockHKBasics {
stockInfo := &models.StockInfoHK{
Code: stock.Code,
Name: stock.Name,
BKName: stock.BKName,
BKCode: stock.BKCode,
}
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stock.Code).First(stockInfo)
if stockInfo.ID == 0 {
db.Dao.Model(&models.StockInfoHK{}).Create(stockInfo)
} else {
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stock.Code).Updates(stockInfo)
}
db.Dao.Unscoped().Model(&models.StockInfoHK{}).Where("1=1").Delete(&models.StockInfoHK{})
err = db.Dao.CreateInBatches(stockHKBasics, 400).Error
if err != nil {
logger.SugaredLogger.Errorf("保存StockInfoHK股票基础信息失败:%s", err.Error())
}
//for _, stock := range *stockHKBasics {
// stockInfo := &models.StockInfoHK{
// Code: stock.Code,
// Name: stock.Name,
// BKName: stock.BKName,
// BKCode: stock.BKCode,
// }
// db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stock.Code).First(stockInfo)
// if stockInfo.ID == 0 {
// db.Dao.Model(&models.StockInfoHK{}).Create(stockInfo)
// } else {
// db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stock.Code).Updates(stockInfo)
// }
//}
stockUSBasics := &[]models.StockInfoUS{}
resty.New().R().
SetHeader("user", "go-stock").
SetResult(stockUSBasics).
Get("http://8.134.249.145:18080/go-stock/stock_base_info_us.json")
for _, stock := range *stockUSBasics {
stockInfo := &models.StockInfoUS{
Code: stock.Code,
Name: stock.Name,
BKName: stock.BKName,
BKCode: stock.BKCode,
}
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stock.Code).First(stockInfo)
if stockInfo.ID == 0 {
db.Dao.Model(&models.StockInfoUS{}).Create(stockInfo)
} else {
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stock.Code).Updates(stockInfo)
}
db.Dao.Unscoped().Model(&models.StockInfoUS{}).Where("1=1").Delete(&models.StockInfoUS{})
err = db.Dao.CreateInBatches(stockUSBasics, 400).Error
if err != nil {
logger.SugaredLogger.Errorf("保存StockInfoUS股票基础信息失败:%s", err.Error())
}
//for _, stock := range *stockUSBasics {
// stockInfo := &models.StockInfoUS{
// Code: stock.Code,
// Name: stock.Name,
// BKName: stock.BKName,
// BKCode: stock.BKCode,
// }
// db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stock.Code).First(stockInfo)
// if stockInfo.ID == 0 {
// db.Dao.Model(&models.StockInfoUS{}).Create(stockInfo)
// } else {
// db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stock.Code).Updates(stockInfo)
// }
//}
}
func (a *App) NewsPush(news *[]models.Telegraph) {
@@ -1337,7 +1357,8 @@ func (a *App) GetTelegraphList(source string) *[]*models.Telegraph {
}
func (a *App) ReFleshTelegraphList(source string) *[]*models.Telegraph {
data.NewMarketNewsApi().GetNewTelegraph(30)
//data.NewMarketNewsApi().GetNewTelegraph(30)
data.NewMarketNewsApi().TelegraphList(30)
data.NewMarketNewsApi().GetSinaNews(30)
telegraphs := data.NewMarketNewsApi().GetTelegraphList(source)
return telegraphs

View File

@@ -1,10 +1,12 @@
package main
import (
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/agent"
"go-stock/backend/data"
"go-stock/backend/models"
"strings"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// @Author spark
@@ -26,48 +28,66 @@ func (a *App) StockNotice(stockCode string) []any {
func (a *App) IndustryResearchReport(industryCode string) []any {
return data.NewMarketNewsApi().IndustryResearchReport(industryCode, 7)
}
func (a App) EMDictCode(code string) []any {
func (a *App) EMDictCode(code string) []any {
return data.NewMarketNewsApi().EMDictCode(code, a.cache)
}
func (a App) AnalyzeSentiment(text string) data.SentimentResult {
func (a *App) AnalyzeSentiment(text string) data.SentimentResult {
return data.AnalyzeSentiment(text)
}
func (a App) HotStock(marketType string) *[]models.HotItem {
func (a *App) HotStock(marketType string) *[]models.HotItem {
return data.NewMarketNewsApi().XUEQIUHotStock(100, marketType)
}
func (a App) HotEvent(size int) *[]models.HotEvent {
func (a *App) HotEvent(size int) *[]models.HotEvent {
if size <= 0 {
size = 10
}
return data.NewMarketNewsApi().HotEvent(size)
}
func (a App) HotTopic(size int) []any {
func (a *App) HotTopic(size int) []any {
if size <= 0 {
size = 10
}
return data.NewMarketNewsApi().HotTopic(size)
}
func (a App) InvestCalendarTimeLine(yearMonth string) []any {
func (a *App) InvestCalendarTimeLine(yearMonth string) []any {
return data.NewMarketNewsApi().InvestCalendar(yearMonth)
}
func (a App) ClsCalendar() []any {
func (a *App) ClsCalendar() []any {
return data.NewMarketNewsApi().ClsCalendar()
}
func (a App) SearchStock(words string) map[string]any {
func (a *App) SearchStock(words string) map[string]any {
return data.NewSearchStockApi(words).SearchStock(5000)
}
func (a App) GetHotStrategy() map[string]any {
func (a *App) GetHotStrategy() map[string]any {
return data.NewSearchStockApi("").HotStrategy()
}
func (a App) ChatWithAgent(question string, aiConfigId int, sysPromptId *int) {
func (a *App) ChatWithAgent(question string, aiConfigId int, sysPromptId *int) {
ch := agent.NewStockAiAgentApi().Chat(question, aiConfigId, sysPromptId)
for msg := range ch {
runtime.EventsEmit(a.ctx, "agent-message", msg)
}
}
func (a *App) AnalyzeSentimentWithFreqWeight(text string) map[string]any {
if text == "" {
telegraphs := data.NewMarketNewsApi().GetNews24HoursList("财联社电报", 1000)
messageText := strings.Builder{}
for _, telegraph := range *telegraphs {
messageText.WriteString(telegraph.Content + "\n")
}
text = messageText.String()
}
result, frequencies := data.AnalyzeSentimentWithFreqWeight(text)
// 过滤标点符号和分隔符
cleanFrequencies := data.FilterAndSortWords(frequencies)
return map[string]any{
"result": result,
"frequencies": cleanFrequencies,
}
}

View File

@@ -4,11 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
"go-stock/backend/data"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/random"
"go-stock/backend/data"
)
// @Author spark
@@ -55,7 +56,7 @@ func (c ChoiceStockByIndicators) InvokableRun(ctx context.Context, argumentsInJS
}
content := "无符合条件的数据"
words := parms["words"].(string)
res := data.NewSearchStockApi(words).SearchStock(random.RandInt(5, 10))
res := data.NewSearchStockApi(words).SearchStock(random.RandInt(5, 20))
if convertor.ToString(res["code"]) == "100" {
resData := res["data"].(map[string]any)
result := resData["result"].(map[string]any)

View File

@@ -0,0 +1 @@
Some dict/zh data is from [github.com/fxsjy/jieba](https://github.com/fxsjy/jieba)

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1 @@
dict.txt 通过内部工具生成, Copyright 2017 ego authors. 商用和拷贝请注明来源和版权

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
,
.
?
!
"
@
 
~
*
<
>
/
\
|
-
_
+
=
&
^
%
#
`
;
$
︿
哎呀
哎哟
俺们
按照
吧哒
罢了
本着
比方
比如
鄙人
彼此
别的
别说

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,73 @@ func NewMarketNewsApi() *MarketNewsApi {
return &MarketNewsApi{}
}
func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
//https://www.cls.cn/nodeapi/telegraphList
url := "https://www.cls.cn/nodeapi/telegraphList"
res := map[string]any{}
_, _ = resty.New().SetTimeout(time.Duration(crawlTimeOut)*time.Second).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").
SetResult(&res).
Get(url)
var telegraphs []models.Telegraph
if v, _ := convertor.ToInt(res["error"]); v == 0 {
if res["data"] == nil {
return m.GetNewTelegraph(30)
}
data := res["data"].(map[string]any)
rollData := data["roll_data"].([]any)
for _, v := range rollData {
news := v.(map[string]any)
ctime, _ := convertor.ToInt(news["ctime"])
dataTime := time.Unix(ctime, 0)
logger.SugaredLogger.Debugf("dataTime: %s", dataTime)
telegraph := models.Telegraph{
Content: news["content"].(string),
Time: dataTime.Format("15:04:05"),
DataTime: &dataTime,
Url: news["shareurl"].(string),
Source: "财联社电报",
IsRed: GetLevel(news["level"].(string)),
SentimentResult: AnalyzeSentiment(news["content"].(string)).Description,
}
cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and content=?", telegraph.Time, telegraph.Content).Count(&cnt)
if cnt > 0 {
continue
}
telegraphs = append(telegraphs, telegraph)
db.Dao.Model(&models.Telegraph{}).Create(&telegraph)
logger.SugaredLogger.Debugf("telegraph: %+v", &telegraph)
if news["subjects"] == nil {
continue
}
subjects := news["subjects"].([]any)
for _, subject := range subjects {
name := subject.(map[string]any)["subject_name"].(string)
tag := &models.Tags{
Name: name,
Type: "subject",
}
db.Dao.Model(tag).Where("name=? and type=?", name, "subject").FirstOrCreate(&tag)
db.Dao.Model(models.TelegraphTags{}).Where("telegraph_id=? and tag_id=?", telegraph.ID, tag.ID).FirstOrCreate(&models.TelegraphTags{
TelegraphId: telegraph.ID,
TagId: tag.ID,
})
}
}
//db.Dao.Model(&models.Telegraph{}).Create(&telegraphs)
//logger.SugaredLogger.Debugf("telegraphs: %+v", &telegraphs)
}
return &telegraphs
}
func GetLevel(s string) bool {
return s > "C"
}
func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
url := "https://www.cls.cn/telegraph"
response, _ := resty.New().SetTimeout(time.Duration(crawlTimeOut)*time.Second).R().
@@ -77,7 +144,7 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
if telegraph.Content != "" {
telegraph.SentimentResult = AnalyzeSentiment(telegraph.Content).Description
cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt)
db.Dao.Model(telegraph).Where("time=? and content=?", telegraph.Time, telegraph.Content).Count(&cnt)
if cnt == 0 {
db.Dao.Create(&telegraph)
telegraphs = append(telegraphs, telegraph)
@@ -118,6 +185,7 @@ func (m MarketNewsApi) GetNewsList(source string, limit int) *[]*models.Telegrap
return news
}
func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegraph {
NewMarketNewsApi().TelegraphList(30)
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc,is_red desc").Limit(limit).Find(news)
@@ -959,3 +1027,33 @@ func (m MarketNewsApi) CailianpressWeb(searchWords string) *models.CailianpressW
return res
}
func (m MarketNewsApi) GetNews24HoursList(source string, limit int) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=? and created_at>?", source, time.Now().Add(-24*time.Hour)).Order("id desc,is_red desc").Limit(limit).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Where("created_at>?", time.Now().Add(-24*time.Hour)).Order("id desc,is_red desc").Limit(limit).Find(news)
}
// 内容去重
uniqueNews := make([]*models.Telegraph, 0)
seenContent := make(map[string]bool)
for _, item := range *news {
tags := &[]models.Tags{}
db.Dao.Model(&models.Tags{}).Where("id in ?", lo.Map(item.TelegraphTags, func(item models.TelegraphTags, index int) uint {
return item.TagId
})).Find(&tags)
tagNames := lo.Map(*tags, func(item models.Tags, index int) string {
return item.Name
})
item.SubjectTags = tagNames
//logger.SugaredLogger.Infof("tagNames %v SubjectTags%s", tagNames, item.SubjectTags)
// 使用内容作为去重键值,可以考虑只使用内容的前几个字符或哈希值
contentKey := strings.TrimSpace(item.Content)
if contentKey != "" && !seenContent[contentKey] {
seenContent[contentKey] = true
uniqueNews = append(uniqueNews, item)
}
}
return &uniqueNews
}

View File

@@ -10,6 +10,7 @@ import (
"testing"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/random"
"github.com/tidwall/gjson"
)
@@ -38,7 +39,6 @@ func TestGetIndustryRank(t *testing.T) {
res := NewMarketNewsApi().GetIndustryRank("0", 10)
for s, a := range res["data"].([]any) {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", s, a)
}
}
func TestGetIndustryMoneyRankSina(t *testing.T) {
@@ -234,3 +234,18 @@ func TestInteractiveAnswer(t *testing.T) {
logger.SugaredLogger.Debugf(md)
}
func TestGetNewsList2(t *testing.T) {
db.Init("../../data/stock.db")
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
messageText.WriteString("### " + telegraph.Content + "\n")
}
logger.SugaredLogger.Debugf("value: %s", messageText.String())
}
func TestTelegraphList(t *testing.T) {
db.Init("../../data/stock.db")
NewMarketNewsApi().TelegraphList(30)
}

View File

@@ -323,7 +323,6 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
})
}()
wg.Wait()
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
@@ -1155,7 +1154,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
content := "无符合条件的数据"
res := NewSearchStockApi(words).SearchStock(random.RandInt(5, 10))
res := NewSearchStockApi(words).SearchStock(random.RandInt(5, 20))
if convertor.ToString(res["code"]) == "100" {
resData := res["data"].(map[string]any)
result := resData["result"].(map[string]any)

View File

@@ -3,9 +3,10 @@ package data
import (
"encoding/json"
"fmt"
"github.com/go-resty/resty/v2"
"go-stock/backend/logger"
"time"
"github.com/go-resty/resty/v2"
)
// @Author spark
@@ -25,25 +26,25 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
SetHeader("Host", "np-tjxg-g.eastmoney.com").
SetHeader("Origin", "https://xuangu.eastmoney.com").
SetHeader("Referer", "https://xuangu.eastmoney.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0").
SetHeader("Content-Type", "application/json").
SetBody(fmt.Sprintf(`{
"keyWord": "%s",
"pageSize": %d,
"pageNo": 1,
"fingerprint": "e38b5faabf9378c8238e57219f0ebc9b",
"fingerprint": "02efa8944b1f90fbfe050e1e695a480d",
"gids": [],
"matchWord": "",
"timestamp": "1751113883290349",
"timestamp": "%d",
"shareToGuba": false,
"requestId": "8xTWgCDAjvQ5lmvz5mDA3Ydk2AE4yoiJ1751113883290",
"requestId": "RMd3Y76AJI98axPvdhdbKvbBDVwLlUK61761559950168",
"needCorrect": true,
"removedConditionIdList": [],
"xcId": "xc0af28549ab330013ed",
"xcId": "xc0d61279aad33008260",
"ownSelectAll": false,
"dxInfo": [],
"extraCondition": ""
}`, s.words, pageSize)).Post(url)
}`, s.words, pageSize, time.Now().Unix())).Post(url)
if err != nil {
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
return map[string]any{}

View File

@@ -2,16 +2,18 @@ package data
import (
"encoding/json"
"github.com/duke-git/lancet/v2/convertor"
"go-stock/backend/db"
"go-stock/backend/logger"
"testing"
"github.com/duke-git/lancet/v2/convertor"
)
func TestSearchStock(t *testing.T) {
db.Init("../../data/stock.db")
res := NewSearchStockApi("算力股;净利润连续3年增长").SearchStock(10)
res := NewSearchStockApi("量比大于2基本面优秀2025年三季报已披露主力连续3日净流入非创业板非科创板非ST").SearchStock(20)
logger.SugaredLogger.Infof("res:%+v", res)
data := res["data"].(map[string]any)
result := data["result"].(map[string]any)
dataList := result["dataList"].([]any)

View File

@@ -3,11 +3,12 @@ package data
import (
"encoding/json"
"errors"
"github.com/samber/lo"
"go-stock/backend/db"
"go-stock/backend/logger"
"gorm.io/gorm"
"time"
"github.com/samber/lo"
"gorm.io/gorm"
)
type Settings struct {
@@ -35,6 +36,7 @@ type Settings struct {
SponsorCode string `json:"sponsorCode"`
HttpProxy string `json:"httpProxy"`
HttpProxyEnabled bool `json:"httpProxyEnabled"`
EnableAgent bool `json:"enableAgent"`
}
func (receiver Settings) TableName() string {
@@ -105,6 +107,7 @@ func UpdateConfig(s *SettingConfig) string {
"sponsor_code": s.SponsorCode,
"http_proxy": s.HttpProxy,
"http_proxy_enabled": s.HttpProxyEnabled,
"enable_agent": s.EnableAgent,
})
//更新AiConfig

View File

@@ -9,6 +9,14 @@ import (
"context"
"encoding/json"
"fmt"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"io"
"io/ioutil"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/chromedp/chromedp"
"github.com/duke-git/lancet/v2/convertor"
@@ -17,17 +25,10 @@ import (
"github.com/go-resty/resty/v2"
"github.com/robertkrimen/otto"
"github.com/samber/lo"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
"io"
"io/ioutil"
"strings"
"time"
)
const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s"
@@ -1744,6 +1745,11 @@ func (receiver StockDataApi) GetCommonKLineData(stockCode string, kLineType stri
return K
}
// GetStockHistoryMoneyData 获取股票历史资金流向数据
func (receiver StockDataApi) GetStockHistoryMoneyData() {
}
// JSONToMarkdownTable 将JSON数据转换为Markdown表格
func JSONToMarkdownTable(jsonData []byte) (string, error) {
var data []map[string]interface{}

View File

@@ -265,3 +265,20 @@ func TestStockDataApi_GetIndexBasic(t *testing.T) {
stockDataApi := NewStockDataApi()
stockDataApi.GetIndexBasic()
}
func TestName(t *testing.T) {
db.Init("../../data/stock.db")
stockBasics := &[]StockBasic{}
resty.New().R().
SetHeader("user", "go-stock").
SetResult(stockBasics).
Get("http://8.134.249.145:18080/go-stock/stock_basic.json")
db.Dao.Unscoped().Model(&StockBasic{}).Where("1=1").Delete(&StockBasic{})
err := db.Dao.CreateInBatches(stockBasics, 400).Error
if err != nil {
t.Log(err.Error())
}
}

View File

@@ -2,11 +2,19 @@ package data
import (
"bufio"
_ "embed"
"fmt"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"os"
"regexp"
"sort"
"strings"
"unicode"
"github.com/duke-git/lancet/v2/fileutil"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-ego/gse"
)
@@ -16,7 +24,7 @@ var (
// 正面金融词汇及其权重
positiveFinanceWords = map[string]float64{
"上涨": 2.0, "涨停": 3.0, "牛市": 3.0, "反弹": 2.0, "新高": 2.5,
"涨": 1.0, "上涨": 2.0, "涨停": 3.0, "牛市": 3.0, "反弹": 2.0, "新高": 2.5,
"利好": 2.5, "增持": 2.0, "买入": 2.0, "推荐": 1.5, "看多": 2.0,
"盈利": 2.0, "增长": 2.0, "超预期": 2.5, "强劲": 1.5, "回升": 1.5,
"复苏": 2.0, "突破": 2.0, "创新高": 3.0, "回暖": 1.5, "上扬": 1.5,
@@ -27,13 +35,13 @@ var (
// 负面金融词汇及其权重
negativeFinanceWords = map[string]float64{
"下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
"跌": 1.0, "下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
"利空": 2.5, "减持": 2.0, "卖出": 2.0, "看空": 2.0, "亏损": 2.5,
"下滑": 2.0, "萎缩": 2.0, "不及预期": 2.5, "疲软": 1.5, "恶化": 2.0,
"衰退": 2.0, "跌破": 2.0, "创新低": 3.0, "走弱": 1.5, "下挫": 1.5,
"利空消息": 3.0, "收益下降": 2.5, "利润下滑": 2.5, "业绩不佳": 2.5,
"垃圾股": 2.0, "风险股": 2.0, "弱势": 1.5, "走低": 1.5, "缩量": 2.5,
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0,
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0, "跌超": 2.5, "跌逾": 2.5,
}
// 否定词,用于反转情感极性
@@ -45,7 +53,7 @@ var (
degreeWords = map[string]float64{
"非常": 1.8, "极其": 2.2, "太": 1.8, "很": 1.5,
"比较": 0.8, "稍微": 0.6, "有点": 0.7, "显著": 1.5,
"大幅": 1.8, "急剧": 2.0, "轻微": 0.6, "小幅": 0.7,
"大幅": 1.8, "急剧": 2.0, "轻微": 0.6, "小幅": 0.7, "逾": 1.8,
}
// 转折词,用于识别情感转折
@@ -54,12 +62,206 @@ var (
}
)
func init() {
// 加载默认词典
err := seg.LoadDict()
//go:embed data/dict/base.txt
var baseDict string
//go:embed data/dict/zh/s_1.txt
var zhDict string
func InitAnalyzeSentiment() {
logger.SugaredLogger.Infof("初始化词典库路径:%s", fileutil.CurrentPath())
//加载默认词典
err := seg.LoadDictEmbed(zhDict)
err = seg.LoadDictEmbed(baseDict)
if err != nil {
logger.SugaredLogger.Error(err.Error())
} else {
logger.SugaredLogger.Info("加载默认词典成功")
}
//加载用户自定义词典 先判断用户词典是否存在
if !fileutil.IsExist(fileutil.CurrentPath() + "/data/dict/user.txt") {
err = seg.LoadDictEmbed(fileutil.CurrentPath() + "/data/dict/user.txt")
if err != nil {
logger.SugaredLogger.Error(err.Error())
} else {
logger.SugaredLogger.Info("加载用户词典成功")
}
}
stocks := &[]StockBasic{}
db.Dao.Model(&StockBasic{}).Find(stocks)
for _, stock := range *stocks {
if strutil.Trim(stock.Name) == "" {
continue
}
err := seg.AddToken(stock.Name, 188888, "n")
if strutil.Trim(stock.BKName) != "" {
err = seg.AddToken(stock.BKName, 188888, "n")
}
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
}
}
stockhks := &[]models.StockInfoHK{}
db.Dao.Model(&models.StockInfoHK{}).Find(stockhks)
for _, stock := range *stockhks {
if strutil.Trim(stock.Name) == "" {
continue
}
err := seg.AddToken(stock.Name, 188888, "n")
if strutil.Trim(stock.BKName) != "" {
err = seg.AddToken(stock.BKName, 188888, "n")
}
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
}
}
//stockus := &[]models.StockInfoUS{}
//db.Dao.Model(&models.StockInfoUS{}).Where("trim(name) != ?", "").Find(stockus)
//for _, stock := range *stockus {
// err := seg.AddToken(stock.Name, 500)
// if err != nil {
// logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
// }
//}
tags := &[]models.Tags{}
db.Dao.Model(&models.Tags{}).Find(tags)
for _, tag := range *tags {
err := seg.AddToken(tag.Name, 188888, "n")
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", tag.Name, err.Error())
}
}
logger.SugaredLogger.Info("加载词典成功")
}
// WordFreqWithWeight 词频统计结果,包含权重信息
type WordFreqWithWeight struct {
Word string
Frequency int
Weight float64
}
// getWordWeight 获取词汇权重
func getWordWeight(word string) float64 {
// 从分词器获取词汇权重
freq, pos, _ := seg.Find(word)
if pos == "n" {
return freq
}
return 0
}
// SortByWeightAndFrequency 按权重和频次排序词频结果
func SortByWeightAndFrequency(frequencies map[string]WordFreqWithWeight) []WordFreqWithWeight {
// 将map转换为slice以便排序
freqSlice := make([]WordFreqWithWeight, 0, len(frequencies))
for _, freq := range frequencies {
freqSlice = append(freqSlice, freq)
}
// 按权重降序排列,如果权重相同则按频次降序排列
sort.Slice(freqSlice, func(i, j int) bool {
if freqSlice[i].Weight != freqSlice[j].Weight {
return freqSlice[i].Weight > freqSlice[j].Weight // 权重高的排前面
}
return freqSlice[i].Frequency > freqSlice[j].Frequency // 权重相同时频次高的排前面
})
return freqSlice
}
// FilterAndSortWords 过滤标点符号并按权重频次排序
func FilterAndSortWords(frequencies map[string]WordFreqWithWeight) []WordFreqWithWeight {
// 先过滤标点符号和分隔符
cleanFrequencies := FilterPunctuationAndSeparators(frequencies)
// 再按权重和频次排序
sortedFrequencies := SortByWeightAndFrequency(cleanFrequencies)
return sortedFrequencies
}
func FilterPunctuationAndSeparators(frequencies map[string]WordFreqWithWeight) map[string]WordFreqWithWeight {
filteredWords := make(map[string]WordFreqWithWeight)
for word, freqInfo := range frequencies {
// 过滤纯标点符号和分隔符
if !isPunctuationOrSeparator(word) {
filteredWords[word] = freqInfo
}
}
return filteredWords
}
// isPunctuationOrSeparator 判断是否为标点符号或分隔符
func isPunctuationOrSeparator(word string) bool {
// 空字符串
if strings.TrimSpace(word) == "" {
return true
}
// 检查是否全部由标点符号组成
for _, r := range word {
if !unicode.IsPunct(r) && !unicode.IsSymbol(r) && !unicode.IsSpace(r) {
return false
}
}
return true
}
// FilterWithRegex 使用正则表达式过滤标点和特殊字符
func FilterWithRegex(frequencies map[string]WordFreqWithWeight) map[string]WordFreqWithWeight {
filteredWords := make(map[string]WordFreqWithWeight)
// 匹配标点符号、特殊字符的正则表达式
punctuationRegex := regexp.MustCompile(`^[[:punct:][:space:]]+$`)
for word, freqInfo := range frequencies {
// 过滤纯标点符号
if !punctuationRegex.MatchString(word) && strings.TrimSpace(word) != "" {
filteredWords[word] = freqInfo
}
}
return filteredWords
}
// countWordFrequencyWithWeight 统计词频并包含权重信息
func countWordFrequencyWithWeight(text string) map[string]WordFreqWithWeight {
words := splitWords(text)
freqMap := make(map[string]WordFreqWithWeight)
// 统计词频
wordCount := make(map[string]int)
for _, word := range words {
wordCount[word]++
}
// 构建包含权重的结果
for word, frequency := range wordCount {
weight := getWordWeight(word)
if weight > 100 {
freqMap[word] = WordFreqWithWeight{
Word: word,
Frequency: frequency,
Weight: weight,
}
}
}
return freqMap
}
// AnalyzeSentimentWithFreqWeight 带权重词频统计的情感分析
func AnalyzeSentimentWithFreqWeight(text string) (SentimentResult, map[string]WordFreqWithWeight) {
// 原有情感分析逻辑
result := AnalyzeSentiment(text)
// 带权重的词频统计
frequencies := countWordFrequencyWithWeight(text)
return result, frequencies
}
// SentimentResult 情感分析结果类型

View File

@@ -2,6 +2,8 @@ package data
import (
"fmt"
"go-stock/backend/db"
"go-stock/backend/logger"
"strings"
"testing"
)
@@ -12,25 +14,33 @@ import (
//-----------------------------------------------------------------------------------
func TestAnalyzeSentiment(t *testing.T) {
// 分析情感
text := " 【调查韩国近两成中小学生过度使用智能手机或互联网】财联社6月19日电韩国女性家族部18日公布的一项年度调查结果显示接受调查的韩国中小学生中共计约17.3%、即超过21万人使用智能手机或互联网的程度达到了“危险水平”这意味着他们因过度依赖智能手机或互联网而需要关注或干预这一比例引人担忧。 (新华社)\n"
text = "消息人士称联合利华Unilever正在为Graze零食品牌寻找买家。\n"
text = "【韩国未来5年将投入51万亿韩元发展文化产业】 据韩联社韩国文化体育观光部文体部今后5年将投入51万亿韩元约合人民币2667亿元预算落实总统李在明在竞选时期提出的“将韩国打造成全球五大文化强国之一”的承诺。\n"
//text = "【油气股持续拉升 国际实业午后涨停】财联社6月19日电油气股午后持续拉升国际实业、宝莫股份午后涨停准油股份、山东墨龙。茂化实华此前涨停通源石油、海默科技、贝肯能源、中曼石油、科力股份等多股涨超5%。\n"
//text = " 【三大指数均跌逾1% 下跌个股近4800只】财联社6月19日电指数持续走弱沪指下挫跌逾1.00%深成指跌1.25%创业板指跌1.39%。核聚变、风电、军工、食品消费等板块指数跌幅居前沪深京三市下跌个股近4800只。\n"
text = "【银行理财首单网下打新落地】财联社6月20日电记者从多渠道获悉光大理财以申报价格17元参与信通电子网下打新并成功入围有效报价成为行业内首家参与网下打新的银行理财公司。光大理财工作人员向证券时报记者表示本次光大理财是以其管理的混合类产品“阳光橙增盈绝对收益策略”参与了此次网下打新该产品为光大理财“固收+”银行理财产品。资料显示信通电子成立于1996年核心产品包括输电线路智能巡检系统、变电站智能辅控系统、移动智能终端及其他产品。根据其招股说明书信通电子2023、2024年营业收入分别较上年增长19.08%和7.97%净利润分别较上年增长5.6%和15.11%。 (证券时报)"
text = " 【以军称拦截数枚伊朗导弹】财联社6月20日电据央视新闻报道以军在贝尔谢巴及周边区域拦截了数枚伊朗导弹但仍有导弹或拦截残骸落地。以色列国防军发文表示搜救队伍正在一处“空中物体落地”的所在区域开展工作公众目前可以离开避难场所。伊朗方面对上述说法暂无回应。"
db.Init("../../data/stock.db")
InitAnalyzeSentiment()
messageText := strings.Builder{}
//news := NewMarketNewsApi().GetNewsList2("", random.RandInt(500, 1000))
//for _, telegraph := range *news {
// messageText.WriteString(telegraph.Content + "\n")
//}
text := messageText.String()
text = " 【周六你需要知道的隔夜全球要闻:美联储鸽声重振 美股走势回稳】 1、纽约联储行长威廉姆斯表示随着劳动力市场走软美联储近期内仍有再次降息的空间。 2、美联储理事斯蒂芬·米兰表示自上次联邦公开市场委员会FOMC会议以来的经济数据应“促使人们偏向鸽派立场”。 3、波士顿联邦储备银行行长柯林斯表示由于通胀可能在一段时间内保持高位维持利率不变“目前合适”。 4、据CME“美联储观察”截至北京时间11月22日6时30分美联储12月降息25个基点的概率为69.4%维持利率不变的概率为30.6%。 5、美国劳工统计局表示11月CPI报告将于12月18日发布同时取消了10月CPI报告发布表示无法追溯采集政府停摆期间未能收集的部分数据。 6、俄罗斯总统普京表示已收到美提出解决俄乌冲突的计划俄罗斯愿意进行和平谈判。美国总统特朗普表示他认为27日是乌克兰接受美国支持的和平计划的最后期限。 7、美联储高官鸽派言论提振市场情绪美股三大指数收盘集体上涨道琼斯指数涨1.08%标普500指数涨0.98%纳斯达克综合指数涨0.88%。甲骨文跌超5%英伟达跌超1%。纳指本周累计跌2.74%标普500指数累跌1.95%道指累跌1.91%。英伟达本周累跌5.9%。 8、热门中概股多数上涨纳斯达克中国金龙指数收涨1.23%。蔚来涨超3%哔哩哔哩、理想汽车涨超2%京东、小鹏汽车涨超1%。 9、国际油价下跌交易员评估乌克兰与俄罗斯可能达成和平协议的前景。WTI 1月期货下跌1.6%结算价报每桶58.06美元为过去五个交易日中第四次下跌。布伦特1月期货下跌1.3%结算价报每桶62.56美元。 10、美联储延长压力测试改进方案征询期为银行反馈提供更多时间。 11、由于美国人对个人财务状况的看法恶化美国消费者信心在11月跌至接近纪录最低水平密歇根大学数据显示11月消费者信心指数降至5110月为53.6。 12、日本央行政策委员会委员Kazuyuki Masu表示日本央行接近作出加息决定。 13、穆迪将意大利信用评级从BAA3上调至BAA2展望稳定。\n"
// 分析情感
words := splitWords(text)
fmt.Println(strings.Join(words, " "))
result := AnalyzeSentiment(text)
result, frequencies := AnalyzeSentimentWithFreqWeight(text)
// 过滤标点符号和分隔符
cleanFrequencies := FilterPunctuationAndSeparators(frequencies)
// 输出结果
fmt.Printf("情感分析结果: %s (得分: %.2f, 正面词:%d, 负面词:%d)\n",
logger.SugaredLogger.Infof("情感分析结果: %s (得分: %.2f, 正面词:%d, 负面词:%d)\n 词频统计结果: %v",
result.Description,
result.Score,
result.PositiveCount,
result.NegativeCount)
result.NegativeCount,
cleanFrequencies,
)
}

View File

@@ -232,6 +232,7 @@ type Prompt struct {
type Telegraph struct {
gorm.Model
Time string `json:"time"`
DataTime *time.Time `json:"dataTime"`
Content string `json:"content"`
SubjectTags []string `json:"subjects" gorm:"-:all"`
StocksTags []string `json:"stocks" gorm:"-:all"`
@@ -239,7 +240,7 @@ type Telegraph struct {
Url string `json:"url"`
Source string `json:"source"`
TelegraphTags []TelegraphTags `json:"tags" gorm:"-:migration;foreignKey:TelegraphId"`
SentimentResult string `json:"sentimentResult" gorm:"-:all"`
SentimentResult string `json:"sentimentResult"`
}
type TelegraphTags struct {
gorm.Model

View File

@@ -11,6 +11,7 @@ declare module 'vue' {
About: typeof import('./src/components/about.vue')['default']
AgentChat: typeof import('./src/components/agent-chat.vue')['default']
AgentChat_bk: typeof import('./src/components/agent-chat_bk.vue')['default']
AnalyzeMartket: typeof import('./src/components/AnalyzeMartket.vue')['default']
ClsCalendarTimeLine: typeof import('./src/components/ClsCalendarTimeLine.vue')['default']
EmbeddedUrl: typeof import('./src/components/EmbeddedUrl.vue')['default']
Fund: typeof import('./src/components/fund.vue')['default']

View File

@@ -6,7 +6,8 @@ import {
Quit,
WindowFullscreen,
WindowHide,
WindowUnfullscreen
WindowUnfullscreen,
WindowSetTitle
} from '../wailsjs/runtime'
import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue";
import {RouterLink, useRouter} from 'vue-router'
@@ -43,6 +44,7 @@ const loadingMsg = ref("加载数据中...")
const enableNews = ref(false)
const contentStyle = ref("")
const enableFund = ref(false)
const enableAgent = ref(false)
const enableDarkTheme = ref(null)
const content = ref('未经授权,禁止商业目的!\n\n数据来源于网络,仅供参考;投资有风险,入市需谨慎')
const isFullscreen = ref(false)
@@ -440,6 +442,7 @@ const menuOptions = ref([
{default: () => 'Ai智能体'}
),
key: 'agent',
show:enableAgent.value,
icon: renderIcon(Robot),
},
{
@@ -646,11 +649,15 @@ onBeforeMount(() => {
GetConfig().then((res) => {
//console.log(res)
enableFund.value = res.enableFund
enableAgent.value = res.enableAgent
menuOptions.value.filter((item) => {
if (item.key === 'fund') {
item.show = res.enableFund
}
if (item.key === 'agent') {
item.show = res.enableAgent
}
})
if (res.darkTheme) {
@@ -662,12 +669,14 @@ onBeforeMount(() => {
})
onMounted(() => {
WindowSetTitle("go-stockAI赋能股票分析✨ 未经授权,禁止商业目的! [数据来源于网络,仅供参考;投资有风险,入市需谨慎]")
contentStyle.value = "max-height: calc(92vh);overflow: hidden"
GetConfig().then((res) => {
if (res.enableNews) {
enableNews.value = true
}
enableFund.value = res.enableFund
enableAgent.value = res.enableAgent
const {notification } =createDiscreteApi(["notification"], {
configProviderProps: {
theme: enableDarkTheme.value ? darkTheme : lightTheme ,
@@ -714,7 +723,7 @@ onMounted(() => {
<n-modal-provider>
<n-dialog-provider>
<n-watermark
:content="content"
:content="''"
cross
selectable
:font-size="16"

View File

@@ -0,0 +1,11 @@
<script setup>
</script>
<template>
</template>
<style scoped>
</style>

View File

@@ -5,7 +5,9 @@ import 'md-editor-v3/lib/preview.css';
import {h, onBeforeUnmount, onMounted, ref} from 'vue';
import {CheckUpdate, GetVersionInfo,GetSponsorInfo,OpenURL} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn,Environment} from "../../wailsjs/runtime";
import {NAvatar, NButton, useNotification} from "naive-ui";
import {NAvatar, NButton, useNotification,NText} from "naive-ui";
import { addMonths, format ,parse} from 'date-fns';
import { zhCN } from 'date-fns/locale';
const updateLog = ref('');
const versionInfo = ref('');
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
@@ -16,6 +18,7 @@ const notify = useNotification()
const vipLevel=ref("");
const vipStartTime=ref("");
const vipEndTime=ref("");
const expired=ref(false)
onMounted(() => {
document.title = '关于软件';
@@ -31,6 +34,13 @@ onMounted(() => {
vipLevel.value = res.vipLevel;
vipStartTime.value = res.vipStartTime;
vipEndTime.value = res.vipEndTime;
//判断时间是否到期
if (res.vipLevel) {
if (res.vipEndTime < format(new Date(), 'yyyy-MM-dd HH:mm:ss')) {
notify.warning({content: 'VIP已到期'})
expired.value = true;
}
}
})
});
@@ -115,10 +125,10 @@ EventsOn("updateVersion",async (msg) => {
<n-gradient-text type="info" :size="50" >go-stock</n-gradient-text>
</n-badge>
<n-badge v-if="vipLevel" :value="versionInfo" :offset="[50,10]" type="success">
<n-gradient-text type="warning" :size="50" >go-stock</n-gradient-text><n-tag :bordered="false" size="small" type="warning">VIP{{vipLevel}}</n-tag>
<n-gradient-text :type="expired?'error':'warning'" :size="50" >go-stock</n-gradient-text><n-tag :bordered="false" size="small" type="warning">VIP{{vipLevel}}</n-tag>
</n-badge>
</h1>
<n-gradient-text type="warning" v-if="vipLevel" >vip到期时间{{vipEndTime}}</n-gradient-text>
<n-gradient-text :type="expired?'error':'warning'" v-if="vipLevel" >vip到期时间{{vipEndTime}}</n-gradient-text>
<n-button size="tiny" @click="CheckUpdate(1)" type="info" tertiary >检查更新</n-button>
<div style="justify-self: center;text-align: left" >
<p>自选股行情实时监控基于Wails和NaiveUI构建的AI赋能股票分析工具</p>

View File

@@ -1,5 +1,6 @@
<script setup>
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, ref} from 'vue'
import * as echarts from "echarts";
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted,onUnmounted, ref} from 'vue'
import {
GetAIResponseResult,
GetConfig,
@@ -12,7 +13,7 @@ import {
SaveAsMarkdown,
ShareAnalysis,
SummaryStockNews,
GetAiConfigs
GetAiConfigs, AnalyzeSentimentWithFreqWeight
} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from "../../wailsjs/runtime";
import NewsList from "./newsList.vue";
@@ -75,6 +76,8 @@ const indexInterval = ref(null)
const indexIndustryRank = ref(null)
const stockCode= ref('')
const enableTools= ref(true)
const treemapRef = ref(null);
let treemapchart =null;
function getIndex() {
GlobalStockIndexes().then((res) => {
@@ -120,7 +123,13 @@ onBeforeMount(() => {
indexIndustryRank.value = setInterval(() => {
industryRank()
}, 1000 * 10)
})
onMounted(() => {
Analyze() // 页面显示
})
onBeforeUnmount(() => {
EventsOff("changeMarketTab")
@@ -131,8 +140,12 @@ onBeforeUnmount(() => {
clearInterval(indexIndustryRank.value)
})
onUnmounted(() => {
});
EventsOn("changeMarketTab", async (msg) => {
//message.info(msg.name)
console.log(msg.name)
updateTab(msg.name)
})
@@ -142,6 +155,7 @@ EventsOn("newTelegraph", (data) => {
telegraphList.value.pop()
}
telegraphList.value.unshift(...data)
Analyze() // 页面显示
}
})
EventsOn("newSinaNews", (data) => {
@@ -150,6 +164,7 @@ EventsOn("newSinaNews", (data) => {
sinaNewsList.value.pop()
}
sinaNewsList.value.unshift(...data)
Analyze() // 页面显示
}
})
@@ -158,6 +173,32 @@ window.onresize = () => {
panelHeight.value = window.innerHeight - 240
}
function Analyze(){
console.log("treemapchart:",treemapchart)
console.log("treemapRef:",treemapRef.value)
treemapchart = echarts.init(treemapRef.value);
treemapchart.showLoading()
AnalyzeSentimentWithFreqWeight("").then((res) => {
let option = {
legend: {
show: false
},
series: [
{
type: 'treemap',
data: res['frequencies'].slice(0, 20).map(item => ({
name: item.Word,
value: item.Frequency*item.Weight,
}))
}
]
};
treemapchart.setOption(option);
treemapchart.hideLoading()
})
}
function getAreaName(code) {
switch (code) {
case "america":
@@ -232,6 +273,9 @@ function getAiSummary() {
function updateTab(name) {
summaryBTN.value = (name === "市场快讯");
nowTab.value = name
if (name === "市场快讯") {
Analyze()
}
}
EventsOn("summaryStockNews", async (msg) => {
@@ -320,14 +364,22 @@ function ReFlesh(source) {
<n-card>
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab" style="--wails-draggable:no-drag">
<n-tab-pane name="市场快讯" tab="市场快讯">
<n-grid :cols="2" :y-gap="0">
<n-grid :cols="1" :y-gap="0">
<n-gi>
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list>
<div ref="treemapRef" style="width: 100%;height: 300px;" ></div>
</n-gi>
<n-gi>
<news-list :newsList="sinaNewsList" :header-title="'新浪财经'" @update:message="ReFlesh"></news-list>
<n-grid :cols="2" :y-gap="0">
<n-gi>
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list>
</n-gi>
<n-gi>
<news-list :newsList="sinaNewsList" :header-title="'新浪财经'" @update:message="ReFlesh"></news-list>
</n-gi>
</n-grid>
</n-gi>
</n-grid>
</n-tab-pane>
<n-tab-pane name="全球股指" tab="全球股指">
<n-tabs type="segment" animated>

View File

@@ -49,6 +49,9 @@ const updateMessage = () => {
<n-text type="warning">查看原文</n-text>
</a>
</n-tag>
<n-tag v-if="item.sentimentResult" :bordered="false" :type="item.sentimentResult==='看涨'?'error':item.sentimentResult==='看跌'?'success':'info'" size="small">
{{ item.sentimentResult }}
</n-tag>
</n-space>
</n-list-item>
</n-list>

View File

@@ -45,6 +45,7 @@ const formValue = ref({
sponsorCode: "",
httpProxy:"",
httpProxyEnabled:false,
enableAgent: false,
})
// 添加一个新的AI配置到列表
@@ -103,6 +104,7 @@ onMounted(() => {
formValue.value.sponsorCode = res.sponsorCode
formValue.value.httpProxy=res.httpProxy;
formValue.value.httpProxyEnabled=res.httpProxyEnabled;
formValue.value.enableAgent = res.enableAgent;
})
@@ -142,6 +144,7 @@ function saveConfig() {
sponsorCode: formValue.value.sponsorCode,
httpProxy:formValue.value.httpProxy,
httpProxyEnabled:formValue.value.httpProxyEnabled,
enableAgent: formValue.value.enableAgent,
})
if (config.sponsorCode) {
@@ -231,6 +234,7 @@ function importConfig() {
formValue.value.sponsorCode = config.sponsorCode
formValue.value.httpProxy=config.httpProxy
formValue.value.httpProxyEnabled=config.httpProxyEnabled
formValue.value.enableAgent = config.enableAgent
};
reader.readAsText(file);
};
@@ -321,6 +325,10 @@ function deletePrompt(ID) {
<n-form-item-gi :span="3" label="指数基金:" path="enableFund">
<n-switch v-model:value="formValue.enableFund"/>
</n-form-item-gi>
<n-form-item-gi :span="3" label="AI智能体" path="enableAgent">
<n-switch v-model:value="formValue.enableAgent"/>
</n-form-item-gi>
<n-form-item-gi :span="11" label="赞助码:" path="sponsorCode">
<n-input-group>
<n-input :show-count="true" placeholder="赞助码" v-model:value="formValue.sponsorCode"/>

View File

@@ -9,18 +9,21 @@ import EmbeddedUrl from "./EmbeddedUrl.vue";
<n-tab-pane name="选股通" tab="选股通">
<embedded-url url="https://xuangutong.com.cn" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<!-- <n-tab-pane name="百度股市通" tab="百度股市通">-->
<!-- <embedded-url url="https://gushitong.baidu.com" :height="'calc(100vh - 252px)'"/>-->
<!-- </n-tab-pane>-->
<n-tab-pane name="百度股市通" tab="百度股市通">
<embedded-url url="https://gushitong.baidu.com" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="东财大盘星图" tab="东财大盘星图">
<embedded-url url="https://quote.eastmoney.com/stockhotmap/" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="TopHub" tab="TopHub(今日热榜)">
<embedded-url url="https://tophub.today/c/finance" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<!-- <n-tab-pane name="摸鱼" tab="摸鱼">-->
<!-- <embedded-url url="https://996.ninja/" :height="'calc(100vh - 252px)'"/>-->
<!-- </n-tab-pane>-->
<n-tab-pane name="摸鱼" tab="摸鱼">
<embedded-url url="https://996.ninja/" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="财联社-行情数据" tab="财联社-行情数据">
<embedded-url url="https://www.cls.cn/quotation" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="欢迎推荐更多有趣的财经网页" tab="欢迎推荐更多有趣的财经网页">

View File

@@ -14,6 +14,8 @@ export function AddStockGroup(arg1:number,arg2:string):Promise<string>;
export function AnalyzeSentiment(arg1:string):Promise<data.SentimentResult>;
export function AnalyzeSentimentWithFreqWeight(arg1:string):Promise<Record<string, any>>;
export function ChatWithAgent(arg1:string,arg2:number,arg3:any):Promise<void>;
export function CheckSponsorCode(arg1:string):Promise<Record<string, any>>;

View File

@@ -22,6 +22,10 @@ export function AnalyzeSentiment(arg1) {
return window['go']['main']['App']['AnalyzeSentiment'](arg1);
}
export function AnalyzeSentimentWithFreqWeight(arg1) {
return window['go']['main']['App']['AnalyzeSentimentWithFreqWeight'](arg1);
}
export function ChatWithAgent(arg1, arg2, arg3) {
return window['go']['main']['App']['ChatWithAgent'](arg1, arg2, arg3);
}

View File

@@ -393,6 +393,7 @@ export namespace data {
sponsorCode: string;
httpProxy: string;
httpProxyEnabled: boolean;
enableAgent: boolean;
aiConfigs: AIConfig[];
static createFrom(source: any = {}) {
@@ -428,6 +429,7 @@ export namespace data {
this.sponsorCode = source["sponsorCode"];
this.httpProxy = source["httpProxy"];
this.httpProxyEnabled = source["httpProxyEnabled"];
this.enableAgent = source["enableAgent"];
this.aiConfigs = this.convertValues(source["aiConfigs"], AIConfig);
}

View File

@@ -60,6 +60,7 @@ var BuildKey string
func main() {
checkDir("data")
db.Init("")
data.InitAnalyzeSentiment()
go AutoMigrate()
//db.Dao.Model(&data.Group{}).Where("id = ?", 0).FirstOrCreate(&data.Group{