mirror of
https://github.com/injoyai/tdx.git
synced 2025-11-26 21:25:35 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4c0d1dfa4 | ||
|
|
73b80857cc | ||
|
|
e7e8c6a46a | ||
|
|
2e17db7faf | ||
|
|
accda98f3b | ||
|
|
1783643f47 | ||
|
|
b9f0951b15 | ||
|
|
2e4ecd034c | ||
|
|
9269bca388 | ||
|
|
fc1f25c6c9 | ||
|
|
705f6e4e3a | ||
|
|
12079f1ee2 | ||
|
|
34701c4197 | ||
|
|
1187e1dcdf | ||
|
|
2adcc9a322 | ||
|
|
b035cc7fbf | ||
|
|
e58130b3c3 | ||
|
|
d840e0d33d | ||
|
|
78843ed6b7 | ||
|
|
04378dadf0 | ||
|
|
e74aeba7b2 | ||
|
|
bea9762c50 |
104
client.go
104
client.go
@@ -157,10 +157,10 @@ func (this *Client) handlerDealMessage(c *client.Client, msg ios.Acker) {
|
|||||||
resp, err = protocol.MHistoryMinute.Decode(f.Data)
|
resp, err = protocol.MHistoryMinute.Decode(f.Data)
|
||||||
|
|
||||||
case protocol.TypeMinuteTrade:
|
case protocol.TypeMinuteTrade:
|
||||||
resp, err = protocol.MMinuteTrade.Decode(f.Data, conv.String(val))
|
resp, err = protocol.MTrade.Decode(f.Data, val.(protocol.TradeCache))
|
||||||
|
|
||||||
case protocol.TypeHistoryMinuteTrade:
|
case protocol.TypeHistoryMinuteTrade:
|
||||||
resp, err = protocol.MHistoryMinuteTrade.Decode(f.Data, conv.String(val))
|
resp, err = protocol.MHistoryTrade.Decode(f.Data, val.(protocol.TradeCache))
|
||||||
|
|
||||||
case protocol.TypeKline:
|
case protocol.TypeKline:
|
||||||
resp, err = protocol.MKline.Decode(f.Data, val.(protocol.KlineCache))
|
resp, err = protocol.MKline.Decode(f.Data, val.(protocol.KlineCache))
|
||||||
@@ -233,6 +233,40 @@ func (this *Client) GetCodeAll(exchange protocol.Exchange) (*protocol.CodeResp,
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStockAll 获取所有股票代码
|
||||||
|
func (this *Client) GetStockAll() ([]string, error) {
|
||||||
|
ls := []string(nil)
|
||||||
|
for _, ex := range []protocol.Exchange{protocol.ExchangeSH, protocol.ExchangeSZ} {
|
||||||
|
resp, err := this.GetCodeAll(ex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, v := range resp.List {
|
||||||
|
if protocol.IsStock(v.Code) {
|
||||||
|
ls = append(ls, v.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetETFAll 获取所有ETF代码
|
||||||
|
func (this *Client) GetETFAll() ([]string, error) {
|
||||||
|
ls := []string(nil)
|
||||||
|
for _, ex := range []protocol.Exchange{protocol.ExchangeSH, protocol.ExchangeSZ} {
|
||||||
|
resp, err := this.GetCodeAll(ex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, v := range resp.List {
|
||||||
|
if protocol.IsETF(v.Code) {
|
||||||
|
ls = append(ls, v.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetQuote 获取盘口五档报价
|
// GetQuote 获取盘口五档报价
|
||||||
func (this *Client) GetQuote(codes ...string) (protocol.QuotesResp, error) {
|
func (this *Client) GetQuote(codes ...string) (protocol.QuotesResp, error) {
|
||||||
for i := range codes {
|
for i := range codes {
|
||||||
@@ -316,23 +350,34 @@ func (this *Client) GetHistoryMinute(date, code string) (*protocol.MinuteResp, e
|
|||||||
return result.(*protocol.MinuteResp), nil
|
return result.(*protocol.MinuteResp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Client) GetTrade(code string, start, count uint16) (*protocol.TradeResp, error) {
|
||||||
|
return this.GetMinuteTrade(code, start, count)
|
||||||
|
}
|
||||||
|
|
||||||
// GetMinuteTrade 获取分时交易详情,服务器最多返回1800条,count-start<=1800
|
// GetMinuteTrade 获取分时交易详情,服务器最多返回1800条,count-start<=1800
|
||||||
func (this *Client) GetMinuteTrade(code string, start, count uint16) (*protocol.MinuteTradeResp, error) {
|
func (this *Client) GetMinuteTrade(code string, start, count uint16) (*protocol.TradeResp, error) {
|
||||||
code = protocol.AddPrefix(code)
|
code = protocol.AddPrefix(code)
|
||||||
f, err := protocol.MMinuteTrade.Frame(code, start, count)
|
f, err := protocol.MTrade.Frame(code, start, count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result, err := this.SendFrame(f, code)
|
result, err := this.SendFrame(f, protocol.TradeCache{
|
||||||
|
Date: time.Now().Format("20060102"),
|
||||||
|
Code: code,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result.(*protocol.MinuteTradeResp), nil
|
return result.(*protocol.TradeResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Client) GetTradeAll(code string) (*protocol.TradeResp, error) {
|
||||||
|
return this.GetMinuteTradeAll(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMinuteTradeAll 获取分时全部交易详情,todo 只做参考 因为交易实时在进行,然后又是分页读取的,所以会出现读取间隔内产生的交易会丢失
|
// GetMinuteTradeAll 获取分时全部交易详情,todo 只做参考 因为交易实时在进行,然后又是分页读取的,所以会出现读取间隔内产生的交易会丢失
|
||||||
func (this *Client) GetMinuteTradeAll(code string) (*protocol.MinuteTradeResp, error) {
|
func (this *Client) GetMinuteTradeAll(code string) (*protocol.TradeResp, error) {
|
||||||
resp := &protocol.MinuteTradeResp{}
|
resp := &protocol.TradeResp{}
|
||||||
size := uint16(1800)
|
size := uint16(1800)
|
||||||
for start := uint16(0); ; start += size {
|
for start := uint16(0); ; start += size {
|
||||||
r, err := this.GetMinuteTrade(code, start, size)
|
r, err := this.GetMinuteTrade(code, start, size)
|
||||||
@@ -349,26 +394,37 @@ func (this *Client) GetMinuteTradeAll(code string) (*protocol.MinuteTradeResp, e
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Client) GetHistoryTrade(date, code string, start, count uint16) (*protocol.TradeResp, error) {
|
||||||
|
return this.GetHistoryMinuteTrade(date, code, start, count)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHistoryMinuteTrade 获取历史分时交易
|
// GetHistoryMinuteTrade 获取历史分时交易
|
||||||
// 只能获取昨天及之前的数据,服务器最多返回2000条,count-start<=2000,如果日期输入错误,则返回0
|
// 只能获取昨天及之前的数据,服务器最多返回2000条,count-start<=2000,如果日期输入错误,则返回0
|
||||||
// 历史数据sz000001在20241116只能查到21111112,13年差几天,3141天,或者其他规则
|
// 历史数据sz000001在20241116只能查到21111112,13年差几天,3141天,或者其他规则
|
||||||
func (this *Client) GetHistoryMinuteTrade(date, code string, start, count uint16) (*protocol.HistoryMinuteTradeResp, error) {
|
func (this *Client) GetHistoryMinuteTrade(date, code string, start, count uint16) (*protocol.TradeResp, error) {
|
||||||
code = protocol.AddPrefix(code)
|
code = protocol.AddPrefix(code)
|
||||||
f, err := protocol.MHistoryMinuteTrade.Frame(date, code, start, count)
|
f, err := protocol.MHistoryTrade.Frame(date, code, start, count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result, err := this.SendFrame(f, code)
|
result, err := this.SendFrame(f, protocol.TradeCache{
|
||||||
|
Date: date,
|
||||||
|
Code: code,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result.(*protocol.HistoryMinuteTradeResp), nil
|
return result.(*protocol.TradeResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Client) GetHistoryTradeAll(date, code string) (*protocol.TradeResp, error) {
|
||||||
|
return this.GetHistoryMinuteTradeAll(date, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHistoryMinuteTradeAll 获取历史分时全部交易,通过多次请求来拼接,只能获取昨天及之前的数据
|
// GetHistoryMinuteTradeAll 获取历史分时全部交易,通过多次请求来拼接,只能获取昨天及之前的数据
|
||||||
// 历史数据sz000001在20241116只能查到21111112,13年差几天,3141天,或者其他规则
|
// 历史数据sz000001在20241116只能查到21111112,13年差几天,3141天,或者其他规则
|
||||||
func (this *Client) GetHistoryMinuteTradeAll(date, code string) (*protocol.HistoryMinuteTradeResp, error) {
|
func (this *Client) GetHistoryMinuteTradeAll(date, code string) (*protocol.TradeResp, error) {
|
||||||
resp := &protocol.HistoryMinuteTradeResp{}
|
resp := &protocol.TradeResp{}
|
||||||
size := uint16(2000)
|
size := uint16(2000)
|
||||||
for start := uint16(0); ; start += size {
|
for start := uint16(0); ; start += size {
|
||||||
r, err := this.GetHistoryMinuteTrade(date, code, start, size)
|
r, err := this.GetHistoryMinuteTrade(date, code, start, size)
|
||||||
@@ -581,18 +637,32 @@ func (this *Client) GetKline30MinuteUntil(code string, f func(k *protocol.Kline)
|
|||||||
return this.GetKlineUntil(protocol.TypeKline30Minute, code, f)
|
return this.GetKlineUntil(protocol.TypeKline30Minute, code, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetKline60Minute 获取60分钟k线数据
|
||||||
|
func (this *Client) GetKline60Minute(code string, start, count uint16) (*protocol.KlineResp, error) {
|
||||||
|
return this.GetKline(protocol.TypeKline60Minute, code, start, count)
|
||||||
|
}
|
||||||
|
|
||||||
// GetKlineHour 获取小时k线数据
|
// GetKlineHour 获取小时k线数据
|
||||||
func (this *Client) GetKlineHour(code string, start, count uint16) (*protocol.KlineResp, error) {
|
func (this *Client) GetKlineHour(code string, start, count uint16) (*protocol.KlineResp, error) {
|
||||||
return this.GetKline(protocol.TypeKlineHour, code, start, count)
|
return this.GetKline(protocol.TypeKline60Minute, code, start, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKline60MinuteAll 获取60分钟k线全部数据
|
||||||
|
func (this *Client) GetKline60MinuteAll(code string) (*protocol.KlineResp, error) {
|
||||||
|
return this.GetKlineAll(protocol.TypeKline60Minute, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKlineHourAll 获取小时k线全部数据
|
// GetKlineHourAll 获取小时k线全部数据
|
||||||
func (this *Client) GetKlineHourAll(code string) (*protocol.KlineResp, error) {
|
func (this *Client) GetKlineHourAll(code string) (*protocol.KlineResp, error) {
|
||||||
return this.GetKlineAll(protocol.TypeKlineHour, code)
|
return this.GetKlineAll(protocol.TypeKline60Minute, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Client) GetKline60MinuteUntil(code string, f func(k *protocol.Kline) bool) (*protocol.KlineResp, error) {
|
||||||
|
return this.GetKlineUntil(protocol.TypeKline60Minute, code, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Client) GetKlineHourUntil(code string, f func(k *protocol.Kline) bool) (*protocol.KlineResp, error) {
|
func (this *Client) GetKlineHourUntil(code string, f func(k *protocol.Kline) bool) (*protocol.KlineResp, error) {
|
||||||
return this.GetKlineUntil(protocol.TypeKlineHour, code, f)
|
return this.GetKlineUntil(protocol.TypeKline60Minute, code, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKlineDay 获取日k线数据
|
// GetKlineDay 获取日k线数据
|
||||||
|
|||||||
2
codes.go
2
codes.go
@@ -70,7 +70,7 @@ func NewCodes(c *Client, filenames ...string) (*Codes, error) {
|
|||||||
|
|
||||||
{ //设置定时器,每天早上9点更新数据
|
{ //设置定时器,每天早上9点更新数据
|
||||||
task := cron.New(cron.WithSeconds())
|
task := cron.New(cron.WithSeconds())
|
||||||
task.AddFunc("0 0 9 * * *", func() {
|
task.AddFunc("10 0 9 * * *", func() {
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
if err := cc.Update(); err == nil {
|
if err := cc.Update(); err == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
ls := tdx.FastHosts(tdx.Hosts...)
|
ls := tdx.FastHosts(tdx.Hosts...)
|
||||||
for _, v := range ls {
|
for _, v := range ls {
|
||||||
logs.Debug(v)
|
logs.Debug(v.Host, v.Spend)
|
||||||
}
|
}
|
||||||
logs.Debug("总数量:", len(ls))
|
logs.Debug("总数量:", len(ls))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("20241025", "sz000001", 0, 20)
|
resp, err := c.GetHistoryMinuteTrade("20250609", "sz000001", 0, 20)
|
||||||
logs.PanicErr(err)
|
logs.PanicErr(err)
|
||||||
|
|
||||||
for _, v := range resp.List {
|
for _, v := range resp.List {
|
||||||
20
example/GetTrade/main.go
Normal file
20
example/GetTrade/main.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/injoyai/logs"
|
||||||
|
"github.com/injoyai/tdx"
|
||||||
|
"github.com/injoyai/tdx/example/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
common.Test(func(c *tdx.Client) {
|
||||||
|
resp, err := c.GetTrade("sz000001", 0, 20)
|
||||||
|
logs.PanicErr(err)
|
||||||
|
|
||||||
|
for _, v := range resp.List {
|
||||||
|
logs.Debug(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.Debug("总数:", resp.Count)
|
||||||
|
})
|
||||||
|
}
|
||||||
20
example/PullTrade/main.go
Normal file
20
example/PullTrade/main.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/injoyai/logs"
|
||||||
|
"github.com/injoyai/tdx"
|
||||||
|
"github.com/injoyai/tdx/extend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
pt := extend.NewPullTrade("./data/trade")
|
||||||
|
|
||||||
|
m, err := tdx.NewManage(nil)
|
||||||
|
logs.PanicErr(err)
|
||||||
|
|
||||||
|
err = pt.PullYear(context.Background(), m, 2025, "sz000001")
|
||||||
|
logs.Err(err)
|
||||||
|
|
||||||
|
}
|
||||||
172
extend/pull-trade.go
Normal file
172
extend/pull-trade.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package extend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/injoyai/conv"
|
||||||
|
"github.com/injoyai/logs"
|
||||||
|
"github.com/injoyai/tdx"
|
||||||
|
"github.com/injoyai/tdx/protocol"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPullTrade(dir string) *PullTrade {
|
||||||
|
return &PullTrade{
|
||||||
|
Dir: dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PullTrade struct {
|
||||||
|
Dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PullTrade) Pull(ctx context.Context, m *tdx.Manage, code string) error {
|
||||||
|
for i := 2000; i <= time.Now().Year(); i++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if err := this.PullYear(ctx, m, i, code); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PullTrade) PullYear(ctx context.Context, m *tdx.Manage, year int, code string) (err error) {
|
||||||
|
|
||||||
|
tss := protocol.Trades{}
|
||||||
|
kss1 := protocol.Klines(nil)
|
||||||
|
kss5 := protocol.Klines(nil)
|
||||||
|
kss15 := protocol.Klines(nil)
|
||||||
|
kss30 := protocol.Klines(nil)
|
||||||
|
kss60 := protocol.Klines(nil)
|
||||||
|
|
||||||
|
m.Workday.RangeYear(year, func(t time.Time) bool {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err = ctx.Err()
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
date := t.Format("20060102")
|
||||||
|
|
||||||
|
var resp *protocol.TradeResp
|
||||||
|
err = m.Do(func(c *tdx.Client) error {
|
||||||
|
resp, err = c.GetHistoryTradeAll(date, code)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logs.Err(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tss = append(tss, resp.List...)
|
||||||
|
|
||||||
|
//转成分时K线
|
||||||
|
ks, err := resp.List.Klines1()
|
||||||
|
if err != nil {
|
||||||
|
logs.Err(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
kss1 = append(kss1, ks...)
|
||||||
|
kss5 = append(kss5, ks.Merge(5)...)
|
||||||
|
kss15 = append(kss5, ks.Merge(15)...)
|
||||||
|
kss30 = append(kss5, ks.Merge(30)...)
|
||||||
|
kss60 = append(kss5, ks.Merge(60)...)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(this.Dir, "分时成交", code+"-"+conv.String(year)+".csv")
|
||||||
|
filename1 := filepath.Join(this.Dir, "1分钟", code+"-"+conv.String(year)+".csv")
|
||||||
|
filename5 := filepath.Join(this.Dir, "5分钟", code+"-"+conv.String(year)+".csv")
|
||||||
|
filename15 := filepath.Join(this.Dir, "15分钟", code+"-"+conv.String(year)+".csv")
|
||||||
|
filename30 := filepath.Join(this.Dir, "30分钟", code+"-"+conv.String(year)+".csv")
|
||||||
|
filename60 := filepath.Join(this.Dir, "60分钟", code+"-"+conv.String(year)+".csv")
|
||||||
|
name := m.Codes.GetName(code)
|
||||||
|
|
||||||
|
err = TradeToCsv(filename, tss)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = KlinesToCsv(filename1, code, name, kss1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = KlinesToCsv(filename5, code, name, kss5)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = KlinesToCsv(filename15, code, name, kss15)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = KlinesToCsv(filename30, code, name, kss30)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = KlinesToCsv(filename60, code, name, kss60)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func KlinesToCsv(filename string, code, name string, ks protocol.Klines) error {
|
||||||
|
data := [][]any{{"日期", "时间", "代码", "名称", "开盘", "最高", "最低", "收盘", "总手", "金额"}}
|
||||||
|
for _, v := range ks {
|
||||||
|
data = append(data, []any{
|
||||||
|
v.Time.Format("20060102"),
|
||||||
|
v.Time.Format("15:04"),
|
||||||
|
code,
|
||||||
|
name,
|
||||||
|
v.Open.Float64(),
|
||||||
|
v.High.Float64(),
|
||||||
|
v.Low.Float64(),
|
||||||
|
v.Close.Float64(),
|
||||||
|
v.Volume,
|
||||||
|
v.Amount.Float64(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := toCsv(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFile(filename, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TradeToCsv(filename string, ts protocol.Trades) error {
|
||||||
|
data := [][]any{{"日期", "时间", "价格", "成交量(手)", "成交额", "方向(0买,1卖)"}}
|
||||||
|
for _, v := range ts {
|
||||||
|
data = append(data, []any{
|
||||||
|
v.Time.Format(time.DateOnly),
|
||||||
|
v.Time.Format("15:04"),
|
||||||
|
v.Price.Float64(),
|
||||||
|
v.Volume,
|
||||||
|
v.Amount().Float64(),
|
||||||
|
v.Status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
buf, err := toCsv(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return newFile(filename, buf)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/injoyai/conv"
|
"github.com/injoyai/conv"
|
||||||
|
"github.com/injoyai/tdx"
|
||||||
"github.com/injoyai/tdx/protocol"
|
"github.com/injoyai/tdx/protocol"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -18,6 +19,51 @@ const (
|
|||||||
THS_HFQ uint8 = 2 //后复权
|
THS_HFQ uint8 = 2 //后复权
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetTHSDayKlineFull
|
||||||
|
获取[不复权,前复权,后复权]数据,并补充成交金额数据
|
||||||
|
前复权,和通达信对的上,和东方财富对不上
|
||||||
|
后复权,和通达信,东方财富都对不上
|
||||||
|
*/
|
||||||
|
func GetTHSDayKlineFull(code string, c *tdx.Client) ([3][]*Kline, error) {
|
||||||
|
resp, err := c.GetKlineDayAll(code)
|
||||||
|
if err != nil {
|
||||||
|
return [3][]*Kline{}, err
|
||||||
|
}
|
||||||
|
mAmount := make(map[int64]protocol.Price)
|
||||||
|
bfq := []*Kline(nil)
|
||||||
|
for _, v := range resp.List {
|
||||||
|
mAmount[v.Time.Unix()] = v.Amount
|
||||||
|
bfq = append(bfq, &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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//前复权
|
||||||
|
qfq, err := GetTHSDayKline(code, THS_QFQ)
|
||||||
|
if err != nil {
|
||||||
|
return [3][]*Kline{}, err
|
||||||
|
}
|
||||||
|
for i := range qfq {
|
||||||
|
qfq[i].Amount = mAmount[qfq[i].Date]
|
||||||
|
}
|
||||||
|
//后复权
|
||||||
|
hfq, err := GetTHSDayKline(code, THS_HFQ)
|
||||||
|
if err != nil {
|
||||||
|
return [3][]*Kline{}, err
|
||||||
|
}
|
||||||
|
for i := range hfq {
|
||||||
|
hfq[i].Amount = mAmount[hfq[i].Date]
|
||||||
|
}
|
||||||
|
return [3][]*Kline{bfq, qfq, hfq}, nil
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
GetTHSDayKline
|
GetTHSDayKline
|
||||||
前复权,和通达信对的上,和东方财富对不上
|
前复权,和通达信对的上,和东方财富对不上
|
||||||
@@ -68,15 +114,11 @@ func GetTHSDayKline(code string, _type uint8) ([]*Kline, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
total := conv.Int(m["total"])
|
total := conv.Int(m["total"])
|
||||||
|
sortYears := conv.Interfaces(m["sortYear"])
|
||||||
priceFactor := conv.Float64(m["priceFactor"])
|
priceFactor := conv.Float64(m["priceFactor"])
|
||||||
prices := strings.Split(conv.String(m["price"]), ",")
|
prices := strings.Split(conv.String(m["price"]), ",")
|
||||||
dates := strings.Split(conv.String(m["dates"]), ",")
|
dates := strings.Split(conv.String(m["dates"]), ",")
|
||||||
volumes := strings.Split(conv.String(m["volumn"]), ",")
|
volumes := strings.Split(conv.String(m["volumn"]), ",")
|
||||||
start := conv.String(m["start"])
|
|
||||||
t, err := time.Parse("20060102", start)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//好像到了22点,总数量会比实际多1
|
//好像到了22点,总数量会比实际多1
|
||||||
if total == len(dates)+1 && total == len(volumes)+1 {
|
if total == len(dates)+1 && total == len(volumes)+1 {
|
||||||
@@ -87,31 +129,43 @@ func GetTHSDayKline(code string, _type uint8) ([]*Kline, error) {
|
|||||||
return nil, fmt.Errorf("total=%d prices=%d dates=%d volumns=%d", total, len(prices), len(dates), len(volumes))
|
return nil, fmt.Errorf("total=%d prices=%d dates=%d volumns=%d", total, len(prices), len(dates), len(volumes))
|
||||||
}
|
}
|
||||||
|
|
||||||
ls := []*Kline(nil)
|
mYear := make(map[int][]string)
|
||||||
|
index := 0
|
||||||
|
for i, v := range sortYears {
|
||||||
|
if ls := conv.Ints(v); len(ls) == 2 {
|
||||||
|
year := conv.Int(ls[0])
|
||||||
|
length := conv.Int(ls[1])
|
||||||
|
if i == len(sortYears)-1 {
|
||||||
|
mYear[year] = dates[index:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mYear[year] = dates[index : index+length]
|
||||||
|
index += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
year := t.Year()
|
ls := []*Kline(nil)
|
||||||
lastDate := ""
|
i := 0
|
||||||
for i := 0; i < total; i++ {
|
nowYear := time.Now().Year()
|
||||||
//当日前变小时(12xx变01xx),说明过了1年,除非该股票停牌了1年多则数据错误
|
for year := 1990; year <= nowYear; year++ {
|
||||||
if dates[i] < lastDate {
|
for _, d := range mYear[year] {
|
||||||
year++
|
x, err := time.Parse("0102", d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x = time.Date(year, x.Month(), x.Day(), 15, 0, 0, 0, time.Local)
|
||||||
|
low := protocol.Price(conv.Float64(prices[i*4+0]) * 1000 / priceFactor)
|
||||||
|
ls = append(ls, &Kline{
|
||||||
|
Code: protocol.AddPrefix(code),
|
||||||
|
Date: x.Unix(),
|
||||||
|
Open: protocol.Price(conv.Float64(prices[i*4+1])*1000/priceFactor) + low,
|
||||||
|
High: protocol.Price(conv.Float64(prices[i*4+2])*1000/priceFactor) + low,
|
||||||
|
Low: low,
|
||||||
|
Close: protocol.Price(conv.Float64(prices[i*4+3])*1000/priceFactor) + low,
|
||||||
|
Volume: (conv.Int64(volumes[i]) + 50) / 100,
|
||||||
|
})
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
lastDate = dates[i]
|
|
||||||
x, err := time.Parse("0102", dates[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x = time.Date(year, x.Month(), x.Day(), 15, 0, 0, 0, time.Local)
|
|
||||||
low := protocol.Price(conv.Float64(prices[i*4+0]) * 1000 / priceFactor)
|
|
||||||
ls = append(ls, &Kline{
|
|
||||||
Code: protocol.AddPrefix(code),
|
|
||||||
Date: x.Unix(),
|
|
||||||
Open: protocol.Price(conv.Float64(prices[i*4+1])*1000/priceFactor) + low,
|
|
||||||
High: protocol.Price(conv.Float64(prices[i*4+2])*1000/priceFactor) + low,
|
|
||||||
Low: low,
|
|
||||||
Close: protocol.Price(conv.Float64(prices[i*4+3])*1000/priceFactor) + low,
|
|
||||||
Volume: (conv.Int64(volumes[i]) + 50) / 100,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ls, nil
|
return ls, nil
|
||||||
58
extend/util.go
Normal file
58
extend/util.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package extend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
|
"github.com/injoyai/conv"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toCsv(data [][]interface{}) (*bytes.Buffer, error) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.WriteString("\xEF\xBB\xBF")
|
||||||
|
w := csv.NewWriter(buf)
|
||||||
|
for _, rows := range data {
|
||||||
|
if err := w.Write(conv.Strings(rows)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFile 新建文件,会覆盖
|
||||||
|
func newFile(filename string, v ...interface{}) error {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return os.MkdirAll(filename, 0777)
|
||||||
|
}
|
||||||
|
dir, name := filepath.Split(filename)
|
||||||
|
if len(dir) > 0 {
|
||||||
|
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
for _, k := range v {
|
||||||
|
switch r := k.(type) {
|
||||||
|
case nil:
|
||||||
|
case io.Reader:
|
||||||
|
if _, err = io.Copy(f, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err = f.Write(conv.Bytes(r)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
6
go.mod
6
go.mod
@@ -5,9 +5,9 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/glebarez/go-sqlite v1.22.0
|
github.com/glebarez/go-sqlite v1.22.0
|
||||||
github.com/go-sql-driver/mysql v1.7.0
|
github.com/go-sql-driver/mysql v1.7.0
|
||||||
github.com/injoyai/base v1.2.7
|
github.com/injoyai/base v1.2.8
|
||||||
github.com/injoyai/conv v1.2.2
|
github.com/injoyai/conv v1.2.5
|
||||||
github.com/injoyai/ios v0.0.7
|
github.com/injoyai/ios v0.0.10
|
||||||
github.com/injoyai/logs v1.0.9
|
github.com/injoyai/logs v1.0.9
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.16.0
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -27,12 +27,12 @@ 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.7 h1:uYZoUIPGidkTNMfQfObsyHulu5VvNCOzuCh1DGn+Hz0=
|
github.com/injoyai/base v1.2.8 h1:voEPqxE5U+wI1RvcQStVbjUZJDLENbl1SsoQQWMn4s0=
|
||||||
github.com/injoyai/base v1.2.7/go.mod h1:BsXiJ6/hXpswWxI4zLdKz+pW2Cwo+qhdfyaWDfR/vwg=
|
github.com/injoyai/base v1.2.8/go.mod h1:NfCQjml3z2pCvQ3J3YcOXtecqXD0xVPKjo4YTsMLhr8=
|
||||||
github.com/injoyai/conv v1.2.2 h1:nxFD3zCYq/ZvVE6xAExBR+agi6gB+vc9O0si67VAsPk=
|
github.com/injoyai/conv v1.2.5 h1:G4OCyF0NTZul5W1u9IgXDOhW4/zmIigdPKXFHQGmv1M=
|
||||||
github.com/injoyai/conv v1.2.2/go.mod h1:s05l3fQJQ4mT4VX+KIdbvCWQB0YzZHprmUfUu2uxd1k=
|
github.com/injoyai/conv v1.2.5/go.mod h1:s05l3fQJQ4mT4VX+KIdbvCWQB0YzZHprmUfUu2uxd1k=
|
||||||
github.com/injoyai/ios v0.0.7 h1:7k/brTmpnoqE6ajodyilkr2EJmJmcvSkpUD+LptgUVU=
|
github.com/injoyai/ios v0.0.10 h1:Zd37Rwp90PYV5eFhirR0LJ+ni/aYLSCAYgHltk/ZzJA=
|
||||||
github.com/injoyai/ios v0.0.7/go.mod h1:9HemWSJTmhyJCnr+kH+lRfmvrEdABi1rkCBVsbqV5X8=
|
github.com/injoyai/ios v0.0.10/go.mod h1:zLTmvQmIbdTf7zZxymOVxbxvvSeUMpcs4SCVPDT9BOc=
|
||||||
github.com/injoyai/logs v1.0.9 h1:Wq7rCVIQKcPx+z+lzKQb2qyDK4TML/cgmaSZN9tx33c=
|
github.com/injoyai/logs v1.0.9 h1:Wq7rCVIQKcPx+z+lzKQb2qyDK4TML/cgmaSZN9tx33c=
|
||||||
github.com/injoyai/logs v1.0.9/go.mod h1:CLchJCGhb39Obyrci816R+KMtbxZhgPs0FuikhyixK4=
|
github.com/injoyai/logs v1.0.9/go.mod h1:CLchJCGhb39Obyrci816R+KMtbxZhgPs0FuikhyixK4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
|||||||
27
hosts.go
27
hosts.go
@@ -1,10 +1,12 @@
|
|||||||
package tdx
|
package tdx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/injoyai/base/types"
|
||||||
"github.com/injoyai/logs"
|
"github.com/injoyai/logs"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -75,12 +77,12 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// FastHosts 通过tcp(ping不可用)的方式筛选可用的地址,并排序(有点误差)
|
// FastHosts 通过tcp(ping不可用)连接速度的方式筛选排序可用的地址
|
||||||
func FastHosts(hosts ...string) []string {
|
func FastHosts(hosts ...string) []DialResult {
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(hosts))
|
wg.Add(len(hosts))
|
||||||
mu := sync.Mutex{}
|
mu := sync.Mutex{}
|
||||||
ls := []string(nil)
|
ls := types.List[DialResult](nil)
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
go func(host string) {
|
go func(host string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -88,17 +90,30 @@ func FastHosts(hosts ...string) []string {
|
|||||||
if !strings.Contains(addr, ":") {
|
if !strings.Contains(addr, ":") {
|
||||||
addr += ":7709"
|
addr += ":7709"
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
c, err := net.Dial("tcp", addr)
|
c, err := net.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Err(err)
|
logs.Err(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.Close()
|
spend := time.Since(now)
|
||||||
|
c.Close()
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
ls = append(ls, host)
|
ls = append(ls, DialResult{
|
||||||
|
Host: host,
|
||||||
|
Spend: spend,
|
||||||
|
})
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}(host)
|
}(host)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return ls
|
return ls.Sort(func(a, b DialResult) bool {
|
||||||
|
return a.Spend < b.Spend
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialResult 连接结果
|
||||||
|
type DialResult struct {
|
||||||
|
Host string
|
||||||
|
Spend time.Duration
|
||||||
}
|
}
|
||||||
|
|||||||
41
manage.go
41
manage.go
@@ -25,13 +25,21 @@ func NewManage(cfg *ManageConfig, op ...client.Option) (*Manage, error) {
|
|||||||
cfg.Dial = DialDefault
|
cfg.Dial = DialDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
//代码
|
//通用客户端
|
||||||
codesClient, err := cfg.Dial(op...)
|
commonClient, err := cfg.Dial(op...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
codesClient.Wait.SetTimeout(time.Second * 5)
|
commonClient.Wait.SetTimeout(time.Second * 5)
|
||||||
codes, err := NewCodes(codesClient, cfg.CodesFilename)
|
|
||||||
|
//代码管理
|
||||||
|
codes, err := NewCodes(commonClient, cfg.CodesFilename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//工作日管理
|
||||||
|
workday, err := NewWorkday(commonClient, cfg.WorkdayFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -44,17 +52,6 @@ func NewManage(cfg *ManageConfig, op ...client.Option) (*Manage, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//工作日
|
|
||||||
workdayClient, err := cfg.Dial(op...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
workdayClient.Wait.SetTimeout(time.Second * 5)
|
|
||||||
workday, err := NewWorkday(workdayClient, cfg.WorkdayFileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Manage{
|
return &Manage{
|
||||||
Pool: p,
|
Pool: p,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
@@ -72,6 +69,20 @@ type Manage struct {
|
|||||||
Cron *cron.Cron
|
Cron *cron.Cron
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RangeStocks 遍历所有股票
|
||||||
|
func (this *Manage) RangeStocks(f func(code string)) {
|
||||||
|
for _, v := range this.Codes.GetStocks() {
|
||||||
|
f(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeETFs 遍历所有ETF
|
||||||
|
func (this *Manage) RangeETFs(f func(code string)) {
|
||||||
|
for _, v := range this.Codes.GetETFs() {
|
||||||
|
f(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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.Cron.AddFunc(spec, func() {
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
MConnect = connect{}
|
MConnect = connect{}
|
||||||
MHeart = heart{}
|
MHeart = heart{}
|
||||||
MCount = count{}
|
MCount = count{}
|
||||||
MQuote = quote{}
|
MQuote = quote{}
|
||||||
MCode = code{}
|
MCode = code{}
|
||||||
MMinute = minute{}
|
MMinute = minute{}
|
||||||
MHistoryMinute = historyMinute{}
|
MHistoryMinute = historyMinute{}
|
||||||
MMinuteTrade = minuteTrade{}
|
MTrade = trade{}
|
||||||
MHistoryMinuteTrade = historyMinuteTrade{}
|
MHistoryTrade = historyTrade{}
|
||||||
MKline = kline{}
|
MKline = kline{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConnectResp struct {
|
type ConnectResp struct {
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/injoyai/conv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HistoryMinuteTradeResp 历史分时交易比实时少了单量
|
|
||||||
type HistoryMinuteTradeResp struct {
|
|
||||||
Count uint16
|
|
||||||
List []*HistoryMinuteTrade
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryMinuteTrade struct {
|
|
||||||
Time string //时间
|
|
||||||
Price Price //价格
|
|
||||||
Volume int //成交量
|
|
||||||
Status int //0是买,1是卖,2无效(汇总出现)中途也可能出现2,例20241115(sz000001)的14:56
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HistoryMinuteTrade) String() string {
|
|
||||||
return fmt.Sprintf("%s \t%s \t%-6s \t%-6d(手) \t%-4s", this.Time, this.Price, this.Amount(), this.Volume, this.StatusString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amount 成交额
|
|
||||||
func (this *HistoryMinuteTrade) Amount() Price {
|
|
||||||
return this.Price * Price(this.Volume*100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HistoryMinuteTrade) StatusString() string {
|
|
||||||
switch this.Status {
|
|
||||||
case 0:
|
|
||||||
return "买入"
|
|
||||||
case 1:
|
|
||||||
return "卖出"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type historyMinuteTrade struct{}
|
|
||||||
|
|
||||||
func (historyMinuteTrade) Frame(date, code string, start, count uint16) (*Frame, error) {
|
|
||||||
exchange, number, err := DecodeCode(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dataBs := Bytes(conv.Uint32(date)) //req.Time.Format("20060102"))
|
|
||||||
dataBs = append(dataBs, exchange.Uint8(), 0x0)
|
|
||||||
dataBs = append(dataBs, []byte(number)...)
|
|
||||||
dataBs = append(dataBs, Bytes(start)...)
|
|
||||||
dataBs = append(dataBs, Bytes(count)...)
|
|
||||||
return &Frame{
|
|
||||||
Control: Control01,
|
|
||||||
Type: TypeHistoryMinuteTrade,
|
|
||||||
Data: dataBs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (historyMinuteTrade) Decode(bs []byte, code string) (*HistoryMinuteTradeResp, error) {
|
|
||||||
if len(bs) < 2 {
|
|
||||||
return nil, errors.New("数据长度不足")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, number, err := DecodeCode(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &HistoryMinuteTradeResp{
|
|
||||||
Count: Uint16(bs[:2]),
|
|
||||||
}
|
|
||||||
|
|
||||||
//第2-6字节不知道是啥
|
|
||||||
bs = bs[2+4:]
|
|
||||||
|
|
||||||
lastPrice := Price(0)
|
|
||||||
for i := uint16(0); i < resp.Count; i++ {
|
|
||||||
mt := &HistoryMinuteTrade{
|
|
||||||
Time: GetHourMinute([2]byte(bs[:2])),
|
|
||||||
}
|
|
||||||
var sub Price
|
|
||||||
bs, sub = GetPrice(bs[2:])
|
|
||||||
lastPrice += sub * 10 //把分转成厘
|
|
||||||
mt.Price = lastPrice / basePrice(number)
|
|
||||||
bs, mt.Volume = CutInt(bs)
|
|
||||||
bs, mt.Status = CutInt(bs)
|
|
||||||
bs, _ = CutInt(bs) //这个得到的是0,不知道是啥
|
|
||||||
resp.List = append(resp.List, mt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
67
protocol/model_history_trade.go
Normal file
67
protocol/model_history_trade.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/injoyai/conv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HistoryTradeResp 兼容之前的版本
|
||||||
|
type HistoryTradeResp = TradeResp
|
||||||
|
|
||||||
|
type historyTrade struct{}
|
||||||
|
|
||||||
|
func (historyTrade) Frame(date, code string, start, count uint16) (*Frame, error) {
|
||||||
|
exchange, number, err := DecodeCode(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dataBs := Bytes(conv.Uint32(date)) //req.Time.Format("20060102"))
|
||||||
|
dataBs = append(dataBs, exchange.Uint8(), 0x0)
|
||||||
|
dataBs = append(dataBs, []byte(number)...)
|
||||||
|
dataBs = append(dataBs, Bytes(start)...)
|
||||||
|
dataBs = append(dataBs, Bytes(count)...)
|
||||||
|
return &Frame{
|
||||||
|
Control: Control01,
|
||||||
|
Type: TypeHistoryMinuteTrade,
|
||||||
|
Data: dataBs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (historyTrade) Decode(bs []byte, c TradeCache) (*TradeResp, error) {
|
||||||
|
if len(bs) < 2 {
|
||||||
|
return nil, errors.New("数据长度不足")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, number, err := DecodeCode(c.Code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &TradeResp{
|
||||||
|
Count: Uint16(bs[:2]),
|
||||||
|
}
|
||||||
|
|
||||||
|
//第2-6字节不知道是啥
|
||||||
|
bs = bs[2+4:]
|
||||||
|
|
||||||
|
lastPrice := Price(0)
|
||||||
|
for i := uint16(0); i < resp.Count; i++ {
|
||||||
|
timeStr := GetHourMinute([2]byte(bs[:2]))
|
||||||
|
t, err := time.Parse("2006010215:04", c.Date+timeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mt := &Trade{Time: t}
|
||||||
|
var sub Price
|
||||||
|
bs, sub = GetPrice(bs[2:])
|
||||||
|
lastPrice += sub * 10 //把分转成厘
|
||||||
|
mt.Price = lastPrice / basePrice(number)
|
||||||
|
bs, mt.Volume = CutInt(bs)
|
||||||
|
bs, mt.Status = CutInt(bs)
|
||||||
|
bs, _ = CutInt(bs) //这个得到的是0,不知道是啥
|
||||||
|
resp.List = append(resp.List, mt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
func Test_stockHistoryMinuteTrade_Frame(t *testing.T) {
|
func Test_stockHistoryMinuteTrade_Frame(t *testing.T) {
|
||||||
// 预期 0c 02000000 00 1200 1200 b50f 84da3401 0000 30303030303100006400
|
// 预期 0c 02000000 00 1200 1200 b50f 84da3401 0000 30303030303100006400
|
||||||
// 0c000000000112001200b50f84da3401000030303030303100006400
|
// 0c000000000112001200b50f84da3401000030303030303100006400
|
||||||
f, err := MHistoryMinuteTrade.Frame("20241028", "sz000001", 0, 100)
|
f, err := MHistoryTrade.Frame("20241028", "sz000001", 0, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/injoyai/base/types"
|
"github.com/injoyai/base/types"
|
||||||
"github.com/injoyai/conv"
|
"github.com/injoyai/conv"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,7 +161,7 @@ func (kline) Decode(bs []byte, c KlineCache) (*KlineResp, error) {
|
|||||||
k.Volume = int64(getVolume(Uint32(bs[:4])))
|
k.Volume = int64(getVolume(Uint32(bs[:4])))
|
||||||
bs = bs[4:]
|
bs = bs[4:]
|
||||||
switch c.Type {
|
switch c.Type {
|
||||||
case TypeKlineMinute, TypeKline5Minute, TypeKlineMinute2, TypeKline15Minute, TypeKline30Minute, TypeKlineHour, TypeKlineDay2:
|
case TypeKlineMinute, TypeKline5Minute, TypeKlineMinute2, TypeKline15Minute, TypeKline30Minute, TypeKline60Minute, TypeKlineDay2:
|
||||||
k.Volume /= 100
|
k.Volume /= 100
|
||||||
}
|
}
|
||||||
k.Amount = Price(getVolume(Uint32(bs[:4])) * 1000) //从元转为厘,并去除多余的小数
|
k.Amount = Price(getVolume(Uint32(bs[:4])) * 1000) //从元转为厘,并去除多余的小数
|
||||||
@@ -209,3 +210,66 @@ func FixKlineTime(ks []*Kline) []*Kline {
|
|||||||
}
|
}
|
||||||
return ks
|
return ks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Klines []*Kline
|
||||||
|
|
||||||
|
func (this Klines) Len() int {
|
||||||
|
return len(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Klines) Swap(i, j int) {
|
||||||
|
this[i], this[j] = this[j], this[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Klines) Less(i, j int) bool {
|
||||||
|
return this[i].Time.Before(this[j].Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Klines) Sort() {
|
||||||
|
sort.Sort(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kline 计算多个K线,成一个K线
|
||||||
|
func (this Klines) Kline() *Kline {
|
||||||
|
if this == nil {
|
||||||
|
return new(Kline)
|
||||||
|
}
|
||||||
|
k := new(Kline)
|
||||||
|
for i, v := range this {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
k.Open = v.Open
|
||||||
|
k.High = v.High
|
||||||
|
k.Low = v.Low
|
||||||
|
k.Close = v.Close
|
||||||
|
case len(this) - 1:
|
||||||
|
k.Close = v.Close
|
||||||
|
k.Time = v.Time
|
||||||
|
}
|
||||||
|
if v.High > k.High {
|
||||||
|
k.High = v.High
|
||||||
|
}
|
||||||
|
if v.Low < k.Low {
|
||||||
|
k.Low = v.Low
|
||||||
|
}
|
||||||
|
k.Volume += v.Volume
|
||||||
|
k.Amount += v.Amount
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge 合并K线,1分钟转成5,15,30分钟等
|
||||||
|
func (this Klines) Merge(n int) Klines {
|
||||||
|
if this == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ks := []*Kline(nil)
|
||||||
|
for i := 0; i < len(this); i += n {
|
||||||
|
if i+n > len(this) {
|
||||||
|
ks = append(ks, this[i:].Kline())
|
||||||
|
} else {
|
||||||
|
ks = append(ks, this[i:i+n].Kline())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
package protocol
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MinuteTradeResp struct {
|
|
||||||
Count uint16
|
|
||||||
List []*MinuteTrade
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinuteTrade 分时成交,todo 时间没有到秒,客户端上也没有,东方客户端能显示秒
|
|
||||||
type MinuteTrade struct {
|
|
||||||
Time string //时间
|
|
||||||
Price Price //价格
|
|
||||||
Volume int //成交量
|
|
||||||
Number int //单数,历史数据该字段无效
|
|
||||||
Status int //0是买,1是卖,2无效(汇总出现)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *MinuteTrade) String() string {
|
|
||||||
return fmt.Sprintf("%s \t%-6s \t%-6s \t%-6d(手) \t%-4d(单) \t%-4s",
|
|
||||||
this.Time, this.Price, this.Amount(), this.Volume, this.Number, this.StatusString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amount 成交额
|
|
||||||
func (this *MinuteTrade) Amount() Price {
|
|
||||||
return this.Price * Price(this.Volume) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *MinuteTrade) StatusString() string {
|
|
||||||
switch this.Status {
|
|
||||||
case 0:
|
|
||||||
return "买入"
|
|
||||||
case 1:
|
|
||||||
return "卖出"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AvgVolume 平均每单成交量
|
|
||||||
func (this *MinuteTrade) AvgVolume() float64 {
|
|
||||||
return float64(this.Volume) / float64(this.Number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AvgPrice 平均每单成交金额
|
|
||||||
func (this *MinuteTrade) AvgPrice() Price {
|
|
||||||
return Price(this.AvgVolume() * float64(this.Price) * 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBuy 是否是买单
|
|
||||||
func (this *MinuteTrade) IsBuy() bool {
|
|
||||||
return this.Status == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSell 是否是卖单
|
|
||||||
func (this *MinuteTrade) IsSell() bool {
|
|
||||||
return this.Status == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
type minuteTrade struct{}
|
|
||||||
|
|
||||||
func (minuteTrade) Frame(code string, start, count uint16) (*Frame, error) {
|
|
||||||
exchange, number, err := DecodeCode(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
codeBs := []byte(number)
|
|
||||||
codeBs = append(codeBs, Bytes(start)...)
|
|
||||||
codeBs = append(codeBs, Bytes(count)...)
|
|
||||||
return &Frame{
|
|
||||||
Control: Control01,
|
|
||||||
Type: TypeMinuteTrade,
|
|
||||||
Data: append([]byte{exchange.Uint8(), 0x0}, codeBs...),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (minuteTrade) Decode(bs []byte, code string) (*MinuteTradeResp, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
_, code, err = DecodeCode(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bs) < 2 {
|
|
||||||
return nil, errors.New("数据长度不足")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &MinuteTradeResp{
|
|
||||||
Count: Uint16(bs[:2]),
|
|
||||||
}
|
|
||||||
|
|
||||||
bs = bs[2:]
|
|
||||||
|
|
||||||
lastPrice := Price(0)
|
|
||||||
for i := uint16(0); i < resp.Count; i++ {
|
|
||||||
mt := &MinuteTrade{
|
|
||||||
Time: GetHourMinute([2]byte(bs[:2])),
|
|
||||||
}
|
|
||||||
var sub Price
|
|
||||||
bs, sub = GetPrice(bs[2:])
|
|
||||||
lastPrice += sub * 10 //把分转换成厘
|
|
||||||
mt.Price = lastPrice / basePrice(code)
|
|
||||||
bs, mt.Volume = CutInt(bs)
|
|
||||||
bs, mt.Number = CutInt(bs)
|
|
||||||
bs, mt.Status = CutInt(bs)
|
|
||||||
bs, _ = CutInt(bs) //这个得到的是0,不知道是啥
|
|
||||||
resp.List = append(resp.List, mt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
175
protocol/model_trade.go
Normal file
175
protocol/model_trade.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/injoyai/conv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TradeResp struct {
|
||||||
|
Count uint16
|
||||||
|
List Trades
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trade 分时成交,todo 时间没有到秒,客户端上也没有,东方客户端能显示秒
|
||||||
|
type Trade struct {
|
||||||
|
Time time.Time //时间, 09:30
|
||||||
|
Price Price //价格
|
||||||
|
Volume int //成交量,手
|
||||||
|
Status int //0是买,1是卖,2中性/汇总 中途也可能出现2,例20241115(sz000001)的14:56
|
||||||
|
Number int //单数,历史数据该字段无效
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Trade) String() string {
|
||||||
|
return fmt.Sprintf("%s \t%-6s \t%-6s \t%-6d(手) \t%-4d(单) \t%-4s",
|
||||||
|
this.Time, this.Price, this.Amount(), this.Volume, this.Number, this.StatusString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amount 成交额
|
||||||
|
func (this *Trade) Amount() Price {
|
||||||
|
return this.Price * Price(this.Volume*100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Trade) StatusString() string {
|
||||||
|
switch this.Status {
|
||||||
|
case 0:
|
||||||
|
return "买入"
|
||||||
|
case 1:
|
||||||
|
return "卖出"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvgVolume 平均每单成交量
|
||||||
|
func (this *Trade) AvgVolume() float64 {
|
||||||
|
return float64(this.Volume) / float64(this.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvgPrice 平均每单成交金额
|
||||||
|
func (this *Trade) AvgPrice() Price {
|
||||||
|
return Price(this.AvgVolume() * float64(this.Price) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBuy 是否是买单
|
||||||
|
func (this *Trade) IsBuy() bool {
|
||||||
|
return this.Status == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSell 是否是卖单
|
||||||
|
func (this *Trade) IsSell() bool {
|
||||||
|
return this.Status == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type trade struct{}
|
||||||
|
|
||||||
|
func (trade) Frame(code string, start, count uint16) (*Frame, error) {
|
||||||
|
exchange, number, err := DecodeCode(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
codeBs := []byte(number)
|
||||||
|
codeBs = append(codeBs, Bytes(start)...)
|
||||||
|
codeBs = append(codeBs, Bytes(count)...)
|
||||||
|
return &Frame{
|
||||||
|
Control: Control01,
|
||||||
|
Type: TypeMinuteTrade,
|
||||||
|
Data: append([]byte{exchange.Uint8(), 0x0}, codeBs...),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (trade) Decode(bs []byte, c TradeCache) (*TradeResp, error) {
|
||||||
|
|
||||||
|
_, code, err := DecodeCode(c.Code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bs) < 2 {
|
||||||
|
return nil, errors.New("数据长度不足")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &TradeResp{
|
||||||
|
Count: Uint16(bs[:2]),
|
||||||
|
}
|
||||||
|
|
||||||
|
bs = bs[2:]
|
||||||
|
|
||||||
|
lastPrice := Price(0)
|
||||||
|
for i := uint16(0); i < resp.Count; i++ {
|
||||||
|
timeStr := GetHourMinute([2]byte(bs[:2]))
|
||||||
|
t, err := time.Parse("2006010215:04", c.Date+timeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mt := &Trade{Time: t}
|
||||||
|
var sub Price
|
||||||
|
bs, sub = GetPrice(bs[2:])
|
||||||
|
lastPrice += sub * 10 //把分转换成厘
|
||||||
|
mt.Price = lastPrice / basePrice(code)
|
||||||
|
bs, mt.Volume = CutInt(bs)
|
||||||
|
bs, mt.Number = CutInt(bs)
|
||||||
|
bs, mt.Status = CutInt(bs)
|
||||||
|
bs, _ = CutInt(bs) //这个得到的是0,不知道是啥
|
||||||
|
resp.List = append(resp.List, mt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Trades []*Trade
|
||||||
|
|
||||||
|
func (this Trades) Kline() (k *Kline, err error) {
|
||||||
|
k = &Kline{}
|
||||||
|
for i, v := range this {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
k.Time = v.Time
|
||||||
|
k.Open = v.Price
|
||||||
|
k.High = v.Price
|
||||||
|
k.Low = v.Price
|
||||||
|
k.Close = v.Price
|
||||||
|
case len(this) - 1:
|
||||||
|
k.Close = v.Price
|
||||||
|
}
|
||||||
|
k.High = conv.Select(v.Price > k.High, v.Price, k.High)
|
||||||
|
k.Low = conv.Select(v.Price < k.Low, v.Price, k.Low)
|
||||||
|
k.Volume += int64(v.Volume)
|
||||||
|
k.Amount += v.Amount()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Klines1 1分K线
|
||||||
|
func (this Trades) Klines1() (Klines, error) {
|
||||||
|
m := make(map[int64]Trades)
|
||||||
|
for _, v := range this {
|
||||||
|
//小于9点30的数据归类到9点30
|
||||||
|
if v.Time.Hour() == 9 && v.Time.Minute() < 30 {
|
||||||
|
v.Time = time.Date(v.Time.Year(), v.Time.Month(), v.Time.Day(), 9, 30, 0, 0, v.Time.Location())
|
||||||
|
}
|
||||||
|
//15:00之前和11:30之前+1
|
||||||
|
if (v.Time.Hour() >= 13 && v.Time.Hour() < 15) || (v.Time.Hour() == 11 && v.Time.Minute() < 30) || v.Time.Hour() < 11 {
|
||||||
|
v.Time = v.Time.Add(time.Minute)
|
||||||
|
}
|
||||||
|
m[v.Time.Unix()] = append(m[v.Time.Unix()], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls := Klines(nil)
|
||||||
|
for _, v := range m {
|
||||||
|
k, err := v.Kline()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ls = append(ls, k)
|
||||||
|
}
|
||||||
|
ls.Sort()
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TradeCache struct {
|
||||||
|
Date string //日期
|
||||||
|
Code string //计算倍数
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ const (
|
|||||||
TypeKline5Minute uint8 = 0 // 5分钟K 线
|
TypeKline5Minute uint8 = 0 // 5分钟K 线
|
||||||
TypeKline15Minute uint8 = 1 // 15分钟K 线
|
TypeKline15Minute uint8 = 1 // 15分钟K 线
|
||||||
TypeKline30Minute uint8 = 2 // 30分钟K 线
|
TypeKline30Minute uint8 = 2 // 30分钟K 线
|
||||||
|
TypeKline60Minute uint8 = 3 // 60分钟K 线
|
||||||
TypeKlineHour uint8 = 3 // 1小时K 线
|
TypeKlineHour uint8 = 3 // 1小时K 线
|
||||||
TypeKlineDay2 uint8 = 4 // 日K 线, 发现和Day的区别是这个要除以100,其他未知
|
TypeKlineDay2 uint8 = 4 // 日K 线, 发现和Day的区别是这个要除以100,其他未知
|
||||||
TypeKlineWeek uint8 = 5 // 周K 线
|
TypeKlineWeek uint8 = 5 // 周K 线
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func GetHourMinute(bs [2]byte) string {
|
|||||||
|
|
||||||
func GetTime(bs [4]byte, Type uint8) time.Time {
|
func GetTime(bs [4]byte, Type uint8) time.Time {
|
||||||
switch Type {
|
switch Type {
|
||||||
case TypeKlineMinute, TypeKlineMinute2, TypeKline5Minute, TypeKline15Minute, TypeKline30Minute, TypeKlineHour:
|
case TypeKlineMinute, TypeKlineMinute2, TypeKline5Minute, TypeKline15Minute, TypeKline30Minute, TypeKline60Minute:
|
||||||
|
|
||||||
yearMonthDay := Uint16(bs[:2])
|
yearMonthDay := Uint16(bs[:2])
|
||||||
hourMinute := Uint16(bs[2:4])
|
hourMinute := Uint16(bs[2:4])
|
||||||
@@ -271,15 +271,24 @@ func IsETF(code string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPrefix 添加股票代码前缀,针对股票生效,例如000001,会增加前缀sz000001(平安银行),而不是sh000001(上证指数)
|
// AddPrefix 添加股票/基金代码前缀,针对股票/基金生效,例如000001,会增加前缀sz000001(平安银行),而不是sh000001(上证指数)
|
||||||
func AddPrefix(code string) string {
|
func AddPrefix(code string) string {
|
||||||
if len(code) == 6 {
|
if len(code) == 6 {
|
||||||
switch {
|
switch {
|
||||||
case code[:1] == "6":
|
case code[:1] == "6":
|
||||||
|
//上海股票
|
||||||
code = ExchangeSH.String() + code
|
code = ExchangeSH.String() + code
|
||||||
case code[:1] == "0":
|
case code[:1] == "0":
|
||||||
|
//深圳股票
|
||||||
code = ExchangeSZ.String() + code
|
code = ExchangeSZ.String() + code
|
||||||
case code[:2] == "30":
|
case code[:2] == "30":
|
||||||
|
//深圳股票
|
||||||
|
code = ExchangeSZ.String() + code
|
||||||
|
case code[:3] == "510" || code[:3] == "511" || code[:3] == "512" || code[:3] == "513" || code[:3] == "515":
|
||||||
|
//上海基金
|
||||||
|
code = ExchangeSH.String() + code
|
||||||
|
case code[:3] == "159":
|
||||||
|
//深圳基金
|
||||||
code = ExchangeSZ.String() + code
|
code = ExchangeSZ.String() + code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
workday.go
25
workday.go
@@ -4,7 +4,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
_ "github.com/glebarez/go-sqlite"
|
_ "github.com/glebarez/go-sqlite"
|
||||||
"github.com/injoyai/base/maps"
|
"github.com/injoyai/base/maps"
|
||||||
|
"github.com/injoyai/conv"
|
||||||
"github.com/injoyai/logs"
|
"github.com/injoyai/logs"
|
||||||
|
"github.com/injoyai/tdx/protocol"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -116,6 +118,29 @@ func (this *Workday) TodayIs() bool {
|
|||||||
return this.Is(time.Now())
|
return this.Is(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RangeYear 遍历一年的所有工作日
|
||||||
|
func (this *Workday) RangeYear(year int, f func(t time.Time) bool) {
|
||||||
|
this.Range(
|
||||||
|
time.Date(year, 1, 1, 0, 0, 0, 0, time.Local),
|
||||||
|
time.Date(year, 12, 31, 0, 0, 0, 0, time.Local),
|
||||||
|
f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range 遍历指定范围的工作日
|
||||||
|
func (this *Workday) Range(start, end time.Time, f func(t time.Time) bool) {
|
||||||
|
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) {
|
||||||
|
if this.Is(start) {
|
||||||
|
if !f(start) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RangeDesc 倒序遍历工作日,从今天-1990年12月19日(上海交易所成立时间)
|
// RangeDesc 倒序遍历工作日,从今天-1990年12月19日(上海交易所成立时间)
|
||||||
func (this *Workday) RangeDesc(f func(t time.Time) bool) {
|
func (this *Workday) RangeDesc(f func(t time.Time) bool) {
|
||||||
t := IntegerDay(time.Now())
|
t := IntegerDay(time.Now())
|
||||||
|
|||||||
Reference in New Issue
Block a user