同花顺日线历史数据文件格式

2013年01月27日 14:18:05 苏内容
  标签: 日线/API
阅读:7328

同花顺采用了简易的数据库组织方式。在同花顺的发布文件包中包括了SQLite 3的驱动,但不是很清楚同花顺是如何使用这个驱动的。基本上,对同花顺数据结构的解析不需要这么复杂,同花顺发布文件包中的SQLite文件被核新程序员重新编译过,不过就与SQLite Manager的简单配合使用来看,并不影响对标准数据库文件的读取,当然,也不支持把同花顺数据文件当成标准的数据库文件进行读取。
基本上,同花顺的数据文件可以分为两类,一类是history\下行情数据文件,另一类是finance\下财务数据文件。
在history\下的数据文件,通过子目录名称识别市场代码,通过子目录下的文件名称识别交易标的(包括股票、期货等等)。文件格式采用的是简单格式,每个数据文件分别由:
文件头;
列定义;
内容;
三个部分组成。
在finance\下的数据文件,采用的是单文件容纳所有品种数据的方式,因此采用的是复合格式,每个数据文件分别由:
文件头;
列定义;
填充区域;
复合索引数据块;
内容;
五个部分组成。
文件头
文件头固定为16个字节,包括:
byte [6],6 字节长度,固定为 {0x68,0x64,0x31,0x2E, 0x30,0x00},用于识别数据文件类型;
dword,4 字节长度,记录“内容”区域的记录条数;
word,2 字节长度,记录“内容”区域的开始位置;
word,2 字节长度,记录“内容”区域每条记录的字节长度;
word,2字节长度,记录“列定义”的列个数;
列定义
列定义固定为4 个字节一组,标示一个列,第4 个字节为列内容的字节长度,由于是使用1 个字节表示,因此数据文件每列的最大长度为255个字节。
填充区域
在finance\目录下的数据文件,采用的是包括索引的复合格式存储。在列定义和符合索引数据块之间存在着一个未使用的填充区域,填充区域的长度总是列定义数量的两倍,即文件头偏移 0x0E 处 WORD 类型 * 2。
就目前所知,这一区域基本上都使用 0x00 填充,可能是保留未来使用。
复合索引数据块
在finance\目录下的数据文件,文件的“内容”区域并不包含记录所隶属的证券品种,而是把此内容放置在一个单独的索引区域。索引区的开始位置 = 0x10 + 0x04 * 列定义数量 + 0x02 * 列定义数量。
复合索引数据块由三个部分组成:
word,2 个字节,记录本索引数据区域的字节长度;
word,2 个字节,记录本索引数据区域中所包含的索引的条数;
byte[],不定长的索引记录,每条索引记录为18个字节长度,因此总长度 = 索引条数 * 0x12;
索引
每条索引的长度为18个字节,格式分别为:
byte,1 字节长度,标识证券品种类型,目前已知的包括3 类4 种标示,分别是:0x10,国内证券;0x48和0x50,港股;0x4A,基金。在“权息资料”中,使用0x50标示港股,在“现金流量”中,使用0x48标示港股,不知道是设计的 bug,还是存在其他用意;
byte [9],9 字节长度,标识交易品种的符号,目前已知是ASCII格式,从整个文件的多字节字符处理来看,文件的整体字符编码应该是GB2312编码,因此这一部分原始的处理应该是使用GB2312编码通读;
word,2 字节长度,标识隶属此交易品种的记录区域中实际上未使用的记录条数,有关的组织方式下面再详细解释;
dword,4 字节长度,标识隶属于此交易品种的记录的开始下标,注意是记录在数据文件中从0 开始排列的顺序号,而不是文件的偏移地址,其实际的偏移地址 = “内容”区域的开始地址 + 此开始下标 * 记录的长度;
word,2 个字节长度,标识隶属于此交易品种的记录的条数;
总体来说,存在索引的文件其实际的完整数据由两个部分组成,一个就是索引数据区,另一个就是记录内容数据区。其与索引对应的记录内容数据采取分块的存储模式,即,每一个交易品种总是使用一个固定的、连续的内容数据区存储相对应的数据,因此在实际的数据存储过程中,存在着并未被实际使用的“空余”记录的数据区。
复合文件通过两个方法来识别有效和无效的记录,首先,索引记录其所使用的数据区总容纳的记录条数和未被使用的、无效的记录条数,两者相减可知被实际使用的记录条数;其次,复合文件的记录总是使用一个类型为Int32的4 字节用于标识时间的列开始,如果这个列的值为<=0,则表明这是无效的数据记录。
内容
数据内容开始的地址偏移在文件头0x0A处的一个word类型的2字节数据指示,其有效长度 = 列长度 * 记录条数。
附录
同花顺的数据文件读取器和支持类库在 https://sourceforge.net/projects/ociathena/
---------------------------------------------------------------------------------------------
方法不算好方法,但比较简单,以前我做数据分析的时候,都是用这个方法,平时数据库不存数据,需要分析的时候动态调入,调入速度很快,有更好方法的朋友请多多指教。

