mirror of
https://github.com/injoyai/tdx.git
synced 2025-11-26 21:25:35 +08:00
Compare commits
7 Commits
530de4fa5a
...
v0.0.49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eeab6f533 | ||
|
|
5fd492e881 | ||
|
|
fcb6c995ad | ||
|
|
e6411858e9 | ||
|
|
f4b2497e92 | ||
|
|
e250223e57 | ||
|
|
d19cfb4416 |
1
codes.go
1
codes.go
@@ -20,7 +20,6 @@ type ICodes interface {
|
|||||||
GetName(code string) string
|
GetName(code string) string
|
||||||
GetStocks(limit ...int) []string
|
GetStocks(limit ...int) []string
|
||||||
GetETFs(limit ...int) []string
|
GetETFs(limit ...int) []string
|
||||||
Update() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultCodes 增加单例,部分数据需要通过Codes里面的信息计算
|
// DefaultCodes 增加单例,部分数据需要通过Codes里面的信息计算
|
||||||
|
|||||||
92
codes_v2.go
92
codes_v2.go
@@ -8,9 +8,11 @@ import (
|
|||||||
"github.com/injoyai/ios"
|
"github.com/injoyai/ios"
|
||||||
"github.com/injoyai/ios/client"
|
"github.com/injoyai/ios/client"
|
||||||
"github.com/injoyai/logs"
|
"github.com/injoyai/logs"
|
||||||
|
"github.com/injoyai/tdx/internal/gbbq"
|
||||||
"github.com/injoyai/tdx/internal/xorms"
|
"github.com/injoyai/tdx/internal/xorms"
|
||||||
"github.com/injoyai/tdx/protocol"
|
"github.com/injoyai/tdx/protocol"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
@@ -18,9 +20,15 @@ import (
|
|||||||
|
|
||||||
type Codes2Option func(*Codes2)
|
type Codes2Option func(*Codes2)
|
||||||
|
|
||||||
func WithFilename(filename string) Codes2Option {
|
func WithDBFilename(filename string) Codes2Option {
|
||||||
return func(c *Codes2) {
|
return func(c *Codes2) {
|
||||||
c.filename = filename
|
c.dbFilename = filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTempDir(dir string) Codes2Option {
|
||||||
|
return func(c *Codes2) {
|
||||||
|
c.tempDir = dir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,9 +38,9 @@ func WithSpec(spec string) Codes2Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithKey(key string) Codes2Option {
|
func WithUpdateKey(key string) Codes2Option {
|
||||||
return func(c *Codes2) {
|
return func(c *Codes2) {
|
||||||
c.key = key
|
c.updateKey = key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,30 +50,55 @@ func WithRetry(retry int) Codes2Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithClient(c *Client) Codes2Option {
|
||||||
|
return func(cs *Codes2) {
|
||||||
|
cs.c = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDial(dial ios.DialFunc, op ...client.Option) Codes2Option {
|
||||||
|
return func(c *Codes2) {
|
||||||
|
c.dial = dial
|
||||||
|
c.dialOption = op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDialOption(op ...client.Option) Codes2Option {
|
||||||
|
return func(c *Codes2) {
|
||||||
|
c.dialOption = op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewCodes2(op ...Codes2Option) (*Codes2, error) {
|
func NewCodes2(op ...Codes2Option) (*Codes2, error) {
|
||||||
cs := &Codes2{
|
cs := &Codes2{
|
||||||
filename: filepath.Join(DefaultDatabaseDir, "codes.db"),
|
dbFilename: filepath.Join(DefaultDatabaseDir, "codes2.db"),
|
||||||
|
tempDir: filepath.Join(DefaultDataDir, "temp"),
|
||||||
spec: "10 0 9 * * *",
|
spec: "10 0 9 * * *",
|
||||||
key: "codes",
|
updateKey: "codes",
|
||||||
retry: 3,
|
retry: 3,
|
||||||
dial: NewRangeDial(Hosts),
|
dial: NewRangeDial(Hosts),
|
||||||
dialOption: nil,
|
dialOption: nil,
|
||||||
|
m: maps.NewGeneric[string, *CodeModel](),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range op {
|
for _, o := range op {
|
||||||
o(cs)
|
o(cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.MkdirAll(cs.tempDir, 0777)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// 初始化连接
|
// 初始化连接
|
||||||
cs.c, err = DialWith(cs.dial, cs.dialOption...)
|
if cs.c == nil {
|
||||||
if err != nil {
|
cs.c, err = DialWith(cs.dial, cs.dialOption...)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化数据库
|
// 初始化数据库
|
||||||
cs.db, err = xorms.NewSqlite(cs.filename)
|
cs.db, err = xorms.NewSqlite(cs.dbFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -103,9 +136,10 @@ func NewCodes2(op ...Codes2Option) (*Codes2, error) {
|
|||||||
var _ ICodes = &Codes2{}
|
var _ ICodes = &Codes2{}
|
||||||
|
|
||||||
type Codes2 struct {
|
type Codes2 struct {
|
||||||
filename string //数据库文件
|
dbFilename string //数据库文件
|
||||||
|
tempDir string //临时目录
|
||||||
spec string //定时规则
|
spec string //定时规则
|
||||||
key string //标识
|
updateKey string //标识
|
||||||
retry int //重试次数
|
retry int //重试次数
|
||||||
dial ios.DialFunc //连接
|
dial ios.DialFunc //连接
|
||||||
dialOption []client.Option //
|
dialOption []client.Option //
|
||||||
@@ -147,11 +181,11 @@ func (this *Codes2) GetETFs(limit ...int) []string {
|
|||||||
func (this *Codes2) updated() (bool, error) {
|
func (this *Codes2) updated() (bool, error) {
|
||||||
update := new(UpdateModel)
|
update := new(UpdateModel)
|
||||||
{ //查询或者插入一条数据
|
{ //查询或者插入一条数据
|
||||||
has, err := this.db.Where("`Key`=?", this.key).Get(update)
|
has, err := this.db.Where("`Key`=?", this.updateKey).Get(update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
update.Key = this.key
|
update.Key = this.updateKey
|
||||||
if _, err = this.db.Insert(update); err != nil {
|
if _, err = this.db.Insert(update); err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
@@ -188,6 +222,7 @@ func (this *Codes2) Update() error {
|
|||||||
etfs := []string(nil)
|
etfs := []string(nil)
|
||||||
for _, v := range codes {
|
for _, v := range codes {
|
||||||
fullCode := v.FullCode()
|
fullCode := v.FullCode()
|
||||||
|
this.m.Set(fullCode, v)
|
||||||
switch {
|
switch {
|
||||||
case protocol.IsStock(fullCode):
|
case protocol.IsStock(fullCode):
|
||||||
stocks = append(stocks, fullCode)
|
stocks = append(stocks, fullCode)
|
||||||
@@ -255,7 +290,32 @@ func (this *Codes2) update() ([]*CodeModel, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//4. 插入或者更新数据库
|
//4. 获取gbbq
|
||||||
|
ss, err := gbbq.DownloadAndDecode(this.tempDir)
|
||||||
|
if err != nil {
|
||||||
|
logs.Err(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mStock := map[string]gbbq.Stock{}
|
||||||
|
for _, v := range ss {
|
||||||
|
mStock[protocol.AddPrefix(v.Code)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
//5. 赋值流通股和总股本
|
||||||
|
for _, v := range insert {
|
||||||
|
if protocol.IsStock(v.FullCode()) {
|
||||||
|
v.FloatStock, v.TotalStock = ss.GetStock(v.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range update {
|
||||||
|
if stock, ok := mStock[v.FullCode()]; ok {
|
||||||
|
v.FloatStock = stock.Float
|
||||||
|
v.TotalStock = stock.Total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//6. 插入或者更新数据库
|
||||||
err = this.db.SessionFunc(func(session *xorm.Session) error {
|
err = this.db.SessionFunc(func(session *xorm.Session) error {
|
||||||
for _, v := range mCode {
|
for _, v := range mCode {
|
||||||
if _, err = session.Where("Exchange=? and Code=? ", v.Exchange, v.Code).Delete(v); err != nil {
|
if _, err = session.Where("Exchange=? and Code=? ", v.Exchange, v.Code).Delete(v); err != nil {
|
||||||
@@ -279,6 +339,6 @@ func (this *Codes2) update() ([]*CodeModel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//更新时间
|
//更新时间
|
||||||
_, err = this.db.Where("`Key`=?", this.key).Update(&UpdateModel{Time: time.Now().Unix()})
|
_, err = this.db.Where("`Key`=?", this.updateKey).Update(&UpdateModel{Time: time.Now().Unix()})
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
|||||||
16
example/Codes2/main.go
Normal file
16
example/Codes2/main.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/injoyai/logs"
|
||||||
|
"github.com/injoyai/tdx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cs, err := tdx.NewCodes2()
|
||||||
|
logs.PanicErr(err)
|
||||||
|
|
||||||
|
c := cs.Get("sz000001")
|
||||||
|
|
||||||
|
fmt.Println(c.FloatStock, c.TotalStock)
|
||||||
|
}
|
||||||
@@ -8,13 +8,17 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
common.Test(func(c *tdx.Client) {
|
common.Test(func(c *tdx.Client) {
|
||||||
resp, err := c.GetTrade("sz000001", 0, 20)
|
resp, err := c.GetTrade("sz000001", 0, 200)
|
||||||
logs.PanicErr(err)
|
logs.PanicErr(err)
|
||||||
|
|
||||||
for _, v := range resp.List {
|
for _, v := range resp.List {
|
||||||
logs.Debug(v)
|
logs.Debug(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, v := range resp.List.Klines() {
|
||||||
|
logs.Debug(v, v.Order)
|
||||||
|
}
|
||||||
|
|
||||||
logs.Debug("总数:", resp.Count)
|
logs.Debug("总数:", resp.Count)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ var HexKeys = "38 A7 C2 1D E0 6A 17 E2 D1 39 A2 40 9C BA 46 AF 42 C6 FF 05 74 EA
|
|||||||
|
|
||||||
const ZipURL = "http://www.tdx.com.cn/products/data/data/dbf/gbbq.zip"
|
const ZipURL = "http://www.tdx.com.cn/products/data/data/dbf/gbbq.zip"
|
||||||
|
|
||||||
func DownloadAndDecode(dir string) ([]Stock, error) {
|
func DownloadAndDecode(dir string) (Stocks, error) {
|
||||||
filename, err := Download(dir)
|
filename, err := Download(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -57,11 +57,13 @@ func Download(dir string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
err = zip.Decode(zipFilename, dir)
|
decodeDir := filepath.Join(dir, "gbbq")
|
||||||
|
os.MkdirAll(decodeDir, 0777)
|
||||||
|
err = zip.Decode(zipFilename, decodeDir)
|
||||||
return filepath.Join(dir, "gbbq", "gbbq"), err
|
return filepath.Join(dir, "gbbq", "gbbq"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Decode(content []byte) ([]Stock, error) {
|
func Decode(content []byte) (Stocks, error) {
|
||||||
hexStr := strings.ReplaceAll(HexKeys, " ", "")
|
hexStr := strings.ReplaceAll(HexKeys, " ", "")
|
||||||
keys, err := hex.DecodeString(hexStr)
|
keys, err := hex.DecodeString(hexStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -173,6 +175,7 @@ type Stock struct {
|
|||||||
|
|
||||||
type Stocks []Stock
|
type Stocks []Stock
|
||||||
|
|
||||||
|
// GetStock 输入920000,返回流通股本
|
||||||
func (this Stocks) GetStock(code string) (float float64, total float64) {
|
func (this Stocks) GetStock(code string) (float float64, total float64) {
|
||||||
ls := types.List[Stock](this)
|
ls := types.List[Stock](this)
|
||||||
ls = ls.Where(func(i int, v Stock) bool {
|
ls = ls.Where(func(i int, v Stock) bool {
|
||||||
@@ -183,10 +186,12 @@ func (this Stocks) GetStock(code string) (float float64, total float64) {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
ls = ls.Sort(func(a, b Stock) bool {
|
ls = ls.Sort(func(a, b Stock) bool {
|
||||||
return a.Date.Unix() < b.Date.Unix()
|
return a.Date.Unix() > b.Date.Unix()
|
||||||
})
|
})
|
||||||
if len(ls) > 0 {
|
for _, v := range ls {
|
||||||
return ls[0].Float, ls[0].Total
|
if v.Float > 0 && v.Total > 0 {
|
||||||
|
return v.Float, v.Total
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
|||||||
65
manage.go
65
manage.go
@@ -4,10 +4,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"github.com/injoyai/ios/client"
|
"github.com/injoyai/ios/client"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
DefaultDataDir = "./data"
|
||||||
DefaultDatabaseDir = "./data/database"
|
DefaultDatabaseDir = "./data/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,7 +60,6 @@ func NewManageMysql(cfg *ManageConfig, op ...client.Option) (*Manage, error) {
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
Codes: codes,
|
Codes: codes,
|
||||||
Workday: workday,
|
Workday: workday,
|
||||||
Cron: cron.New(cron.WithSeconds()),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,16 +110,66 @@ func NewManage(cfg *ManageConfig, op ...client.Option) (*Manage, error) {
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
Codes: codes,
|
Codes: codes,
|
||||||
Workday: workday,
|
Workday: workday,
|
||||||
Cron: cron.New(cron.WithSeconds()),
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManage2(cfg *ManageConfig, op ...client.Option) (*Manage, error) {
|
||||||
|
//初始化配置
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = &ManageConfig{}
|
||||||
|
}
|
||||||
|
if cfg.CodesFilename == "" {
|
||||||
|
cfg.CodesFilename = DefaultDatabaseDir + "/codes2.db"
|
||||||
|
}
|
||||||
|
if cfg.WorkdayFileName == "" {
|
||||||
|
cfg.WorkdayFileName = DefaultDatabaseDir + "/workday.db"
|
||||||
|
}
|
||||||
|
if cfg.Dial == nil {
|
||||||
|
cfg.Dial = DialDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
//通用客户端
|
||||||
|
commonClient, err := cfg.Dial(op...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
commonClient.Wait.SetTimeout(time.Second * 5)
|
||||||
|
|
||||||
|
//代码管理
|
||||||
|
codes, err := NewCodes2(WithClient(commonClient), WithDBFilename(cfg.CodesFilename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//工作日管理
|
||||||
|
workday, err := NewWorkdaySqlite(commonClient, cfg.WorkdayFileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//连接池
|
||||||
|
p, err := NewPool(func() (*Client, error) {
|
||||||
|
return cfg.Dial(op...)
|
||||||
|
}, cfg.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Manage{
|
||||||
|
Pool: p,
|
||||||
|
Config: cfg,
|
||||||
|
Codes: codes,
|
||||||
|
Workday: workday,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manage struct {
|
type Manage struct {
|
||||||
*Pool
|
*Pool
|
||||||
Config *ManageConfig
|
Config *ManageConfig
|
||||||
Codes *Codes
|
Codes ICodes
|
||||||
Workday *Workday
|
Workday *Workday
|
||||||
Cron *cron.Cron
|
cron *cron.Cron
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// RangeStocks 遍历所有股票
|
// RangeStocks 遍历所有股票
|
||||||
@@ -137,7 +188,11 @@ func (this *Manage) RangeETFs(f func(code string)) {
|
|||||||
|
|
||||||
// AddWorkdayTask 添加工作日任务
|
// AddWorkdayTask 添加工作日任务
|
||||||
func (this *Manage) AddWorkdayTask(spec string, f func(m *Manage)) {
|
func (this *Manage) AddWorkdayTask(spec string, f func(m *Manage)) {
|
||||||
this.Cron.AddFunc(spec, func() {
|
this.once.Do(func() {
|
||||||
|
this.cron = cron.New(cron.WithSeconds())
|
||||||
|
this.cron.Start()
|
||||||
|
})
|
||||||
|
this.cron.AddFunc(spec, func() {
|
||||||
if this.Workday.TodayIs() {
|
if this.Workday.TodayIs() {
|
||||||
f(this)
|
f(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type Kline struct {
|
|||||||
High Price //最高价
|
High Price //最高价
|
||||||
Low Price //最低价
|
Low Price //最低价
|
||||||
Close Price //收盘价,如果是当天,则是最新价/实时价
|
Close Price //收盘价,如果是当天,则是最新价/实时价
|
||||||
|
Order int //成交单数,不一定有值
|
||||||
Volume int64 //成交量
|
Volume int64 //成交量
|
||||||
Amount Price //成交额
|
Amount Price //成交额
|
||||||
Time time.Time //时间
|
Time time.Time //时间
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ func (this Trades) Kline(t time.Time, last Price) *Kline {
|
|||||||
}
|
}
|
||||||
k.Close = v.Price
|
k.Close = v.Price
|
||||||
k.Volume += int64(v.Volume)
|
k.Volume += int64(v.Volume)
|
||||||
|
k.Order += v.Number
|
||||||
k.Amount += v.Price * Price(v.Volume) * 100
|
k.Amount += v.Price * Price(v.Volume) * 100
|
||||||
first++
|
first++
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user