Compare commits

...

7 Commits

Author SHA1 Message Date
ArvinLovegood
1d1e437b47 feat(stock):添加股票资金流向和概念信息查询功能
- 新增 GetStockMoneyData 工具用于查询今日股票资金流入排名
- 新增 GetStockConceptInfo 工具用于获取股票所属概念详细信息
- 添加 StockMoneyDataResp、StockMoneyData、StockMoneyDataDiff 数据模型
- 添加 StockConceptInfoResp、StockConceptInfoResult、StockConceptInfo 数据模型
- 实现 GetStockMoneyData 方法从东方财富接口获取资金流向数据
- 实现 GetStockConceptInfo 方法通过股票代码查询概念题材信息
- 在 OpenAI API 中集成两个新工具的调用逻辑
- 添加相关单元测试验证功能正常工作
2026-01-16 19:32:04 +08:00
ArvinLovegood
eca2f8adee feat(app):添加新闻推送时间限制和代理配置支持
- 在app.go中添加时间差检查,仅当数据时间与当前时间差小于5分钟时才推送新闻
- 在openai_api.go中添加HTTP代理配置支持,根据设置启用代理连接
- 重构openai_api.go中的请求体构建逻辑,支持动态配置thinking参数
- 更新openai_api_test.go中的测试参数以匹配新功能
- 移除settings_api.go中的默认设置初始化逻辑以优化启动流程
2026-01-15 16:42:12 +08:00
ArvinLovegood
49d2109d60 fix(config):修正K线数据天数配置限制
- 将默认K线天数从120调整为60
- 更新前端表单验证最大值为60
- 修复后端配置默认值为60天
- 添加更详细的选股语言示例说明
2026-01-07 17:51:12 +08:00
ArvinLovegood
f9294fbffd feat(stock):添加热门股票排名功能
- 在选股自然语言描述中增加了近期趋势示例说明
- 新增HotStockTable工具函数用于获取热门股票数据
- 实现HotStockTable工具的调用逻辑和参数处理
- 集成雪球热门股票API接口
- 添加分页大小参数支持,默认为50条记录
- 实现Markdown表格格式的热门股票排名展示
2026-01-06 15:33:35 +08:00
ArvinLovegood
cc12a886c1 feat(news):扩展新闻标签匹配和添加热门股票数据功能
- 在新闻标签匹配中增加"外媒简讯"和"外媒"标签支持
- 过滤掉"rotating_light"和"loudspeaker"标签避免重复处理
- 优化GetSource函数支持多标签匹配判断新闻来源
- 添加THSHotStrategy数据模型定义热门策略数据结构
- 实现StrategySquare接口获取热门选股策略数据
- 在OpenAI接口中增加热门股票排名数据查询功能
- 为HotItem模型添加markdown标签支持表格显示
- 增加测试函数验证热门策略表格功能
2026-01-04 13:31:09 +08:00
ArvinLovegood
34ea989d47 refactor(data):优化数据API和测试用例
- 调整并发请求数量,移除市场指数行情的获取逻辑
- 注释掉多个市场指数行情查询的协程,减少不必要的数据请求
- 添加投资者互动数据和宏观经济数据的获取功能
- 更新24小时新闻列表的格式化方式,包含时间和内容标题
- 移除顶部新闻列表和外媒全球新闻资讯的获取逻辑
- 调整测试用例中的参数值,将历史数据天数从30改为5
- 修复股价数据检查逻辑,移除对空字符串的特殊处理
2026-01-01 22:34:19 +08:00
ArvinLovegood
aadff1c5eb ci(workflow):添加macOS平台的Intel和ARM64架构构建支持
- 添加 go-stock-darwin-intel 构建配置
- 添加 go-stock-darwin-arm64 构建配置
- 扩展 macOS 平台的架构覆盖范围
2026-01-01 16:18:29 +08:00
12 changed files with 624 additions and 194 deletions

View File

@@ -29,6 +29,12 @@ jobs:
- name: 'go-stock-darwin-universal'
platform: 'darwin/universal'
os: 'macos-latest'
- name: 'go-stock-darwin-intel'
platform: 'darwin'
os: 'macos-latest'
- name: 'go-stock-darwin-arm64'
platform: 'darwin/arm64'
os: 'macos-latest'
runs-on: ${{ matrix.build.os }}
steps:

73
app.go
View File

