Compare commits

...

25 Commits

Author SHA1 Message Date
injoyai
36a8479aa4 优化codes-server,待完成 2025-11-26 17:00:05 +08:00
injoyai
0d1e6b6b51 修复指数判断不全的问题 2025-11-26 16:49:11 +08:00
injoyai
6fd178245a 增加LICENSE文件 2025-11-26 15:41:18 +08:00
injoyai
0efd0735e6 Merge pull request #27 from jingmian/patch-1
Update main.go
2025-11-25 19:08:44 +08:00
镜面王子
b01da71236 Update main.go
NewManageMysql函数不接受*ManageConfig类型参数,而是接受Option函数类型参数
2025-11-25 17:34:49 +08:00
injoyai
26e2479e2f 细节优化 2025-11-21 16:08:46 +08:00
injoyai
6d0125afef 优化manage,修改成Option的方式,老版本的名称从NewManage改成MewManageSqlite 2025-11-21 14:17:09 +08:00
injoyai
2d77d769fd 增加Codes2的Option名称前缀 2025-11-21 14:14:54 +08:00
injoyai
33627c3d6c 增加新版迭代器,Iter和IterYear 2025-11-21 14:13:40 +08:00
injoyai
1ff1ceb8d7 开放interval包,命名为lib 2025-11-21 08:44:37 +08:00
injoyai
233d1b689e 更新etf的判断 2025-11-20 09:31:08 +08:00
injoyai
2a27eea873 增加指数代码的判断 2025-11-20 09:08:22 +08:00
injoyai
fcfb329712 增加指数代码的判断 2025-11-20 08:56:53 +08:00
injoyai
ed2c814fab 增加指数分钟k线的方法 2025-11-19 15:57:02 +08:00
injoyai
fc04b5042a 重新定义接口 2025-11-17 15:48:57 +08:00
injoyai
b2a4c00253 重新定义接口 2025-11-17 15:48:35 +08:00
injoyai
c68c7582bc go版本升级到1.23 2025-11-17 15:40:41 +08:00
injoyai
4e62ee1c5e 更新文档 2025-11-17 14:44:25 +08:00
injoyai
8eeab6f533 增加Trades生成Kline的单数字段,当天数据才有效 2025-11-17 11:05:35 +08:00
injoyai
5fd492e881 优化Codes2 2025-11-17 11:04:55 +08:00
injoyai
fcb6c995ad 优化Codes2 2025-11-17 09:48:20 +08:00
injoyai
e6411858e9 定义ICodes接口 2025-11-17 09:48:06 +08:00
injoyai
f4b2497e92 把manage中的Codes改成接口 2025-11-17 09:47:29 +08:00
injoyai
e250223e57 增加codes2的示例 2025-11-17 09:46:58 +08:00
injoyai
d19cfb4416 优化gbbq 2025-11-17 09:45:33 +08:00
30 changed files with 767 additions and 218 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 injoyai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,9 +1,8 @@
### 说明 ### 说明
* 参考golang库 [`https://github.com/bensema/gotdx`](https://github.com/bensema/gotdx) * 参考 [`https://github.com/bensema/gotdx`](https://github.com/bensema/gotdx)
* 参考python库 [`https://github.com/mootdx/mootdx`](https://github.com/mootdx/mootdx) * 参考 [`https://github.com/mootdx/mootdx`](https://github.com/mootdx/mootdx)
* 参考 [`https://github.com/jing2uo/tdx2db`](https://github.com/jing2uo/tdx2db)
* 数据入库示例(开发中) [`https://github.com/injoyai/stock`](https://github.com/injoyai/stock)
### 如何使用 ### 如何使用

View File

@@ -3,6 +3,10 @@ package tdx
import ( import (
"errors" "errors"
"fmt" "fmt"
"runtime/debug"
"sync/atomic"
"time"
"github.com/injoyai/base/maps" "github.com/injoyai/base/maps"
"github.com/injoyai/base/maps/wait" "github.com/injoyai/base/maps/wait"
"github.com/injoyai/conv" "github.com/injoyai/conv"
@@ -10,11 +14,8 @@ import (
"github.com/injoyai/ios/client" "github.com/injoyai/ios/client"
"github.com/injoyai/ios/module/common" "github.com/injoyai/ios/module/common"
"github.com/injoyai/logs" "github.com/injoyai/logs"
"github.com/injoyai/tdx/internal/bse" "github.com/injoyai/tdx/lib/bse"
"github.com/injoyai/tdx/protocol" "github.com/injoyai/tdx/protocol"
"runtime/debug"
"sync/atomic"
"time"
) )
const ( const (
@@ -560,6 +561,26 @@ func (this *Client) GetIndexAll(Type uint8, code string) (*protocol.KlineResp, e
return this.GetIndexUntil(Type, code, func(k *protocol.Kline) bool { return false }) return this.GetIndexUntil(Type, code, func(k *protocol.Kline) bool { return false })
} }
func (this *Client) GetIndexMinute(code string, start, count uint16) (*protocol.KlineResp, error) {
return this.GetIndex(protocol.TypeKlineMinute, code, start, count)
}
func (this *Client) GetIndex5Minute(code string, start, count uint16) (*protocol.KlineResp, error) {
return this.GetIndex(protocol.TypeKline5Minute, code, start, count)
}
func (this *Client) GetIndex15Minute(code string, start, count uint16) (*protocol.KlineResp, error) {
return this.GetIndex(protocol.TypeKline15Minute, code, start, count)
}
func (this *Client) GetIndex30Minute(code string, start, count uint16) (*protocol.KlineResp, error) {
return this.GetIndex(protocol.TypeKline30Minute, code, start, count)
}
func (this *Client) GetIndex60Minute(code string, start, count uint16) (*protocol.KlineResp, error) {
return this.GetIndex(protocol.TypeKline60Minute, code, start, count)
}
func (this *Client) GetIndexDay(code string, start, count uint16) (*protocol.KlineResp, error) { func (this *Client) GetIndexDay(code string, start, count uint16) (*protocol.KlineResp, error) {
return this.GetIndex(protocol.TypeKlineDay, code, start, count) return this.GetIndex(protocol.TypeKlineDay, code, start, count)
} }

View File

@@ -2,25 +2,31 @@ package tdx
import ( import (
"errors" "errors"
"iter"
"math"
"os"
"path/filepath"
"time"
"github.com/injoyai/conv" "github.com/injoyai/conv"
"github.com/injoyai/ios/client" "github.com/injoyai/ios/client"
"github.com/injoyai/logs" "github.com/injoyai/logs"
"github.com/injoyai/tdx/protocol" "github.com/injoyai/tdx/protocol"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"math"
"os"
"path/filepath"
"time"
"xorm.io/core" "xorm.io/core"
"xorm.io/xorm" "xorm.io/xorm"
) )
type ICodes interface { type ICodes interface {
Iter() iter.Seq2[string, *CodeModel]
Get(code string) *CodeModel Get(code string) *CodeModel
GetName(code string) string GetName(code string) string
GetStocks(limit ...int) []string GetStocks(limit ...int) CodeModels
GetETFs(limit ...int) []string GetStockCodes(limit ...int) []string
Update() error GetETFs(limit ...int) CodeModels
GetETFCodes(limit ...int) []string
GetIndexes(limits ...int) CodeModels
GetIndexCodes(limits ...int) []string
} }
// DefaultCodes 增加单例,部分数据需要通过Codes里面的信息计算 // DefaultCodes 增加单例,部分数据需要通过Codes里面的信息计算
@@ -131,6 +137,8 @@ func NewCodes(c *Client, db *xorm.Engine) (*Codes, error) {
return cc, cc.Update(true) return cc, cc.Update(true)
} }
var _ ICodes = &Codes{}
type Codes struct { type Codes struct {
*Client //客户端 *Client //客户端
db *xorm.Engine //数据库实例 db *xorm.Engine //数据库实例
@@ -139,6 +147,20 @@ type Codes struct {
exchanges map[string][]string //交易所缓存 exchanges map[string][]string //交易所缓存
} }
func (this *Codes) Get(code string) *CodeModel {
return this.Map[code]
}
func (this *Codes) Iter() iter.Seq2[string, *CodeModel] {
return func(yield func(string, *CodeModel) bool) {
for _, code := range this.list {
if !yield(code.FullCode(), code) {
break
}
}
}
}
// GetName 获取股票名称 // GetName 获取股票名称
func (this *Codes) GetName(code string) string { func (this *Codes) GetName(code string) string {
if v, ok := this.Map[code]; ok { if v, ok := this.Map[code]; ok {
@@ -148,13 +170,13 @@ func (this *Codes) GetName(code string) string {
} }
// GetStocks 获取股票代码,sh6xxx sz0xx sz30xx // GetStocks 获取股票代码,sh6xxx sz0xx sz30xx
func (this *Codes) GetStocks(limits ...int) []string { func (this *Codes) GetStocks(limits ...int) CodeModels {
limit := conv.Default(-1, limits...) limit := conv.Default(-1, limits...)
ls := []string(nil) ls := []*CodeModel(nil)
for _, m := range this.list { for _, m := range this.list {
code := m.FullCode() code := m.FullCode()
if protocol.IsStock(code) { if protocol.IsStock(code) {
ls = append(ls, code) ls = append(ls, m)
} }
if limit > 0 && len(ls) >= limit { if limit > 0 && len(ls) >= limit {
break break
@@ -163,14 +185,18 @@ func (this *Codes) GetStocks(limits ...int) []string {
return ls return ls
} }
func (this *Codes) GetStockCodes(limits ...int) []string {
return this.GetStocks(limits...).Codes()
}
// GetETFs 获取基金代码,sz159xxx,sh510xxx,sh511xxx // GetETFs 获取基金代码,sz159xxx,sh510xxx,sh511xxx
func (this *Codes) GetETFs(limits ...int) []string { func (this *Codes) GetETFs(limits ...int) CodeModels {
limit := conv.Default(-1, limits...) limit := conv.Default(-1, limits...)
ls := []string(nil) ls := []*CodeModel(nil)
for _, m := range this.list { for _, m := range this.list {
code := m.FullCode() code := m.FullCode()
if protocol.IsETF(code) { if protocol.IsETF(code) {
ls = append(ls, code) ls = append(ls, m)
} }
if limit > 0 && len(ls) >= limit { if limit > 0 && len(ls) >= limit {
break break
@@ -179,8 +205,29 @@ func (this *Codes) GetETFs(limits ...int) []string {
return ls return ls
} }
func (this *Codes) Get(code string) *CodeModel { // GetETFCodes 获取基金代码,sz159xxx,sh510xxx,sh511xxx
return this.Map[code] func (this *Codes) GetETFCodes(limits ...int) []string {
return this.GetETFs(limits...).Codes()
}
// GetIndexes 获取基金代码,sz159xxx,sh510xxx,sh511xxx
func (this *Codes) GetIndexes(limits ...int) CodeModels {
limit := conv.Default(-1, limits...)
ls := []*CodeModel(nil)
for _, m := range this.list {
code := m.FullCode()
if protocol.IsIndex(code) {
ls = append(ls, m)
}
if limit > 0 && len(ls) >= limit {
break
}
}
return ls
}
func (this *Codes) GetIndexCodes(limits ...int) []string {
return this.GetIndexes(limits...).Codes()
} }
func (this *Codes) AddExchange(code string) string { func (this *Codes) AddExchange(code string) string {
@@ -368,3 +415,13 @@ func NewSessionFunc(db *xorm.Engine, fn func(session *xorm.Session) error) error
} }
return nil return nil
} }
type CodeModels []*CodeModel
func (this CodeModels) Codes() []string {
codes := make([]string, len(this))
for i, v := range this {
codes[i] = v.FullCode()
}
return codes
}

View File

@@ -2,70 +2,105 @@ package tdx
import ( import (
"errors" "errors"
"iter"
"os"
"path/filepath"
"time"
"github.com/injoyai/base/maps" "github.com/injoyai/base/maps"
"github.com/injoyai/base/types" "github.com/injoyai/base/types"
"github.com/injoyai/conv" "github.com/injoyai/conv"
"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/xorms" "github.com/injoyai/tdx/lib/gbbq"
"github.com/injoyai/tdx/lib/xorms"
"github.com/injoyai/tdx/protocol" "github.com/injoyai/tdx/protocol"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"path/filepath"
"time"
"xorm.io/xorm" "xorm.io/xorm"
) )
type Codes2Option func(*Codes2) type Codes2Option func(*Codes2)
func WithFilename(filename string) Codes2Option { func WithCodes2Database(filename string) Codes2Option {
return func(c *Codes2) { return func(c *Codes2) {
c.filename = filename c.dbFilename = filename
} }
} }
func WithSpec(spec string) Codes2Option { func WithCodes2TempDir(dir string) Codes2Option {
return func(c *Codes2) {
c.tempDir = dir
}
}
func WithCodes2Spec(spec string) Codes2Option {
return func(c *Codes2) { return func(c *Codes2) {
c.spec = spec c.spec = spec
} }
} }
func WithKey(key string) Codes2Option { func WithCodes2UpdateKey(key string) Codes2Option {
return func(c *Codes2) { return func(c *Codes2) {
c.key = key c.updateKey = key
} }
} }
func WithRetry(retry int) Codes2Option { func WithCodes2Retry(retry int) Codes2Option {
return func(c *Codes2) { return func(c *Codes2) {
c.retry = retry c.retry = retry
} }
} }
func WithCodes2Client(c *Client) Codes2Option {
return func(cs *Codes2) {
cs.c = c
}
}
func WithCodes2Dial(dial ios.DialFunc, op ...client.Option) Codes2Option {
return func(c *Codes2) {
c.dial = dial
c.dialOption = op
}
}
func WithCodes2DialOption(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: DefaultRetry,
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
} }
@@ -82,7 +117,7 @@ func NewCodes2(op ...Codes2Option) (*Codes2, error) {
// 定时更新 // 定时更新
cr := cron.New(cron.WithSeconds()) cr := cron.New(cron.WithSeconds())
_, err = cr.AddFunc(cs.spec, func() { _, err = cr.AddFunc(cs.spec, func() {
for i := 0; i < 3; i++ { for i := 0; i == 0 || i < cs.retry; i++ {
if err := cs.Update(); err != nil { if err := cs.Update(); err != nil {
logs.Err(err) logs.Err(err)
<-time.After(time.Minute * 5) <-time.After(time.Minute * 5)
@@ -103,9 +138,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 //
@@ -114,11 +150,13 @@ type Codes2 struct {
内部字段 内部字段
*/ */
c *Client // c *Client //
db *xorms.Engine // db *xorms.Engine //
stocks types.List[string] //缓存 stocks types.List[*CodeModel] //股票缓存
etfs types.List[string] //缓存 etfs types.List[*CodeModel] //etf缓存
m *maps.Generic[string, *CodeModel] //缓存 indexes types.List[*CodeModel] //指数缓存
all types.List[*CodeModel] //全部缓存
m *maps.Generic[string, *CodeModel] //缓存
} }
func (this *Codes2) Get(code string) *CodeModel { func (this *Codes2) Get(code string) *CodeModel {
@@ -126,6 +164,16 @@ func (this *Codes2) Get(code string) *CodeModel {
return v return v
} }
func (this *Codes2) Iter() iter.Seq2[string, *CodeModel] {
return func(yield func(string, *CodeModel) bool) {
for _, code := range this.all {
if !yield(code.FullCode(), code) {
break
}
}
}
}
func (this *Codes2) GetName(code string) string { func (this *Codes2) GetName(code string) string {
v, _ := this.m.Get(code) v, _ := this.m.Get(code)
if v == nil { if v == nil {
@@ -134,24 +182,41 @@ func (this *Codes2) GetName(code string) string {
return v.Name return v.Name
} }
func (this *Codes2) GetStocks(limit ...int) []string { func (this *Codes2) GetStocks(limit ...int) CodeModels {
size := conv.Default(this.stocks.Len(), limit...) size := conv.Default(this.stocks.Len(), limit...)
return this.stocks.Limit(size) return CodeModels(this.stocks.Limit(size))
} }
func (this *Codes2) GetETFs(limit ...int) []string { func (this *Codes2) GetStockCodes(limit ...int) []string {
return this.GetStocks(limit...).Codes()
}
func (this *Codes2) GetETFs(limit ...int) CodeModels {
size := conv.Default(this.etfs.Len(), limit...) size := conv.Default(this.etfs.Len(), limit...)
return this.etfs.Limit(size) return CodeModels(this.etfs.Limit(size))
}
func (this *Codes2) GetETFCodes(limit ...int) []string {
return this.GetETFs(limit...).Codes()
}
func (this *Codes2) GetIndexes(limit ...int) CodeModels {
size := conv.Default(this.etfs.Len(), limit...)
return CodeModels(this.indexes.Limit(size))
}
func (this *Codes2) GetIndexCodes(limit ...int) []string {
return this.GetIndexes(limit...).Codes()
} }
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
} }
@@ -184,20 +249,26 @@ func (this *Codes2) Update() error {
return err return err
} }
stocks := []string(nil) stocks := []*CodeModel(nil)
etfs := []string(nil) etfs := []*CodeModel(nil)
indexes := []*CodeModel(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, v)
case protocol.IsETF(fullCode): case protocol.IsETF(fullCode):
etfs = append(etfs, fullCode) etfs = append(etfs, v)
case protocol.IsIndex(fullCode):
indexes = append(indexes, v)
} }
} }
this.stocks = stocks this.stocks = stocks
this.etfs = etfs this.etfs = etfs
this.indexes = indexes
this.all = codes
return nil return nil
} }
@@ -255,7 +326,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 +375,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
} }

View File

@@ -1,16 +1,17 @@
package main package main
import ( import (
"time"
"github.com/injoyai/logs" "github.com/injoyai/logs"
"github.com/injoyai/tdx" "github.com/injoyai/tdx"
"time"
) )
func main() { func main() {
m, err := tdx.NewManage(nil) m, err := tdx.NewManage()
logs.PanicErr(err) logs.PanicErr(err)
codes := m.Codes.GetStocks() codes := m.Codes.GetStocks().Codes()
//codes = []string{ //codes = []string{
// "sz000001", // "sz000001",
// "sz000002", // "sz000002",

25
example/Codes2/main.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"strings"
"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)
for _, v := range cs.GetIndexes().Codes() {
if strings.HasPrefix(v, "sz") {
logs.Debug(v)
}
}
}

View File

@@ -0,0 +1,29 @@
package main
import (
"strings"
"github.com/injoyai/logs"
"github.com/injoyai/tdx"
)
func main() {
cs, err := tdx.NewCodes2()
logs.PanicErr(err)
ls := cs.GetETFCodes()
shNumber := 0
szNumber := 0
for _, v := range ls {
switch {
case strings.HasPrefix(v, "sh"):
shNumber++
case strings.HasPrefix(v, "sz"):
szNumber++
}
}
logs.Debug("sh:", shNumber)
logs.Debug("sz:", szNumber)
}

View File

@@ -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)
}) })
} }

23
example/Manage/main.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"github.com/injoyai/logs"
"github.com/injoyai/tdx"
)
func main() {
m, err := tdx.NewManage()
logs.PanicErr(err)
err = m.Do(func(c *tdx.Client) error {
resp, err := c.GetIndexDayAll("sh000001")
if err != nil {
return err
}
for _, v := range resp.List {
logs.Debug(v)
}
return nil
})
logs.PanicErr(err)
}

View File

@@ -6,12 +6,11 @@ import (
) )
func main() { func main() {
_, err := tdx.NewManageMysql(&tdx.ManageConfig{ _, err := tdx.NewManageMysql(
Number: 2, tdx.WithClients(2),
CodesFilename: "root:root@tcp(192.168.1.105:3306)/stock?charset=utf8mb4&parseTime=True&loc=Local", tdx.WithCodesDatabase("root:root@tcp(192.168.1.105:3306)/stock?charset=utf8mb4&parseTime=True&loc=Local"),
WorkdayFileName: "root:root@tcp(192.168.1.105:3306)/stock?charset=utf8mb4&parseTime=True&loc=Local", tdx.WithWorkdayDatabase("root:root@tcp(192.168.1.105:3306)/stock?charset=utf8mb4&parseTime=True&loc=Local"),
Dial: nil, )
})
logs.PanicErr(err) logs.PanicErr(err)
logs.Debug("done") logs.Debug("done")
} }

View File

@@ -2,16 +2,17 @@ package main
import ( import (
"context" "context"
"path/filepath"
"time"
"github.com/injoyai/logs" "github.com/injoyai/logs"
"github.com/injoyai/tdx" "github.com/injoyai/tdx"
"github.com/injoyai/tdx/extend" "github.com/injoyai/tdx/extend"
"path/filepath"
"time"
) )
func main() { func main() {
m, err := tdx.NewManage(nil) m, err := tdx.NewManage()
logs.PanicErr(err) logs.PanicErr(err)
err = extend.NewPullKline(extend.PullKlineConfig{ err = extend.NewPullKline(extend.PullKlineConfig{

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"github.com/injoyai/logs" "github.com/injoyai/logs"
"github.com/injoyai/tdx" "github.com/injoyai/tdx"
"github.com/injoyai/tdx/extend" "github.com/injoyai/tdx/extend"
@@ -11,7 +12,7 @@ func main() {
pt := extend.NewPullTrade("./data/trade") pt := extend.NewPullTrade("./data/trade")
m, err := tdx.NewManage(nil) m, err := tdx.NewManage()
logs.PanicErr(err) logs.PanicErr(err)
err = pt.PullYear(context.Background(), m, 2025, "sz000001") err = pt.PullYear(context.Background(), m, 2025, "sz000001")

View File

@@ -1,7 +1,7 @@
package extend package extend
import ( import (
"github.com/injoyai/tdx/internal/bse" "github.com/injoyai/tdx/lib/bse"
) )
func GetBjCodes() ([]string, error) { func GetBjCodes() ([]string, error) {

View File

@@ -3,43 +3,147 @@ package extend
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/injoyai/conv"
"github.com/injoyai/tdx"
"io" "io"
"iter"
"net/http" "net/http"
"path/filepath"
"github.com/injoyai/base/maps"
"github.com/injoyai/conv"
"github.com/injoyai/logs"
"github.com/injoyai/tdx"
"github.com/robfig/cron/v3"
) )
func ListenCodesHTTP(port int, filename ...string) error { func ListenCodesHTTP(port int, op ...tdx.Codes2Option) error {
code, err := tdx.DialCodes(conv.Default(filepath.Join(tdx.DefaultDatabaseDir, "codes.db"), filename...)) code, err := tdx.NewCodes2(op...)
if err != nil { if err != nil {
return nil return nil
} }
succ := func(w http.ResponseWriter, data any) {
w.WriteHeader(http.StatusOK)
w.Write(conv.Bytes(data))
}
return http.ListenAndServe(fmt.Sprintf(":%d", port), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.ListenAndServe(fmt.Sprintf(":%d", port), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.RequestURI { switch r.RequestURI {
case "/all":
case "/stocks": case "/stocks":
ls := code.GetStocks() succ(w, code.GetStocks())
w.WriteHeader(http.StatusOK)
w.Write(conv.Bytes(ls))
case "/etfs": case "/etfs":
ls := code.GetETFs() succ(w, code.GetETFs())
w.WriteHeader(http.StatusOK) case "/indexes":
w.Write(conv.Bytes(ls)) succ(w, code.GetIndexes())
default: default:
http.NotFound(w, r) http.NotFound(w, r)
} }
})) }))
} }
func DialCodesHTTP(address string) *CodesHTTP { func DialCodesHTTP(address string) (c *CodesHTTP, err error) {
return &CodesHTTP{address: address} c = &CodesHTTP{address: address}
cr := cron.New(cron.WithSeconds())
_, err = cr.AddFunc("0 20 9 * * *", func() { logs.PrintErr(c.Update()) })
if err != nil {
return
}
err = c.Update()
if err != nil {
return
}
cr.Start()
return c, nil
} }
type CodesHTTP struct { type CodesHTTP struct {
address string address string
stocks tdx.CodeModels
etfs tdx.CodeModels
indexes tdx.CodeModels
m maps.Generic[string, *tdx.CodeModel]
} }
func (this *CodesHTTP) getList(path string) ([]string, error) { func (this *CodesHTTP) Iter() iter.Seq2[string, *tdx.CodeModel] {
return func(yield func(string, *tdx.CodeModel) bool) {
for _, v := range this.stocks {
if !yield(v.FullCode(), v) {
return
}
}
for _, v := range this.etfs {
if !yield(v.FullCode(), v) {
return
}
}
for _, v := range this.indexes {
if !yield(v.FullCode(), v) {
return
}
}
}
}
func (this *CodesHTTP) Get(code string) *tdx.CodeModel {
return this.m.MustGet(code)
}
func (this *CodesHTTP) GetName(code string) string {
v := this.m.MustGet(code)
if v != nil {
return v.Name
}
return ""
}
func (this *CodesHTTP) GetStocks(limit ...int) tdx.CodeModels {
return this.stocks
}
func (this *CodesHTTP) GetStockCodes(limit ...int) []string {
return this.stocks.Codes()
}
func (this *CodesHTTP) GetETFs(limit ...int) tdx.CodeModels {
return this.etfs
}
func (this *CodesHTTP) GetETFCodes(limit ...int) []string {
return this.etfs.Codes()
}
func (this *CodesHTTP) GetIndexes(limits ...int) tdx.CodeModels {
return this.indexes
}
func (this *CodesHTTP) GetIndexCodes(limits ...int) []string {
return this.indexes.Codes()
}
func (this *CodesHTTP) Update() (err error) {
this.stocks, err = this.getList("/stocks")
if err != nil {
return
}
for _, v := range this.stocks {
this.m.Set(v.FullCode(), v)
}
this.etfs, err = this.getList("/etfs")
if err != nil {
return
}
for _, v := range this.etfs {
this.m.Set(v.FullCode(), v)
}
this.indexes, err = this.getList("/indexes")
if err != nil {
return
}
for _, v := range this.indexes {
this.m.Set(v.FullCode(), v)
}
return
}
func (this *CodesHTTP) getList(path string) (tdx.CodeModels, error) {
resp, err := http.DefaultClient.Get(this.address + path) resp, err := http.DefaultClient.Get(this.address + path)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -52,15 +156,7 @@ func (this *CodesHTTP) getList(path string) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ls := []string(nil) ls := tdx.CodeModels{}
err = json.Unmarshal(bs, &ls) err = json.Unmarshal(bs, &ls)
return ls, err return ls, err
} }
func (this *CodesHTTP) GetStocks() ([]string, error) {
return this.getList("/stocks")
}
func (this *CodesHTTP) GetETFs() ([]string, error) {
return this.getList("/etfs")
}

View File

@@ -48,7 +48,7 @@ func (this *PullKlineMysql) Run(ctx context.Context, m *tdx.Manage) error {
//1. 获取所有股票代码 //1. 获取所有股票代码
codes := this.Config.Codes codes := this.Config.Codes
if len(codes) == 0 { if len(codes) == 0 {
codes = m.Codes.GetStocks() codes = m.Codes.GetStockCodes()
} }
for _, v := range codes { for _, v := range codes {

View File

@@ -110,7 +110,7 @@ func (this *PullKline) Run(ctx context.Context, m *tdx.Manage) error {
//1. 获取所有股票代码 //1. 获取所有股票代码
codes := this.Config.Codes codes := this.Config.Codes
if len(codes) == 0 { if len(codes) == 0 {
codes = m.Codes.GetStocks() codes = m.Codes.GetStockCodes()
} }
for _, v := range codes { for _, v := range codes {

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/injoyai/tdx module github.com/injoyai/tdx
go 1.20 go 1.23
require ( require (
github.com/glebarez/go-sqlite v1.22.0 github.com/glebarez/go-sqlite v1.22.0

16
go.sum
View File

@@ -22,12 +22,11 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/injoyai/base v1.2.15 h1:K/ysPqZl7vgNUAz/jpG1IdDpzdSMWvUfoJL+1gPdM9g=
github.com/injoyai/base v1.2.15/go.mod h1:NfCQjml3z2pCvQ3J3YcOXtecqXD0xVPKjo4YTsMLhr8=
github.com/injoyai/base v1.2.17 h1:+qYeCSeEMWgmTla+LBC0Ozan9ysS4mV0ne5nfMt9opU= github.com/injoyai/base v1.2.17 h1:+qYeCSeEMWgmTla+LBC0Ozan9ysS4mV0ne5nfMt9opU=
github.com/injoyai/base v1.2.17/go.mod h1:NfCQjml3z2pCvQ3J3YcOXtecqXD0xVPKjo4YTsMLhr8= github.com/injoyai/base v1.2.17/go.mod h1:NfCQjml3z2pCvQ3J3YcOXtecqXD0xVPKjo4YTsMLhr8=
github.com/injoyai/conv v1.2.5 h1:G4OCyF0NTZul5W1u9IgXDOhW4/zmIigdPKXFHQGmv1M= github.com/injoyai/conv v1.2.5 h1:G4OCyF0NTZul5W1u9IgXDOhW4/zmIigdPKXFHQGmv1M=
@@ -39,6 +38,7 @@ github.com/injoyai/logs v1.0.12/go.mod h1:+dKEL6GvaFqqVRatqUBiCicJbZnAgtj7hVs824
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -50,6 +50,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -84,11 +85,14 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -98,9 +102,11 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
@@ -114,8 +120,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
@@ -123,10 +132,13 @@ modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWP
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0= xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0=

View File

@@ -12,7 +12,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/injoyai/base/types" "github.com/injoyai/base/types"
"github.com/injoyai/tdx/internal/zip" "github.com/injoyai/tdx/lib/zip"
"io" "io"
"math" "math"
"net/http" "net/http"
@@ -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
} }

300
manage.go
View File

@@ -2,142 +2,258 @@ package tdx
import ( import (
"errors" "errors"
"sync"
"github.com/injoyai/conv"
"github.com/injoyai/ios/client" "github.com/injoyai/ios/client"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"time"
) )
const ( const (
DefaultClients = 1
DefaultRetry = 3
DefaultDataDir = "./data"
DefaultDatabaseDir = "./data/database" DefaultDatabaseDir = "./data/database"
) )
func NewManageMysql(cfg *ManageConfig, op ...client.Option) (*Manage, error) { func NewManageMysql(op ...Option) (*Manage, error) {
//初始化配置 return NewManage(
if cfg == nil { WithOptions(op...),
cfg = &ManageConfig{} WithDialCodes(func(c *Client, database string) (ICodes, error) {
} if database == "" {
if cfg.CodesFilename == "" { return nil, errors.New("未配置Codes的数据库")
return nil, errors.New("未配置Codes的数据库") }
} return NewCodesMysql(c, database)
if cfg.WorkdayFileName == "" { }),
return nil, errors.New("未配置Workday的数据库") WithDialWorkday(func(c *Client, database string) (*Workday, error) {
} if database == "" {
if cfg.Dial == nil { return nil, errors.New("未配置Workday的数据库")
cfg.Dial = DialDefault }
} return NewWorkdayMysql(c, database)
}),
//通用客户端 )
commonClient, err := cfg.Dial(op...)
if err != nil {
return nil, err
}
commonClient.Wait.SetTimeout(time.Second * 5)
//代码管理
codes, err := NewCodesMysql(commonClient, cfg.CodesFilename)
if err != nil {
return nil, err
}
//工作日管理
workday, err := NewWorkdayMysql(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,
Cron: cron.New(cron.WithSeconds()),
}, nil
} }
func NewManage(cfg *ManageConfig, op ...client.Option) (*Manage, error) { func NewManageSqlite(op ...Option) (*Manage, error) {
//初始化配置 return NewManage(
if cfg == nil { WithCodesDatabase(DefaultDatabaseDir+"/codes.db"),
cfg = &ManageConfig{} WithWorkdayDatabase(DefaultDatabaseDir+"/workday.db"),
} WithOptions(op...),
if cfg.CodesFilename == "" { WithDialCodes(func(c *Client, database string) (ICodes, error) {
cfg.CodesFilename = DefaultDatabaseDir + "/codes.db" return NewCodesSqlite(c, database)
} }),
if cfg.WorkdayFileName == "" { WithDialWorkday(func(c *Client, database string) (*Workday, error) {
cfg.WorkdayFileName = DefaultDatabaseDir + "/workday.db" return NewWorkdaySqlite(c, database)
} }),
if cfg.Dial == nil { )
cfg.Dial = DialDefault }
func NewManageSqlite2(op ...Option) (*Manage, error) {
return NewManage(
WithCodesDatabase(DefaultDatabaseDir+"/codes2.db"),
WithWorkdayDatabase(DefaultDatabaseDir+"/workday.db"),
WithOptions(op...),
WithDialCodes(func(c *Client, database string) (ICodes, error) {
return NewCodes2(
WithCodes2Client(c),
WithCodes2Database(database),
)
}),
WithDialWorkday(func(c *Client, database string) (*Workday, error) {
return NewWorkdaySqlite(c, database)
}),
)
}
func NewManage(op ...Option) (m *Manage, err error) {
m = &Manage{
clients: DefaultClients,
dial: DialDefault,
dialOptions: nil,
dialCodes: nil,
codesDatabase: DefaultDatabaseDir + "/codes2.db",
dialWorkday: nil,
workdayDatabase: DefaultDatabaseDir + "/workday.db",
Pool: nil,
Codes: nil,
Workday: nil,
cron: nil,
once: sync.Once{},
} }
//通用客户端 for _, v := range op {
commonClient, err := cfg.Dial(op...) if v != nil {
v(m)
}
}
m.clients = conv.Select(m.clients <= 0, 1, m.clients)
m.dial = conv.Select(m.dial == nil, DialDefault, m.dial)
//连接池
m.Pool, err = NewPool(func() (*Client, error) { return m.dial(m.dialOptions...) }, m.clients)
if err != nil { if err != nil {
return nil, err return nil, err
} }
commonClient.Wait.SetTimeout(time.Second * 5)
//代码管理 //代码管理
codes, err := NewCodesSqlite(commonClient, cfg.CodesFilename) if m.Codes == nil {
if err != nil { if m.dialCodes == nil {
return nil, err m.dialCodes = func(c *Client, database string) (ICodes, error) {
return NewCodes2(WithCodes2Client(c), WithCodes2Database(database))
}
}
err = m.Pool.Do(func(c *Client) error {
m.Codes, err = m.dialCodes(c, m.codesDatabase)
return err
})
if err != nil {
return nil, err
}
} }
//工作日管理 //工作日管理
workday, err := NewWorkdaySqlite(commonClient, cfg.WorkdayFileName) if m.Workday == nil {
if err != nil { if m.dialWorkday == nil {
return nil, err m.dialWorkday = func(c *Client, database string) (*Workday, error) {
return NewWorkdaySqlite(c, database)
}
}
err = m.Pool.Do(func(c *Client) error {
m.Workday, err = m.dialWorkday(c, m.workdayDatabase)
return err
})
if err != nil {
return nil, err
}
} }
//连接池 return
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, */
Cron: cron.New(cron.WithSeconds()),
}, nil type Option func(m *Manage)
type DialWorkdayFunc func(c *Client, database string) (*Workday, error)
type DialCodesFunc func(c *Client, database string) (ICodes, error)
func WithClients(clients int) Option {
return func(m *Manage) {
m.clients = clients
}
}
func WithDial(dial func(op ...client.Option) (*Client, error), op ...client.Option) Option {
return func(m *Manage) {
m.dial = dial
m.dialOptions = op
}
}
func WithDialOptions(op ...client.Option) Option {
return func(m *Manage) {
m.dialOptions = op
}
}
func WithCodes(codes ICodes) Option {
return func(m *Manage) {
m.Codes = codes
}
}
func WithDialCodes(dial DialCodesFunc) Option {
return func(m *Manage) {
m.dialCodes = dial
}
}
func WithCodesDatabase(database string) Option {
return func(m *Manage) {
m.codesDatabase = database
}
}
func WithWorkday(w *Workday) Option {
return func(m *Manage) {
m.Workday = w
}
}
func WithDialWorkday(dial DialWorkdayFunc) Option {
return func(m *Manage) {
m.dialWorkday = dial
}
}
func WithWorkdayDatabase(database string) Option {
return func(m *Manage) {
m.workdayDatabase = database
}
}
func WithOptions(op ...Option) Option {
return func(m *Manage) {
for _, v := range op {
v(m)
}
}
} }
type Manage struct { type Manage struct {
clients int
dial func(op ...client.Option) (cli *Client, err error)
dialOptions []client.Option
dialCodes func(c *Client, database string) (ICodes, error)
codesDatabase string
dialWorkday DialWorkdayFunc
workdayDatabase string
/*
*/
*Pool *Pool
Config *ManageConfig Codes ICodes
Codes *Codes
Workday *Workday Workday *Workday
Cron *cron.Cron cron *cron.Cron
once sync.Once
} }
// RangeStocks 遍历所有股票 // RangeStocks 遍历所有股票
func (this *Manage) RangeStocks(f func(code string)) { func (this *Manage) RangeStocks(f func(code string)) {
for _, v := range this.Codes.GetStocks() { for _, v := range this.Codes.GetStocks() {
f(v) f(v.FullCode())
} }
} }
// RangeETFs 遍历所有ETF // RangeETFs 遍历所有ETF
func (this *Manage) RangeETFs(f func(code string)) { func (this *Manage) RangeETFs(f func(code string)) {
for _, v := range this.Codes.GetETFs() { for _, v := range this.Codes.GetETFs() {
f(v) f(v.FullCode())
}
}
// RangeIndexes 遍历所有指数
func (this *Manage) RangeIndexes(f func(code string)) {
for _, v := range this.Codes.GetETFs() {
f(v.FullCode())
} }
} }
// 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)
} }

View File

@@ -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 //时间

View File

@@ -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++
} }

View File

@@ -3,13 +3,14 @@ package protocol
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/injoyai/conv"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io" "io"
"math" "math"
"strings" "strings"
"time" "time"
"github.com/injoyai/conv"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
) )
// String 字节先转小端,再转字符 // String 字节先转小端,再转字符
@@ -285,7 +286,24 @@ func IsETF(code string) bool {
return true return true
case code[0:2] == ExchangeSZ.String() && case code[0:2] == ExchangeSZ.String() &&
(code[2:4] == "15" || code[2:4] == "16"): (code[2:4] == "15"):
return true
}
return false
}
// IsIndex 是否是指数,sh000001,sz399001,bj899100
func IsIndex(code string) bool {
if len(code) != 8 {
return false
}
code = strings.ToLower(code)
switch {
case code[0:2] == ExchangeSH.String() && code[2:5] == "000":
return true
case code[0:2] == ExchangeSZ.String() && code[2:5] == "399":
return true
case code[0:2] == ExchangeBJ.String() && code[2:5] == "899":
return true return true
} }
return false return false

View File

@@ -2,6 +2,11 @@ package tdx
import ( import (
"errors" "errors"
"iter"
"os"
"path/filepath"
"time"
_ "github.com/glebarez/go-sqlite" _ "github.com/glebarez/go-sqlite"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/injoyai/base/maps" "github.com/injoyai/base/maps"
@@ -10,9 +15,6 @@ import (
"github.com/injoyai/logs" "github.com/injoyai/logs"
"github.com/injoyai/tdx/protocol" "github.com/injoyai/tdx/protocol"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"os"
"path/filepath"
"time"
"xorm.io/core" "xorm.io/core"
"xorm.io/xorm" "xorm.io/xorm"
) )
@@ -154,7 +156,7 @@ func (this *Workday) TodayIs() bool {
func (this *Workday) RangeYear(year int, f func(t time.Time) bool) { func (this *Workday) RangeYear(year int, f func(t time.Time) bool) {
this.Range( this.Range(
time.Date(year, 1, 1, 0, 0, 0, 0, time.Local), time.Date(year, 1, 1, 0, 0, 0, 0, time.Local),
time.Date(year, 12, 31, 0, 0, 0, 0, time.Local), time.Date(year, 12, 31, 0, 0, 0, 1, time.Local),
f, f,
) )
} }
@@ -162,8 +164,6 @@ func (this *Workday) RangeYear(year int, f func(t time.Time) bool) {
// Range 遍历指定范围的工作日,推荐start带上时间15:00,这样当天小于15点不会触发 // Range 遍历指定范围的工作日,推荐start带上时间15:00,这样当天小于15点不会触发
func (this *Workday) Range(start, end time.Time, f func(t time.Time) bool) { func (this *Workday) Range(start, end time.Time, f func(t time.Time) bool) {
start = conv.Select(start.Before(protocol.ExchangeEstablish), protocol.ExchangeEstablish, start) start = conv.Select(start.Before(protocol.ExchangeEstablish), protocol.ExchangeEstablish, start)
//now := IntegerDay(time.Now())
//end = conv.Select(end.After(now), now, end).Add(1)
for ; start.Before(end); start = start.Add(time.Hour * 24) { for ; start.Before(end); start = start.Add(time.Hour * 24) {
if this.Is(start) { if this.Is(start) {
if !f(start) { if !f(start) {
@@ -173,13 +173,36 @@ func (this *Workday) Range(start, end time.Time, f func(t time.Time) bool) {
} }
} }
// RangeDesc 倒序遍历工作日,从今天-1990年12月19日(上海交易所成立时间) func (this *Workday) IterYear(year int, desc ...bool) iter.Seq[time.Time] {
func (this *Workday) RangeDesc(f func(t time.Time) bool) { return this.Iter(
t := IntegerDay(time.Now()) time.Date(year, 1, 1, 0, 0, 0, 0, time.Local),
for ; t.After(time.Date(1990, 12, 18, 0, 0, 0, 0, time.Local)); t = t.Add(-time.Hour * 24) { time.Date(year, 12, 31, 0, 0, 0, 1, time.Local),
if this.Is(t) { desc...,
if !f(t) { )
return }
// Iter 遍历指定范围的工作日,推荐start带上时间15:00,这样当天小于15点不会触发
func (this *Workday) Iter(start, end time.Time, desc ...bool) iter.Seq[time.Time] {
start = conv.Select(start.Before(protocol.ExchangeEstablish), protocol.ExchangeEstablish, start)
if len(desc) > 0 && desc[0] {
//倒序遍历
return func(yield func(time.Time) bool) {
for ; end.After(start); end = end.Add(-time.Hour * 24) {
if this.Is(end) {
if !yield(end) {
return
}
}
}
}
}
//正序遍历
return func(yield func(time.Time) bool) {
for ; start.Before(end); start = start.Add(time.Hour * 24) {
if this.Is(start) {
if !yield(start) {
return
}
} }
} }
} }