Compare commits

...

32 Commits

Author SHA1 Message Date
ArvinLovegood
483ffa2244 feat(data): 优化金融分词词典与情感分析逻辑
- 更新基础词典文件,完善覆盖场景并调整部分词条权重
- 移除重复或冗余的美股及中概股名称条目
- 提升多个关键金融术语如“人工智能”、“半导体”等权重至350
- 新增“冲高”、“打开涨停”等交易行为相关词汇并设定合理权重
- 完善“十五五规划”相关内容条目,并分类整理结构
- 在情感分析模块引入basefreq常量替代硬编码数值
- 调整股票名称和板块名称添加逻辑中的频率值计算方式
- 重构用户自定义词典加载流程,增强兼容性和健壮性
- 支持更灵活的用户词典格式(支持词、频、词性三元素)
- 修改词频结果结构体,新增Score字段用于综合评分
- 优化排序规则,依据频率与权重乘积进行降序排列
- 增加调试日志输出,便于追踪分析过程与结果
- 前端Treemap图表展示逻辑同步更新以适配新的评分标准
2025-11-22 17:17:55 +08:00
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
ArvinLovegood
173ce6f243 fix(backend):修复新闻列表排序逻辑
- 将新闻列表按ID降序排列,确保最新新闻优先显示
- 保持is_red字段的降序排序,确保重要新闻优先显示
- 修复了查询条件中缺少的排序字段逗号问题
2025-09-29 18:10:55 +08:00
ArvinLovegood
e7875e73d3 feat(backend):新增并使用GetNewsList2方法以支持标签功能
- 在MarketNewsApi中添加GetNewsList2方法,支持根据source和limit获取新闻列表
- GetNewsList2方法中预加载TelegraphTags并关联标签名称
- 修改openai_api.go中调用GetNewsList的地方为GetNewsList2- 调整获取新闻列表的参数,使用固定source和随机limit值
2025-09-29 17:34:31 +08:00
ArvinLovegood
ca4727db80 fix(app):更新股票研究报告工具描述
- 修改了GetStockResearchReport函数的描述信息
- 简化了描述文本,去除冗余的"机构的"前缀
- 保持了原有功能和参数结构不变
2025-09-27 19:12:51 +08:00
ArvinLovegood
84ffe7c5fd refactor(openai_api):移除行业板块相关工具调用逻辑
- 注释掉QueryBKDictInfo工具的调用实现
- 注释掉GetIndustryResearchReport工具的调用实现
- 移除对freecache包的依赖引用- 保留GetStockResearchReport工具的调用逻辑
- 简化工具调用处理流程
2025-09-27 18:55:28 +08:00
ArvinLovegood
da02d1bd1c feat(stock):更新股票研究报接口参数并完善行业研究描述
- 修改行业研究工具函数描述,增加调用前需查询行业代码的提示
- 更新股票研究报接口测试用例中的股票代码参数值
- 完善行业研究报相关功能的使用说明和参数校验逻辑- 优化研究报数据获取流程,提升接口稳定性与准确性
2025-09-27 16:59:05 +08:00
ArvinLovegood
bae2bf9c5c docs(readme): 更新AI智能选股功能描述
- 在功能说明中添加AI智能体功能的描述
- 保持其他功能状态和备注信息不变
2025-09-27 15:46:33 +08:00
ArvinLovegood
6568b5949a docs(readme): 更新AI智能选股功能状态
- 将AI智能选股功能状态从开发中更新为已完成
- 修改功能描述为"市场行情-》AI总结"
- 调整了功能备注信息的表述方式
2025-09-27 15:44:03 +08:00
ArvinLovegood
c4287f9b78 feat(ai):新增了机构/券商的研究报告AI工具函数
- 添加 QueryBKDictInfo 工具用于获取板块/行业字典信息
- 实现 GetIndustryResearchReport 工具用于获取行业研究报告
- 实现 GetStockResearchReport 工具用于获取股票研究报告
- 在数据库中新增 BKDict 模型并自动迁移
- 更新 MarketNewsApi 测试用例以支持新工具调用
- 在 OpenAI API 中集成新工具的调用逻辑与响应处理
2025-09-27 15:39:37 +08:00
ArvinLovegood
87441d8923 feat(ai):新增了机构/券商的研究报告AI工具函数
- 添加 QueryBKDictInfo 工具用于获取板块/行业字典信息
- 实现 GetIndustryResearchReport 工具用于获取行业研究报告
- 实现 GetStockResearchReport 工具用于获取股票研究报告
- 在数据库中新增 BKDict 模型并自动迁移
- 更新 MarketNewsApi 测试用例以支持新工具调用
- 在 OpenAI API 中集成新工具的调用逻辑与响应处理
2025-09-27 15:39:25 +08:00
ArvinLovegood
ebd166e72b fix(agent):调整代理最大步骤数并修复前端显示逻辑
- 减少代理工具调用的最大步骤数计算方式
- 启用并修复前端聊天组件中的推理内容显示
- 修复删除分组时传递正确参数类型
- 更新依赖项,移除旧版本的 chromedp 和 golang.org/x/sys
2025-09-27 10:59:45 +08:00
ArvinLovegood
494a60debe refactor(backend):注释掉获取股势通资讯的代码
- 在 openai_api.go 中注释掉了获取股势通资讯的相关代码
- 在 openai_api_test.go 中添加了对 SearchGuShiTongStockInfo 函数的测试用例
2025-09-16 15:14:38 +08:00
ArvinLovegood
b3e2565a02 build: 更新多个依赖至最新版本 2025-09-16 14:19:11 +08:00
44 changed files with 1747644 additions and 166 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)
@@ -62,7 +60,7 @@
| 功能说明 | 状态 | 备注 |
|-----------------|----|----------------------------------------------------------------------------------------------------------|
| 股票分析知识库 | 🚧 | 未来计划 |
| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) |
| Ai智能选股 | | Ai智能选股功能(市场行情-》AI总结/AI智能体功能) |
| ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) |
| 美股支持 | ✅ | 美股数据支持 |
| 港股支持 | ✅ | 港股数据支持 |

