增加了获取k线图,简单的对了下日k线,成交量暂时不知道是否解析成功,其它的数据对的上

This commit is contained in:
钱纯净
2024-10-29 23:39:43 +08:00
parent cbb2aabb7e
commit 4b067a0f8f
8 changed files with 234 additions and 45 deletions

View File

@@ -72,6 +72,12 @@ type Client struct {
// handlerDealMessage 处理服务器响应的数据 // handlerDealMessage 处理服务器响应的数据
func (this *Client) handlerDealMessage(c *client.Client, msg ios.Acker) { func (this *Client) handlerDealMessage(c *client.Client, msg ios.Acker) {
defer func() {
if e := recover(); e != nil {
logs.Err(e)
}
}()
f, err := protocol.Decode(msg.Payload()) f, err := protocol.Decode(msg.Payload())
if err != nil { if err != nil {
logs.Err(err) logs.Err(err)
@@ -79,7 +85,6 @@ func (this *Client) handlerDealMessage(c *client.Client, msg ios.Acker) {
} }
val, _ := this.m.GetAndDel(conv.String(f.MsgID)) val, _ := this.m.GetAndDel(conv.String(f.MsgID))
code := conv.String(val)
var resp any var resp any
switch f.Type { switch f.Type {
@@ -101,10 +106,13 @@ func (this *Client) handlerDealMessage(c *client.Client, msg ios.Acker) {
resp, err = protocol.MStockMinute.Decode(f.Data) resp, err = protocol.MStockMinute.Decode(f.Data)
case protocol.TypeStockMinuteTrade: case protocol.TypeStockMinuteTrade:
resp, err = protocol.MStockMinuteTrade.Decode(f.Data, code) //todo resp, err = protocol.MStockMinuteTrade.Decode(f.Data, conv.String(val)) //todo
case protocol.TypeStockHistoryMinuteTrade: case protocol.TypeStockHistoryMinuteTrade:
resp, err = protocol.MStockHistoryMinuteTrade.Decode(f.Data, code) resp, err = protocol.MStockHistoryMinuteTrade.Decode(f.Data, conv.String(val))
case protocol.TypeStockKline:
resp, err = protocol.MStockKline.Decode(f.Data, protocol.TypeKline(conv.Uint16(val)))
default: default:
err = fmt.Errorf("通讯类型未解析:0x%X", f.Type) err = fmt.Errorf("通讯类型未解析:0x%X", f.Type)
@@ -244,3 +252,61 @@ func (this *Client) GetStockHistoryMinuteTradeAll(exchange protocol.Exchange, co
} }
return resp, nil return resp, nil
} }
// GetStockKline 获取k线数据
func (this *Client) GetStockKline(Type protocol.TypeKline, req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
f, err := protocol.MStockKline.Frame(Type, req)
if err != nil {
return nil, err
}
result, err := this.SendFrame(f)
if err != nil {
return nil, err
}
return result.(*protocol.StockKlineResp), nil
}
// GetStockKlineMinute 获取一分钟k线数据
func (this *Client) GetStockKlineMinute(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKlineMinute, req)
}
// GetStockKline5Minute 获取五分钟k线数据
func (this *Client) GetStockKline5Minute(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKline5Minute, req)
}
// GetStockKline15Minute 获取十五分钟k线数据
func (this *Client) GetStockKline15Minute(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKline15Minute, req)
}
// GetStockKline30Minute 获取三十分钟k线数据
func (this *Client) GetStockKline30Minute(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKline30Minute, req)
}
// GetStockKlineDay 获取日k线数据
func (this *Client) GetStockKlineDay(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKlineDay, req)
}
// GetStockKlineWeek 获取周k线数据
func (this *Client) GetStockKlineWeek(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKlineWeek, req)
}
// GetStockKlineMonth 获取月k线数据
func (this *Client) GetStockKlineMonth(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKlineMonth, req)
}
// GetStockKlineQuarter 获取季k线数据
func (this *Client) GetStockKlineQuarter(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKlineQuarter, req)
}
// GetStockKlineYear 获取年k线数据
func (this *Client) GetStockKlineYear(req *protocol.StockKlineReq) (*protocol.StockKlineResp, error) {
return this.GetStockKline(protocol.TypeKlineYear, req)
}

