mirror of
https://github.com/injoyai/tdx.git
synced 2025-11-26 21:25:35 +08:00
234 lines
5.1 KiB
Go
234 lines
5.1 KiB
Go
package protocol
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"github.com/injoyai/base/types"
|
||
"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
|
||
|
||
// Klines 合并分时成交成k线
|
||
func (this Trades) Klines() Klines {
|
||
//按天分割
|
||
m := make(types.SortMap[int64, Trades])
|
||
for _, v := range this {
|
||
//获取当天零点的时间戳
|
||
unix := time.Date(v.Time.Year(), v.Time.Month(), v.Time.Day(), 0, 0, 0, 0, v.Time.Location()).Unix()
|
||
m[unix] = append(m[unix], v)
|
||
}
|
||
|
||
//按天排序
|
||
mKline := types.SortMap[int64, Klines]{}
|
||
for date, v := range m {
|
||
//生成一分钟k线
|
||
t := time.Unix(date, 0)
|
||
mKline[date] = v.klinesForDay(t)
|
||
}
|
||
//按时间排序
|
||
lss := mKline.Sort()
|
||
ls := Klines{}
|
||
for _, v := range lss {
|
||
ls = append(ls, v...)
|
||
}
|
||
return ls
|
||
}
|
||
|
||
// Kline 合并分时成交成1个k线,注意分时成交时间保持一致
|
||
func (this Trades) Kline(t time.Time, last Price) *Kline {
|
||
k := &Kline{
|
||
Time: t,
|
||
Last: last,
|
||
Open: last,
|
||
High: last,
|
||
Low: last,
|
||
Close: last,
|
||
}
|
||
first := 0
|
||
for _, v := range this {
|
||
if v.Price <= 0 {
|
||
continue
|
||
}
|
||
switch first {
|
||
case 0:
|
||
k.Open = v.Price
|
||
k.High = v.Price
|
||
k.Low = v.Price
|
||
k.Close = v.Price
|
||
default:
|
||
k.High = conv.Select(k.High < v.Price, v.Price, k.High)
|
||
k.Low = conv.Select(k.Low > v.Price, v.Price, k.Low)
|
||
}
|
||
k.Close = v.Price
|
||
k.Volume += int64(v.Volume)
|
||
k.Amount += v.Price * Price(v.Volume) * 100
|
||
first++
|
||
}
|
||
return k
|
||
}
|
||
|
||
// kline1 生成一分钟k线,一天
|
||
func (this Trades) klinesForDay(date time.Time) Klines {
|
||
_929 := 569 //9:29 的分钟,为了兼容性强,把9:25算9:29
|
||
_930 := 570 //9:30 的分钟
|
||
_1130 := 690 //11:30 的分钟
|
||
_1300 := 780 //13:00 的分钟
|
||
_1500 := 900 //15:00 的分钟
|
||
keys := []int(nil)
|
||
//早上
|
||
m := map[int]Trades{}
|
||
for i := 0; i <= 120; i++ {
|
||
keys = append(keys, _930+i)
|
||
m[_930+i] = []*Trade{}
|
||
}
|
||
//下午
|
||
for i := 1; i <= 120; i++ {
|
||
keys = append(keys, _1300+i)
|
||
m[_1300+i] = []*Trade{}
|
||
}
|
||
//获取开盘价,有可能前几分钟没有数据,先遍历一遍
|
||
var open Price
|
||
for _, v := range this {
|
||
if v.Price > 0 {
|
||
open = v.Price
|
||
break
|
||
}
|
||
}
|
||
//分组,按
|
||
for _, v := range this {
|
||
ms := minutes(v.Time)
|
||
t := conv.Select(ms < _929, _929, ms)
|
||
t++
|
||
t = conv.Select(t > _1130 && t <= _1300, _1130, t)
|
||
t = conv.Select(t > _1500, _1500, t)
|
||
m[t] = append(m[t], v)
|
||
}
|
||
//合并
|
||
ls := []*Kline(nil)
|
||
for _, v := range keys {
|
||
k := m[v].Kline(time.Date(date.Year(), date.Month(), date.Day(), v/60, v%60, 0, 0, date.Location()), open)
|
||
open = k.Close
|
||
ls = append(ls, k)
|
||
}
|
||
return ls
|
||
}
|
||
|
||
type TradeCache struct {
|
||
Date string //日期
|
||
Code string //计算倍数
|
||
}
|