├── .gitignore ├── CHANGELOG.md ├── README.md ├── pom.xml └── src ├── main └── java │ └── org │ └── zoomdev │ └── stock │ └── tdx │ ├── BlockFileType.java │ ├── BlockStock.java │ ├── BlockType.java │ ├── Category.java │ ├── Market.java │ ├── StockInfo.java │ ├── TdxClient.java │ ├── TdxClientService.java │ ├── TdxCommand.java │ ├── TimePrice.java │ ├── commands │ ├── BaseCommand.java │ ├── GetHistoryTimePriceCommand.java │ ├── GetIndexQuotesCommand.java │ ├── GetQuotesCommand.java │ ├── GetStockCommand.java │ ├── GetTimePriceCommand.java │ ├── GroupCommand.java │ ├── ListCommand.java │ ├── LoginCommand.java │ └── RecordOutputStream.java │ ├── impl │ ├── DefaultIpRecord.java │ ├── IpInfo.java │ ├── IpRecord.java │ ├── Ips.java │ ├── ServiceThread.java │ ├── TdxClientImpl.java │ ├── TdxClientServiceImpl.java │ ├── TdxInputStream.java │ └── TdxUtils.java │ ├── reader │ ├── TdxBlockReader.java │ └── TdxQuoteReader.java │ ├── utils │ ├── DataInputStream.java │ ├── DataOutputStream.java │ └── HexUtils.java │ └── writer │ └── TdxQuoteWriter.java └── test └── java ├── TestReaderWriter.java ├── TestSocketClient.java └── TestSocketClientService.java /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | */logs 3 | target/* 4 | .idea/** 5 | session/* 6 | **/uploads/* 7 | Src/main/webapp/avatar/* 8 | **/target/** -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.2 2 | * 修正概念获取的时候获取到指数等无关信息 3 | 4 | ## 0.2.0 5 | * 完善板块信息的获取(概念、通达信行业、风格),需要设置通达信目录以便加载配置信息 6 | 7 | ## 0.1.8 8 | * 不同线程连接不同地址 9 | 10 | ## 0.1.7 11 | * 增加reconnectTimeout属性 12 | 13 | ## 0.1.6 14 | * StockInfo增加market字段 15 | 16 | ## 0.1.4 17 | * 整合getStockList 18 | 19 | ## 0.1.2 20 | * 修改日期格式为yyyyMMdd与yyyyMMddHHmm 21 | 22 | ## 0.1.1 23 | * 新增writer 24 | 25 | 26 | ## 0.1.0 27 | * 一些代码优化 28 | 29 | 30 | ## 0.0.6 31 | 32 | * 限定count 800 33 | 34 | ## 0.0.5 35 | * 文件解析类改成DataInput参数的方法 36 | 37 | ## 0.0.4 38 | 39 | * 文件解析类增加DataInputStream参数的方法 40 | 41 | ## 0.0.3 42 | 43 | 44 | * 修正分钟线解析日期多了:的bug 45 | 46 | 47 | ## 0.0.2 48 | 49 | * 查询商品数量,心跳 50 | 51 | ## 0.0.1 52 | 53 | * 查询基本功能(板块信息、k线、交易品种 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 通达信客户端java版本 3 | 4 | 目前仅支持基本行情查询,之后有时间再搞出扩展和交易接口 5 | 6 | 7 | pythone项目: https://github.com/rainx/pytdx 8 | 感谢作者的贡献 9 | 10 | 11 | 12 | # 功能列表 13 | 14 | ## 基本行情 15 | 16 | + [x] 查询交易品种 17 | + [x] 查询板块信息 18 | + [x] 查询k线 19 | 20 | 21 | ## 使用说明 22 | 23 | 24 | 目前有两种使用方式,一种是非多线程非线程安全的方式,直接初始化TxdClient,并以此来运行所有功能,缺点是需要自行维护连接。 25 | 另一中是可在多线程环境下使用的方式,本方式可自行维护最快服务器的选择、自动连接、心跳、重连等 26 | 27 | #### 单线程方式 28 | 29 | 30 | ``` 31 | TdxClient client = new TdxClient(); 32 | client.setSocketAddress(new InetSocketAddress("119.147.212.81", 7709)); 33 | client.connect(); 34 | 35 | List blocks= client.getBlockInfo(BlockType.BLOCK_DEFAULT); 36 | System.out.println(blocks); 37 | ``` 38 | 39 | 40 | #### 多线程方式 41 | 42 | 43 | ```` 44 | TdxClientService service = new TdxClientService(); 45 | service.start(); 46 | 47 | Future> list = service.getStockList(Market.sh,0); 48 | List result = list.get(); 49 | System.out.println(result); 50 | 51 | service.stop(); 52 | ```` 53 | 54 | 所有api在多线程模式下均返回Future,用户可酌情处理 55 | 56 | 57 | # API列表 58 | 59 | #### 1、查询交易品种 60 | 61 | #### 2、查询板块信息 62 | 63 | #### 3、查询k线行情 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 4.0.0 7 | 0.2.4 8 | tdx 9 | org.zoomdev.stock 10 | txd 11 | 12 | http://www.example.com 13 | 14 | 15 | 16 | 17 | nexus-release 18 | http://nexus.zoom-dev.org/repository/maven-releases/ 19 | 20 | 21 | 22 | nexus-snapshot 23 | http://nexus.zoom-dev.org/repository/maven-snapshots/ 24 | 25 | 26 | 27 | 28 | 29 | UTF-8 30 | 1.8 31 | 1.8 32 | 33 | 34 | 35 | 36 | junit 37 | junit 38 | 4.11 39 | test 40 | 41 | 42 | 43 | 0.0.6 44 | stock 45 | org.zoomdev.stock 46 | 47 | 48 | commons-logging 49 | commons-logging 50 | 1.2 51 | 52 | 53 | 54 | com.jcraft 55 | jzlib 56 | 1.1.3 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/BlockFileType.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | public enum BlockFileType { 4 | 5 | 6 | BLOCK_ZS("block_zs.dat"), 7 | BLOCK_FG("block_fg.dat"), 8 | BLOCK_GN("block_gn.dat"), 9 | BLOCK_DEFAULT("block.dat"); 10 | 11 | 12 | private final String name; 13 | 14 | BlockFileType(String name) { 15 | this.name = name; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/BlockStock.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | import java.util.List; 4 | 5 | public class BlockStock { 6 | 7 | String code; 8 | String name; 9 | int level; 10 | List codes; 11 | BlockType type; 12 | 13 | public String getCode() { 14 | return code; 15 | } 16 | 17 | public void setCode(String code) { 18 | this.code = code; 19 | } 20 | 21 | public BlockType getType() { 22 | return type; 23 | } 24 | 25 | public void setType(BlockType type) { 26 | this.type = type; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return String.format("%s %s %d (%d)", name, code, level, codes != null ? codes.size() : 0); 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | 42 | public int getLevel() { 43 | return level; 44 | } 45 | 46 | public void setLevel(int level) { 47 | this.level = level; 48 | } 49 | 50 | public List getCodes() { 51 | return codes; 52 | } 53 | 54 | public void setCodes(List codes) { 55 | this.codes = codes; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/BlockType.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | public enum BlockType { 4 | //通达信行业 5 | TdxIndustry(2), 6 | Area(3), 7 | 8 | 9 | //概念 10 | Concept(4), 11 | 12 | //风格 13 | Style(5), 14 | 15 | 16 | //申万行业 17 | SwIndustry(8), 18 | 19 | 20 | //指数板块 21 | Index(9); 22 | 23 | private final int type; 24 | 25 | BlockType(int type) { 26 | this.type = type; 27 | } 28 | 29 | public int getType() { 30 | return type; 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/Category.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | /** 4 | * 时间线 5 | */ 6 | public enum Category { 7 | m5,//0 8 | m15,//1 9 | m30,//2 10 | hour,//3 11 | day,//4 12 | week,//5 13 | month,//6 14 | m1,//7 15 | _m1,//8 16 | _d,//9 17 | season,//10 18 | year,//11 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/Market.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | public enum Market { 4 | 5 | sz, //0 深圳 6 | sh //1 上海 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/StockInfo.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | public class StockInfo { 4 | 5 | String code; 6 | double price; 7 | String name; 8 | int decimalPoint; 9 | Market market; 10 | 11 | public Market getMarket() { 12 | return market; 13 | } 14 | 15 | public void setMarket(Market market) { 16 | this.market = market; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return String.format("%s %s %.02f", code, name, price); 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public String getCode() { 33 | return code; 34 | } 35 | 36 | public void setCode(String code) { 37 | this.code = code; 38 | } 39 | 40 | public double getPrice() { 41 | return price; 42 | } 43 | 44 | public void setPrice(double price) { 45 | this.price = price; 46 | } 47 | 48 | public int getDecimalPoint() { 49 | return decimalPoint; 50 | } 51 | 52 | public void setDecimalPoint(int decimalPoint) { 53 | this.decimalPoint = decimalPoint; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/TdxClient.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | import org.zoomdev.stock.Quote; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.util.Collection; 8 | import java.util.List; 9 | 10 | public interface TdxClient { 11 | 12 | void setTdxRootDir(String tdxRootDir); 13 | 14 | 15 | void setSoTimeout(int soTimeout); 16 | 17 | void setConnectTimeout(int connectTimeout); 18 | 19 | void setSocketAddress(InetSocketAddress address); 20 | 21 | void connect() throws IOException; 22 | 23 | boolean isConnected(); 24 | 25 | void close(); 26 | 27 | List getIndexQuotes( 28 | Category category, 29 | Market market, 30 | String code, 31 | int start, 32 | int count 33 | ) throws IOException; 34 | 35 | List getQuotes( 36 | Category category, 37 | Market market, 38 | String code, 39 | int start, 40 | int count 41 | 42 | 43 | ) throws IOException; 44 | 45 | int getCount(Market market) throws IOException; 46 | 47 | List getStockList(Market market, int start) throws IOException; 48 | 49 | //全部 50 | List getStockList() throws IOException; 51 | 52 | Collection getBlockInfo(BlockType type) throws IOException; 53 | 54 | 55 | List getTimePrice(Market market, String code) throws IOException; 56 | 57 | List getHistoryTimePrice(Market market, String code, String date) throws IOException; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/TdxClientService.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | import org.zoomdev.stock.Quote; 4 | 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.concurrent.Future; 8 | 9 | public interface TdxClientService { 10 | void setTdxRootDir(String tdxRootDir); 11 | 12 | void start(); 13 | 14 | void stop(); 15 | 16 | Future getCount(Market market); 17 | 18 | Future> getStockList(Market market, final int start); 19 | 20 | Future> getBlockInfo(BlockType type); 21 | 22 | Future> getIndexQuotes( 23 | Category category, 24 | Market market, 25 | String code, 26 | int start, 27 | int count 28 | ); 29 | 30 | 31 | //全部 32 | Future> getStockList(); 33 | 34 | Future> getQuotes( 35 | Category category, 36 | Market market, 37 | String code, 38 | int start, 39 | int count 40 | 41 | 42 | ); 43 | 44 | 45 | // 设置断线之后重连的延时(毫秒) 46 | void setReconnectTimeout(int timeout); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/TdxCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 4 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 5 | 6 | import java.io.IOException; 7 | 8 | public interface TdxCommand { 9 | R process(DataOutputStream outputStream, TdxInputStream inputStream) throws IOException; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/TimePrice.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx; 2 | 3 | public class TimePrice { 4 | 5 | int vol; 6 | double close; 7 | 8 | @Override 9 | public String toString() { 10 | return new StringBuilder() 11 | .append(close).append(" ").append(vol).toString(); 12 | } 13 | 14 | public int getVol() { 15 | return vol; 16 | } 17 | 18 | public void setVol(int vol) { 19 | this.vol = vol; 20 | } 21 | 22 | public double getClose() { 23 | return close; 24 | } 25 | 26 | public void setClose(double close) { 27 | this.close = close; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/BaseCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.zoomdev.stock.tdx.TdxCommand; 6 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 7 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 8 | import org.zoomdev.stock.tdx.utils.HexUtils; 9 | 10 | import java.io.IOException; 11 | 12 | public abstract class BaseCommand implements TdxCommand { 13 | 14 | 15 | protected static final Log log = LogFactory.getLog(BaseCommand.class); 16 | 17 | 18 | public R process(DataOutputStream outputStream, TdxInputStream inputStream) throws IOException { 19 | if (log.isDebugEnabled()) { 20 | RecordOutputStream recordOutputStream = new RecordOutputStream(outputStream); 21 | doOutput(recordOutputStream); 22 | log.debug("Send data " + HexUtils.encodeHexStr(recordOutputStream.toByteArray())); 23 | } else { 24 | doOutput(outputStream); 25 | } 26 | outputStream.flush(); 27 | inputStream.readPack(false); 28 | return doInput(inputStream); 29 | } 30 | 31 | protected abstract void doOutput(DataOutputStream outputStream) throws IOException; 32 | 33 | protected abstract R doInput(TdxInputStream inputStream) throws IOException; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/GetHistoryTimePriceCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.zoomdev.stock.tdx.Market; 4 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 5 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 6 | 7 | import java.io.IOException; 8 | 9 | public class GetHistoryTimePriceCommand extends GetTimePriceCommand { 10 | 11 | /// yyyMMdd 12 | private String date; 13 | 14 | public GetHistoryTimePriceCommand(Market market, String code, String date) { 15 | super(market, code); 16 | this.date = date; 17 | } 18 | 19 | @Override 20 | protected void doOutput(DataOutputStream outputStream) throws IOException { 21 | outputStream.writeHexString("0c01300001010d000d00b40f"); 22 | outputStream.writeInt(Integer.parseInt(date)); 23 | outputStream.write(market.ordinal()); 24 | outputStream.writeAscii(code); 25 | } 26 | 27 | @Override 28 | protected void skip(TdxInputStream inputStream) { 29 | inputStream.skip(4); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/GetIndexQuotesCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.zoomdev.stock.IndexQuote; 4 | import org.zoomdev.stock.Quote; 5 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 6 | 7 | import java.io.IOException; 8 | 9 | public class GetIndexQuotesCommand extends GetQuotesCommand { 10 | 11 | public GetIndexQuotesCommand(int category, int market, String code, int start, int count) { 12 | super(category, market, code, start, count); 13 | } 14 | 15 | @Override 16 | protected Quote parseItem(TdxInputStream stream) throws IOException { 17 | IndexQuote quote = (IndexQuote) super.parseItem(stream); 18 | int upCount = stream.readShort(); 19 | int downCount = stream.readShort(); 20 | quote.setUpCount(upCount); 21 | quote.setDownCount(downCount); 22 | return quote; 23 | } 24 | 25 | 26 | @Override 27 | protected Quote createQuote() { 28 | return new IndexQuote(); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/GetQuotesCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.zoomdev.stock.Quote; 4 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 5 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 6 | 7 | import java.io.IOException; 8 | 9 | public class GetQuotesCommand extends ListCommand { 10 | 11 | private final String code; 12 | private final int market; 13 | private final int start; 14 | private final int count; 15 | private final int category; 16 | double pre_diff_base = 0; 17 | 18 | public GetQuotesCommand( 19 | int category, 20 | int market, 21 | String code, 22 | 23 | int start, 24 | int count 25 | ) { 26 | if (count > 800) { 27 | count = 800; 28 | } 29 | 30 | this.code = code; 31 | this.market = market; 32 | this.start = start; 33 | this.count = count; 34 | this.category = category; 35 | } 36 | 37 | static String getDate(int year, int month, int date, int hour, int minute) { 38 | return String.format("%d%02d%02d%02d%02d", year, month, date, hour, minute); 39 | } 40 | 41 | static String getDate(int year, int month, int date) { 42 | return String.format("%d%02d%02d", year, month, date); 43 | } 44 | 45 | protected static double getPrice(double base, double diff) { 46 | return (base + diff) / 1000; 47 | } 48 | 49 | public static double getVolumn(int ivol) { 50 | int logpoint = ivol >> (8 * 3); 51 | int hheax = ivol >> (8 * 3); 52 | int hleax = (ivol >> (8 * 2)) & 0xff; 53 | int lheax = (ivol >> 8) & 0xff; 54 | int lleax = ivol & 0xff; 55 | double dbl_1 = 1.0; 56 | double dbl_2 = 2.0; 57 | double dbl_128 = 128.0; 58 | 59 | int dwEcx = logpoint * 2 - 0x7f; 60 | int dwEdx = logpoint * 2 - 0x86; 61 | int dwEsi = logpoint * 2 - 0x8e; 62 | int dwEax = logpoint * 2 - 0x96; 63 | 64 | int tmpEax; 65 | if (dwEcx < 0) { 66 | tmpEax = -dwEcx; 67 | } else { 68 | tmpEax = dwEcx; 69 | } 70 | 71 | double dbl_xmm6 = 0.0; 72 | dbl_xmm6 = Math.pow(2.0, tmpEax); 73 | if (dwEcx < 0) { 74 | dbl_xmm6 = 1.0 / dbl_xmm6; 75 | } 76 | 77 | double dbl_xmm4 = 0; 78 | double tmpdbl_xmm3; 79 | double tmpdbl_xmm1; 80 | if (hleax > 0x80) { 81 | tmpdbl_xmm3 = 0.0; 82 | tmpdbl_xmm1 = 0.0; 83 | int dwtmpeax = dwEdx + 1; 84 | tmpdbl_xmm3 = Math.pow(2.0, dwtmpeax); 85 | double dbl_xmm0 = Math.pow(2.0, dwEdx) * 128.0; 86 | dbl_xmm0 += (hleax & 0x7f) * tmpdbl_xmm3; 87 | dbl_xmm4 = dbl_xmm0; 88 | } else { 89 | double dbl_xmm0 = 0.0; 90 | if (dwEdx >= 0) { 91 | dbl_xmm0 = Math.pow(2.0, dwEdx) * hleax; 92 | } else { 93 | dbl_xmm0 = (1 / Math.pow(2.0, dwEdx)) * hleax; 94 | dbl_xmm4 = dbl_xmm0; 95 | } 96 | 97 | } 98 | 99 | 100 | double dbl_xmm3 = Math.pow(2.0, dwEsi) * lheax; 101 | double dbl_xmm1 = Math.pow(2.0, dwEax) * lleax; 102 | if ((hleax & 0x80) != 0) { 103 | dbl_xmm3 *= 2.0; 104 | dbl_xmm1 *= 2.0; 105 | } 106 | 107 | 108 | double dbl_ret = dbl_xmm6 + dbl_xmm4 + dbl_xmm3 + dbl_xmm1; 109 | return dbl_ret; 110 | } 111 | 112 | @Override 113 | protected void doOutput(DataOutputStream outputStream) throws IOException { 114 | 115 | outputStream.writeShort(0x10c); 116 | outputStream.writeInt(0x01016408); 117 | outputStream.writeShort(0x1c); 118 | outputStream.writeShort(0x1c); 119 | outputStream.writeShort(0x052d); 120 | 121 | outputStream.writeShort(market); 122 | outputStream.writeAscii(code); 123 | outputStream.writeShort(category); 124 | outputStream.writeShort(1); 125 | outputStream.writeShort(start); 126 | outputStream.writeShort(count); 127 | 128 | outputStream.writeInt(0); 129 | outputStream.writeInt(0); 130 | outputStream.writeShort(0); 131 | 132 | } 133 | 134 | String getDate(TdxInputStream inputStream) throws IOException { 135 | int year; 136 | int month; 137 | int hour; 138 | int minute; 139 | int day; 140 | if (category < 4 || category == 7 || category == 8) { 141 | int zipday = inputStream.readShort(); 142 | int tminutes = inputStream.readShort(); 143 | year = (zipday >> 11) + 2004; 144 | month = (int) ((zipday % 2048) / 100); 145 | day = (zipday % 2048) % 100; 146 | 147 | hour = (tminutes / 60); 148 | minute = tminutes % 60; 149 | return getDate(year, month, day, hour, minute); 150 | } else { 151 | int zipday = inputStream.readInt(); 152 | year = (zipday / 10000); 153 | month = ((zipday % 10000) / 100); 154 | day = zipday % 100; 155 | return getDate(year, month, day); 156 | } 157 | 158 | 159 | } 160 | 161 | @Override 162 | protected Quote parseItem(TdxInputStream stream) throws IOException { 163 | String date = getDate(stream); 164 | double price_open_diff = stream.readPrice(); 165 | double price_close_diff = stream.readPrice(); 166 | double price_high_diff = stream.readPrice(); 167 | double price_low_diff = stream.readPrice(); 168 | 169 | int vol_row = stream.readInt(); 170 | double vol = getVolumn(vol_row); 171 | int dbvol_row = stream.readInt(); 172 | double amt = getVolumn(dbvol_row); 173 | 174 | double open = getPrice(price_open_diff, pre_diff_base); 175 | price_open_diff = price_open_diff + pre_diff_base; 176 | 177 | double close = getPrice(price_open_diff, price_close_diff); 178 | double high = getPrice(price_open_diff, price_high_diff); 179 | double low = getPrice(price_open_diff, price_low_diff); 180 | 181 | pre_diff_base = price_open_diff + price_close_diff; 182 | 183 | Quote quote = createQuote(); 184 | quote.setDate(date); 185 | quote.setClose(close); 186 | quote.setOpen(open); 187 | quote.setHigh(high); 188 | quote.setLow(low); 189 | quote.setVol((int) vol); 190 | quote.setAmt(amt); 191 | 192 | return quote; 193 | } 194 | 195 | protected Quote createQuote() { 196 | return new Quote(); 197 | } 198 | 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/GetStockCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.zoomdev.stock.tdx.Market; 4 | import org.zoomdev.stock.tdx.StockInfo; 5 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 6 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 7 | import org.zoomdev.stock.tdx.utils.HexUtils; 8 | 9 | import java.io.IOException; 10 | 11 | public class GetStockCommand extends ListCommand { 12 | Market market; 13 | int start; 14 | 15 | public GetStockCommand(Market market, int start) { 16 | this.market = market; 17 | this.start = start; 18 | } 19 | 20 | @Override 21 | protected void doOutput(DataOutputStream outputStream) throws IOException { 22 | outputStream.write(HexUtils.decodeHex("0c0118640101060006005004")); 23 | outputStream.writeShort(market.ordinal()); 24 | outputStream.writeShort(start); 25 | } 26 | 27 | 28 | @Override 29 | protected StockInfo parseItem(TdxInputStream inputStream) throws IOException { 30 | //6sH8s4sBI4s 31 | String code = inputStream.readUtf8String(6); 32 | int volunit = inputStream.readShort(); 33 | String name = inputStream.readGbkString(8); 34 | inputStream.skip(4); 35 | int decimal_point = inputStream.readByte(); 36 | int pre_close_raw = inputStream.readInt(); 37 | inputStream.skip(4); 38 | double pre_close = GetQuotesCommand.getVolumn(pre_close_raw); 39 | StockInfo stockInfo = new StockInfo(); 40 | stockInfo.setCode(code); 41 | stockInfo.setMarket(market); 42 | 43 | stockInfo.setDecimalPoint(decimal_point); 44 | stockInfo.setPrice(pre_close); 45 | stockInfo.setName(name); 46 | return stockInfo; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/GetTimePriceCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | 4 | import org.zoomdev.stock.tdx.Market; 5 | import org.zoomdev.stock.tdx.TimePrice; 6 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 7 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 8 | 9 | import java.io.IOException; 10 | 11 | public class GetTimePriceCommand extends ListCommand { 12 | 13 | protected String code; 14 | protected Market market; 15 | 16 | private double lastPrice = 0; 17 | 18 | public GetTimePriceCommand(Market market, String code) { 19 | this.market = market; 20 | this.code = code; 21 | } 22 | 23 | @Override 24 | protected TimePrice parseItem(TdxInputStream inputStream) throws IOException { 25 | double priceRaw = inputStream.readPrice(); 26 | inputStream.readPrice(); 27 | double vol = inputStream.readPrice(); 28 | lastPrice += priceRaw; 29 | 30 | TimePrice price = new TimePrice(); 31 | price.setVol((int) vol); 32 | price.setClose(lastPrice / 100); 33 | return price; 34 | } 35 | 36 | @Override 37 | protected void doOutput(DataOutputStream outputStream) throws IOException { 38 | outputStream.writeHexString("0c1b080001010e000e001d05"); 39 | outputStream.writeShort(market.ordinal()); 40 | outputStream.writeAscii(code); 41 | outputStream.writeInt(0); 42 | } 43 | 44 | @Override 45 | protected void skip(TdxInputStream inputStream) { 46 | inputStream.skip(2); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/GroupCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.zoomdev.stock.tdx.TdxCommand; 4 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 5 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 6 | 7 | import java.io.IOException; 8 | 9 | public class GroupCommand implements TdxCommand { 10 | 11 | private final TdxCommand[] commands; 12 | 13 | public GroupCommand(TdxCommand... commands) { 14 | this.commands = commands; 15 | } 16 | 17 | @Override 18 | public Object process(DataOutputStream outputStream, TdxInputStream inputStream) throws IOException { 19 | for (TdxCommand c : commands) { 20 | c.process(outputStream, inputStream); 21 | } 22 | return null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/ListCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | 4 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 5 | 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public abstract class ListCommand extends BaseCommand> { 11 | 12 | 13 | @Override 14 | protected List doInput(TdxInputStream inputStream) throws IOException { 15 | int count = inputStream.readShort(); 16 | 17 | List list = new ArrayList(count); 18 | for (int i = 0; i < count; ++i) { 19 | list.add(parseItem(inputStream)); 20 | } 21 | return list; 22 | } 23 | 24 | protected void skip(TdxInputStream inputStream) { 25 | 26 | } 27 | 28 | protected abstract T parseItem(TdxInputStream inputStream) throws IOException; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/LoginCommand.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.zoomdev.stock.tdx.impl.TdxInputStream; 6 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 7 | import org.zoomdev.stock.tdx.utils.HexUtils; 8 | 9 | import java.io.IOException; 10 | 11 | public class LoginCommand extends GroupCommand { 12 | private static final Log log = LogFactory.getLog(LoginCommand.class); 13 | 14 | public LoginCommand() { 15 | super(new Cmd1(), new Cmd2(), new Cmd3()); 16 | } 17 | 18 | static class Cmd1 extends BaseCommand { 19 | 20 | @Override 21 | protected void doOutput(DataOutputStream out) throws IOException { 22 | out.write(HexUtils.decodeHex("0c0218930001030003000d0001")); 23 | } 24 | 25 | @Override 26 | protected Object doInput(TdxInputStream inputStream) throws IOException { 27 | return null; 28 | } 29 | } 30 | 31 | static class Cmd2 extends BaseCommand { 32 | 33 | @Override 34 | protected void doOutput(DataOutputStream out) throws IOException { 35 | out.write(HexUtils.decodeHex("0c0218940001030003000d0002")); 36 | } 37 | 38 | @Override 39 | protected Object doInput(TdxInputStream inputStream) throws IOException { 40 | 41 | return null; 42 | } 43 | } 44 | 45 | static class Cmd3 extends BaseCommand { 46 | 47 | @Override 48 | protected void doOutput(DataOutputStream out) throws IOException { 49 | out.write(HexUtils.decodeHex(("0c031899000120002000db0fd5d0c9ccd6a4a8af0000008fc22540130000d500c9ccbdf0d7ea00000002"))); 50 | } 51 | 52 | @Override 53 | protected Object doInput(TdxInputStream inputStream) throws IOException { 54 | log.info("通达信登录成功,准备发送指令"); 55 | return null; 56 | } 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/commands/RecordOutputStream.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.commands; 2 | 3 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | 9 | class RecordOutputStream extends DataOutputStream { 10 | 11 | 12 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 13 | 14 | public RecordOutputStream(OutputStream stream) { 15 | super(stream); 16 | 17 | } 18 | 19 | @Override 20 | public void write(int b) throws IOException { 21 | super.write(b); 22 | byteArrayOutputStream.write(b); 23 | } 24 | 25 | public byte[] toByteArray() { 26 | return byteArrayOutputStream.toByteArray(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/DefaultIpRecord.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | import java.io.*; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | class DefaultIpRecord implements IpRecord { 8 | 9 | 10 | private File getFile() { 11 | String folder = System.getProperty("java.io.tmpdir"); 12 | File recordFild = new File(folder, "tdx-ip-record.txt"); 13 | return recordFild; 14 | } 15 | 16 | 17 | @Override 18 | public IpInfo[] load(IpInfo[] infos) { 19 | File file = getFile(); 20 | BufferedReader reader = null; 21 | try { 22 | Map map = new HashMap(); 23 | reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); 24 | String line = null; 25 | while ((line = reader.readLine()) != null) { 26 | String[] parts = line.split(" "); 27 | if (parts.length == 2) { 28 | map.put(parts[0], Integer.parseInt(parts[1])); 29 | } 30 | } 31 | 32 | for (IpInfo info : infos) { 33 | Integer count = map.get(String.format("%s:%d", info.host, info.port)); 34 | if (count != null) { 35 | info.successCount = count; 36 | } 37 | } 38 | 39 | 40 | } catch (IOException e) { 41 | } finally { 42 | TdxUtils.close(reader); 43 | } 44 | return infos; 45 | } 46 | 47 | @Override 48 | public void save(IpInfo[] infos) { 49 | File file = getFile(); 50 | BufferedWriter writer = null; 51 | try { 52 | writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); 53 | 54 | for (IpInfo info : infos) { 55 | 56 | writer.write(String.format("%s:%d %d", info.host, info.port, info.successCount)); 57 | writer.newLine(); 58 | } 59 | writer.flush(); 60 | } catch (FileNotFoundException e) { 61 | 62 | } catch (IOException e) { 63 | 64 | } finally { 65 | TdxUtils.close(writer); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/IpInfo.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | class IpInfo { 4 | 5 | 6 | String host; 7 | int port; 8 | String name; 9 | //从存储中加载失败历史数据和成功历史数据 10 | int successCount; 11 | 12 | public IpInfo(String host, int port, String name) { 13 | this.host = host; 14 | this.port = port; 15 | this.name = name; 16 | } 17 | 18 | public IpInfo(String host, int port) { 19 | this.host = host; 20 | this.port = port; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/IpRecord.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | interface IpRecord { 4 | //取得host连接成功数量 5 | IpInfo[] load(IpInfo[] infos); 6 | 7 | //将host连接成功数量保存起来,下次可以用此判断最佳连接路线 8 | void save(IpInfo[] infos); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/Ips.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | import java.util.Arrays; 4 | import java.util.Comparator; 5 | 6 | class Ips { 7 | 8 | 9 | private static final IpInfo[] IP_INFOS = new IpInfo[]{ 10 | new IpInfo("106.120.74.86", 7711, "北京行情主站1"), 11 | new IpInfo("113.105.73.88", 7709, "深圳行情主站"), 12 | new IpInfo("113.105.73.88", 7711, "深圳行情主站"), 13 | new IpInfo("114.80.80.222", 7711, "上海行情主站"), 14 | new IpInfo("117.184.140.156", 7711, "移动行情主站"), 15 | new IpInfo("119.147.171.206", 443, "广州行情主站"), 16 | new IpInfo("119.147.171.206", 80, "广州行情主站"), 17 | new IpInfo("218.108.50.178", 7711, "杭州行情主站"), 18 | new IpInfo("221.194.181.176", 7711, "北京行情主站2"), 19 | new IpInfo("106.120.74.86", 7709), 20 | new IpInfo("112.95.140.74", 7709), 21 | new IpInfo("112.95.140.92", 7709), 22 | new IpInfo("112.95.140.93", 7709), 23 | new IpInfo("113.05.73.88", 7709), 24 | new IpInfo("114.67.61.70", 7709), 25 | new IpInfo("114.80.149.19", 7709), 26 | new IpInfo("114.80.149.22", 7709), 27 | new IpInfo("114.80.149.84", 7709), 28 | new IpInfo("114.80.80.222", 7709), 29 | new IpInfo("115.238.56.198", 7709), 30 | new IpInfo("115.238.90.165", 7709), 31 | new IpInfo("117.184.140.156", 7709), 32 | new IpInfo("119.147.164.60", 7709), 33 | new IpInfo("119.147.171.206", 7709), 34 | new IpInfo("119.29.51.30", 7709), 35 | new IpInfo("121.14.104.70", 7709), 36 | new IpInfo("121.14.104.72", 7709), 37 | new IpInfo("121.14.110.194", 7709), 38 | new IpInfo("121.14.2.7", 7709), 39 | new IpInfo("123.125.108.23", 7709), 40 | new IpInfo("123.125.108.24", 7709), 41 | new IpInfo("124.160.88.183", 7709), 42 | new IpInfo("180.153.18.17", 7709), 43 | new IpInfo("180.153.18.170", 7709), 44 | new IpInfo("180.153.18.171", 7709), 45 | new IpInfo("180.153.39.51", 7709), 46 | new IpInfo("218.108.47.69", 7709), 47 | new IpInfo("218.108.50.178", 7709), 48 | new IpInfo("218.108.98.244", 7709), 49 | new IpInfo("218.75.126.9", 7709), 50 | new IpInfo("218.9.148.108", 7709), 51 | new IpInfo("221.194.181.176", 7709), 52 | new IpInfo("59.173.18.69", 7709), 53 | new IpInfo("60.12.136.250", 7709), 54 | new IpInfo("60.191.117.167", 7709), 55 | new IpInfo("60.28.29.69", 7709), 56 | new IpInfo("61.135.142.73", 7709), 57 | new IpInfo("61.135.142.88", 7709), 58 | new IpInfo("61.152.107.168", 7721), 59 | new IpInfo("61.152.249.56", 7709), 60 | new IpInfo("61.153.144.179", 7709), 61 | new IpInfo("61.153.209.138", 7709), 62 | new IpInfo("61.153.209.139", 7709), 63 | new IpInfo("hq.cjis.cn", 7709), 64 | new IpInfo("hq1.daton.com.cn", 7709), 65 | new IpInfo("jstdx.gtjas.com", 7709), 66 | new IpInfo("shtdx.gtjas.com", 7709), 67 | new IpInfo("sztdx.gtjas.com", 7709), 68 | new IpInfo("113.105.142.162", 7721), 69 | new IpInfo("23.129.245.199", 7721) 70 | }; 71 | 72 | public static IpInfo[] getIpInfos(IpRecord record) { 73 | 74 | IpInfo[] infos = new IpInfo[IP_INFOS.length]; 75 | for (int i = 0; i < infos.length; ++i) { 76 | infos[i] = IP_INFOS[i]; 77 | } 78 | record.load(infos); 79 | Arrays.sort(infos, new Comparator() { 80 | @Override 81 | public int compare(IpInfo o1, IpInfo o2) { 82 | if (o1.successCount > o2.successCount) { 83 | return -1; 84 | } 85 | if (o1.successCount < o2.successCount) { 86 | return 1; 87 | } 88 | return 0; 89 | } 90 | }); 91 | 92 | return infos; 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/ServiceThread.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | 7 | /** 8 | * 服务线程 9 | * 10 | * @author Randy 11 | */ 12 | abstract class ServiceThread extends Thread { 13 | 14 | 15 | private static final Log log = LogFactory.getLog(ServiceThread.class); 16 | 17 | /** 18 | * 是否正在运行 19 | */ 20 | protected volatile boolean running; 21 | 22 | 23 | public ServiceThread() { 24 | this.setDaemon(true); 25 | } 26 | 27 | 28 | public boolean isRunning() { 29 | return running; 30 | } 31 | 32 | /** 33 | * 启动 34 | */ 35 | public synchronized void start() { 36 | if (running) return; 37 | running = true; 38 | super.start(); 39 | } 40 | 41 | 42 | /** 43 | * 停止 44 | */ 45 | public synchronized void shutdown() { 46 | if (!running) return; 47 | running = false; 48 | interrupt(); 49 | 50 | } 51 | 52 | protected abstract boolean repetitionRun(); 53 | 54 | 55 | protected void init() { 56 | 57 | } 58 | 59 | @Override 60 | public void run() { 61 | while (running) { 62 | try { 63 | if (!repetitionRun()) break; 64 | } catch (Throwable e) { 65 | log.warn("Service exception:", e); 66 | } 67 | } 68 | 69 | log.info("ServiceThread has exit!"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/TdxClientImpl.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.zoomdev.stock.Quote; 6 | import org.zoomdev.stock.tdx.*; 7 | import org.zoomdev.stock.tdx.commands.*; 8 | import org.zoomdev.stock.tdx.reader.TdxBlockReader; 9 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 10 | import org.zoomdev.stock.tdx.utils.HexUtils; 11 | 12 | import java.io.*; 13 | import java.net.InetSocketAddress; 14 | import java.net.Socket; 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.List; 18 | 19 | 20 | /** 21 | * 非线程安全,如要实现多线程,则需要在每一个线程中实例化一个TxdClient 22 | */ 23 | public class TdxClientImpl implements TdxClient { 24 | 25 | 26 | public static final int DEFAULT_SO_TIMEOUT = 10000; 27 | static final int CHUNK_SIZE = 0x7530; 28 | private static final int DEFAULT_CONNECT_TIMEOUT = 500; 29 | private static final Log log = LogFactory.getLog(TdxClient.class); 30 | Socket socket; 31 | TdxInputStream inputStream; 32 | private DataOutputStream outputStream; 33 | private InetSocketAddress address; 34 | private int soTimeout = DEFAULT_SO_TIMEOUT; 35 | private int connectTimeout = DEFAULT_CONNECT_TIMEOUT; 36 | private String tdxRootDir; 37 | 38 | public String getTdxRootDir() { 39 | return tdxRootDir; 40 | } 41 | 42 | public void setTdxRootDir(String tdxRootDir) { 43 | this.tdxRootDir = tdxRootDir; 44 | } 45 | 46 | public int getSoTimeout() { 47 | return soTimeout; 48 | } 49 | 50 | public void setSoTimeout(int soTimeout) { 51 | this.soTimeout = soTimeout; 52 | } 53 | 54 | public int getConnectTimeout() { 55 | return connectTimeout; 56 | } 57 | 58 | public void setConnectTimeout(int connectTimeout) { 59 | this.connectTimeout = connectTimeout; 60 | } 61 | 62 | public InetSocketAddress getSocketAddress() { 63 | return this.address; 64 | } 65 | 66 | public void setSocketAddress(InetSocketAddress address) { 67 | this.address = address; 68 | } 69 | 70 | public void connect() throws IOException { 71 | socket = new Socket(); 72 | socket.setSoTimeout(soTimeout); 73 | socket.connect(address, connectTimeout); 74 | InputStream inputStream = socket.getInputStream(); 75 | OutputStream out = socket.getOutputStream(); 76 | TdxInputStream txdInput = new TdxInputStream(new BufferedInputStream(inputStream)); 77 | DataOutputStream txdOutput = new DataOutputStream(new BufferedOutputStream(out)); 78 | this.inputStream = txdInput; 79 | this.outputStream = txdOutput; 80 | 81 | new LoginCommand().process(this.outputStream, this.inputStream); 82 | } 83 | 84 | public boolean isConnected() { 85 | if (socket == null) { 86 | return false; 87 | } 88 | return socket.isConnected(); 89 | } 90 | 91 | public void close() { 92 | if (socket != null) { 93 | try { 94 | socket.close(); 95 | } catch (Throwable e) { 96 | 97 | } 98 | } 99 | } 100 | 101 | public List getIndexQuotes( 102 | Category category, 103 | Market market, 104 | String code, 105 | int start, 106 | int count 107 | ) throws IOException { 108 | 109 | GetIndexQuotesCommand cmd = new GetIndexQuotesCommand(category.ordinal(), market.ordinal(), code, start, count); 110 | return cmd.process(this.outputStream, this.inputStream); 111 | } 112 | 113 | public List getQuotes( 114 | Category category, 115 | Market market, 116 | String code, 117 | int start, 118 | int count 119 | 120 | 121 | ) throws IOException { 122 | GetQuotesCommand cmd = new GetQuotesCommand(category.ordinal(), market.ordinal(), code, start, count); 123 | return cmd.process(this.outputStream, this.inputStream); 124 | } 125 | 126 | @Override 127 | public List getTimePrice(Market market, String code) throws IOException { 128 | GetTimePriceCommand cmd = new GetTimePriceCommand(market, code); 129 | return cmd.process(this.outputStream, this.inputStream); 130 | } 131 | 132 | @Override 133 | public List getHistoryTimePrice(Market market, String code, String date) throws IOException { 134 | GetHistoryTimePriceCommand cmd = new GetHistoryTimePriceCommand(market, code, date); 135 | return cmd.process(this.outputStream, this.inputStream); 136 | } 137 | 138 | public int getCount(Market market) throws IOException { 139 | outputStream.writeHexString("0c0c186c0001080008004e04"); 140 | outputStream.writeShort(market.ordinal()); 141 | outputStream.writeHexString("75c73301"); 142 | 143 | outputStream.flush(); 144 | 145 | inputStream.readPack(false); 146 | return inputStream.readShort(); 147 | } 148 | 149 | public List getStockList(Market market, int start) throws IOException { 150 | GetStockCommand cmd = new GetStockCommand(market, start); 151 | return cmd.process(this.outputStream, this.inputStream); 152 | } 153 | 154 | private void getStockList(Market market, List result) throws IOException { 155 | int start = 0; 156 | while (true) { 157 | List list = getStockList(market, start); 158 | result.addAll(list); 159 | if (list.size() < 1000) { 160 | break; 161 | } 162 | start += 1000; 163 | } 164 | } 165 | 166 | @Override 167 | public List getStockList() throws IOException { 168 | List result = new ArrayList(); 169 | getStockList(Market.sh, result); 170 | getStockList(Market.sz, result); 171 | return result; 172 | } 173 | 174 | private BlockInfoMeta getBlockInfoMeta(String type) throws IOException { 175 | outputStream.write(HexUtils.decodeHex("0C39186900012A002A00C502")); 176 | outputStream.writeUtf8String(type, 0x2a - 2); 177 | outputStream.flush(); 178 | 179 | inputStream.readPack(false); 180 | 181 | //I1s32s1s 182 | int size = inputStream.readInt(); 183 | inputStream.skip(1); 184 | String hash = inputStream.readUtf8String(32); 185 | inputStream.skip(1); 186 | 187 | return new BlockInfoMeta(size, hash); 188 | 189 | } 190 | 191 | public byte[] getRawBlockInfo(String type, int start, int size) throws IOException { 192 | 193 | 194 | outputStream.writeHexString("0c37186a00016e006e00b906"); 195 | outputStream.writeInt(start); 196 | outputStream.writeInt(size); 197 | outputStream.writeUtf8String(type, 0x6e - 10); 198 | outputStream.flush(); 199 | 200 | inputStream.readPack(false); 201 | byte[] bytes = inputStream.toByteArray(); 202 | //取出后面4个字节的 203 | return bytes; 204 | 205 | } 206 | 207 | @Override 208 | public Collection getBlockInfo(BlockType type) throws IOException { 209 | if (type == BlockType.Concept) { 210 | return getBlockInfoByList(type); 211 | } else if (type == BlockType.Style) { 212 | return getBlockInfoByList(type); 213 | } else if (type == BlockType.Index) { 214 | return getBlockInfoByList(type); 215 | } 216 | //此时可以加载对应的信息 217 | if (type == BlockType.TdxIndustry) { 218 | if (tdxRootDir == null) { 219 | log.warn("没有设置通达信根目录,不能获取全部信息"); 220 | } 221 | byte[] stockBytes = downFile("tdxhy.cfg"); 222 | Collection stocks = TdxBlockReader.getBlockStockInfos(tdxRootDir, new ByteArrayInputStream(stockBytes), type); 223 | return stocks; 224 | } 225 | throw new RuntimeException("不支持的type类型" + type); 226 | } 227 | 228 | private Collection getBlockInfoByList(BlockType type) throws IOException { 229 | List list = getStockList(); 230 | if (type == BlockType.Concept) { 231 | return TdxBlockReader.fillCode(getBlockInfo(BlockFileType.BLOCK_GN), list,type); 232 | } else if (type == BlockType.Style) { 233 | return TdxBlockReader.fillCode(getBlockInfo(BlockFileType.BLOCK_FG), list,type); 234 | } else if (type == BlockType.Index) { 235 | return TdxBlockReader.fillCode(getBlockInfo(BlockFileType.BLOCK_ZS), list,type); 236 | } else { 237 | throw new RuntimeException("不支持的type类型" + type); 238 | } 239 | } 240 | 241 | private Collection getBlockInfoByFile(BlockType type) throws IOException { 242 | 243 | if (type == BlockType.Concept) { 244 | return TdxBlockReader.getBlockStockInfos(tdxRootDir, getBlockInfo(BlockFileType.BLOCK_GN), type); 245 | } else if (type == BlockType.Style) { 246 | return TdxBlockReader.getBlockStockInfos(tdxRootDir, getBlockInfo(BlockFileType.BLOCK_FG), type); 247 | } else if (type == BlockType.Index) { 248 | return TdxBlockReader.getBlockStockInfos(tdxRootDir, getBlockInfo(BlockFileType.BLOCK_ZS), type); 249 | } else { 250 | throw new RuntimeException("不支持的type类型" + type); 251 | } 252 | } 253 | 254 | 255 | public List getBlockInfo(BlockFileType type) throws IOException { 256 | byte[] bytes = downFile(type.getName()); 257 | return TdxBlockReader.fromDatFile(bytes, type); 258 | } 259 | 260 | 261 | public byte[] downFile(String name) throws IOException { 262 | BlockInfoMeta meta = getBlockInfoMeta(name); 263 | 264 | 265 | int chuncks = meta.size / CHUNK_SIZE; 266 | if (meta.size % CHUNK_SIZE != 0) { 267 | chuncks++; 268 | } 269 | 270 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 271 | for (int seg = 0; seg < chuncks; ++seg) { 272 | int start = seg * CHUNK_SIZE; 273 | byte[] contents = getRawBlockInfo(name, start, meta.size); 274 | out.write(contents, 4, contents.length - 4); 275 | 276 | } 277 | return out.toByteArray(); 278 | } 279 | 280 | 281 | class BlockInfoMeta { 282 | int size; 283 | String hash; 284 | 285 | public BlockInfoMeta(int size, String hash) { 286 | this.size = size; 287 | this.hash = hash; 288 | } 289 | } 290 | 291 | } 292 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/TdxClientServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.zoomdev.stock.Quote; 6 | import org.zoomdev.stock.tdx.*; 7 | 8 | import java.io.IOException; 9 | import java.net.InetSocketAddress; 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.concurrent.*; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | public class TdxClientServiceImpl implements TdxClientService { 17 | 18 | public static final Log log = LogFactory.getLog(TdxClientServiceImpl.class); 19 | 20 | public static final int DEFAULT_RECONNECT_TIMEOUT = 1000; 21 | 22 | private final int threadCount; 23 | private final BlockingQueue queue = new LinkedBlockingDeque(); 24 | private IpInfo[] ipInfos; 25 | private ServiceThread[] serviceThreads; 26 | private IpRecord ipRecord = new DefaultIpRecord(); 27 | private AtomicInteger connectedCounter = new AtomicInteger(0); 28 | 29 | private int reconnectTimeout = DEFAULT_RECONNECT_TIMEOUT; 30 | private List connectedAddress = new ArrayList(); 31 | private String tdxRootDir; 32 | 33 | public TdxClientServiceImpl() { 34 | this(1); 35 | } 36 | 37 | public TdxClientServiceImpl(int threadCount) { 38 | this.threadCount = threadCount; 39 | } 40 | 41 | public String getTdxRootDir() { 42 | return tdxRootDir; 43 | } 44 | 45 | public void setTdxRootDir(String tdxRootDir) { 46 | this.tdxRootDir = tdxRootDir; 47 | } 48 | 49 | public IpRecord getIpRecord() { 50 | return ipRecord; 51 | } 52 | 53 | public void setIpRecord(IpRecord ipRecord) { 54 | this.ipRecord = ipRecord; 55 | } 56 | 57 | public synchronized void saveIpInfos() { 58 | ipRecord.save(ipInfos); 59 | } 60 | 61 | protected void autoSelectHost(TdxClientImpl client) throws IOException { 62 | IpInfo[] infos = this.ipInfos; 63 | for (int i = 0; i < infos.length; ++i) { 64 | IpInfo info = infos[i]; 65 | InetSocketAddress address = new InetSocketAddress(info.host, info.port); 66 | 67 | 68 | try { 69 | synchronized (connectedAddress) { 70 | boolean found = false; 71 | for (InetSocketAddress addr : connectedAddress) { 72 | if (addr.equals(address)) { 73 | found = true; 74 | break; 75 | } 76 | } 77 | if (found) { 78 | continue; 79 | } 80 | 81 | log.info("Try to connect to host:" + info.host); 82 | client.setSocketAddress(address); 83 | client.connect(); 84 | connectedAddress.add(client.getSocketAddress()); 85 | info.successCount++; 86 | } 87 | 88 | break; 89 | } catch (IOException e) { 90 | try { 91 | Thread.sleep(reconnectTimeout); 92 | } catch (InterruptedException ex) { 93 | return; 94 | } 95 | log.warn("Connect to host " + info.host + " fail ", e); 96 | if (info.successCount > 0) { 97 | info.successCount--; 98 | } 99 | continue; 100 | } 101 | } 102 | 103 | } 104 | 105 | private void createServiceThreads() { 106 | serviceThreads = new TxdServiceThread[threadCount]; 107 | for (int i = 0; i < threadCount; ++i) { 108 | serviceThreads[i] = new TxdServiceThread(); 109 | } 110 | 111 | 112 | } 113 | 114 | private void startSeviceThreads() { 115 | for (int i = 0; i < threadCount; ++i) { 116 | assert (serviceThreads[i] != null); 117 | serviceThreads[i].start(); 118 | } 119 | } 120 | 121 | private void stopServiceThreads() { 122 | for (int i = 0; i < threadCount; ++i) { 123 | assert (serviceThreads[i] != null); 124 | serviceThreads[i].shutdown(); 125 | } 126 | } 127 | 128 | public synchronized void start() { 129 | ipInfos = Ips.getIpInfos(ipRecord); 130 | createServiceThreads(); 131 | startSeviceThreads(); 132 | } 133 | 134 | public synchronized void stop() { 135 | stopServiceThreads(); 136 | 137 | } 138 | 139 | public Future> getQuotes( 140 | final Category category, 141 | final Market market, 142 | final String code, 143 | final int start, 144 | final int count 145 | 146 | 147 | ) { 148 | return submit(new Callable>() { 149 | @Override 150 | public List call() throws Exception { 151 | TdxClientImpl client = getClient(); 152 | return client.getQuotes(category, market, code, start, count); 153 | } 154 | }); 155 | } 156 | 157 | @Override 158 | public void setReconnectTimeout(int timeout) { 159 | reconnectTimeout = timeout; 160 | } 161 | 162 | private TdxClientImpl getClient() { 163 | TxdServiceThread thread = (TxdServiceThread) Thread.currentThread(); 164 | return thread.client; 165 | } 166 | 167 | protected Future submit(Callable callable) { 168 | FutureTask future = new FutureTask(callable); 169 | ; 170 | queue.add(future); 171 | return future; 172 | } 173 | 174 | public Future getCount(final Market market) { 175 | return submit(new Callable() { 176 | @Override 177 | public Integer call() throws Exception { 178 | TdxClientImpl client = getClient(); 179 | return client.getCount(market); 180 | } 181 | }); 182 | } 183 | 184 | public Future> getStockList(final Market market, final int start) { 185 | return submit(new Callable>() { 186 | @Override 187 | public List call() throws Exception { 188 | TdxClientImpl client = getClient(); 189 | return client.getStockList(market, start); 190 | } 191 | }); 192 | } 193 | 194 | @Override 195 | public Future> getBlockInfo(final BlockType type) { 196 | return submit(new Callable>() { 197 | @Override 198 | public Collection call() throws Exception { 199 | TdxClientImpl client = getClient(); 200 | return client.getBlockInfo(type); 201 | } 202 | }); 203 | } 204 | 205 | public Future> getBlockInfo(final BlockFileType type) { 206 | return submit(new Callable>() { 207 | @Override 208 | public List call() throws Exception { 209 | TdxClientImpl client = getClient(); 210 | return client.getBlockInfo(type); 211 | } 212 | }); 213 | } 214 | 215 | public Future> getIndexQuotes( 216 | final Category category, 217 | final Market market, 218 | final String code, 219 | final int start, 220 | final int count 221 | ) { 222 | return submit(new Callable>() { 223 | @Override 224 | public List call() throws Exception { 225 | TdxClientImpl client = getClient(); 226 | return client.getIndexQuotes(category, market, code, start, count); 227 | } 228 | }); 229 | } 230 | 231 | @Override 232 | public Future> getStockList() { 233 | return submit(new Callable>() { 234 | @Override 235 | public List call() throws Exception { 236 | TdxClientImpl client = getClient(); 237 | return client.getStockList(); 238 | } 239 | }); 240 | } 241 | 242 | class TxdServiceThread extends ServiceThread { 243 | final TdxClientImpl client; 244 | private boolean connected = false; 245 | private int count; 246 | 247 | 248 | public TxdServiceThread() { 249 | client = new TdxClientImpl(); 250 | client.setTdxRootDir(tdxRootDir); 251 | } 252 | 253 | /// 1分钟一次 254 | private void sendHeart() throws IOException { 255 | ++count; 256 | if (count >= 5 * 30) { 257 | count = 0; 258 | client.getCount(Market.sh); 259 | } 260 | } 261 | 262 | 263 | private void setConnected(boolean connected) { 264 | this.connected = connected; 265 | if (connected) { 266 | int connectCount = connectedCounter.incrementAndGet(); 267 | if (connectCount == threadCount) { 268 | saveIpInfos(); 269 | } 270 | } else { 271 | connectedCounter.decrementAndGet(); 272 | } 273 | } 274 | 275 | @Override 276 | protected boolean repetitionRun() { 277 | if (!connected) { 278 | try { 279 | connectToHost(); 280 | setConnected(true); 281 | } catch (IOException e) { 282 | //仍然等待下次连接的机会 283 | try { 284 | Thread.sleep(1000); 285 | return true; 286 | } catch (InterruptedException ex) { 287 | return false; 288 | } 289 | } 290 | } 291 | 292 | 293 | try { 294 | FutureTask task = queue.poll(200, TimeUnit.MILLISECONDS); 295 | try { 296 | if (task == null) { 297 | //检查心跳 298 | sendHeart(); 299 | return true; 300 | } 301 | task.run(); 302 | task.get(); 303 | return true; 304 | } catch (Exception e) { 305 | log.info("An error has happened, close the socket and reconnect", e); 306 | setConnected(false); 307 | client.close(); 308 | return true; 309 | 310 | } 311 | 312 | } catch (InterruptedException e) { 313 | return false; 314 | } 315 | 316 | } 317 | 318 | private void connectToHost() throws IOException { 319 | for (int j = 0; j < 3; ++j) { 320 | try { 321 | autoSelectHost(client); 322 | return; 323 | } catch (IOException ex) { 324 | log.warn("服务器连接失败", ex); 325 | } 326 | } 327 | throw new IOException("Connect to host failed"); 328 | } 329 | } 330 | 331 | 332 | } 333 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/TdxInputStream.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | import com.jcraft.jzlib.Inflater; 4 | import com.jcraft.jzlib.JZlib; 5 | import org.zoomdev.stock.tdx.utils.DataInputStream; 6 | import org.zoomdev.stock.tdx.utils.HexUtils; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | public class TdxInputStream extends DataInputStream { 12 | 13 | public static final byte[] EMPTY = new byte[0]; 14 | public static final int EOF = -1; 15 | private InputStream is; 16 | private byte[] header = new byte[0x10]; 17 | private int packSize; 18 | private int uncompressSize; 19 | 20 | public TdxInputStream(InputStream is) { 21 | this.is = is; 22 | } 23 | 24 | private int ensureRead(byte[] buffer, int offset, int length) throws IOException { 25 | int remaining = length; 26 | while (remaining > 0) { 27 | final int location = length - remaining; 28 | final int count = is.read(buffer, offset + location, remaining); 29 | if (EOF == count) { // EOF 30 | throw new IOException("Cannot read data"); 31 | } 32 | remaining -= count; 33 | } 34 | return length - remaining; 35 | } 36 | 37 | public void ensureRead(byte[] cache) throws IOException { 38 | 39 | int pos = 0; 40 | for (int i = 0; i < 10; ++i) { 41 | int readed = ensureRead(cache, pos, cache.length - pos); 42 | pos += readed; 43 | if (pos >= cache.length) { 44 | return; 45 | } 46 | } 47 | 48 | } 49 | 50 | public void readHeader() throws IOException { 51 | 52 | ensureRead(header); 53 | packSize = HexUtils.readShort(header, 12); 54 | uncompressSize = HexUtils.readShort(header, 14); 55 | } 56 | 57 | public boolean needsToInflate() { 58 | return packSize != uncompressSize; 59 | } 60 | 61 | 62 | public byte[] readBody(boolean skipInflate) throws IOException { 63 | byte[] data = new byte[packSize]; 64 | ensureRead(data); 65 | if (needsToInflate() && !skipInflate) { 66 | //后面的还有104个 67 | byte[] unzip = new byte[uncompressSize]; 68 | Inflater inflater = new Inflater(); 69 | inflater.setInput(data); 70 | inflater.setOutput(unzip); 71 | int err = inflater.init(); 72 | // CHECK_ERR(inflater, err, "inflateInit"); 73 | 74 | while (inflater.total_out < uncompressSize && 75 | inflater.total_in < packSize) { 76 | inflater.avail_in = inflater.avail_out = 1; /* force small buffers */ 77 | err = inflater.inflate(JZlib.Z_NO_FLUSH); 78 | if (err == JZlib.Z_STREAM_END) break; 79 | // CHECK_ERR(inflater, err, "inflate"); 80 | } 81 | 82 | setBuf(unzip); 83 | //System.out.println(Hex.encodeHexStr(unzip)); 84 | return unzip; 85 | } 86 | setBuf(data); 87 | return data; 88 | } 89 | 90 | 91 | public byte[] toByteArray() { 92 | return buf; 93 | } 94 | 95 | 96 | public byte[] readPack(boolean skipInflate) throws IOException { 97 | readHeader(); 98 | byte[] pack = readBody(skipInflate); 99 | return pack; 100 | } 101 | 102 | 103 | // 104 | public double readPrice() throws IOException { 105 | int pos_byte = 6; 106 | int bdata = readByte(); 107 | int intdata = bdata & 0x3f; 108 | boolean sign; 109 | if ((bdata & 0x40) != 0) { 110 | sign = true; 111 | } else { 112 | sign = false; 113 | } 114 | if ((bdata & 0x80) != 0) { 115 | while (true) { 116 | bdata = readByte(); 117 | intdata += (bdata & 0x7f) << pos_byte; 118 | pos_byte += 7; 119 | 120 | if ((bdata & 0x80) != 0) { 121 | continue; 122 | } 123 | break; 124 | } 125 | } 126 | 127 | if (sign) { 128 | intdata = -intdata; 129 | } 130 | return intdata; 131 | } 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/impl/TdxUtils.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.impl; 2 | 3 | import java.io.Closeable; 4 | 5 | public class TdxUtils { 6 | public static void close(Closeable closeable) { 7 | if (closeable != null) { 8 | try { 9 | closeable.close(); 10 | } catch (Throwable t) { 11 | 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/reader/TdxBlockReader.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.reader; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.zoomdev.stock.tdx.*; 6 | import org.zoomdev.stock.tdx.impl.TdxUtils; 7 | import org.zoomdev.stock.tdx.utils.DataInputStream; 8 | 9 | import java.io.*; 10 | import java.util.*; 11 | import java.util.stream.Collectors; 12 | 13 | public class TdxBlockReader { 14 | 15 | 16 | public static final Log log = LogFactory.getLog(TdxBlockReader.class); 17 | 18 | public static Map getMap( 19 | InputStream is, final BlockType blockType 20 | ) throws IOException { 21 | final Map map = new HashMap(); 22 | read(is, new Visitor() { 23 | @Override 24 | public void visit(String line) { 25 | String[] parts = line.split("\\|"); 26 | if (parts.length == 6) { 27 | String name = parts[0]; 28 | String code = parts[1]; 29 | int type = Integer.parseInt(parts[2]); 30 | String typeCode = parts[5]; 31 | if (type == blockType.getType()) { 32 | //行业 33 | //System.out.println(name); 34 | BlockStock blockStock = new BlockStock(); 35 | blockStock.setType(blockType); 36 | blockStock.setName(name); 37 | blockStock.setCode(code); 38 | if (blockType == BlockType.TdxIndustry) { 39 | blockStock.setLevel((typeCode.length() - 1) / 2); 40 | } 41 | map.put(typeCode, blockStock); 42 | } 43 | } 44 | } 45 | }); 46 | return map; 47 | } 48 | // File dir = new File(path,"T0002/hq_cache"); 49 | // //指数 50 | // File type = new File(dir,"tdxzs.cfg"); 51 | // new File(dir, "tdxhy.cfg") 52 | 53 | public static Map getMap( 54 | String path, BlockType blockType 55 | ) throws IOException { 56 | return getMap(new FileInputStream(new File(path, "T0002/hq_cache/tdxzs.cfg")), blockType); 57 | } 58 | 59 | public static Collection getBlockStockInfos( 60 | String path, InputStream stockMapStream, BlockType blockType 61 | ) throws IOException { 62 | final Map map = getMap(path, blockType); 63 | read(stockMapStream, new Visitor() { 64 | @Override 65 | public void visit(String line) { 66 | String[] parts = line.split("\\|"); 67 | if (parts.length == 5) { 68 | // int market = Integer.parseInt(parts[0]); 69 | String code = parts[1]; 70 | String type = parts[2]; 71 | addTo(map, type, code); 72 | } 73 | } 74 | }); 75 | 76 | return map.values(); 77 | } 78 | 79 | public static Collection getBlockStockInfos( 80 | String path, Collection stocks, BlockType blockType 81 | ) throws IOException { 82 | final Map map = getMap(path, blockType); 83 | fillCode(stocks, map); 84 | return stocks; 85 | } 86 | 87 | public static Collection fillCode(Collection stocks, 88 | List allStocks, 89 | BlockType type) { 90 | //必须88开头,否则可能有其他指数之类的 91 | Map map = new HashMap(); 92 | if(type == BlockType.Concept || type == BlockType.Style){ 93 | for (StockInfo info : allStocks) { 94 | if(info.getCode().startsWith("88")){ 95 | map.put(info.getName(), info.getCode()); 96 | } 97 | } 98 | }else if(type == BlockType.Index){ 99 | //指数要留下指数,0开头 100 | for (StockInfo info : allStocks) { 101 | if(info.getCode().startsWith("88") || (info.getCode().startsWith("0") && info.getMarket()== Market.sh 102 | || info.getCode().startsWith("399") 103 | )){ 104 | map.put(info.getName(), info.getCode()); 105 | } 106 | } 107 | 108 | } 109 | 110 | for (BlockStock stock : stocks) { 111 | String code = map.get(stock.getName()); 112 | if (code == null) { 113 | log.warn("没有找到[" + stock.getName() + "]对应的code"); 114 | continue; 115 | } 116 | stock.setCode(code); 117 | } 118 | return stocks; 119 | } 120 | 121 | public static void fillCode(Collection stocks, Map map) { 122 | for (BlockStock stock : stocks) { 123 | BlockStock stockWithCode = map.get(stock.getName()); 124 | if (stockWithCode == null) { 125 | log.warn("没有找到[" + stock.getName() + "]对应的板块配置"); 126 | continue; 127 | } 128 | stock.setCode(stockWithCode.getCode()); 129 | } 130 | } 131 | 132 | /** 133 | * 提供通达信目录 134 | * 先提供通达信行业分类再说 135 | * 136 | * @param path 137 | * @return 138 | */ 139 | public static Collection getTdxHy( 140 | String path 141 | ) throws IOException { 142 | File hy = new File(path, "T0002/hq_cache/tdxhy.cfg"); //个股和行业映射表 143 | return getBlockStockInfos(path, new FileInputStream(hy), BlockType.TdxIndustry); 144 | } 145 | 146 | 147 | private static void addTo(Map map, String type, String code) { 148 | BlockStock blockStock = map.get(type); 149 | if (blockStock == null) { 150 | return; 151 | } 152 | 153 | if (type.length() > 3) { 154 | addTo(map, type.substring(0, type.length() - 2), code); 155 | } 156 | 157 | List codes = blockStock.getCodes(); 158 | if (codes == null) { 159 | codes = new ArrayList(); 160 | blockStock.setCodes(codes); 161 | } 162 | codes.add(code); 163 | } 164 | 165 | private static void read(InputStream is, Visitor visitor) throws IOException { 166 | BufferedReader reader = null; 167 | try { 168 | reader = new BufferedReader( 169 | new InputStreamReader(is, "gbk") 170 | ); 171 | 172 | String line = null; 173 | while ((line = reader.readLine()) != null) { 174 | visitor.visit(line); 175 | } 176 | } finally { 177 | TdxUtils.close(reader); 178 | } 179 | 180 | } 181 | 182 | private static void read(File file, Visitor visitor) throws IOException { 183 | read(new FileInputStream(file), visitor); 184 | } 185 | 186 | /** 187 | * dat文件 188 | * 189 | * @param content 190 | * @return 191 | * @throws IOException 192 | */ 193 | public static List fromDatFile(byte[] content, BlockFileType type) throws IOException { 194 | DataInputStream inputStream = new DataInputStream(); 195 | inputStream.setBuf(content); 196 | inputStream.skip(384); 197 | int blockCount = inputStream.readShort(); 198 | List result = new ArrayList(blockCount); 199 | for (int i = 0; i < blockCount; ++i) { 200 | String blockName = inputStream.readGbkString(9); 201 | int stockCount = inputStream.readShort(); 202 | assert (stockCount < 10000); 203 | int blockType = inputStream.readShort(); 204 | int block_stock_begin = inputStream.getPos(); 205 | BlockStock blockStock = new BlockStock(); 206 | result.add(blockStock); 207 | blockStock.setName(blockName); 208 | blockStock.setLevel(blockType); 209 | if (type == BlockFileType.BLOCK_FG) { 210 | blockStock.setType(BlockType.Style); 211 | } else if (type == BlockFileType.BLOCK_GN) { 212 | blockStock.setType(BlockType.Concept); 213 | } else if (type == BlockFileType.BLOCK_ZS) { 214 | blockStock.setType(BlockType.Index); 215 | } 216 | List codes = new ArrayList(stockCount); 217 | blockStock.setCodes(codes); 218 | for (int code_index = 0; code_index < stockCount; ++code_index) { 219 | String code = inputStream.readUtf8String(7); 220 | codes.add(code); 221 | } 222 | inputStream.setPos(block_stock_begin + 2800); 223 | 224 | } 225 | 226 | return result; 227 | 228 | } 229 | 230 | 231 | interface Visitor { 232 | void visit(String line); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/reader/TdxQuoteReader.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.reader; 2 | 3 | import org.zoomdev.stock.Quote; 4 | import org.zoomdev.stock.tdx.utils.DataInputStream; 5 | import org.zoomdev.stock.tdx.utils.HexUtils; 6 | 7 | import java.io.IOException; 8 | import java.math.BigDecimal; 9 | import java.math.RoundingMode; 10 | 11 | 12 | /** 13 | * 通达信本地文件解析工具类 14 | */ 15 | public class TdxQuoteReader { 16 | 17 | /** 18 | * 日线数据取出之后 19 | * 20 | * @param bytes 21 | * @param start 22 | * @return 23 | */ 24 | public static Quote parseForDay(byte[] bytes, int start) { 25 | int time = HexUtils.readInt(bytes, start); 26 | 27 | double open = (double) HexUtils.readInt(bytes, start + 4) / 100; 28 | double high = (double) HexUtils.readInt(bytes, start + 8) / 100; 29 | double low = (double) HexUtils.readInt(bytes, start + 12) / 100; 30 | double close = (double) HexUtils.readInt(bytes, start + 16) / 100; 31 | double amt = (double) HexUtils.readInt(bytes, start + 20) / 100; 32 | int vol = HexUtils.readInt(bytes, start + 24); 33 | 34 | 35 | Quote quote = new Quote(); 36 | quote.setDate(String.format("%04d%02d%02d", time / 10000, time % 10000 / 100, time % 10000 % 100)); 37 | quote.setClose(close); 38 | quote.setOpen(open); 39 | quote.setLow(low); 40 | quote.setHigh(high); 41 | quote.setVol(vol); 42 | quote.setTor(0); 43 | quote.setAmt(amt); 44 | return quote; 45 | } 46 | 47 | public static Quote parseForDay(DataInputStream is) throws IOException { 48 | int time = is.readInt(); 49 | 50 | double open = (double) is.readInt() / 100; 51 | double high = (double) is.readInt() / 100; 52 | double low = (double) is.readInt() / 100; 53 | double close = (double) is.readInt() / 100; 54 | 55 | double amt = (double) is.readFloat(); 56 | int vol = is.readInt(); 57 | 58 | 59 | Quote quote = new Quote(); 60 | quote.setDate(String.format("%04d%02d%02d", time / 10000, time % 10000 / 100, time % 10000 % 100)); 61 | quote.setClose(close); 62 | quote.setOpen(open); 63 | quote.setLow(low); 64 | quote.setHigh(high); 65 | quote.setVol(vol); 66 | quote.setTor(0); 67 | quote.setAmt(amt); 68 | return quote; 69 | } 70 | 71 | 72 | public static Quote parseForMin(DataInputStream is) throws IOException { 73 | int time = is.readShort(); 74 | int year; 75 | int month; 76 | int day; 77 | 78 | if((0x8000 & time) == 0x8000){ 79 | time = time - 0x8000; 80 | year = 2004-(time /2048) ; 81 | month = time % 2048 / 100; 82 | day = time % 2048 % 100; 83 | }else{ 84 | year = (time /2048) + 2004; 85 | month = time % 2048 / 100; 86 | day = time % 2048 % 100; 87 | } 88 | 89 | 90 | int minute = is.readShort(); 91 | int hour = minute / 60; 92 | minute = minute % 60; 93 | String date = getDate(year, month, day, hour, minute); 94 | 95 | double open = is.readFloat(); 96 | double high = is.readFloat(); 97 | double low = is.readFloat(); 98 | double close = is.readFloat(); 99 | double amt = is.readFloat(); 100 | int vol = is.readInt(); 101 | 102 | 103 | Quote quote = new Quote(); 104 | quote.setDate(date); 105 | quote.setClose(close); 106 | quote.setOpen(open); 107 | quote.setLow(low); 108 | quote.setHigh(high); 109 | quote.setVol(vol); 110 | quote.setTor(0); 111 | quote.setAmt(amt); 112 | return quote; 113 | } 114 | 115 | /** 116 | * 1分钟数据加载出来之后,解析,每32个字节一个k线 117 | * 118 | * @param bytes 119 | * @param start 120 | * @return 121 | */ 122 | public static Quote parseForMin(byte[] bytes, int start) { 123 | String date = parseDate(bytes, start); 124 | double open = getFloat(bytes, start + 4); 125 | double high = getFloat(bytes, start + 8); 126 | double low = getFloat(bytes, start + 12); 127 | double close = getFloat(bytes, start + 16); 128 | double amt = getFloat(bytes, start + 20); 129 | int vol = HexUtils.readInt(bytes, start + 24); 130 | 131 | 132 | Quote quote = new Quote(); 133 | quote.setDate(date); 134 | quote.setClose(close); 135 | quote.setOpen(open); 136 | quote.setLow(low); 137 | quote.setHigh(high); 138 | quote.setVol(vol); 139 | quote.setTor(0); 140 | quote.setAmt(amt); 141 | return quote; 142 | } 143 | 144 | public static double getFloat(byte[] b, int index) { 145 | int l; 146 | l = b[index + 0]; 147 | l &= 0xff; 148 | l |= ((long) b[index + 1] << 8); 149 | l &= 0xffff; 150 | l |= ((long) b[index + 2] << 16); 151 | l &= 0xffffff; 152 | l |= ((long) b[index + 3] << 24); 153 | //保留两位小数 154 | float r = Float.intBitsToFloat(l); 155 | BigDecimal bg = new BigDecimal(r).setScale(2, RoundingMode.HALF_UP); 156 | return bg.doubleValue(); 157 | 158 | } 159 | 160 | static String getDate(int year, int month, int date, int hour, int minute) { 161 | return String.format("%04d%02d%02d%02d%02d", year, month, date, hour, minute); 162 | } 163 | 164 | public static String parseDate(byte[] bytes, int start) { 165 | int time = HexUtils.readShort(bytes, start); 166 | int year = (time / 2048) + 2004; 167 | int month = ((time % 2048) / 100); 168 | int day = ((time % 2048) % 100); 169 | 170 | 171 | int minute = HexUtils.readShort(bytes, start + 2); 172 | int hour = minute / 60; 173 | minute = minute % 60; 174 | return getDate(year, month, day, hour, minute); 175 | } 176 | 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/utils/DataInputStream.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.utils; 2 | 3 | import java.io.IOException; 4 | 5 | public class DataInputStream { 6 | 7 | protected byte[] buf; 8 | protected int pos; 9 | 10 | public DataInputStream() { 11 | 12 | } 13 | 14 | public void setBuf(byte[] buf) { 15 | this.buf = buf; 16 | this.pos = 0; 17 | } 18 | 19 | public int readByte() { 20 | return read(); 21 | } 22 | 23 | 24 | public int read() { 25 | return buf[pos++] & 0xff; 26 | } 27 | 28 | 29 | public int readInt() { 30 | int a0 = read(); 31 | int a1 = read(); 32 | int a2 = read(); 33 | int a3 = read(); 34 | 35 | return (a0 & 0xff) | ((a1 << 8) & 0xff00) | ((a2 << 16) & 0xff0000) | ((a3 << 24) & 0xff000000); 36 | } 37 | 38 | public String readUtf8String(int len) throws IOException { 39 | return readString(len, "utf-8"); 40 | } 41 | 42 | public String readGbkString(int len) throws IOException { 43 | return readString(len, "gbk"); 44 | } 45 | 46 | 47 | public String readString(int len, String encoding) throws IOException { 48 | byte[] bytes = new byte[len]; 49 | read(bytes); 50 | //去掉0 51 | int count = bytes.length; 52 | for (int i = bytes.length - 1; i >= 0; --i) { 53 | if (bytes[i] == 0) { 54 | --count; 55 | continue; 56 | } 57 | } 58 | return new String(bytes, 0, count, encoding); 59 | } 60 | 61 | public int read(byte b[]) throws IOException { 62 | return read(b, 0, b.length); 63 | } 64 | 65 | public int read(byte b[], int off, int len) throws IOException { 66 | if (b == null) { 67 | throw new NullPointerException(); 68 | } else if (off < 0 || len < 0 || len > b.length - off) { 69 | throw new IndexOutOfBoundsException(); 70 | } else if (len == 0) { 71 | return 0; 72 | } 73 | 74 | int c = read(); 75 | if (c == -1) { 76 | return -1; 77 | } 78 | b[off] = (byte) c; 79 | 80 | int i = 1; 81 | for (; i < len; i++) { 82 | c = read(); 83 | if (c == -1) { 84 | break; 85 | } 86 | b[off + i] = (byte) c; 87 | } 88 | return i; 89 | } 90 | 91 | //一定是unsigned short 92 | public int readShort() { 93 | 94 | int first = read(); 95 | int second = read(); 96 | 97 | return (first & 0xff) | ((second << 8) & 0xff00); 98 | } 99 | 100 | public float readFloat() { 101 | return Float.intBitsToFloat(readInt()); 102 | } 103 | 104 | 105 | public void reset() { 106 | pos = 0; 107 | } 108 | 109 | //不判断,一切由外面来处理 110 | public void skip(int n) { 111 | pos += n; 112 | } 113 | 114 | public int getPos() { 115 | return pos; 116 | } 117 | 118 | public void setPos(int pos) { 119 | this.pos = pos; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/utils/DataOutputStream.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.utils; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public class DataOutputStream extends OutputStream { 7 | 8 | private final OutputStream outputStream; 9 | 10 | public DataOutputStream(OutputStream outputStream) { 11 | this.outputStream = outputStream; 12 | } 13 | 14 | public void flush() throws IOException { 15 | this.outputStream.flush(); 16 | } 17 | 18 | public void writeShort(int i) throws IOException { 19 | write(i & 0xff); 20 | write((i >> 8) & 0xff); 21 | } 22 | 23 | public void writeFloat(float f) throws IOException { 24 | //writeFloat 25 | writeInt(Float.floatToIntBits(f)); 26 | } 27 | 28 | 29 | public void write(int i) throws IOException { 30 | outputStream.write(i); 31 | } 32 | 33 | public void writeInt(int i) throws IOException { 34 | write(i & 0xff); 35 | write((i >> 8) & 0xff); 36 | write((i >> 16) & 0xff); 37 | write((i >> 24) & 0xff); 38 | } 39 | 40 | 41 | public void writeAscii(String code) throws IOException { 42 | for (byte b : code.getBytes()) { 43 | write(b); 44 | } 45 | } 46 | 47 | public void writeUtf8String(String text, int len) throws IOException { 48 | byte[] bytes = text.getBytes("utf-8"); 49 | if (bytes.length > len) { 50 | throw new IllegalArgumentException("text is too long"); 51 | } 52 | 53 | write(bytes); 54 | for (int i = bytes.length; i < len; ++i) { 55 | write(0); 56 | } 57 | } 58 | 59 | public void writeHexString(String s) throws IOException { 60 | write(HexUtils.decodeHex(s)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/utils/HexUtils.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.utils; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public class HexUtils { 7 | private static final char[] DIGITS_LOWER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 8 | 9 | /** 10 | * @param data 11 | * @param start 12 | * @return 13 | */ 14 | public static int readShort(byte[] data, int start) { 15 | return (((data[start + 1] << 8) & 0xff00) | 16 | (data[start] & 0xff)); 17 | } 18 | 19 | public static int readInt(byte[] data, int start) { 20 | return ((data[start + 3] << 24) & 0xff000000) | 21 | ((data[start + 2] << 16) & 0xff0000) | 22 | ((data[start + 1] << 8) & 0xff00) | 23 | (data[start] & 0xff); 24 | } 25 | 26 | public static void writeShort(byte[] data, int start, int value) { 27 | data[start] = (byte) (value & 0xff); 28 | data[start + 1] = (byte) ((value & 0xff00) >> 8); 29 | } 30 | 31 | public static void writeInt(byte[] data, int start, int value) { 32 | 33 | } 34 | 35 | public static void writeShort(OutputStream stream, int value) { 36 | try { 37 | stream.write(value & 0xff); 38 | stream.write((value & 0xff00) >> 8); 39 | } catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | public static void writeInt(OutputStream stream, int value) { 45 | try { 46 | stream.write(value & 0xff); 47 | stream.write((value & 0xff00) >> 8); 48 | stream.write((value & 0xff0000) >> 16); 49 | stream.write((value & 0xff000000) >> 24); 50 | } catch (IOException e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | 55 | public static String encodeHexStr(byte[] data) { 56 | return new String(encodeHex(data, 0, data.length, DIGITS_LOWER)); 57 | } 58 | 59 | protected static char[] encodeHex(byte[] data, int start, int l, char[] toDigits) { 60 | char[] out = new char[l << 1]; 61 | int i = start; 62 | 63 | for (int j = 0; i < l; ++i) { 64 | out[j++] = toDigits[(240 & data[i]) >>> 4]; 65 | out[j++] = toDigits[15 & data[i]]; 66 | } 67 | 68 | return out; 69 | } 70 | 71 | public static byte[] decodeHex(String data) { 72 | return decodeHex(data.toCharArray()); 73 | } 74 | 75 | public static byte[] decodeHex(char[] data) { 76 | int len = data.length; 77 | if ((len & 1) != 0) { 78 | throw new RuntimeException("Odd number of characters."); 79 | } else { 80 | byte[] out = new byte[len >> 1]; 81 | int i = 0; 82 | 83 | for (int j = 0; j < len; ++i) { 84 | int f = toDigit(data[j], j) << 4; 85 | ++j; 86 | f |= toDigit(data[j], j); 87 | ++j; 88 | out[i] = (byte) (f & 255); 89 | } 90 | 91 | return out; 92 | } 93 | } 94 | 95 | protected static int toDigit(char ch, int index) { 96 | int digit = Character.digit(ch, 16); 97 | if (digit == -1) { 98 | throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); 99 | } else { 100 | return digit; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/zoomdev/stock/tdx/writer/TdxQuoteWriter.java: -------------------------------------------------------------------------------- 1 | package org.zoomdev.stock.tdx.writer; 2 | 3 | import org.zoomdev.stock.Quote; 4 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 5 | 6 | import java.io.IOException; 7 | 8 | public class TdxQuoteWriter { 9 | 10 | public static void writeForDay(Quote quote, DataOutputStream os) throws IOException { 11 | 12 | String date = quote.getDate(); 13 | int year = Integer.parseInt(date.substring(0, 4)); 14 | int month = Integer.parseInt(date.substring(4, 6)); 15 | int day = Integer.parseInt(date.substring(6, 8)); 16 | int time = year * 10000 + month * 100 + day; 17 | 18 | os.writeInt(time); 19 | os.writeInt((int) Math.round(quote.getOpen() * 100)); 20 | os.writeInt((int) Math.round(quote.getHigh() * 100)); 21 | os.writeInt((int) Math.round(quote.getLow() * 100)); 22 | os.writeInt((int) Math.round(quote.getClose() * 100)); 23 | os.writeFloat((float) quote.getAmt()); 24 | os.writeInt(quote.getVol()); 25 | os.writeInt(0); 26 | 27 | 28 | } 29 | 30 | public static void writeForMin(Quote quote, DataOutputStream os) throws IOException { 31 | String date = quote.getDate(); 32 | int year = Integer.parseInt(date.substring(0, 4)); 33 | int month = Integer.parseInt(date.substring(4, 6)); 34 | int day = Integer.parseInt(date.substring(6, 8)); 35 | int time ; 36 | if(year<2004){ 37 | time = ((2004-year) * 0x800 + month * 100 + day) | 0x8000; 38 | }else{ 39 | time = ((year - 2004) * 0x800 + month * 100 + day); 40 | } 41 | os.writeShort(time&0xffff); 42 | int hour = Integer.parseInt(date.substring(8, 10)); 43 | int minute = Integer.parseInt(date.substring(10, 12)); 44 | os.writeShort(hour * 60 + minute); 45 | os.writeFloat((float)quote.getOpen()); 46 | os.writeFloat((float)quote.getHigh()); 47 | os.writeFloat((float)quote.getLow()); 48 | os.writeFloat((float)quote.getClose()); 49 | os.writeFloat((float)quote.getAmt()); 50 | os.writeInt(quote.getVol()); 51 | os.writeInt(0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/TestReaderWriter.java: -------------------------------------------------------------------------------- 1 | import junit.framework.TestCase; 2 | import org.zoomdev.stock.Quote; 3 | import org.zoomdev.stock.tdx.reader.TdxQuoteReader; 4 | import org.zoomdev.stock.tdx.utils.DataInputStream; 5 | import org.zoomdev.stock.tdx.utils.DataOutputStream; 6 | import org.zoomdev.stock.tdx.writer.TdxQuoteWriter; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | 12 | public class TestReaderWriter extends TestCase { 13 | 14 | public void test() throws IOException { 15 | Quote quote = new Quote(); 16 | quote.setDate("20190101"); 17 | quote.setOpen(1); 18 | quote.setClose(2); 19 | quote.setHigh(3); 20 | quote.setLow(4); 21 | quote.setAmt(5); 22 | quote.setVol(6); 23 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 24 | DataOutputStream os = new DataOutputStream(byteArrayOutputStream); 25 | TdxQuoteWriter.writeForDay(quote, os); 26 | byte[] bytes = byteArrayOutputStream.toByteArray(); 27 | 28 | ByteArrayInputStream is = new ByteArrayInputStream(bytes); 29 | DataInputStream dataInputStream = new DataInputStream(); 30 | dataInputStream.setBuf(bytes); 31 | Quote quote1 = TdxQuoteReader.parseForDay(dataInputStream); 32 | 33 | 34 | System.out.println(quote1); 35 | 36 | 37 | assertEquals(quote1.getDate(), quote.getDate()); 38 | 39 | } 40 | 41 | public void test2() throws IOException { 42 | Quote quote = new Quote(); 43 | quote.setDate("201901010302"); 44 | quote.setOpen(1); 45 | quote.setClose(2); 46 | quote.setHigh(3); 47 | quote.setLow(4); 48 | quote.setAmt(5); 49 | quote.setVol(6); 50 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 51 | DataOutputStream os = new DataOutputStream(byteArrayOutputStream); 52 | TdxQuoteWriter.writeForMin(quote, os); 53 | byte[] bytes = byteArrayOutputStream.toByteArray(); 54 | 55 | ByteArrayInputStream is = new ByteArrayInputStream(bytes); 56 | DataInputStream dataInputStream = new DataInputStream(); 57 | dataInputStream.setBuf(bytes); 58 | Quote quote1 = TdxQuoteReader.parseForMin(dataInputStream); 59 | 60 | 61 | System.out.println(quote1); 62 | 63 | 64 | assertEquals(quote1.getDate(), quote.getDate()); 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/TestSocketClient.java: -------------------------------------------------------------------------------- 1 | import junit.framework.TestCase; 2 | import org.zoomdev.stock.tdx.*; 3 | import org.zoomdev.stock.tdx.impl.TdxClientImpl; 4 | import org.zoomdev.stock.tdx.reader.TdxBlockReader; 5 | 6 | import java.io.ByteArrayInputStream; 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.util.Collection; 10 | 11 | public class TestSocketClient extends TestCase { 12 | private void printBytes(byte[] bytes, int len) { 13 | for (int i = 0; i < len; ++i) { 14 | System.out.print(bytes[i] + " ,"); 15 | } 16 | } 17 | 18 | public void testAddress(){ 19 | InetSocketAddress address1 = new InetSocketAddress("localhost",8080); 20 | InetSocketAddress address2 = new InetSocketAddress("localhost",8080); 21 | assertEquals(address1,address2); 22 | 23 | } 24 | 25 | // public void testAsync() throws IOException, InterruptedException, ExecutionException { 26 | // 27 | // final BlockingQueue queue = new LinkedBlockingQueue(); 28 | // 29 | // final TdxClientImpl[] clients = new TdxClientImpl[2]; 30 | // List tasks = new ArrayList(); 31 | // 32 | // 33 | // 34 | // for(int i=0; i < 2; ++i){ 35 | // TdxClientImpl client = new TdxClientImpl(); 36 | // client.setSocketAddress(new InetSocketAddress("119.147.212.81", 7709)); 37 | // client.connect(); 38 | // clients[i] = client; 39 | // Thread thread= new Thread(new Runnable() { 40 | // @Override 41 | // public void run() { 42 | // try{ 43 | // 44 | // String code = "000001"; 45 | // int start = 0; 46 | // 47 | // 48 | // while (true){ 49 | // FutureTask data = (FutureTask)queue.poll(20000, TimeUnit.MILLISECONDS); 50 | // if(data==null){ 51 | // break; 52 | // } 53 | // 54 | // data.run(); 55 | // data.get(); 56 | // 57 | // 58 | // } 59 | // 60 | // }catch (Exception e){ 61 | // e.printStackTrace(); 62 | // } 63 | // } 64 | // }); 65 | // thread.setName(String.valueOf(i)); 66 | // thread.start(); 67 | // } 68 | // 69 | // 70 | // long now = System.currentTimeMillis(); 71 | // for(int i=0; i < 100; ++i){ 72 | // FutureTask task = new FutureTask(new Callable() { 73 | // @Override 74 | // public Object call() throws Exception { 75 | // 76 | // Thread thread = Thread.currentThread(); 77 | // String name = thread.getName(); 78 | // int index = Integer.parseInt(name); 79 | // TdxClientImpl client = clients[index]; 80 | // List quotes = client.getIndexQuotes(Category.day, Market.sh,"000001",0,1); 81 | // return null; 82 | // } 83 | // }); 84 | // queue.add(task); 85 | // tasks.add(task); 86 | // } 87 | // 88 | // for(int i=0; i < 100; ++i){ 89 | // tasks.get(i).get(); 90 | // } 91 | // 92 | // System.out.println("耗时"+(System.currentTimeMillis()-now)); 93 | // } 94 | 95 | public void test() throws IOException { 96 | TdxClientImpl client = new TdxClientImpl(); 97 | client.setSocketAddress(new InetSocketAddress("119.147.212.81", 7709)); 98 | client.connect(); 99 | 100 | // String code = "000001"; 101 | // int start = 0; 102 | // int count = 1; 103 | // 104 | // System.out.println(client.getCount(Market.sh)); 105 | // System.out.println(client.getCount(Market.sz)); 106 | 107 | // List quotes = client.getQuotes(Category.day, Market.sz,code,start,100); 108 | // System.out.println(quotes); 109 | // 110 | // System.out.println(client.getStockList(Market.sh, 0)); 111 | // 112 | // System.out.println(client.getIndexQuotes(Category._d,Market.sh,code,start,100)); 113 | 114 | // 115 | // for(char i='a'; i <= 'z'; ++i){ 116 | // for(char j='a'; j <= 'z'; ++j){ 117 | // try{ 118 | // System.out.println(client.getBlockInfo(String.format("block_%c%c.dat",i,j))); 119 | // System.out.println(String.format("%c%c",i,j)); 120 | // }catch (Exception e){ 121 | // 122 | // } 123 | // } 124 | // } 125 | // System.out.println(client.getBlockInfo("spblock.dat")); 126 | } 127 | 128 | static final String path = "/Users/jzoom/Downloads/new_tdx"; 129 | 130 | public void testReadFile() throws IOException { 131 | TdxClientImpl client = new TdxClientImpl(); 132 | client.setSocketAddress(new InetSocketAddress("119.147.212.81", 7709)); 133 | client.connect(); 134 | client.setTdxRootDir(path); 135 | 136 | Collection stocks = client.getBlockInfo(BlockType.TdxIndustry); 137 | System.out.println(stocks); 138 | 139 | System.out.println("正在获取概念============="); 140 | stocks = client.getBlockInfo(BlockType.Concept); 141 | System.out.println(stocks); 142 | 143 | System.out.println("正在获取指数============="); 144 | stocks = client.getBlockInfo(BlockType.Index); 145 | System.out.println(stocks); 146 | 147 | System.out.println("正在获取风格============="); 148 | stocks = client.getBlockInfo(BlockType.Style); 149 | System.out.println(stocks); 150 | } 151 | 152 | public void test1() throws IOException { 153 | TdxClient client = new TdxClientImpl(); 154 | client.setSocketAddress(new InetSocketAddress("119.147.212.81", 7709)); 155 | client.connect(); 156 | 157 | System.out.println(client.getTimePrice(Market.sz, "000001")); 158 | System.out.println(client.getHistoryTimePrice(Market.sz, "000001", "20190115")); 159 | 160 | } 161 | 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/test/java/TestSocketClientService.java: -------------------------------------------------------------------------------- 1 | import junit.framework.TestCase; 2 | import org.zoomdev.stock.Quote; 3 | import org.zoomdev.stock.tdx.Category; 4 | import org.zoomdev.stock.tdx.Market; 5 | import org.zoomdev.stock.tdx.StockInfo; 6 | import org.zoomdev.stock.tdx.impl.TdxClientServiceImpl; 7 | 8 | import java.io.IOException; 9 | import java.util.List; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.Future; 12 | 13 | public class TestSocketClientService extends TestCase { 14 | 15 | public void test() throws ExecutionException, InterruptedException, IOException { 16 | // TdxClientServiceImpl service = new TdxClientServiceImpl(2); 17 | // service.start(); 18 | // long now = System.currentTimeMillis(); 19 | // for(int i=0; i < 100; ++i){ 20 | // Future> quotes = service.getQuotes(Category.m1, Market.sh, "000001", 0, 1); 21 | // quotes.get(); 22 | // } 23 | // System.out.println("耗时"+(System.currentTimeMillis()-now)); 24 | // 25 | // 26 | // //System.out.println(quotes.get()); 27 | // 28 | // quotes = service.getQuotes(Category._d, Market.sh, "000001", 0, 1); 29 | // 30 | // System.out.println(quotes.get()); 31 | // 32 | // 33 | // Future> list = service.getStockList(Market.sz, 1000); 34 | // List result = list.get(); 35 | // System.out.println(result); 36 | // 37 | // 38 | // quotes = service.getQuotes(Category._d, Market.sh, "000001", 0, 1); 39 | // 40 | // System.out.println(quotes.get()); 41 | } 42 | } 43 | --------------------------------------------------------------------------------