Compare commits

..

5 Commits

Author SHA1 Message Date
injoyai
8fb069b855 简单测试北交所数据 2025-09-30 11:44:12 +08:00
injoyai
110eaddc4d 兼容北交所代码到GetCodeAll,方便原先的逻辑不用改动 2025-09-30 11:43:37 +08:00
injoyai
aec2cf1518 优化NewWorkday 2025-09-30 11:42:33 +08:00
injoyai
a596139d3e 移动GetBjCodes至tdx包 2025-09-30 11:42:10 +08:00
injoyai
578617e458 修复北交所(历史)分时成交小10倍的问题 2025-09-30 11:41:13 +08:00
8 changed files with 187 additions and 149 deletions

View File

@@ -220,6 +220,26 @@ func (this *Client) GetCode(exchange protocol.Exchange, start uint16) (*protocol
// GetCodeAll 通过多次请求的方式获取全部证券代码 // GetCodeAll 通过多次请求的方式获取全部证券代码
func (this *Client) GetCodeAll(exchange protocol.Exchange) (*protocol.CodeResp, error) { func (this *Client) GetCodeAll(exchange protocol.Exchange) (*protocol.CodeResp, error) {
resp := &protocol.CodeResp{} resp := &protocol.CodeResp{}
//通达信没有北交所代码列表,通过爬虫的方式从北交所官网获取,放在这里是为了方便业务逻辑
//不放在extend包时防止循环引用
//todo 这是临时方案,等通达信有北交所代码列表时再改
if exchange == protocol.ExchangeBJ {
codes, err := GetBjCodes()
if err != nil {
return nil, err
}
resp.Count = uint16(len(codes))
for _, v := range codes {
resp.List = append(resp.List, &protocol.Code{
Code: v.Code,
Name: v.Name,
LastPrice: v.Last,
})
}
return resp, nil
}
size := uint16(1000) size := uint16(1000)
for start := uint16(0); ; start += size { for start := uint16(0); ; start += size {
r, err := this.GetCode(exchange, start) r, err := this.GetCode(exchange, start)
@@ -238,7 +258,7 @@ func (this *Client) GetCodeAll(exchange protocol.Exchange) (*protocol.CodeResp,
// GetStockAll 获取所有股票代码 // GetStockAll 获取所有股票代码
func (this *Client) GetStockAll() ([]string, error) { func (this *Client) GetStockAll() ([]string, error) {
ls := []string(nil) ls := []string(nil)
for _, ex := range []protocol.Exchange{protocol.ExchangeSH, protocol.ExchangeSZ} { for _, ex := range []protocol.Exchange{protocol.ExchangeSH, protocol.ExchangeSZ, protocol.ExchangeBJ} {
resp, err := this.GetCodeAll(ex) resp, err := this.GetCodeAll(ex)
if err != nil { if err != nil {
return nil, err return nil, err

102
client_bj_code.go Normal file
View File

@@ -0,0 +1,102 @@
package tdx
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/injoyai/conv"
"io"
"net/http"
"strings"
"time"
)
const (
// UrlBjCodes 最后跟的是时间戳(ms),但是随便什么时间戳都能请求成功
UrlBjCodes = "https://www.bse.cn/nqhqController/nqhq_en.do?callback=jQuery3710848510589806625_%d"
)
func GetBjCodes() ([]*BjCode, error) {
list := []*BjCode(nil)
//这个200预防下bug,除非北京上市公司有4000个
for page := 0; page < 200; page++ {
ls, done, err := getBjCodes(page)
if err != nil {
return nil, err
}
list = append(list, ls...)
if done {
break
}
<-time.After(time.Millisecond * 100)
}
return list, nil
}
func getBjCodes(page int) (_ []*BjCode, last bool, err error) {
url := fmt.Sprintf(UrlBjCodes, time.Now().UnixMilli())
bodyStr := "page=" + conv.String(page) + "&type_en=%5B%22B%22%5D&sortfield=hqcjsl&sorttype=desc&xxfcbj_en=%5B2%5D&zqdm="
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(bodyStr))
if err != nil {
return nil, false, err
}
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.39 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, false, err
}
defer resp.Body.Close()
bs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, false, err
}
//处理数据
i := bytes.IndexByte(bs, '(')
if len(bs) < 1 || len(bs) <= i {
return nil, false, errors.New("未知错误: " + string(bs))
}
bs = bs[i+1 : len(bs)-1]
ls := []*BjCodes(nil)
err = json.Unmarshal(bs, &ls)
if err != nil {
return nil, false, err
}
if len(ls) == 0 {
return nil, false, errors.New("未知错误: " + string(bs))
}
return ls[0].Data, ls[0].LastPage, nil
}
type BjCodes struct {
Data []*BjCode `json:"content"`
TotalNumber int `json:"totalElements"`
TotalPage int `json:"totalPages"`
LastPage bool `json:"lastPage"`
}
type BjCode struct {
Date string `json:"hqjsrq"` //日期
Code string `json:"hqzqdm"` //代码
Name string `json:"hqzqjc"` //名称
LastClose float64 `json:"hqzrsp"` //前一天收盘价
Open float64 `json:"hqjrkp"` //开盘价
High float64 `json:"hqzgcj"` //最高价
Low float64 `json:"hqzdcj"` //最低价
Last float64 `json:"hqzjcj"` //最新价/收盘价
Volume int `json:"hqcjsl"` //成交量,股
Amount float64 `json:"hqcjje"` //成交额,元
}

View File

@@ -155,52 +155,53 @@ func (this *Codes) Get(code string) *CodeModel {
return this.Map[code] return this.Map[code]
} }
// GetExchange 获取股票交易所,这里的参数不需要带前缀 //// GetExchange 获取股票交易所,这里的参数不需要带前缀
func (this *Codes) GetExchange(code string) protocol.Exchange { //func (this *Codes) GetExchange(code string) protocol.Exchange {
if len(code) == 6 { // if len(code) == 6 {
switch { // switch {
case code[:1] == "6": // case code[:1] == "6":
return protocol.ExchangeSH // return protocol.ExchangeSH
case code[:1] == "0": // case code[:1] == "0":
return protocol.ExchangeSZ // return protocol.ExchangeSZ
case code[:2] == "30": // case code[:2] == "30":
return protocol.ExchangeSZ // return protocol.ExchangeSZ
} // }
} // }
var exchange string // var exchange string
exchanges := this.exchanges[code] // exchanges := this.exchanges[code]
if len(exchanges) >= 1 { // if len(exchanges) >= 1 {
exchange = exchanges[0] // exchange = exchanges[0]
} // }
if len(code) == 8 { // if len(code) == 8 {
exchange = code[0:2] // exchange = code[0:2]
} // }
switch exchange { // switch exchange {
case protocol.ExchangeSH.String(): // case protocol.ExchangeSH.String():
return protocol.ExchangeSH // return protocol.ExchangeSH
case protocol.ExchangeSZ.String(): // case protocol.ExchangeSZ.String():
return protocol.ExchangeSZ // return protocol.ExchangeSZ
default: // default:
return protocol.ExchangeSH // return protocol.ExchangeSH
} // }
} //}
func (this *Codes) AddExchange(code string) string { func (this *Codes) AddExchange(code string) string {
if exchanges := this.exchanges[code]; len(exchanges) == 1 { return protocol.AddPrefix(code)
return exchanges[0] + code //if exchanges := this.exchanges[code]; len(exchanges) == 1 {
} // return exchanges[0] + code
if len(code) == 6 { //}
switch { //if len(code) == 6 {
case code[:1] == "6": // switch {
return protocol.ExchangeSH.String() + code // case code[:1] == "6":
case code[:1] == "0": // return protocol.ExchangeSH.String() + code
return protocol.ExchangeSZ.String() + code // case code[:1] == "0":
case code[:2] == "30": // return protocol.ExchangeSZ.String() + code
return protocol.ExchangeSZ.String() + code // case code[:2] == "30":
} // return protocol.ExchangeSZ.String() + code
return this.GetExchange(code).String() + code // }
} // return this.GetExchange(code).String() + code
return code //}
//return code
} }
// Update 更新数据,从服务器或者数据库 // Update 更新数据,从服务器或者数据库
@@ -249,7 +250,7 @@ func (this *Codes) GetCodes(byDatabase bool) ([]*CodeModel, error) {
//3. 从服务器获取所有股票代码 //3. 从服务器获取所有股票代码
insert := []*CodeModel(nil) insert := []*CodeModel(nil)
update := []*CodeModel(nil) update := []*CodeModel(nil)
for _, exchange := range []protocol.Exchange{protocol.ExchangeSH, protocol.ExchangeSZ} { for _, exchange := range []protocol.Exchange{protocol.ExchangeSH, protocol.ExchangeSZ, protocol.ExchangeBJ} {
resp, err := this.Client.GetCodeAll(exchange) resp, err := this.Client.GetCodeAll(exchange)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -334,7 +335,7 @@ func (this *CodeModel) FullCode() string {
func (this *CodeModel) Price(p protocol.Price) protocol.Price { func (this *CodeModel) Price(p protocol.Price) protocol.Price {
return protocol.Price(float64(p) * math.Pow10(int(2-this.Decimal))) return protocol.Price(float64(p) * math.Pow10(int(2-this.Decimal)))
return p * protocol.Price(math.Pow10(int(2-this.Decimal))) //return p * protocol.Price(math.Pow10(int(2-this.Decimal)))
} }
func NewSessionFunc(db *xorm.Engine, fn func(session *xorm.Session) error) error { func NewSessionFunc(db *xorm.Engine, fn func(session *xorm.Session) error) error {

View File

@@ -8,7 +8,7 @@ import (
func main() { func main() {
common.Test(func(c *tdx.Client) { common.Test(func(c *tdx.Client) {
resp, err := c.GetHistoryMinuteTrade("20250609", "sz000001", 0, 20) resp, err := c.GetHistoryMinuteTrade("20250929", "bj838971", 0, 20)
logs.PanicErr(err) logs.PanicErr(err)
for _, v := range resp.List { for _, v := range resp.List {

View File

@@ -9,11 +9,12 @@ import (
func main() { func main() {
common.Test(func(c *tdx.Client) { common.Test(func(c *tdx.Client) {
_, err := tdx.NewWorkday(c, "./workday.db") _, err := tdx.NewWorkday(c) //"./workday.db"
logs.PanicErr(err) logs.PanicErr(err)
_, err = tdx.NewCodes(c, "./codes.db") _, err = tdx.NewCodes(c) //"./codes.db"
logs.PanicErr(err) logs.PanicErr(err)
c.Close()
}) })
} }

View File

@@ -1,102 +1,9 @@
package extend package extend
import ( import (
"bytes" "github.com/injoyai/tdx"
"encoding/json"
"errors"
"fmt"
"github.com/injoyai/conv"
"io"
"net/http"
"strings"
"time"
) )
const ( func GetBjCodes() ([]*tdx.BjCode, error) {
// UrlBjCodes 最后跟的是时间戳(ms),但是随便什么时间戳都能请求成功 return tdx.GetBjCodes()
UrlBjCodes = "https://www.bse.cn/nqhqController/nqhq_en.do?callback=jQuery3710848510589806625_%d"
)
func GetBjCodes() ([]*BjCode, error) {
list := []*BjCode(nil)
//这个200预防下bug,除非北京上市公司有4000个
for page := 0; page < 200; page++ {
ls, done, err := getBjCodes(page)
if err != nil {
return nil, err
}
list = append(list, ls...)
if done {
break
}
<-time.After(time.Millisecond * 100)
}
return list, nil
}
func getBjCodes(page int) (_ []*BjCode, last bool, err error) {
url := fmt.Sprintf(UrlBjCodes, time.Now().UnixMilli())
bodyStr := "page=" + conv.String(page) + "&type_en=%5B%22B%22%5D&sortfield=hqcjsl&sorttype=desc&xxfcbj_en=%5B2%5D&zqdm="
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(bodyStr))
if err != nil {
return nil, false, err
}
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.39 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, false, err
}
defer resp.Body.Close()
bs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, false, err
}
//处理数据
i := bytes.IndexByte(bs, '(')
if len(bs) < 1 || len(bs) <= i {
return nil, false, errors.New("未知错误: " + string(bs))
}
bs = bs[i+1 : len(bs)-1]
ls := []*BjCodes(nil)
err = json.Unmarshal(bs, &ls)
if err != nil {
return nil, false, err
}
if len(ls) == 0 {
return nil, false, errors.New("未知错误: " + string(bs))
}
return ls[0].Data, ls[0].LastPage, nil
}
type BjCodes struct {
Data []*BjCode `json:"content"`
TotalNumber int `json:"totalElements"`
TotalPage int `json:"totalPages"`
LastPage bool `json:"lastPage"`
}
type BjCode struct {
Date string `json:"hqjsrq"` //日期
Code string `json:"hqzqdm"` //代码
Name string `json:"hqzqjc"` //名称
LastClose float64 `json:"hqzrsp"` //前一天收盘价
Open float64 `json:"hqjrkp"` //开盘价
High float64 `json:"hqzgcj"` //最高价
Low float64 `json:"hqzdcj"` //最低价
Last float64 `json:"hqzjcj"` //最新价/收盘价
Volume int `json:"hqcjsl"` //成交量,股
Amount float64 `json:"hqcjje"` //成交额,元
} }

View File

@@ -127,11 +127,15 @@ func GetTime(bs [4]byte, Type uint8) time.Time {
} }
func basePrice(code string) Price { func basePrice(code string) Price {
if len(code) == 0 { if len(code) < 2 {
return 1
}
switch code[:1] {
case "8":
return 1 return 1
} }
switch code[:2] { switch code[:2] {
case "60", "30", "68", "00": case "60", "30", "68", "00", "92", "43":
return 1 return 1
default: default:
return 10 return 10

View File

@@ -15,7 +15,10 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
func NewWorkday(c *Client, filename string) (*Workday, error) { func NewWorkday(c *Client, filenames ...string) (*Workday, error) {
defaultFilename := filepath.Join(DefaultDatabaseDir, "workday.db")
filename := conv.Default(defaultFilename, filenames...)
//如果文件夹不存在就创建 //如果文件夹不存在就创建
dir, _ := filepath.Split(filename) dir, _ := filepath.Split(filename)