Compare commits

...

6 Commits

Author SHA1 Message Date
injoyai
b86e7dcacf 优化首次拉取会报错的问题(未创建目录) 2025-04-18 13:33:59 +08:00
injoyai
af505eb55e 优化首次拉取会报错的问题 2025-04-18 13:29:15 +08:00
钱纯净
630cbb8939 Merge remote-tracking branch 'origin/master' 2025-04-17 23:17:28 +08:00
钱纯净
0b89aadd7f 优化 2025-04-17 23:17:09 +08:00
injoyai
7c8b4989f6 增加pull-kline的mysql版本,待测试 2025-04-16 14:22:10 +08:00
injoyai
b00f1b65d5 默认按股票处理,不用配置DefaultCodes,基金才需要 2025-04-16 10:44:06 +08:00
8 changed files with 207 additions and 29 deletions

View File

@@ -235,12 +235,18 @@ func (this *Client) GetCodeAll(exchange protocol.Exchange) (*protocol.CodeResp,
// GetQuote 获取盘口五档报价
func (this *Client) GetQuote(codes ...string) (protocol.QuotesResp, error) {
if DefaultCodes == nil {
return nil, errors.New("DefaultCodes未初始化")
}
for i := range codes {
codes[i] = DefaultCodes.AddExchange(codes[i])
//如果是股票代码,则加上前缀
codes[i] = protocol.AddPrefix(codes[i])
if !protocol.IsStock(codes[i]) {
if DefaultCodes == nil {
return nil, errors.New("DefaultCodes未初始化")
}
//不是股票代码的话根据codes的信息加上前缀
codes[i] = DefaultCodes.AddExchange(codes[i])
}
}
f, err := protocol.MQuote.Frame(codes...)
if err != nil {
return nil, err
@@ -256,26 +262,25 @@ func (this *Client) GetQuote(codes ...string) (protocol.QuotesResp, error) {
if len(quotes) != len(codes) {
return nil, fmt.Errorf("预期%d个实际%d个", len(codes), len(quotes))
}
if DefaultCodes == nil {
return nil, errors.New("DefaultCodes未初始化")
}
for i, code := range codes {
m := DefaultCodes.Get(code)
if m == nil {
return nil, fmt.Errorf("未查询到代码[%s]相关信息", code)
}
for ii, v := range quotes[i].SellLevel {
quotes[i].SellLevel[ii].Price = m.Price(v.Price)
}
for ii, v := range quotes[i].BuyLevel {
quotes[i].BuyLevel[ii].Price = m.Price(v.Price)
}
quotes[i].K = protocol.K{
Last: m.Price(quotes[i].K.Last),
Open: m.Price(quotes[i].K.Open),
High: m.Price(quotes[i].K.High),
Low: m.Price(quotes[i].K.Low),
Close: m.Price(quotes[i].K.Close),
if !protocol.IsStock(code) {
m := DefaultCodes.Get(code)
if m == nil {
return nil, fmt.Errorf("未查询到代码[%s]相关信息", code)
}
for ii, v := range quotes[i].SellLevel {
quotes[i].SellLevel[ii].Price = m.Price(v.Price)
}
for ii, v := range quotes[i].BuyLevel {
quotes[i].BuyLevel[ii].Price = m.Price(v.Price)
}
quotes[i].K = protocol.K{
Last: m.Price(quotes[i].K.Last),
Open: m.Price(quotes[i].K.Open),
High: m.Price(quotes[i].K.High),
Low: m.Price(quotes[i].K.Low),
Close: m.Price(quotes[i].K.Close),
}
}
}
}

View File

@@ -314,7 +314,8 @@ func (this *CodeModel) FullCode() string {
}
func (this *CodeModel) Price(p protocol.Price) protocol.Price {
return p * protocol.Price(math.Pow10(int(3-this.Decimal)))
return protocol.Price(float64(p) * 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 {

148
extend/pull-kline-mysql.go Normal file
View File

@@ -0,0 +1,148 @@
package extend
import (
"context"
_ "github.com/go-sql-driver/mysql"
"github.com/injoyai/base/chans"
"github.com/injoyai/logs"
"github.com/injoyai/tdx"
"github.com/injoyai/tdx/protocol"
"xorm.io/core"
"xorm.io/xorm"
)
func NewPullKlineMysql(cfg PullKlineConfig) (*PullKlineMysql, error) {
db, err := xorm.NewEngine("mysql", cfg.Dir)
if err != nil {
return nil, err
}
db.SetMapper(core.SameMapper{})
_tables := []*KlineTable(nil)
for _, v := range cfg.Tables {
table := KlineTableMap[v]
if err = db.Sync2(table); err != nil {
return nil, err
}
_tables = append(_tables, table)
}
return &PullKlineMysql{
tables: _tables,
Config: cfg,
DB: db,
}, nil
}
type PullKlineMysql struct {
tables []*KlineTable
Config PullKlineConfig
DB *xorm.Engine
}
func (this *PullKlineMysql) Name() string {
return "拉取k线数据"
}
func (this *PullKlineMysql) Run(ctx context.Context, m *tdx.Manage) error {
limit := chans.NewWaitLimit(uint(this.Config.Limit))
//1. 获取所有股票代码
codes := this.Config.Codes
if len(codes) == 0 {
codes = m.Codes.GetStocks()
}
for _, v := range codes {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
limit.Add()
go func(code string) {
defer limit.Done()
for _, table := range this.tables {
if table == nil {
continue
}
select {
case <-ctx.Done():
return
default:
}
var err error
//2. 获取最后一条数据
last := new(Kline)
if _, err = this.DB.Table(table).Where("Code=?", code).Desc("Date").Get(last); err != nil {
logs.Err(err)
return
}
//3. 从服务器获取数据
insert := Klines{}
err = m.Do(func(c *tdx.Client) error {
insert, err = this.pull(code, last.Date, table.Handler(c))
return err
})
if err != nil {
logs.Err(err)
return
}
//4. 插入数据库
err = tdx.NewSessionFunc(this.DB, func(session *xorm.Session) error {
for i, v := range insert {
if i == 0 {
if _, err := session.Table(table).Where("Code=? and Date >= ?", code, v.Date).Delete(); err != nil {
return err
}
}
if _, err := session.Table(table).Insert(v); err != nil {
return err
}
}
return nil
})
logs.PrintErr(err)
}
}(v)
}
limit.Wait()
return nil
}
func (this *PullKlineMysql) pull(code string, lastDate int64, f func(code string, f func(k *protocol.Kline) bool) (*protocol.KlineResp, error)) (Klines, error) {
if lastDate == 0 {
lastDate = protocol.ExchangeEstablish.Unix()
}
resp, err := f(code, func(k *protocol.Kline) bool {
return k.Time.Unix() <= lastDate || k.Time.Unix() <= this.Config.StartAt.Unix()
})
if err != nil {
return nil, err
}
ks := Klines{}
for _, v := range resp.List {
ks = append(ks, &Kline{
Code: code,
Date: v.Time.Unix(),
Open: v.Open,
High: v.High,
Low: v.Low,
Close: v.Close,
Volume: v.Volume,
Amount: v.Amount,
})
}
return ks, nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/injoyai/logs"
"github.com/injoyai/tdx"
"github.com/injoyai/tdx/protocol"
"os"
"path/filepath"
"sort"
"time"
@@ -41,6 +42,18 @@ var (
Quarter: NewKlineTable("QuarterKline", func(c *tdx.Client) KlineHandler { return c.GetKlineQuarterUntil }),
Year: NewKlineTable("YearKline", func(c *tdx.Client) KlineHandler { return c.GetKlineYearUntil }),
}
AllKlineTables = []string{
"MinuteKline",
"Minute5Kline",
"Minute15Kline",
"Minute30Kline",
"HourKline",
"DayKline",
"WeekKline",
"MonthKline",
"QuarterKline",
"YearKline",
}
)
type PullKlineConfig struct {
@@ -91,6 +104,8 @@ func (this *PullKline) Run(ctx context.Context, m *tdx.Manage) error {
go func(code string) {
defer limit.Done()
_ = os.MkdirAll(this.Config.Dir, 0777)
//连接数据库
db, err := xorm.NewEngine("sqlite", filepath.Join(this.Config.Dir, code+".db"))
if err != nil {
@@ -111,7 +126,7 @@ func (this *PullKline) Run(ctx context.Context, m *tdx.Manage) error {
default:
}
db.Sync2(table)
logs.PrintErr(db.Sync2(table))
//2. 获取最后一条数据
last := new(Kline)
@@ -186,7 +201,7 @@ func (this *PullKline) pull(code string, lastDate int64, f func(code string, f f
}
type Kline struct {
Code string `json:"code" xorm:"-"` //代码
Code string `json:"code"` //代码
Date int64 `json:"date"` //时间节点 2006-01-02 15:00
Open protocol.Price `json:"open"` //开盘价
High protocol.Price `json:"high"` //最高价

1
go.mod
View File

@@ -4,6 +4,7 @@ go 1.20
require (
github.com/glebarez/go-sqlite v1.22.0
github.com/go-sql-driver/mysql v1.7.0
github.com/injoyai/base v1.0.18
github.com/injoyai/conv v1.1.10
github.com/injoyai/ios v0.0.4

1
go.sum
View File

@@ -14,6 +14,7 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=

View File

@@ -47,7 +47,7 @@ type Quote struct {
func (this *Quote) String() string {
return fmt.Sprintf(`%s%s
%s
%s, 现量:%s, 总金额:%s, 内盘:%s, 外盘:%s
%s, 现量:%s, 总金额:%s, 内盘:%s, 外盘:%s
%s%s
`,
this.Exchange.String(), this.Code, this.K,
@@ -142,9 +142,9 @@ func (this quote) Decode(bs []byte) QuotesResp {
sellLevel := PriceLevel{}
bs, p = GetPrice(bs)
buyLevel.Price = p + sec.K.Close
buyLevel.Price = p*10 + sec.K.Close
bs, p = GetPrice(bs)
sellLevel.Price = p + sec.K.Close
sellLevel.Price = p*10 + sec.K.Close
bs, buyLevel.Number = CutInt(bs)
bs, sellLevel.Number = CutInt(bs)

View File

@@ -78,6 +78,13 @@ func DecodeK(bs []byte) ([]byte, K) {
bs, k.Low = GetPrice(bs)
k.Low += k.Close
//默认按股票展示
k.Last *= 10
k.Open *= 10
k.Close *= 10
k.High *= 10
k.Low *= 10
return bs, k
}