@@ -70,8 +70,10 @@ func AddTools(tools []data.Tool) []data.Tool {
"words": map[string]any{
"type": "string",
"description": "选股自然语言。" +
"例如查看技术指标:上海贝岭,macd,rsi,kdj,boll,5日均线,14日均线,30日均线,60日均线,成交量,OBV,EMA" +
"例如查看有潜力的成交量爆发股最近7日成交量量比大于3出现过一次非ST" +
"例如:查看技术指标:上海贝岭,macd,rsi,kdj,boll,5日均线,14日均线,30日均线,60日均线,成交量,OBV,EMA" +
"例如:查看近期趋势量比连续2天>1主力连续2日净流入且递增主力净额>3000万元行业股价在20日线上" +
"例如:当日成交量 ≥ 近 5 日平均成交量 ×1.5,收盘价 ≥ 20 日均线20 日均线 ≥ 60 日均线,当日涨幅 3%-7% 3日主力资金净流入累计≥5000 万元,当日换手率 5%-15%筹码集中度90% 筹码峰≤15%非创业板非科创板非ST非北交所行业" +
"例如:查看有潜力的成交量爆发股最近7日成交量量比大于3出现过一次非ST" +
"例1创新药,半导体;PE<30;净利润增长率>50%。 " +
"例2上证指数,科创50。 " +
"例3长电科技,上海贝岭。" +
@@ -187,6 +189,59 @@ func AddTools(tools []data.Tool) []data.Tool {
},
})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "HotStockTable",
Description: "当前热门股票排名",
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"pageSize": map[string]any{
"type": "string",
"description": "分页大小",
},
},
Required: []string{"pageSize"},
},
},
})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "GetStockMoneyData",
Description: "今日股票资金流入排名",
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"pageSize": map[string]any{
"type": "string",
"description": "分页大小",
},
},
Required: []string{"pageSize"},
},
},
})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "GetStockConceptInfo",
Description: "获取股票所属概念详细信息",
Parameters: &data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"code": map[string]any{
"type": "string",
"description": "股票代码,如601138.SH。注意 上海证券交易所股票以.SH结尾深圳证券交易所股票以.SZ结尾港股股票以.HK结尾北交所股票以.BJ结尾",
},
},
Required: []string{"code"},
},
},
})
return tools
}
@@ -431,7 +486,7 @@ func (a *App) syncNews() {
}
dataTime := time.UnixMilli(int64(news.Time * 1000))
if slice.ContainAny(news.Tags, []string{"外媒资讯", "财联社电报", "新浪财经"}) {
if slice.ContainAny(news.Tags, []string{"外媒资讯", "财联社电报", "新浪财经", "外媒简讯", "外媒"}) {
isRed := false
if slice.Contain(news.Tags, "rotating_light") {
isRed = true
@@ -453,8 +508,14 @@ func (a *App) syncNews() {
}
if cnt == 0 {
db.Dao.Model(telegraph).Create(&telegraph)
a.NewsPush(&[]models.Telegraph{*telegraph})
for _, subject := range news.Tags {
//计算时间差如果<5分钟则推送
if time.Now().Sub(dataTime) < 5*time.Minute {
a.NewsPush(&[]models.Telegraph{*telegraph})
}
tags := slice.Filter(news.Tags, func(index int, item string) bool {
return !(item == "rotating_light" || item == "loudspeaker")
})
for _, subject := range tags {
tag := &models.Tags{
Name: subject,
Type: "subject",
@@ -471,7 +532,7 @@ func (a *App) syncNews() {
}
func GetSource(tags []string) string {
if slices.Contains(tags, "外媒简讯") {
if slice.ContainAny(tags, []string{"外媒简讯", "外媒资讯", "外媒"}) {
return "外媒"
}
if slices.Contains(tags, "财联社电报") {

View File

@@ -142,6 +142,8 @@ func TestXUEQIUHotStock(t *testing.T) {
logger.SugaredLogger.Debugf("value: %+v", a)
}
md := util.MarkdownTableWithTitle("当前热门股票排名", res)
logger.SugaredLogger.Debugf(md)
}
func TestHotEvent(t *testing.T) {
@@ -235,7 +237,7 @@ func TestReutersNew(t *testing.T) {
func TestInteractiveAnswer(t *testing.T) {
db.Init("../../data/stock.db")
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "")
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "立讯精密")
logger.SugaredLogger.Debugf("PageSize:%d", datas.PageSize)
md := util.MarkdownTableWithTitle("投资互动", datas.Results)
logger.SugaredLogger.Debugf(md)

View File

@@ -67,7 +67,7 @@ func NewDeepSeekOpenAi(ctx context.Context, aiConfigId int) *OpenAi {
settingConfig.CrawlTimeOut = 60
}
if settingConfig.KDays < 30 {
settingConfig.KDays = 120
settingConfig.KDays = 60
}
}
o := &OpenAi{
@@ -193,7 +193,22 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(5)
wg.Add(4)
//go func() {
// defer wg.Done()
// res := NewMarketNewsApi().XUEQIUHotStock(50, "10")
// md := util.MarkdownTableWithTitle("当前热门股票排名", res)
// msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": "当前热门股票排名数据",
// })
// msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "reasoning_content": "使用工具查询",
// "content": md,
// })
//}()
go func() {
defer wg.Done()
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "")
@@ -236,30 +251,30 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
})
}()
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(GetZSInfo("上证指数", "sh000001", 30) + "\n")
market.WriteString(GetZSInfo("深证成指", "sz399001", 30) + "\n")
market.WriteString(GetZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(GetZSInfo("科创50", "sh000688", 30) + "\n")
market.WriteString(GetZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(GetZSInfo("中证银行", "sz399986", 30) + "\n")
market.WriteString(GetZSInfo("科创芯片", "sh000685", 30) + "\n")
market.WriteString(GetZSInfo("上证医药", "sh000037", 30) + "\n")
market.WriteString(GetZSInfo("证券龙头", "sz399437", 30) + "\n")
market.WriteString(GetZSInfo("中证白酒", "sz399997", 30) + "\n")
//logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "当前市场/大盘/行业/指数行情",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "当前市场/大盘/行业/指数行情如下:\n" + market.String(),
})
}()
//go func() {
// defer wg.Done()
// var market strings.Builder
// market.WriteString(GetZSInfo("上证指数", "sh000001", 5) + "\n")
// market.WriteString(GetZSInfo("深证成指", "sz399001", 5) + "\n")
// market.WriteString(GetZSInfo("创业板指数", "sz399006", 5) + "\n")
// market.WriteString(GetZSInfo("科创50", "sh000688", 5) + "\n")
// market.WriteString(GetZSInfo("沪深300指数", "sh000300", 5) + "\n")
// market.WriteString(GetZSInfo("中证银行", "sz399986", 5) + "\n")
// //market.WriteString(GetZSInfo("科创芯片", "sh000685", 30) + "\n")
// //market.WriteString(GetZSInfo("上证医药", "sh000037", 30) + "\n")
// //market.WriteString(GetZSInfo("证券龙头", "sz399437", 30) + "\n")
// //market.WriteString(GetZSInfo("中证白酒", "sz399997", 30) + "\n")
// //logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
// msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": "当前市场/大盘/行业/指数行情",
// })
// msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "reasoning_content": "使用工具查询",
// "content": "当前市场/大盘/行业/指数行情如下:\n" + market.String(),
// })
//}()
go func() {
defer wg.Done()
@@ -409,30 +424,30 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(4)
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(GetZSInfo("上证指数", "sh000001", 30) + "\n")
market.WriteString(GetZSInfo("深证成指", "sz399001", 30) + "\n")
market.WriteString(GetZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(GetZSInfo("科创50", "sh000688", 30) + "\n")
market.WriteString(GetZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(GetZSInfo("中证银行", "sz399986", 30) + "\n")
market.WriteString(GetZSInfo("科创芯片", "sh000685", 30) + "\n")
market.WriteString(GetZSInfo("上证医药", "sh000037", 30) + "\n")
market.WriteString(GetZSInfo("证券龙头", "sz399437", 30) + "\n")
market.WriteString(GetZSInfo("中证白酒", "sz399997", 30) + "\n")
//logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "当前市场指数行情",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "当前市场指数行情情况如下:\n" + market.String(),
})
}()
wg.Add(3)
//go func() {
// defer wg.Done()
// var market strings.Builder
// market.WriteString(GetZSInfo("上证指数", "sh000001", 5) + "\n")
// market.WriteString(GetZSInfo("深证成指", "sz399001", 5) + "\n")
// market.WriteString(GetZSInfo("创业板指数", "sz399006", 5) + "\n")
// market.WriteString(GetZSInfo("科创50", "sh000688", 5) + "\n")
// market.WriteString(GetZSInfo("沪深300指数", "sh000300", 5) + "\n")
// market.WriteString(GetZSInfo("中证银行", "sz399986", 5) + "\n")
// //market.WriteString(GetZSInfo("科创芯片", "sh000685", 30) + "\n")
// //market.WriteString(GetZSInfo("上证医药", "sh000037", 30) + "\n")
// //market.WriteString(GetZSInfo("证券龙头", "sz399437", 30) + "\n")
// //market.WriteString(GetZSInfo("中证白酒", "sz399997", 30) + "\n")
// //logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
// msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": "当前市场指数行情",
// })
// msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "content": "当前市场指数行情情况如下:\n" + market.String(),
// })
//}()
go func() {
defer wg.Done()
@@ -655,21 +670,103 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(GetZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(GetZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(GetZSInfo("沪深300指数", "sh000300", 30) + "\n")
//logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, stock)
content := util.MarkdownTableWithTitle("当前最新投资者互动数据", datas.Results)
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "市场指数",
"content": "投资者互动数据",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "市场指数情况如下:\n" + market.String(),
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": content,
})
}()
go func() {
defer wg.Done()
var market strings.Builder
res := NewMarketNewsApi().GetGDP()
md := util.MarkdownTableWithTitle("国内生产总值(GDP)", res.GDPResult.Data)
market.WriteString(md)
res2 := NewMarketNewsApi().GetCPI()
md2 := util.MarkdownTableWithTitle("居民消费价格指数(CPI)", res2.CPIResult.Data)
market.WriteString(md2)
res3 := NewMarketNewsApi().GetPPI()
md3 := util.MarkdownTableWithTitle("工业品出厂价格指数(PPI)", res3.PPIResult.Data)
market.WriteString(md3)
res4 := NewMarketNewsApi().GetPMI()
md4 := util.MarkdownTableWithTitle("采购经理人指数(PMI)", res4.PMIResult.Data)
market.WriteString(md4)
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "国内宏观经济数据",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "\n# 国内宏观经济数据:\n" + market.String(),
})
}()
//go func() {
// defer wg.Done()
// var market strings.Builder
// market.WriteString(GetZSInfo("上证指数", "sh000001", 5) + "\n")
// market.WriteString(GetZSInfo("深证成指", "sz399001", 5) + "\n")
// market.WriteString(GetZSInfo("创业板指数", "sz399006", 5) + "\n")
// market.WriteString(GetZSInfo("科创50", "sh000688", 5) + "\n")
// market.WriteString(GetZSInfo("沪深300指数", "sh000300", 5) + "\n")
// market.WriteString(GetZSInfo("中证银行", "sz399986", 5) + "\n")
// //market.WriteString(GetZSInfo("科创芯片", "sh000685", 30) + "\n")
// //market.WriteString(GetZSInfo("上证医药", "sh000037", 30) + "\n")
// //market.WriteString(GetZSInfo("证券龙头", "sz399437", 30) + "\n")
// //market.WriteString(GetZSInfo("中证白酒", "sz399997", 30) + "\n")
// //logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
// msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": "当前市场/大盘/行业/指数行情",
// })
// msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "reasoning_content": "使用工具查询",
// "content": "当前市场/大盘/行业/指数行情如下:\n" + market.String(),
// })
//}()
go func() {
defer wg.Done()
md := strings.Builder{}
res := NewMarketNewsApi().ClsCalendar()
for _, a := range res {
bytes, err := json.Marshal(a)
if err != nil {
continue
}
//logger.SugaredLogger.Debugf("value: %+v", string(bytes))
date := gjson.Get(string(bytes), "calendar_day")
md.WriteString("\n### 事件/会议日期:" + date.String())
list := gjson.Get(string(bytes), "items")
//logger.SugaredLogger.Debugf("value: %+v,list: %+v", date.String(), list)
list.ForEach(func(key, value gjson.Result) bool {
logger.SugaredLogger.Debugf("key: %+v,value: %+v", key.String(), gjson.Get(value.String(), "title"))
md.WriteString("\n- " + gjson.Get(value.String(), "title").String())
return true
})
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "近期重大事件/会议",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"reasoning_content": "使用工具查询",
"content": "近期重大事件/会议如下:\n" + md.String(),
})
}()
go func() {
defer wg.Done()
//endDate := time.Now().Format("20060102")
@@ -787,7 +884,7 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
go func() {
defer wg.Done()
messages := GetTelegraphList(o.CrawlTimeOut)
messages := NewMarketNewsApi().GetNews24HoursList("", random.RandInt(200, 1000))
if messages == nil || len(*messages) == 0 {
logger.SugaredLogger.Error("获取市场资讯失败")
//ch <- "***❗获取市场资讯失败,分析结果可能不准确***<hr>"
@@ -795,8 +892,9 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
return
}
var messageText strings.Builder
for _, message := range *messages {
messageText.WriteString(message + "\n")
for _, telegraph := range *messages {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
messageText.WriteString("### " + telegraph.Content + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
@@ -806,26 +904,6 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
"role": "assistant",
"content": messageText.String(),
})
messages = GetTopNewsList(o.CrawlTimeOut)
if messages == nil || len(*messages) == 0 {
logger.SugaredLogger.Error("获取新闻资讯失败")
//ch <- "***❗获取新闻资讯失败,分析结果可能不准确***<hr>"
//go runtime.EventsEmit(o.ctx, "warnMsg", "❗获取新闻资讯失败,分析结果可能不准确")
return
}
var newsText strings.Builder
for _, message := range *messages {
newsText.WriteString(message + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
}()
//go func() {
@@ -867,54 +945,8 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
})
}()
go func() {
defer wg.Done()
if checkIsIndexBasic(stock) {
return
}
//messages := SearchGuShiTongStockInfo(stockCode, o.CrawlTimeOut)
//if messages == nil || len(*messages) == 0 {
// logger.SugaredLogger.Error("获取股势通资讯失败")
// //ch <- "***❗获取股势通资讯失败,分析结果可能不准确***<hr>"
// //go runtime.EventsEmit(o.ctx, "warnMsg", "❗获取股势通资讯失败,分析结果可能不准确")
// return
//}
//var newsText strings.Builder
//for _, message := range *messages {
// newsText.WriteString(message + "\n")
//}
//msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": stock + "相关新闻资讯",
//})
//msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "content": newsText.String(),
//})
}()
go func() {
defer wg.Done()
resp := NewMarketNewsApi().TradingViewNews()
var newsText strings.Builder
for _, a := range *resp {
logger.SugaredLogger.Debugf("value: %s", a.Title)
newsText.WriteString(a.Title + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "外媒全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
}()
wg.Wait()
msg = append(msg, map[string]interface{}{
"role": "user",
"content": question,
@@ -945,18 +977,28 @@ func AskAi(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[
thinking = "enabled"
}
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
bodyMap := map[string]interface{}{
"model": o.Model,
"max_tokens": o.MaxTokens,
"temperature": o.Temperature,
"stream": true,
"messages": messages,
}
if think {
bodyMap["thinking"] = map[string]any{
//"type": "disabled",
//"type": "enabled",
"type": thinking,
}
}
resp, err := client.R().
SetDoNotParseResponse(true).
SetBody(map[string]interface{}{
"model": o.Model,
"thinking": map[string]any{
"type": thinking,
},
"max_tokens": o.MaxTokens,
"temperature": o.Temperature,
"stream": true,
"messages": messages,
}).
SetBody(bodyMap).
Post("/chat/completions")
body := resp.RawBody()
@@ -1096,21 +1138,29 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
thinking = "enabled"
}
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
bodyMap := map[string]interface{}{
"model": o.Model,
"max_tokens": o.MaxTokens,
"temperature": o.Temperature,
"stream": true,
"messages": messages,
"tools": tools,
}
if thinkingMode {
bodyMap["thinking"] = map[string]any{
//"type": "disabled",
//"type": "enabled",
"type": thinking,
}
}
resp, err := client.R().
SetDoNotParseResponse(true).
SetBody(map[string]interface{}{
"model": o.Model,
"thinking": map[string]any{
//"type": "disabled",
//"type": "enabled",
"type": thinking,
},
"max_tokens": o.MaxTokens,
"temperature": o.Temperature,
"stream": true,
"messages": messages,
"tools": tools,
}).
SetBody(bodyMap).
Post("/chat/completions")
body := resp.RawBody()
@@ -1643,6 +1693,128 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
})
}
if funcName == "HotStockTable" {
pageSize := gjson.Get(funcArguments, "pageSize").String()
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具HotStockTable\n参数" + funcArguments + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
pageSizeNum, err := convertor.ToInt(pageSize)
if err != nil {
pageSizeNum = 50
}
res := NewMarketNewsApi().XUEQIUHotStock(int(pageSizeNum), "10")
md := util.MarkdownTableWithTitle("当前热门股票排名", res)
logger.SugaredLogger.Infof("pageSize:%s HotStockTable:\n %s", pageSize, md)
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": md,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
if funcName == "GetStockMoneyData" {
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具GetStockMoneyData\n参数" + funcArguments + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
res := NewStockDataApi().GetStockMoneyData()
md := util.MarkdownTableWithTitle("今日个股资金流向Top50", res.Data.Diff)
logger.SugaredLogger.Infof("%s", md)
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": md,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
if funcName == "GetStockConceptInfo" {
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具GetStockConceptInfo\n参数" + funcArguments + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
code := gjson.Get(funcArguments, "code").String()
res := NewStockDataApi().GetStockConceptInfo(code)
md := util.MarkdownTableWithTitle(code+" 股票所属概念详细信息", res.Result.Data)
logger.SugaredLogger.Infof("%s", md)
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": md,
"tool_call_id": currentCallId,
//"reasoning_content": reasoningContentText.String(),
//"tool_calls": choice.Delta.ToolCalls,
})
}
}
AskAiWithTools(o, err, messages, ch, question, tools, thinkingMode)
}

View File

@@ -30,9 +30,9 @@ func TestNewDeepSeekOpenAiConfig(t *testing.T) {
},
})
ai := NewDeepSeekOpenAi(context.TODO(), 0)
ai := NewDeepSeekOpenAi(context.TODO(), 11)
//res := ai.NewChatStream("长电科技", "sh600584", "长电科技分析和总结", nil)
res := ai.NewSummaryStockNewsStreamWithTools("总结市场资讯,发掘潜力标的/行业/板块/概念,控制风险。调用工具函数验证", nil, tools, true)
res := ai.NewSummaryStockNewsStreamWithTools("总结市场资讯,发掘潜力标的/行业/板块/概念,控制风险。调用工具函数验证", nil, tools, false)
for {
select {
@@ -65,6 +65,6 @@ func TestSearchGuShiTongStockInfo(t *testing.T) {
func TestGetZSInfo(t *testing.T) {
db.Init("../../data/stock.db")
GetZSInfo("中证银行", "sz399986", 30)
GetZSInfo("上海贝岭", "sh600171", 30)
GetZSInfo("中证银行", "sz399986", 5)
GetZSInfo("上海贝岭", "sh600171", 5)
}

View File

@@ -97,3 +97,22 @@ func (s SearchStockApi) HotStrategyTable() string {
markdownTable = util.MarkdownTableWithTitle("当前热门选股策略", strategy.Data)
return markdownTable
}
func (s SearchStockApi) StrategySquare() map[string]any {
//https://backtest.10jqka.com.cn/strategysquare/list?order=desc&page=1&pageNum=10&sortType=hot&keyword=
url := "https://backtest.10jqka.com.cn/strategysquare/list?order=desc&page=1&pageNum=10&sortType=hot&keyword="
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "backtest.10jqka.com.cn").
SetHeader("Origin", "https://backtest.10jqka.com.cn").
SetHeader("Referer", "https://backtest.10jqka.com.cn/strategysquare/list").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("StrategySquare-err:%+v", err)
return map[string]any{}
}
respMap := map[string]any{}
json.Unmarshal(resp.Body(), &respMap)
logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
return respMap
}

View File

@@ -80,3 +80,8 @@ func TestSearchStockApi_HotStrategy(t *testing.T) {
// logger.SugaredLogger.Infof("v:%+v", d)
//}
}
func TestSearchStockApi_HotStrategyTable(t *testing.T) {
db.Init("../../data/stock.db")
res := NewSearchStockApi("").StrategySquare()
logger.SugaredLogger.Infof("res:%+v", res)
}

View File

@@ -198,12 +198,6 @@ func GetSettingConfig() *SettingConfig {
aiConfigs := make([]*AIConfig, 0)
// 处理数据库查询可能返回的空结果
result := db.Dao.Model(&Settings{}).First(settings)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 初始化默认设置并保存到数据库
settings = &Settings{OpenAiEnable: false, CrawlTimeOut: 60}
db.Dao.Create(settings)
}
if settings.OpenAiEnable {
// 处理AI配置查询可能出现的错误
result = db.Dao.Model(&AIConfig{}).Find(&aiConfigs)
@@ -220,7 +214,7 @@ func GetSettingConfig() *SettingConfig {
settings.CrawlTimeOut = 60
}
if settings.KDays < 30 {
settings.KDays = 120
settings.KDays = 60
}
}
if settings.BrowserPath == "" {

View File

@@ -14,6 +14,7 @@ import (
"go-stock/backend/models"
"io"
"io/ioutil"
url2 "net/url"
"strings"
"time"
@@ -1203,7 +1204,7 @@ func GetZSInfo(name, stockCode string, crawlTimeOut int64) string {
price := strutil.RemoveWhiteSpace(document.Find("div#price").First().Text(), false)
hqTime := strutil.RemoveWhiteSpace(document.Find("div#hqTime").First().Text(), false)
if strutil.ContainsAny(price, []string{"-", "--", ""}) {
if strutil.ContainsAny(price, []string{"-", "--"}) {
return "暂无数据"
}
@@ -1750,6 +1751,65 @@ func (receiver StockDataApi) GetStockHistoryMoneyData() {
}
// GetStockMoneyData 获取个股资金流数据
func (receiver StockDataApi) GetStockMoneyData() models.StockMoneyDataResp {
var resData models.StockMoneyDataResp
url := "https://push2.eastmoney.com/api/qt/clist/get?cb=data&fid=f62&po=1&pz=50&pn=1&np=1&fltt=2&invt=2&ut=8dec03ba335b81bf4ebdf7b29ec27d15&fs=m:0+t:6+f:!2,m:0+t:13+f:!2,m:0+t:80+f:!2,m:1+t:2+f:!2,m:1+t:23+f:!2,m:0+t:7+f:!2,m:1+t:3+f:!2&fields=f12,f14,f2,f3,f62,f184,f66,f69,f72,f75,f78,f81,f84,f87,f204,f205,f124,f1,f13,f100,f265"
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "push2.eastmoney.com").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
}
body := string(resp.Body())
logger.SugaredLogger.Infof("resp:%s", body)
vm := otto.New()
vm.Run("function data(res){return res};")
val, err := vm.Run(body)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
}
value, err := val.Export()
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
}
marshal, err := json.Marshal(value)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return models.StockMoneyDataResp{}
}
err = json.Unmarshal(marshal, &resData)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return models.StockMoneyDataResp{}
}
return resData
}
// 获取股票概念题材信息
func (receiver StockDataApi) GetStockConceptInfo(stockCode string) models.StockConceptInfoResp {
//601138.SH
url := "https://datacenter.eastmoney.com/securities/api/data/v1/get?reportName=RPT_F10_CORETHEME_BOARDTYPE&columns=SECUCODE%2CSECURITY_CODE%2CSECURITY_NAME_ABBR%2CNEW_BOARD_CODE%2CBOARD_NAME%2CSELECTED_BOARD_REASON%2CIS_PRECISE%2CBOARD_RANK%2CBOARD_YIELD%2CDERIVE_BOARD_CODE&quoteColumns=f3~05~NEW_BOARD_CODE~BOARD_YIELD&filter=(SECUCODE%3D%22" + stockCode + "%22)(IS_PRECISE%3D%221%22)&pageNumber=1&pageSize=&sortTypes=1&sortColumns=BOARD_RANK&source=HSF10&client=PC&v=005634233622011753"
logger.SugaredLogger.Infof("url:%s", url2.QueryEscape(url))
var data models.StockConceptInfoResp
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "datacenter.eastmoney.com").
SetHeader("Referer", "https://emweb.securities.eastmoney.com/").
SetHeader("Origin", "https://emweb.securities.eastmoney.com").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
}
err = json.Unmarshal(resp.Body(), &data)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return models.StockConceptInfoResp{}
}
return data
}
// JSONToMarkdownTable 将JSON数据转换为Markdown表格
func JSONToMarkdownTable(jsonData []byte) (string, error) {
var data []map[string]interface{}

View File

@@ -283,3 +283,17 @@ func TestName(t *testing.T) {
//}
}
func TestGetStockMoneyData(t *testing.T) {
db.Init("../../data/stock.db")
stockDataApi := NewStockDataApi()
res := stockDataApi.GetStockMoneyData()
logger.SugaredLogger.Infof("%s", util.MarkdownTableWithTitle("今日个股资金流向Top50", res.Data.Diff))
}
func TestGetStockConceptInfo(t *testing.T) {
db.Init("../../data/stock.db")
stockDataApi := NewStockDataApi()
res := stockDataApi.GetStockConceptInfo("601138.SH")
logger.SugaredLogger.Infof("%s", util.MarkdownTableWithTitle("601138.SH所属概念/板块信息", res.Result.Data))
}

View File

@@ -359,29 +359,29 @@ type XUEQIUHot struct {
}
type HotItem struct {
Type int `json:"type"`
Code string `json:"code"`
Name string `json:"name"`
Value float64 `json:"value"`
Increment int `json:"increment"`
RankChange int `json:"rank_change"`
HasExist interface{} `json:"has_exist"`
Symbol string `json:"symbol"`
Percent float64 `json:"percent"`
Current float64 `json:"current"`
Chg float64 `json:"chg"`
Exchange string `json:"exchange"`
StockType int `json:"stock_type"`
SubType string `json:"sub_type"`
Ad int `json:"ad"`
AdId interface{} `json:"ad_id"`
ContentId interface{} `json:"content_id"`
Page interface{} `json:"page"`
Model interface{} `json:"model"`
Location interface{} `json:"location"`
TradeSession interface{} `json:"trade_session"`
CurrentExt interface{} `json:"current_ext"`
PercentExt interface{} `json:"percent_ext"`
Type int `json:"type" md:"-"`
Code string `json:"code" md:"股票代码"`
Name string `json:"name" md:"股票名称"`
Value float64 `json:"value" md:"热度"`
Increment int `json:"increment" md:"热度变化"`
RankChange int `json:"rank_change" md:"排名变化"`
HasExist interface{} `json:"has_exist" md:"-"`
Symbol string `json:"symbol" md:"-"`
Percent float64 `json:"percent" md:"涨跌幅(%)"`
Current float64 `json:"current" md:"股价"`
Chg float64 `json:"chg" md:"股价变化"`
Exchange string `json:"exchange" md:"交易所代码"`
StockType int `json:"stock_type" md:"-"`
SubType string `json:"sub_type" md:"-"`
Ad int `json:"ad" md:"-"`
AdId interface{} `json:"ad_id" md:"-"`
ContentId interface{} `json:"content_id" md:"-"`
Page interface{} `json:"page" md:"-"`
Model interface{} `json:"model" md:"-"`
Location interface{} `json:"location" md:"-"`
TradeSession interface{} `json:"trade_session" md:"-"`
CurrentExt interface{} `json:"current_ext" md:"-"`
PercentExt interface{} `json:"percent_ext" md:"-"`
}
type HotEvent struct {
@@ -779,3 +779,100 @@ type NtfyNews struct {
Tags []string `json:"tags"`
Icon string `json:"icon"`
}
type THSHotStrategy struct {
Result struct {
Num int `json:"num"`
List []struct {
Author struct {
Avatar string `json:"avatar"`
UserName string `json:"userName"`
UserId int `json:"userId"`
} `json:"author"`
Property struct {
Id int `json:"id"`
Name string `json:"name"`
Query string `json:"query"`
Logic string `json:"logic"`
BuyPosition interface{} `json:"buyPosition"`
Ctime string `json:"ctime"`
Tags []string `json:"tags"`
WinRate string `json:"winRate"`
AnnualYield string `json:"annualYield"`
Type int `json:"type"`
} `json:"property"`
Interaction struct {
CommentNum int `json:"commentNum"`
CollectNum int `json:"collectNum"`
IsCollected bool `json:"isCollected"`
IsSubscribe int `json:"isSubscribe"`
IsPublish int `json:"isPublish"`
Pid int `json:"pid"`
} `json:"interaction"`
} `json:"list"`
} `json:"result"`
}
type StockMoneyDataResp struct {
Rc int `json:"rc"`
Rt int `json:"rt"`
Svr int `json:"svr"`
Lt int `json:"lt"`
Full int `json:"full"`
Dlmkts string `json:"dlmkts"`
Data StockMoneyData `json:"data"`
}
type StockMoneyData struct {
Total int `json:"total"`
Diff []StockMoneyDataDiff `json:"diff"`
}
type StockMoneyDataDiff struct {
F1 int `json:"f1" md:"-"`
F12 string `json:"f12" md:"股票代码"`
F13 int `json:"f13" md:"-"`
F14 string `json:"f14" md:"股票名称"`
F2 float64 `json:"f2" md:"最新价"`
F3 float64 `json:"f3" md:"今日涨跌幅(%)"`
F62 float64 `json:"f62" md:"今日主力净额(元)"`
F184 float64 `json:"f184" md:"今日主力净占比(%)"`
F66 float64 `json:"f66" md:"今日超大单净额(元)"`
F69 float64 `json:"f69" md:"今日超大单净占比(%)"`
F72 float64 `json:"f72" md:"今日大单净额(元)"`
F75 float64 `json:"f75" md:"今日大单净占比(%)"`
F78 float64 `json:"f78" md:"今日中单净额(元)"`
F81 float64 `json:"f81" md:"今日中单净占比(%)"`
F84 float64 `json:"f84" md:"今日小单净额(元)"`
F87 float64 `json:"f87" md:"今日小单净占比(%)"`
F124 int `json:"f124" md:"f124"`
F100 string `json:"f100" md:"所属板块"`
F265 string `json:"f265" md:"板块代码"`
}
type StockConceptInfoResp struct {
Version string `json:"version"`
Result StockConceptInfoResult `json:"result"`
Success bool `json:"success"`
Message string `json:"message"`
Code int `json:"code"`
}
type StockConceptInfoResult struct {
Pages int `json:"pages"`
Data []StockConceptInfo `json:"data"`
Count int `json:"count"`
}
type StockConceptInfo struct {
SECUCODE string `json:"SECUCODE" md:"完整股票代码"`
SECURITYCODE string `json:"SECURITY_CODE" md:"股票代码"`
SECURITYNAMEABBR string `json:"SECURITY_NAME_ABBR" md:"股票名称"`
NEWBOARDCODE string `json:"NEW_BOARD_CODE" md:"板块/概念代码"`
BOARDNAME string `json:"BOARD_NAME" md:"板块/概念名称"`
SELECTEDBOARDREASON string `json:"SELECTED_BOARD_REASON" md:"板块/概念描述"`
ISPRECISE string `json:"IS_PRECISE" md:"-"`
BOARDRANK int `json:"BOARD_RANK" md:"-"`
BOARDYIELD float64 `json:"BOARD_YIELD" md:"板块/概念涨跌幅(%)"`
DERIVEBOARDCODE string `json:"DERIVE_BOARD_CODE" md:"-"`
}

View File

@@ -388,7 +388,7 @@ function deletePrompt(ID) {
</n-form-item-gi>
<n-form-item-gi :span="4" v-if="formValue.openAI.enable" title="天数越多消耗tokens越多"
label="日K线数据(天)" path="openAI.kDays">
<n-input-number min="30" step="1" max="365" v-model:value="formValue.openAI.kDays"/>
<n-input-number min="30" step="1" max="60" v-model:value="formValue.openAI.kDays"/>
</n-form-item-gi>
<n-form-item-gi :span="2" label="http代理" path="httpProxyEnabled">
<n-switch v-model:value="formValue.httpProxyEnabled"/>