惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
V
Visual Studio Blog
C
Check Point Blog
Google DeepMind News
Google DeepMind News
S
SegmentFault 最新的问题
博客园 - 聂微东
量子位
T
Tailwind CSS Blog
罗磊的独立博客
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Y
Y Combinator Blog
L
LangChain Blog
小众软件
小众软件
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
M
MIT News - Artificial intelligence
Know Your Adversary
Know Your Adversary
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
有赞技术团队
有赞技术团队
V
Vulnerabilities – Threatpost
Martin Fowler
Martin Fowler
A
Arctic Wolf
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
Cyberwarzone
Cyberwarzone
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
GbyAI
GbyAI
Latest news
Latest news
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
腾讯CDC
AWS News Blog
AWS News Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
博客园 - Franky
S
Securelist
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
美团技术团队

虞双齐的博客

共识机制PoW-真的是去中心化的吗 共识算法PoW之算力与挖矿难度 共识算法PoW之由来 理解以太坊Gas和手续费 理解与计算比特币难度值Difficulty 解析比特币区块 程序员该掌握区块链技术 Docker使用问题集 区块链比特币学习路线与资源 Mac下搭建以太坊私有链 技术大牛成长 做爱折腾的技术人 如何发现自我优势成为斜杠青年 Golang Generate命令说明与使用 希腊八日游游记 Go面试题答案与解析 你误会蟑螂了 如何提高邮件件打开率和回复率 简述电子邮件发展历史
Go读取通达信历史日线数据
虞双齐 · 2017-07-26 · via 虞双齐的博客

突然间想使用Go从通达信读取A股历史行情信息,其实也蛮简单的。从通达信获取数据难点在于分析数据结构,而读取则各类语言分分钟搞定。

准备工作

  1. 下载安装通达信,通达信官网
  2. 下载历史行情数据

下载操作路径:系统->盘后数据下载

通达信下载盘后数据

下载后数据按股票市场分别存放:

  • 上海交易所:{通达信安装目录}\vipdoc\sh\lday\*.day
  • 深圳交易所:{通达信安装目录}\vipdoc\sz\lday\*.day

通达信历史日线数据文件格式

每只股票一个day文件,如:sh000001.day。文件中每一天数据总共32字节。其中每32字节数据格式如下:

数据含义数据类型数据长度举例单位
日期Integer420170703
开盘价Integer42476当前值/100,元
最高价Integer42520当前值 /100,元
最低价Integer42436当前值 / 100,元
收盘价Integer42457当前值 / 100,元
成交金额single41317335898
成交量Integer445293799
保留Integer4

注意,因为价格均是两位小数,故文件中的价格放大100倍,以便按数字存储。

Go读取日线数据文件

文件每32字节存储一天数据,在Go中只需读取指定长度的字节,再转换为int即可。

第一步:读取文件 以万科股票为例,打开该day文件,获得 reader。

f, err := os.Open(`D:\new_tdx\vipdoc\sz\lday\sz000002.day`)
if err != nil {
	log.Fatal(err)
}
defer f.Close()

第二步:获取32字节数据

从文件流中填充32个字节的数据到oneDay中,如果无错误则可以按照数据格式读取单独一天的日行情数据。

注意取价格时需再除以100,已显示正确的金额。

