diff --git a/cmd/cli/main.go b/cmd/cli/main.go index c0c093b..8a58a3c 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -22,6 +22,18 @@ func main() { config.New(ServiceKey) impl.NewImpl() + code := "601899.SH" + model := models.NewStratModel("selector", code) + stratRule := rule.NewRule(model) + stratRule.RunAi(code) + +} + +func main2() { + log.Println("Hello Cli!") + config.New(ServiceKey) + impl.NewImpl() + for _, code := range strategy.GetStocks() { strategy.InitCacheByCode(code) model := models.NewStratModel("selector", code) @@ -40,7 +52,7 @@ func main() { } } -func main2() { +func main3() { log.Println("Hello Cli!") config.New(ServiceKey) impl.NewImpl() diff --git a/etc/gostock_dev.yaml b/etc/gostock_dev.yaml index 145b9fb..d22b043 100644 --- a/etc/gostock_dev.yaml +++ b/etc/gostock_dev.yaml @@ -9,6 +9,8 @@ Databases: # cache DB的选择请在后面直接带参数,不带会自动HASH计算选择DB库。 Cache: redis://null:Weidong2023~!@8.137.107.29:19379/ +# 密钥 +DeepSeekApiKey: sk-7e69ad7a07d74c409ad52bfab18be786 # 日志配置 Log: diff --git a/etc/gostock_prod.yaml b/etc/gostock_prod.yaml index 4eb2083..dea0cff 100644 --- a/etc/gostock_prod.yaml +++ b/etc/gostock_prod.yaml @@ -9,6 +9,8 @@ Databases: # cache DB的选择请在后面直接带参数,不带会自动HASH计算选择DB库。 Cache: redis://null:Weidong2023~!@8.137.107.29:19379/ +# 密钥 +DeepSeekApiKey: sk-7e69ad7a07d74c409ad52bfab18be786 # 日志配置 Log: diff --git a/etc/gostock_test.yaml b/etc/gostock_test.yaml index 4eb2083..dea0cff 100644 --- a/etc/gostock_test.yaml +++ b/etc/gostock_test.yaml @@ -9,6 +9,8 @@ Databases: # cache DB的选择请在后面直接带参数,不带会自动HASH计算选择DB库。 Cache: redis://null:Weidong2023~!@8.137.107.29:19379/ +# 密钥 +DeepSeekApiKey: sk-7e69ad7a07d74c409ad52bfab18be786 # 日志配置 Log: diff --git a/go.mod b/go.mod index dff1cb6..a9fa32c 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-deepseek/deepseek v0.8.0 github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.28.0 // indirect diff --git a/go.sum b/go.sum index 7533384..fbf67f2 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-deepseek/deepseek v0.8.0 h1:uB+iC63LtWKt892Bm3H6/4YSXqlkwDVo4FRodAHMs+Y= +github.com/go-deepseek/deepseek v0.8.0/go.mod h1:dhwH6SkBBaizgFTgzPkcKBT0kivqS17SiWYOhrtd+j8= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= diff --git a/internal/config/config.go b/internal/config/config.go index c02a81b..eb4ba5c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,8 +11,9 @@ var ( ) type SrvConfig struct { - conf.Base `yaml:",inline"` - Databases *conf.DBConf `yaml:"Databases"` + conf.Base `yaml:",inline"` + Databases *conf.DBConf `yaml:"Databases"` + DeepSeekApiKey string `yaml:"DeepSeekApiKey"` } func New(srvKey string) { diff --git a/internal/logic/deepseek/dpsk.go b/internal/logic/deepseek/dpsk.go new file mode 100644 index 0000000..0f40cfe --- /dev/null +++ b/internal/logic/deepseek/dpsk.go @@ -0,0 +1,266 @@ +package deepseek + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "time" +) + +// 配置结构体 +type Config struct { + APIKey string `json:"api_key"` + BaseURL string `json:"base_url"` + Model string `json:"model"` + MaxTokens int `json:"max_tokens"` + Temperature float64 `json:"temperature"` +} + +// 上传文件响应结构体 +type UploadResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Bytes int `json:"bytes"` + CreatedAt int64 `json:"created_at"` + Filename string `json:"filename"` + Purpose string `json:"purpose"` +} + +// 消息结构体 +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} + +// 聊天请求结构体 +type ChatRequest struct { + Model string `json:"model"` + Messages []Message `json:"messages"` + MaxTokens int `json:"max_tokens,omitempty"` + Temperature float64 `json:"temperature,omitempty"` + Stream bool `json:"stream,omitempty"` +} + +// 聊天响应结构体 +type ChatResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []struct { + Index int `json:"index"` + Message struct { + Role string `json:"role"` + Content string `json:"content"` + } `json:"message"` + FinishReason string `json:"finish_reason"` + } `json:"choices"` + Usage struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` + } `json:"usage"` +} + +// JSON响应包装器 +type JSONResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +// 上传文件到DeepSeek +func UploadFile(apiKey, filePath string) (*UploadResponse, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("打开文件失败: %v", err) + } + defer file.Close() + + // 创建multipart表单 + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // 添加文件字段 + part, err := writer.CreateFormFile("file", filepath.Base(filePath)) + if err != nil { + return nil, fmt.Errorf("创建表单文件失败: %v", err) + } + + _, err = io.Copy(part, file) + if err != nil { + return nil, fmt.Errorf("复制文件内容失败: %v", err) + } + + // 添加purpose字段 + _ = writer.WriteField("purpose", "assistants") + + err = writer.Close() + if err != nil { + return nil, fmt.Errorf("关闭writer失败: %v", err) + } + + // 创建请求 + req, err := http.NewRequest("POST", "https://api.deepseek.com/files", body) + if err != nil { + return nil, fmt.Errorf("创建请求失败: %v", err) + } + + req.Header.Set("Authorization", "Bearer "+apiKey) + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // 发送请求 + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("发送请求失败: %v", err) + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API返回错误: %d %s", resp.StatusCode, string(respBody)) + } + + // 解析响应 + var uploadResp UploadResponse + err = json.Unmarshal(respBody, &uploadResp) + if err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &uploadResp, nil +} + +// 发送聊天请求(带文件) +func ChatWithFile(apiKey, fileID, question string, config Config) (*ChatResponse, error) { + // 构建消息内容 + content := fmt.Sprintf(`请分析我上传的Markdown文件并回答以下问题: +问题:%s + +请基于文件内容提供详细的回答。`, question) + + // 创建聊天请求 + chatReq := ChatRequest{ + Model: config.Model, + Messages: []Message{ + { + Role: "user", + Content: content, + }, + }, + MaxTokens: config.MaxTokens, + Temperature: config.Temperature, + Stream: false, + } + + // 如果有文件ID,添加到消息中 + if fileID != "" { + chatReq.Messages[0].Content = fmt.Sprintf(`我上传了一个Markdown文件(ID: %s),请分析这个文件并回答以下问题: +问题:%s + +请基于文件内容提供详细的回答。`, fileID, question) + } + + // 序列化请求体 + reqBody, err := json.Marshal(chatReq) + if err != nil { + return nil, fmt.Errorf("序列化请求失败: %v", err) + } + + // 创建请求 + req, err := http.NewRequest("POST", config.BaseURL+"/chat/completions", bytes.NewBuffer(reqBody)) + if err != nil { + return nil, fmt.Errorf("创建请求失败: %v", err) + } + + req.Header.Set("Authorization", "Bearer "+apiKey) + req.Header.Set("Content-Type", "application/json") + + // 发送请求 + client := &http.Client{Timeout: 60 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("发送请求失败: %v", err) + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API返回错误: %d - %s", resp.StatusCode, string(respBody)) + } + + // 解析响应 + var chatResp ChatResponse + err = json.Unmarshal(respBody, &chatResp) + if err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &chatResp, nil +} + +// 处理Markdown文件上传和问答 +func ProcessMarkdownQA(config Config, filePath, question string) ([]byte, error) { + var jsonResponse JSONResponse + + // 1. 上传文件 + fmt.Println("正在上传Markdown文件...") + uploadResp, err := UploadFile(config.APIKey, filePath) + if err != nil { + jsonResponse.Success = false + jsonResponse.Error = fmt.Sprintf("上传文件失败: %v", err) + return json.Marshal(jsonResponse) + } + + fmt.Printf("文件上传成功!文件ID: %s\n", uploadResp.ID) + + // 2. 使用文件进行问答 + fmt.Println("正在分析文件并回答问题...") + chatResp, err := ChatWithFile(config.APIKey, uploadResp.ID, question, config) + if err != nil { + jsonResponse.Success = false + jsonResponse.Error = fmt.Sprintf("问答失败: %v", err) + return json.Marshal(jsonResponse) + } + + // 3. 构建成功响应 + if len(chatResp.Choices) > 0 { + responseData := map[string]interface{}{ + "file_info": map[string]interface{}{ + "file_id": uploadResp.ID, + "filename": uploadResp.Filename, + "size_bytes": uploadResp.Bytes, + }, + "question": question, + "answer": chatResp.Choices[0].Message.Content, + "usage": chatResp.Usage, + "model": chatResp.Model, + } + + jsonResponse.Success = true + jsonResponse.Message = "处理成功" + jsonResponse.Data = responseData + } else { + jsonResponse.Success = false + jsonResponse.Error = "未收到有效回答" + } + + return json.MarshalIndent(jsonResponse, "", " ") +} diff --git a/internal/logic/strategy/data.go b/internal/logic/strategy/data.go index eca093b..72ce3ba 100644 --- a/internal/logic/strategy/data.go +++ b/internal/logic/strategy/data.go @@ -1,9 +1,6 @@ package strategy import ( - "fmt" - - "git.apinb.com/bsm-sdk/core/utils" "git.apinb.com/quant/gostock/internal/impl" "git.apinb.com/quant/gostock/internal/models" ) @@ -36,462 +33,6 @@ func GetFullData(code string) *StockData { return &data } -func GenMarkData(code string) { - md := NewMarkdownBuilder() - - var basic models.StockBasic - impl.DBService.Where("ts_code = ?", code).First(&basic) - - md.Title(basic.TsCode + " " + basic.Name + " 股票详情") - md.Catalog("公司基础情况") - md.KvText("股票代码", basic.TsCode) - md.KvText("股票名称", basic.Name) - md.KvText("股票行业", basic.Industry) - md.KvText("股票地域", basic.Area) - md.KvText("上市日期", basic.ListDate) - md.KvText("股票市场", basic.Market) - md.KvText("股票类型", basic.Market) - md.KvText("股票是否沪深港通", basic.IsHS) - md.KvText("股票中文全称", basic.FullName) - md.KvText("股票实控人", basic.ActName) - md.KvText("股票实控人企业性质", basic.ActEntType) - md.BR() - - var daily []models.StockDaily - impl.DBService.Where("ts_code = ?", code).Order("trade_date desc").Limit(200).Find(&daily) - var data [][]string - for _, row := range daily { - data = append(data, []string{ - utils.Int2String(row.TradeDate), - fmt.Sprintf("%.2f", row.Open), - fmt.Sprintf("%.2f", row.Close), - fmt.Sprintf("%.2f", row.High), - fmt.Sprintf("%.2f", row.Low), - fmt.Sprintf("%.2f", row.Vol), - fmt.Sprintf("%.2f", row.Amount), - }) - } - md.Table("最近200天股票行情", []string{"日期", "开盘价", "收盘价", "最高价", "最低价", "成交量", "成交额"}, data) - md.BR() - - var indicator []models.StockIndicator - impl.DBService.Where("ts_code = ?", code).Order("trade_date desc").Limit(200).Find(&indicator) - var indiData [][]string - for _, row := range indicator { - indiData = append(indiData, []string{ - utils.Int2String(row.TradeDate), - fmt.Sprintf("%.2f", row.Close), - fmt.Sprintf("%.2f%%", row.TurnoverRate), - fmt.Sprintf("%.2f", row.VolumeRatio), - fmt.Sprintf("%.2f", row.Pe), - fmt.Sprintf("%.2f", row.PeTtm), - fmt.Sprintf("%.2f", row.Pb), - fmt.Sprintf("%.2f", row.Ps), - fmt.Sprintf("%.2f", row.PsTtm), - fmt.Sprintf("%.2f", row.DvRatio), - fmt.Sprintf("%.2f", row.DvTtm), - fmt.Sprintf("%.2f", row.TotalShare), - fmt.Sprintf("%.2f", row.FloatShare), - fmt.Sprintf("%.2f", row.FreeShare), - fmt.Sprintf("%.2f", row.TotalMv), - fmt.Sprintf("%.2f", row.CircMv), - }) - } - md.Table("最近200天股票指标", []string{"日期", "当日收盘价", "换手率", "量比", "市盈率", "市盈率 TTM", "市净率", "市销率", "市销率 TTM", "股息率", "股息率 TTM", "总股本", "流通股本", "自由流通股本", "总市值", "流通市值"}, indiData) - md.BR() - - var finaIndicator []models.StockFinaIndicator - impl.DBService.Where("ts_code = ?", code).Order("period desc").Find(&finaIndicator) - var fiHeaders []string = []string{ - "报告期数", - "公告日期", - "报告期", - - // 每股指标 - "基本每股收益", - "稀释每股收益", - "每股营业总收入", - "每股营业收入", - "每股资本公积", - "每股盈余公积", - "每股未分配利润", - "期末摊薄每股收益", - "每股净资产", - "每股经营活动产生的现金流量净额", - "每股留存收益", - "每股现金流量净额", - "每股息税前利润", - "每股企业自由现金流量", - "每股股东自由现金流量", - - // 利润表相关 - "非经常性损益", - "扣除非经常性损益后的净利润", - "毛利", - "经营活动净收益", - "价值变动净收益", - "利息费用", - "折旧与摊销", - "息税前利润", - "息税折旧摊销前利润", - "企业自由现金流量", - "股权自由现金流量", - "研发费用", - "固定资产合计", - "扣除财务费用前营业利润", - "非营业利润", - - // 资产负债表相关 - "无息流动负债", - "无息非流动负债", - "带息债务", - "净债务", - "有形资产", - "营运资金", - "营运流动资本", - "全部投入资本", - "留存收益", - - // 偿债能力指标 - "流动比率", - "速动比率", - "保守速动比率", - "资产负债率", - "权益乘数", - "权益乘数(杜邦分析)", - "产权比率", - "归属于母公司的股东权益/负债合计", - "经营活动产生的现金流量净额/流动负债", - "已获利息倍数", - - // 运营能力指标 - "存货周转天数", - "应收账款周转天数", - "存货周转率", - "应收账款周转率", - "流动资产周转率", - "固定资产周转率", - "总资产周转率", - "营业周期", - - // 盈利能力指标 - "销售净利率", - "销售毛利率", - "销售成本率", - "销售期间费用率", - "净资产收益率", - "加权平均净资产收益率", - "净资产收益率(扣除非经常损益)", - "总资产报酬率", - "总资产净利润", - "投入资本回报率", - "总资产净利率(杜邦分析)", - - // 结构指标 - "流动资产/总资产", - "非流动资产/总资产", - "有形资产/总资产", - "带息债务/全部投入资本", - "归属于母公司的股东权益/全部投入资本", - "流动负债/负债合计", - "非流动负债/负债合计", - "有形资产/负债合计", - - // 单季度指标 - "经营活动单季度净收益", - "价值变动单季度净收益", - "扣除非经常损益后的单季度净利润", - "每股收益(单季度)", - "销售净利率(单季度)", - "销售毛利率(单季度)", - "销售期间费用率(单季度)", - "净资产收益率(单季度)", - "净资产单季度收益率(扣除非经常损益)", - "总资产净利润(单季度)", - - // 同比增长率 - "基本每股收益同比增长率(%)", - "稀释每股收益同比增长率(%)", - "每股经营活动产生的现金流量净额同比增长率(%)", - "营业利润同比增长率(%)", - "利润总额同比增长率(%)", - "归属母公司股东的净利润同比增长率(%)", - "归属母公司股东的净利润-扣除非经常损益同比增长率(%)", - "经营活动产生的现金流量净额同比增长率(%)", - "净资产收益率(摊薄)同比增长率(%)", - "每股净资产相对年初增长率(%)", - "资产总计相对年初增长率(%)", - "归属母公司的股东权益相对年初增长率(%)", - "营业总收入同比增长率(%)", - "营业收入同比增长率(%)", - "净资产同比增长率", - - // 其他比率指标 - "净利润/营业总收入", - "销售费用/营业总收入", - "管理费用/营业总收入", - "财务费用/营业总收入", - "资产减值损失/营业总收入", - "营业总成本/营业总收入", - "营业利润/营业总收入", - "息税前利润/营业总收入", - "经营活动净收益/利润总额", - "价值变动净收益/利润总额", - "营业外收支净额/利润总额", - "所得税/利润总额", - "扣除非经常损益后的净利润/净利润", - "销售商品提供劳务收到的现金/营业收入", - "经营活动产生的现金流量净额/营业收入", - "经营活动产生的现金流量净额/经营活动净收益", - "资本支出/折旧和摊销", - "经营活动产生的现金流量净额/负债合计", - "经营活动产生的现金流量净额/带息债务", - "经营活动产生的现金流量净额/净债务", - "长期债务与营运资金比率", - "息税折旧摊销前利润/负债合计", - "营业利润/利润总额", - "非营业利润/利润总额", - "经营活动产生的现金流量净额/营业利润", - "货币资金/流动负债", - "货币资金/带息流动负债", - "营业利润/流动负债", - "营业利润/负债合计", - "利润总额/营业收入", - - // 年度化指标 - "年化净资产收益率", - "年化总资产报酬率", - "年化总资产净利率", - "年化投入资本回报率", - "平均净资产收益率(增发条件)", - - // 单季度增长比率 - "营业总收入同比增长率(%)(单季度)", - "营业总收入环比增长率(%)(单季度)", - "营业收入同比增长率(%)(单季度)", - "营业收入环比增长率(%)(单季度)", - "营业利润同比增长率(%)(单季度)", - "营业利润环比增长率(%)(单季度)", - "净利润同比增长率(%)(单季度)", - "净利润环比增长率(%)(单季度)", - "归属母公司股东的净利润同比增长率(%)(单季度)", - "归属母公司股东的净利润环比增长率(%)(单季度)", - - // 单季度比率指标 - "净利润/营业总收入(单季度)", - "销售费用/营业总收入 (单季度)", - "管理费用/营业总收入 (单季度)", - "财务费用/营业总收入 (单季度)", - "资产减值损失/营业总收入(单季度)", - "营业总成本/营业总收入 (单季度)", - "营业利润/营业总收入(单季度)", - "经营活动净收益/利润总额(单季度)", - "价值变动净收益/利润总额(单季度)", - "扣除非经常损益后的净利润/净利润(单季度)", - "销售商品提供劳务收到的现金/营业收入(单季度)", - "经营活动产生的现金流量净额/营业收入(单季度)", - "经营活动产生的现金流量净额/经营活动净收益(单季度)", - } - var fiData [][]string - for _, row := range finaIndicator { - fiData = append(fiData, []string{ - fmt.Sprintf("%d", row.Period), - row.AnnDate, - row.EndDate, - - // 每股指标 - fmt.Sprintf("%.2f", row.Eps), - fmt.Sprintf("%.2f", row.DtEps), - fmt.Sprintf("%.2f", row.TotalRevenuePs), - fmt.Sprintf("%.2f", row.RevenuePs), - fmt.Sprintf("%.2f", row.CapitalResePs), - fmt.Sprintf("%.2f", row.SurplusResePs), - fmt.Sprintf("%.2f", row.UndistProfitPs), - fmt.Sprintf("%.2f", row.Diluted2Eps), - fmt.Sprintf("%.2f", row.Bps), - fmt.Sprintf("%.2f", row.Ocfps), - fmt.Sprintf("%.2f", row.Retainedps), - fmt.Sprintf("%.2f", row.Cfps), - fmt.Sprintf("%.2f", row.EbitPs), - fmt.Sprintf("%.2f", row.FcffPs), - fmt.Sprintf("%.2f", row.FcfePs), - - // 利润表相关 - fmt.Sprintf("%.2f", row.ExtraItem), - fmt.Sprintf("%.2f", row.ProfitDedt), - fmt.Sprintf("%.2f", row.GrossMargin), - fmt.Sprintf("%.2f", row.OpIncome), - fmt.Sprintf("%.2f", row.ValuechangeIncome), - fmt.Sprintf("%.2f", row.InterstIncome), - fmt.Sprintf("%.2f", row.Daa), - fmt.Sprintf("%.2f", row.Ebit), - fmt.Sprintf("%.2f", row.Ebitda), - fmt.Sprintf("%.2f", row.Fcff), - fmt.Sprintf("%.2f", row.Fcfe), - fmt.Sprintf("%.2f", row.RdExp), - fmt.Sprintf("%.2f", row.FixedAssets), - fmt.Sprintf("%.2f", row.ProfitPrefinExp), - fmt.Sprintf("%.2f", row.NonOpProfit), - - // 资产负债表相关 - fmt.Sprintf("%.2f", row.CurrentExint), - fmt.Sprintf("%.2f", row.NoncurrentExint), - fmt.Sprintf("%.2f", row.Interestdebt), - fmt.Sprintf("%.2f", row.Netdebt), - fmt.Sprintf("%.2f", row.TangibleAsset), - fmt.Sprintf("%.2f", row.WorkingCapital), - fmt.Sprintf("%.2f", row.NetworkingCapital), - fmt.Sprintf("%.2f", row.InvestCapital), - fmt.Sprintf("%.2f", row.RetainedEarnings), - - // 偿债能力指标 - fmt.Sprintf("%.2f", row.CurrentRatio), - fmt.Sprintf("%.2f", row.QuickRatio), - fmt.Sprintf("%.2f", row.CashRatio), - fmt.Sprintf("%.2f", row.DebtToAssets), - fmt.Sprintf("%.2f", row.AssetsToEqt), - fmt.Sprintf("%.2f", row.DpAssetsToEqt), - fmt.Sprintf("%.2f", row.DebtToEqt), - fmt.Sprintf("%.2f", row.EqtToDebt), - fmt.Sprintf("%.2f", row.OcfToShortdebt), - fmt.Sprintf("%.2f", row.EbitToInterest), - - // 运营能力指标 - fmt.Sprintf("%.2f", row.InvturnDays), - fmt.Sprintf("%.2f", row.ArturnDays), - fmt.Sprintf("%.2f", row.InvTurn), - fmt.Sprintf("%.2f", row.ArTurn), - fmt.Sprintf("%.2f", row.CaTurn), - fmt.Sprintf("%.2f", row.FaTurn), - fmt.Sprintf("%.2f", row.AssetsTurn), - fmt.Sprintf("%.2f", row.TurnDays), - fmt.Sprintf("%.2f", row.TotalFaTrun), - - // 盈利能力指标 - fmt.Sprintf("%.2f", row.NetprofitMargin), - fmt.Sprintf("%.2f", row.GrossprofitMargin), - fmt.Sprintf("%.2f", row.CogsOfSales), - fmt.Sprintf("%.2f", row.ExpenseOfSales), - fmt.Sprintf("%.2f", row.Roe), - fmt.Sprintf("%.2f", row.RoeWaa), - fmt.Sprintf("%.2f", row.RoeDt), - fmt.Sprintf("%.2f", row.Roa), - fmt.Sprintf("%.2f", row.Npta), - fmt.Sprintf("%.2f", row.Roic), - fmt.Sprintf("%.2f", row.RoaDp), - - // 结构指标 - fmt.Sprintf("%.2f", row.CaToAssets), - fmt.Sprintf("%.2f", row.NcaToAssets), - fmt.Sprintf("%.2f", row.TbassetsToTotalassets), - fmt.Sprintf("%.2f", row.IntToTalcap), - fmt.Sprintf("%.2f", row.EqtToTalcapital), - fmt.Sprintf("%.2f", row.CurrentdebtToDebt), - fmt.Sprintf("%.2f", row.LongdebToDebt), - fmt.Sprintf("%.2f", row.TangibleassetToDebt), - - // 单季度指标 - fmt.Sprintf("%.2f", row.QOpincome), - fmt.Sprintf("%.2f", row.QInvestincome), - fmt.Sprintf("%.2f", row.QDtprofit), - fmt.Sprintf("%.2f", row.QEps), - fmt.Sprintf("%.2f", row.QNetprofitMargin), - fmt.Sprintf("%.2f", row.QGscaleprofitMargin), - fmt.Sprintf("%.2f", row.QExpToSales), - fmt.Sprintf("%.2f", row.QRoe), - fmt.Sprintf("%.2f", row.QDtRoe), - fmt.Sprintf("%.2f", row.QNpta), - - // 同比增长率 - fmt.Sprintf("%.2f%%", row.BasicEpsYoy), - fmt.Sprintf("%.2f%%", row.DtEpsYoy), - fmt.Sprintf("%.2f%%", row.CfpsYoy), - fmt.Sprintf("%.2f%%", row.OpYoy), - fmt.Sprintf("%.2f%%", row.EbtYoy), - fmt.Sprintf("%.2f%%", row.NetprofitYoy), - fmt.Sprintf("%.2f%%", row.DtNetprofitYoy), - fmt.Sprintf("%.2f%%", row.OcfYoy), - fmt.Sprintf("%.2f%%", row.RoeYoy), - fmt.Sprintf("%.2f%%", row.BpsYoy), - fmt.Sprintf("%.2f%%", row.AssetsYoy), - fmt.Sprintf("%.2f%%", row.EqtYoy), - fmt.Sprintf("%.2f%%", row.TrYoy), - fmt.Sprintf("%.2f%%", row.OrYoy), - fmt.Sprintf("%.2f%%", row.EquityYoy), - - // 其他比率指标 - fmt.Sprintf("%.2f", row.ProfitToGr), - fmt.Sprintf("%.2f", row.SaleexpToGr), - fmt.Sprintf("%.2f", row.AdminexpOfGr), - fmt.Sprintf("%.2f", row.FinaexpOfGr), - fmt.Sprintf("%.2f", row.ImpaiTtm), - fmt.Sprintf("%.2f", row.GcOfGr), - fmt.Sprintf("%.2f", row.OpOfGr), - fmt.Sprintf("%.2f", row.EbitOfGr), - fmt.Sprintf("%.2f", row.OpincomeOfEbt), - fmt.Sprintf("%.2f", row.InvestincomeOfEbt), - fmt.Sprintf("%.2f", row.NOpProfitOfEbt), - fmt.Sprintf("%.2f", row.TaxToEbt), - fmt.Sprintf("%.2f", row.DtprofitToProfit), - fmt.Sprintf("%.2f", row.SalescashToOr), - fmt.Sprintf("%.2f", row.OcfToOr), - fmt.Sprintf("%.2f", row.OcfToOpincome), - fmt.Sprintf("%.2f", row.CapitalizedToDa), - fmt.Sprintf("%.2f", row.OcfToDebt), - fmt.Sprintf("%.2f", row.OcfToInterestdebt), - fmt.Sprintf("%.2f", row.OcfToNetdebt), - fmt.Sprintf("%.2f", row.LongdebtToWorkingcapital), - fmt.Sprintf("%.2f", row.EbitdaToDebt), - fmt.Sprintf("%.2f", row.OpToEbt), - fmt.Sprintf("%.2f", row.NopToEbt), - fmt.Sprintf("%.2f", row.OcfToProfit), - fmt.Sprintf("%.2f", row.CashToLiqdebt), - fmt.Sprintf("%.2f", row.CashToLiqdebtWithinterest), - fmt.Sprintf("%.2f", row.OpToLiqdebt), - fmt.Sprintf("%.2f", row.OpToDebt), - fmt.Sprintf("%.2f", row.ProfitToOp), - - // 年度化指标 - fmt.Sprintf("%.2f", row.RoeYearly), - fmt.Sprintf("%.2f", row.Roa2Yearly), - fmt.Sprintf("%.2f", row.RoaYearly), - fmt.Sprintf("%.2f", row.RoicYearly), - fmt.Sprintf("%.2f", row.RoeAvg), - - // 单季度增长比率 - fmt.Sprintf("%.2f%%", row.QGrYoy), - fmt.Sprintf("%.2f%%", row.QGrQoq), - fmt.Sprintf("%.2f%%", row.QSalesYoy), - fmt.Sprintf("%.2f%%", row.QSalesQoq), - fmt.Sprintf("%.2f%%", row.QOpYoy), - fmt.Sprintf("%.2f%%", row.QOpQoq), - fmt.Sprintf("%.2f%%", row.QProfitYoy), - fmt.Sprintf("%.2f%%", row.QProfitQoq), - fmt.Sprintf("%.2f%%", row.QNetprofitYoy), - fmt.Sprintf("%.2f%%", row.QNetprofitQoq), - - // 单季度比率指标 - fmt.Sprintf("%.2f", row.QProfitToGr), - fmt.Sprintf("%.2f", row.QSaleexpToGr), - fmt.Sprintf("%.2f", row.QAdminexpToGr), - fmt.Sprintf("%.2f", row.QFinaexpToGr), - fmt.Sprintf("%.2f", row.QImpairToGrTtm), - fmt.Sprintf("%.2f", row.QGcToGr), - fmt.Sprintf("%.2f", row.QOpToGr), - fmt.Sprintf("%.2f", row.QOpincomeToEbt), - fmt.Sprintf("%.2f", row.QInvestincomeToEbt), - fmt.Sprintf("%.2f", row.QDtprofitToProfit), - fmt.Sprintf("%.2f", row.QSalescashToOr), - fmt.Sprintf("%.2f", row.QOcfToSales), - fmt.Sprintf("%.2f", row.QOcfToOr), - }) - } - md.Table("最近3年的股票财务指标", fiHeaders, fiData) - md.BR() - - md.SaveToFile("./result/" + code + ".md") -} - func GetStocks() (stocks []string) { impl.DBService.Model(&models.StockBasic{}).Group("ts_code").Pluck("ts_code", &stocks) return diff --git a/internal/logic/strategy/markdown.go b/internal/logic/strategy/markdown.go deleted file mode 100644 index 5ee19e3..0000000 --- a/internal/logic/strategy/markdown.go +++ /dev/null @@ -1,79 +0,0 @@ -package strategy - -import ( - "bytes" - "io" - "os" - "strings" -) - -type MarkdownBuilder struct { - buf bytes.Buffer -} - -func NewMarkdownBuilder() *MarkdownBuilder { - return &MarkdownBuilder{} -} -func (m *MarkdownBuilder) Title(title string) { - m.buf.WriteString("# " + title + "\n\n") -} - -func (m *MarkdownBuilder) Catalog(text string) { - m.buf.WriteString("## " + text + "\n\n") -} -func (m *MarkdownBuilder) Text(text string) { - m.buf.WriteString(text + "\n\n") -} - -func (m *MarkdownBuilder) KvText(key, val string) { - m.buf.WriteString(key + " :" + val + " \n") -} - -func (m *MarkdownBuilder) BR() { - m.buf.WriteString("\n") -} - -func (m *MarkdownBuilder) Code(lang, code string) { - m.buf.WriteString("```" + lang + "\n") - m.buf.WriteString(code + "\n") - m.buf.WriteString("```\n\n") -} - -// 生成Markdown表格 -// | Header1 | Header2 | -// |---------|---------| -// | Cell1 | Cell2 | -func (m *MarkdownBuilder) Table(catalog string, headers []string, rows [][]string) { - if catalog != "" { - m.buf.WriteString("## " + catalog + "\n") - } - m.buf.WriteString("| " + strings.Join(headers, " | ") + " |\n") - - m.buf.WriteString("| ") - for _, header := range headers { - m.buf.WriteString(strings.Repeat("-", len(header)) + "|") - } - m.buf.WriteString("\n") - for _, row := range rows { - m.buf.WriteString("| " + strings.Join(row, " | ") + " |\n") - } -} - -func (m *MarkdownBuilder) Build() string { - return m.buf.String() -} - -func (m *MarkdownBuilder) SaveToFile(filePath string) error { - rf, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) - if err != nil { - return err - } - defer rf.Close() - - // 将buffer内容复制到文件 - _, err = io.Copy(rf, &m.buf) - if err != nil { - return err - } - return nil -} diff --git a/internal/logic/strategy/rule/ai.go b/internal/logic/strategy/rule/ai.go new file mode 100644 index 0000000..2386cb1 --- /dev/null +++ b/internal/logic/strategy/rule/ai.go @@ -0,0 +1,42 @@ +package rule + +import ( + "fmt" + + "git.apinb.com/bsm-sdk/core/utils" + "git.apinb.com/quant/gostock/internal/config" + "git.apinb.com/quant/gostock/internal/logic/deepseek" +) + +var ( + MarkdataPath = "./markdata/" +) + +func (r *RuleFactory) RunAi(code string) { + mdPath := MarkdataPath + code + ".md" + if !utils.PathExists(mdPath) { + r.Model.AiScrore = -1 + r.Model.AddDesc(fmt.Sprintf("%s markdown 文件未找友", mdPath)) + return + } + + c := deepseek.Config{ + APIKey: config.Spec.DeepSeekApiKey, // 替换为你的API Key + BaseURL: "https://api.deepseek.com", + Model: "deepseek-chat", // 或 "deepseek-coder" + MaxTokens: 2000, + Temperature: 0.7, + } + + prompt := "你是一名资深量化投研分析师。请根据markdown格式的附件内容,给出详细总结与买入评分(0-10),输出JSON,字段:summary(500字内中文总结)、score(0-10数字)、action(买入/谨慎/观望)、risk(一句风险提示)。" + result, err := deepseek.ProcessMarkdownQA(c, mdPath, prompt) + if err != nil { + r.Model.AiScrore = -1 + r.Model.AddDesc(fmt.Sprintf("处理失败: %v", err)) + return + } + + // 输出JSON格式的结果 + fmt.Println(string(result)) + +} diff --git a/internal/models/strat_model.go b/internal/models/strat_model.go index 7031cfe..ee8fdef 100644 --- a/internal/models/strat_model.go +++ b/internal/models/strat_model.go @@ -30,6 +30,10 @@ type StratModel struct { ValRsiPrve float64 ValRsiLast float64 Desc []StratDesc `gorm:"foreignKey:StratModelID"` + + //AI + AiScrore int + AiReply string } func init() { diff --git a/result/601899.SH.md b/markdata/601899.SH.md similarity index 100% rename from result/601899.SH.md rename to markdata/601899.SH.md