mirror of
https://github.com/injoyai/tdx.git
synced 2025-11-26 21:25:35 +08:00
363 lines
8.2 KiB
Go
363 lines
8.2 KiB
Go
package protocol
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"github.com/injoyai/base/types"
|
||
"github.com/injoyai/conv"
|
||
"sort"
|
||
"time"
|
||
)
|
||
|
||
type KlineReq struct {
|
||
Exchange Exchange
|
||
Code string
|
||
Start uint16
|
||
Count uint16
|
||
}
|
||
|
||
func (this *KlineReq) Bytes(Type uint8) (types.Bytes, error) {
|
||
if this.Count > 800 {
|
||
return nil, errors.New("单次数量不能超过800")
|
||
}
|
||
if len(this.Code) != 6 {
|
||
return nil, errors.New("股票代码长度错误")
|
||
}
|
||
data := []byte{this.Exchange.Uint8(), 0x0}
|
||
data = append(data, []byte(this.Code)...) //这里怎么是正序了?
|
||
data = append(data, Type, 0x0)
|
||
data = append(data, 0x01, 0x0)
|
||
data = append(data, Bytes(this.Start)...)
|
||
data = append(data, Bytes(this.Count)...)
|
||
data = append(data, make([]byte, 10)...) //未知啥含义
|
||
return data, nil
|
||
}
|
||
|
||
type KlineResp struct {
|
||
Count uint16
|
||
List []*Kline
|
||
}
|
||
|
||
type Kline struct {
|
||
Last Price //昨日收盘价,这个是列表的上一条数据的收盘价,如果没有上条数据,那么这个值为0
|
||
Open Price //开盘价
|
||
High Price //最高价
|
||
Low Price //最低价
|
||
Close Price //收盘价,如果是当天,则是最新价/实时价
|
||
Order int //成交单数,不一定有值
|
||
Volume int64 //成交量
|
||
Amount Price //成交额
|
||
Time time.Time //时间
|
||
UpCount int //上涨数量,指数有效
|
||
DownCount int //下跌数量,指数有效
|
||
}
|
||
|
||
func (this *Kline) String() string {
|
||
return fmt.Sprintf("%s 昨收盘:%.3f 开盘价:%.3f 最高价:%.3f 最低价:%.3f 收盘价:%.3f 涨跌:%s 涨跌幅:%0.2f 成交量:%s 成交额:%s 涨跌数: %d/%d",
|
||
this.Time.Format("2006-01-02 15:04:05"),
|
||
this.Last.Float64(), this.Open.Float64(), this.High.Float64(), this.Low.Float64(), this.Close.Float64(),
|
||
this.RisePrice(), this.RiseRate(),
|
||
Int64UnitString(this.Volume), FloatUnitString(this.Amount.Float64()),
|
||
this.UpCount, this.DownCount,
|
||
)
|
||
}
|
||
|
||
// MaxDifference 最大差值,最高-最低
|
||
func (this *Kline) MaxDifference() Price {
|
||
return this.High - this.Low
|
||
}
|
||
|
||
// RisePrice 涨跌金额,第一个数据不准,仅做参考
|
||
func (this *Kline) RisePrice() Price {
|
||
if this.Last == 0 {
|
||
//稍微数据准确点,没减去0这么夸张,还是不准的
|
||
return this.Close - this.Open
|
||
}
|
||
return this.Close - this.Last
|
||
|
||
}
|
||
|
||
// RiseRate 涨跌比例/涨跌幅,第一个数据不准,仅做参考
|
||
func (this *Kline) RiseRate() float64 {
|
||
if this.Last == 0 {
|
||
return float64(this.Close-this.Open) / float64(this.Open) * 100
|
||
}
|
||
return float64(this.Close-this.Last) / float64(this.Last) * 100
|
||
}
|
||
|
||
type kline struct{}
|
||
|
||
/*
|
||
Frame
|
||
Prefix: 0c
|
||
MsgID: 0208d301
|
||
Control: 01
|
||
Length: 1c00
|
||
Length: 1c00
|
||
Type: 2d05
|
||
Data: 000030303030303104000100a401a40100000000000000000000
|
||
|
||
Data:
|
||
Exchange: 00
|
||
Unknown: 00
|
||
Code: 303030303031
|
||
Type: 04
|
||
Unknown: 00
|
||
Unknown: 0100
|
||
Start: a401
|
||
Count: a401
|
||
Append: 00000000000000000000
|
||
*/
|
||
func (kline) Frame(Type uint8, code string, start, count uint16) (*Frame, error) {
|
||
if count > 800 {
|
||
return nil, errors.New("单次数量不能超过800")
|
||
}
|
||
|
||
exchange, number, err := DecodeCode(code)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
data := []byte{exchange.Uint8(), 0x0}
|
||
data = append(data, []byte(number)...) //这里怎么是正序了?
|
||
data = append(data, Type, 0x0)
|
||
data = append(data, 0x01, 0x0)
|
||
data = append(data, Bytes(start)...)
|
||
data = append(data, Bytes(count)...)
|
||
data = append(data, make([]byte, 10)...) //未知啥含义
|
||
|
||
return &Frame{
|
||
Control: Control01,
|
||
Type: TypeKline,
|
||
Data: data,
|
||
}, nil
|
||
}
|
||
|
||
func (kline) Decode(bs []byte, c KlineCache) (*KlineResp, error) {
|
||
|
||
if len(bs) < 2 {
|
||
return nil, errors.New("数据长度不足")
|
||
}
|
||
resp := &KlineResp{
|
||
Count: Uint16(bs[:2]),
|
||
}
|
||
bs = bs[2:]
|
||
|
||
var last Price //上条数据(昨天)的收盘价
|
||
for i := uint16(0); i < resp.Count; i++ {
|
||
k := &Kline{
|
||
Time: GetTime([4]byte(bs[:4]), c.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.Last = last
|
||
k.Open = open + last
|
||
k.Close = last + open + _close
|
||
k.High = open + last + high
|
||
k.Low = open + last + low
|
||
last = last + open + _close
|
||
|
||
/*
|
||
发现不同的K线数据处理不一致,测试如下:
|
||
1分: 需要除以100
|
||
5分: 需要除以100
|
||
15分: 需要除以100
|
||
30分: 需要除以100
|
||
60分: 需要除以100
|
||
日: 不需要操作
|
||
周: 不需要操作
|
||
月: 不需要操作
|
||
季: 不需要操作
|
||
年: 不需要操作
|
||
|
||
*/
|
||
k.Volume = int64(getVolume(Uint32(bs[:4])))
|
||
bs = bs[4:]
|
||
switch c.Type {
|
||
case TypeKlineMinute, TypeKline5Minute, TypeKlineMinute2, TypeKline15Minute, TypeKline30Minute, TypeKline60Minute, TypeKlineDay2:
|
||
k.Volume /= 100
|
||
}
|
||
k.Amount = Price(getVolume(Uint32(bs[:4])) * 1000) //从元转为厘,并去除多余的小数
|
||
bs = bs[4:]
|
||
|
||
switch c.Kind {
|
||
case KindIndex:
|
||
//指数和股票的差别,指数多解析4字节,并处理成交量*100
|
||
k.Volume *= 100
|
||
k.UpCount = conv.Int([]byte{bs[1], bs[0]})
|
||
k.DownCount = conv.Int([]byte{bs[3], bs[2]})
|
||
bs = bs[4:]
|
||
}
|
||
|
||
resp.List = append(resp.List, k)
|
||
}
|
||
resp.List = FixKlineTime(resp.List)
|
||
return resp, nil
|
||
}
|
||
|
||
type KlineCache struct {
|
||
Type uint8 //1分钟,5分钟,日线等
|
||
Kind string //指数,个股等
|
||
}
|
||
|
||
// FixKlineTime 修复盘内下午(13~15点)拉取数据的时候,11.30的时间变成13.00
|
||
func FixKlineTime(ks []*Kline) []*Kline {
|
||
if len(ks) == 0 {
|
||
return ks
|
||
}
|
||
now := time.Now()
|
||
//只有当天下午13~15点之间才会出现的时间问题
|
||
node1 := time.Date(now.Year(), now.Month(), now.Day(), 13, 0, 0, 0, now.Location())
|
||
node2 := time.Date(now.Year(), now.Month(), now.Day(), 15, 0, 0, 0, now.Location())
|
||
if ks[len(ks)-1].Time.Unix() < node1.Unix() || ks[len(ks)-1].Time.Unix() > node2.Unix() {
|
||
return ks
|
||
}
|
||
ls := ks
|
||
if len(ls) >= 120 {
|
||
ls = ls[len(ls)-120:]
|
||
}
|
||
for i, v := range ls {
|
||
if v.Time.Unix() == node1.Unix() {
|
||
ls[i].Time = time.Date(now.Year(), now.Month(), now.Day(), 11, 30, 0, 0, now.Location())
|
||
}
|
||
}
|
||
return ks
|
||
}
|
||
|
||
type Klines []*Kline
|
||
|
||
// LastPrice 获取最后一个K线的收盘价
|
||
func (this Klines) LastPrice() Price {
|
||
if len(this) == 0 {
|
||
return 0
|
||
}
|
||
return this[len(this)-1].Close
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
func (this Klines) Kline(t time.Time, last Price) *Kline {
|
||
k := &Kline{
|
||
Time: t,
|
||
Open: last,
|
||
High: last,
|
||
Low: last,
|
||
Close: last,
|
||
Volume: 0,
|
||
Amount: 0,
|
||
}
|
||
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
|
||
default:
|
||
if k.Open == 0 {
|
||
k.Open = v.Open
|
||
}
|
||
k.High = conv.Select(k.High < v.High, v.High, k.High)
|
||
k.Low = conv.Select(k.Low > v.Low, v.Low, k.Low)
|
||
}
|
||
k.Close = v.Close
|
||
k.Volume += v.Volume
|
||
k.Amount += v.Amount
|
||
}
|
||
return k
|
||
}
|
||
|
||
// Merge 合并成其他类型的K线
|
||
func (this Klines) Merge(n int) Klines {
|
||
if n <= 1 {
|
||
return this
|
||
}
|
||
|
||
ks := Klines(nil)
|
||
ls := Klines(nil)
|
||
for i := 0; ; i++ {
|
||
if len(this) <= i*n {
|
||
break
|
||
}
|
||
if len(this) < (i+1)*n {
|
||
ls = this[i*n:]
|
||
} else {
|
||
ls = this[i*n : (i+1)*n]
|
||
}
|
||
if len(ls) == 0 {
|
||
break
|
||
}
|
||
last := ls[len(ls)-1]
|
||
k := ls.Kline(last.Time, ls[0].Open)
|
||
ks = append(ks, k)
|
||
}
|
||
return ks
|
||
}
|
||
|
||
//// 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
|
||
//}
|