159
app.go
View File

@@ -131,6 +131,50 @@ func AddTools(tools []data.Tool) []data.Tool {
},
})
//tools = append(tools, data.Tool{
// Type: "function",
// Function: data.ToolFunction{
// Name: "QueryBKDictInfo",
// Description: "获取所有板块/行业名称或者代码(bkCode,bkName)",
// },
//})
//tools = append(tools, data.Tool{
// Type: "function",
// Function: data.ToolFunction{
// Name: "GetIndustryResearchReport",
// Description: "获取行业/板块研究报告,请先使用QueryBKDictInfo工具获取行业代码然后输入行业代码调用",
// Parameters: data.FunctionParameters{
// Type: "object",
// Properties: map[string]any{
// "bkCode": map[string]any{
// "type": "string",
// "description": "板块/行业代码",
// },
// },
// Required: []string{"bkCode"},
// },
// },
//})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "GetStockResearchReport",
Description: "获取股票的分析/研究报告",
Parameters: data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"stockCode": map[string]any{
"type": "string",
"description": "股票代码",
},
},
Required: []string{"stockCode"},
},
},
})
return tools
}
@@ -384,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)
}
@@ -520,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) {
@@ -1293,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

@@ -73,13 +73,14 @@ func GetStockAiAgent(ctx *context.Context, aiConfig data.AIConfig) *react.Agent
tools.GetFinancialReportTool(),
tools.GetQueryStockNewsTool(),
tools.GetIndustryResearchReportTool(),
tools.GetQueryBKDictTool(),
},
}
// 创建 agent
agent, err := react.NewAgent(*ctx, &react.AgentConfig{
ToolCallingModel: toolableChatModel,
ToolsConfig: aiTools,
MaxStep: len(aiTools.Tools)*3 + 2,
MaxStep: len(aiTools.Tools)*1 + 3,
MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
return input
},

View File

@@ -0,0 +1,34 @@
package tools
import (
"context"
"encoding/json"
"go-stock/backend/data"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/coocood/freecache"
)
// @Author spark
// @Date 2025/9/27 14:09
// @Desc
// -----------------------------------------------------------------------------------
type ToolQueryBKDict struct{}
func (t ToolQueryBKDict) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryBKDictInfo",
Desc: "获取所有板块/行业名称或者代码(bkCode,bkName)",
}, nil
}
func (t ToolQueryBKDict) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
resp := data.NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
bytes, err := json.Marshal(resp)
return string(bytes), err
}
func GetQueryBKDictTool() tool.InvokableTool {
return &ToolQueryBKDict{}
}

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)

View File

