Skip to content

Commit

Permalink
Merge branch 'main' of github.com:axiaoxin-com/investool into main
Browse files Browse the repository at this point in the history
  • Loading branch information
axiaoxin committed Jul 13, 2022
2 parents 2dc140d + 53a30da commit d608644
Show file tree
Hide file tree
Showing 14 changed files with 579 additions and 22 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,26 @@ OPTIONS:

随着时间的推移,从中报发布结束开始,满足默认筛选条件的公司会越来越多,筛选并检测的耗时也会逐渐增加,服务部署时反向代理的超时时间需调大。

## index

获取指数代码 000905 信息

```
./investool index -c 000905 -d
```

获取指数代码 000905 的成分股:

```
./investool index -c 000905 -s
```

获取指数代码 000905 和指数代码 000922 的成分股交集:

```
./investool index -c 000905 -i 000922
```

# 欢迎 Star

[![Stargazers over time](https://starchart.cc/axiaoxin-com/investool.svg)](https://githuv.com/axiaoxin-com/investool)
Expand Down
109 changes: 109 additions & 0 deletions cmds/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package cmds

import (
"fmt"
"os"
"strconv"

"github.com/axiaoxin-com/investool/datacenter/eastmoney"
"github.com/axiaoxin-com/logging"
"github.com/olekukonko/tablewriter"
)

func showIndexData(data *eastmoney.IndexData) {
table := tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetRowSeparator("")
table.SetBorder(false)
table.SetNoWhiteSpace(true)
headers := []string{}
table.SetHeader(headers)
table.SetCaption(true, data.IndexCode+"指数信息")
rows := [][]string{
{"指数名称", data.FullIndexName},
{"指数说明", data.Reaprofile},
{"编制方", data.MakerName},
{"估值", data.IndexValueCN()},
{"估值PE值", data.Petim},
{"估值PE百分位", data.Pep100},
{"指数代码", data.IndexCode},
{"板块名称", data.BKName},
{"当前点数", data.NewPrice},
{"最新涨幅", data.NewCHG},
{"最近一周涨幅", data.W},
{"最近一月涨幅", data.M},
{"最近三月涨幅", data.Q},
{"最近六月涨幅", data.Hy},
{"最近一年涨幅", data.Y},
{"最近两年涨幅", data.Twy},
{"最近三年涨幅", data.Try},
{"最近五年涨幅", data.Fy},
{"今年来涨幅", data.Sy},
}
table.AppendBulk(rows)

table.Render()
}

func showIndexStocks(stocks []eastmoney.ZSCFGItem) {
table := tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetRowLine(true)
headers := []string{"股票名称", "股票代码", "持仓占比"}
table.SetHeader(headers)

sum := 0.0
for _, stock := range stocks {
row := []string{stock.StockName, stock.StockCode, stock.Marketcappct}
table.Append(row)
if stock.Marketcappct != "" && stock.Marketcappct != "--" {
v, err := strconv.ParseFloat(stock.Marketcappct, 64)
if err != nil {
logging.Error(nil, err.Error())
continue
}
sum += v
}
}

if len(stocks) > 0 {
table.SetCaption(true, stocks[0].IndexName+"成分股")
}
footers := []string{fmt.Sprintf("总数:%d", len(stocks)), "--", fmt.Sprintf("占比求和:%.2f", sum)}
table.SetFooter(footers)
table.Render()
}

func showIntersecStocks(stocks1, stocks2 []eastmoney.ZSCFGItem) {
table := tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetRowLine(true)
headers := []string{"股票名称-代码"}
table.SetHeader(headers)

counter := map[string]int{}
for _, stock := range stocks1 {
key := fmt.Sprintf("%v-%v", stock.StockName, stock.StockCode)
counter[key]++
}

for _, stock := range stocks2 {
key := fmt.Sprintf("%v-%v", stock.StockName, stock.StockCode)
counter[key]++
}

intersecCount := 0
for k, v := range counter {
if v == 2 {
table.Append([]string{k})
intersecCount++
}
}

if intersecCount > 0 {
table.SetCaption(true, fmt.Sprintf("%s∩%s 成分股交集", stocks1[0].IndexName, stocks2[0].IndexName))
}
footers := []string{fmt.Sprintf("交集总数:%d", intersecCount)}
table.SetFooter(footers)
table.Render()
}
102 changes: 102 additions & 0 deletions cmds/index_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// 指数数据

package cmds

import (
"context"
"fmt"

"github.com/axiaoxin-com/investool/datacenter"
"github.com/urfave/cli/v2"
)

const (
// ProcessorIndex 指数数据处理
ProcessorIndex = "index"
)

// FlagsIndex cli flags
func FlagsIndex() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "code",
Aliases: []string{"c"},
Value: "",
Usage: "指定指数代码",
Required: true,
},
&cli.BoolFlag{
Name: "desc",
Aliases: []string{"d"},
Value: false,
Usage: "返回指数信息",
Required: false,
},
&cli.BoolFlag{
Name: "stocks",
Aliases: []string{"s"},
Value: false,
Usage: "返回指数成分股",
Required: false,
},
&cli.StringFlag{
Name: "intersec",
Aliases: []string{"i"},
Value: "",
Usage: "返回成分股交集",
Required: false,
},
}
}