View File

@@ -0,0 +1,26 @@
package main
import (
"github.com/injoyai/logs"
"github.com/injoyai/tdx"
"github.com/injoyai/tdx/example/common"
"github.com/injoyai/tdx/protocol"
)
func main() {
common.Test(func(c *tdx.Client) {
resp, err := c.GetStockKlineDay(&protocol.StockKlineReq{
Exchange: protocol.ExchangeSH,
Code: "000001",
Start: 0,
Count: 10,
})
logs.PanicErr(err)
for _, v := range resp.List {
logs.Debug(v)
}
logs.Debug("总数:", resp.Count)
})
}

View File

@@ -58,18 +58,5 @@ const (
) )
const (
KLINE_TYPE_5MIN = 0 // 5分钟K 线
KLINE_TYPE_15MIN = 1 // 15分钟K 线
KLINE_TYPE_30MIN = 2 // 30分钟K 线
KLINE_TYPE_1HOUR = 3 // 1小时K 线
KLINE_TYPE_DAILY = 4 // 日K 线
KLINE_TYPE_WEEKLY = 5 // 周K 线
KLINE_TYPE_MONTHLY = 6 // 月K 线
KLINE_TYPE_EXHQ_1MIN = 7 // 1分钟
KLINE_TYPE_1MIN = 8 // 1分钟K 线
KLINE_TYPE_RI_K = 9 // 日K 线
KLINE_TYPE_3MONTH = 10 // 季K 线
KLINE_TYPE_YEARLY = 11 // 年K 线
)
*/ */

View File

