Compare commits
16 Commits
v2026.01.0
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
469c4826a3 | ||
|
|
6c31f5aa76 | ||
|
|
e797b64d41 | ||
|
|
12299e8b47 | ||
|
|
7412d56409 | ||
|
|
f75b457082 | ||
|
|
a43095cdd4 | ||
|
|
6ca0d0df32 | ||
|
|
fea9b06a27 | ||
|
|
1d1e437b47 | ||
|
|
eca2f8adee | ||
|
|
49d2109d60 | ||
|
|
f9294fbffd | ||
|
|
cc12a886c1 | ||
|
|
34ea989d47 | ||
|
|
aadff1c5eb |
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -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:
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|:--------------------------------|----------------|:-------------------------------------------------------|
|
||||
| 每月 0 RMB | vip0 | 🌟 全部功能,软件自动更新(从GitHub下载),自行解决github平台网络问题。 |
|
||||
| 每月赞助 18.8 RMB<br>每年赞助 120 RMB | vip1 | 💕 全部功能,软件自动更新(从CDN下载),更新快速便捷。AI配置指导,提示词参考等 |
|
||||
| 每月赞助 28.8 RMB<br>每年赞助 240 RMB | vip2 | 💕 💕 vip1全部功能,赠送硅基流动AI分析服务,启动时自动同步最近24小时市场资讯(包括外媒简讯) |
|
||||
| 每月赞助 28.8 RMB<br>每年赞助 240 RMB | vip2 | 💕 💕 vip1全部功能,启动时自动同步最近24小时市场资讯(包括外媒简讯) |
|
||||
| 每月赞助 X RMB | vipX | 🧩 更多计划,视go-stock开源项目发展情况而定...(承接GitHub项目README广告推广💖) |
|
||||
|
||||
## 🧩 重大功能开发计划
|
||||
|
||||
303
app.go
303
app.go
@@ -40,6 +40,7 @@ type App struct {
|
||||
cronEntrys map[string]cron.EntryID
|
||||
AiTools []data.Tool
|
||||
SponsorInfo map[string]any
|
||||
VipLevel int64
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
@@ -62,24 +63,87 @@ func AddTools(tools []data.Tool) []data.Tool {
|
||||
tools = append(tools, data.Tool{
|
||||
Type: "function",
|
||||
Function: data.ToolFunction{
|
||||
Name: "SearchStockByIndicators",
|
||||
Description: "根据自然语言筛选股票,返回自然语言选股条件要求的股票所有相关数据。输入股票名称可以获取当前股票最新的股价交易数据和基础财务指标信息,多个股票名称使用,分隔。",
|
||||
Name: "SearchStockByIndicators",
|
||||
Description: "根据自然语言筛选股票,返回自然语言选股条件要求的股票所有相关数据。输入股票名称可以获取当前股票最新的股价交易数据和基础财务指标信息,多个股票名称使用,分隔。" +
|
||||
"例如:查看涨停板:涨停板,按涨幅从高到低排序。" +
|
||||
"例如:查看跌停板:跌停板,按跌幅从高到低排序。" +
|
||||
"例如:查看龙虎榜:龙虎榜,按涨幅从高到低排序。" +
|
||||
"例如:查看昨日龙虎榜:昨日龙虎榜。" +
|
||||
"例如:查看板块龙头行情:板块/概念龙头,按涨幅从高到低排序。" +
|
||||
"例如:查看板块龙头行情:龙头股,按成交量从高到低排序。" +
|
||||
"例如:查看技术指标:上海贝岭,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.8倍;当日最高价创60日新高当日收盘价大于5日均线;当日为阳线;股价小于200;" +
|
||||
"例1:创新药,半导体;PE<30;净利润增长率>50%。 按成交量从高到低排序。" +
|
||||
"例2:上证指数,科创50。 " +
|
||||
"例3:长电科技,上海贝岭。" +
|
||||
"例4:长电科技,上海贝岭;KDJ,MACD,RSI,BOLL,主力资金。" +
|
||||
"例5:换手率大于3%小于25%.量比1以上. 10日内有过涨停.股价处于峰值的二分之一以下.流通股本<100亿.当日和连续四日净流入;股价在20日均线以上.分时图股价在均线之上.热门板块下涨幅领先的A股. 当日量能20000手以上.沪深个股.近一年市盈率波动小于150%.MACD金叉;不要ST股及不要退市股,非北交所,每股收益>0。按成交量从高到低排序。" +
|
||||
"例6:沪深主板.流通市值小于100亿.市值大于10亿.60分钟dif大于dea.60分钟skdj指标k值大于d值.skdj指标k值小于90.换手率大于3%.成交额大于1亿元.量比大于2.涨幅大于2%小于7%.股价大于5小于50.创业板.10日均线大于20日均线;不要ST股及不要退市股;不要北交所;不要科创板;不要创业板。按成交量从高到低排序。" +
|
||||
"例7:股价在20日线上,一月之内涨停次数>=1,量比大于1,换手率大于3%。按成交量从高到低排序。" +
|
||||
"例8:基本条件:前期有爆量,回调到 10 日线,当日是缩量阴线,均线趋势向上。;优选条件:一月之内涨停次数>=1。按成交量从高到低排序。",
|
||||
Parameters: &data.FunctionParameters{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"words": map[string]any{
|
||||
"type": "string",
|
||||
"description": "选股自然语言。" +
|
||||
"例如,查看技术指标:上海贝岭,macd,rsi,kdj,boll,5日均线,14日均线,30日均线,60日均线,成交量,OBV,EMA" +
|
||||
"例如,查看有潜力的成交量爆发股:最近7日成交量量比大于3,出现过一次,非ST" +
|
||||
"例1:创新药,半导体;PE<30;净利润增长率>50%。 " +
|
||||
"例如:查看涨停板:涨停板,按涨幅从高到低排序。" +
|
||||
"例如:查看跌停板:跌停板,按跌幅从高到低排序。" +
|
||||
"例如:查看龙虎榜:龙虎榜,按涨幅从高到低排序。" +
|
||||
"例如:查看昨日龙虎榜:昨日龙虎榜。" +
|
||||
"例如:查看板块龙头行情:板块/概念龙头,按涨幅从高到低排序。" +
|
||||
"例如:查看板块龙头行情:龙头股,按成交量从高到低排序。" +
|
||||
"例如:查看技术指标:上海贝岭,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.8倍;当日最高价创60日新高当日收盘价大于5日均线;当日为阳线;股价小于200;" +
|
||||
"例1:创新药,半导体;PE<30;净利润增长率>50%。 按成交量从高到低排序。" +
|
||||
"例2:上证指数,科创50。 " +
|
||||
"例3:长电科技,上海贝岭。" +
|
||||
"例4:长电科技,上海贝岭;KDJ,MACD,RSI,BOLL,主力资金" +
|
||||
"例5:换手率大于3%小于25%.量比1以上. 10日内有过涨停.股价处于峰值的二分之一以下.流通股本<100亿.当日和连续四日净流入;股价在20日均线以上.分时图股价在均线之上.热门板块下涨幅领先的A股. 当日量能20000手以上.沪深个股.近一年市盈率波动小于150%.MACD金叉;不要ST股及不要退市股,非北交所,每股收益>0。" +
|
||||
"例6:沪深主板.流通市值小于100亿.市值大于10亿.60分钟dif大于dea.60分钟skdj指标k值大于d值.skdj指标k值小于90.换手率大于3%.成交额大于1亿元.量比大于2.涨幅大于2%小于7%.股价大于5小于50.创业板.10日均线大于20日均线;不要ST股及不要退市股;不要北交所;不要科创板;不要创业板。" +
|
||||
"例7:股价在20日线上,一月之内涨停次数>=1,量比大于1,换手率大于3%" +
|
||||
"例8:基本条件:前期有爆量,回调到 10 日线,当日是缩量阴线,均线趋势向上。;优选条件:一月之内涨停次数>=1",
|
||||
"例4:长电科技,上海贝岭;KDJ,MACD,RSI,BOLL,主力资金。" +
|
||||
"例5:换手率大于3%小于25%.量比1以上. 10日内有过涨停.股价处于峰值的二分之一以下.流通股本<100亿.当日和连续四日净流入;股价在20日均线以上.分时图股价在均线之上.热门板块下涨幅领先的A股. 当日量能20000手以上.沪深个股.近一年市盈率波动小于150%.MACD金叉;不要ST股及不要退市股,非北交所,每股收益>0。按成交量从高到低排序。" +
|
||||
"例6:沪深主板.流通市值小于100亿.市值大于10亿.60分钟dif大于dea.60分钟skdj指标k值大于d值.skdj指标k值小于90.换手率大于3%.成交额大于1亿元.量比大于2.涨幅大于2%小于7%.股价大于5小于50.创业板.10日均线大于20日均线;不要ST股及不要退市股;不要北交所;不要科创板;不要创业板。按成交量从高到低排序。" +
|
||||
"例7:股价在20日线上,一月之内涨停次数>=1,量比大于1,换手率大于3%。按成交量从高到低排序。" +
|
||||
"例8:基本条件:前期有爆量,回调到 10 日线,当日是缩量阴线,均线趋势向上。;优选条件:一月之内涨停次数>=1。按成交量从高到低排序。",
|
||||
},
|
||||
},
|
||||
Required: []string{"words"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
tools = append(tools, data.Tool{
|
||||
Type: "function",
|
||||
Function: data.ToolFunction{
|
||||
Name: "SearchBk",
|
||||
Description: "根据自然语言查询板块/概念/指数整体数据。" +
|
||||
"例如:存储芯片,成分股" +
|
||||
"例如:查看指数:上证指数。" +
|
||||
"例如:查看存储芯片板块:存储芯片。" +
|
||||
"例如:查看概念板块排名:今日涨幅前5的概念板块。" +
|
||||
"例如:查看概念板块排名:今日净流入前5的概念板块。" +
|
||||
"例如:查看板块/概念排名数据:今日主力净流出前15的概念板块。" +
|
||||
"例如:查看板块板块/概念:今日成交量前15的概念板块。" +
|
||||
"例如:通过市盈率查询板块:当前市盈率介于30-50的板块/概念。",
|
||||
|
||||
Parameters: &data.FunctionParameters{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"words": map[string]any{
|
||||
"type": "string",
|
||||
"description": "板块/概念数据查询自然语言。" +
|
||||
"例如:存储芯片,成分股" +
|
||||
"例如:查看指数:上证指数。" +
|
||||
"例如:查看存储芯片板块:存储芯片。" +
|
||||
"例如:查看概念板块排名:今日涨幅前5的概念板块。" +
|
||||
"例如:查看概念板块排名:今日净流入前5的概念板块。" +
|
||||
"例如:查看板块/概念排名数据:今日主力净流出前15的概念板块。" +
|
||||
"例如:查看板块板块/概念:今日成交量前15的概念板块。" +
|
||||
"例如:通过市盈率查询板块:当前市盈率介于30-50的板块/概念。",
|
||||
},
|
||||
},
|
||||
Required: []string{"words"},
|
||||
@@ -187,6 +251,210 @@ 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"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
//CreateAiRecommendStocks
|
||||
tools = append(tools, data.Tool{
|
||||
Type: "function",
|
||||
Function: data.ToolFunction{
|
||||
Name: "CreateAiRecommendStocks",
|
||||
Description: "创建/保存AI推荐股票记录",
|
||||
Parameters: &data.FunctionParameters{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"modelName": map[string]any{
|
||||
"type": "string",
|
||||
"description": "模型名称",
|
||||
},
|
||||
"stockCode": map[string]any{
|
||||
"type": "string",
|
||||
"description": "股票代码,如:601138.SH。注意 上海证券交易所股票以.SH结尾,深圳证券交易所股票以.SZ结尾,港股股票以.HK结尾,北交所股票以.BJ结尾,",
|
||||
},
|
||||
"stockName": map[string]any{
|
||||
"type": "string",
|
||||
"description": "股票名称",
|
||||
},
|
||||
"bkCode": map[string]any{
|
||||
"type": "string",
|
||||
"description": "板块/行业代码",
|
||||
},
|
||||
"bkName": map[string]any{
|
||||
"type": "string",
|
||||
"description": "板块/概念/行业名称",
|
||||
},
|
||||
"stockPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "推荐时股票价格",
|
||||
},
|
||||
"stockPrePrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "前一交易日股票价格",
|
||||
},
|
||||
"stockClosePrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "推荐时股票收盘价格",
|
||||
},
|
||||
"recommendReason": map[string]any{
|
||||
"type": "string",
|
||||
"description": "推荐理由/驱动因素/逻辑",
|
||||
},
|
||||
"recommendBuyPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "ai建议买入价区间最低价和最高价之间用`-`分隔",
|
||||
},
|
||||
"recommendStopProfitPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "ai建议止盈价区间最低价和最高价之间用`-`分隔",
|
||||
},
|
||||
"recommendStopLossPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "ai建议止损价",
|
||||
},
|
||||
"riskRemarks": map[string]any{
|
||||
"type": "string",
|
||||
"description": "风险提示",
|
||||
},
|
||||
"remarks": map[string]any{
|
||||
"type": "string",
|
||||
"description": "操作总结/备注",
|
||||
},
|
||||
},
|
||||
Required: []string{"stockCode", "stockName", "bkName"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
//BatchCreateAiRecommendStocks
|
||||
tools = append(tools, data.Tool{
|
||||
Type: "function",
|
||||
Function: data.ToolFunction{
|
||||
Name: "BatchCreateAiRecommendStocks",
|
||||
Description: "批量创建/保存AI推荐股票记录,建议每次批量保存5条记录",
|
||||
Parameters: &data.FunctionParameters{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"stocks": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"modelName": map[string]any{
|
||||
"type": "string",
|
||||
"description": "模型名称",
|
||||
},
|
||||
"stockCode": map[string]any{
|
||||
"type": "string",
|
||||
"description": "股票代码,如:601138.SH。注意 上海证券交易所股票以.SH结尾,深圳证券交易所股票以.SZ结尾,港股股票以.HK结尾,北交所股票以.BJ结尾,",
|
||||
},
|
||||
"stockName": map[string]any{
|
||||
"type": "string",
|
||||
"description": "股票名称",
|
||||
},
|
||||
"bkCode": map[string]any{
|
||||
"type": "string",
|
||||
"description": "板块/行业代码",
|
||||
},
|
||||
"bkName": map[string]any{
|
||||
"type": "string",
|
||||
"description": "板块/概念/行业名称",
|
||||
},
|
||||
"stockPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "推荐时股票价格",
|
||||
},
|
||||
"stockPrePrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "前一交易日股票价格",
|
||||
},
|
||||
"stockClosePrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "推荐时股票收盘价格",
|
||||
},
|
||||
"recommendReason": map[string]any{
|
||||
"type": "string",
|
||||
"description": "推荐理由/驱动因素/逻辑",
|
||||
},
|
||||
"recommendBuyPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "ai建议买入价区间最低价和最高价之间用`-`分隔",
|
||||
},
|
||||
"recommendStopProfitPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "ai建议止盈价区间最低价和最高价之间用`-`分隔",
|
||||
},
|
||||
"recommendStopLossPrice": map[string]any{
|
||||
"type": "string",
|
||||
"description": "ai建议止损价",
|
||||
},
|
||||
"riskRemarks": map[string]any{
|
||||
"type": "string",
|
||||
"description": "风险提示",
|
||||
},
|
||||
"remarks": map[string]any{
|
||||
"type": "string",
|
||||
"description": "操作总结/备注",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Required: []string{"stockCode", "stockName", "bkName"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
|
||||
@@ -260,6 +528,7 @@ func (a *App) CheckUpdate(flag int) {
|
||||
|
||||
if _, vipLevel, ok := a.isVip(sponsorCode, "", releaseVersion); ok {
|
||||
level, _ := convertor.ToInt(vipLevel)
|
||||
a.VipLevel = level
|
||||
if level >= 2 {
|
||||
go a.syncNews()
|
||||
}
|
||||
@@ -431,7 +700,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 +722,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 +746,7 @@ func (a *App) syncNews() {
|
||||
}
|
||||
|
||||
func GetSource(tags []string) string {
|
||||
if slices.Contains(tags, "外媒简讯") {
|
||||
if slice.ContainAny(tags, []string{"外媒简讯", "外媒资讯", "外媒"}) {
|
||||
return "外媒"
|
||||
}
|
||||
if slices.Contains(tags, "财联社电报") {
|
||||
|
||||
@@ -80,3 +80,33 @@ func (a *App) AnalyzeSentimentWithFreqWeight(text string) map[string]any {
|
||||
"frequencies": cleanFrequencies,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) GetAIResponseResultList(query models.AIResponseResultQuery) *models.AIResponseResultPageData {
|
||||
page, err := data.NewAIResponseResultService().GetAIResponseResultList(query)
|
||||
if err != nil {
|
||||
return &models.AIResponseResultPageData{}
|
||||
}
|
||||
return page
|
||||
}
|
||||
func (a *App) DeleteAIResponseResult(id string) string {
|
||||
err := data.NewAIResponseResultService().DeleteAIResponseResult(id)
|
||||
if err != nil {
|
||||
return "删除失败"
|
||||
}
|
||||
return "删除成功"
|
||||
}
|
||||
func (a *App) BatchDeleteAIResponseResult(ids []uint) string {
|
||||
err := data.NewAIResponseResultService().BatchDeleteAIResponseResult(ids)
|
||||
if err != nil {
|
||||
return "删除失败"
|
||||
}
|
||||
return "删除成功"
|
||||
}
|
||||
|
||||
func (a *App) GetAiRecommendStocksList(query models.AiRecommendStocksQuery) *models.AiRecommendStocksPageData {
|
||||
page, err := data.NewAiRecommendStocksService().GetAiRecommendStocksList(&query)
|
||||
if err != nil {
|
||||
return &models.AiRecommendStocksPageData{}
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
10
app_test.go
10
app_test.go
@@ -62,3 +62,13 @@ func TestUpdateCheck(t *testing.T) {
|
||||
}
|
||||
logger.SugaredLogger.Infof("releaseVersion:%+v", releaseVersion)
|
||||
}
|
||||
|
||||
func TestGetScreenResolution(t *testing.T) {
|
||||
x, y, w, h, err := getScreenResolution()
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("get screen resolution error:%s", err.Error())
|
||||
return
|
||||
}
|
||||
logger.SugaredLogger.Infof("x:%d,y:%d,w:%d,h:%d", x, y, w, h)
|
||||
|
||||
}
|
||||
|
||||
@@ -6,16 +6,17 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/energye/systray"
|
||||
"github.com/go-toast/toast"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
// startup is called at application startup
|
||||
@@ -209,6 +210,7 @@ func getScreenResolution() (int, int, int, int, error) {
|
||||
//
|
||||
//width, _, _ := getSystemMetrics.Call(0)
|
||||
//height, _, _ := getSystemMetrics.Call(1)
|
||||
//return int(width), int(height), 1456, 768, nil
|
||||
|
||||
return int(1366), int(768), 1456, 768, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/cloudwego/eino/compose"
|
||||
"github.com/cloudwego/eino/flow/agent"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/duke-git/lancet/v2/fileutil"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -61,7 +62,7 @@ func TestGetStockAiAgent(t *testing.T) {
|
||||
logger.SugaredLogger.Errorf("failed to recv: %v", err)
|
||||
return
|
||||
}
|
||||
//logger.SugaredLogger.Infof("stream recv: %v", msg)
|
||||
logger.SugaredLogger.Infof("stream recv: %v", msg)
|
||||
if msg.ReasoningContent != "" {
|
||||
md.WriteString(msg.ReasoningContent)
|
||||
}
|
||||
@@ -76,9 +77,12 @@ func TestGetStockAiAgent(t *testing.T) {
|
||||
func TestAgent(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
|
||||
ch := NewStockAiAgentApi().Chat("分析一下海立股份,使用工具", 1, nil)
|
||||
md := strings.Builder{}
|
||||
ch := NewStockAiAgentApi().Chat("分析一下立讯精密", 0, nil)
|
||||
for message := range ch {
|
||||
logger.SugaredLogger.Infof("res:%s", message.String())
|
||||
md.WriteString(message.String())
|
||||
}
|
||||
|
||||
logger.SugaredLogger.Info(md.String())
|
||||
fileutil.WriteStringToFile("../../data/result.md", md.String(), false)
|
||||
}
|
||||
|
||||
138
backend/data/ai_recommend_stocks_api.go
Normal file
138
backend/data/ai_recommend_stocks_api.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// Package data ai_recommend_stocks_api.go
|
||||
package data
|
||||
|
||||
import (
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/models"
|
||||
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
type AiRecommendStocksService struct{}
|
||||
|
||||
func NewAiRecommendStocksService() *AiRecommendStocksService {
|
||||
return &AiRecommendStocksService{}
|
||||
}
|
||||
|
||||
// CreateAiRecommendStocks 创建AI推荐股票记录
|
||||
func (s *AiRecommendStocksService) CreateAiRecommendStocks(recommend *models.AiRecommendStocks) error {
|
||||
result := db.Dao.Create(recommend)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
func (s *AiRecommendStocksService) BatchCreateAiRecommendStocks(recommends []*models.AiRecommendStocks) error {
|
||||
result := db.Dao.Create(recommends)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// GetAiRecommendStocksList 分页查询AI推荐股票记录
|
||||
func (s *AiRecommendStocksService) GetAiRecommendStocksList(query *models.AiRecommendStocksQuery) (*models.AiRecommendStocksPageData, error) {
|
||||
var list []models.AiRecommendStocks
|
||||
var total int64
|
||||
|
||||
q := db.Dao.Model(&models.AiRecommendStocks{})
|
||||
|
||||
// 构建查询条件
|
||||
if query.StockCode != "" {
|
||||
q.Or("stock_code LIKE ?", "%"+query.StockCode+"%")
|
||||
}
|
||||
if query.StockName != "" {
|
||||
q.Or("stock_name LIKE ?", "%"+query.StockName+"%")
|
||||
}
|
||||
if query.BkCode != "" {
|
||||
q.Or("bk_code LIKE ?", "%"+query.BkCode+"%")
|
||||
}
|
||||
if query.BkName != "" {
|
||||
q.Or("bk_name LIKE ?", "%"+query.BkName+"%")
|
||||
}
|
||||
|
||||
if query.StartDate != "" && query.EndDate != "" {
|
||||
query.StartDate = strutil.ReplaceWithMap(query.StartDate, map[string]string{
|
||||
"T": " ",
|
||||
"Z": "",
|
||||
})
|
||||
query.StartDate = strutil.ReplaceWithMap(query.StartDate, map[string]string{
|
||||
"T": " ",
|
||||
"Z": "",
|
||||
})
|
||||
q = q.Where("data_time BETWEEN ? AND ?", query.StartDate, query.EndDate)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
err := q.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置默认分页参数
|
||||
page := query.Page
|
||||
pageSize := query.PageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
err = q.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&list).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
|
||||
|
||||
stockCodes := slice.Map(list, func(index int, item models.AiRecommendStocks) string {
|
||||
return ConvertTushareCodeToStockCode(item.StockCode)
|
||||
})
|
||||
stockData, _ := NewStockDataApi().GetStockCodeRealTimeData(stockCodes...)
|
||||
for _, info := range *stockData {
|
||||
for idx, item := range list {
|
||||
if ConvertTushareCodeToStockCode(item.StockCode) == ConvertTushareCodeToStockCode(info.Code) {
|
||||
list[idx].StockCurrentPrice = info.Price
|
||||
list[idx].StockPrePrice = info.PreClose
|
||||
list[idx].StockCurrentPriceTime = info.Date + " " + info.Time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &models.AiRecommendStocksPageData{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAiRecommendStocksByID 根据ID获取AI推荐股票记录
|
||||
func (s *AiRecommendStocksService) GetAiRecommendStocksByID(id uint) (*models.AiRecommendStocks, error) {
|
||||
var recommend models.AiRecommendStocks
|
||||
err := db.Dao.First(&recommend, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &recommend, nil
|
||||
}
|
||||
|
||||
// UpdateAiRecommendStocks 更新AI推荐股票记录
|
||||
func (s *AiRecommendStocksService) UpdateAiRecommendStocks(id uint, recommend *models.AiRecommendStocks) error {
|
||||
result := db.Dao.Model(&models.AiRecommendStocks{}).Where("id = ?", id).Updates(recommend)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// DeleteAiRecommendStocks 根据ID删除AI推荐股票记录
|
||||
func (s *AiRecommendStocksService) DeleteAiRecommendStocks(id uint) error {
|
||||
// 使用软删除
|
||||
result := db.Dao.Where("id = ?", id).Delete(&models.AiRecommendStocks{})
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// BatchDeleteAiRecommendStocks 批量删除AI推荐股票记录
|
||||
func (s *AiRecommendStocksService) BatchDeleteAiRecommendStocks(ids []uint) error {
|
||||
// 使用软删除
|
||||
result := db.Dao.Where("id IN ?", ids).Delete(&models.AiRecommendStocks{})
|
||||
return result.Error
|
||||
}
|
||||
97
backend/data/ai_response_result_api.go
Normal file
97
backend/data/ai_response_result_api.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/models"
|
||||
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
type AIResponseResultService struct{}
|
||||
|
||||
func NewAIResponseResultService() *AIResponseResultService {
|
||||
return &AIResponseResultService{}
|
||||
}
|
||||
|
||||
// GetAIResponseResultList 分页查询AI响应结果
|
||||
func (s *AIResponseResultService) GetAIResponseResultList(query models.AIResponseResultQuery) (*models.AIResponseResultPageData, error) {
|
||||
var list []models.AIResponseResult
|
||||
var total int64
|
||||
|
||||
q := db.Dao.Model(&models.AIResponseResult{})
|
||||
|
||||
// 构建查询条件
|
||||
if query.ChatId != "" {
|
||||
q.Where("chat_id LIKE ?", "%"+query.ChatId+"%")
|
||||
}
|
||||
if query.ModelName != "" {
|
||||
q.Or("model_name LIKE ?", "%"+query.ModelName+"%")
|
||||
}
|
||||
if query.StockCode != "" {
|
||||
q.Or("stock_code LIKE ?", "%"+query.StockCode+"%")
|
||||
}
|
||||
if query.Question != "" {
|
||||
q.Or("question LIKE ?", "%"+query.Question+"%")
|
||||
}
|
||||
if query.StartDate != "" && query.EndDate != "" {
|
||||
query.StartDate = strutil.ReplaceWithMap(query.StartDate, map[string]string{
|
||||
"T": " ",
|
||||
"Z": "",
|
||||
})
|
||||
query.StartDate = strutil.ReplaceWithMap(query.StartDate, map[string]string{
|
||||
"T": " ",
|
||||
"Z": "",
|
||||
})
|
||||
q = q.Where("created_at BETWEEN ? AND ?", query.StartDate, query.EndDate)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
err := q.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置默认分页参数
|
||||
page := query.Page
|
||||
pageSize := query.PageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
err = q.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&list).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
|
||||
|
||||
return &models.AIResponseResultPageData{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteAIResponseResult 根据ID删除AI响应结果
|
||||
func (s *AIResponseResultService) DeleteAIResponseResult(id string) error {
|
||||
|
||||
// 使用软删除
|
||||
result := db.Dao.Where("id = ?", id).Delete(&models.AIResponseResult{})
|
||||
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// BatchDeleteAIResponseResult 批量删除AI响应结果
|
||||
func (s *AIResponseResultService) BatchDeleteAIResponseResult(ids []uint) error {
|
||||
// 使用软删除
|
||||
result := db.Dao.Where("id IN ?", ids).Delete(&models.AIResponseResult{})
|
||||
|
||||
return result.Error
|
||||
}
|
||||
26
backend/data/ai_response_result_api_test.go
Normal file
26
backend/data/ai_response_result_api_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/models"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2026/1/23 17:39
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestAIResponseResultService_GetAIResponseResultList(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
service := NewAIResponseResultService()
|
||||
list, err := service.GetAIResponseResultList(models.AIResponseResultQuery{
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Log(list)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -43,6 +43,8 @@ type OpenAi struct {
|
||||
CrawlTimeOut int64 `json:"crawl_time_out"`
|
||||
KDays int64 `json:"kDays"`
|
||||
BrowserPath string `json:"browser_path"`
|
||||
HttpProxy string `json:"httpProxy"`
|
||||
HttpProxyEnabled bool `json:"httpProxyEnabled"`
|
||||
}
|
||||
|
||||
func (o OpenAi) String() string {
|
||||
@@ -67,7 +69,7 @@ func NewDeepSeekOpenAi(ctx context.Context, aiConfigId int) *OpenAi {
|
||||
settingConfig.CrawlTimeOut = 60
|
||||
}
|
||||
if settingConfig.KDays < 30 {
|
||||
settingConfig.KDays = 120
|
||||
settingConfig.KDays = 60
|
||||
}
|
||||
}
|
||||
o := &OpenAi{
|
||||
@@ -78,6 +80,8 @@ func NewDeepSeekOpenAi(ctx context.Context, aiConfigId int) *OpenAi {
|
||||
MaxTokens: aiConfig.MaxTokens,
|
||||
Temperature: aiConfig.Temperature,
|
||||
TimeOut: aiConfig.TimeOut,
|
||||
HttpProxy: aiConfig.HttpProxy,
|
||||
HttpProxyEnabled: aiConfig.HttpProxyEnabled,
|
||||
Prompt: settingConfig.Prompt,
|
||||
QuestionTemplate: settingConfig.QuestionTemplate,
|
||||
CrawlTimeOut: settingConfig.CrawlTimeOut,
|
||||
@@ -175,6 +179,8 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
|
||||
sysPrompt = o.Prompt
|
||||
}
|
||||
|
||||
sysPrompt += "最后必须调用CreateAiRecommendStocks工具函数保存ai股票推荐记录。"
|
||||
|
||||
msg := []map[string]interface{}{
|
||||
{
|
||||
"role": "system",
|
||||
@@ -193,7 +199,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 +257,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 +430,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 +676,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 +890,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 +898,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 +910,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 +951,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 +983,27 @@ func AskAi(o *OpenAi, err error, messages []map[string]interface{}, ch chan map[
|
||||
thinking = "enabled"
|
||||
}
|
||||
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
|
||||
if o.HttpProxyEnabled && o.HttpProxy != "" {
|
||||
client.SetProxy(o.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 +1143,28 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
|
||||
thinking = "enabled"
|
||||
}
|
||||
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
|
||||
if o.HttpProxyEnabled && o.HttpProxy != "" {
|
||||
client.SetProxy(o.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()
|
||||
@@ -1231,6 +1285,83 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
|
||||
if choice.FinishReason == "tool_calls" {
|
||||
logger.SugaredLogger.Infof("functions: %+v", functions)
|
||||
for funcName, funcArguments := range functions {
|
||||
|
||||
if funcName == "SearchBk" {
|
||||
words := gjson.Get(funcArguments, "words").String()
|
||||
ch <- map[string]any{
|
||||
"code": 1,
|
||||
"question": question,
|
||||
"chatId": streamResponse.Id,
|
||||
"model": streamResponse.Model,
|
||||
"content": "\r\n```\r\n开始调用工具:SearchBk,\n参数:" + words + "\r\n```\r\n",
|
||||
"time": time.Now().Format(time.DateTime),
|
||||
}
|
||||
|
||||
content := "无符合条件的数据"
|
||||
|
||||
res := NewSearchStockApi(words).SearchBk(random.RandInt(50, 120))
|
||||
if convertor.ToString(res["code"]) == "100" {
|
||||
resData := res["data"].(map[string]any)
|
||||
result := resData["result"].(map[string]any)
|
||||
dataList := result["dataList"].([]any)
|
||||
columns := result["columns"].([]any)
|
||||
headers := map[string]string{}
|
||||
for _, v := range columns {
|
||||
//logger.SugaredLogger.Infof("v:%+v", v)
|
||||
d := v.(map[string]any)
|
||||
//logger.SugaredLogger.Infof("key:%s title:%s dateMsg:%s unit:%s", d["key"], d["title"], d["dateMsg"], d["unit"])
|
||||
title := convertor.ToString(d["title"])
|
||||
if convertor.ToString(d["dateMsg"]) != "" {
|
||||
title = title + "[" + convertor.ToString(d["dateMsg"]) + "]"
|
||||
}
|
||||
if convertor.ToString(d["unit"]) != "" {
|
||||
title = title + "(" + convertor.ToString(d["unit"]) + ")"
|
||||
}
|
||||
headers[d["key"].(string)] = title
|
||||
}
|
||||
table := &[]map[string]any{}
|
||||
for _, v := range dataList {
|
||||
d := v.(map[string]any)
|
||||
tmp := map[string]any{}
|
||||
for key, title := range headers {
|
||||
tmp[title] = convertor.ToString(d[key])
|
||||
}
|
||||
*table = append(*table, tmp)
|
||||
}
|
||||
jsonData, _ := json.Marshal(*table)
|
||||
markdownTable, _ := JSONToMarkdownTable(jsonData)
|
||||
//logger.SugaredLogger.Infof("markdownTable=\n%s", markdownTable)
|
||||
content = "\r\n### 工具筛选出的相关板块/概念数据:\r\n" + markdownTable + "\r\n"
|
||||
}
|
||||
logger.SugaredLogger.Infof("SearchBk:words:%s --> \n%s", words, content)
|
||||
|
||||
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": content,
|
||||
"tool_call_id": currentCallId,
|
||||
//"reasoning_content": reasoningContentText.String(),
|
||||
//"tool_calls": choice.Delta.ToolCalls,
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
if funcName == "SearchStockByIndicators" {
|
||||
words := gjson.Get(funcArguments, "words").String()
|
||||
|
||||
@@ -1276,7 +1407,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
|
||||
jsonData, _ := json.Marshal(*table)
|
||||
markdownTable, _ := JSONToMarkdownTable(jsonData)
|
||||
//logger.SugaredLogger.Infof("markdownTable=\n%s", markdownTable)
|
||||
content = "\r\n### 工具筛选出的股票数据:\r\n" + markdownTable + "\r\n"
|
||||
content = "\r\n### 工具筛选出的相关股票数据:\r\n" + markdownTable + "\r\n"
|
||||
}
|
||||
logger.SugaredLogger.Infof("SearchStockByIndicators:words:%s --> \n%s", words, content)
|
||||
|
||||
@@ -1643,6 +1774,232 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
if funcName == "CreateAiRecommendStocks" {
|
||||
ch <- map[string]any{
|
||||
"code": 1,
|
||||
"question": question,
|
||||
"chatId": streamResponse.Id,
|
||||
"model": streamResponse.Model,
|
||||
"content": "\r\n```\r\n开始调用工具:CreateAiRecommendStocks,\n参数:" + funcArguments + "\r\n```\r\n",
|
||||
"time": time.Now().Format(time.DateTime),
|
||||
}
|
||||
recommend := models.AiRecommendStocks{}
|
||||
err := json.Unmarshal([]byte(funcArguments), &recommend)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Infof("CreateAiRecommendStocks error : %s", err.Error())
|
||||
return
|
||||
}
|
||||
err = NewAiRecommendStocksService().CreateAiRecommendStocks(&recommend)
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Infof("CreateAiRecommendStocks error : %s", err.Error())
|
||||
ch <- map[string]any{
|
||||
"code": 0,
|
||||
"question": question,
|
||||
"content": "保存股票推荐失败:" + err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
messages = append(messages, map[string]interface{}{
|
||||
"role": "tool",
|
||||
"content": "保存股票推荐成功",
|
||||
"tool_call_id": currentCallId,
|
||||
//"reasoning_content": reasoningContentText.String(),
|
||||
//"tool_calls": choice.Delta.ToolCalls,
|
||||
})
|
||||
}
|
||||
|
||||
//BatchCreateAiRecommendStocks
|
||||
if funcName == "BatchCreateAiRecommendStocks" {
|
||||
ch <- map[string]any{
|
||||
"code": 1,
|
||||
"question": question,
|
||||
"chatId": streamResponse.Id,
|
||||
"model": streamResponse.Model,
|
||||
"content": "\r\n```\r\n开始调用工具:BatchCreateAiRecommendStocks,\n参数:" + funcArguments + "\r\n```\r\n",
|
||||
"time": time.Now().Format(time.DateTime),
|
||||
}
|
||||
stocks := gjson.Get(funcArguments, "stocks").String()
|
||||
var recommends []*models.AiRecommendStocks
|
||||
err := json.Unmarshal([]byte(stocks), &recommends)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Infof("BatchCreateAiRecommendStocks error : %s", err.Error())
|
||||
return
|
||||
}
|
||||
err = NewAiRecommendStocksService().BatchCreateAiRecommendStocks(recommends)
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Infof("BatchCreateAiRecommendStocks error : %s", err.Error())
|
||||
ch <- map[string]any{
|
||||
"code": 0,
|
||||
"question": question,
|
||||
"content": "批量保存股票推荐失败:" + err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
messages = append(messages, map[string]interface{}{
|
||||
"role": "tool",
|
||||
"content": "批量保存股票推荐成功",
|
||||
"tool_call_id": currentCallId,
|
||||
//"reasoning_content": reasoningContentText.String(),
|
||||
//"tool_calls": choice.Delta.ToolCalls,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
AskAiWithTools(o, err, messages, ch, question, tools, thinkingMode)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,51 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
|
||||
return respMap
|
||||
}
|
||||
|
||||
func (s SearchStockApi) SearchBk(pageSize int) map[string]any {
|
||||
url := "https://np-tjxg-b.eastmoney.com/api/smart-tag/bkc/v3/pw/search-code"
|
||||
qgqpBId := NewSettingsApi().Config.QgqpBId
|
||||
if qgqpBId == "" {
|
||||
return map[string]any{
|
||||
"code": -1,
|
||||
"message": "请先获取东财用户标识(qgqp_b_id):打开浏览器,访问东财网站,按F12打开开发人员工具-》网络面板,随便点开一个请求,复制请求cookie中qgqp_b_id对应的值。保存到设置中的东财唯一标识输入框",
|
||||
}
|
||||
}
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "np-tjxg-g.eastmoney.com").
|
||||
SetHeader("Origin", "https://xuangu.eastmoney.com").
|
||||
SetHeader("Referer", "https://xuangu.eastmoney.com/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(fmt.Sprintf(`{
|
||||
"keyWord": "%s",
|
||||
"pageSize": %d,
|
||||
"pageNo": 1,
|
||||
"fingerprint": "%s",
|
||||
"gids": [],
|
||||
"matchWord": "",
|
||||
"timestamp": "%d",
|
||||
"shareToGuba": false,
|
||||
"requestId": "",
|
||||
"needCorrect": true,
|
||||
"removedConditionIdList": [],
|
||||
"xcId": "",
|
||||
"ownSelectAll": false,
|
||||
"dxInfo": [],
|
||||
"extraCondition": ""
|
||||
}`, s.words, pageSize, qgqpBId, time.Now().Unix())).Post(url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
|
||||
return map[string]any{
|
||||
"code": -1,
|
||||
"message": err.Error(),
|
||||
}
|
||||
}
|
||||
respMap := map[string]any{}
|
||||
json.Unmarshal(resp.Body(), &respMap)
|
||||
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
|
||||
return respMap
|
||||
}
|
||||
|
||||
func (s SearchStockApi) HotStrategy() map[string]any {
|
||||
url := fmt.Sprintf("https://np-ipick.eastmoney.com/recommend/stock/heat/ranking?count=20&trace=%d&client=web&biz=web_smart_tag", time.Now().Unix())
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
@@ -97,3 +142,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
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ func TestSearchStock(t *testing.T) {
|
||||
}
|
||||
logger.SugaredLogger.Infof("e:%s", e)
|
||||
|
||||
res := NewSearchStockApi("量比大于2,基本面优秀,2025年三季报已披露,主力连续3日净流入,非创业板非科创板非ST").SearchStock(20)
|
||||
//res := NewSearchStockApi("量比大于2,基本面优秀,2025年三季报已披露,主力连续3日净流入,非创业板非科创板非ST").SearchStock(20)
|
||||
res := NewSearchStockApi("今日涨幅前5的概念板块").SearchBk(50)
|
||||
|
||||
logger.SugaredLogger.Infof("res:%+v", res)
|
||||
data := res["data"].(map[string]any)
|
||||
result := data["result"].(map[string]any)
|
||||
@@ -80,3 +82,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)
|
||||
}
|
||||
|
||||
@@ -45,16 +45,18 @@ func (receiver Settings) TableName() string {
|
||||
}
|
||||
|
||||
type AIConfig struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `json:"name"`
|
||||
BaseUrl string `json:"baseUrl"`
|
||||
ApiKey string `json:"apiKey" `
|
||||
ModelName string `json:"modelName"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TimeOut int `json:"timeOut"`
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `json:"name"`
|
||||
BaseUrl string `json:"baseUrl"`
|
||||
ApiKey string `json:"apiKey" `
|
||||
ModelName string `json:"modelName"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TimeOut int `json:"timeOut"`
|
||||
HttpProxy string `json:"httpProxy"`
|
||||
HttpProxyEnabled bool `json:"httpProxyEnabled"`
|
||||
}
|
||||
|
||||
func (AIConfig) TableName() string {
|
||||
@@ -163,13 +165,15 @@ func updateAiConfigs(aiConfigs []*AIConfig) error {
|
||||
} else {
|
||||
notDeleteIds = append(notDeleteIds, item.ID)
|
||||
e = db.Dao.Model(&AIConfig{}).Where("id=?", item.ID).Updates(map[string]interface{}{
|
||||
"name": item.Name,
|
||||
"base_url": item.BaseUrl,
|
||||
"api_key": item.ApiKey,
|
||||
"model_name": item.ModelName,
|
||||
"max_tokens": item.MaxTokens,
|
||||
"temperature": item.Temperature,
|
||||
"time_out": item.TimeOut,
|
||||
"name": item.Name,
|
||||
"base_url": item.BaseUrl,
|
||||
"api_key": item.ApiKey,
|
||||
"model_name": item.ModelName,
|
||||
"max_tokens": item.MaxTokens,
|
||||
"temperature": item.Temperature,
|
||||
"time_out": item.TimeOut,
|
||||
"http_proxy": item.HttpProxy,
|
||||
"http_proxy_enabled": item.HttpProxyEnabled,
|
||||
}).Error
|
||||
if e != nil {
|
||||
return
|
||||
@@ -198,12 +202,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 +218,7 @@ func GetSettingConfig() *SettingConfig {
|
||||
settings.CrawlTimeOut = 60
|
||||
}
|
||||
if settings.KDays < 30 {
|
||||
settings.KDays = 120
|
||||
settings.KDays = 60
|
||||
}
|
||||
}
|
||||
if settings.BrowserPath == "" {
|
||||
|
||||
@@ -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"eColumns=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{}
|
||||
|
||||
@@ -195,7 +195,7 @@ func TestParseFullSingleStockData(t *testing.T) {
|
||||
func TestNewStockDataApi(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
stockDataApi := NewStockDataApi()
|
||||
datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745", "gb_tsla", "hk09660", "hk00700")
|
||||
datas, _ := stockDataApi.GetStockCodeRealTimeData("sz002352", "sh600859", "sh600745", "gb_tsla", "hk09660", "hk00700")
|
||||
for _, data := range *datas {
|
||||
t.Log(data)
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
|
||||
@@ -149,6 +149,34 @@ func (receiver AIResponseResult) TableName() string {
|
||||
return "ai_response_result"
|
||||
}
|
||||
|
||||
// AIResponseResultQuery 分页查询参数
|
||||
type AIResponseResultQuery struct {
|
||||
Page int `form:"page" json:"page"` // 页码
|
||||
PageSize int `form:"pageSize" json:"pageSize"` // 每页大小
|
||||
ChatId string `form:"chatId" json:"chatId"` // 聊天ID筛选
|
||||
ModelName string `form:"modelName" json:"modelName"` // 模型名称筛选
|
||||
StockCode string `form:"stockCode" json:"stockCode"` // 股票代码筛选
|
||||
StockName string `form:"stockName" json:"stockName"` // 股票名称筛选
|
||||
Question string `form:"question" json:"question"` // 问题内容模糊搜索
|
||||
StartDate string `form:"startDate" json:"startDate"` // 开始日期
|
||||
EndDate string `form:"endDate" json:"endDate"` // 结束日期
|
||||
}
|
||||
|
||||
// AIResponseResultPageResp 分页查询响应
|
||||
type AIResponseResultPageResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data AIResponseResultPageData `json:"data"`
|
||||
}
|
||||
|
||||
type AIResponseResultPageData struct {
|
||||
List []AIResponseResult `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
}
|
||||
|
||||
type VersionInfo struct {
|
||||
gorm.Model
|
||||
Version string `json:"version"`
|
||||
@@ -359,29 +387,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 +807,148 @@ 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:"-"`
|
||||
}
|
||||
|
||||
type AiRecommendStocks struct {
|
||||
gorm.Model
|
||||
DataTime *time.Time `json:"dataTime" gorm:"index;autoCreateTime"`
|
||||
ModelName string `json:"modelName" md:"模型名称"`
|
||||
StockCode string `json:"stockCode" md:"股票代码"`
|
||||
StockName string `json:"stockName" md:"股票名称"`
|
||||
BkCode string `json:"bkCode" md:"行业/板块代码"`
|
||||
BkName string `json:"bkName" md:"行业/板块名称"`
|
||||
StockPrice string `json:"stockPrice" md:"推荐时股票价格"`
|
||||
StockCurrentPrice string `json:"stockCurrentPrice" md:"当前价格"`
|
||||
StockCurrentPriceTime string `json:"stockCurrentPriceTime" md:"当前价格时间"`
|
||||
StockClosePrice string `json:"stockClosePrice" md:"推荐时股票收盘价格"`
|
||||
StockPrePrice string `json:"stockPrePrice" md:"前一交易日股票价格"`
|
||||
RecommendReason string `json:"recommendReason" md:"推荐理由/驱动因素/逻辑"`
|
||||
RecommendBuyPrice string `json:"recommendBuyPrice" md:"ai建议买入价"`
|
||||
RecommendStopProfitPrice string `json:"recommendStopProfitPrice" md:"ai建议止盈价"`
|
||||
RecommendStopLossPrice string `json:"recommendStopLossPrice" md:"ai建议止损价"`
|
||||
RiskRemarks string `json:"riskRemarks" md:"风险提示"`
|
||||
Remarks string `json:"remarks" md:"备注"`
|
||||
}
|
||||
|
||||
func (receiver AiRecommendStocks) TableName() string { return "ai_recommend_stocks" }
|
||||
|
||||
type AiRecommendStocksQuery struct {
|
||||
Page int `form:"page" json:"page"` // 页码
|
||||
PageSize int `form:"pageSize" json:"pageSize"` // 每页大小
|
||||
StockCode string `form:"stockCode" json:"stockCode"` // 股票代码筛选
|
||||
StockName string `form:"stockName" json:"stockName"` // 股票名称筛选
|
||||
BkCode string `form:"bkCode" json:"bkCode"` // 板块代码筛选
|
||||
BkName string `form:"bkName" json:"bkName"` // 板块名称筛选
|
||||
StartDate string `form:"startDate" json:"startDate"` // 开始日期
|
||||
EndDate string `form:"endDate" json:"endDate"` // 结束日期
|
||||
}
|
||||
|
||||
type AiRecommendStocksPageResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data AiRecommendStocksPageData `json:"data"`
|
||||
}
|
||||
|
||||
type AiRecommendStocksPageData struct {
|
||||
List []AiRecommendStocks `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
}
|
||||
|
||||
3
frontend/components.d.ts
vendored
3
frontend/components.d.ts
vendored
@@ -11,6 +11,7 @@ declare module 'vue' {
|
||||
About: typeof import('./src/components/about.vue')['default']
|
||||
AgentChat: typeof import('./src/components/agent-chat.vue')['default']
|
||||
AgentChat_bk: typeof import('./src/components/agent-chat_bk.vue')['default']
|
||||
AiRecommendStocksList: typeof import('./src/components/aiRecommendStocksList.vue')['default']
|
||||
AnalyzeMartket: typeof import('./src/components/AnalyzeMartket.vue')['default']
|
||||
ClsCalendarTimeLine: typeof import('./src/components/ClsCalendarTimeLine.vue')['default']
|
||||
EmbeddedUrl: typeof import('./src/components/EmbeddedUrl.vue')['default']
|
||||
@@ -27,6 +28,8 @@ declare module 'vue' {
|
||||
MoneyTrend: typeof import('./src/components/moneyTrend.vue')['default']
|
||||
NewsList: typeof import('./src/components/newsList.vue')['default']
|
||||
RankTable: typeof import('./src/components/rankTable.vue')['default']
|
||||
ResearchIndex: typeof import('./src/components/researchIndex.vue')['default']
|
||||
ResearchReport: typeof import('./src/components/researchReport.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SelectStock: typeof import('./src/components/SelectStock.vue')['default']
|
||||
|
||||
@@ -15,9 +15,9 @@ import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,NButton,dateZhCN,z
|
||||
import {
|
||||
AlarmOutline,
|
||||
AnalyticsOutline,
|
||||
BarChartSharp, Bonfire, BonfireOutline, EaselSharp,
|
||||
BarChartSharp, Bonfire, BonfireOutline, DiamondOutline, EaselSharp,
|
||||
ExpandOutline, Flag,
|
||||
Flame, FlameSharp, InformationOutline,
|
||||
Flame, FlameSharp, FlaskOutline, InformationOutline,
|
||||
LogoGithub,
|
||||
NewspaperOutline,
|
||||
NewspaperSharp, Notifications,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
} from '@vicons/ionicons5'
|
||||
import {AnalyzeSentiment, GetConfig, GetGroupList,GetVersionInfo} from "../wailsjs/go/main/App";
|
||||
import {Dragon, Fire, FirefoxBrowser, Gripfire, Robot} from "@vicons/fa";
|
||||
import {ReportSearch} from "@vicons/tabler";
|
||||
import {ReportAnalytics, ReportMoney, ReportSearch} from "@vicons/tabler";
|
||||
import {LocalFireDepartmentRound} from "@vicons/material";
|
||||
import {BoxSearch20Regular, CommentNote20Filled} from "@vicons/fluent";
|
||||
import {FireFilled, FireOutlined, NotificationFilled, StockOutlined} from "@vicons/antd";
|
||||
@@ -446,6 +446,77 @@ const menuOptions = ref([
|
||||
show:enableAgent.value,
|
||||
icon: renderIcon(Robot),
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'research',
|
||||
query: {
|
||||
name:"研究中心",
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'research'
|
||||
setTimeout(() => {
|
||||
EventsEmit("changeResearchTab", {ID: 0, name: 'AI分析报告'})
|
||||
}, 100)
|
||||
},
|
||||
},
|
||||
{default: () => '研究中心'}
|
||||
),
|
||||
key: 'research',
|
||||
icon: renderIcon(FlaskOutline),
|
||||
children:[
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'research',
|
||||
query: {
|
||||
name:"AI分析报告",
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'research'
|
||||
setTimeout(() => {
|
||||
EventsEmit("changeResearchTab", {ID: 0, name: 'AI分析报告'})
|
||||
}, 100)
|
||||
},
|
||||
},
|
||||
{default: () => 'AI分析报告'}
|
||||
),
|
||||
key: 'research1',
|
||||
icon: renderIcon(ReportAnalytics),
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'research',
|
||||
query: {
|
||||
name:"股票推荐记录",
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'research'
|
||||
setTimeout(() => {
|
||||
EventsEmit("changeResearchTab", {ID: 1, name: '股票推荐记录'})
|
||||
}, 100)
|
||||
},
|
||||
},
|
||||
{default: () => '股票推荐记录'}
|
||||
),
|
||||
key: 'research2',
|
||||
icon: renderIcon(DiamondOutline),
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
|
||||
@@ -59,7 +59,7 @@ function getMarketCode(item) {
|
||||
<template #trigger>
|
||||
<n-tag type="info" :bordered="false"> {{item.name}} {{item.code}}</n-tag>
|
||||
</template>
|
||||
<k-line-chart style="width: 800px" :code="getMarketCode(item)" :chart-height="500" :name="item.name" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
<k-line-chart style="width: 800px" :code="getMarketCode(item)" :chart-height="500" :stockName="item.name" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
</n-popover>
|
||||
</n-text></n-td>
|
||||
<n-td><n-text :type="item.percent>0?'error':'success'">{{item.percent}}%</n-text></n-td>
|
||||
|
||||
@@ -4,12 +4,12 @@ import {GetStockKLine} from "../../wailsjs/go/main/App";
|
||||
import * as echarts from "echarts";
|
||||
import {onMounted, ref} from "vue";
|
||||
import _ from "lodash";
|
||||
const { code,name,darkTheme,kDays ,chartHeight} = defineProps({
|
||||
const { code,stockName,darkTheme,kDays ,chartHeight} = defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: {
|
||||
stockName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
@@ -33,13 +33,15 @@ const downBorderColor = '';
|
||||
const kLineChartRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
handleKLine(code,name)
|
||||
handleKLine(code,stockName)
|
||||
})
|
||||
|
||||
function handleKLine(code,name){
|
||||
GetStockKLine(code,name,365).then(result => {
|
||||
function handleKLine(code,stockName){
|
||||
console.log("handleKLine",code,stockName)
|
||||
const chart = echarts.init(kLineChartRef.value);
|
||||
chart.showLoading()
|
||||
GetStockKLine(code,stockName,365).then(result => {
|
||||
//console.log("GetStockKLine",result)
|
||||
const chart = echarts.init(kLineChartRef.value);
|
||||
const categoryData = [];
|
||||
const values = [];
|
||||
const volumns=[];
|
||||
@@ -47,24 +49,28 @@ function handleKLine(code,name){
|
||||
let resultElement=result[i]
|
||||
//console.log("resultElement:{}",resultElement)
|
||||
categoryData.push(resultElement.day)
|
||||
let flag=resultElement.close>resultElement.open?1:-1
|
||||
let flag=Number(resultElement.close)>Number(resultElement.open)?1:-1
|
||||
if(i>0){
|
||||
flag=Number(resultElement.close)>Number(result[i-1].close)?1:-1
|
||||
}
|
||||
values.push([
|
||||
resultElement.open,
|
||||
resultElement.close,
|
||||
resultElement.low,
|
||||
resultElement.high
|
||||
Number(resultElement.open),
|
||||
Number(resultElement.close),
|
||||
Number(resultElement.low),
|
||||
Number(resultElement.high)
|
||||
])
|
||||
volumns.push([i,resultElement.volume/10000,flag])
|
||||
volumns.push([i,Number(resultElement.volume)/10000,flag])
|
||||
}
|
||||
////console.log("categoryData",categoryData)
|
||||
////console.log("values",values)
|
||||
let option = {
|
||||
title: {
|
||||
text: name+" "+code,
|
||||
left: '20px',
|
||||
text: stockName+" "+categoryData[values.length-1]+" "+values[values.length-1][1]+" "+((values[values.length-1][1]-values[values.length-2][1])/values[values.length-2][1]*100).toFixed(2)+"%",
|
||||
left: '0px',
|
||||
textStyle: {
|
||||
color: darkTheme?'#ccc':'#456'
|
||||
}
|
||||
color: Number(values[values.length-1][1])>Number(values[values.length-2][1])?'red':'green',
|
||||
fontSize: 14
|
||||
},
|
||||
},
|
||||
darkMode: darkTheme,
|
||||
//backgroundColor: '#1c1c1c',
|
||||
@@ -150,7 +156,7 @@ function handleKLine(code,name){
|
||||
left: '8%',
|
||||
right: '8%',
|
||||
top: '66%',
|
||||
height: '15%'
|
||||
height: '18%'
|
||||
}
|
||||
],
|
||||
xAxis: [
|
||||
@@ -184,7 +190,11 @@ function handleKLine(code,name){
|
||||
scale: true,
|
||||
splitArea: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
axisLabel: { show: true },
|
||||
axisLine: { show: true },
|
||||
axisTick: { show: true },
|
||||
splitLine: { show: false }
|
||||
},
|
||||
{
|
||||
scale: true,
|
||||
@@ -354,10 +364,7 @@ function handleKLine(code,name){
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
|
||||
chart.on('click',{seriesName:'日K'}, function(params) {
|
||||
//console.log("click:",params);
|
||||
});
|
||||
chart.hideLoading()
|
||||
})
|
||||
}
|
||||
function calculateMA(dayCount,values) {
|
||||
|
||||
@@ -168,7 +168,7 @@ function handleEXPLANATION(value, option){
|
||||
<template #trigger>
|
||||
<n-button tag="a" text :type="item.CHANGE_RATE>0?'error':'success'" :bordered=false >{{ item.SECURITY_NAME_ABBR }}</n-button>
|
||||
</template>
|
||||
<k-line-chart style="width: 800px" :code="item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0]" :chart-height="500" :name="item.SECURITY_NAME_ABBR" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
<k-line-chart style="width: 800px" :code="item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0]" :chart-height="500" :stockName="item.SECURITY_NAME_ABBR" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
</n-popover>
|
||||
</n-td>
|
||||
<n-td>
|
||||
|
||||
@@ -123,7 +123,7 @@ function getmMarketCode(market,code) {
|
||||
<template #trigger>
|
||||
<n-tag type="info" :bordered="false">{{item.codes[0].short_name }}</n-tag>
|
||||
</template>
|
||||
<k-line-chart style="width: 800px" :code="getmMarketCode(item.codes[0].market_code,item.codes[0].stock_code)" :chart-height="500" :name="item.codes[0].short_name" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
<k-line-chart style="width: 800px" :code="getmMarketCode(item.codes[0].market_code,item.codes[0].stock_code)" :chart-height="500" :stockName="item.codes[0].short_name" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
</n-popover>
|
||||
</n-td>
|
||||
<n-td>
|
||||
|
||||
@@ -111,7 +111,7 @@ function handleSearch(value) {
|
||||
<template #trigger>
|
||||
<n-tag type="info" :bordered="false">{{item.stockName}}</n-tag>
|
||||
</template>
|
||||
<k-line-chart style="width: 800px" :code="getmMarketCode(item.market,item.stockCode)" :chart-height="500" :name="item.stockName" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
<k-line-chart style="width: 800px" :code="getmMarketCode(item.market,item.stockCode)" :chart-height="500" :stockName="item.stockName" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
</n-popover>
|
||||
</n-td>
|
||||
<n-td><n-tag type="info" :bordered="false">{{item.indvInduName}}</n-tag></n-td>
|
||||
|
||||
@@ -121,10 +121,10 @@ EventsOn("updateVersion",async (msg) => {
|
||||
<n-space vertical >
|
||||
<n-image width="100" :src="icon" />
|
||||
<h1>
|
||||
<n-badge v-if="!vipLevel" :value="versionInfo" :offset="[50,10]" type="success">
|
||||
<n-badge v-if="!vipLevel" :value="versionInfo" :offset="[80,10]" type="success">
|
||||
<n-gradient-text type="info" :size="50" >go-stock</n-gradient-text>
|
||||
</n-badge>
|
||||
<n-badge v-if="vipLevel" :value="versionInfo" :offset="[50,10]" type="success">
|
||||
<n-badge v-if="vipLevel" :value="versionInfo" :offset="[70,10]" type="success">
|
||||
<n-gradient-text :type="expired?'error':'warning'" :size="50" >go-stock</n-gradient-text><n-tag :bordered="false" size="small" type="warning">VIP{{vipLevel}}</n-tag>
|
||||
</n-badge>
|
||||
</h1>
|
||||
@@ -166,7 +166,7 @@ EventsOn("updateVersion",async (msg) => {
|
||||
<n-td>赞助 18.8 RMB/月<br>赞助 120 RMB/年</n-td><n-td>vip1</n-td><n-td>💕 全部功能,软件自动更新(从CDN下载),更新快速便捷。AI配置指导,提示词参考等</n-td>
|
||||
</n-tr>
|
||||
<n-tr>
|
||||
<n-td>赞助 28.8 RMB/月<br>赞助 240 RMB/年</n-td><n-td>vip2</n-td><n-td>💕 vip1全部功能,赠送硅基流动AI分析服务,启动时自动同步最近24小时市场资讯(包括外媒简讯) 💕</n-td>
|
||||
<n-td>赞助 28.8 RMB/月<br>赞助 240 RMB/年</n-td><n-td>vip2</n-td><n-td>💕 vip1全部功能,启动时自动同步最近24小时市场资讯(包括外媒简讯) 💕</n-td>
|
||||
</n-tr>
|
||||
<n-tr>
|
||||
<n-td>每月赞助 X RMB</n-td><n-td>vipX</n-td><n-td>🧩 更多计划,视go-stock开源项目发展情况而定...(承接GitHub项目README广告推广💖)</n-td>
|
||||
|
||||
405
frontend/src/components/aiRecommendStocksList.vue
Normal file
405
frontend/src/components/aiRecommendStocksList.vue
Normal file
@@ -0,0 +1,405 @@
|
||||
<script setup>
|
||||
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted,onUnmounted, ref,reactive} from 'vue'
|
||||
import {
|
||||
GetAiRecommendStocksList,
|
||||
GetConfig,
|
||||
GetSponsorInfo,
|
||||
SaveAsMarkdown,
|
||||
ShareAnalysis
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {NAvatar, NButton, NEllipsis, NTag, NText, useMessage, useNotification} from "naive-ui";
|
||||
import KLineChart from "./KLineChart.vue";
|
||||
import {format} from "date-fns";
|
||||
|
||||
const notify = useNotification()
|
||||
const vipLevel=ref("");
|
||||
const vipStartTime=ref("");
|
||||
const vipEndTime=ref("");
|
||||
const expired=ref(false)
|
||||
const isValidVip=ref(false) // 是否是会员
|
||||
|
||||
onBeforeMount(()=> {
|
||||
GetConfig().then(result => {
|
||||
if (result.darkTheme) {
|
||||
editorDataRef.darkTheme = true
|
||||
}
|
||||
})
|
||||
|
||||
GetSponsorInfo().then((res) => {
|
||||
// console.log(res)
|
||||
vipLevel.value = res.vipLevel;
|
||||
vipStartTime.value = res.vipStartTime;
|
||||
vipEndTime.value = res.vipEndTime;
|
||||
//判断时间是否到期
|
||||
if (res.vipLevel) {
|
||||
if (res.vipEndTime < format(new Date(), 'yyyy-MM-dd HH:mm:ss')) {
|
||||
//notify.warning({content: 'VIP已到期'})
|
||||
expired.value = true;
|
||||
}
|
||||
}else{
|
||||
//notify.success({content: '未开通VIP'})
|
||||
}
|
||||
isValidVip.value = !(vipLevel.value === "" || Number(vipLevel.value) <= 0);
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
onMounted(() => {
|
||||
query({
|
||||
page: 1,
|
||||
pageSize: paginationReactive.pageSize,
|
||||
order: "desc",
|
||||
keyword: paginationReactive.keyword,
|
||||
startDate: paginationReactive.range[0],
|
||||
endDate: paginationReactive.range[1]
|
||||
}).then((data) => {
|
||||
console.log( data)
|
||||
dataRef.value = data.data
|
||||
paginationReactive.page = 1
|
||||
paginationReactive.pageCount = data.pageCount
|
||||
paginationReactive.itemCount = data.total
|
||||
loadingRef.value = false
|
||||
})
|
||||
})
|
||||
const message = useMessage()
|
||||
const mdPreviewRef = ref(null)
|
||||
const mdEditorRef = ref(null)
|
||||
const editorDataRef = reactive({
|
||||
show: false,
|
||||
loading: false,
|
||||
darkTheme: false,
|
||||
chatId: "",
|
||||
modelName: "",
|
||||
CreatedAt: "",
|
||||
stockName: "",
|
||||
stockCode: "",
|
||||
question: "",
|
||||
content: "",
|
||||
})
|
||||
const dataRef = ref([])
|
||||
const loadingRef = ref(true)
|
||||
|
||||
// StockClosePrice string `json:"StockClosePrice" md:"推荐时股票收盘价格"`
|
||||
// StockPrePrice string `json:"stockPrePricePrice" md:"前一交易日股票价格"`
|
||||
// RecommendReason string `json:"recommendReason" md:"推荐理由/驱动因素/逻辑"`
|
||||
// RecommendBuyPrice string `json:"recommendBuyPrice" md:"ai建议买入价"`
|
||||
// RecommendStopProfitPrice string `json:"recommendStopProfitPrice" md:"ai建议止盈价"`
|
||||
// RecommendStopLossPrice string `json:"recommendStopLossPrice" md:"ai建议止损价"`
|
||||
// RiskRemarks string `json:"riskRemarks" md:"风险提示"`
|
||||
// Remarks string `json:"remarks" md:"备注"`
|
||||
const columnsRef = ref([
|
||||
{
|
||||
title: '推荐时间',
|
||||
key: 'dataTime',
|
||||
render(row, index) {
|
||||
//2026-01-14T22:13:27.2693252+08:00 格式化为常用时间格式
|
||||
return row.CreatedAt.substring(0, 19).replace('T', ' ')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '板块概念',
|
||||
key: 'bkName'
|
||||
},
|
||||
{
|
||||
title: '股票名称',
|
||||
key: 'stockName',
|
||||
render(row, index) {
|
||||
return h(NText, { type: "info" }, { default: () => row.stockName })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '股票代码',
|
||||
key: 'stockCode'
|
||||
},
|
||||
{
|
||||
title: '最新',
|
||||
key: 'stockCurrentPrice',
|
||||
minWidth: 120,
|
||||
render(row, index) {
|
||||
|
||||
let diff = ((Number(row.stockCurrentPrice) - Number(row.stockPrePrice))/ Number(row.stockPrePrice)*100).toFixed(2)
|
||||
|
||||
if(Number(row.stockCurrentPrice)< Number(row.stockPrePrice)) {
|
||||
return [h(NText, { type: "success", bordered: false }, { default: () => row.stockCurrentPrice+` | ${diff}%` })]
|
||||
} else {
|
||||
return [h(NText, { type: "error" , bordered: false}, { default: () => row.stockCurrentPrice+` | ${diff}%` })]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '推荐时',
|
||||
key: 'stockPrice',
|
||||
render(row, index) {
|
||||
|
||||
if(vipLevel.value===""|| Number(vipLevel.value) <=0){
|
||||
return h(NText, { type: "info" }, { default: () => row.stockPrice })
|
||||
}
|
||||
|
||||
let diff = ((Number(row.stockCurrentPrice) - Number(row.stockPrice))/ Number(row.stockPrice)*100).toFixed(2)
|
||||
let flagStr="暂平"
|
||||
let flag="info"
|
||||
if(Number(row.stockCurrentPrice)>Number(row.stockPrice)) {
|
||||
flagStr="暂赢 "+diff+"%"
|
||||
flag="error"
|
||||
}else if(Number(row.stockCurrentPrice)===Number(row.stockPrice)){
|
||||
flagStr="暂平"
|
||||
flag="info"
|
||||
}else{
|
||||
flagStr="暂亏 "+ diff+"%"
|
||||
flag="success"
|
||||
}
|
||||
|
||||
return [h(NText, { type: "info" }, { default: () => row.stockPrice }),h(NTag, { type: flag,size: "tiny", bordered: false }, { default: () => flagStr })]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '昨收',
|
||||
key: 'stockPrePrice',
|
||||
render(row, index) {
|
||||
return h(NText, { type: "info" }, { default: () => row.stockPrePrice })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'ai建议买入价',
|
||||
key: 'recommendBuyPrice',
|
||||
render(row, index) {
|
||||
if(vipLevel.value===""|| Number(vipLevel.value) <=0){
|
||||
return h(NText, { type: "info" }, { default: () => row.recommendBuyPrice })
|
||||
}
|
||||
|
||||
|
||||
if(row.recommendBuyPrice.includes("-")){
|
||||
let prices= row.recommendBuyPrice.split("-")
|
||||
if(Number(row.stockCurrentPrice)>=Number(prices[0])&&Number(row.stockCurrentPrice)<=Number(prices[1])){
|
||||
return [h(NText, { type: "success" }, { default: () => row.recommendBuyPrice }),h(NTag, { type: "error", size: "tiny", bordered: false }, { default: () => "Buy" })]
|
||||
}
|
||||
}
|
||||
return h(NText, { type: "info" }, { default: () => row.recommendBuyPrice })
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'ai建议止盈价',
|
||||
key: 'recommendStopProfitPrice'
|
||||
},
|
||||
{
|
||||
title: 'ai建议止损价',
|
||||
key: 'recommendStopLossPrice'
|
||||
},
|
||||
{
|
||||
title: '推荐理由',
|
||||
key: 'recommendReason',
|
||||
ellipsis: {
|
||||
tooltip: isValidVip
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '风险提示',
|
||||
key: 'riskRemarks',
|
||||
ellipsis: {
|
||||
tooltip: isValidVip
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remarks',
|
||||
ellipsis: {
|
||||
tooltip: isValidVip
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render(row, index) {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
tertiary: true,
|
||||
size: 'small',
|
||||
type: 'warning', // 橙色按钮
|
||||
style: 'font-size: 14px; padding: 0 10px;', // 稍微大一点的按钮
|
||||
onClick: () => rowProps(row)
|
||||
},
|
||||
{ default: () => '查看详细' }
|
||||
)
|
||||
}
|
||||
},
|
||||
])
|
||||
const paginationReactive = reactive({
|
||||
page: 1,
|
||||
pageCount: 1,
|
||||
pageSize: 12,
|
||||
itemCount: 0,
|
||||
keyword: "",
|
||||
range: [new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000), new Date(new Date().getTime() + 24 * 60 * 60 * 1000)],
|
||||
prefix({ itemCount }) {
|
||||
return `${itemCount} 条记录`
|
||||
}
|
||||
})
|
||||
|
||||
const modalDataRef = reactive({
|
||||
visible: false,
|
||||
title: "",
|
||||
content: "",
|
||||
riskRemarks: "",
|
||||
stockCode: "",
|
||||
stockName: "",
|
||||
remarks: "",
|
||||
})
|
||||
|
||||
const theme = computed(() => {
|
||||
return editorDataRef.darkTheme ? 'dark' : 'light'
|
||||
})
|
||||
|
||||
|
||||
function query({
|
||||
page,
|
||||
pageSize = 10,
|
||||
order = 'desc',
|
||||
keyword = "",
|
||||
startDate = "",
|
||||
endDate = ""
|
||||
}) {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
GetAiRecommendStocksList({
|
||||
"page": page,
|
||||
"pageSize": pageSize,
|
||||
"modelName":keyword,
|
||||
"stockName":keyword,
|
||||
"stockCode":keyword,
|
||||
"bkName":keyword,
|
||||
"startDate": startDate,
|
||||
"endDate": endDate
|
||||
}).then((res) => {
|
||||
const pagedData =res.list
|
||||
const total = res.total
|
||||
const pageCount =res.totalPages
|
||||
resolve({
|
||||
pageCount,
|
||||
data: pagedData,
|
||||
total
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handlePageChange(currentPage) {
|
||||
if (!loadingRef.value) {
|
||||
loadingRef.value = true
|
||||
query({
|
||||
page: currentPage,
|
||||
pageSize: paginationReactive.pageSize,
|
||||
order: "desc",
|
||||
keyword: paginationReactive.keyword,
|
||||
startDate: formatDate(paginationReactive.range[0]), // Format date to string
|
||||
endDate: formatDate(paginationReactive.range[1]) // Format date to string
|
||||
}).then((data) => {
|
||||
dataRef.value = data.data
|
||||
paginationReactive.page = currentPage
|
||||
paginationReactive.pageCount = data.pageCount
|
||||
paginationReactive.itemCount = data.total
|
||||
loadingRef.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
function handleSearch() {
|
||||
if (!loadingRef.value) {
|
||||
loadingRef.value = true
|
||||
query({
|
||||
page: 1,
|
||||
pageSize: paginationReactive.pageSize,
|
||||
order: "desc",
|
||||
keyword: paginationReactive.keyword,
|
||||
startDate: formatDate(paginationReactive.range[0]),
|
||||
endDate: formatDate(paginationReactive.range[1])
|
||||
}).then((data) => {
|
||||
dataRef.value = data.data
|
||||
paginationReactive.page = 1
|
||||
paginationReactive.pageCount = data.pageCount
|
||||
paginationReactive.itemCount = data.total
|
||||
loadingRef.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
function getStockCode(stockCode) {
|
||||
if(stockCode.indexOf( ".")>0){
|
||||
stockCode=stockCode.split(".")[1]+stockCode.split(".")[0]
|
||||
}
|
||||
//转化为小写
|
||||
stockCode=stockCode.toLowerCase()
|
||||
return stockCode
|
||||
|
||||
}
|
||||
|
||||
function rowProps(row) {
|
||||
return {
|
||||
style: 'cursor: pointer;',
|
||||
onClick: () => {
|
||||
if(vipLevel.value===""|| Number(vipLevel.value) <=0){
|
||||
notify.warning({content: '未开通VIP或者已经过期'})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//message.info(row.stockName)
|
||||
modalDataRef.title = row.stockName
|
||||
modalDataRef.content = row.recommendReason
|
||||
modalDataRef.riskRemarks = row.riskRemarks
|
||||
modalDataRef.stockCode = getStockCode(row.stockCode)
|
||||
modalDataRef.stockName = row.stockName
|
||||
modalDataRef.visible = true
|
||||
modalDataRef.remarks = row.remarks
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-input-group>
|
||||
<n-date-picker v-model:value="paginationReactive.range" type="datetimerange" style="width: 50%"/>
|
||||
<n-input clearable placeholder="输入关键词搜索" v-model:value="paginationReactive.keyword"/>
|
||||
<n-button type="primary" ghost @click="handleSearch" @input="handleSearch">
|
||||
搜索
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<n-data-table
|
||||
remote
|
||||
:row-props="rowProps"
|
||||
size="small"
|
||||
:columns="columnsRef"
|
||||
:data="dataRef"
|
||||
:loading="loadingRef"
|
||||
:pagination="paginationReactive"
|
||||
:row-key="(rowData)=>rowData.ID"
|
||||
@update:page="handlePageChange"
|
||||
flex-height
|
||||
style="height: calc(100vh - 210px);margin-top: 10px"
|
||||
/>
|
||||
|
||||
<n-modal v-model:show="modalDataRef.visible" :title="modalDataRef.title" preset="card" style="width: 850px;">
|
||||
<n-gradient-text :size="16" type="warning">{{modalDataRef.remarks}}</n-gradient-text>
|
||||
<n-card size="small">
|
||||
<KLineChart style="width: 800px" :code="getStockCode(modalDataRef.stockCode)" :chart-height="500" :stock-name="modalDataRef.stockName" :k-days="30" :dark-theme="editorDataRef.darkTheme"></KLineChart>
|
||||
</n-card>
|
||||
<n-card size="small">
|
||||
<n-text type="info">{{modalDataRef.content}}</n-text>
|
||||
<n-divider><n-gradient-text type="error">风险提示</n-gradient-text></n-divider>
|
||||
<n-text type="error">{{modalDataRef.riskRemarks}}</n-text>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -411,31 +411,31 @@ function ReFlesh(source) {
|
||||
</n-grid>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="上证指数" tab="上证指数">
|
||||
<k-line-chart code="sh000001" :chart-height="panelHeight" name="上证指数" :k-days="20"
|
||||
<k-line-chart code="sh000001" :chart-height="panelHeight" stockName="上证指数" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="深证成指" tab="深证成指">
|
||||
<k-line-chart code="sz399001" :chart-height="panelHeight" name="深证成指" :k-days="20"
|
||||
<k-line-chart code="sz399001" :chart-height="panelHeight" stockName="深证成指" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="创业板指" tab="创业板指">
|
||||
<k-line-chart code="sz399006" :chart-height="panelHeight" name="创业板指" :k-days="20"
|
||||
<k-line-chart code="sz399006" :chart-height="panelHeight" stockName="创业板指" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="恒生指数" tab="恒生指数">
|
||||
<k-line-chart code="hkHSI" :chart-height="panelHeight" name="恒生指数" :k-days="20"
|
||||
<k-line-chart code="hkHSI" :chart-height="panelHeight" stockName="恒生指数" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="纳斯达克" tab="纳斯达克">
|
||||
<k-line-chart code="us.IXIC" :chart-height="panelHeight" name="纳斯达克" :k-days="20"
|
||||
<k-line-chart code="us.IXIC" :chart-height="panelHeight" stockName="纳斯达克" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="道琼斯" tab="道琼斯">
|
||||
<k-line-chart code="us.DJI" :chart-height="panelHeight" name="道琼斯" :k-days="20"
|
||||
<k-line-chart code="us.DJI" :chart-height="panelHeight" stockName="道琼斯" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="标普500" tab="标普500">
|
||||
<k-line-chart code="us.INX" :chart-height="panelHeight" name="标普500" :k-days="20"
|
||||
<k-line-chart code="us.INX" :chart-height="panelHeight" stockName="标普500" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
@@ -443,59 +443,59 @@ function ReFlesh(source) {
|
||||
<n-tab-pane name="重大指数" tab="重大指数">
|
||||
<n-tabs type="segment" animated>
|
||||
<n-tab-pane name="恒生科技指数" tab="恒生科技指数">
|
||||
<k-line-chart code="hkHSTECH" :chart-height="panelHeight" name="恒生科技指数" :k-days="20"
|
||||
<k-line-chart code="hkHSTECH" :chart-height="panelHeight" stockName="恒生科技指数" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="科创50" tab="科创50" >
|
||||
<k-line-chart code="sh000688" :chart-height="panelHeight" name="科创50" :k-days="20"
|
||||
<k-line-chart code="sh000688" :chart-height="panelHeight" stockName="科创50" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="科创芯片" tab="科创芯片" >
|
||||
<k-line-chart code="sh000685" :chart-height="panelHeight" name="科创芯片" :k-days="20"
|
||||
<k-line-chart code="sh000685" :chart-height="panelHeight" stockName="科创芯片" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="证券龙头" tab="证券龙头" >
|
||||
<k-line-chart code="sz399437" :chart-height="panelHeight" name="证券龙头" :k-days="20"
|
||||
<k-line-chart code="sz399437" :chart-height="panelHeight" stockName="证券龙头" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="高端装备" tab="高端装备" >
|
||||
<k-line-chart code="sz399437" :chart-height="panelHeight" name="高端装备" :k-days="20"
|
||||
<k-line-chart code="sz399437" :chart-height="panelHeight" stockName="高端装备" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="中证银行" tab="中证银行">
|
||||
<k-line-chart code="sz399986" :chart-height="panelHeight" name="中证银行" :k-days="20"
|
||||
<k-line-chart code="sz399986" :chart-height="panelHeight" stockName="中证银行" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="上证医药" tab="上证医药">
|
||||
<k-line-chart code="sh000037" :chart-height="panelHeight" name="上证医药" :k-days="20"
|
||||
<k-line-chart code="sh000037" :chart-height="panelHeight" stockName="上证医药" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="沪深300" tab="沪深300">
|
||||
<k-line-chart code="sh000300" :chart-height="panelHeight" name="沪深300" :k-days="20"
|
||||
<k-line-chart code="sh000300" :chart-height="panelHeight" stockName="沪深300" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="上证50" tab="上证50">
|
||||
<k-line-chart code="sh000016" :chart-height="panelHeight" name="上证50" :k-days="20"
|
||||
<k-line-chart code="sh000016" :chart-height="panelHeight" stockName="上证50" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="中证A500" tab="中证A500">
|
||||
<k-line-chart code="sh000510" :chart-height="panelHeight" name="中证A500" :k-days="20"
|
||||
<k-line-chart code="sh000510" :chart-height="panelHeight" stockName="中证A500" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="中证1000" tab="中证1000">
|
||||
<k-line-chart code="sh000852" :chart-height="panelHeight" name="中证1000" :k-days="20"
|
||||
<k-line-chart code="sh000852" :chart-height="panelHeight" stockName="中证1000" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="中证白酒" tab="中证白酒">
|
||||
<k-line-chart code="sz399997" :chart-height="panelHeight" name="中证白酒" :k-days="20"
|
||||
<k-line-chart code="sz399997" :chart-height="panelHeight" stockName="中证白酒" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="富时中国三倍做多" tab="富时中国三倍做多">
|
||||
<k-line-chart code="usYINN.AM" :chart-height="panelHeight" name="富时中国三倍做多" :k-days="20"
|
||||
<k-line-chart code="usYINN.AM" :chart-height="panelHeight" stockName="富时中国三倍做多" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="VIX恐慌指数" tab="VIX恐慌指数">
|
||||
<k-line-chart code="usUVXY.AM" :chart-height="panelHeight" name="VIX恐慌指数" :k-days="20"
|
||||
<k-line-chart code="usUVXY.AM" :chart-height="panelHeight" stockName="VIX恐慌指数" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
@@ -72,7 +72,7 @@ function GetMoneyRankSinaData(){
|
||||
<template #trigger>
|
||||
<n-button tag="a" text :type="item.changeratio>0?'error':'success'" :bordered=false >{{ item.name }}</n-button>
|
||||
</template>
|
||||
<k-line-chart style="width: 800px" :code="item.symbol" :chart-height="500" :name="item.name" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
<k-line-chart style="width: 800px" :code="item.symbol" :chart-height="500" :stockName="item.name" :k-days="20" :dark-theme="true"></k-line-chart>
|
||||
</n-popover>
|
||||
</n-td>
|
||||
<n-td><n-text :type="item.changeratio>0?'error':'success'">{{item.trade}}</n-text></n-td>
|
||||
|
||||
49
frontend/src/components/researchIndex.vue
Normal file
49
frontend/src/components/researchIndex.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup>
|
||||
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted,onUnmounted, ref,reactive} from 'vue'
|
||||
import {GetAIResponseResultList} from "../../wailsjs/go/main/App";
|
||||
import {NButton, NEllipsis, NText} from "naive-ui";
|
||||
import ResearchReport from "./researchReport.vue";
|
||||
import AiRecommendStocksList from "./aiRecommendStocksList.vue";
|
||||
import {EventsOff, EventsOn} from "../../wailsjs/runtime";
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
|
||||
const nowTab = ref("AI分析报告")
|
||||
const route = useRoute()
|
||||
onBeforeMount(() => {
|
||||
nowTab.value = route.query.name
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
EventsOff("changeResearchTab")
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
|
||||
EventsOn("changeResearchTab", async (msg) => {
|
||||
console.log("changeResearchTab", msg)
|
||||
updateTab(msg.name)
|
||||
})
|
||||
function updateTab(name) {
|
||||
nowTab.value = name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card>
|
||||
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab" style="--wails-draggable:no-drag">
|
||||
<n-tab-pane name="AI分析报告">
|
||||
<ResearchReport/>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="股票推荐记录">
|
||||
<AiRecommendStocksList/>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
292
frontend/src/components/researchReport.vue
Normal file
292
frontend/src/components/researchReport.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<script setup>
|
||||
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted,onUnmounted, ref,reactive} from 'vue'
|
||||
import {GetAIResponseResultList, GetConfig, SaveAsMarkdown, ShareAnalysis} from "../../wailsjs/go/main/App";
|
||||
import {NAvatar, NButton, NEllipsis, NText, useMessage} from "naive-ui";
|
||||
import {MdEditor, MdPreview} from 'md-editor-v3';
|
||||
|
||||
|
||||
|
||||
onBeforeMount(()=> {
|
||||
GetConfig().then(result => {
|
||||
if (result.darkTheme) {
|
||||
editorDataRef.darkTheme = true
|
||||
}
|
||||
})
|
||||
})
|
||||
onMounted(() => {
|
||||
query({
|
||||
page: 1,
|
||||
pageSize: paginationReactive.pageSize,
|
||||
order: "desc",
|
||||
keyword: paginationReactive.keyword,
|
||||
startDate: paginationReactive.range[0],
|
||||
endDate: paginationReactive.range[1]
|
||||
}).then((data) => {
|
||||
console.log( data)
|
||||
dataRef.value = data.data
|
||||
paginationReactive.page = 1
|
||||
paginationReactive.pageCount = data.pageCount
|
||||
paginationReactive.itemCount = data.total
|
||||
loadingRef.value = false
|
||||
})
|
||||
})
|
||||
const message = useMessage()
|
||||
const mdPreviewRef = ref(null)
|
||||
const mdEditorRef = ref(null)
|
||||
const editorDataRef = reactive({
|
||||
show: false,
|
||||
loading: false,
|
||||
darkTheme: false,
|
||||
chatId: "",
|
||||
modelName: "",
|
||||
CreatedAt: "",
|
||||
stockName: "",
|
||||
stockCode: "",
|
||||
question: "",
|
||||
content: "",
|
||||
})
|
||||
const dataRef = ref([])
|
||||
const loadingRef = ref(true)
|
||||
const columnsRef = ref([
|
||||
{
|
||||
title: '分析时间',
|
||||
key: 'CreatedAt',
|
||||
render(row, index) {
|
||||
//2026-01-14T22:13:27.2693252+08:00 格式化为常用时间格式
|
||||
return row.CreatedAt.substring(0, 19).replace('T', ' ')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '模型名称',
|
||||
key: 'modelName'
|
||||
},
|
||||
{
|
||||
title: '分析对象',
|
||||
key: 'stockName'
|
||||
},
|
||||
{
|
||||
title: '提示词',
|
||||
key: 'question',
|
||||
render(row, index) {
|
||||
return h(NEllipsis, { tooltip: true ,style: "max-width: 240px;"}, {default: () => h(NText,{type: "info"},{default: () => row.question}),})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render(row, index) {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
tertiary: true,
|
||||
size: 'small',
|
||||
type: 'warning', // 橙色按钮
|
||||
style: 'font-size: 14px; padding: 0 10px;', // 稍微大一点的按钮
|
||||
onClick: () => showReport(row)
|
||||
},
|
||||
{ default: () => '查看分析报告' }
|
||||
)
|
||||
}
|
||||
},
|
||||
])
|
||||
const paginationReactive = reactive({
|
||||
page: 1,
|
||||
pageCount: 1,
|
||||
pageSize: 12,
|
||||
itemCount: 0,
|
||||
keyword: "",
|
||||
range: [new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000), new Date(new Date().getTime() + 24 * 60 * 60 * 1000)],
|
||||
prefix({ itemCount }) {
|
||||
return `${itemCount} 条记录`
|
||||
}
|
||||
})
|
||||
const theme = computed(() => {
|
||||
return editorDataRef.darkTheme ? 'dark' : 'light'
|
||||
})
|
||||
function showReport(row) {
|
||||
|
||||
editorDataRef.show = true
|
||||
editorDataRef.chatId = row.chatId
|
||||
editorDataRef.modelName = row.modelName
|
||||
editorDataRef.CreatedAt = row.CreatedAt.substring(0, 19).replace('T', ' ')
|
||||
editorDataRef.stockName = row.stockName
|
||||
editorDataRef.stockCode = row.stockCode
|
||||
editorDataRef.question = row.question
|
||||
editorDataRef.content = row.content
|
||||
editorDataRef.loading = false
|
||||
}
|
||||
|
||||
function query({
|
||||
page,
|
||||
pageSize = 10,
|
||||
order = 'desc',
|
||||
keyword = "",
|
||||
startDate = "",
|
||||
endDate = ""
|
||||
}) {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
GetAIResponseResultList({
|
||||
"page": page,
|
||||
"pageSize": pageSize,
|
||||
"modelName":keyword,
|
||||
"question":keyword,
|
||||
"stockName":keyword,
|
||||
"stockCode":keyword,
|
||||
"startDate":startDate,
|
||||
"endDate":endDate
|
||||
}).then((res) => {
|
||||
const pagedData =res.list
|
||||
const total = res.total
|
||||
const pageCount =res.totalPages
|
||||
resolve({
|
||||
pageCount,
|
||||
data: pagedData,
|
||||
total
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handlePageChange(currentPage) {
|
||||
if (!loadingRef.value) {
|
||||
loadingRef.value = true
|
||||
query({
|
||||
page: currentPage,
|
||||
pageSize: paginationReactive.pageSize,
|
||||
order: "desc",
|
||||
keyword: paginationReactive.keyword,
|
||||
startDate: formatDate(paginationReactive.range[0]),
|
||||
endDate: formatDate(paginationReactive.range[1])
|
||||
}).then((data) => {
|
||||
dataRef.value = data.data
|
||||
paginationReactive.page = currentPage
|
||||
paginationReactive.pageCount = data.pageCount
|
||||
paginationReactive.itemCount = data.total
|
||||
loadingRef.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
function handleSearch() {
|
||||
if (!loadingRef.value) {
|
||||
loadingRef.value = true
|
||||
query({
|
||||
page: 1,
|
||||
pageSize: paginationReactive.pageSize,
|
||||
order: "desc",
|
||||
keyword: paginationReactive.keyword,
|
||||
startDate: formatDate(paginationReactive.range[0]),
|
||||
endDate: formatDate(paginationReactive.range[1])
|
||||
}).then((data) => {
|
||||
dataRef.value = data.data
|
||||
paginationReactive.page = 1
|
||||
paginationReactive.pageCount = data.pageCount
|
||||
paginationReactive.itemCount = data.total
|
||||
loadingRef.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
function share(code, name) {
|
||||
ShareAnalysis(code, name).then(msg => {
|
||||
//message.info(msg)
|
||||
notify.info({
|
||||
avatar: () =>
|
||||
h(NAvatar, {
|
||||
size: 'small',
|
||||
round: false,
|
||||
src: icon.value
|
||||
}),
|
||||
title: '分享到社区',
|
||||
duration: 1000 * 30,
|
||||
content: () => {
|
||||
return h('div', {
|
||||
style: {
|
||||
'text-align': 'left',
|
||||
'font-size': '14px',
|
||||
}
|
||||
}, {default: () => msg})
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function saveAsMarkdown(code,name) {
|
||||
SaveAsMarkdown(code, name).then(result => {
|
||||
if(result !== ""){
|
||||
message.success(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
async function copyToClipboard() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(editorDataRef.content);
|
||||
message.success('分析结果已复制到剪切板');
|
||||
} catch (err) {
|
||||
message.error('复制失败: ' + err);
|
||||
}
|
||||
}
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-input-group>
|
||||
<n-date-picker v-model:value="paginationReactive.range" type="datetimerange" style="width: 50%"/>
|
||||
<n-input clearable placeholder="输入关键词搜索" v-model:value="paginationReactive.keyword"/>
|
||||
<n-button type="primary" ghost @click="handleSearch" @input="handleSearch">
|
||||
搜索
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<n-data-table
|
||||
remote
|
||||
size="small"
|
||||
:columns="columnsRef"
|
||||
:data="dataRef"
|
||||
:loading="loadingRef"
|
||||
:pagination="paginationReactive"
|
||||
:row-key="(rowData)=>rowData.ID"
|
||||
@update:page="handlePageChange"
|
||||
flex-height
|
||||
style="height: calc(100vh - 210px);margin-top: 10px"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<n-modal transform-origin="center" v-model:show="editorDataRef.show" preset="card" style="width: 800px;"
|
||||
:title="'['+editorDataRef.stockName+']AI分析'">
|
||||
<n-spin size="small" :show="editorDataRef.loading">
|
||||
<MdPreview ref="mdPreviewRef" style="height: 540px;text-align: left"
|
||||
:modelValue="editorDataRef.content" :theme="theme"/>
|
||||
</n-spin>
|
||||
<template #footer>
|
||||
<n-flex justify="space-between" ref="tipsRef">
|
||||
<n-text type="info" v-if="editorDataRef.chatId">
|
||||
<n-tag v-if="editorDataRef.modelName" type="warning" round :title="editorDataRef.chatId" :bordered="false">
|
||||
{{ editorDataRef.modelName }}
|
||||
</n-tag>
|
||||
{{ editorDataRef.CreatedAt }}
|
||||
</n-text>
|
||||
<n-text type="error">*AI分析结果仅供参考,请以实际行情为准。投资需谨慎,风险自担。</n-text>
|
||||
</n-flex>
|
||||
</template>
|
||||
<template #action>
|
||||
<n-flex justify="right">
|
||||
<n-button size="tiny" type="success" @click="copyToClipboard">复制到剪切板</n-button>
|
||||
<n-button size="tiny" type="primary" @click="saveAsMarkdown(editorDataRef.stockCode,editorDataRef.stockName)">保存为Markdown文件</n-button>
|
||||
<n-button size="tiny" type="error" @click="share(editorDataRef.stockCode,editorDataRef.stockName)">分享到项目社区</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -34,6 +34,8 @@ const formValue = ref({
|
||||
questionTemplate: "{{stockName}}分析和总结",
|
||||
crawlTimeOut: 30,
|
||||
kDays: 30,
|
||||
httpProxy:"",
|
||||
httpProxyEnabled:false,
|
||||
},
|
||||
enableDanmu: false,
|
||||
browserPath: '',
|
||||
@@ -57,8 +59,10 @@ function addAiConfig() {
|
||||
apiKey: '',
|
||||
modelName: 'deepseek-chat',
|
||||
temperature: 0.1,
|
||||
maxTokens: 1024,
|
||||
maxTokens: 4096,
|
||||
timeOut: 60,
|
||||
httpProxy:"",
|
||||
httpProxyEnabled:false,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -92,6 +96,8 @@ onMounted(() => {
|
||||
questionTemplate: res.questionTemplate ? res.questionTemplate : '{{stockName}}分析和总结',
|
||||
crawlTimeOut: res.crawlTimeOut,
|
||||
kDays: res.kDays,
|
||||
httpProxy:"",
|
||||
httpProxyEnabled:false,
|
||||
}
|
||||
|
||||
|
||||
@@ -388,14 +394,14 @@ 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-form-item-gi :span="2" label="爬虫http代理" path="httpProxyEnabled">
|
||||
<n-switch v-model:value="formValue.httpProxyEnabled"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" v-if="formValue.httpProxyEnabled" title="http代理地址"
|
||||
label="http代理地址" path="httpProxy">
|
||||
<n-input type="text" placeholder="http代理地址" v-model:value="formValue.httpProxy" clearable/>
|
||||
<n-input type="text" placeholder="爬虫http代理地址" v-model:value="formValue.httpProxy" clearable/>
|
||||
</n-form-item-gi>
|
||||
|
||||
|
||||
@@ -452,6 +458,12 @@ function deletePrompt(ID) {
|
||||
<n-form-item-gi :span="5" label="Timeout(秒)" :path="`openAI.aiConfigs[${index}].timeOut`">
|
||||
<n-input-number min="60" step="1" placeholder="超时(秒)" v-model:value="aiConfig.timeOut"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" label="http代理" :path="`openAI.aiConfigs[${index}].httpProxyEnabled`">
|
||||
<n-switch v-model:value="aiConfig.httpProxyEnabled"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" v-if="aiConfig.httpProxyEnabled" title="http代理地址" :path="`openAI.aiConfigs[${index}].httpProxy`">
|
||||
<n-input type="text" placeholder="http代理地址" v-model:value="aiConfig.httpProxy" clearable/>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
<n-button type="primary" dashed @click="addAiConfig" style="width: 100%;">+ 添加AI配置</n-button>
|
||||
|
||||
@@ -6,6 +6,7 @@ import aboutView from "../components/about.vue";
|
||||
import fundView from "../components/fund.vue";
|
||||
import marketView from "../components/market.vue";
|
||||
import agentChat from "../components/agent-chat.vue"
|
||||
import research from "../components/researchIndex.vue";
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: stockView,name: 'stock'},
|
||||
@@ -14,6 +15,8 @@ const routes = [
|
||||
{ path: '/about', component: aboutView,name: 'about' },
|
||||
{ path: '/market', component: marketView,name: 'market' },
|
||||
{ path: '/agent', component: agentChat,name: 'agent' },
|
||||
{ path: '/research', component: research,name: 'research' },
|
||||
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
8
frontend/wailsjs/go/main/App.d.ts
vendored
8
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -16,6 +16,8 @@ export function AnalyzeSentiment(arg1:string):Promise<models.SentimentResult>;
|
||||
|
||||
export function AnalyzeSentimentWithFreqWeight(arg1:string):Promise<Record<string, any>>;
|
||||
|
||||
export function BatchDeleteAIResponseResult(arg1:Array<number>):Promise<string>;
|
||||
|
||||
export function ChatWithAgent(arg1:string,arg2:number,arg3:any):Promise<void>;
|
||||
|
||||
export function CheckSponsorCode(arg1:string):Promise<Record<string, any>>;
|
||||
@@ -28,6 +30,8 @@ export function ClsCalendar():Promise<Array<any>>;
|
||||
|
||||
export function DelPrompt(arg1:number):Promise<string>;
|
||||
|
||||
export function DeleteAIResponseResult(arg1:string):Promise<string>;
|
||||
|
||||
export function EMDictCode(arg1:string):Promise<Array<any>>;
|
||||
|
||||
export function ExportConfig():Promise<string>;
|
||||
@@ -38,8 +42,12 @@ export function FollowFund(arg1:string):Promise<string>;
|
||||
|
||||
export function GetAIResponseResult(arg1:string):Promise<models.AIResponseResult>;
|
||||
|
||||
export function GetAIResponseResultList(arg1:models.AIResponseResultQuery):Promise<models.AIResponseResultPageData>;
|
||||
|
||||
export function GetAiConfigs():Promise<Array<data.AIConfig>>;
|
||||
|
||||
export function GetAiRecommendStocksList(arg1:models.AiRecommendStocksQuery):Promise<models.AiRecommendStocksPageData>;
|
||||
|
||||
export function GetConfig():Promise<data.SettingConfig>;
|
||||
|
||||
export function GetFollowList(arg1:number):Promise<any>;
|
||||
|
||||
@@ -26,6 +26,10 @@ export function AnalyzeSentimentWithFreqWeight(arg1) {
|
||||
return window['go']['main']['App']['AnalyzeSentimentWithFreqWeight'](arg1);
|
||||
}
|
||||
|
||||
export function BatchDeleteAIResponseResult(arg1) {
|
||||
return window['go']['main']['App']['BatchDeleteAIResponseResult'](arg1);
|
||||
}
|
||||
|
||||
export function ChatWithAgent(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['ChatWithAgent'](arg1, arg2, arg3);
|
||||
}
|
||||
@@ -50,6 +54,10 @@ export function DelPrompt(arg1) {
|
||||
return window['go']['main']['App']['DelPrompt'](arg1);
|
||||
}
|
||||
|
||||
export function DeleteAIResponseResult(arg1) {
|
||||
return window['go']['main']['App']['DeleteAIResponseResult'](arg1);
|
||||
}
|
||||
|
||||
export function EMDictCode(arg1) {
|
||||
return window['go']['main']['App']['EMDictCode'](arg1);
|
||||
}
|
||||
@@ -70,10 +78,18 @@ export function GetAIResponseResult(arg1) {
|
||||
return window['go']['main']['App']['GetAIResponseResult'](arg1);
|
||||
}
|
||||
|
||||
export function GetAIResponseResultList(arg1) {
|
||||
return window['go']['main']['App']['GetAIResponseResultList'](arg1);
|
||||
}
|
||||
|
||||
export function GetAiConfigs() {
|
||||
return window['go']['main']['App']['GetAiConfigs']();
|
||||
}
|
||||
|
||||
export function GetAiRecommendStocksList(arg1) {
|
||||
return window['go']['main']['App']['GetAiRecommendStocksList'](arg1);
|
||||
}
|
||||
|
||||
export function GetConfig() {
|
||||
return window['go']['main']['App']['GetConfig']();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ export namespace data {
|
||||
maxTokens: number;
|
||||
temperature: number;
|
||||
timeOut: number;
|
||||
httpProxy: string;
|
||||
httpProxyEnabled: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AIConfig(source);
|
||||
@@ -30,6 +32,8 @@ export namespace data {
|
||||
this.maxTokens = source["maxTokens"];
|
||||
this.temperature = source["temperature"];
|
||||
this.timeOut = source["timeOut"];
|
||||
this.httpProxy = source["httpProxy"];
|
||||
this.httpProxyEnabled = source["httpProxyEnabled"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
@@ -707,6 +711,210 @@ export namespace models {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class AIResponseResultPageData {
|
||||
list: AIResponseResult[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AIResponseResultPageData(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.list = this.convertValues(source["list"], AIResponseResult);
|
||||
this.total = source["total"];
|
||||
this.page = source["page"];
|
||||
this.pageSize = source["pageSize"];
|
||||
this.totalPages = source["totalPages"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class AIResponseResultQuery {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
chatId: string;
|
||||
modelName: string;
|
||||
stockCode: string;
|
||||
stockName: string;
|
||||
question: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AIResponseResultQuery(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.page = source["page"];
|
||||
this.pageSize = source["pageSize"];
|
||||
this.chatId = source["chatId"];
|
||||
this.modelName = source["modelName"];
|
||||
this.stockCode = source["stockCode"];
|
||||
this.stockName = source["stockName"];
|
||||
this.question = source["question"];
|
||||
this.startDate = source["startDate"];
|
||||
this.endDate = source["endDate"];
|
||||
}
|
||||
}
|
||||
export class AiRecommendStocks {
|
||||
ID: number;
|
||||
// Go type: time
|
||||
CreatedAt: any;
|
||||
// Go type: time
|
||||
UpdatedAt: any;
|
||||
// Go type: gorm
|
||||
DeletedAt: any;
|
||||
// Go type: time
|
||||
dataTime?: any;
|
||||
modelName: string;
|
||||
stockCode: string;
|
||||
stockName: string;
|
||||
bkCode: string;
|
||||
bkName: string;
|
||||
stockPrice: string;
|
||||
stockCurrentPrice: string;
|
||||
stockCurrentPriceTime: string;
|
||||
stockClosePrice: string;
|
||||
stockPrePrice: string;
|
||||
recommendReason: string;
|
||||
recommendBuyPrice: string;
|
||||
recommendStopProfitPrice: string;
|
||||
recommendStopLossPrice: string;
|
||||
riskRemarks: string;
|
||||
remarks: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AiRecommendStocks(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.ID = source["ID"];
|
||||
this.CreatedAt = this.convertValues(source["CreatedAt"], null);
|
||||
this.UpdatedAt = this.convertValues(source["UpdatedAt"], null);
|
||||
this.DeletedAt = this.convertValues(source["DeletedAt"], null);
|
||||
this.dataTime = this.convertValues(source["dataTime"], null);
|
||||
this.modelName = source["modelName"];
|
||||
this.stockCode = source["stockCode"];
|
||||
this.stockName = source["stockName"];
|
||||
this.bkCode = source["bkCode"];
|
||||
this.bkName = source["bkName"];
|
||||
this.stockPrice = source["stockPrice"];
|
||||
this.stockCurrentPrice = source["stockCurrentPrice"];
|
||||
this.stockCurrentPriceTime = source["stockCurrentPriceTime"];
|
||||
this.stockClosePrice = source["stockClosePrice"];
|
||||
this.stockPrePrice = source["stockPrePrice"];
|
||||
this.recommendReason = source["recommendReason"];
|
||||
this.recommendBuyPrice = source["recommendBuyPrice"];
|
||||
this.recommendStopProfitPrice = source["recommendStopProfitPrice"];
|
||||
this.recommendStopLossPrice = source["recommendStopLossPrice"];
|
||||
this.riskRemarks = source["riskRemarks"];
|
||||
this.remarks = source["remarks"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class AiRecommendStocksPageData {
|
||||
list: AiRecommendStocks[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AiRecommendStocksPageData(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.list = this.convertValues(source["list"], AiRecommendStocks);
|
||||
this.total = source["total"];
|
||||
this.page = source["page"];
|
||||
this.pageSize = source["pageSize"];
|
||||
this.totalPages = source["totalPages"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class AiRecommendStocksQuery {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
stockCode: string;
|
||||
stockName: string;
|
||||
bkCode: string;
|
||||
bkName: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AiRecommendStocksQuery(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.page = source["page"];
|
||||
this.pageSize = source["pageSize"];
|
||||
this.stockCode = source["stockCode"];
|
||||
this.stockName = source["stockName"];
|
||||
this.bkCode = source["bkCode"];
|
||||
this.bkName = source["bkName"];
|
||||
this.startDate = source["startDate"];
|
||||
this.endDate = source["endDate"];
|
||||
}
|
||||
}
|
||||
export class Prompt {
|
||||
ID: number;
|
||||
name: string;
|
||||
|
||||
9
main.go
9
main.go
@@ -152,9 +152,9 @@ func main() {
|
||||
BackgroundColour: backgroundColour,
|
||||
Assets: assets,
|
||||
Menu: AppMenu,
|
||||
Logger: nil,
|
||||
Logger: logger.NewFileLogger("./logs/wails.log"),
|
||||
LogLevel: logger.DEBUG,
|
||||
LogLevelProduction: logger.ERROR,
|
||||
LogLevelProduction: logger.INFO,
|
||||
OnStartup: app.startup,
|
||||
OnDomReady: app.domReady,
|
||||
OnBeforeClose: app.beforeClose,
|
||||
@@ -189,7 +189,7 @@ func main() {
|
||||
WindowIsTranslucent: true,
|
||||
About: &mac.AboutInfo{
|
||||
Title: "go-stock",
|
||||
Message: "",
|
||||
Message: "go-stock:AI赋能股票分析✨ ",
|
||||
Icon: icon,
|
||||
},
|
||||
},
|
||||
@@ -243,8 +243,9 @@ func AutoMigrate() {
|
||||
db.Dao.AutoMigrate(&models.BKDict{})
|
||||
db.Dao.AutoMigrate(&models.WordAnalyze{})
|
||||
db.Dao.AutoMigrate(&models.SentimentResultAnalyze{})
|
||||
db.Dao.AutoMigrate(&models.AiRecommendStocks{})
|
||||
|
||||
updateMultipleModel()
|
||||
//updateMultipleModel()
|
||||
}
|
||||
|
||||
func initStockDataUS(ctx context.Context) {
|
||||
|
||||
Reference in New Issue
Block a user