// ActionIndex cli action
func ActionIndex() func(c *cli.Context) error {
return func(c *cli.Context) error {
ctx := context.Background()
indexCode := c.String("code")

showDesc := c.Bool("desc")
if showDesc {
indexData, err := datacenter.EastMoney.Index(ctx, indexCode)
if err != nil {
fmt.Println(err)
}
showIndexData(indexData)
}

showStocks := c.Bool("stocks")
if showStocks {
stocks, err := datacenter.EastMoney.ZSCFG(ctx, indexCode)
if err != nil {
return err
}
showIndexStocks(stocks)
}

intersecIndexCode := c.String("intersec")
if intersecIndexCode != "" {
stocks1, err := datacenter.EastMoney.ZSCFG(ctx, indexCode)
if err != nil {
return err
}
stocks2, err := datacenter.EastMoney.ZSCFG(ctx, intersecIndexCode)
if err != nil {
return err
}
showIntersecStocks(stocks1, stocks2)
}
return nil
}
}

// CommandIndex 指数成分股 cli command
func CommandIndex() *cli.Command {
flags := FlagsIndex()
cmd := &cli.Command{
Name: ProcessorIndex,
Usage: "指数数据",
Flags: flags,
Action: ActionIndex(),
}
return cmd
}
63 changes: 63 additions & 0 deletions datacenter/eastmoney/hs300.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 沪深300成分股

package eastmoney

import (
"context"
"fmt"

"github.com/axiaoxin-com/goutils"
"github.com/corpix/uarand"
)

// HS300Item 沪深300成分股信息
type HS300Item struct {
Secucode string `json:"SECUCODE"` // 股票代码.XX
SecurityCode string `json:"SECURITY_CODE"` // 股票代码
SecurityNameAbbr string `json:"SECURITY_NAME_ABBR"` // 股票简称
ClosePrice float64 `json:"CLOSE_PRICE"` // 最新价格
Industry string `json:"INDUSTRY"` // 主营行业
Region string `json:"REGION"` // 地区
Weight float64 `json:"WEIGHT"` // 持仓比例(%)
Eps float64 `json:"EPS"` // 每股收益
Bps float64 `json:"BPS"` // 每股净资产
Roe float64 `json:"ROE"` // 净资产收益率
TotalShares float64 `json:"TOTAL_SHARES"` // 总股本(亿股)
FreeShares float64 `json:"FREE_SHARES"` // 流通股本(亿股)
FreeCap float64 `json:"FREE_CAP"` // 流通市值(亿元)
Type string `json:"TYPE"`
F2 interface{} `json:"f2"`
F3 interface{} `json:"f3"`
}

// RspHS300 HS300接口返回结构
type RspHS300 struct {
Version string `json:"version"`
Result struct {
Pages int `json:"pages"`
Data []HS300Item `json:"data"`
Count int `json:"count"`
} `json:"result"`
Success bool `json:"success"`
Message string `json:"message"`
Code int `json:"code"`
}

// HS300 返回沪深300成分股列表
func (e EastMoney) HS300(ctx context.Context) (results []HS300Item, err error) {
apiurl := "https://datacenter-web.eastmoney.com/api/data/v1/get?sortColumns=ROE&sortTypes=-1&pageSize=300&pageNumber=1&reportName=RPT_INDEX_TS_COMPONENT&columns=SECUCODE%2CSECURITY_CODE%2CTYPE%2CSECURITY_NAME_ABBR%2CCLOSE_PRICE%2CINDUSTRY%2CREGION%2CWEIGHT%2CEPS%2CBPS%2CROE%2CTOTAL_SHARES%2CFREE_SHARES%2CFREE_CAP&quoteColumns=f2%2Cf3&quoteType=0&source=WEB&client=WEB&filter=(TYPE%3D%221%22)"
header := map[string]string{
"user-agent": uarand.GetRandom(),
}
rsp := RspHS300{}
if err := goutils.HTTPGET(ctx, e.HTTPClient, apiurl, header, &rsp); err != nil {
return nil, err
}
if rsp.Code != 0 {
return nil, fmt.Errorf("HS300 rsp code error, rsp:%+v", rsp)
}
if len(rsp.Result.Data) != 300 {
return nil, fmt.Errorf("HS300 rsp data len != 300, len=%d", len(rsp.Result.Data))
}
return rsp.Result.Data, nil
}
13 changes: 13 additions & 0 deletions datacenter/eastmoney/hs300_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package eastmoney

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestHS300(t *testing.T) {
results, err := _em.HS300(_ctx)
require.Nil(t, err)
t.Log(results)
}

0 comments on commit d608644

Please sign in to comment.