diff --git a/decode/decode.go b/decode/decode.go new file mode 100644 index 0000000..e7800de --- /dev/null +++ b/decode/decode.go @@ -0,0 +1,43 @@ +package decode + +import ( + "easyquotation/stock" + "easyquotation/utils" + "fmt" + "reflect" + "strings" +) + +var str string = "var hq_str_sz161505=\"银河通利,0.000,1.258,0.000,0.000,0.000,1.239,1.280,0,0.000,3000,1.239,8100,1.234,1000,1.233,1000,1.198,900,1.188,5548,1.280,900,1.300,1000,1.314,1000,1.326,1600,1.330,2020-07-24,10:58:12,00\"" + +func Split(r rune) bool { + return r == ' ' || r == '_' || r == '=' +} +func DecodeMarket() { + l := utils.SplitString(str, Split) + symbol := l[3] + + market := l[4][1 : len(l[4])-1] + marketList := strings.Split(market, ",") + + a := stock.Market{} + eleVals := reflect.ValueOf(&a).Elem() + utils.DecodeStock(marketList[:10], eleVals) + for i := 0; i < 10; i += 2 { + bEntry := stock.Entry{} + vals := reflect.ValueOf(&bEntry).Elem() + utils.DecodeStock(marketList[10+i:10+i+2], vals) + a.BuyEntryList = append(a.BuyEntryList, bEntry) + } + for i := 0; i < 10; i += 2 { + bEntry := stock.Entry{} + vals := reflect.ValueOf(&bEntry).Elem() + utils.DecodeStock(marketList[20+i:20+i+2], vals) + a.SellEntryList = append(a.SellEntryList, bEntry) + } + a.Date = marketList[30] + a.Time = marketList[31] + a.Flag = marketList[32] + + fmt.Println(a) +} diff --git a/decode/decode_test.go b/decode/decode_test.go new file mode 100644 index 0000000..d6cf25a --- /dev/null +++ b/decode/decode_test.go @@ -0,0 +1,7 @@ +package decode + +import "testing" + +func TestDecodeMarket(t *testing.T) { + DecodeMarket() +} \ No newline at end of file diff --git a/quotation.go b/quotation.go new file mode 100644 index 0000000..8b3c2b6 --- /dev/null +++ b/quotation.go @@ -0,0 +1,31 @@ +package easyquotation + +import ( + . "easyquotation/stock" + "easyquotation/utils" + "log" + "os" +) + +func Init() { + path, err := os.Getwd() + file := path + "/message" + ".txt" + logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766) + if err != nil { + panic(err) + } + log.SetOutput(logFile) // 将文件设置为log输出的文件 + log.SetPrefix("[quotation]") + log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC) + + // 获取新浪股票代码信息 + utils.UpdateStockCodes() + // + G_STOCK_MANAGER = &StockManager{ + make(map[string]*Stock), + make(map[string]*Stock), + make(map[string]*Stock), + make(map[string]*Stock), + } + G_STOCK_MANAGER.Load() +} diff --git a/quotation_test.go b/quotation_test.go new file mode 100644 index 0000000..a47eea1 --- /dev/null +++ b/quotation_test.go @@ -0,0 +1,26 @@ +package easyquotation + +import ( + "easyquotation/sina" + "easyquotation/stock" + "fmt" + "github.com/gocolly/colly" + "testing" + "time" +) + +func TestInit(t *testing.T) { + Init() + + c := colly.NewCollector() + SinaStock_spider := sina.NewSinaStock(c) + SinaStock_spider.Start() + + tt := time.NewTicker(time.Second * 3) + for { + select { + case <- tt.C: + fmt.Println(stock.G_STOCK_MANAGER.StockList["sh600000"]) + } + } +} diff --git a/sina/sina.go b/sina/sina.go new file mode 100644 index 0000000..361093c --- /dev/null +++ b/sina/sina.go @@ -0,0 +1,19 @@ +package sina + +import ( + "github.com/gocolly/colly" + "time" +) + +type ISpiderIntface interface { + Url() string + Collector(c *colly.Collector) + OnRequest(r *colly.Request) + OnResponse(res *colly.Response) + Start() +} + +type Spider struct { + C *colly.Collector + Timer *time.Ticker // 个股 +} diff --git a/sina/sinaindex.go b/sina/sinaindex.go new file mode 100644 index 0000000..fe30a93 --- /dev/null +++ b/sina/sinaindex.go @@ -0,0 +1,63 @@ +package sina + +import ( + "easyquotation/stock" + "easyquotation/utils" + "fmt" + "github.com/gocolly/colly" + "log" + "time" +) + +type SinaIndex struct { + Spider +} + +func (s *SinaIndex) Url() string { + return fmt.Sprintf("http://hq.sinajs.cn/rn=%s&list=", utils.Random(5)) +} + +func NewSinaIndex(c *colly.Collector) *SinaIndex { + s := &SinaIndex{Spider{Timer:time.NewTicker(time.Second * 10)}} + s.Collector(c) + s.C.OnRequest(s.OnRequest) + s.C.OnResponse(s.OnResponse) + return s +} +func (s *SinaIndex) Collector(c *colly.Collector) { + s.Spider.C = c.Clone() +} + +func (s *SinaIndex) OnRequest(r *colly.Request) { +} + +func (s *SinaIndex) OnResponse(res *colly.Response) { + log.Println(string(res.Body)) +} + +func (s *SinaIndex) Start() { + go func() { + for { + select { + case <- s.Spider.Timer.C: + ipos := 1 + params := make([]string, 0) + param := "" + for k, _ := range stock.G_STOCK_MANAGER.StockList { + if ipos%800 == 0 { + params = append(params, param) + param = "" + } + param += k + param += "," + ipos++ + } + params = append(params, param) + for _, str := range params { + url := s.Url() + str + go s.C.Visit(url) + } + } + } + }() +} \ No newline at end of file diff --git a/sina/sinastock.go b/sina/sinastock.go new file mode 100644 index 0000000..40af42f --- /dev/null +++ b/sina/sinastock.go @@ -0,0 +1,104 @@ +package sina + +import ( + "easyquotation/stock" + "easyquotation/utils" + "fmt" + "github.com/gocolly/colly" + "reflect" + "strings" + "time" +) + +type SinaStock struct { + Spider +} + +func NewSinaStock(c *colly.Collector) *SinaStock { + s := &SinaStock{Spider{Timer: time.NewTicker(time.Second * 3)}} + s.Collector(c) + s.C.OnRequest(s.OnRequest) + s.C.OnResponse(s.OnResponse) + return s +} +func (s *SinaStock) Collector(c *colly.Collector) { + s.Spider.C = c.Clone() +} +func (s *SinaStock) Url() string { + return fmt.Sprintf("http://hq.sinajs.cn/rn=%d&list=", time.Now().Unix()) +} + +func (s *SinaStock) OnRequest(r *colly.Request) { + // fmt.Println(r.URL) +} + +func (s *SinaStock) Split(r rune) bool { + return r == ' ' || r == '_' || r == '=' +} +func (s *SinaStock) DecodeMarket(str string) { + l := utils.SplitString(str, s.Split) + if (len(l) < 5){ + return + } + symbol := l[3] // 代码 + market := l[4][1 : len(l[4])-1] // 去除左右" " + marketList := strings.Split(market, ",") + if (len(marketList) < 33) { + return + } + a := stock.Market{} + eleVals := reflect.ValueOf(&a).Elem() + utils.DecodeStock(marketList[:10], eleVals) + // 买五档 + for i := 0; i < 10; i += 2 { + bEntry := stock.Entry{} + vals := reflect.ValueOf(&bEntry).Elem() + utils.DecodeStock(marketList[10+i:10+i+2], vals) + a.BuyEntryList = append(a.BuyEntryList, bEntry) + } + // 卖五档 + for i := 0; i < 10; i += 2 { + bEntry := stock.Entry{} + vals := reflect.ValueOf(&bEntry).Elem() + utils.DecodeStock(marketList[20+i:20+i+2], vals) + a.SellEntryList = append(a.SellEntryList, bEntry) + } + a.Date = marketList[30] + a.Time = marketList[31] + a.Flag = marketList[32] + stock.G_STOCK_MANAGER.StockList[symbol].Market = a +} + +func (s *SinaStock) OnResponse(res *colly.Response) { + l := strings.Split(string(res.Body), ";") + for _, str := range l { + s.DecodeMarket(str) + } +} + +func (s *SinaStock) Start() { + go func() { + for { + select { + case <-s.Spider.Timer.C: + ipos := 1 + params := make([]string, 0) + param := "" + for k, _ := range stock.G_STOCK_MANAGER.StockList { + if ipos%800 == 0 { + params = append(params, param) + param = "" + } + param += k + param += "," + ipos++ + } + params = append(params, param) + for _, str := range params { + url := s.Url() + str + go s.C.Visit(url) + } + } + } + }() +} diff --git a/sina/sinastock_test.go b/sina/sinastock_test.go new file mode 100644 index 0000000..30b7bb3 --- /dev/null +++ b/sina/sinastock_test.go @@ -0,0 +1,2 @@ +package sina + diff --git a/stock/stock.go b/stock/stock.go new file mode 100644 index 0000000..9e303f9 --- /dev/null +++ b/stock/stock.go @@ -0,0 +1,47 @@ +package stock + +const ( + TICK_DAY_NUM = 4*60 + 2 // 分时数据量 +) + +type Base struct { + Symbol string // 代码 + Name string // 全称 + Short string // 简称 +} + +type Entry struct { + Qty float64 `json:"qty"` // 数量 + Price float64 `json:"price"` // 价格 +} + +type Tick struct { + Time string // 时间 + LastPrice float64 // 最新价 + AvgPrice float64 // 均价 + Volumn float64 // 成交量 + Value float64 // 成交额 +} + +type Market struct { + Name string `json:"name"` // 名称 + Open float64 `json:"open"` // 开盘 + PreClose float64 `json:"pre_close"` // 昨收 + LastPrice float64 `json:"last_price"` // 最新价 + High float64 `json:"high"` // 最高价 + Low float64 `json:"low"` // 最低价 + BidPice float64 `json:"bid_pice"` // 竞买价 + OfferPice float64 `json:"offer_pice"` // 竞卖价 + Volumn float64 `json:"volumn"` // 成交量(股) + Value float64 `json:"value"` // 成交额(元) + BuyEntryList []Entry `json:"buy_entry_list"` // 买5档 + SellEntryList []Entry `json:"sell_entry_list"` // 卖5档 + Date string `json:"date"` // 日期 + Time string `json:"time"` // 时间 + Flag string `json:"flag"` // 状态 +} + +type Stock struct { + Base + Market +} diff --git a/stock/stockmanager.go b/stock/stockmanager.go new file mode 100644 index 0000000..f8bed1b --- /dev/null +++ b/stock/stockmanager.go @@ -0,0 +1,66 @@ +package stock + +import ( + "bufio" + "easyquotation/utils" + "fmt" + "os" + "reflect" + "strings" +) + +var G_STOCK_MANAGER *StockManager + +type StockManager struct { + StockList map[string]*Stock //个股 + IndexList map[string]*Stock //指数 + BKkList map[string]*Stock //板块 + ZZIndexList map[string]*Stock //中证指数 +} + +func (s *StockManager) SetBase(list []string) *Stock { + b := Base{} + eleVals := reflect.ValueOf(&b).Elem() + utils.DecodeStock(list, eleVals) + return &Stock{Base: b} +} + +// 加载股票信息 +func (s *StockManager) Load() { + path, err := os.Getwd() + if err != nil { + path = "./" + } + if file, err := os.Open(path + "/stock_codes"); err != nil { + panic(err) + } else { + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + eleList := strings.Split(line, " ") + if len(eleList) < 3 { + continue + } else { + if strings.HasPrefix(eleList[0], "zz") { + s.ZZIndexList[eleList[0]] = s.SetBase(eleList) + } else { + if utils.EndSwitch(eleList[1], []string{".沪指数", ".深指数"}) { + if !strings.HasPrefix(eleList[0], "sh") { + eleList[0] = fmt.Sprintf("sz%s", eleList[0]) + } + s.IndexList[eleList[0]] = s.SetBase(eleList) + } else if strings.HasSuffix(eleList[1], ".板块") { + s.BKkList[eleList[0]] = s.SetBase(eleList) + } else { + if utils.StartSwitch(eleList[0], []string{"5", "6", "9"}) { + eleList[0] = fmt.Sprintf("sh%s", eleList[0]) + } else { + eleList[0] = fmt.Sprintf("sz%s", eleList[0]) + } + s.StockList[eleList[0]] = s.SetBase(eleList) + } + } + } + } + } +} diff --git a/stock/stockmanager_test.go b/stock/stockmanager_test.go new file mode 100644 index 0000000..cde1ef9 --- /dev/null +++ b/stock/stockmanager_test.go @@ -0,0 +1,6 @@ +package stock + +import "testing" + +func TestStockManager_Load(t *testing.T) { +} diff --git a/utils/helper.go b/utils/helper.go new file mode 100644 index 0000000..331cce4 --- /dev/null +++ b/utils/helper.go @@ -0,0 +1,131 @@ +package utils + +import ( + "io/ioutil" + "math/rand" + "net/http" + "os" + "reflect" + "strconv" + "strings" + "time" + "unsafe" + "errors" +) + +func UpdateStockCodes() { + path, err := os.Getwd() + if err != nil { + path = "./" + } + req, err := http.NewRequest("GET", "http://www.shdjt.com/js/lib/astock.js", nil) + if err != nil { + return + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(resp.Body) + + list := strings.Split(string(respBody), "=") + list = strings.Split(list[1][1:len(list[1])-1], "~") + if fileObj, err := os.OpenFile(path+"/stock_codes", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err == nil { + defer fileObj.Close() + + realList := list[1:] + realLen := len(realList) + for index0, item := range realList { + l := strings.Split(item, "`") + for _, s := range l { + fileObj.WriteString(s + " ") + } + if index0 != realLen-1 { + fileObj.WriteString("\n") + } + + } + } +} + +func StartSwitch(s string, prefixs []string) bool { + for _, prefix := range prefixs { + if strings.HasPrefix(s, prefix) == true { + return true + } + } + return false +} + +func EndSwitch(s string, suffixs []string) bool { + for _, suffix := range suffixs { + if strings.HasSuffix(s, suffix) == true { + return true + } + } + return false +} + +// 生成随机串 +// ref:https://cloud.tencent.com/developer/article/1580647 +var src = rand.NewSource(time.Now().UnixNano()) + +const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return *(*string)(unsafe.Pointer(&b)) +} + +func Min(a ...int) int { + m := int(^uint(0) >> 1) + for _, i := range a { + if i < m { + m = i + } + } + return m +} + +func SplitString(s string, f func(rune) bool) []string { + a := strings.FieldsFunc(s, f) + return a +} + +func DecodeStock(list []string, data reflect.Value) { + length := Min(len(list), data.NumField()) + for i := 0; i < length; i++ { + switch data.Field(i).Kind() { + case reflect.String: + data.Field(i).SetString(list[i]) + case reflect.Int: + i64, _ := strconv.ParseInt(list[i], 10,64) + data.Field(i).SetInt(i64) + case reflect.Float64: + f, _ := strconv.ParseFloat(list[i], 64) + data.Field(i).SetFloat(f) + default: + panic(errors.New("Decode error")) + } + } +} \ No newline at end of file diff --git a/utils/helper_test.go b/utils/helper_test.go new file mode 100644 index 0000000..4cbab77 --- /dev/null +++ b/utils/helper_test.go @@ -0,0 +1,20 @@ +package utils + +import ( + "fmt" + "testing" +) + +func TestUpdateStockCodes(t *testing.T) { + UpdateStockCodes() +} + +func TestStockMarket(t *testing.T) { + +} + +func TestRandom(t *testing.T) { + for i:=0; i< 100; i++ { + fmt.Println(Random(5)) + } +} \ No newline at end of file