@@ -0,0 +1,982 @@
# 金融股票全场景分词字典(最终去重优化版)
# 格式:单词 权重 词性 | 权重280-350分核心术语优先匹配无重复词汇
# 覆盖:净买卖、股指、财务指标、交易操作、政策宏观、热点概念、机构媒体、美股中概股、十五五规划等全场景
# 一、净买卖与资金流向(核心交易表述)
净卖出 340 v
净买入 340 v
净卖出额 330 n
净买入额 330 n
净卖出量 330 n
净买入量 330 n
资金净流出 340 n
资金净流入 340 n
净额 330 n
买卖净额 330 n
资金净额 330 n
北向资金净买入 330 n
北向资金净卖出 330 n
南向资金净买入 320 n
南向资金净卖出 320 n
主力资金净买入 330 n
主力资金净卖出 330 n
散户资金净买入 320 n
散户资金净卖出 320 n
机构资金净买入 330 n
机构资金净卖出 330 n
游资净买入 320 n
游资净卖出 320 n
大单净买入 320 n
大单净卖出 320 n
中单净买入 320 n
中单净卖出 320 n
小单净买入 320 n
小单净卖出 320 n
净买入占比 320 n
净卖出占比 320 n
净买入率 320 n
净卖出率 320 n
连续净买入 320 v
连续净卖出 320 v
单日净买入 320 n
单日净卖出 320 n
累计净买入 320 n
累计净卖出 320 n
净买入创纪录 310 adj
净卖出创纪录 310 adj
净买入放量 310 v
净卖出放量 310 v
净买入缩量 310 v
净卖出缩量 310 v
净多 310 n
净空 310 n
净多头 310 n
净空头 310 n
净多头头寸 310 n
净空头头寸 310 n
跌超 310 n
跌逾 310 n
# 二、金融资讯与市场分析
金融资讯 350 n
市场快讯 340 n
财经新闻 340 n
政策解读 330 n
市场分析 330 n
行业研报 320 n
宏观经济 330 n
微观层面 310 n
基本面 320 n
技术面 320 n
资金面 320 n
政策面 320 n
市场情绪 320 n
风险偏好 310 n
流动性 320 n
估值修复 310 n
价值投资 310 n
趋势投资 310 n
波段操作 310 n
左侧交易 290 n
右侧交易 290 n
止损止盈 300 n
仓位管理 300 n
资产配置 310 n
分散投资 290 n
集中投资 290 n
风险控制 310 n
系统性风险 300 n
非系统性风险 290 n
黑天鹅事件 310 n
灰犀牛事件 300 n
熔断机制 300 n
市场监管 310 n
信息披露 310 n
内幕交易 300 n
操纵市场 300 n
# 三、全球主要股指(含中英文缩写)
# 中国市场
A股 350 n
港股 350 n
上证指数 350 n
深证成指 350 n
创业板指 340 n
科创板指 330 n
北证50 330 n
沪深300 350 n
沪深300指数 350 n
中证500 340 n
中证500指数 340 n
中证1000 330 n
中证1000指数 330 n
上证50 340 n
上证50指数 340 n
科创50 330 n
科创50指数 330 n
上证综指 350 n
富时中国A50指数 340 n
FTSE China A50 330 n
恒生指数 340 n
HSI 330 n
恒生科技指数 340 n
恒生国企指数 330 n
H股指数 330 n
# 美洲市场
道琼斯工业平均指数 350 n
DJIA 340 n
标普500指数 350 n
S&P 500 340 n
纳斯达克综合指数 340 n
纳斯达克100指数 340 n
Nasdaq 100 330 n
罗素2000指数 320 n
Russell 2000 310 n
标普400中型股指数 310 n
标普600小型股指数 310 n
纽约证交所综合指数 310 n
NYSE Composite 300 n
纳斯达克中国金龙指数 310 n
# 欧洲市场
德国DAX指数 330 n
DAX 30 320 n
法国CAC40指数 330 n
CAC 40 320 n
富时100指数 330 n
FTSE 100 320 n
欧元斯托克50指数 320 n
Euro Stoxx 50 310 n
英国富时250指数 310 n
FTSE 250 300 n
意大利富时MIB指数 310 n
FTSE MIB 300 n
西班牙IBEX 35指数 310 n
IBEX 35 300 n
# 亚太其他市场
日经225指数 330 n
Nikkei 225 320 n
日经500指数 310 n
韩国综合股价指数 320 n
韩国kospi指数 320 n
KOSPI 310 n
澳洲标普200指数 310 n
S&P/ASX 200 300 n
印度孟买敏感指数 310 n
Sensex 300 n
印度Nifty 50指数 310 n
Nifty 50 300 n
# 全球综合指数
MSCI指数 320 n
MSCI全球指数 330 n
MSCI World Index 320 n
MSCI新兴市场指数 330 n
MSCI Emerging Markets 320 n
富时罗素全球指数 320 n
FTSE Russell Global Index 310 n
摩根大通全球债券指数 310 n
全球股指 300 n
发达市场指数 300 n
新兴市场指数 300 n
金砖国家指数 300 n
G20国家指数 300 n
# 股指衍生工具
指数期货 320 n
股指期货 320 n
富时中国A50指数期货 320 n
沪深300股指期货 320 n
标普500股指期货 320 n
纳斯达克100股指期货 310 n
指数成分股 320 n
指数权重股 320 n
指数涨幅 320 n
指数跌幅 320 n
指数反弹 310 n
指数回调 310 n
指数创新高 310 v
指数创新低 310 v
指数估值 310 n
指数市盈率 310 n
# 四、A股龙头公司资讯高频
贵州茅台 310 n
宁德时代 350 n
比亚迪 340 n
隆基绿能 300 n
长江电力 290 n
中国平安 300 n
招商银行 300 n
五粮液 290 n
美的集团 290 n
格力电器 290 n
海康威视 290 n
迈瑞医疗 290 n
恒瑞医药 290 n
中芯国际 300 n
中兴通讯 290 n
东方财富 290 n
爱尔眼科 290 n
通威股份 290 n
药明康德 320 n
阳光电源 290 n
天齐锂业 290 n
赣锋锂业 290 n
中国中免 290 n
海螺水泥 280 n
万科A 280 n
保利发展 280 n
招商蛇口 280 n
上汽集团 280 n
宝钢股份 280 n
# 五、财务与估值核心指标
市盈率 350 n
PE 350 n
动态市盈率 340 n
静态市盈率 340 n
滚动市盈率 340 n
市净率 350 n
PB 350 n
市销率 330 n
PS 330 n
市现率 320 n
PCF 320 n
净资产收益率 350 n
ROE 350 n
总资产收益率 330 n
ROA 330 n
毛利率 340 n
净利率 340 n
销售净利率 330 n
资产负债率 340 n
营收 340 n
营业收入 340 n
净利润 350 n
归母净利润 340 n
扣非净利润 340 n
EPS 330 n
每股收益 330 n
现金流 340 n
经营活动现金流 330 n
自由现金流 330 n
营收增长率 330 n
净利润增长率 330 n
股息率 320 n
分红率 320 n
换手率 330 n
成交量 340 n
成交额 340 n
量比 320 n
振幅 320 n
# 六、政策与宏观经济
货币政策 330 n
财政政策 330 n
稳健货币政策 320 n
积极财政政策 320 n
宽松政策 320 n
紧缩政策 320 n
利率 330 n
基准利率 320 n
LPR 330 n
贷款市场报价利率 320 n
存款准备金率 320 n
MLF 320 n
中期借贷便利 310 n
逆回购 320 n
正回购 310 n
汇率 330 n
人民币汇率 330 n
美元汇率 320 n
通胀 320 n
CPI 330 n
PPI 330 n
GDP 330 n
国内生产总值 320 n
PMI 330 n
采购经理人指数 320 n
行业政策 320 n
产业政策 320 n
税收政策 310 n
补贴政策 310 n
关税 310 n
贸易政策 310 n
地缘政治 310 n
大宗商品 320 n
原油价格 310 n
黄金价格 310 n
有色金属价格 300 n
# 七、金融产品与机构
股票 320 n
基金 320 n
公募基金 310 n
私募基金 310 n
ETF 320 n
指数基金 310 n
混合型基金 300 n
股票型基金 310 n
债券型基金 300 n
货币基金 290 n
REITs 310 n
可转债 310 n
可交换债 300 n
期货 310 n
股指期货 310 n
国债期货 300 n
商品期货 300 n
期权 300 n
融资融券 310 n
两融余额 300 n
北向资金 320 n
南向资金 310 n
沪股通 310 n
深股通 310 n
陆股通 310 n
证券公司 310 n
券商 320 n
基金公司 300 n
保险公司 300 n
银行 310 n
监管机构 310 n
证监会 320 n
交易所 320 n
上交所 320 n
深交所 320 n
北交所 310 n
港交所 310 n
社保基金 310 n
养老金 300 n
QFII 300 n
RQFII 290 n
北向资金机构 300 n
# 八、热点概念与行业
AI 330 n
人工智能 350 n
算力 330 n
大数据 320 n
云计算 320 n
半导体 350 n
芯片 350 n
集成电路 340 n
新能源 350 n
光伏 340 n
锂电 320 n
储能 340 n
充电桩 310 n
新能源车 320 n
智能汽车 310 n
自动驾驶 330 n
军工 310 n
国防军工 300 n
医药 310 n
创新药 310 n
医疗器械 300 n
CXO 300 n
白酒 310 n
消费 320 n
可选消费 300 n
必选消费 300 n
食品饮料 310 n
家电 300 n
地产 300 n
房地产 300 n
基建 300 n
新基建 310 n
数字经济 350 n
数字货币 310 n
区块链 300 n
元宇宙 300 n
低空经济 340 n
人形机器人 330 n
工业互联网 330 n
物联网 300 n
5G 300 n
6G 340 n
# 九、交易操作与行情
上涨 310 v
下跌 310 v
涨停 310 v
跌停 310 v
反弹 300 v
反转 300 v
回调 300 v
横盘 290 v
震荡 290 v
跳水 300 v
拉升 300 v
砸盘 300 v
护盘 290 v
建仓 300 v
加仓 300 v
减仓 300 v
清仓 300 v
平仓 300 v
抄底 300 v
逃顶 300 v
追涨 290 v
杀跌 290 v
套牢 280 v
解套 280 v
净流入 300 n
净流出 300 n
主力资金 300 n
资金流入 290 v
资金流出 290 v
放量 290 v
缩量 290 v
高换手 290 n
低换手 280 n
高估值 290 n
低估值 290 n
超预期 300 v
不及预期 300 v
符合预期 290 v
利好 310 n
利空 310 n
政策利好 310 n
业绩利好 310 n
风险警示 300 n
涨停板 300 n
跌停板 300 n
一字涨停 290 n
一字跌停 290 n
打开涨停 320 v
打开跌停 320 v
集合竞价 290 n
连续竞价 280 n
开盘价 340 n
收盘价 340 n
最高价 330 n
最低价 330 n
均价 330 n
昨日收盘价 320 n
涨跌额 330 n
涨跌幅 340 n
涨幅 340 n
跌幅 340 n
涨停价 330 n
跌停价 330 n
熔断 330 n
临时停牌 320 n
复牌 320 v
停牌 320 n
量价齐升 320 n
量价背离 320 n
高开 320 n
低开 320 n
平开 320 n
高走 320 v
低走 320 v
震荡上行 320 v
震荡下行 320 v
# 十、委托交易与规则
限价委托 340 n
市价委托 340 n
止损委托 330 n
止盈委托 330 n
预埋单 320 n
条件单 330 n
触发委托 320 n
追涨委托 320 n
抄底委托 320 n
挂单 330 n
撤单 330 v
成交 340 v
未成交 320 adj
部分成交 320 adj
委托价 320 n
成交价 320 n
委托量 320 n
买单 330 n
卖单 330 n
买入 340 v
卖出 340 v
做多 330 v
做空 330 v
开仓 330 v
满仓 330 v
空仓 330 v
半仓 320 v
轻仓 320 v
重仓 320 v
底仓 320 n
补仓 320 v
T+1交易 330 n
T+0交易 330 n
日内交易 320 n
短线交易 320 n
中线交易 320 n
长线交易 320 n
集合竞价交易 320 n
连续竞价交易 320 n
保证金 320 n
杠杆 320 n
融资 320 n
融券 320 n
融资买入 320 v
融券卖出 320 v
融资余额 320 n
融券余额 320 n
两融业务 320 n
信用账户 320 n
普通账户 320 n
资金账户 320 n
证券账户 320 n
持仓 330 n
持仓股 320 n
持仓数量 320 n
可用资金 320 n
可取资金 320 n
冻结资金 320 n
交易成本 320 n
手续费 320 n
佣金 320 n
印花税 320 n
过户费 320 n
交易规费 320 n
B股 310 n
H股 310 n
美股 310 n
创业板 320 n
科创板 320 n
主板 320 n
纳斯达克 350 n
纽交所 310 n
标普500 350 n
道琼斯 310 n
成分股 310 n
权重股 310 n
龙头股 310 n
中小盘股 310 n
大盘股 310 n
小盘股 310 n
ST股 320 n
*ST股 320 n
退市股 320 n
次新股 320 n
新股 320 n
打新 320 v
新股申购 320 n
中签 320 v
新股上市 320 n
限售股 310 n
解禁 310 v
股权登记日 310 n
除权除息日 310 n
派息 310 n
分红 310 n
送股 310 n
转增股 310 n
配股 310 n
除权 310 n
除息 310 n
填权 310 v
贴权 310 v
筹码分析 310 n
盘口分析 310 n
K线图 310 n
均线 310 n
日均线 310 n
周均线 310 n
月均线 310 n
MACD 310 n
KDJ 310 n
RSI 310 n
布林带 310 n
成交量均线 310 n
支撑位 310 n
压力位 310 n
阻力位 310 n
突破 310 v
跌破 310 v
站稳 310 v
回落 310 v
冲高 310 v
横盘整理 310 n
震荡整理 310 n
洗盘 310 n
吸筹 310 n
出货 310 n
建仓成本 310 n
持仓周期 310 n
交易频率 310 n
长线持有 310 v
高抛低吸 310 v
追涨杀跌 310 v
低吸高抛 310 v
顺势而为 310 n
逆向投资 310 n
交易软件 300 n
行情软件 300 n
交易终端 300 n
手机炒股 300 n
电脑炒股 300 n
网上交易 300 n
电话委托 290 n
营业部交易 290 n
交易系统 300 n
行情系统 300 n
Level-2行情 300 n
实时行情 300 n
延时行情 290 n
交易接口 300 n
量化交易 310 n
算法交易 300 n
程序化交易 300 n
自动交易 300 n
智能投顾 300 n
券商APP 300 n
交易佣金 300 n
开户 300 v
销户 290 v
转户 290 v
绑定银行卡 290 v
银证转账 300 n
银证通 290 n
第三方存管 290 n
赛道股 330 n
抱团股 310 n
妖股 310 n
庄股 310 n
# 十一、主要财经网站与机构
# 国内财经网站
东方财富网 350 n
同花顺财经 340 n
财新网 340 n
新浪财经 340 n
第一财经 330 n
金融界 330 n
华尔街见闻 330 n
每日经济新闻 330 n
证券时报网 330 n
财联社 330 n
和讯网 320 n
证券之星 320 n
中国证券报 330 n
上海证券报 330 n
证券日报 320 n
界面新闻 320 n
澎湃新闻财经 320 n
腾讯财经 320 n
网易财经 320 n
凤凰财经 320 n
# 国际财经媒体
彭博社 350 n
路透社 350 n
金融时报 340 n
华尔街日报 340 n
雅虎财经 320 n
CNBC 320 n
路透财经 330 n
彭博财经 330 n
英国金融时报 330 n
美国消费者新闻与商业频道 320 n
日经新闻 320 n
韩国经济新闻 310 n
# 国际金融机构
高盛集团 350 n
摩根士丹利 350 n
摩根大通 350 n
瑞银集团 340 n
汇丰银行 340 n
野村证券 330 n
贝莱德 350 n
桥水基金 340 n
黑石集团 340 n
橡树资本 330 n
花旗集团 330 n
美银美林 330 n
德意志银行 320 n
瑞士信贷 320 n
法国巴黎银行 320 n
三菱日联金融集团 310 n
# 国内外金融监管与交易机构
中国证监会 350 n
美联储 350 n
英国金融行为管理局 330 n
香港证监会 330 n
纽约证券交易所 350 n
香港交易所 340 n
伦敦证券交易所 340 n
芝商所集团 330 n
泛欧证券交易所 330 n
上海证券交易所 340 n
深圳证券交易所 340 n
北京证券交易所 330 n
中国人民银行 350 n
银保监会 340 n
国家金融监督管理总局 340 n
财政部 340 n
发改委 330 n
石油输出国组织 340 n
国际能源署 330 n
美国能源信息署 320 n
世界银行 330 n
国际货币基金组织 330 n
# 国内核心金融机构
中国工商银行 340 n
中国建设银行 340 n
中国农业银行 340 n
中国银行 340 n
交通银行 330 n
招商银行 330 n
兴业银行 320 n
浦发银行 320 n
中信证券 340 n
华泰证券 330 n
中金公司 330 n
中信建投 330 n
国泰君安 330 n
广发证券 320 n
东方证券 320 n
南方基金 330 n
易方达基金 330 n
华夏基金 330 n
嘉实基金 320 n
博时基金 320 n
# 十二、知名美股与中概股
# 知名美股
苹果 350 n
Apple 340 n
英伟达 350 n
Nvidia 340 n
特斯拉 350 n
Tesla 340 n
谷歌 340 n
Alphabet 330 n
亚马逊 340 n
Amazon 330 n
Meta Platforms 330 n
Meta 330 n
微软 350 n
Microsoft 340 n
甲骨文 320 n
Oracle 310 n
伯克希尔哈撒韦 320 n
Berkshire Hathaway 310 n
闪迪 300 n
SanDisk 290 n
高通 320 n
Qualcomm 310 n
英特尔 320 n
Intel 310 n
AMD 320 n
超威半导体 310 n
脸书 320 n
Facebook 310 n
推特 310 n
Twitter 300 n
Square 300 n
Block 290 n
PayPal 310 n
贝宝 300 n
星巴克 310 n
Starbucks 300 n
可口可乐 310 n
Coca-Cola 300 n
百事可乐 300 n
PepsiCo 290 n
沃尔玛 310 n
Walmart 300 n
家得宝 300 n
Home Depot 290 n
美国银行 310 n
Bank of America 300 n
# 知名中概股
阿里巴巴 350 n
Alibaba 340 n
拼多多 340 n
PDD 330 n
蔚来汽车 330 n
NIO 320 n
理想汽车 330 n
LI 320 n
腾讯音乐 320 n
TME 310 n
唯品会 310 n
VIPS 300 n
富途控股 310 n
FUTU 300 n
老虎证券 310 n
TIGR 300 n
万物新生 300 n
RERE 290 n
涂鸦智能 300 n
TUYA 290 n
百济神州 320 n
BeiGene 310 n
百度集团 340 n
Baidu 330 n
文远知行 300 n
WRD 290 n
贝壳 310 n
KE 300 n
新氧 300 n
SY 290 n
京东 340 n
JD.com 330 n
京东物流 320 n
JD Logistics 310 n
腾讯控股 350 n
Tencent Holdings 340 n
美团 340 n
Meituan 330 n
小米集团 340 n
Xiaomi Group 330 n
快手 330 n
Kuaishou 320 n
字节跳动 340 n
ByteDance 330 n
滴滴 330 n
DiDi 320 n
携程 320 n
Ctrip 310 n
去哪儿 310 n
Qunar 300 n
同程旅行 300 n
LY.com 290 n
途牛 300 n
Tuniu 290 n
哔哩哔哩 320 n
Bilibili 310 n
爱奇艺 320 n
iQiyi 310 n
优酷 310 n
Youku 300 n
芒果超媒 310 n
Mango TV 300 n
网易 330 n
NetEase 320 n
新浪 320 n
Sina 310 n
微博 320 n
Weibo 310 n
知乎 310 n
Zhihu 300 n
小红书 310 n
Xiaohongshu 300 n
商汤科技 310 n
SenseTime 300 n
旷视科技 300 n
Megvii 290 n
云从科技 300 n
Cloudwalk 290 n
依图科技 300 n
Yitu 290 n
药明生物 320 n
WuXi Biologics 310 n
康希诺 310 n
CanSino Biologics 300 n
智飞生物 310 n
Innovax 300 n
CATL 340 n
BYD 330 n
小鹏汽车 330 n
Xpeng 320 n
哪吒汽车 310 n
Nezha Auto 300 n
零跑汽车 310 n
Leapmotor 300 n
极氪 310 n
ZEEKR 300 n
岚图汽车 300 n
VOYAH 290 n
华为 350 n
Huawei 340 n
荣耀 330 n
Honor 320 n
OPPO 330 n
vivo 330 n
一加 310 n
OnePlus 300 n
真我 310 n
realme 300 n
传音控股 310 n
Transsion 300 n
# 十三、十五五规划重点领域
# 核心战略方向
新质生产力 350 n
科技自立自强 350 n
全要素生产率 340 n
高质量发展 340 n
中国式现代化 340 n
共同富裕 330 n
区域协调发展 330 n
城乡融合发展 330 n
国家安全体系 330 n
双循环 330 n
高水平对外开放 320 n
# 科技创新与数字经济
量子科技 340 n
脑机接口 340 n
具身智能 340 n
第六代移动通信 340 n
生成式AI 340 n
通用人工智能 330 n
工业母机 340 n
集成电路 340 n
高端算力 330 n
数据要素 340 n
数字基础设施 330 n
智能制造 340 n
服务型制造 320 n
绿色制造 320 n
# 能源与绿色转型
新型能源体系 340 n
氢能 340 n
核聚变能 330 n
可再生能源 330 n
风电 330 n
生物质能 320 n
碳排放双控 340 n
碳达峰 330 n
碳中和 330 n
绿色低碳 330 n
循环经济 320 n
节能环保 320 n
碳交易 320 n
# 高端制造与新兴产业
航空航天 340 n
新材料 340 n
生物制造 330 n
生物医药 330 n
基因编辑 320 n
合成生物学 320 n
智能网联汽车 330 n
机器人 330 n
海洋工程 320 n
核电 320 n
高端装备 330 n
# 乡村振兴与农业现代化
乡村振兴 340 n
农业机械化 330 n
智慧农业 330 n
现代农业 320 n
粮食安全 330 n
农产品深加工 320 n
农村电商 320 n
乡村旅游 310 n
# 对外开放与贸易升级
跨境电商 330 n
自贸试验区 320 n
海南自贸港 320 n
一带一路 330 n
服务贸易 320 n
数字贸易 320 n
跨境金融 320 n
外资准入 310 n
# 社会民生与公共服务
教育强国 320 n
健康中国 320 n
养老服务 310 n
医疗保障 310 n
保障性住房 310 n
公共卫生 320 n
文化强国 320 n
体育强国 310 n

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)
@@ -117,6 +184,28 @@ 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)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc,is_red desc").Limit(limit).Find(news)
}
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)
}
return news
}
func (m MarketNewsApi) GetTelegraphList(source string) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
@@ -938,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