下面是日线数据(600787只是例子不构成任何投资建议):
SQL code

--===============================================================
-- 文件头16个字节剖析(日线)
-- 0x6864312E3000 6  固定
-- 0x????????     4  记录数
-- 0x4800         2  记录开始位置: 64是错的, 文件头 + 列定义 = 72
-- 0x3800         2  每记录的长度: 56
-- 0x0E00         2  每记录的列数: 14
-----------------------------------------------------------------
-- 列定义: 04表示列长度
-- 0x01300004     4 日期
-- 0x07700004     4 开盘价
-- 0x08700004     4 最高价
-- 0x09700004     4 最低价
-- 0x0B700004     4 收盘价
-- 0x13700004     4 成交金额(元)
-- 0x0D700004     4 成交量(股)
-- 0x0E700004     4 FFFFFFFF
-- 0x0F700004     4 FFFFFFFF
-- 0x11700004     4 FFFFFFFF
-- 0x12700004     4 FFFFFFFF
-- 0x50700004     4 FFFFFFFF
-- 0xE7700004     4 FFFFFFFF
-- 0xE8700004     4 FFFFFFFF
--===============================================================
if object_id('tempdb.dbo.#') is not null drop table #
go
declare @ varbinary(max), @max int, @e float
select @ = BulkColumn, @e = 10 from OPENROWSET(BULK N'X:\...\history\shase\day\600787.day', SINGLE_BLOB) as bin
select @max = substring(@,10,1)+substring(@,9,1)+substring(@,8,1)+substring(@,7,1)
select top (@max) n = identity(int,72,56) into # from syscolumns a, syscolumns b
;with cte as
(
 select
  -- SQL没有提供按字节reverse(binary)的函数或方法,只能substring每个字节倒过来合成:
  d = convert(int,substring(@, 4+n,1)+substring(@, 3+n,1)+substring(@, 2+n,1)+substring(@, 1+n,1)),
  o = convert(int,substring(@, 8+n,1)+substring(@, 7+n,1)+substring(@, 6+n,1)+substring(@, 5+n,1))&0x0FFFFFFF,
  p = convert(int,substring(@,12+n,1)+substring(@,11+n,1)+substring(@,10+n,1)+substring(@, 9+n,1))&0x0FFFFFFF,
  q = convert(int,substring(@,16+n,1)+substring(@,15+n,1)+substring(@,14+n,1)+substring(@,13+n,1))&0x0FFFFFFF,
  r = convert(int,substring(@,20+n,1)+substring(@,19+n,1)+substring(@,18+n,1)+substring(@,17+n,1))&0x0FFFFFFF,
  s = convert(int,substring(@,24+n,1)+substring(@,23+n,1)+substring(@,22+n,1)+substring(@,21+n,1))&0x0FFFFFFF,
  t = convert(int,substring(@,28+n,1)+substring(@,27+n,1)+substring(@,26+n,1)+substring(@,25+n,1))&0x0FFFFFFF,
  u = convert(int,substring(@, 8+n,1))/16,
  v = convert(int,substring(@,12+n,1))/16,
  w = convert(int,substring(@,16+n,1))/16,
  x = convert(int,substring(@,20+n,1))/16,
  y = convert(int,substring(@,24+n,1))/16,
  z = convert(int,substring(@,28+n,1))/16
 from #
)
select
 id = row_number()over(order by d desc),
 日期 = d,
 开盘 = o*power(@e,(u&7)*power(-1,sign(u&8))),
 最高 = p*power(@e,(v&7)*power(-1,sign(v&8))),
 最低 = q*power(@e,(w&7)*power(-1,sign(w&8))),
 收盘 = r*power(@e,(x&7)*power(-1,sign(x&8))),
 金额 = s*power(@e,(y&7)*power(-1,sign(y&8))),
 成交 = t*power(@e,(z&7)*power(-1,sign(z&8)))
