Compare commits

...

41 Commits

Author SHA1 Message Date
ArvinLovegood
70e0d19c58 feat(ai):新增AI思考模式与热门选股策略功能
- 在NewChatStream和SummaryStockNews接口中增加think参数以支持AI思考模式
- 更新前端组件market.vue和stock.vue,添加思考模式开关控件
- 升级github.com/cloudwego/eino依赖至v0.7.9版本
- 修改ToolFunction结构体,新增Strict字段并调整Parameters为指针类型
- 实现HotStrategyTable工具函数,用于获取并展示热门选股策略表格
- 优化市场新闻API中的去重逻辑,当标题为空时使用内容进行判重
- 在AI对话流中增加对reasoning_content的支持,提升推理过程可见性
- 完善AskAi和AskAiWithTools方法,支持传递thinkingMode参数控制模型推理行为
- 调整FunctionParameters结构体,新增AdditionalProperties字段以增强灵活性
- 修复测试文件中IndustryResearchReport调用参数及注释问题
2025-12-16 15:49:59 +08:00
ArvinLovegood
4e04bacf22 refactor(data):重构情感分析与词频统计模块
- 将 SentimentResult 和 WordFreqWithWeight 结构体移至 models 包统一管理
- 更新 AnalyzeSentiment 和 AnalyzeSentimentWithFreqWeight 函数返回值类型
- 优化 NewsAnalyze 函数实现新闻内容自动获取与分析
- 新增定时任务定期执行新闻情感分析
- 完善数据库模型迁移,支持词频与情感分析结果存储
- 调整前端接口声明文件,确保类型一致性
- 移除冗余代码注释,提升代码可读性
2025-12-12 15:35:50 +08:00
ArvinLovegood
8c75c8533a feat(news):优化新闻列表展示并提取标题
- 后端从富文本中提取 Telegraph 标题
- 前端使用折叠面板展示带标题的新闻项
- 无标题新闻项保持原有展示方式
- 调整标签和文字的排版与样式
- 支持展开/收起新闻详情内容
- 保留时间标签和高亮显示逻辑
2025-12-11 18:39:58 +08:00
ArvinLovegood
1ad02d4b0c style(newsList):优化新闻列表文本显示样式
- 为新闻标题和内容添加换行样式以防止文本溢出
- 使用条件渲染仅在存在标题时显示标题文本
- 调整新闻内容文本的断行属性以提高可读性
- 移除不再使用的渐变文本组件以简化模板结构
2025-12-11 18:26:11 +08:00
ArvinLovegood
a354ab8925 feat(news):添加新闻标题字段并更新测试代码
- 在 Telegraph 模型中新增 Title 字段以存储新闻标题
- 更新 market_news_api.go 文件以支持标题数据的提取和赋值
2025-12-11 16:47:35 +08:00
ArvinLovegood
6d50be8541 fix(main):添加全局panic捕获
- 增加defer函数捕获程序异常
- 记录panic堆栈信息便于调试
- 注释掉包含构建密钥的日志输出

chore(test): 新增字符串处理工具包引入

- 引入lancet/v2/strutil包用于测试
- 添加调试日志展示字符串截取功能
- 完善市场资讯API测试用例
2025-12-10 17:52:18 +08:00
ArvinLovegood
6303d535bc refactor(app):移除测试代码中的授权令牌并调整更新检查逻辑
移除app_test.go中硬编码的GitHub授权令牌,避免安全风险。在app.go的CheckUpdate方法中,将授权令牌从请求头中移除,改为无认证方式获取版本信息,同时优化VIP用户下载链接逻辑。
2025-12-10 16:26:09 +08:00
ArvinLovegood
b464d8f563 chore(config):更新访问令牌
- 替换 app.go 中的旧访问令牌为新令牌
- 注释掉 app_test.go 中的访问令牌以避免测试时使用
- 确保所有 GitHub API 请求使用最新的认证凭证
2025-12-10 16:24:03 +08:00
ArvinLovegood
eea0856c1c feat(stock):更新股票研究与市场新闻功能
- 修改股票研究工具描述,明确获取分析师研究报告
- 调整 TradingView 新闻接口参数,优化请求过滤条件
- 新增代理测试与通知推送测试方法
- 扩大股票搜索范围,提升数据获取数量
- 更新东方财富接口 User-Agent,增强请求稳定性
- 优化美股与港股数据抓取逻辑及休眠时间
- 增加前端消息墙展示标签页,集成外部链接页面
- 调整数据库操作注释,便于后续调试与维护
2025-12-10 15:55:06 +08:00
ArvinLovegood
1dd77d5c08 feat(stock):增强股票情感分析功能并优化GitHub请求
- 为GitHub API请求添加授权头部信息
- 禁用OpenAI接口中的思考模式
- 重构情感分析初始化逻辑,增加异常恢复机制
- 优化词典加载流程,提升系统稳定性
- 调整词语权重计算方式,提高准确性
- 更新测试用例,增强覆盖场景
- 移除无用的错误包引用,清理依赖项
- 修复URL格式化问题,确保请求正确性
2025-12-02 18:45:15 +08:00
ArvinLovegood
9c1c0382ca chore(deps):更新多个依赖库版本
-更新多个依赖库版本
2025-12-02 16:06:54 +08:00
ArvinLovegood
46065f448b feat(openai):启用模型思考模式并设置工具选择
- 在请求体中添加 thinking 字段以启用模型思考模式
- 设置 tool_choice 为 required 以强制使用工具调用
- 保持现有模型配置和其他参数不变
2025-12-02 14:23:52 +08:00
ArvinLovegood
a7cee69e68 chore(deps):更新模块依赖版本
- 更新 github.com/PuerkitoBio/goquery 从 v1.10.1 到 v1.11.0
- 更新 github.com/chromedp/chromedp 从 v0.14.1 到 v0.14.2
- 更新 github.com/cloudwego/eino 从 v0.4.1 到 v0.7.3
- 更新 github.com/cloudwego/eino-ext/components/model/ark 从 v0.1.19 到 v0.1.51
- 更新 github.com/cloudwego/eino-ext/components/model/deepseek 到最新提交
- 更新 github.com/cloudwego/eino-ext/components/model/openai 从 v0.0.0 到 v0.1.5
- 更新 github.com/duke-git/lancet/v2 从 v2.3.4 到 v2.3.8
- 更新 github.com/go-resty/resty/v2 从 v2.16.2 到 v2.17.0
- 更新 github.com/samber/lo 从 v1.49.1 到 v1.52.0
- 更新 github.com/stretchr/testify 从 v1.10.0 到 v1.11.1
- 更新 github.com/tidwall/gjson 从 v1.14.4 到 v1.18.0
- 更新 github.com/wailsapp/wails/v2 从 v2.10.1 到 v2.11.0
- 更新 go.uber.org/zap 从 v1.27.0 到 v1.27.1
- 更新 gorm.io/gorm 从 v1.25.12 到 v1.31.1
- 更新 gorm.io/plugin/dbresolver 从 v1.5.3 到 v1.6.2
- 移除 github.com/getkin/kin-openapi 间接依赖
- 移除 github.com/meguminnnnnnnnn/go-openai 旧版本,更新为 v0.1.0
- 移除 github.com/openai/openai-go 间接依赖
- 添加 github.com/bahlo/generic-list-go 作为间接依赖
- 添加 github.com/eino-contrib/jsonschema 作为间接依赖
- 添加 github.com/gorilla/websocket 作为间接依赖
- 添加 github.com/wk8/go-ordered-map/v2 作为间接依赖
- 添加 google.golang.org/protobuf 和 gopkg.in/check.v1 作为间接依赖
2025-11-30 19:43:30 +08:00
ArvinLovegood
459441f838 feat(stock):优化股票情绪分析词典加载逻辑
- 引入 errors 包处理词典加载错误
- 使用 github.com/vcaesar/cedar 优化词典存储结构
- 修复重复添加词汇时的错误处理逻辑
- 增强用户词典读取稳定性,避免空行导致崩溃
- 改进词汇频率更新机制,提高分词准确性
2025-11-30 10:56:57 +08:00
ArvinLovegood
b4b3b61e8c feat(db):优化数据库连接配置并调整SQLite缓存大小
- 调整SQLite连接字符串,设置缓存大小为-524288
- 修改数据库最大空闲连接数为4
- 修改数据库最大打开连接数为10
- 重新排列导入包顺序,将标准库包放在第三方库之前
2025-11-27 18:10:27 +08:00
ArvinLovegood
b6a99940ab feat(db):优化数据库连接配置并调整SQLite缓存大小
- 调整SQLite连接字符串,设置缓存大小为-524288
- 修改数据库最大空闲连接数为4
- 修改数据库最大打开连接数为10
- 重新排列导入包顺序,将标准库包放在第三方库之前
2025-11-27 18:06:15 +08:00
ArvinLovegood
5b0f34a3bd chore(deps): 更新 golang.org/x 包依赖版本
- 将 golang.org/x/crypto 从 v0.39.0 升级到 v0.44.0
- 将 golang.org/x/net 从 v0.38.0 升级到 v0.47.0
- 将 golang.org/x/sys 从 v0.36.0 升级到 v0.38.0
- 将 golang.org/x/text 从 v0.26.0 升级到 v0.31.0
- 将 golang.org/x/term 从 v0.32.0 升级到 v0.37.0
2025-11-27 17:55:29 +08:00
ArvinLovegood
e34ebf9895 chore(deps):更新前端依赖包版本
- 升级 @vitejs/plugin-vue 至 6.0.2 版本
- 升级 naive-ui 至 2.43.2 版本
- 升级 vite 至 7.2.4 版本
- 更新相关子依赖及类型定义文件
- 调整部分组件展示结构以支持图片显示
2025-11-26 18:27:35 +08:00
ArvinLovegood
c3521c6d7f feat(stock):新增东财用户标识支持并优化新闻抓取逻辑
- 在设置中增加东财唯一标识(qgqpBId)字段,用于搜索股票接口鉴权
- 优化 Telegraph 列表获取逻辑,使用协程并发执行多个数据源请求
- 调整 TradingView 新闻定时任务间隔时间,从60秒减少到10秒
- 修改新闻列表排序规则,优先按数据时间降序排列
- 更新 TradingView 新闻去重条件,由内容匹配改为时间和标题匹配
- 限制 TradingView 新闻详情处理数量,最多只处理前10条
- 完善错误提示信息显示逻辑,兼容不同字段的消息返回
- 删除冗余的 GetLevel 函数,直接在赋值处判断等级逻辑
- 增加测试初始化情感分析模块调用
- 前端表单同步增加 qgqpBId 字段绑定与持久化配置支持
2025-11-26 14:58:12 +08:00
ArvinLovegood
93b37ca621 feat(market):新增外媒新闻功能并优化展示逻辑
- 在定时任务中增加TradingViewNews新闻抓取
- 增加TradingView新闻详情接口及数据解析逻辑
- 修改Telegraph模型字段索引提升查询性能
- 前端新增"外媒"新闻列表展示模块
- 根据HTTP代理配置动态调整新闻栏布局
- 修复时间转换及去重逻辑提高数据准确性
- 调整新闻列表显示样式增强可读性
2025-11-25 17:44:13 +08:00
ArvinLovegood
7069af869b feat(frontend):优化市场分析组件并增强标签处理逻辑
- 添加主要指数(mainIndex),包括中美日韩等地重要城市指数
- 更新图表展示逻辑,使用mainIndex代替原有的china索引
- 引入数字动画效果,提升用户体验
- 在后端增加标签类型过滤,仅处理type为"subject"的标签
- 调整标签词频,通过ReAddToken方法提高关键词权重
2025-11-25 12:23:54 +08:00
ArvinLovegood
dbb6789c05 feat(frontend):市场快讯里面添加全球股指展示功能并优化情绪分析图表
- 引入 GlobalStockIndexes 接口获取全球主要股指数据
- 添加中国主要股指筛选逻辑(上海、深圳、香港等)
- 优化市场情绪图表数值显示精度
- 调整图表布局结构,增强可视化效果
- 修改市场情绪强弱指标名称提升可读性
- 定时获取最新股指数据(每2秒刷新)
- 调整负面金融词汇权重以提高分析准确性
2025-11-24 18:08:10 +08:00
ArvinLovegood
8aed4d2753 feat(dict):优化金融股票分词字典结构与内容
- 删除重复或冗余的词条,如“人工智能”、“云计算”等在多个分类中重复出现的词汇
- 调整并统一章节编号,确保从一至九的连续性和逻辑性
- 移除不再适用的覆盖场景描述,提升字典的专业性与准确性
- 更新权重说明注释,去除不必要的分数细节,保持清晰易懂
- 重新组织词条顺序,使同类项集中,提高检索效率
- 清理负权重词汇列表中的多余条目,强化过滤机制
- 精简A股龙头公司条目,聚焦更广泛的财务与估值指标词条
- 统一格式排版,增强可读性和维护便利性
2025-11-23 20:48:28 +08:00
ArvinLovegood
6bd1bdae02 feat(dict):优化金融股票分词字典结构与内容
- 删除重复或冗余的词条,如“人工智能”、“云计算”等在多个分类中重复出现的词汇
- 调整并统一章节编号,确保从一至九的连续性和逻辑性
- 移除不再适用的覆盖场景描述,提升字典的专业性与准确性
- 更新权重说明注释,去除不必要的分数细节,保持清晰易懂
- 重新组织词条顺序,使同类项集中,提高检索效率
- 清理负权重词汇列表中的多余条目,强化过滤机制
- 精简A股龙头公司条目,聚焦更广泛的财务与估值指标词条
- 统一格式排版,增强可读性和维护便利性
2025-11-23 20:40:57 +08:00
ArvinLovegood
9a40d343aa feat(dict): 优化金融股票分词字典结构与内容
- 删除重复或冗余的词条,如“人工智能”、“云计算”等在多个分类中重复出现的词汇
- 调整并统一章节编号,确保从一至九的连续性和逻辑性
- 移除不再适用的覆盖场景描述,提升字典的专业性与准确性
- 更新权重说明注释,去除不必要的分数细节,保持清晰易懂
- 重新组织词条顺序,使同类项集中,提高检索效率
- 清理负权重词汇列表中的多余条目,强化过滤机制
- 精简A股龙头公司条目,聚焦更广泛的财务与估值指标词条
- 统一格式排版,增强可读性和维护便利性
2025-11-23 20:39:06 +08:00
ArvinLovegood
e4cdad6ffe feat(data): 更新用户词典,新增热点概念与板块词汇
- 添加负权重词汇以降低无差别匹配干扰
- 新增核心热点概念词汇,权重设为700分
- 扩展重点赛道板块词汇,权重设为500分
- 增加事件驱动型概念词汇,权重设为400分
- 调整部分已有词汇格式,确保兼容性
2025-11-23 20:22:29 +08:00
ArvinLovegood
a0005dab96 feat(data): 更新用户词典文件
- 调整了原有词汇的权重值从0.1为-0.1
- 新增多个金融及行业相关词汇,如基金、保险等
- 增加了热点概念词汇,例如冰雪旅游、新能源汽车等
- 添加了具体公司或产品名称,如摩尔线程及其相关概念
- 保留并确认具身智能一词的权重与分类不变
2025-11-23 20:03:23 +08:00
ArvinLovegood
c945ca9322 feat(data):调整新闻获取逻辑与情感分析词频权重
- 修改市场新闻接口查询逻辑,按创建时间倒序排序
- 增加单次获取新闻数量上限至10000条
- 调整股票名称及板块名称在分词器中的基础频率权重
- 修改标签添加时的基础频率值
- 更新情感分析中词语权重判断条件,使用动态基准频率替代固定值200
2025-11-23 19:12:07 +08:00
ArvinLovegood
7bbc6831f4 feat(frontend):实现词云图按权重和频次切换显示
- 添加工具箱按钮用于切换数据展示方式
- 支持按权重、频次和分数三种方式展示词云图
- 优化图表标题和坐标轴标签颜色适配暗色主题
- 调整树图布局顶部间距以避免遮挡标题
- 隐藏树图面包屑导航提升界面简洁性
2025-11-23 11:20:32 +08:00
ArvinLovegood
ab0ccc4fe0 feat(frontend):调整市场情绪仪表盘配置与数据映射逻辑
- 为仪表盘启用暗色主题支持
- 修改仪表盘数值范围从[0,1]到[-100,100]
- 更新颜色分段以匹配新的评分区间
- 调整轴标签位置并重新定义文案映射规则
- 修正数据传入方式,将原始分数转换为适配图表的比例值
- 优化后端接口调用参数,移除固定来源限制以获取更全面的新闻数据
2025-11-23 08:13:35 +08:00
ArvinLovegood
fa658357c9 feat(sentiment):新增市场情绪分析功能
- 添加 AnalyzeMartket 组件用于展示情绪热力图和仪表盘
- 引入 ECharts 实现数据可视化
- 配置定时任务定期更新图表数据
- 在 backend 中扩展情感词典,新增关键词如“被抓”、“超”等
- 优化 sentiment 分析逻辑,提升准确性
- 移除旧版 treemap 图表实现,统一使用新组件渲染
- 调整 single instance ID 为 go-stock
- 更新 base.txt 词库,增加“亏损”、“加工”等词汇
2025-11-22 23:35:02 +08:00
ArvinLovegood
746589a972 feat(backend):优化词典加载与情感分析逻辑
- 重构词典初始化流程,提升加载效率
- 新增CalcToken调用以确保词典更新生效
- 改进用户词典读取路径并增强格式校验
- 忽略空行及注释行,防止无效词条干扰分析
- 使用ReAddToken方法替换原有AddToken实现热更新
- 调整词频权重过滤阈值,提高情感识别准确度
- 引入Score字段用于treemap可视化展示
- 更新基础词典和用户词典内容,强化专业术语覆盖
- 注释冗余测试文本,避免影响正式环境运行
- 增加日志记录以便追踪词典加载与匹配过程
2025-11-22 20:16:14 +08:00
ArvinLovegood
401dd17fa8 feat(stock):加载用户自定义词典进行情感分析(用户可以自己定义词典调整热词权重)
- 在默认词典加载成功后,增加加载用户自定义词典逻辑
- 判断用户词典文件是否存在,避免加载空文件
- 记录用户词典加载成功或失败的日志信息
- 保持原有默认词典加载流程不变
- 确保词典加载错误不会中断程序运行
- 统一词典加载后的日志输出格式
2025-11-22 17:28:40 +08:00
ArvinLovegood
c365bd9534 feat(frontend): 添加官方声明显示功能
- 在 App.vue 中新增 officialStatement 响应式变量
- 从版本信息接口获取官方声明内容并存储
- 将官方声明动态插入到窗口标题中
- 调整窗口标题显示逻辑以包含官方声明
2025-11-22 17:26:31 +08:00
ArvinLovegood
104ee51e13 feat(frontend): 添加官方声明显示功能
- 在 App.vue 中新增 officialStatement 响应式变量
- 从版本信息接口获取官方声明内容并存储
- 将官方声明动态插入到窗口标题中
- 调整窗口标题显示逻辑以包含官方声明
2025-11-22 17:26:12 +08:00
ArvinLovegood
00f3e5f0e0 feat(data): 添加用户词典文件
- 新增包含13个常用词汇的用户词典
- 词汇涵盖公司、国家、市场等业务相关术语
- 为自然语言处理模块提供基础词汇支持
2025-11-22 17:18:35 +08:00
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
38 changed files with 2294 additions and 895 deletions