@@ -4,11 +4,13 @@ import (
"encoding/json"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"strings"
"testing"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/random"
"github.com/tidwall/gjson"
)
@@ -37,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) {
@@ -72,9 +73,12 @@ func TestLongTiger(t *testing.T) {
func TestStockResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().StockResearchReport("600584.sh", 7)
resp := NewMarketNewsApi().StockResearchReport("688082", 7)
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
data := a.(map[string]any)
logger.SugaredLogger.Debugf("value: %s infoCode:%s", data["title"], data["infoCode"])
NewMarketNewsApi().GetIndustryReportInfo(data["infoCode"].(string))
}
}
@@ -108,7 +112,11 @@ func TestEMDictCode(t *testing.T) {
if err != nil {
return
}
dict := &[]models.BKDict{}
json.Unmarshal(bytes, dict)
logger.SugaredLogger.Debugf("value: %s", string(bytes))
md := util.MarkdownTableWithTitle("行业/板块代码", dict)
logger.SugaredLogger.Debugf(md)
}
@@ -226,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,8 +323,7 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
})
}()
wg.Wait()
news := NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(100, 500))
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
@@ -473,7 +472,7 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
wg.Wait()
news := NewMarketNewsApi().GetNewsList("", 100)
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
@@ -808,25 +807,25 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
return
}
messages := SearchGuShiTongStockInfo(stockCode, o.CrawlTimeOut)
if messages == nil || len(*messages) == 0 {
logger.SugaredLogger.Error("获取股势通资讯失败")
//ch <- "***❗获取股势通资讯失败,分析结果可能不准确***<hr>"
//go runtime.EventsEmit(o.ctx, "warnMsg", "❗获取股势通资讯失败,分析结果可能不准确")
return
}
var newsText strings.Builder
for _, message := range *messages {
newsText.WriteString(message + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": stock + "相关新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
//messages := SearchGuShiTongStockInfo(stockCode, o.CrawlTimeOut)
//if messages == nil || len(*messages) == 0 {
// logger.SugaredLogger.Error("获取股势通资讯失败")
// //ch <- "***❗获取股势通资讯失败,分析结果可能不准确***<hr>"
// //go runtime.EventsEmit(o.ctx, "warnMsg", "❗获取股势通资讯失败,分析结果可能不准确")
// return
//}
//var newsText strings.Builder
//for _, message := range *messages {
// newsText.WriteString(message + "\n")
//}
//msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": stock + "相关新闻资讯",
//})
//msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "content": newsText.String(),
//})
}()
go func() {
@@ -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)
@@ -1366,6 +1365,140 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
"tool_call_id": currentCallId,
})
}
//
//if funcName == "QueryBKDictInfo" {
// ch <- map[string]any{
// "code": 1,
// "question": question,
// "chatId": streamResponse.Id,
// "model": streamResponse.Model,
// "content": "\r\n```\r\n开始调用工具QueryBKDictInfo\n参数" + funcArguments + "\r\n```\r\n",
// "time": time.Now().Format(time.DateTime),
// }
// res := NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
// bytes, err := json.Marshal(res)
// if err != nil {
// return
// }
// dict := &[]models.BKDict{}
// json.Unmarshal(bytes, dict)
// md := util.MarkdownTableWithTitle("行业/板块代码", dict)
// logger.SugaredLogger.Infof("行业/板块代码=\n%s", md)
// messages = append(messages, map[string]interface{}{
// "role": "assistant",
// "content": currentAIContent.String(),
// "tool_calls": []map[string]any{
// {
// "id": currentCallId,
// "tool_call_id": currentCallId,
// "type": "function",
// "function": map[string]string{
// "name": funcName,
// "arguments": funcArguments,
// "parameters": funcArguments,
// },
// },
// },
// })
// messages = append(messages, map[string]interface{}{
// "role": "tool",
// "content": md,
// "tool_call_id": currentCallId,
// })
//}
//if funcName == "GetIndustryResearchReport" {
// bkCode := gjson.Get(funcArguments, "bkCode").String()
// ch <- map[string]any{
// "code": 1,
// "question": question,
// "chatId": streamResponse.Id,
// "model": streamResponse.Model,
// "content": "\r\n```\r\n开始调用工具GetIndustryResearchReport\n参数" + bkCode + "\r\n```\r\n",
// "time": time.Now().Format(time.DateTime),
// }
// bkCode = strutil.ReplaceWithMap(bkCode, map[string]string{
// "-": "",
// "_": "",
// "bk": "",
// "BK": "",
// "bk0": "",
// "BK0": "",
// })
//
// logger.SugaredLogger.Debugf("code:%s", bkCode)
// codeStr := convertor.ToString(bkCode)
// res := NewMarketNewsApi().IndustryResearchReport(codeStr, 7)
// md := strings.Builder{}
// for _, a := range res {
// d := a.(map[string]any)
// md.WriteString(NewMarketNewsApi().GetIndustryReportInfo(d["infoCode"].(string)))
// }
// logger.SugaredLogger.Infof("bkCode:%s IndustryResearchReport:\n %s", bkCode, md.String())
// messages = append(messages, map[string]interface{}{
// "role": "assistant",
// "content": currentAIContent.String(),
// "tool_calls": []map[string]any{
// {
// "id": currentCallId,
// "tool_call_id": currentCallId,
// "type": "function",
// "function": map[string]string{
// "name": funcName,
// "arguments": funcArguments,
// "parameters": funcArguments,
// },
// },
// },
// })
// messages = append(messages, map[string]interface{}{
// "role": "tool",
// "content": md.String(),
// "tool_call_id": currentCallId,
// })
//}
if funcName == "GetStockResearchReport" {
stockCode := gjson.Get(funcArguments, "stockCode").String()
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具GetStockResearchReport\n参数" + stockCode + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
res := NewMarketNewsApi().StockResearchReport(stockCode, 7)
md := strings.Builder{}
for _, a := range res {
logger.SugaredLogger.Debugf("value: %+v", a)
d := a.(map[string]any)
logger.SugaredLogger.Debugf("value: %s infoCode:%s", d["title"], d["infoCode"])
md.WriteString(NewMarketNewsApi().GetIndustryReportInfo(d["infoCode"].(string)))
}
logger.SugaredLogger.Infof("stockCode:%s StockResearchReport:\n %s", stockCode, md.String())
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
"tool_call_id": currentCallId,
"type": "function",
"function": map[string]string{
"name": funcName,
"arguments": funcArguments,
"parameters": funcArguments,
},
},
},
})
messages = append(messages, map[string]interface{}{
"role": "tool",
"content": md.String(),
"tool_call_id": currentCallId,
})
}
}
AskAiWithTools(o, err, messages, ch, question, tools)