from cte


下面是分钟线数据,具体内容就不写了,只是调整一下数字表#标识列的初始大小和增量问题:
SQL code

--===============================================================
-- 文件头16个字节剖析(5/1分钟线)
-- 0x6864312E3000 6  固定
-- 0x????????     4  记录数
-- 0x3800         2  记录开始位置: 56是对的, 文件头 + 列定义 = 56
-- 0x2800         2  每记录的长度: 40
-- 0x0A00         2  每记录的列数: 10
-----------------------------------------------------------------
-- 列定义: 04表示列长度
-- 0x01300004     4 日期: 已加密, 不要问怎么解密, 我不知道.
-- 0x07700004     4 开盘价
-- 0x08700004     4 最高价
-- 0x09700004     4 最低价
-- 0x0B700004     4 收盘价
-- 0x13700004     4 成交金额(元)
-- 0x0D700004     4 成交量(股)
-- 0x0E700004     4 FFFFFFFF
-- 0x0F700004     4 FFFFFFFF
-- 0x12700004     4 FFFFFFFF
--===============================================================

用SQL来分析二进制文件格式还是比较方便的,可以直接select/convert,快捷直观。我一般用来分析game的存档文件,呵呵
 
c#示例代码:
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            string filePath = @"D:\同花顺软件\同花顺\history\sznse\day\000001.day";//同花顺目录下history目录是历史日线数据,我例子打开的是平安银行000001的
            byte[] stockFileBytes = System.IO.File.ReadAllBytes(filePath);
            int recordStartPos = readByteToInt(stockFileBytes, 10, 2);//记录开始位置
            int recordLength = readByteToInt(stockFileBytes, 12, 2);//记录长度
            int recordCount = readByteToInt(stockFileBytes, 14, 2);//文件中记录条数
            int fileBytesLength=stockFileBytes.Length;
            int pos=recordStartPos;
            List stockDayList = new List();//日线数据暂时读到List中
            do
            {
                StockDay sd = new StockDay();
                sd.DateInt = readByteToInt(stockFileBytes, pos, 4);//时间,整形表示的,可转为日期型
                sd.OpenPrice = readByteToInt(stockFileBytes, pos+4, 2)*0.001f;//开盘价
                sd.HighPrice = readByteToInt(stockFileBytes, pos + 8, 2) * 0.001f;//最高价
                sd.LowPrice = readByteToInt(stockFileBytes, pos + 12, 2) * 0.001f;//最低价
                sd.ClosePrice = readByteToInt(stockFileBytes, pos + 16, 2) * 0.001f;//收盘价
                sd.VolumeValue = readByteToInt(stockFileBytes, pos + 20, 4);//成交额
                sd.Volume = readByteToInt(stockFileBytes, pos + 24, 4);//成交量
                stockDayList.Add(sd);
                pos = pos + recordLength;
            } while (pos < fileBytesLength);
            foreach(StockDay sd in stockDayList)
            {
                this.richTextBox1.AppendText(sd.DateInt.ToString()+"\t"+sd.ClosePrice.ToString()+"\n");
            }
        }
        private string readByteToHex(byte[] stockFileBytes, int startPos, int length)
        {
            string r = "";
            for (int i = startPos + length - 1; i >= startPos; i--)
            {
                r += stockFileBytes[i].ToString("X2");
            }
            return r;
        }
        ///
        ///  读取某位置开始的byte转换为16进制字符串
        ///
        ///
        ///
        ///
        private int readByteToInt(byte[] stockFileBytes, int startPos, int length)
        {
            string r = readByteToHex(stockFileBytes, startPos, length);
            int v = Convert.ToInt32(r, 16);
            return v;
        }
        //每一天的日K线封装为 类
        public class StockDay
        {
            int dateInt;
            public int DateInt
            {
                get { return dateInt; }
                set { dateInt = value; }
            }
            DateTime date;//日期
            public DateTime Date
            {
                get { return date; }
                set { date = value; }
            }
            float openPrice;//开盘价
            public float OpenPrice
            {
                get { return openPrice; }
                set { openPrice = value; }
            }
            float closePrice;//收盘价
            public float ClosePrice
            {
                get { return closePrice; }
                set { closePrice = value; }
            }
            float highPrice;//最高价
            public float HighPrice
            {
                get { return highPrice; }
                set { highPrice = value; }
            }
            float lowPrice;//最低价
            public float LowPrice
            {
                get { return lowPrice; }
                set { lowPrice = value; }
            }
            float volume;//成交量
            public float Volume
            {
                get { return volume; }
                set { volume = value; }
            }
            float volumeValue;//成交额
            public float VolumeValue
            {
                get { return volumeValue; }
                set { volumeValue = value; }
            }
        }
    }


 

