From 1e4a361f3c635a681de78d6a32cbaf6a7e9cfdc4 Mon Sep 17 00:00:00 2001 From: yanweidonog Date: Thu, 26 Feb 2026 16:53:51 +0800 Subject: [PATCH] optimize --- cmd/pnl/main.go | 50 ++++++++++++++++++++--- cmd/rk/main.go | 17 ++++++++ go.mod | 1 + go.sum | 2 + internal/logic/libs/real.go | 79 +++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 cmd/rk/main.go create mode 100644 internal/logic/libs/real.go diff --git a/cmd/pnl/main.go b/cmd/pnl/main.go index 97ed4b9..b233496 100644 --- a/cmd/pnl/main.go +++ b/cmd/pnl/main.go @@ -6,6 +6,7 @@ import ( "git.apinb.com/bsm-sdk/core/utils" "git.apinb.com/quant/gostock/internal/config" "git.apinb.com/quant/gostock/internal/impl" + "git.apinb.com/quant/gostock/internal/logic/libs" "git.apinb.com/quant/gostock/internal/models" "github.com/jedib0t/go-pretty/v6/table" ) @@ -20,7 +21,8 @@ func main() { fmt.Println("") ClosedTable() - UnclosedTable() + UnclosedTableByRealtime() + UnclosedTableByDay() fmt.Println("") } @@ -28,7 +30,7 @@ func ClosedTable() { tw := table.NewWriter() tw.SetStyle(table.StyleLight) tw.SetTitle("已清仓列表") - tw.AppendHeader(table.Row{"ID", "Code", "Name", "OpenDate", "OpenPrice", "CloseDate", "ClosePrice", "PNL/Per", "PNLRate(%)"}) + tw.AppendHeader(table.Row{"ID", "Code", "Name", "Open_Date", "Open_Price", "Close_Date", "Close_Price", "PNL/Per", "PNLRate(%)"}) var data []models.MockPosition impl.DBService.Where("status=?", 1).Find(&data) var tPNL, tPNLR, cost, sell float64 @@ -54,11 +56,49 @@ func ClosedTable() { fmt.Println(tw.Render()) } -func UnclosedTable() { +func UnclosedTableByRealtime() { tw := table.NewWriter() tw.SetStyle(table.StyleLight) - tw.SetTitle("未平仓列表") - tw.AppendHeader(table.Row{"ID", "Code", "Name", "OpenDate", "OpenPrice", "TodayPrice", "PNL/Per", "PNLRate(%)"}) + tw.SetTitle("未平仓列表(实时)") + tw.AppendHeader(table.Row{"ID", "Code", "Name", "Open_Date", "Open_Price", "Realtime_Price", "PNL/Per", "PNLRate(%)"}) + var data []models.MockPosition + impl.DBService.Where("status=?", 0).Find(&data) + var tPNL, tPNLR, cost, sell float64 + + for idx, item := range data { + var stock models.StockBasic + impl.DBService.Where("ts_code=?", item.Code).First(&stock) + + currentPrice, err := libs.GetSinaStockPrice(item.Code) + if err != nil { + var daily models.StockDaily + impl.DBService.Model(&models.StockDaily{}).Where("ts_code=?", item.Code).Order("id desc").Limit(1).First(&daily) + currentPrice = daily.High + } + + high := utils.FloatRound(currentPrice*0.995, 2) + pnl := utils.FloatRound(high-item.OpenPrice, 2) + pnlRate := utils.FloatRound(pnl/item.OpenPrice*100, 2) + + tPNL = tPNL + pnl + cost = cost + item.OpenPrice + sell = sell + high + + tw.AppendRow(table.Row{idx + 1, item.Code, stock.Name, item.CreatedAt.Format("2006-01-02"), item.OpenPrice, high, pnl, pnlRate}) + } + + tPNLR = utils.FloatRound(((sell-cost)/cost)*100, 2) + + tw.AppendFooter(table.Row{"", "", "", "TOTAL", utils.FloatRound(cost, 2), utils.FloatRound(sell, 2), utils.FloatRound(tPNL, 2), tPNLR}) + + fmt.Println(tw.Render()) +} + +func UnclosedTableByDay() { + tw := table.NewWriter() + tw.SetStyle(table.StyleLight) + tw.SetTitle("未平仓列表(日线)") + tw.AppendHeader(table.Row{"ID", "Code", "Name", "Open_Date", "Open_Price", "Today_Price", "PNL/Per", "PNLRate(%)"}) var data []models.MockPosition impl.DBService.Where("status=?", 0).Find(&data) var tPNL, tPNLR, cost, sell float64 diff --git a/cmd/rk/main.go b/cmd/rk/main.go new file mode 100644 index 0000000..5d93a2a --- /dev/null +++ b/cmd/rk/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + + "git.apinb.com/quant/gostock/internal/logic/libs" +) + +func main() { + // 示例:获取贵州茅台(sh600519)的最新价 + price, err := libs.GetSinaStockPrice("600519.SH") + if err != nil { + fmt.Printf("获取失败: %v\n", err) + return + } + fmt.Printf("贵州茅台最新价: %.2f\n", price) +} diff --git a/go.mod b/go.mod index a4ced6c..924138d 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( ) require ( + github.com/ShawnRong/tushare-go v0.0.0-20200418035301-a5d4e0f72854 github.com/bitly/go-simplejson v0.5.1 github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect diff --git a/go.sum b/go.sum index f167dc0..4799dbd 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ git.apinb.com/bsm-sdk/core v0.0.95 h1:UD4Z1lZ+Hn+k+u4lG3HhO+4Ucdko2h9NgjdXuX4+tE git.apinb.com/bsm-sdk/core v0.0.95/go.mod h1:rCmMma8R2pvByImgoZDm2OPLdr+IUNr7LBPyayb8aN0= git.apinb.com/bsm-sdk/core v0.1.8 h1:INSp+Yw+X+SzRV1Refxx1kqnFCflWc0RyMdPy+r7YHk= git.apinb.com/bsm-sdk/core v0.1.8/go.mod h1:rCmMma8R2pvByImgoZDm2OPLdr+IUNr7LBPyayb8aN0= +github.com/ShawnRong/tushare-go v0.0.0-20200418035301-a5d4e0f72854 h1:JuJt9mJKjvALjxD9qOoHivQNr5Cw58nDQLr3dmbXOYU= +github.com/ShawnRong/tushare-go v0.0.0-20200418035301-a5d4e0f72854/go.mod h1:itk99hhMTCA0hAiX614YcECQZaqd56qysQzZluyCbs0= github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= diff --git a/internal/logic/libs/real.go b/internal/logic/libs/real.go new file mode 100644 index 0000000..d99df2b --- /dev/null +++ b/internal/logic/libs/real.go @@ -0,0 +1,79 @@ +package libs + +import ( + "fmt" + "io" + "net/http" + "strings" + "time" +) + +// GetSinaStockPrice 获取新浪财经某只股票的最新价 +// 股票代码格式: 沪市为 sh + 代码,深市为 sz + 代码,例如 "sh600519" +func GetSinaStockPrice(stockCode string) (float64, error) { + tmp := strings.Split(strings.ToLower(stockCode), ".") + code := tmp[1] + tmp[0] + // 1. 构建请求URL + url := fmt.Sprintf("http://hq.sinajs.cn/list=%s", code) + + // 2. 创建HTTP客户端并设置超时(避免长时间阻塞) + client := &http.Client{ + Timeout: 5 * time.Second, + } + + // 3. 创建请求,并设置Referer,增加稳定性 [citation:5] + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return 0, fmt.Errorf("创建请求失败: %w", err) + } + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + req.Header.Set("Referer", "https://finance.sina.com.cn") + + // 4. 发送请求 + resp, err := client.Do(req) + if err != nil { + return 0, fmt.Errorf("HTTP请求失败: %w", err) + } + defer resp.Body.Close() + + // 5. 读取响应体 + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, fmt.Errorf("读取响应体失败: %w", err) + } + + // 6. 解析数据 + // 响应示例: var hq_str_sh600519="贵州茅台,1710.00,1705.00,1720.00,..."; + dataStr := string(body) + + // 检查是否获取到数据 + if !strings.Contains(dataStr, "hq_str_") { + return 0, fmt.Errorf("返回数据格式异常或股票代码不存在") + } + + // 提取引号内的部分 + start := strings.Index(dataStr, "\"") + end := strings.LastIndex(dataStr, "\"") + if start == -1 || end == -1 || end <= start { + return 0, fmt.Errorf("无法解析数据格式") + } + + content := dataStr[start+1 : end] + // 按逗号分割 + fields := strings.Split(content, ",") + + // 检查字段数量是否足够 (至少需要4个字段来获取价格) + if len(fields) < 4 { + return 0, fmt.Errorf("返回数据字段不足: %v", fields) + } + + // 当前价格是第4个字段 (索引为3) [citation:8][citation:9] + priceStr := fields[3] + var price float64 + _, err = fmt.Sscanf(priceStr, "%f", &price) + if err != nil { + return 0, fmt.Errorf("解析价格失败: %s, 错误: %w", priceStr, err) + } + + return price, nil +}