oneDay := make([]byte, 32)
_, err = f.Read(oneDay)
if err != nil {
	log.Fatal(err)
}
fmt.Println("日期:\t", binary.LittleEndian.Uint32(oneDay[0:4]))
fmt.Println("开盘价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[4:8]))/100)
fmt.Println("最高价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[8:12]))/100)
fmt.Println("最低价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[12:16]))/100)
fmt.Println("收盘价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[16:20]))/100)
fmt.Println("成交金额(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[20:24]))/100)
fmt.Println("成交量:\t", binary.LittleEndian.Uint32(oneDay[24:28]))
fmt.Println("Other:\t", binary.LittleEndian.Uint32(oneDay[28:32]))

//output:
/*
日期:    20170703
开盘价(元):      24.76
最高价(元):      25.2
最低价(元):      24.36
收盘价(元):      24.57
成交金额(元):    1.317335898e+07
成交量:  45293799
Other:   65536
*/

第三步:遍历获取所有日线数据

oneDay := make([]byte, 32)
for {
	l, err := f.Read(oneDay)
	if err == io.EOF {
		break
	} else if err != nil {
		log.Fatal(err)
	} else if l != 32 {
		log.Fatal("数据不完整,终止")
	}
    //Date
	fmt.Printf("%d,", binary.LittleEndian.Uint32(oneDay[0:4]))
	//other
}

历史数据可在其他网站上查看,下图来自搜狐数据

通达信下载盘后数据

完整代码:

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"os" 
)

func main() {
	f, err := os.Open(`D:\new_tdx\vipdoc\sz\lday\sz000002.day`)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	oneDay := make([]byte, 32)
	fmt.Println("日期\t\t开盘价(元)\t最高价(元)\t最低价(元)\t收盘价(元)\t成交金额(元)\t成交量\tOther")
	for {
		l, err := f.Read(oneDay)
		if err == io.EOF {
			break
		} else if err != nil {
			log.Fatal(err)
		} else if l != 32 {
			log.Fatal("数据不完整,终止")
		}
		fmt.Printf("%d\t%f\t%f\t%f\t%f\t%f\t%d\t%d\t\n",
			binary.LittleEndian.Uint32(oneDay[0:4]),
			(float64)(binary.LittleEndian.Uint32(oneDay[4:8]))/100,
			(float64)(binary.LittleEndian.Uint32(oneDay[8:12]))/100,
			(float64)(binary.LittleEndian.Uint32(oneDay[12:16]))/100,
			(float64)(binary.LittleEndian.Uint32(oneDay[16:20]))/100,
			(float64)(binary.LittleEndian.Uint32(oneDay[20:24]))/100,
			binary.LittleEndian.Uint32(oneDay[24:28]),
			binary.LittleEndian.Uint32(oneDay[28:32]),
		)
	}
}

进价

有没有更好的办法来进行数据转换?如下格式处理,过于麻烦,幸好该格式数据不多。但容易弄错,或者调整麻烦。

fmt.Println("日期:\t", binary.LittleEndian.Uint32(oneDay[0:4]))
fmt.Println("开盘价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[4:8]))/100)
fmt.Println("最高价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[8:12]))/100)
fmt.Println("最低价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[12:16]))/100)
fmt.Println("收盘价(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[16:20]))/100)
fmt.Println("成交金额(元):\t", (float64)(binary.LittleEndian.Uint32(oneDay[20:24]))/100)
fmt.Println("成交量:\t", binary.LittleEndian.Uint32(oneDay[24:28]))
fmt.Println("Other:\t", binary.LittleEndian.Uint32(oneDay[28:32])) `

可以利用Go的encoding/binary包从reader中读取二元数据到指针对象中:

// Read reads structured binary data from r into data.
// Data must be a pointer to a fixed-size value or a slice
// of fixed-size values.
// Bytes read from r are decoded using the specified byte order
// and written to successive fields of the data.
// When decoding boolean values, a zero byte is decoded as false, and
// any other non-zero byte is decoded as true.
// When reading into structs, the field data for fields with
// blank (_) field names is skipped; i.e., blank field names
// may be used for padding.
// When reading into a struct, all non-blank fields must be exported.
//
// The error is EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// Read returns ErrUnexpectedEOF.
func binary.Read(r io.Reader, order ByteOrder, data interface{}) error{}

这样便可以方便的将byte读取到对象中,下面我们定义data结构:

type DayData struct {
	Date                   int32
	Open, High, Low, Close int32
	Amount, Qty            int32
	Other                  int32
}

var d DataData
buf := bytes.NewBuffer(oneDay)
binary.Read(buf, binary.LittleEndian, &d)
fmt.Printf("%v\n", d)

注意,在binary读取buf到对象时,是依次遍历对象的内存结构赋值的,因为文件中各数据均是4位长度。故在定义字段类型时,选择int32,占4个字节。字段定义顺序便是内存结构顺序,同文件数据定义顺序保持一致。

但因为数据中均存放的是int32,而收盘价等需要进行转换,故对外时提供另一个结构体,已方便正常访问各数据。 在解析时,进行一次解析即可。

//DayQuotaion 日线行情
type DayQuotaion struct {
	Marker    string  //股票市场
	StockCode string  //股票代码
	Date      int     //日期
	Open      float32 //开盘价
	High      float32 //最高价
	Low       float32 //最低价
	Close     float32 //收盘价
	Amount    float32 //总成交金额
	Qty       float32 //  总成交量
} 

完整代码

package main

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

const historyDailyQuotationPath = `D:\new_tdx\vipdoc`

type dayData struct {
	Date                   int32
	Open, High, Low, Close int32
	Amount, Qty            int32
	Other                  int32
}

// To 转换为日线行情数据
func (d *dayData) To() *DayQuotation {
	return &DayQuotation{
		Date:   d.Date,
		Open:   float32(d.Open) / 100,
		High:   float32(d.High) / 100,
		Low:    float32(d.Low) / 100,
		Close:  float32(d.Close) / 100,
		Amount: float32(d.Amount),
		Qty:    d.Qty,
	}
}

// DayQuotation 日线行情
type DayQuotation struct {
	Date   int32   //日期
	Open   float32 //开盘价
	High   float32 //最高价
	Low    float32 //最低价
	Close  float32 //收盘价
	Amount float32 //总成交金额
	Qty    int32   //  总成交量
}

//GetStockQuoation 获取股票历史行情
func GetStockQuoation(marker, stockCode string) ([]*DayQuotation, error) {
	marker = strings.ToLower(strings.TrimSpace(marker))
	stockCode = strings.ToLower(strings.TrimSpace(stockCode))
	if marker == "" || stockCode == "" {
		return nil, errors.New("marker和stockCode不能为空")
	}
	//文件路径,e.g. D:\new_tdx\vipdoc\sz\lday\sz000002.day
	name := filepath.Join(historyDailyQuotationPath, marker, "lday", fmt.Sprintf("%s%s.day", marker, stockCode))
	f, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	quos := []*DayQuotation{}
	oneDay := make([]byte, 32)
	data := &dayData{}
	for {
		l, err := f.Read(oneDay)
		if err == io.EOF {
			break
		} else if err != nil {
			return quos, err
		} else if l != 32 {
			return quos, errors.New("数据不完整")
		}
		buf := bytes.NewBuffer(oneDay)
		err = binary.Read(buf, binary.LittleEndian, data)
		if err != nil {
			return quos, err
		}
		quos = append(quos, data.To())
	}
	return quos, nil
}

func main() {
	quos, err := GetStockQuoation("sz", "000002")
	if err != nil {
		log.Fatal(err)
	}
	for _, v := range quos {
		fmt.Printf("%v\n", v)
	}

}