View File

@@ -3,6 +3,7 @@ package data
import (
"context"
"go-stock/backend/db"
log "go-stock/backend/logger"
"testing"
)
@@ -52,9 +53,12 @@ func TestGetTopNewsList(t *testing.T) {
func TestSearchGuShiTongStockInfo(t *testing.T) {
db.Init("../../data/stock.db")
SearchGuShiTongStockInfo("hk01810", 60)
SearchGuShiTongStockInfo("sh600745", 60)
SearchGuShiTongStockInfo("gb_goog", 60)
//SearchGuShiTongStockInfo("hk01810", 60)
msgs := SearchGuShiTongStockInfo("sh600745", 60)
for _, msg := range *msgs {
log.SugaredLogger.Infof("%s", msg)
}
//SearchGuShiTongStockInfo("gb_goog", 60)
}

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,21 +2,32 @@ 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/convertor"
"github.com/duke-git/lancet/v2/fileutil"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-ego/gse"
)
const basefreq float64 = 100
// 金融情感词典,包含股票市场相关的专业词汇
var (
seg gse.Segmenter
// 正面金融词汇及其权重
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 +38,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 +56,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 +65,222 @@ 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(baseDict)
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, basefreq+500, "n")
if strutil.Trim(stock.BKName) != "" {
err = seg.AddToken(stock.BKName, basefreq+500, "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, basefreq+500, "n")
if strutil.Trim(stock.BKName) != "" {
err = seg.AddToken(stock.BKName, basefreq+500, "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, basefreq, "n")
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", tag.Name, err.Error())
}
}
//加载用户自定义词典 先判断用户词典是否存在
if fileutil.IsExist(fileutil.CurrentPath() + "/data/dict/user.txt") {
lines, err := fileutil.ReadFileByLine(fileutil.CurrentPath() + "/data/dict/user.txt")
if err != nil {
logger.SugaredLogger.Error(err.Error())
return
}
for _, line := range lines {
k := strutil.SplitAndTrim(line, " ")
switch len(k) {
case 1:
err = seg.AddToken(k[0], 100)
case 2:
freq, _ := convertor.ToFloat(k[1])
err = seg.AddToken(k[0], freq)
case 3:
freq, _ := convertor.ToFloat(k[1])
err = seg.AddToken(k[0], freq, k[2])
default:
logger.SugaredLogger.Errorf("用户词典格式错误:%s", line)
}
}
if err != nil {
logger.SugaredLogger.Error(err.Error())
} else {
logger.SugaredLogger.Error("加载用户词典成功")
}
}
}
// WordFreqWithWeight 词频统计结果,包含权重信息
type WordFreqWithWeight struct {
Word string
Frequency int
Weight float64
Score float64
}
// getWordWeight 获取词汇权重
func getWordWeight(word string) float64 {
// 从分词器获取词汇权重
freq, pos, ok := seg.Dictionary().Find([]byte(word))
if ok && pos == "n" {
return basefreq + 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 {
return freqSlice[i].Weight*float64(freqSlice[i].Frequency) > freqSlice[j].Weight*float64(freqSlice[j].Frequency)
})
logger.SugaredLogger.Infof("排序后的结果:%v", freqSlice)
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 > 0 {
freqMap[word] = WordFreqWithWeight{
Word: word,
Frequency: frequency,
Weight: weight,
Score: float64(frequency) * 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,32 @@ 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"
text = "财联社电英伟达周五冲高回落股价涨幅收于1%,市场普遍认为其走势疲软"
// 分析情感
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

@@ -1,9 +1,10 @@
package models
import (
"time"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
"time"
)
// @Author spark
@@ -231,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"`
@@ -238,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
@@ -687,3 +689,16 @@ type CailianpressWeb struct {
Author string `json:"author" md:"资讯发布者"`
} `json:"list"`
}
type BKDict struct {
gorm.Model `md:"-"`
BkCode string `json:"bkCode" md:"行业/板块代码"`
BkName string `json:"bkName" md:"行业/板块名称"`
FirstLetter string `json:"firstLetter" md:"first_letter"`
FubkCode string `json:"fubkCode" md:"fubk_code"`
PublishCode string `json:"publishCode" md:"publish_code"`
}
func (b BKDict) TableName() string {
return "bk_dict"
}

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

@@ -14,7 +14,7 @@
<template #content="{ item, index }">
<t-chat-reasoning v-if="item.role === 'assistant'" expand-icon-placement="right">
<t-chat-loading v-if="isStreamLoad" text="思考中..." />
<!-- <t-chat-content v-if="item.reasoning.length > 0" :content="item.reasoning" />-->
<t-chat-content v-if="item.reasoning.length > 0" :content="item.reasoning" />
</t-chat-reasoning>
<t-chat-content v-if="item.content.length > 0" :content="item.content" />
</template>
@@ -97,9 +97,9 @@ EventsOn("agent-message", (data) => {
if(data['role']==="assistant"){
loading.value = false;
const lastItem = chatList.value[0];
// if (data['reasoning_content']){
// lastItem.reasoning = data['reasoning_content'];
// }
if (data['reasoning_content']){
lastItem.reasoning += data['reasoning_content'];
}
if (data['content']){
lastItem.content +=data['content'];
}

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) => {
console.log(res)
let option = {
legend: {
show: false
},
series: [
{
type: 'treemap',
data: res['frequencies'].map(item => ({
name: item.Word,
value: item.Score,
}))
}
]
};
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

@@ -1853,8 +1853,8 @@ function updateTab(name) {
})
}
function delTab(name) {
let infos = groupList.value = groupList.value.filter(item => item.ID === Number(name))
function delTab(groupId) {
let infos = groupList.value = groupList.value.filter(item => item.ID === Number(groupId))
dialog.create({
title: '删除分组',
type: 'warning',
@@ -1862,7 +1862,7 @@ function delTab(name) {
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
RemoveGroup(name).then(result => {
RemoveGroup(Number(groupId)).then(result => {
message.info(result)
GetGroupList().then(result => {
groupList.value = result

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);
}

7
go.mod
View File

@@ -4,7 +4,7 @@ go 1.25.0
require (
github.com/PuerkitoBio/goquery v1.10.1
github.com/chromedp/chromedp v0.11.2
github.com/chromedp/chromedp v0.14.1
github.com/cloudwego/eino v0.4.1
github.com/cloudwego/eino-ext/components/model/ark v0.1.19
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250804092122-8845979a2228
@@ -26,7 +26,7 @@ require (
github.com/wailsapp/wails/v2 v2.10.1
go.uber.org/zap v1.27.0
golang.org/x/net v0.38.0
golang.org/x/sys v0.35.0
golang.org/x/sys v0.36.0
golang.org/x/text v0.26.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/gorm v1.25.12
@@ -41,7 +41,7 @@ require (
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // indirect
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250804062529-6e67726a4b3f // indirect
@@ -52,6 +52,7 @@ require (
github.com/evanphx/json-patch v0.5.2 // indirect
github.com/getkin/kin-openapi v0.118.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect

14
go.sum
View File

@@ -24,10 +24,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb h1:noKVm2SsG4v0Yd0lHNtFYc9EUxIVvrr4kJ6hM8wvIYU=
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM=
github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0=
github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d h1:ZtA1sedVbEW7EW80Iz2GR3Ye6PwbJAJXjv7D74xG6HU=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
github.com/chromedp/chromedp v0.14.1 h1:0uAbnxewy/Q+Bg7oafVePE/6EXEho9hnaC38f+TTENg=
github.com/chromedp/chromedp v0.14.1/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -76,6 +76,8 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJY
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -409,8 +411,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

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{
@@ -228,6 +229,7 @@ func AutoMigrate() {
db.Dao.AutoMigrate(&models.TelegraphTags{})
db.Dao.AutoMigrate(&models.LongTigerRankData{})
db.Dao.AutoMigrate(&data.AIConfig{})
db.Dao.AutoMigrate(&models.BKDict{})
updateMultipleModel()
}