同花顺日线数据格式

日线文件位于history\XXX\day目录下,XXX为证交所名称(上海证券交易所的目录为shase或者深圳证券交易所的目录为sznse)。
数据文件通过文件名称识别交易标的(包括股票、期货等等)。每个数据文件分别由:
文件头,固定为16个字节,包括:
byte [6],6 字节长度,固定为 {0x68,0x64,0x31,0x2E, 0x30,0x00},用于识别数据文件类型;
dword,4 字节长度,记录“内容”区域的记录条数;
word,2 字节长度,记录“内容”区域的开始位置;(第184个字节开始,文件头16+列定义168=184)
word,2 字节长度,记录“内容”区域每条记录的字节长度;(168个字节,42 x 4 = 168字节)
word, 2 字节长度,记录“内容”区域列的个数:(42列)
列定义:固定为4 个字节一组,标示一个列,第4 个字节为列内容长度。(42个列字段,每列4个字节,一共168字节)
内容:每天168个字节,42列,前7个依次为日期、开盘、最高、最低、收盘、成交额、成交量、后35个还未使用,全部填充为0xFF。
注意:
1、开盘、最高、最低、收盘四个数据都是4字节的无符号整数,但是最后一个字节为“B0”,如“CA A0 A4 B0”,只需要将最后的“B0”改为“00”,就能得到这个无符号整数,再除以1000,就是实际的数值;
2、成交额也是用4字节的无符号整数表示,但最后一个字节为“3X”,如“5B 67 E9 35”,只需将“3”改为“0”,即“5B 67 E9 05“就可得到这个无符号整数,再乘以1000,就是成交额;
3、成交量也是用4字节的无符号整数表示,但最后一个字节为“2X”,如“1B 0B 27 25”,只需将“2”改为“0”,即“1B 0B 27 05“就可得到这个无符号整数,再乘以100,就是成交量。

扩展阅读