View File

@@ -22,7 +22,7 @@
- 开发环境主要基于Windows10+,其他平台未测试或功能受限。
### 📦 立即体验
- 安装版:[go-stock-amd64-installer.exe](https://github.com/ArvinLovegood/go-stock/releases)
[//]: # (- 安装版:[go-stock-amd64-installer.exe](https://github.com/ArvinLovegood/go-stock/releases))
- 绿色版:[go-stock-windows-amd64.exe](https://github.com/ArvinLovegood/go-stock/releases)
- MACOS绿色版[go-stock-darwin-universal](https://github.com/ArvinLovegood/go-stock/releases)

60
app.go
View File

@@ -62,7 +62,7 @@ func AddTools(tools []data.Tool) []data.Tool {
Function: data.ToolFunction{
Name: "SearchStockByIndicators",
Description: "根据自然语言筛选股票,返回自然语言选股条件要求的股票所有相关数据。输入股票名称可以获取当前股票最新的股价交易数据和基础财务指标信息,多个股票名称使用,分隔。",
Parameters: data.FunctionParameters{
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"words": map[string]any{
@@ -88,7 +88,7 @@ func AddTools(tools []data.Tool) []data.Tool {
Function: data.ToolFunction{
Name: "GetStockKLine",
Description: "获取股票日K线数据。",
Parameters: data.FunctionParameters{
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"days": map[string]any{
@@ -110,7 +110,7 @@ func AddTools(tools []data.Tool) []data.Tool {
Function: data.ToolFunction{
Name: "InteractiveAnswer",
Description: "获取投资者与上市公司互动问答的数据,反映当前投资者关注的热点问题",
Parameters: data.FunctionParameters{
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"page": map[string]any{
@@ -161,8 +161,8 @@ func AddTools(tools []data.Tool) []data.Tool {
Type: "function",
Function: data.ToolFunction{
Name: "GetStockResearchReport",
Description: "获取股票的分析/研究报告",
Parameters: data.FunctionParameters{
Description: "获取市场分析师的股票研究报告",
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"stockCode": map[string]any{
@@ -175,6 +175,14 @@ func AddTools(tools []data.Tool) []data.Tool {
},
})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "HotStrategyTable",
Description: "获取当前热门选股策略",
},
})
return tools
}
@@ -410,10 +418,18 @@ func (a *App) domReady(ctx context.Context) {
//定时更新数据
config := data.GetSettingConfig()
go func() {
go data.NewMarketNewsApi().TelegraphList(30)
go data.NewMarketNewsApi().GetSinaNews(30)
go data.NewMarketNewsApi().TradingViewNews()
interval := config.RefreshInterval
if interval <= 0 {
interval = 1
}
a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+60), func() {
data.NewsAnalyze("", true)
})
//ticker := time.NewTicker(time.Second * time.Duration(interval))
//defer ticker.Stop()
//for range ticker.C {
@@ -453,6 +469,19 @@ func (a *App) domReady(ctx context.Context) {
} else {
a.cronEntrys["newSinaNews"] = entryIDSina
}
entryIDTradingViewNews, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
news := data.NewMarketNewsApi().TradingViewNews()
if config.EnablePushNews {
go a.NewsPush(news)
}
go runtime.EventsEmit(a.ctx, "tradingViewNews", news)
})
if err != nil {
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
} else {
a.cronEntrys["tradingViewNews"] = entryIDTradingViewNews
}
}()
//刷新基金净值信息
@@ -669,7 +698,7 @@ func (a *App) AddCronTask(follow data.FollowedStock) func() {
return func() {
go runtime.EventsEmit(a.ctx, "warnMsg", "开始自动分析"+follow.Name+"_"+follow.StockCode)
ai := data.NewDeepSeekOpenAi(a.ctx, follow.AiConfigId)
msgs := ai.NewChatStream(follow.Name, follow.StockCode, "", nil, a.AiTools)
msgs := ai.NewChatStream(follow.Name, follow.StockCode, "", nil, a.AiTools, true)
var res strings.Builder
chatId := ""
@@ -700,7 +729,7 @@ func refreshTelegraphList() *[]string {
response, err := resty.New().R().
SetHeader("Referer", "https://www.cls.cn/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(fmt.Sprintf(url))
Get(url)
if err != nil {
return &[]string{}
}
@@ -1034,12 +1063,12 @@ func (a *App) SendDingDingMessageByType(message string, stockCode string, msgTyp
return data.NewDingDingAPI().SendDingDingMessage(message)
}
func (a *App) NewChatStream(stock, stockCode, question string, aiConfigId int, sysPromptId *int, enableTools bool) {
func (a *App) NewChatStream(stock, stockCode, question string, aiConfigId int, sysPromptId *int, enableTools bool, think bool) {
var msgs <-chan map[string]any
if enableTools {
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewChatStream(stock, stockCode, question, sysPromptId, a.AiTools)
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewChatStream(stock, stockCode, question, sysPromptId, a.AiTools, think)
} else {
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewChatStream(stock, stockCode, question, sysPromptId, []data.Tool{})
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewChatStream(stock, stockCode, question, sysPromptId, []data.Tool{}, think)
}
for msg := range msgs {
runtime.EventsEmit(a.ctx, "newChatStream", msg)
@@ -1358,8 +1387,9 @@ func (a *App) GetTelegraphList(source string) *[]*models.Telegraph {
func (a *App) ReFleshTelegraphList(source string) *[]*models.Telegraph {
//data.NewMarketNewsApi().GetNewTelegraph(30)
data.NewMarketNewsApi().TelegraphList(30)
data.NewMarketNewsApi().GetSinaNews(30)
go data.NewMarketNewsApi().TelegraphList(30)
go data.NewMarketNewsApi().GetSinaNews(30)
go data.NewMarketNewsApi().TradingViewNews()
telegraphs := data.NewMarketNewsApi().GetTelegraphList(source)
return telegraphs
}
@@ -1368,12 +1398,12 @@ func (a *App) GlobalStockIndexes() map[string]any {
return data.NewMarketNewsApi().GlobalStockIndexes(30)
}
func (a *App) SummaryStockNews(question string, aiConfigId int, sysPromptId *int, enableTools bool) {
func (a *App) SummaryStockNews(question string, aiConfigId int, sysPromptId *int, enableTools bool, think bool) {
var msgs <-chan map[string]any
if enableTools {
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewSummaryStockNewsStreamWithTools(question, sysPromptId, a.AiTools)
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewSummaryStockNewsStreamWithTools(question, sysPromptId, a.AiTools, think)
} else {
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewSummaryStockNewsStream(question, sysPromptId)
msgs = data.NewDeepSeekOpenAi(a.ctx, aiConfigId).NewSummaryStockNewsStream(question, sysPromptId, think)
}
for msg := range msgs {

View File

@@ -4,7 +4,6 @@ import (
"go-stock/backend/agent"
"go-stock/backend/data"
"go-stock/backend/models"
"strings"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
@@ -32,7 +31,7 @@ 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) models.SentimentResult {
return data.AnalyzeSentiment(text)
}
@@ -75,17 +74,7 @@ func (a *App) ChatWithAgent(question string, aiConfigId int, sysPromptId *int) {
}
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)
result, cleanFrequencies := data.NewsAnalyze(text, false)
return map[string]any{
"result": result,
"frequencies": cleanFrequencies,

View File

@@ -8,6 +8,8 @@ import (
"go-stock/backend/models"
"testing"
"time"
"github.com/go-resty/resty/v2"
)
// @Author spark
@@ -45,3 +47,18 @@ func TestJson(t *testing.T) {
db.Dao.Model(v).Updates(v)
}
func TestUpdateCheck(t *testing.T) {
releaseVersion := &models.GitHubReleaseVersion{}
_, err := resty.New().R().
SetResult(releaseVersion).
SetHeader("Accept", "application/vnd.github+json").
SetHeader("X-GitHub-Api-Version", "2022-11-28").
Get("https://api.github.com/repos/ArvinLovegood/go-stock/releases/latest")
// https://api.github.com/repos/OWNER/REPO/releases/latest
if err != nil {
logger.SugaredLogger.Errorf("get github release version error:%s", err.Error())
return
}
logger.SugaredLogger.Infof("releaseVersion:%+v", releaseVersion)
}

View File

@@ -3,13 +3,14 @@ package tools
import (
"context"
"encoding/json"
"go-stock/backend/data"
"go-stock/backend/logger"
"strings"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/random"
"github.com/tidwall/gjson"
"go-stock/backend/data"
"go-stock/backend/logger"
"strings"
)
// @Author spark
@@ -51,7 +52,7 @@ func (q QueryMarketNews) InvokableRun(ctx context.Context, argumentsInJSON strin
})
}
news := data.NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(100, 500))
news := data.NewMarketNewsApi().GetNewsList("", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")

View File

@@ -1,6 +1,5 @@
# 金融股票全场景分词字典(去重优化版)
# 金融股票全场景分词字典(最终去重优化版)
# 格式:单词 权重 词性 | 权重280-350分核心术语优先匹配无重复词汇
# 覆盖:净买卖、股指、财务指标、交易操作、政策宏观、热点概念等全场景
# 一、净买卖与资金流向(核心交易表述)
净卖出 340 v
@@ -94,6 +93,8 @@
信息披露 310 n
内幕交易 300 n
操纵市场 300 n
亏损 100 n
加工 100 n
# 三、全球主要股指(含中英文缩写)
# 中国市场
@@ -116,62 +117,43 @@ A股 350 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
@@ -196,38 +178,7 @@ G20国家指数 300 n
指数估值 310 n
指数市盈率 310 n
# 四、A股龙头公司资讯高频
贵州茅台 310 n
宁德时代 310 n
比亚迪 300 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
药明康德 290 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
@@ -267,7 +218,7 @@ EPS 330 n
量比 320 n
振幅 320 n
# 、政策与宏观经济
# 、政策与宏观经济
货币政策 330 n
财政政策 330 n
稳健货币政策 320 n
@@ -305,7 +256,7 @@ PMI 330 n
黄金价格 310 n
有色金属价格 300 n
# 、金融产品与机构
# 、金融产品与机构
股票 320 n
基金 320 n
公募基金 310 n
@@ -349,23 +300,23 @@ QFII 300 n
RQFII 290 n
北向资金机构 300 n
# 、热点概念与行业
# 、热点概念与行业
AI 330 n
人工智能 330 n
人工智能 350 n
算力 330 n
大数据 320 n
云计算 320 n
半导体 320 n
芯片 320 n
集成电路 310 n
新能源 320 n
光伏 320 n
半导体 350 n
芯片 350 n
集成电路 340 n
新能源 350 n
光伏 340 n
锂电 320 n
储能 320 n
储能 340 n
充电桩 310 n
新能源车 320 n
智能汽车 310 n
自动驾驶 310 n
自动驾驶 330 n
军工 310 n
国防军工 300 n
医药 310 n
@@ -382,18 +333,18 @@ CXO 300 n
房地产 300 n
基建 300 n
新基建 310 n
数字经济 320 n
数字经济 350 n
数字货币 310 n
区块链 300 n
元宇宙 300 n
低空经济 310 n
人形机器人 310 n
工业互联网 300 n
低空经济 340 n
人形机器人 330 n
工业互联网 330 n
物联网 300 n
5G 300 n
6G 300 n
6G 340 n
# 、交易操作与行情
# 、交易操作与行情
上涨 310 v
下跌 310 v
涨停 310 v
@@ -441,8 +392,8 @@ CXO 300 n
跌停板 300 n
一字涨停 290 n
一字跌停 290 n
打开涨停 280 v
打开跌停 280 v
打开涨停 320 v
打开跌停 320 v
集合竞价 290 n
连续竞价 280 n
开盘价 340 n
@@ -457,8 +408,6 @@ CXO 300 n
跌幅 340 n
涨停价 330 n
跌停价 330 n
打开涨停 320 v
打开跌停 320 v
熔断 330 n
临时停牌 320 n
复牌 320 v
@@ -473,274 +422,7 @@ CXO 300 n
震荡上行 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
纳斯达克 310 n
纽交所 310 n
标普500 310 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 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
# 主要财经网站与机构名词(格式:单词 权重 词性)
# 权重320-350分与核心金融术语优先级一致确保精准识别
# 一、国内财经网站(资讯高频来源)
东方财富网 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
纳斯达克 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
止损委托 330 n

View File

@@ -0,0 +1,185 @@
# 补充热点概念与板块Jieba/gse兼容格式
# 权重说明核心热点500-700分事件类400分负权重词汇按需求保留
# 一、负权重低优先级词汇(减少无差别匹配干扰)
公司 -0.1 n
国家 -0.1 n
国际 -0.1 n
会议 -0.1 n
市场 -0.1 n
经济 -0.1 n
技术 -0.1 n
记者 -0.1 n
时间 -0.1 n
项目 -0.1 n
问题 -0.1 n
企业 -0.1 n
财联社 -0.1 n
上涨 -0.1 v
下跌 -0.1 v
期货 -0.1 n
跌幅 -0.1 n
跌超 -0.1 adj
股票 -0.1 n
基金 -0.1 n
电讯 -0.1 n
建筑 -0.1 n
平开 -0.1 n
保险 -0.1 n
行业 -0.1 n
其他 -0.1 n
# 二、核心热点概念700分最高优先级
比特币 700 n
摩尔线程 700 n
摩尔线程概念 700 n
AI算力 700 n
生成式AI 700 n
量子计算 700 n
脑机接口 700 n
6G通信 700 n
人形机器人 700 n
固态电池 700 n
ChatGPT概念 700 n
Web3.0 700 n
元宇宙 700 n
数字孪生 700 n
量子通信 700 n
# 三、重点赛道板块500分高优先级
冰雪旅游 500 n
特高压 500 n
跨境电商 500 n
新能源汽车 500 n
机器人 500 n
具身智能 500 n
油气 500 n
商业航天 500 n
光伏储能 500 n
锂电材料 500 n
半导体设备 500 n
集成电路 500 n
创新药 500 n
CXO 500 n
医疗器械 500 n
数字经济 500 n
数字货币 500 n
区块链 500 n
低空经济 500 n
工业互联网 500 n
物联网 500 n
5G应用 500 n
充电桩 500 n
氢能源 500 n
核聚变 500 n
工业母机 500 n
新材料 500 n
生物制造 500 n
智能网联汽车 500 n
乡村振兴 500 n
国企改革 500 n
央企重组 500 n
跨境金融 500 n
自贸港 500 n
一带一路 500 n
绿色低碳 500 n
碳交易 500 n
数据要素 500 n
数字基建 500 n
东数西算 500 n
国产替代 500 n
信创 500 n
网络安全 500 n
算力网络 500 n
边缘计算 500 n
虚拟现实 500 n
增强现实 500 n
智能穿戴 500 n
智能家居 500 n
车联网 500 n
激光雷达 500 n
氮化镓 500 n
碳化硅 500 n
第三代半导体 500 n
EDA工具 500 n
光刻胶 500 n
芯片设计 500 n
封装测试 500 n
储能电池 500 n
钠离子电池 500 n
氢燃料电池 500 n
光伏组件 500 n
风电设备 500 n
特高压设备 500 n
电力物联网 500 n
智能电网 500 n
轨道交通 500 n
航空航天 500 n
海洋工程 500 n
高端装备 500 n
军工电子 500 n
卫星互联网 500 n
北斗导航 500 n
国产大飞机 500 n
生物医药 500 n
基因测序 500 n
疫苗 500 n
医疗美容 500 n
养老产业 500 n
教育信息化 500 n
体育产业 500 n
文化创意 500 n
旅游复苏 500 n
预制菜 500 n
白酒 500 n
食品饮料 500 n
家电下乡 500 n
房地产复苏 500 n
基建投资 500 n
新型城镇化 500 n
冷链物流 500 n
快递物流 500 n
跨境支付 500 n
金融科技 500 n
消费电子 500 n
元宇宙基建 500 n
数字藏品 500 n
NFT 500 n
绿色电力 500 n
节能降碳 500 n
抽水蓄能 500 n
生物质能 500 n
地热能 500 n
潮汐能 500 n
# 四、事件驱动型概念400分中优先级
俄乌冲突 400 n
中东局势 400 n
美联储加息 400 n
降息预期 400 n
贸易摩擦 400 n
供应链重构 400 n
能源危机 400 n
粮食安全 400 n
疫情复苏 400 n
政策利好 400 n
产业扶持 400 n
技术突破 400 n
并购重组 400 n
IPO提速 400 n
解禁潮 400 n
北向资金流入 400 n
南向资金流入 400 n
主力资金异动 400 n
行业景气度 400 n
业绩预增 400 n
商誉减值 400 n
退市风险 400 n
监管新规 400 n
税收优惠 400 n
补贴政策 400 n
基建刺激 400 n
消费刺激 400 n
新能源补贴 400 n
碳达峰政策 400 n
碳中和目标 400 n

View File

@@ -8,6 +8,7 @@ import (
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"net/url"
"strconv"
"strings"
"time"
@@ -53,19 +54,23 @@ func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
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)
dataTime := time.Unix(ctime, 0).Local()
telegraph := models.Telegraph{
Title: news["title"].(string),
Content: news["content"].(string),
Time: dataTime.Format("15:04:05"),
DataTime: &dataTime,
Url: news["shareurl"].(string),
Source: "财联社电报",
IsRed: GetLevel(news["level"].(string)),
IsRed: (news["level"].(string)) != "C",
SentimentResult: AnalyzeSentiment(news["content"].(string)).Description,
}
cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and content=?", telegraph.Time, telegraph.Content).Count(&cnt)
if telegraph.Title == "" {
db.Dao.Model(telegraph).Where("content=?", telegraph.Content).Count(&cnt)
} else {
db.Dao.Model(telegraph).Where("title=?", telegraph.Title).Count(&cnt)
}
if cnt > 0 {
continue
}
@@ -96,9 +101,6 @@ func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
return &telegraphs
}
func GetLevel(s string) bool {
return s > "C"
}
func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
url := "https://www.cls.cn/telegraph"
@@ -167,9 +169,9 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
func (m MarketNewsApi) GetNewsList(source string, limit int) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("data_time desc,time desc").Limit(limit).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Order("data_time desc,time desc").Limit(limit).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
@@ -188,9 +190,9 @@ func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegra
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)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("data_time 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)
db.Dao.Model(news).Preload("TelegraphTags").Order("data_time desc,is_red desc").Limit(limit).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
@@ -209,9 +211,9 @@ func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegra
func (m MarketNewsApi) GetTelegraphList(source string) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc").Limit(20).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("data_time desc,time desc").Limit(50).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc").Limit(20).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Order("data_time desc,time desc").Limit(50).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
@@ -267,7 +269,12 @@ func (m MarketNewsApi) GetSinaNews(crawlTimeOut uint) *[]models.Telegraph {
data := item.(map[string]any)
//logger.SugaredLogger.Infof("%s:%s", data["create_time"], data["rich_text"])
telegraph.Content = data["rich_text"].(string)
telegraph.Title = strutil.SubInBetween(data["rich_text"].(string), "【", "】")
telegraph.Time = strings.Split(data["create_time"].(string), " ")[1]
dataTime, _ := time.ParseInLocation("2006-01-02 15:04:05", data["create_time"].(string), time.Local)
if &dataTime != nil {
telegraph.DataTime = &dataTime
}
tags := data["tag"].([]any)
telegraph.SubjectTags = lo.Map(tags, func(tagItem any, index int) string {
name := tagItem.(map[string]any)["name"].(string)
@@ -286,7 +293,11 @@ func (m MarketNewsApi) GetSinaNews(crawlTimeOut uint) *[]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)
if telegraph.Title == "" {
db.Dao.Model(telegraph).Where("content=?", telegraph.Content).Count(&cnt)
} else {
db.Dao.Model(telegraph).Where("title=?", telegraph.Title).Count(&cnt)
}
if cnt == 0 {
db.Dao.Create(&telegraph)
telegraphs = append(telegraphs, telegraph)
@@ -640,15 +651,19 @@ func (m MarketNewsApi) EMDictCode(code string, cache *freecache.Cache) []any {
return respMap["data"].([]any)
}
func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
func (m MarketNewsApi) TradingViewNews() *[]models.Telegraph {
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
TVNews := &[]models.TVNews{}
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=provider:panews,reuters&client=screener&streaming=false"
resp, err := client.SetTimeout(time.Duration(5)*time.Second).R().
news := &[]models.Telegraph{}
// url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=area:WLD&client=screener&streaming=false"
//url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=area%3AWLD&filter=lang%3Azh-Hans&client=screener&streaming=false"
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang%3Azh-Hans&client=screener&streaming=false"
resp, err := client.SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "news-mediator.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
SetHeader("Referer", "https://cn.tradingview.com/").
@@ -656,19 +671,85 @@ func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("TradingViewNews err:%s", err.Error())
return TVNews
return news
}
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
if err != nil {
return TVNews
return news
}
items, err := json.Marshal(respMap["items"])
if err != nil {
return TVNews
return news
}
json.Unmarshal(items, TVNews)
return TVNews
for i, a := range *TVNews {
if i > 10 {
break
}
detail := NewMarketNewsApi().TradingViewNewsDetail(a.Id)
dataTime := time.Unix(int64(a.Published), 0).Local()
description := ""
sentimentResult := ""
if detail != nil {
description = detail.ShortDescription
sentimentResult = AnalyzeSentiment(description).Description
}
if a.Title == "" {
continue
}
telegraph := &models.Telegraph{
Title: a.Title,
Content: description,
DataTime: &dataTime,
IsRed: false,
Time: dataTime.Format("15:04:05"),
Source: "外媒",
Url: fmt.Sprintf("https://cn.tradingview.com/news/%s", a.Id),
SentimentResult: sentimentResult,
}
cnt := int64(0)
if telegraph.Title == "" {
db.Dao.Model(telegraph).Where("content=?", telegraph.Content).Count(&cnt)
} else {
db.Dao.Model(telegraph).Where("title=?", telegraph.Title).Count(&cnt)
}
if cnt > 0 {
continue
}
db.Dao.Model(&models.Telegraph{}).Where("time=? and title=? and source=?", telegraph.Time, telegraph.Title, "外媒").FirstOrCreate(&telegraph)
*news = append(*news, *telegraph)
}
return news
}
func (m MarketNewsApi) TradingViewNewsDetail(id string) *models.TVNewsDetail {
//https://news-headlines.tradingview.com/v3/story?id=panews%3A9be7cf057e3f9%3A0&lang=zh-Hans
newsDetail := &models.TVNewsDetail{}
newsUrl := fmt.Sprintf("https://news-headlines.tradingview.com/v3/story?id=%s&lang=zh-Hans", url.QueryEscape(id))
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
request := client.SetTimeout(time.Duration(3) * time.Second).R()
_, err := request.
SetHeader("Host", "news-headlines.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
SetHeader("Referer", "https://cn.tradingview.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0").
//SetHeader("TE", "trailers").
//SetHeader("Priority", "u=4").
//SetHeader("Connection", "keep-alive").
SetResult(newsDetail).
Get(newsUrl)
if err != nil {
logger.SugaredLogger.Errorf("TradingViewNewsDetail err:%s", err.Error())
return newsDetail
}
logger.SugaredLogger.Infof("resp:%+v", newsDetail)
return newsDetail
}
func (m MarketNewsApi) XUEQIUHotStock(size int, marketType string) *[]models.HotItem {
@@ -961,7 +1042,8 @@ func (m MarketNewsApi) ReutersNew() *models.ReutersNews {
client.SetProxy(config.HttpProxy)
}
news := &models.ReutersNews{}
url := "https://www.reuters.com/pf/api/v3/content/fetch/articles-by-section-alias-or-id-v1?query={\"arc-site\":\"reuters\",\"fetch_type\":\"collection\",\"offset\":0,\"section_id\":\"/world/\",\"size\":9,\"uri\":\"/world/\",\"website\":\"reuters\"}&d=300&mxId=00000000&_website=reuters"
//url := "https://www.reuters.com/pf/api/v3/content/fetch/articles-by-section-alias-or-id-v1?query={\"arc-site\":\"reuters\",\"fetch_type\":\"collection\",\"offset\":0,\"section_id\":\"/world/\",\"size\":9,\"uri\":\"/world/\",\"website\":\"reuters\"}&d=300&mxId=00000000&_website=reuters"
url := "https://www.reuters.com/pf/api/v3/content/fetch/recent-stories-by-sections-v1?query=%7B%22section_ids%22%3A%22%2Fworld%2F%22%2C%22size%22%3A4%2C%22website%22%3A%22reuters%22%7D&d=334&mxId=00000000&_website=reuters"
_, err := client.SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "www.reuters.com").
SetHeader("Origin", "https://www.reuters.com").
@@ -1031,9 +1113,9 @@ func (m MarketNewsApi) CailianpressWeb(searchWords string) *models.CailianpressW
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)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=? and created_at>?", source, time.Now().Add(-24*time.Hour)).Order("data_time 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)
db.Dao.Model(news).Preload("TelegraphTags").Where("created_at>?", time.Now().Add(-24*time.Hour)).Order("data_time desc,is_red desc").Limit(limit).Find(news)
}
// 内容去重
uniqueNews := make([]*models.Telegraph, 0)

View File

@@ -6,11 +6,14 @@ import (
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"path/filepath"
"strings"
"testing"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
"github.com/tidwall/gjson"
)
@@ -21,7 +24,12 @@ import (
func TestGetSinaNews(t *testing.T) {
db.Init("../../data/stock.db")
NewMarketNewsApi().GetSinaNews(30)
InitAnalyzeSentiment()
news := NewMarketNewsApi().GetSinaNews(30)
for i, telegraph := range *news {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", i, telegraph)
}
//NewMarketNewsApi().GetNewTelegraph(30)
}
@@ -84,12 +92,13 @@ func TestStockResearchReport(t *testing.T) {
func TestIndustryResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().IndustryResearchReport("456", 7)
resp := NewMarketNewsApi().IndustryResearchReport("", 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))
logger.SugaredLogger.Debugf("url: https://pdf.dfcfw.com/pdf/H3_%s_1.pdf", data["infoCode"])
//NewMarketNewsApi().GetIndustryReportInfo(data["infoCode"].(string))
}
}
@@ -122,10 +131,8 @@ func TestEMDictCode(t *testing.T) {
func TestTradingViewNews(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().TradingViewNews()
for _, a := range *resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
InitAnalyzeSentiment()
NewMarketNewsApi().TradingViewNews()
}
func TestXUEQIUHotStock(t *testing.T) {
@@ -247,5 +254,41 @@ func TestGetNewsList2(t *testing.T) {
func TestTelegraphList(t *testing.T) {
db.Init("../../data/stock.db")
InitAnalyzeSentiment()
NewMarketNewsApi().TelegraphList(30)
}
func TestProxy(t *testing.T) {
response, err := resty.New().
SetProxy("http://go-stock:778d4ff2-73f3-4d56-b3c3-d9a730a06ae3@stock.sparkmemory.top:8888").
R().
SetHeader("Host", "news-mediator.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
SetHeader("Referer", "https://cn.tradingview.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
//Get("https://api.ipify.org")
Get("https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang%3Azh-Hans&client=screener&streaming=false&user_prostatus=non_pro")
if err != nil {
logger.SugaredLogger.Error(err)
return
}
logger.SugaredLogger.Debugf("value: %s", response.String())
}
func TestNtfy(t *testing.T) {
//attach := "http://go-stock.sparkmemory.top/%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A/%E8%B5%84%E9%87%91%E6%B5%81%E5%90%91/2025-12/AI%EF%BC%9A%E5%B8%82%E5%9C%BA%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A-[2025.12.11_12.02.01].html"
//post, err := resty.New().SetBaseURL("https://go-stock.sparkmemory.top:16667").R().
// SetHeader("Filename", "AI市场分析报告-[2025.12.11_12.02.01].html").
// SetHeader("Icon", "https://go-stock.sparkmemory.top/appicon.png").
// SetHeader("Attach", attach).
// SetBody("AI市场分析报告-[2025.12.11_12.02.01]").Post("/go-stock")
//if err != nil {
// logger.SugaredLogger.Error(err)
// return
//}
//logger.SugaredLogger.Debugf("value: %s", post.String())
logger.SugaredLogger.Debugf("value: %s", filepath.Base("https://go-stock.sparkmemory.top/%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A/2025/12/11/%E5%B8%82%E5%9C%BA%E8%B5%84%E8%AE%AF[%E5%B8%82%E5%9C%BA%E8%B5%84%E8%AE%AF]-(2025-12-11)AI%E5%88%86%E6%9E%90%E7%BB%93%E6%9E%9C_20251211131509.html"))
logger.SugaredLogger.Debugf("value: %s", strutil.After("/data/go-stock-site/docs/分析报告/2025/12/09/市场资讯[市场资讯]-(2025-12-09)AI分析结果.md", "/data/go-stock-site/docs/"))
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/PuerkitoBio/goquery"
"github.com/chromedp/chromedp"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/mathutil"
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
@@ -135,17 +136,19 @@ type Tool struct {
Function ToolFunction `json:"function"`
}
type FunctionParameters struct {
Type string `json:"type"`
Properties map[string]any `json:"properties"`
Required []string `json:"required"`
Type string `json:"type"`
Properties map[string]any `json:"properties"`
Required []string `json:"required"`
AdditionalProperties bool `json:"additionalProperties"`
}
type ToolFunction struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters FunctionParameters `json:"parameters"`
Name string `json:"name"`
Strict bool `json:"strict"`
Description string `json:"description"`
Parameters *FunctionParameters `json:"parameters"`
}
func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysPromptId *int, tools []Tool) <-chan map[string]any {
func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysPromptId *int, tools []Tool, thinking bool) <-chan map[string]any {
ch := make(chan map[string]any, 512)
defer func() {
if err := recover(); err != nil {
@@ -185,12 +188,12 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "当前时间",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(6)
wg.Add(7)
go func() {
defer wg.Done()
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "")
@@ -200,8 +203,9 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "投资者互动数据",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": content,
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": content,
})
}()
@@ -226,8 +230,9 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "国内宏观经济数据",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "\n# 国内宏观经济数据:\n" + market.String(),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "\n# 国内宏观经济数据:\n" + market.String(),
})
}()
@@ -250,8 +255,9 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "当前市场/大盘/行业/指数行情",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "当前市场/大盘/行业/指数行情如下:\n" + market.String(),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "当前市场/大盘/行业/指数行情如下:\n" + market.String(),
})
}()
@@ -280,8 +286,9 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "近期重大事件/会议",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "近期重大事件/会议如下:\n" + md.String(),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "近期重大事件/会议如下:\n" + md.String(),
})
}()
@@ -300,8 +307,9 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": newsText.String(),
})
}()
@@ -318,27 +326,33 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "外媒全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": messageText.String(),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": messageText.String(),
})
}()
wg.Wait()
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.Infof("市场资讯 messageText=\n%s", messageText.String())
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "市场资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": messageText.String(),
})
go func() {
defer wg.Done()
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
messageText.WriteString("### " + telegraph.Content + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "市场资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": messageText.String(),
})
}()
wg.Wait()
if userQuestion == "" {
userQuestion = "请根据当前时间,总结和分析股票市场新闻中的投资机会"
}
@@ -346,12 +360,12 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"role": "user",
"content": userQuestion,
})
AskAiWithTools(o, errors.New(""), msg, ch, userQuestion, tools)
AskAiWithTools(o, errors.New(""), msg, ch, userQuestion, tools, thinking)
}()
return ch
}
func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int) <-chan map[string]any {
func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int, think bool) <-chan map[string]any {
ch := make(chan map[string]any, 512)
defer func() {
if err := recover(); err != nil {
@@ -395,7 +409,7 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(4)
wg.Add(5)
go func() {
defer wg.Done()
var market strings.Builder
@@ -470,6 +484,27 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
})
}()
go func() {
defer wg.Done()
markdownTable := ""
res := NewSearchStockApi("").HotStrategy()
bytes, _ := json.Marshal(res)
strategy := &models.HotStrategy{}
json.Unmarshal(bytes, strategy)
for _, data := range strategy.Data {
data.Chg = mathutil.RoundToFloat(100*data.Chg, 2)
}
markdownTable = util.MarkdownTableWithTitle("当前热门选股策略", strategy.Data)
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "当前热门选股策略",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": markdownTable,
})
}()
wg.Wait()
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
@@ -495,12 +530,12 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
"role": "user",
"content": userQuestion,
})
AskAi(o, errors.New(""), msg, ch, userQuestion)
AskAi(o, errors.New(""), msg, ch, userQuestion, think)
}()
return ch
}
func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptId *int, tools []Tool) <-chan map[string]any {
func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptId *int, tools []Tool, thinking bool) <-chan map[string]any {
ch := make(chan map[string]any, 512)
defer func() {
@@ -856,15 +891,15 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
//reqJson, _ := json.Marshal(msg)
//logger.SugaredLogger.Errorf("Stream request: \n%s\n", reqJson)
if tools != nil && len(tools) > 0 {
AskAiWithTools(o, err, msg, ch, question, tools)
AskAiWithTools(o, err, msg, ch, question, tools, thinking)
} else {
AskAi(o, err, msg, ch, question)
AskAi(o, err, msg, ch, question, thinking)
}
}()
return ch
}
func AskAi(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[string]any, question string) {
func AskAi(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[string]any, question string, think bool) {
client := resty.New()
client.SetBaseURL(strutil.Trim(o.BaseUrl))
client.SetHeader("Authorization", "Bearer "+o.ApiKey)
@@ -873,11 +908,18 @@ func AskAi(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[
if o.TimeOut <= 0 {
o.TimeOut = 300
}
thinking := "disabled"
if think {
thinking = "enabled"
}
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
resp, err := client.R().
SetDoNotParseResponse(true).
SetBody(map[string]interface{}{
"model": o.Model,
"model": o.Model,
"thinking": map[string]any{
"type": thinking,
},
"max_tokens": o.MaxTokens,
"temperature": o.Temperature,
"stream": true,
@@ -1005,7 +1047,10 @@ func AskAi(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[
}
}
func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[string]any, question string, tools []Tool) {
func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[string]any, question string, tools []Tool, thinkingMode bool) {
bytes, _ := json.Marshal(messages)
logger.SugaredLogger.Debugf("Stream request: \n%s\n", string(bytes))
client := resty.New()
client.SetBaseURL(strutil.Trim(o.BaseUrl))
client.SetHeader("Authorization", "Bearer "+o.ApiKey)
@@ -1014,11 +1059,20 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
if o.TimeOut <= 0 {
o.TimeOut = 300
}
thinking := "disabled"
if thinkingMode {
thinking = "enabled"
}
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
resp, err := client.R().
SetDoNotParseResponse(true).
SetBody(map[string]interface{}{
"model": o.Model,
"model": o.Model,
"thinking": map[string]any{
//"type": "disabled",
//"type": "enabled",
"type": thinking,
},
"max_tokens": o.MaxTokens,
"temperature": o.Temperature,
"stream": true,
@@ -1046,6 +1100,8 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
currentFuncName := ""
currentCallId := ""
var currentAIContent strings.Builder
var reasoningContentText strings.Builder
var contentText strings.Builder
for scanner.Scan() {
line := scanner.Text()
@@ -1081,6 +1137,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
if err := json.Unmarshal([]byte(data), &streamResponse); err == nil {
for _, choice := range streamResponse.Choices {
if content := choice.Delta.Content; content != "" {
contentText.WriteString(content)
//ch <- content
//logger.SugaredLogger.Infof("Content data: %s", content)
@@ -1108,6 +1165,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
if reasoningContent := choice.Delta.ReasoningContent; reasoningContent != "" {
reasoningContentText.WriteString(reasoningContent)
//ch <- reasoningContent
ch <- map[string]any{
"code": 1,
@@ -1154,7 +1212,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
content := "无符合条件的数据"
res := NewSearchStockApi(words).SearchStock(random.RandInt(5, 20))
res := NewSearchStockApi(words).SearchStock(random.RandInt(50, 120))
if convertor.ToString(res["code"]) == "100" {
resData := res["data"].(map[string]any)
result := resData["result"].(map[string]any)
@@ -1191,8 +1249,9 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
logger.SugaredLogger.Infof("SearchStockByIndicators:words:%s --> \n%s", words, content)
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"role": "assistant",
"content": currentAIContent.String(),
"reasoning_content": reasoningContentText.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
@@ -1210,6 +1269,9 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
"role": "tool",
"content": content,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
//ch <- map[string]any{
@@ -1264,8 +1326,9 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
logger.SugaredLogger.Infof("getKLineData=\n%s", markdownTable)
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"role": "assistant",
"content": currentAIContent.String(),
"reasoning_content": reasoningContentText.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
@@ -1284,6 +1347,8 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
"role": "tool",
"content": res,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
logger.SugaredLogger.Infof("GetStockKLine:stockCode:%s days:%s --> \n%s", stockCode, days, res)
@@ -1297,8 +1362,9 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
//}
} else {
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"role": "assistant",
"content": currentAIContent.String(),
"reasoning_content": reasoningContentText.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
@@ -1316,6 +1382,8 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
"role": "tool",
"content": "无数据可能股票代码错误。A股sh,sz开头;港股hk开头,美股us开头",
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
}
@@ -1344,8 +1412,9 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
content := util.MarkdownTableWithTitle("投资互动数据", datas.Results)
logger.SugaredLogger.Infof("InteractiveAnswer=\n%s", content)
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"role": "assistant",
"content": currentAIContent.String(),
"reasoning_content": reasoningContentText.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
@@ -1363,6 +1432,8 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
"role": "tool",
"content": content,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
//
@@ -1478,8 +1549,9 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
logger.SugaredLogger.Infof("stockCode:%s StockResearchReport:\n %s", stockCode, md.String())
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"role": "assistant",
"content": currentAIContent.String(),
"reasoning_content": reasoningContentText.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
@@ -1497,11 +1569,50 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
"role": "tool",
"content": md.String(),
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
if funcName == "HotStrategyTable" {
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具HotStrategyTable\n参数" + funcArguments + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
table := NewSearchStockApi("").HotStrategyTable()
logger.SugaredLogger.Infof("%s", table)
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"reasoning_content": reasoningContentText.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": table,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
}
AskAiWithTools(o, err, messages, ch, question, tools)
AskAiWithTools(o, err, messages, ch, question, tools, thinkingMode)
}
if choice.FinishReason == "stop" {
@@ -1549,7 +1660,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
newMessages = append(newMessages, message)
}
AskAi(o, err, newMessages, ch, question)
AskAi(o, err, newMessages, ch, question, thinkingMode)
} else {
ch <- map[string]any{
"code": 0,

View File

@@ -9,6 +9,7 @@ import (
func TestNewDeepSeekOpenAiConfig(t *testing.T) {
db.Init("../../data/stock.db")
InitAnalyzeSentiment()
var tools []Tool
tools = append(tools, Tool{
@@ -29,7 +30,7 @@ func TestNewDeepSeekOpenAiConfig(t *testing.T) {
},
})
ai := NewDeepSeekOpenAi(context.TODO(), 1)
ai := NewDeepSeekOpenAi(context.TODO(), 0)
//res := ai.NewChatStream("长电科技", "sh600584", "长电科技分析和总结", nil)
res := ai.NewSummaryStockNewsStreamWithTools("总结市场资讯,发掘潜力标的/行业/板块/概念,控制风险。调用工具函数验证", nil, tools)

View File

@@ -4,8 +4,11 @@ import (
"encoding/json"
"fmt"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"time"
"github.com/duke-git/lancet/v2/mathutil"
"github.com/go-resty/resty/v2"
)
@@ -21,6 +24,13 @@ func NewSearchStockApi(words string) *SearchStockApi {
return &SearchStockApi{words: words}
}
func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
qgqpBId := NewSettingsApi().Config.QgqpBId
if qgqpBId == "" {
return map[string]any{
"code": -1,
"message": "请先获取东财用户标识qgqp_b_id打开浏览器,访问东财网站按F12打开开发人员工具-》网络面板随便点开一个请求复制请求cookie中qgqp_b_id对应的值。保存到设置中的东财唯一标识输入框",
}
}
url := "https://np-tjxg-g.eastmoney.com/api/smart-tag/stock/v3/pw/search-code"
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "np-tjxg-g.eastmoney.com").
@@ -32,22 +42,25 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
"keyWord": "%s",
"pageSize": %d,
"pageNo": 1,
"fingerprint": "02efa8944b1f90fbfe050e1e695a480d",
"fingerprint": "%s",
"gids": [],
"matchWord": "",
"timestamp": "%d",
"shareToGuba": false,
"requestId": "RMd3Y76AJI98axPvdhdbKvbBDVwLlUK61761559950168",
"requestId": "",
"needCorrect": true,
"removedConditionIdList": [],
"xcId": "xc0d61279aad33008260",
"xcId": "",
"ownSelectAll": false,
"dxInfo": [],
"extraCondition": ""
}`, s.words, pageSize, time.Now().Unix())).Post(url)
}`, s.words, pageSize, qgqpBId, time.Now().Unix())).Post(url)
if err != nil {
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
return map[string]any{}
return map[string]any{
"code": -1,
"message": err.Error(),
}
}
respMap := map[string]any{}
json.Unmarshal(resp.Body(), &respMap)
@@ -71,3 +84,16 @@ func (s SearchStockApi) HotStrategy() map[string]any {
json.Unmarshal(resp.Body(), &respMap)
return respMap
}
func (s SearchStockApi) HotStrategyTable() string {
markdownTable := ""
res := s.HotStrategy()
bytes, _ := json.Marshal(res)
strategy := &models.HotStrategy{}
json.Unmarshal(bytes, strategy)
for _, data := range strategy.Data {
data.Chg = mathutil.RoundToFloat(100*data.Chg, 2)
}
markdownTable = util.MarkdownTableWithTitle("当前热门选股策略", strategy.Data)
return markdownTable
}

View File

@@ -4,14 +4,25 @@ import (
"encoding/json"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"math"
"testing"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/mathutil"
"github.com/duke-git/lancet/v2/random"
)
func TestSearchStock(t *testing.T) {
db.Init("../../data/stock.db")
e := convertor.ToString(math.Floor(float64(9*random.RandFloat(0, 1, 12) + 1)))
for i := 0; i < 19; i++ {
e += convertor.ToString(math.Floor(float64(9 * random.RandFloat(0, 1, 12))))
}
logger.SugaredLogger.Infof("e:%s", e)
res := NewSearchStockApi("量比大于2基本面优秀2025年三季报已披露主力连续3日净流入非创业板非科创板非ST").SearchStock(20)
logger.SugaredLogger.Infof("res:%+v", res)
data := res["data"].(map[string]any)
@@ -52,10 +63,20 @@ func TestSearchStock(t *testing.T) {
func TestSearchStockApi_HotStrategy(t *testing.T) {
db.Init("../../data/stock.db")
res := NewSearchStockApi("").HotStrategy()
logger.SugaredLogger.Infof("res:%+v", res)
dataList := res["data"].([]any)
for _, v := range dataList {
d := v.(map[string]any)
logger.SugaredLogger.Infof("v:%+v", d)
bytes, err := json.Marshal(res)
if err != nil {
return
}
strategy := &models.HotStrategy{}
json.Unmarshal(bytes, strategy)
for _, data := range strategy.Data {
data.Chg = mathutil.RoundToFloat(100*data.Chg, 2)
}
markdownTable := util.MarkdownTable(strategy.Data)
logger.SugaredLogger.Infof("res:%s", markdownTable)
//dataList := res["data"].([]any)
//for _, v := range dataList {
// d := v.(map[string]any)
// logger.SugaredLogger.Infof("v:%+v", d)
//}
}

View File

@@ -37,6 +37,7 @@ type Settings struct {
HttpProxy string `json:"httpProxy"`
HttpProxyEnabled bool `json:"httpProxyEnabled"`
EnableAgent bool `json:"enableAgent"`
QgqpBId string `json:"qgqpBId" gorm:"column:qgqp_b_id"`
}
func (receiver Settings) TableName() string {
@@ -108,6 +109,7 @@ func UpdateConfig(s *SettingConfig) string {
"http_proxy": s.HttpProxy,
"http_proxy_enabled": s.HttpProxyEnabled,
"enable_agent": s.EnableAgent,
"qgqp_b_id": s.QgqpBId,
})
//更新AiConfig

View File

@@ -1484,11 +1484,11 @@ func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) {
url := "https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=data&fs=%s&fields=f12,f13,f14,f1,f2,f4,f3,f152,f5,f6,f7,f15,f18,f16,f17,f10,f8,f9,f23,f100,f265&fid=f3&pn=%d&pz=%d&po=1&dect=1&wbp2u=|0|0|0|web&_=%d"
sprintfUrl := fmt.Sprintf(url, fs, page, pageSize, time.Now().UnixMilli())
logger.SugaredLogger.Infof("url:%s", sprintfUrl)
logger.SugaredLogger.Infof("page:%d url:%s", page, sprintfUrl)
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "push2.eastmoney.com").
SetHeader("Referer", "https://quote.eastmoney.com/center/gridlist.html").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0").
Get(sprintfUrl)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())

View File

@@ -121,10 +121,10 @@ func TestGetHKStockInfo(t *testing.T) {
//NewStockDataApi().GetSinaHKStockInfo()
//m:105,m:106,m:107 //美股
//m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股
//287 224 605
for i := 1; i <= 605; i++ {
NewStockDataApi().getDCStockInfo("us", i, 20)
time.Sleep(time.Duration(random.RandInt(1, 3)) * time.Second)
//274 224 605
for i := 197; i <= 274; i++ {
NewStockDataApi().getDCStockInfo("", i, 20)
time.Sleep(time.Duration(random.RandInt(2, 5)) * time.Second)
}
}
@@ -270,15 +270,16 @@ func TestName(t *testing.T) {
db.Init("../../data/stock.db")
stockBasics := &[]StockBasic{}
resty.New().R().
resty.New().SetProxy("").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())
}
logger.SugaredLogger.Infof("%+v", stockBasics)
//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

@@ -13,11 +13,14 @@ import (
"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
@@ -30,18 +33,19 @@ var (
"复苏": 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, "攀升": 1.5,
"大涨": 2.5, "飙升": 3.0, "井喷": 3.0, "爆发": 2.5, "暴涨": 3.0,
"大涨": 2.5, "飙升": 3.0, "井喷": 3.0, "暴涨": 3.0,
}
// 负面金融词汇及其权重
negativeFinanceWords = map[string]float64{
"跌": 1.0, "下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
"跌": 2.0, "下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 2.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,
"衰退": 2.0, "跌破": 2.0, "创新低": 3.0, "走弱": 2.5, "下挫": 2.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, "跌逾": 2.5,
"垃圾股": 2.0, "风险股": 2.0, "弱势": 2.5, "走低": 2.5, "缩量": 2.5,
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0, "跌超": 2.5, "跌逾": 2.5, "跌近": 3.0,
"被抓": 3.0, "被抓捕": 3.0, "回吐": 3.0, "转跌": 3.0,
}
// 否定词,用于反转情感极性
@@ -53,7 +57,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,
"大幅": 1.8, "急剧": 2.0, "轻微": 0.6, "小幅": 0.7, "逾": 1.8, "超": 1.8,
}
// 转折词,用于识别情感转折
@@ -69,41 +73,56 @@ var baseDict string
var zhDict string
func InitAnalyzeSentiment() {
logger.SugaredLogger.Infof("初始化词典库路径:%s", fileutil.CurrentPath())
//加载默认词典
err := seg.LoadDictEmbed(zhDict)
err = seg.LoadDictEmbed(baseDict)
defer func() {
if r := recover(); r != nil {
logger.SugaredLogger.Error(fmt.Sprintf("panic: %v", r))
}
}()
// 加载简体中文词典
//err := seg.LoadDict("zh_s")
//if err != nil {
// logger.SugaredLogger.Error(err.Error())
//}
err := seg.LoadDictEmbed(baseDict)
if err != nil {
logger.SugaredLogger.Error(err.Error())
} else {
logger.SugaredLogger.Info("加载默认词典成功")
}
seg.CalcToken()
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")
err := seg.AddToken(stock.Name, basefreq+100, "n")
if strutil.Trim(stock.BKName) != "" {
err = seg.AddToken(stock.BKName, 188888, "n")
err = seg.AddToken(stock.BKName, basefreq+100, "n")
}
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
}
}
logger.SugaredLogger.Info("加载股票名称词典成功")
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")
err := seg.AddToken(stock.Name, basefreq+100, "n")
if strutil.Trim(stock.BKName) != "" {
err = seg.AddToken(stock.BKName, 188888, "n")
err = seg.AddToken(stock.BKName, basefreq+100, "n")
}
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
}
}
logger.SugaredLogger.Info("加载港股名称词典成功")
//stockus := &[]models.StockInfoUS{}
//db.Dao.Model(&models.StockInfoUS{}).Where("trim(name) != ?", "").Find(stockus)
//for _, stock := range *stockus {
@@ -113,55 +132,104 @@ func InitAnalyzeSentiment() {
// }
//}
tags := &[]models.Tags{}
db.Dao.Model(&models.Tags{}).Find(tags)
db.Dao.Model(&models.Tags{}).Where("type = ?", "subject").Find(tags)
for _, tag := range *tags {
err := seg.AddToken(tag.Name, 188888, "n")
if tag.Name == "" {
continue
}
err := seg.AddToken(tag.Name, basefreq+100, "n")
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", tag.Name, err.Error())
} else {
logger.SugaredLogger.Infof("添加tags词典[%s]成功", tag.Name)
}
}
logger.SugaredLogger.Info("加载词典成功")
}
// WordFreqWithWeight 词频统计结果,包含权重信息
type WordFreqWithWeight struct {
Word string
Frequency int
Weight float64
logger.SugaredLogger.Info("加载tags词典成功")
seg.CalcToken()
//加载用户自定义词典 先判断用户词典是否存在
if fileutil.IsExist("data/dict/user.txt") {
lines, err := fileutil.ReadFileByLine("data/dict/user.txt")
if err != nil {
logger.SugaredLogger.Error(err.Error())
return
}
for _, line := range lines {
if len(line) == 0 || line[0] == '#' {
continue
}
k := strutil.SplitAndTrim(line, " ")
if len(k) == 0 {
continue
}
_, _, ok := seg.Find(k[0])
switch len(k) {
case 1:
if ok {
err = seg.ReAddToken(k[0], basefreq)
} else {
err = seg.AddToken(k[0], basefreq)
}
case 2:
freq, _ := convertor.ToFloat(k[1])
if ok {
err = seg.ReAddToken(k[0], freq)
} else {
err = seg.AddToken(k[0], freq)
}
case 3:
freq, _ := convertor.ToFloat(k[1])
if ok {
err = seg.ReAddToken(k[0], freq, k[2])
} else {
err = seg.AddToken(k[0], freq, k[2])
}
default:
logger.SugaredLogger.Errorf("用户词典格式错误:%s", line)
}
logger.SugaredLogger.Infof("添加用户词典[%s]成功", line)
}
if err != nil {
logger.SugaredLogger.Error(err.Error())
} else {
logger.SugaredLogger.Infof("加载用户词典成功")
}
} else {
logger.SugaredLogger.Info("用户词典不存在")
}
seg.CalcToken()
}
// getWordWeight 获取词汇权重
func getWordWeight(word string) float64 {
// 从分词器获取词汇权重
freq, pos, _ := seg.Find(word)
if pos == "n" {
freq, pos, ok := seg.Dictionary().Find([]byte(word))
if ok {
logger.SugaredLogger.Infof("获取%s的权重:%f,pos:%s,ok:%v", word, freq, pos, ok)
return freq
}
return 0
}
// SortByWeightAndFrequency 按权重和频次排序词频结果
func SortByWeightAndFrequency(frequencies map[string]WordFreqWithWeight) []WordFreqWithWeight {
func SortByWeightAndFrequency(frequencies map[string]models.WordFreqWithWeight) []models.WordFreqWithWeight {
// 将map转换为slice以便排序
freqSlice := make([]WordFreqWithWeight, 0, len(frequencies))
freqSlice := make([]models.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[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 {
func FilterAndSortWords(frequencies map[string]models.WordFreqWithWeight) []models.WordFreqWithWeight {
// 先过滤标点符号和分隔符
cleanFrequencies := FilterPunctuationAndSeparators(frequencies)
@@ -170,8 +238,8 @@ func FilterAndSortWords(frequencies map[string]WordFreqWithWeight) []WordFreqWit
return sortedFrequencies
}
func FilterPunctuationAndSeparators(frequencies map[string]WordFreqWithWeight) map[string]WordFreqWithWeight {
filteredWords := make(map[string]WordFreqWithWeight)
func FilterPunctuationAndSeparators(frequencies map[string]models.WordFreqWithWeight) map[string]models.WordFreqWithWeight {
filteredWords := make(map[string]models.WordFreqWithWeight)
for word, freqInfo := range frequencies {
// 过滤纯标点符号和分隔符
@@ -199,8 +267,8 @@ func isPunctuationOrSeparator(word string) bool {
}
// FilterWithRegex 使用正则表达式过滤标点和特殊字符
func FilterWithRegex(frequencies map[string]WordFreqWithWeight) map[string]WordFreqWithWeight {
filteredWords := make(map[string]WordFreqWithWeight)
func FilterWithRegex(frequencies map[string]models.WordFreqWithWeight) map[string]models.WordFreqWithWeight {
filteredWords := make(map[string]models.WordFreqWithWeight)
// 匹配标点符号、特殊字符的正则表达式
punctuationRegex := regexp.MustCompile(`^[[:punct:][:space:]]+$`)
@@ -215,9 +283,9 @@ func FilterWithRegex(frequencies map[string]WordFreqWithWeight) map[string]WordF
}
// countWordFrequencyWithWeight 统计词频并包含权重信息
func countWordFrequencyWithWeight(text string) map[string]WordFreqWithWeight {
func countWordFrequencyWithWeight(text string) map[string]models.WordFreqWithWeight {
words := splitWords(text)
freqMap := make(map[string]WordFreqWithWeight)
freqMap := make(map[string]models.WordFreqWithWeight)
// 统计词频
wordCount := make(map[string]int)
@@ -228,11 +296,12 @@ func countWordFrequencyWithWeight(text string) map[string]WordFreqWithWeight {
// 构建包含权重的结果
for word, frequency := range wordCount {
weight := getWordWeight(word)
if weight > 100 {
freqMap[word] = WordFreqWithWeight{
if weight >= basefreq {
freqMap[word] = models.WordFreqWithWeight{
Word: word,
Frequency: frequency,
Weight: weight,
Score: float64(frequency) * weight,
}
}
@@ -242,7 +311,7 @@ func countWordFrequencyWithWeight(text string) map[string]WordFreqWithWeight {
}
// AnalyzeSentimentWithFreqWeight 带权重词频统计的情感分析
func AnalyzeSentimentWithFreqWeight(text string) (SentimentResult, map[string]WordFreqWithWeight) {
func AnalyzeSentimentWithFreqWeight(text string) (models.SentimentResult, map[string]models.WordFreqWithWeight) {
// 原有情感分析逻辑
result := AnalyzeSentiment(text)
@@ -252,26 +321,14 @@ func AnalyzeSentimentWithFreqWeight(text string) (SentimentResult, map[string]Wo
return result, frequencies
}
// SentimentResult 情感分析结果类型
type SentimentResult struct {
Score float64 // 情感得分
Category SentimentType // 情感类别
PositiveCount int // 正面词数量
NegativeCount int // 负面词数量
Description string // 情感描述
}
// SentimentType 情感类型枚举
type SentimentType int
const (
Positive SentimentType = iota
Positive models.SentimentType = iota
Negative
Neutral
)
// AnalyzeSentiment 判断文本的情感
func AnalyzeSentiment(text string) SentimentResult {
func AnalyzeSentiment(text string) models.SentimentResult {
// 初始化得分
score := 0.0
positiveCount := 0
@@ -311,7 +368,7 @@ func AnalyzeSentiment(text string) SentimentResult {
}
// 确定情感类别
var category SentimentType
var category models.SentimentType
switch {
case score > 1.0:
category = Positive
@@ -321,7 +378,7 @@ func AnalyzeSentiment(text string) SentimentResult {
category = Neutral
}
return SentimentResult{
return models.SentimentResult{
Score: score,
Category: category,
PositiveCount: positiveCount,
@@ -434,7 +491,7 @@ func splitWords(text string) []string {
}
// GetSentimentDescription 获取情感类别的文本描述
func GetSentimentDescription(category SentimentType) string {
func GetSentimentDescription(category models.SentimentType) string {
switch category {
case Positive:
return "看涨"
@@ -479,3 +536,43 @@ func main() {
result.NegativeCount)
}
}
func SaveAnalyzeSentimentWithFreqWeight(frequencies []models.WordFreqWithWeight) {
sort.Slice(frequencies, func(i, j int) bool {
return frequencies[i].Frequency > frequencies[j].Frequency
})
wordAnalyzes := make([]models.WordAnalyze, 0)
for _, freq := range frequencies[:10] {
wordAnalyze := models.WordAnalyze{
WordFreqWithWeight: freq,
}
wordAnalyzes = append(wordAnalyzes, wordAnalyze)
}
db.Dao.CreateInBatches(wordAnalyzes, 1000)
}
func SaveStockSentimentAnalysis(result models.SentimentResult) {
db.Dao.Create(&models.SentimentResultAnalyze{
SentimentResult: result,
})
}
func NewsAnalyze(text string, save bool) (models.SentimentResult, []models.WordFreqWithWeight) {
if text == "" {
telegraphs := NewMarketNewsApi().GetNews24HoursList("", 1000*10)
messageText := strings.Builder{}
for _, telegraph := range *telegraphs {
messageText.WriteString(telegraph.Content + "\n")
}
text = messageText.String()
}
result, frequencies := AnalyzeSentimentWithFreqWeight(text)
// 过滤标点符号和分隔符
cleanFrequencies := FilterAndSortWords(frequencies)
if save {
go SaveAnalyzeSentimentWithFreqWeight(cleanFrequencies)
go SaveStockSentimentAnalysis(result)
}
return result, cleanFrequencies
}

View File

@@ -2,6 +2,7 @@ package data
import (
"fmt"
"go-stock/backend/db"
"go-stock/backend/logger"
"strings"
"testing"
@@ -16,17 +17,23 @@ import (
func TestAnalyzeSentiment(t *testing.T) {
news := NewMarketNewsApi().GetNewsList2("", random.RandInt(500, 1000))
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%,市场普遍认为其走势疲软"
//text = "【本轮巴以冲突已致加沙地带69733人死亡】财联社11月22日电当地时间22日下午以军对加沙城西部一辆汽车发动空袭已造成5人死亡多人受伤。自2023年10月7日巴以新一轮大规模冲突爆发以来以色列对加沙地带的袭击已造成69733人死亡、170863人受伤。"
//text = "【牛肉加工亏损 美国泰森公司关停缩减相关业务】财联社11月22日电受牛肉加工业务亏损影响当地时间21日美国泰森食品公司发布公告称将关闭位于内布拉斯加州的一家大型牛肉加工厂还计划缩小得克萨斯州一家牛肉加工厂的生产规模。根据泰森食品公司的公告被关闭的这家工厂位于内布拉斯加州列克星敦日均可宰杀并处理大约5000头牛约占全美日均牛肉屠宰数量的4.8%。与此同时公司还计划缩小得克萨斯州一家牛肉加工厂的生产规模这家工厂每天大约可屠宰6000头牛。据悉泰森此次业务调整影响两个工厂大约5000个工作岗位。《华尔街日报》报道称泰森是美国四大肉类加工公司中首家关闭主要牛肉加工厂的公司其最新财报显示2025财年牛肉加工是唯一亏损的业务部门调整后的营业亏损为4.26亿美元。"
// 分析情感
words := splitWords(text)
fmt.Println(strings.Join(words, " "))
result := AnalyzeSentiment(text)
result, frequencies := AnalyzeSentimentWithFreqWeight(text)
// 过滤标点符号和分隔符
cleanFrequencies := FilterPunctuationAndSeparators(frequencies)

View File

@@ -1,13 +1,14 @@
package db
import (
"log"
"os"
"time"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/plugin/dbresolver"
"log"
"os"
"time"
)
var Dao *gorm.DB
@@ -26,7 +27,7 @@ func Init(sqlitePath string) {
var openDb *gorm.DB
var err error
if sqlitePath == "" {
sqlitePath = "data/stock.db?cache=shared&mode=rwc&_journal_mode=WAL"
sqlitePath = "data/stock.db?cache_size=-524288&journal_mode=WAL"
}
openDb, err = gorm.Open(sqlite.Open(sqlitePath), &gorm.Config{
Logger: dbLogger,
@@ -48,8 +49,8 @@ func Init(sqlitePath string) {
if err != nil {
log.Fatalf("openDb.DB error is %s", err.Error())
}
dbCon.SetMaxIdleConns(10)
dbCon.SetMaxOpenConns(100)
dbCon.SetMaxIdleConns(4)
dbCon.SetMaxOpenConns(10)
dbCon.SetConnMaxLifetime(time.Hour)
Dao = openDb
}

View File

@@ -232,15 +232,16 @@ type Prompt struct {
type Telegraph struct {
gorm.Model
Time string `json:"time"`
DataTime *time.Time `json:"dataTime"`
Content string `json:"content"`
DataTime *time.Time `json:"dataTime" gorm:"index"`
Title string `json:"title" gorm:"index"`
Content string `json:"content" gorm:"index"`
SubjectTags []string `json:"subjects" gorm:"-:all"`
StocksTags []string `json:"stocks" gorm:"-:all"`
IsRed bool `json:"isRed"`
IsRed bool `json:"isRed" gorm:"index"`
Url string `json:"url"`
Source string `json:"source"`
Source string `json:"source" gorm:"index"`
TelegraphTags []TelegraphTags `json:"tags" gorm:"-:migration;foreignKey:TelegraphId"`
SentimentResult string `json:"sentimentResult"`
SentimentResult string `json:"sentimentResult" gorm:"index"`
}
type TelegraphTags struct {
gorm.Model
@@ -331,6 +332,22 @@ type TVNews struct {
LogoId string `json:"logo_id"`
} `json:"provider"`
}
type TVNewsDetail struct {
ShortDescription string `json:"shortDescription"`
Tags []struct {
Title string `json:"title"`
Args []struct {
Id string `json:"id"`
Value string `json:"value"`
} `json:"args"`
} `json:"tags"`
Copyright string `json:"copyright"`
Id string `json:"id"`
Title string `json:"title"`
Published int `json:"published"`
Urgency int `json:"urgency"`
StoryPath string `json:"storyPath"`
}
type XUEQIUHot struct {
Data struct {
@@ -702,3 +719,51 @@ type BKDict struct {
func (b BKDict) TableName() string {
return "bk_dict"
}
type WordAnalyze struct {
gorm.Model
DataTime *time.Time `json:"dataTime" gorm:"index;autoCreateTime"`
WordFreqWithWeight
}
// WordFreqWithWeight 词频统计结果,包含权重信息
type WordFreqWithWeight struct {
Word string
Frequency int
Weight float64
Score float64
}
// SentimentResult 情感分析结果类型
type SentimentResult struct {
Score float64 // 情感得分
Category SentimentType // 情感类别
PositiveCount int // 正面词数量
NegativeCount int // 负面词数量
Description string // 情感描述
}
type SentimentResultAnalyze struct {
gorm.Model
DataTime *time.Time `json:"dataTime" gorm:"index;autoCreateTime"`
SentimentResult
}
// SentimentType 情感类型枚举
type SentimentType int
type HotStrategy struct {
ChgEffect bool `json:"chgEffect"`
Code int `json:"code"`
Data []*HotStrategyData `json:"data"`
Message string `json:"message"`
}
type HotStrategyData struct {
Chg float64 `json:"chg" md:"平均涨幅(%)"`
Code string `json:"code" md:"-"`
HeatValue int `json:"heatValue" md:"热度值"`
Market string `json:"market" md:"-"`
Question string `json:"question" md:"选股策略"`
Rank int `json:"rank" md:"-"`
}

View File

@@ -33,14 +33,14 @@
"@vicons/ionicons5": "^0.13.0",
"@vicons/material": "^0.13.0",
"@vicons/tabler": "^0.13.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue": "^6.0.2",
"html-docx-js-typescript": "^0.1.5",
"less": "^4.4.0",
"naive-ui": "^2.41.0",
"naive-ui": "^2.43.2",
"unplugin-auto-import": "^20.0.0",
"unplugin-vue-components": "^29.0.0",
"vfonts": "^0.0.3",
"vite": "^6.3.5"
"vite": "7.2.4"
}
},
"node_modules/@babel/code-frame": {
@@ -480,6 +480,7 @@
"resolved": "https://registry.npmmirror.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.14.tgz",
"integrity": "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.11"
}
@@ -924,7 +925,8 @@
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
"dev": true
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@lezer/common": {
"version": "1.2.3",
@@ -1115,6 +1117,13 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.50",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",
"integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.43.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz",
@@ -1486,9 +1495,10 @@
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="
},
"node_modules/@types/lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw=="
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
@@ -1602,15 +1612,19 @@
"dev": true
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
"integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.2.tgz",
"integrity": "sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "1.0.0-beta.50"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
"vue": "^3.2.25"
}
},
@@ -2100,7 +2114,8 @@
"version": "0.2.4",
"resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz",
"integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/exsolve": {
"version": "1.0.7",
@@ -2110,11 +2125,14 @@
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -2603,46 +2621,36 @@
"license": "MIT"
},
"node_modules/naive-ui": {
"version": "2.41.0",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.41.0.tgz",
"integrity": "sha512-KnmLg+xPLwXV8QVR7ZZ69eCjvel7R5vru8+eFe4VoAJHEgqAJgVph6Zno9K2IVQRpSF3GBGea3tjavslOR4FAA==",
"version": "2.43.2",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.43.2.tgz",
"integrity": "sha512-YlLMnGrwGTOc+zMj90sG3ubaH5/7czsgLgGcjTLA981IUaz8r6t4WIujNt8r9PNr+dqv6XNEr0vxkARgPPjfBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@css-render/plugin-bem": "^0.15.14",
"@css-render/vue3-ssr": "^0.15.14",
"@types/katex": "^0.16.2",
"@types/lodash": "^4.14.198",
"@types/lodash-es": "^4.17.9",
"@types/lodash": "^4.17.20",
"@types/lodash-es": "^4.17.12",
"async-validator": "^4.2.5",
"css-render": "^0.15.14",
"csstype": "^3.1.3",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"evtd": "^0.2.4",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"seemly": "^0.3.8",
"seemly": "^0.3.10",
"treemate": "^0.3.11",
"vdirs": "^0.1.8",
"vooks": "^0.2.12",
"vueuc": "^0.4.63"
"vueuc": "^0.4.65"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/naive-ui/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -2930,7 +2938,8 @@
"version": "0.3.10",
"resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.10.tgz",
"integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/select": {
"version": "1.1.2",
@@ -3074,14 +3083,14 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"version": "0.2.15",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
@@ -3309,6 +3318,7 @@
"resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz",
"integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"evtd": "^0.2.2"
},
@@ -3323,24 +3333,24 @@
"dev": true
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"version": "7.2.4",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.2.4.tgz",
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
"tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -3349,14 +3359,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
"less": "*",
"less": "^4.0.0",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -3442,6 +3452,7 @@
"resolved": "https://registry.npmmirror.com/vooks/-/vooks-0.2.12.tgz",
"integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"evtd": "^0.2.2"
},
@@ -3493,10 +3504,11 @@
}
},
"node_modules/vueuc": {
"version": "0.4.64",
"resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.64.tgz",
"integrity": "sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA==",
"version": "0.4.65",
"resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.65.tgz",
"integrity": "sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@css-render/vue3-ssr": "^0.15.10",
"@juggle/resize-observer": "^3.3.1",

View File

@@ -34,14 +34,14 @@
"@vicons/ionicons5": "^0.13.0",
"@vicons/material": "^0.13.0",
"@vicons/tabler": "^0.13.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue": "^6.0.2",
"html-docx-js-typescript": "^0.1.5",
"less": "^4.4.0",
"naive-ui": "^2.41.0",
"naive-ui": "^2.43.2",
"unplugin-auto-import": "^20.0.0",
"unplugin-vue-components": "^29.0.0",
"vfonts": "^0.0.3",
"vite": "^6.3.5"
"vite": "7.2.4"
},
"keywords": [
"AI赋能股票分析",

View File

@@ -1 +1 @@
b0b9f944d9af9c00b6d48234793db58c
f4fb0059ba6044c039be717fcc2e40bc

View File

@@ -53,6 +53,7 @@ const containerRef = ref({})
const realtimeProfit = ref(0)
const telegraph = ref([])
const groupList = ref([])
const officialStatement= ref("")
const menuOptions = ref([
{
label: () =>
@@ -599,6 +600,7 @@ onBeforeMount(() => {
GetVersionInfo().then(result => {
if(result.officialStatement){
content.value = result.officialStatement+"\n\n"+content.value
officialStatement.value = result.officialStatement
}
})
@@ -669,7 +671,7 @@ onBeforeMount(() => {
})
onMounted(() => {
WindowSetTitle("go-stockAI赋能股票分析✨ 未经授权,禁止商业目的! [数据来源于网络,仅供参考;投资有风险,入市需谨慎]")
WindowSetTitle("go-stockAI赋能股票分析✨ "+officialStatement.value+" 未经授权,禁止商业目的! [数据来源于网络,仅供参考;投资有风险,入市需谨慎]")
contentStyle.value = "max-height: calc(92vh);overflow: hidden"
GetConfig().then((res) => {
if (res.enableNews) {

View File

@@ -1,9 +1,314 @@
<script setup>
import {AnalyzeSentimentWithFreqWeight,GlobalStockIndexes} from "../../wailsjs/go/main/App";
import * as echarts from "echarts";
import {onMounted,onUnmounted, ref} from "vue";
import _ from "lodash";
const { name,darkTheme,kDays ,chartHeight} = defineProps({
name: {
type: String,
default: ''
},
kDays: {
type: Number,
default: 14
},
chartHeight: {
type: Number,
default: 500
},
darkTheme: {
type: Boolean,
default: false
}
})
const common = ref([])
const america = ref([])
const europe = ref([])
const asia = ref([])
const mainIndex = ref([])
const chinaIndex = ref([])
const other = ref([])
const globalStockIndexes = ref(null)
const chartRef = ref(null);
const gaugeChartRef = ref(null);
const triggerAreas=ref(["main","extra","arrow"])
let handleChartInterval=null
let handleIndexInterval=null
onMounted(() => {
handleChart()
getIndex()
handleChartInterval=setInterval(function () {
handleChart()
}, 1000 * 60)
handleIndexInterval=setInterval(function () {
getIndex()
}, 1000 * 2)
})
onUnmounted(()=>{
clearInterval(handleChartInterval)
clearInterval(handleIndexInterval)
})
function getIndex() {
GlobalStockIndexes().then((res) => {
globalStockIndexes.value = res
common.value = res["common"]
america.value = res["america"]
europe.value = res["europe"]
asia.value = res["asia"]
other.value = res["other"]
mainIndex.value=asia.value.filter(function (item) {
return ['上海',"深圳","香港","台湾","北京","东京","首尔","纽约","纳斯达克"].includes(item.location)
}).concat(america.value.filter(function (item) {
return ['上海',"深圳","香港","台湾","北京","东京","首尔","纽约","纳斯达克"].includes(item.location)
}))
chinaIndex.value=asia.value.filter(function (item) {
return ['上海',"深圳","香港","台湾","北京"].includes(item.location)
})
})
}
function handleChart(){
const formatUtil = echarts.format;
AnalyzeSentimentWithFreqWeight("").then((res) => {
const treemapchart = echarts.init(chartRef.value);
const gaugeChart=echarts.init(gaugeChartRef.value);
let data = res['frequencies'].map(item => ({
name: item.Word,
// value: item.Frequency,
// value: item.Weight,
frequency: item.Frequency,
weight: item.Weight,
value: item.Score,
}));
let data2 = res['frequencies'].map(item => ({
name: item.Word,
value: item.Frequency,
// value: item.Weight,
frequency: item.Frequency,
weight: item.Weight,
//value: item.Score,
}));
let data3 = res['frequencies'].map(item => ({
name: item.Word,
//value: item.Frequency,
value: item.Weight,
frequency: item.Frequency,
weight: item.Weight,
//value: item.Score,
}));
let option = {
darkMode: darkTheme,
title: {
text:name,
left: 'center',
textStyle: {
color: darkTheme?'#ccc':'#456'
}
},
legend: {
show: false
},
toolbox: {
left: '20px',
tooltip:{
textStyle: {
color: darkTheme?'#ccc':'#456'
}
},
feature: {
saveAsImage: {title: '保存图片'},
restore: {
title: '默认',
},
myTool2: {
show: true,
title: '按权重',
icon:"path://M393.8816 148.1216a29.3376 29.3376 0 0 1-15.2576 38.0928c-43.776 17.152-81.92 43.8272-114.2784 76.2368A345.7536 345.7536 0 0 0 159.5392 512 352.8704 352.8704 0 0 0 512 864.4608a351.744 351.744 0 0 0 249.5488-102.912 353.536 353.536 0 0 0 76.2368-114.2784c5.6832-15.2576 22.8352-20.992 38.0928-15.2576 15.2576 5.7344 20.992 22.8864 15.2576 38.0928a421.2224 421.2224 0 0 1-89.6 133.376A412.6208 412.6208 0 0 1 512 921.6c-226.7136 0-409.6-182.8864-409.6-409.6 0-108.544 41.9328-211.456 120.0128-289.5872A421.2224 421.2224 0 0 1 355.84 132.864a29.3376 29.3376 0 0 1 38.0928 15.2576zM512 102.4c226.7136 0 409.6 182.8864 409.6 409.6 0 15.2576-13.312 28.5696-28.5696 28.5696H512A29.2864 29.2864 0 0 1 483.4304 512V130.9696c0-15.2576 13.312-28.5696 28.5696-28.5696z m28.5696 59.0336v321.9968h321.9968a350.976 350.976 0 0 0-321.9968-321.9968z",
onclick: function (){
treemapchart.setOption( {series:{
data: data3
}})
}
},
myTool1: {
show: true,
title: '按频次',
icon:"path://M895.466667 476.8l-87.424-87.424v-123.626667a49.770667 49.770667 0 0 0-49.770667-49.770666h-123.626667L547.2 128.533333a49.792 49.792 0 0 0-70.4 0l-87.424 87.424h-123.626667a49.770667 49.770667 0 0 0-49.770666 49.770667v123.626667L128.533333 476.8a49.792 49.792 0 0 0 0 70.4l87.424 87.424v123.626667a49.770667 49.770667 0 0 0 49.770667 49.770666h123.626667l87.424 87.424a49.792 49.792 0 0 0 70.4 0l87.424-87.424h123.626666a49.770667 49.770667 0 0 0 49.770667-49.770666v-123.626667l87.424-87.424a49.749333 49.749333 0 0 0 0.042667-70.4z m-137.216 137.194667v144.256h-144.256L512 860.266667l-101.994667-101.994667h-144.256v-144.256L163.733333 512l101.994667-101.994667v-144.256h144.256L512 163.733333l101.994667 101.994667h144.256v144.256L860.266667 512l-102.016 101.994667z M414.378667 514.730667l28.672 10.922666c-18.090667 47.445333-38.229333 92.16-60.757334 133.802667l-30.037333-13.653333a1042.133333 1042.133333 0 0 0 62.122667-131.072zM381.952 367.616L355.669333 384c25.258667 26.282667 45.056 50.176 60.074667 72.021333l25.6-17.749333c-13.994667-20.48-33.792-44.032-59.392-70.656zM537.258667 455.338667c-0.682667 43.690667-6.144 79.189333-16.725334 106.837333-14.336 32.768-44.373333 60.416-89.429333 82.944l21.162667 25.941333c52.224-26.624 85.333333-60.074667 99.328-100.693333 1.706667-5.12 3.413333-10.24 4.778666-15.36 21.504 45.738667 52.906667 83.968 93.866667 115.370667l21.504-24.917334c-51.2-34.474667-86.357333-81.237333-105.813333-140.288 1.706667-15.701333 2.730667-32.085333 2.730666-49.834666h-31.402666z M508.586667 434.858667h115.712c-6.826667 25.258667-15.018667 47.786667-24.917334 66.901333l31.744 8.874667a627.008 627.008 0 0 0 27.989334-85.674667v-21.162667H517.12c3.413333-14.336 6.144-29.354667 8.874667-45.738666l-32.426667-5.12c-7.850667 59.392-25.6 105.813333-52.906667 139.264l26.965334 19.114666c16.725333-19.114667 30.378667-44.373333 40.96-76.458666z",
onclick: function (){
treemapchart.setOption( {series:{
data: data2
}})
}
}
}
},
tooltip: {
formatter: function (info) {
var value = info.value.toFixed(2);
var frequency = info.data.frequency;
var weight = info.data.weight;
return [
'<div class="tooltip-title">' + info.name+ '</div>',
'热度: ' + formatUtil.addCommas(value) + '',
'<div class="tooltip-title">频次: ' + formatUtil.addCommas(frequency)+ '</div>',
'<div class="tooltip-title">权重: ' + formatUtil.addCommas(weight)+ '</div>',
].join('');
}
},
series: [
{
type: 'treemap',
breadcrumb:{show: false},
left: '0',
top: '40',
right: '0',
bottom: '0',
tooltip: {
show: true
},
data: data
}
]
};
treemapchart.setOption(option);
let option2 = {
darkMode: darkTheme,
series: [
{
type: 'gauge',
startAngle: 180,
endAngle: 0,
center: ['50%', '75%'],
radius: '90%',
min: -100,
max: 100,
splitNumber: 8,
axisLine: {
lineStyle: {
width: 6,
color: [
// [0.25, '#FF6E76'],
// [0.5, '#FDDD60'],
// [0.75, '#58D9F9'],
// [1, '#7CFFB2'],
[0.25, '#03fb6a'],
[0.5, '#58e1f9'],
[0.75, '#ef5922'],
[1, '#f11d29'],
]
}
},
pointer: {
icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
length: '12%',
width: 20,
offsetCenter: [0, '-60%'],
itemStyle: {
color: 'auto'
}
},
axisTick: {
length: 12,
lineStyle: {
color: 'auto',
width: 2
}
},
splitLine: {
length: 20,
lineStyle: {
color: 'auto',
width: 5
}
},
axisLabel: {
color: darkTheme?'#ccc':'#456',
fontSize: 20,
distance: -45,
rotate: 'tangential',
formatter: function (value) {
if (value ===100) {
return '极热';
} else if (value === 50) {
return '乐观';
} else if (value === 0) {
return '中性';
}else if (value === -50) {
return '谨慎';
} else if (value === -100) {
return '冰点';
}
return '';
}
},
title: {
offsetCenter: [0, '-10%'],
fontSize: 20
},
detail: {
fontSize: 30,
offsetCenter: [0, '-35%'],
valueAnimation: true,
formatter: function (value) {
return value.toFixed(2) + '';
},
color: 'inherit'
},
data: [
{
value: res.result.Score*0.2,
name: '市场情绪强弱'
}
]
}
]
};
gaugeChart.setOption(option2);
})
}
</script>
<template>
<n-collapse :trigger-areas="triggerAreas" :default-expanded-names="['1']" display-directive="show">
<n-collapse-item name="1" >
<template #header>
<n-flex>
<n-tag size="small" :bordered="false" v-for="(item, index) in mainIndex" :type="item.zdf>0?'error':'success'">
<n-flex>
<n-image :width="20" :src="item.img" />
<n-text style="font-size: 14px" :type="item.zdf>0?'error':'success'">{{item.name}}&nbsp;{{item.zxj}}</n-text>
<n-number-animation :precision="2" :from="0" :to="item.zdf" style="font-size: 14px"/>
<n-text style="margin-left: -12px;font-size: 14px" :type="item.zdf>0?'error':'success'">%</n-text>
</n-flex>
</n-tag>
</n-flex>
</template>
<template #header-extra>
主要股指
</template>
<n-grid :cols="24" :y-gap="0">
<n-gi span="6">
<div ref="gaugeChartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
</n-gi>
<n-gi span="18">
<div ref="chartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
</n-gi>
</n-grid>
</n-collapse-item>
</n-collapse>
</template>
<style scoped>

View File

@@ -125,7 +125,12 @@ function Search() {
// 计算并设置表格宽度
tableScrollX.value = calculateTableWidth(columns.value);
} else {
message.error(res.msg)
if(res.msg){
message.error(res.msg)
}
if(res.message){
message.error(res.message)
}
}
}).catch(err => {
message.error(err)

View File

@@ -13,7 +13,7 @@ import {
SaveAsMarkdown,
ShareAnalysis,
SummaryStockNews,
GetAiConfigs, AnalyzeSentimentWithFreqWeight
GetAiConfigs,
} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from "../../wailsjs/runtime";
import NewsList from "./newsList.vue";
@@ -45,6 +45,7 @@ const panelHeight = ref(window.innerHeight - 240)
const telegraphList = ref([])
const sinaNewsList = ref([])
const foreignNewsList = ref([])
const common = ref([])
const america = ref([])
const europe = ref([])
@@ -54,6 +55,7 @@ const globalStockIndexes = ref(null)
const summaryModal = ref(false)
const summaryBTN = ref(true)
const darkTheme = ref(false)
const httpProxyEnabled = ref(false)
const theme = computed(() => {
return darkTheme ? 'dark' : 'light'
})
@@ -76,6 +78,7 @@ const indexInterval = ref(null)
const indexIndustryRank = ref(null)
const stockCode= ref('')
const enableTools= ref(true)
const thinkingMode = ref(true)
const treemapRef = ref(null);
let treemapchart =null;
@@ -96,6 +99,7 @@ onBeforeMount(() => {
GetConfig().then(result => {
summaryBTN.value = result.openAiEnable
darkTheme.value = result.darkTheme
httpProxyEnabled.value = result.httpProxyEnabled
})
GetPromptTemplates("", "").then(res => {
promptTemplates.value = res
@@ -107,13 +111,15 @@ onBeforeMount(() => {
aiConfigs.value = res
aiConfigId.value = res[0].ID
})
GetTelegraphList("财联社电报").then((res) => {
telegraphList.value = res
})
GetTelegraphList("新浪财经").then((res) => {
sinaNewsList.value = res
})
GetTelegraphList("外媒").then((res) => {
foreignNewsList.value = res
})
getIndex();
industryRank();
indexInterval.value = setInterval(() => {
@@ -127,7 +133,6 @@ onBeforeMount(() => {
})
onMounted(() => {
Analyze() // 页面显示
})
@@ -155,7 +160,6 @@ EventsOn("newTelegraph", (data) => {
telegraphList.value.pop()
}
telegraphList.value.unshift(...data)
Analyze() // 页面显示
}
})
EventsOn("newSinaNews", (data) => {
@@ -164,7 +168,14 @@ EventsOn("newSinaNews", (data) => {
sinaNewsList.value.pop()
}
sinaNewsList.value.unshift(...data)
Analyze() // 页面显示
}
})
EventsOn("tradingViewNews", (data) => {
if (data!=null) {
for (let i = 0; i < data.length; i++) {
foreignNewsList.value.pop()
}
foreignNewsList.value.unshift(...data)
}
})
@@ -173,32 +184,6 @@ 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,
}))
}
]
};
treemapchart.setOption(option);
treemapchart.hideLoading()
})
}
function getAreaName(code) {
switch (code) {
case "america":
@@ -239,7 +224,7 @@ function reAiSummary() {
aiSummary.value = ""
summaryModal.value = true
loading.value = true
SummaryStockNews(question.value,aiConfigId.value, sysPromptId.value,enableTools.value)
SummaryStockNews(question.value,aiConfigId.value, sysPromptId.value,enableTools.value,thinkingMode.value)
}
function getAiSummary() {
@@ -273,9 +258,6 @@ function getAiSummary() {
function updateTab(name) {
summaryBTN.value = (name === "市场快讯");
nowTab.value = name
if (name === "市场快讯") {
Analyze()
}
}
EventsOn("summaryStockNews", async (msg) => {
@@ -356,6 +338,9 @@ function ReFlesh(source) {
if (source === "新浪财经") {
sinaNewsList.value = res
}
if (source === "外媒") {
foreignNewsList.value = res
}
})
}
</script>
@@ -366,16 +351,20 @@ function ReFlesh(source) {
<n-tab-pane name="市场快讯" tab="市场快讯">
<n-grid :cols="1" :y-gap="0">
<n-gi>
<div ref="treemapRef" style="width: 100%;height: 300px;" ></div>
<AnalyzeMartket :dark-theme="darkTheme" :chart-height="300" :kDays="1" :name="'最近24小时热词'" />
</n-gi>
<n-gi>
<n-grid :cols="2" :y-gap="0">
<n-grid :cols="httpProxyEnabled?3: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-gi v-if="httpProxyEnabled">
<news-list :newsList="foreignNewsList" :header-title="'外媒'" @update:message="ReFlesh"></news-list>
</n-gi>
</n-grid>
</n-gi>
</n-grid>
@@ -716,6 +705,16 @@ function ReFlesh(source) {
不启用AI函数工具调用
</template>
</n-switch>
<n-switch v-model:value="thinkingMode" :round="false">
<template #checked>
启用思考模式
</template>
<template #unchecked>
不启用思考模式
</template>
</n-switch>
<n-gradient-text type="error" style="margin-left: 10px">*AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens</n-gradient-text>
</n-flex>
<n-flex justify="space-between" style="margin-bottom: 10px">

View File

@@ -29,8 +29,24 @@ const updateMessage = () => {
</n-flex>
</template>
<n-list-item v-for="item in newsList">
<n-space justify="start">
<n-text justify="start" :bordered="false" :type="item.isRed?'error':'info'">
<n-space justify="start" >
<!-- <n-text justify="start" :bordered="false" :type="item.isRed?'error':'info'" style="overflow-wrap: break-word;">-->
<!-- <n-tag size="small" :type="item.isRed?'error':'warning'" :bordered="false"> {{ item.time }}</n-tag>-->
<!-- <n-text size="small" v-if="item.title" type="warning" :bordered="false">{{ item.title }}&nbsp;&nbsp;</n-text>-->
<!-- <n-text style="overflow-wrap: break-word;word-break: break-all; word-wrap: break-word;" :type="item.isRed?'error':'info'">{{ item.content }}</n-text>-->
<!-- </n-text>-->
<n-collapse v-if="item.title" arrow-placement="right">
<n-collapse-item :name="item.title">
<template #header>
<n-tag size="small" :type="item.isRed?'error':'warning'" :bordered="false"> {{ item.time }}</n-tag>
<n-text size="small" :type="item.isRed?'error':'info'" :bordered="false">{{ item.title }}</n-text>
</template>
<n-text justify="start" :bordered="false" :type="item.isRed?'error':'info'">
{{ item.content }}
</n-text>
</n-collapse-item>
</n-collapse>
<n-text v-if="!item.title" justify="start" :bordered="false" :type="item.isRed?'error':'info'">
<n-tag size="small" :type="item.isRed?'error':'warning'" :bordered="false"> {{ item.time }}</n-tag>
{{ item.content }}
</n-text>

View File

@@ -46,6 +46,7 @@ const formValue = ref({
httpProxy:"",
httpProxyEnabled:false,
enableAgent: false,
qgqpBId: '',
})
// 添加一个新的AI配置到列表
@@ -105,6 +106,7 @@ onMounted(() => {
formValue.value.httpProxy=res.httpProxy;
formValue.value.httpProxyEnabled=res.httpProxyEnabled;
formValue.value.enableAgent = res.enableAgent;
formValue.value.qgqpBId = res.qgqpBId;
})
@@ -145,6 +147,7 @@ function saveConfig() {
httpProxy:formValue.value.httpProxy,
httpProxyEnabled:formValue.value.httpProxyEnabled,
enableAgent: formValue.value.enableAgent,
qgqpBId: formValue.value.qgqpBId
})
if (config.sponsorCode) {
@@ -235,6 +238,7 @@ function importConfig() {
formValue.value.httpProxy=config.httpProxy
formValue.value.httpProxyEnabled=config.httpProxyEnabled
formValue.value.enableAgent = config.enableAgent
formValue.value.qgqpBId = config.qgqpBId
};
reader.readAsText(file);
};
@@ -328,6 +332,9 @@ function deletePrompt(ID) {
<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="qgqpBId">
<n-input type="text" placeholder="东财唯一标识" v-model:value="formValue.qgqpBId" clearable/>
</n-form-item-gi>
<n-form-item-gi :span="11" label="赞助码:" path="sponsorCode">
<n-input-group>

View File

@@ -110,6 +110,7 @@ const modalShow4 = ref(false)
const modalShow5 = ref(false)
const addBTN = ref(true)
const enableTools = ref(false)
const thinkingMode = ref(false)
const formModel = ref({
name: "",
code: "",
@@ -1580,7 +1581,7 @@ function aiReCheckStock(stock, stockCode) {
//
//message.info("sysPromptId:"+data.sysPromptId)
NewChatStream(stock, stockCode, data.question, data.aiConfigId, data.sysPromptId, enableTools.value)
NewChatStream(stock, stockCode, data.question, data.aiConfigId, data.sysPromptId, enableTools.value,thinkingMode.value)
}
function aiCheckStock(stock, stockCode) {
@@ -2353,6 +2354,14 @@ function searchStockReport(stockCode) {
不启用AI函数工具调用
</template>
</n-switch>
<n-switch v-model:value="thinkingMode" :round="false">
<template #checked>
启用思考模式
</template>
<template #unchecked>
不启用思考模式
</template>
</n-switch>
<n-gradient-text type="error" style="margin-left: 10px">
*AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens
</n-gradient-text>

View File

@@ -24,6 +24,10 @@ import EmbeddedUrl from "./EmbeddedUrl.vue";
<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="消息墙">
<embedded-url url="https://go-stock.sparkmemory.top:16667/go-stock" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="欢迎推荐更多有趣的财经网页" tab="欢迎推荐更多有趣的财经网页">

View File

@@ -12,7 +12,7 @@ export function AddPrompt(arg1:models.Prompt):Promise<string>;
export function AddStockGroup(arg1:number,arg2:string):Promise<string>;
export function AnalyzeSentiment(arg1:string):Promise<data.SentimentResult>;
export function AnalyzeSentiment(arg1:string):Promise<models.SentimentResult>;
export function AnalyzeSentimentWithFreqWeight(arg1:string):Promise<Record<string, any>>;
@@ -96,7 +96,7 @@ export function InvestCalendarTimeLine(arg1:string):Promise<Array<any>>;
export function LongTigerRank(arg1:string):Promise<any>;
export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:number,arg5:any,arg6:boolean):Promise<void>;
export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:number,arg5:any,arg6:boolean,arg7:boolean):Promise<void>;
export function NewsPush(arg1:any):Promise<void>;
@@ -136,7 +136,7 @@ export function StockNotice(arg1:string):Promise<Array<any>>;
export function StockResearchReport(arg1:string):Promise<Array<any>>;
export function SummaryStockNews(arg1:string,arg2:number,arg3:any,arg4:boolean):Promise<void>;
export function SummaryStockNews(arg1:string,arg2:number,arg3:any,arg4:boolean,arg5:boolean):Promise<void>;
export function UnFollow(arg1:string):Promise<string>;

View File

@@ -186,8 +186,8 @@ export function LongTigerRank(arg1) {
return window['go']['main']['App']['LongTigerRank'](arg1);
}
export function NewChatStream(arg1, arg2, arg3, arg4, arg5, arg6) {
return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4, arg5, arg6);
export function NewChatStream(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}
export function NewsPush(arg1) {
@@ -266,8 +266,8 @@ export function StockResearchReport(arg1) {
return window['go']['main']['App']['StockResearchReport'](arg1);
}
export function SummaryStockNews(arg1, arg2, arg3, arg4) {
return window['go']['main']['App']['SummaryStockNews'](arg1, arg2, arg3, arg4);
export function SummaryStockNews(arg1, arg2, arg3, arg4, arg5) {
return window['go']['main']['App']['SummaryStockNews'](arg1, arg2, arg3, arg4, arg5);
}
export function UnFollow(arg1) {

View File

@@ -342,26 +342,6 @@ export namespace data {
export class SentimentResult {
Score: number;
Category: number;
PositiveCount: number;
NegativeCount: number;
Description: string;
static createFrom(source: any = {}) {
return new SentimentResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Score = source["Score"];
this.Category = source["Category"];
this.PositiveCount = source["PositiveCount"];
this.NegativeCount = source["NegativeCount"];
this.Description = source["Description"];
}
}
export class SettingConfig {
ID: number;
// Go type: time
@@ -394,6 +374,7 @@ export namespace data {
httpProxy: string;
httpProxyEnabled: boolean;
enableAgent: boolean;
qgqpBId: string;
aiConfigs: AIConfig[];
static createFrom(source: any = {}) {
@@ -430,6 +411,7 @@ export namespace data {
this.httpProxy = source["httpProxy"];
this.httpProxyEnabled = source["httpProxyEnabled"];
this.enableAgent = source["enableAgent"];
this.qgqpBId = source["qgqpBId"];
this.aiConfigs = this.convertValues(source["aiConfigs"], AIConfig);
}
@@ -743,6 +725,26 @@ export namespace models {
this.type = source["type"];
}
}
export class SentimentResult {
Score: number;
Category: number;
PositiveCount: number;
NegativeCount: number;
Description: string;
static createFrom(source: any = {}) {
return new SentimentResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Score = source["Score"];
this.Category = source["Category"];
this.PositiveCount = source["PositiveCount"];
this.NegativeCount = source["NegativeCount"];
this.Description = source["Description"];
}
}
export class VersionInfo {
ID: number;
// Go type: time

View File

@@ -48,6 +48,10 @@ export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}

109
go.mod
View File

@@ -3,95 +3,95 @@ module go-stock
go 1.25.0
require (
github.com/PuerkitoBio/goquery v1.10.1
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
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250804092122-8845979a2228
github.com/PuerkitoBio/goquery v1.11.0
github.com/chromedp/chromedp v0.14.2
github.com/cloudwego/eino v0.7.9
github.com/cloudwego/eino-ext/components/model/ark v0.1.52
github.com/cloudwego/eino-ext/components/model/deepseek v0.1.0
github.com/cloudwego/eino-ext/components/model/openai v0.1.5
github.com/coocood/freecache v1.2.4
github.com/duke-git/lancet/v2 v2.3.4
github.com/duke-git/lancet/v2 v2.3.8
github.com/energye/systray v1.0.2
github.com/gen2brain/beeep v0.11.1
github.com/glebarez/sqlite v1.11.0
github.com/go-ego/gse v0.80.3
github.com/go-resty/resty/v2 v2.16.2
github.com/go-resty/resty/v2 v2.17.0
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/robertkrimen/otto v0.5.1
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.49.1
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.14.4
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.36.0
golang.org/x/text v0.26.0
github.com/samber/lo v1.52.0
github.com/stretchr/testify v1.11.1
github.com/tidwall/gjson v1.18.0
github.com/wailsapp/wails/v2 v2.11.0
go.uber.org/zap v1.27.1
golang.org/x/net v0.47.0
golang.org/x/sys v0.38.0
golang.org/x/text v0.31.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/gorm v1.25.12
gorm.io/plugin/dbresolver v1.5.3
gorm.io/gorm v1.31.1
gorm.io/plugin/dbresolver v1.6.2
gorm.io/plugin/soft_delete v1.2.1
)
require (
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bep/debounce v1.2.1 // indirect
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/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // 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
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.6 // indirect
github.com/cohesion-org/deepseek-go v1.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eino-contrib/jsonschema v1.0.3 // indirect
github.com/esiqveland/notify v0.13.3 // indirect
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/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e // 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
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/godbus/dbus/v5 v5.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/goph/emperror v0.17.2 // indirect
github.com/invopop/yaml v0.1.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/labstack/echo/v4 v4.13.4 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250723112853-3bce976e5ccc // indirect
github.com/meguminnnnnnnnn/go-openai v0.1.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nikolalohinski/gonja v1.5.3 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/ollama/ollama v0.6.5 // indirect
github.com/openai/openai-go v1.10.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/ollama/ollama v0.13.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -99,35 +99,36 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
github.com/sergeymakinen/go-ico v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vcaesar/cedar v0.20.2 // indirect
github.com/volcengine/volc-sdk-golang v1.0.23 // indirect
github.com/volcengine/volcengine-go-sdk v1.1.21 // indirect
github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/volcengine/volc-sdk-golang v1.0.229 // indirect
github.com/volcengine/volcengine-go-sdk v1.1.50 // indirect
github.com/wailsapp/go-webview2 v1.0.23 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yargevad/filepathx v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
modernc.org/libc v1.67.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.40.1 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.9.2 => C:\Users\spark\go\pkg\mod

933
go.sum

File diff suppressed because it is too large Load Diff

17
main.go
View File

@@ -58,6 +58,13 @@ var OFFICIAL_STATEMENT string
var BuildKey string
func main() {
defer func() {
if r := recover(); r != nil {
log.SugaredLogger.Error("panic: ", r)
log.SugaredLogger.Error("stack: ", string(debug.Stack()))
}
}()
checkDir("data")
db.Init("")
data.InitAnalyzeSentiment()
@@ -68,6 +75,10 @@ func main() {
// Sort: 0,
//})
log.SugaredLogger.Info("starting...")
log.SugaredLogger.Infof("version: %s commit: %s", Version, VersionCommit)
//log.SugaredLogger.Infof("build key: %s", BuildKey)
// Create an instance of the app structure
app := NewApp()
AppMenu := menu.NewMenu()
@@ -125,7 +136,7 @@ func main() {
// Create application with options
err = wails.Run(&options.App{
Title: "go-stockAI赋能股票分析✨",
Title: "go-stockAI赋能股票分析✨ " + OFFICIAL_STATEMENT,
Width: width * 4 / 5,
Height: 920,
MinWidth: minWidth,
@@ -150,7 +161,7 @@ func main() {
OnShutdown: app.shutdown,
WindowStartState: options.Normal,
SingleInstanceLock: &options.SingleInstanceLock{
UniqueId: "go-stock-dev",
UniqueId: "go-stock",
OnSecondInstanceLaunch: OnSecondInstanceLaunch,
},
Bind: []interface{}{
@@ -230,6 +241,8 @@ func AutoMigrate() {
db.Dao.AutoMigrate(&models.LongTigerRankData{})
db.Dao.AutoMigrate(&data.AIConfig{})
db.Dao.AutoMigrate(&models.BKDict{})
db.Dao.AutoMigrate(&models.WordAnalyze{})
db.Dao.AutoMigrate(&models.SentimentResultAnalyze{})
updateMultipleModel()
}