@@ -65,8 +65,8 @@ type Response struct {
/* /*
Decode Decode
帧头 |未知 |消息ID |控制码 |数据类型 |未解压长度 |解压长度 |数据域 帧头 |未知 |消息ID |控制码 |数据类型 |未解压长度 |解压长度 |数据域
b1cb7400 |1c |00000000 |00 |0d00 |5100 |bd00 |789c6378c1cecb252ace6066c5b4898987b9050ed1f90cc5b74c18a5bc18c1b43490fecff09c81819191f13fc3c9f3bb169f5e7dfefeb5ef57f7199a305009308208e5b32bb6bcbf70148712002d7f1e13 b1cb7400 |1c |00000000 |00 |0d00 |5100 |bd00 |789c6378c1cecb252ace6066c5b4898987b9050ed1f90cc5b74c18a5bc18c1b43490fecff09c81819191f13fc3c9f3bb169f5e7dfefeb5ef57f7199a305009308208e5b32bb6bcbf70148712002d7f1e13
*/ */
func Decode(bs []byte) (*Response, error) { func Decode(bs []byte) (*Response, error) {
if len(bs) < 16 { if len(bs) < 16 {

View File

@@ -2,6 +2,7 @@ package protocol
import ( import (
"errors" "errors"
"fmt"
"github.com/injoyai/base/g" "github.com/injoyai/base/g"
"time" "time"
) )
@@ -9,19 +10,18 @@ import (
type StockKlineReq struct { type StockKlineReq struct {
Exchange Exchange Exchange Exchange
Code string Code string
Type uint16 //类型 1分 5分 等等
Start uint16 Start uint16
Count uint16 Count uint16
} }
func (this *StockKlineReq) Bytes() (g.Bytes, error) { func (this *StockKlineReq) Bytes(Type TypeKline) (g.Bytes, error) {
if len(this.Code) != 6 { if len(this.Code) != 6 {
return nil, errors.New("股票代码长度错误") return nil, errors.New("股票代码长度错误")
} }
data := []byte{this.Exchange.Uint8(), 0x0} data := []byte{this.Exchange.Uint8(), 0x0}
data = append(data, Bytes(this.Code)...) data = append(data, []byte(this.Code)...) //这里怎么是正序了?
data = append(data, Bytes(this.Type)...) data = append(data, Bytes(Type.Uint16())...)
data = append(data, 0x0, 0x0) data = append(data, 0x01, 0x0)
data = append(data, Bytes(this.Start)...) data = append(data, Bytes(this.Start)...)
data = append(data, Bytes(this.Count)...) data = append(data, Bytes(this.Count)...)
data = append(data, make([]byte, 10)...) //未知啥含义 data = append(data, make([]byte, 10)...) //未知啥含义
@@ -34,24 +34,27 @@ type StockKlineResp struct {
} }
type StockKline struct { type StockKline struct {
Open Price Open Price //开盘价
High Price High Price //最高价
Low Price Low Price //最低价
Close Price Close Price //收盘价
Volume int //成交量 Volume float64 //成交量
Number int //成交 Amount float64 //成交
Time time.Time Time time.Time //时间
Year int }
Month int
Day int func (this *StockKline) String() string {
Hour int return fmt.Sprintf("%s 开盘价:%s 最高价:%s 最低价:%s 收盘价:%s 成交量:%s 成交额:%s",
Minute int this.Time.Format("2006-01-02 15:04:05"),
this.Open, this.High, this.Low, this.Close,
FloatUnitString(this.Volume), FloatUnitString(this.Amount),
)
} }
type stockKline struct{} type stockKline struct{}
func (stockKline) Frame(req *StockKlineReq) (*Frame, error) { func (stockKline) Frame(Type TypeKline, req *StockKlineReq) (*Frame, error) {
bs, err := req.Bytes() bs, err := req.Bytes(Type)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -62,7 +65,7 @@ func (stockKline) Frame(req *StockKlineReq) (*Frame, error) {
}, nil }, nil
} }
func (stockKline) Decode(bs []byte) (*StockKlineResp, error) { func (stockKline) Decode(bs []byte, Type TypeKline) (*StockKlineResp, error) {
if len(bs) < 2 { if len(bs) < 2 {
return nil, errors.New("数据长度不足") return nil, errors.New("数据长度不足")
@@ -72,9 +75,39 @@ func (stockKline) Decode(bs []byte) (*StockKlineResp, error) {
Count: Uint16(bs[:2]), Count: Uint16(bs[:2]),
} }
for i := uint16(0); i < resp.Count; i++ { bs = bs[2:]
var last Price
for i := uint16(0); i < resp.Count; i++ {
k := &StockKline{
Time: GetTime([4]byte(bs[:4]), Type),
}
var open Price
bs, open = GetPrice(bs[4:])
var _close Price
bs, _close = GetPrice(bs)
var high Price
bs, high = GetPrice(bs)
var low Price
bs, low = GetPrice(bs)
k.Open = (open + last) / 10
k.Close = (open + last + _close) / 10
k.High = (open + last + high) / 10
k.Low = (open + last + low) / 10
last = last + open + _close
//logs.Debug(Reverse(bs[:4]), getVolume(Uint32(bs[:4])))
//logs.Debug(Reverse(bs[4:8]), getVolume(Uint32(bs[4:8])))
k.Volume = getVolume(Uint32(bs[:4])) / 100
k.Amount = getVolume(Uint32(bs[4:8]))
bs = bs[8:]
resp.List = append(resp.List, k)
} }
return nil, nil return resp, nil
} }

View File

@@ -0,0 +1,36 @@
package protocol
import (
"encoding/hex"
"testing"
)
func Test_stockKline_Frame(t *testing.T) {
//预期0c02000000001c001c002d050000303030303031 0900 0100 0000 0a00 00000000000000000000
// 0c00000000011c001c002d050000313030303030 0900 0000 0000 0a00 00000000000000000000
f, _ := MStockKline.Frame(TypeKlineDay2, &StockKlineReq{
Exchange: ExchangeSH,
Code: "000001",
Start: 0,
Count: 10,
})
t.Log(f.Bytes().HEX())
}
func Test_stockKline_Decode(t *testing.T) {
s := "0a0078da340198b8018404bc055ee8b3e949ad2b094f79da34010af801a002cc0260dec949859ded4e7ada34016882028e04e603b8f91e4a111f394f7dda3401e401c20200f604f84d2b4ad4d0444f7eda3401721eaa0268d87bc549ee80e34e7fda34011e288601c601d08db849230ed54e80da3401727c32da013023584999a0784e81da3401147c0ad001d0fa86498d989a4e84da34015e6800d60278c28e491ca6a14e85da340154d001b801da01403e924989d6a54e"
bs, err := hex.DecodeString(s)
if err != nil {
t.Error(err)
return
}
resp, err := MStockKline.Decode(bs, 9)
if err != nil {
t.Error(err)
return
}
t.Log(len(resp.List))
for _, v := range resp.List {
t.Log(v)
}
}

View File

@@ -45,3 +45,22 @@ const (
ExchangeSZ //深圳交易所 ExchangeSZ //深圳交易所
ExchangeBJ //北京交易所 ExchangeBJ //北京交易所
) )
type TypeKline uint8
func (this TypeKline) Uint16() uint16 { return uint16(this) }
const (
TypeKline5Minute TypeKline = 0 // 5分钟K 线
TypeKline15Minute TypeKline = 1 // 15分钟K 线
TypeKline30Minute TypeKline = 2 // 30分钟K 线
TypeKlineHour TypeKline = 3 // 1小时K 线
TypeKlineDay TypeKline = 4 // 日K 线
TypeKlineWeek TypeKline = 5 // 周K 线
TypeKlineMonth TypeKline = 6 // 月K 线
TypeKlineMinute TypeKline = 7 // 1分钟
TypeKlineMinute2 TypeKline = 8 // 1分钟K 线
TypeKlineDay2 TypeKline = 9 // 日K 线
TypeKlineQuarter TypeKline = 10 // 季K 线
TypeKlineYear TypeKline = 11 // 年K 线
)

View File

@@ -8,6 +8,7 @@ import (
"golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform" "golang.org/x/text/transform"
"io" "io"
"time"
) )
// String 字节先转小端,再转字符 // String 字节先转小端,再转字符
@@ -56,10 +57,11 @@ func FloatUnit(f float64) (float64, string) {
} }
func FloatUnitString(f float64) string { func FloatUnitString(f float64) string {
m := []string{"万", "亿"} m := []string{"万", "亿", "万亿", "亿亿", "万亿亿", "亿亿亿"}
unit := "" unit := ""
for i := 0; f > 1e4 && i < len(m); f /= 1e4 { for i := 0; f > 1e4 && i < len(m); i++ {
unit = m[i] unit = m[i]
f /= 1e4
} }
if unit == "" { if unit == "" {
return conv.String(f) return conv.String(f)
@@ -78,8 +80,28 @@ func GetDate(bs [2]byte) string {
return fmt.Sprintf("%02d:%02d", h, m) return fmt.Sprintf("%02d:%02d", h, m)
} }
func GetTime(bs [4]byte) string { func GetTime(bs [4]byte, Type TypeKline) time.Time {
return "" switch Type {
case TypeKlineDay, TypeKlineMinute, TypeKlineMinute2:
yearMonthDay := Uint16(bs[:2])
hourMinute := Uint16(bs[:2])
year := int(yearMonthDay>>11 + 2004)
month := yearMonthDay % 2048 / 100
day := int((yearMonthDay % 2048) % 100)
hour := int(hourMinute / 60)
minute := int(hourMinute % 60)
return time.Date(year, time.Month(month), day, hour, minute, 0, 0, time.Local)
default:
yearMonthDay := Uint32(bs[:4])
year := int(yearMonthDay / 10000)
month := int((yearMonthDay % 10000) / 100)
day := int(yearMonthDay % 100)
return time.Date(year, time.Month(month), day, 15, 0, 0, 0, time.Local)
}
} }
func basePrice(code string) Price { func basePrice(code string) Price {