├── .gitignore ├── README.md ├── bin ├── cli.py └── trader.py ├── pom.xml └── src ├── main ├── java │ └── net │ │ └── jquant │ │ ├── Indicators.java │ │ ├── Main.java │ │ ├── Quants.java │ │ ├── common │ │ ├── Constants.java │ │ ├── Context.java │ │ ├── DateRange.java │ │ ├── Pair.java │ │ ├── ParallelProcesser.java │ │ ├── StockConstants.java │ │ ├── StockDataParseException.java │ │ └── Utils.java │ │ ├── downloader │ │ ├── AjaxDownloader.java │ │ ├── BasicDownloader.java │ │ ├── Downloader.java │ │ ├── JsonpParser.java │ │ └── THSJSDownloader.java │ │ ├── model │ │ ├── ArticleType.java │ │ ├── Bar.java │ │ ├── BoardType.java │ │ ├── PeriodType.java │ │ ├── StockBlock.java │ │ ├── StockData.java │ │ ├── StockMarketType.java │ │ ├── StockSlice.java │ │ ├── Symbol.java │ │ └── Tick.java │ │ ├── provider │ │ ├── DailyDataProvider.java │ │ ├── FinanceDataProvider.java │ │ ├── MinuteDataProvider.java │ │ ├── MoneyFlowDataProvider.java │ │ ├── Provider.java │ │ ├── RealTimeDataProvider.java │ │ ├── ReferenceDataProvider.java │ │ ├── ReportDataProvider.java │ │ ├── StockIndexDataProvider.java │ │ ├── TickDataProvider.java │ │ └── TopListDataProvider.java │ │ ├── strategy │ │ ├── KellyFormula.java │ │ ├── Strategy.java │ │ ├── StrategyUtils.java │ │ ├── TDXFunction.java │ │ └── TStragegy.java │ │ ├── tools │ │ ├── Analyzer.java │ │ ├── Conditions.java │ │ ├── SharpeRatio.java │ │ ├── Sleeper.java │ │ ├── SortinoRatio.java │ │ ├── StockCategory.java │ │ ├── StockList.java │ │ ├── StockPool.java │ │ ├── Suggest.java │ │ └── tableformat │ │ │ ├── AbstractTableFormatter.java │ │ │ ├── SimpleTableFormatter.java │ │ │ └── TableFormatter.java │ │ └── trade │ │ ├── BackTester.java │ │ ├── Commission.java │ │ ├── FixedSlippage.java │ │ ├── Order.java │ │ ├── OrderStatus.java │ │ ├── OrderStyle.java │ │ ├── OrderType.java │ │ ├── Portfolio.java │ │ ├── Position.java │ │ ├── PriceRelatedSlippage.java │ │ ├── Record.java │ │ ├── RiskAnalysis.java │ │ ├── Slippage.java │ │ ├── Trade.java │ │ └── TradingSystem.java └── resources │ └── log4j.properties └── test └── java └── net └── jquant ├── common └── UtilsTest.java ├── model └── StockDataTest.java ├── provider ├── FinanceDataProviderTest.java └── ProviderTest.java └── tools └── StockListTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | *.class 4 | 5 | # Mobile Tools for Java (J2ME) 6 | .mtj.tmp/ 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | 16 | *.iml 17 | .idea 18 | *.log 19 | *.json 20 | target -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JQuant 2 | 3 | # 项目迁移 4 | 5 | JQuant已经停止更新,请使用全新的SQuant项目,新项目是JQuant的升级版本,包含JQuant所有功能,并在JQuant基础上增加了回测和交易等功能,使用scala开发与java兼容 6 | 7 | 新项目地址:https://github.com/eryk/squant 8 | 9 | # [changelog](https://github.com/eryk/JQuant/wiki#changelog) 10 | 11 | # Maven依赖 12 | 13 | ```xml 14 | 15 | 16 | net.jquant 17 | JQuant 18 | 0.1 19 | 20 | 21 | 22 | 23 | 24 | oss 25 | https://oss.sonatype.org/content/groups/public/ 26 | 27 | 28 | ``` 29 | 30 | # 示例 31 | 32 | ```java 33 | package quant.fans; 34 | 35 | import quant.fans.model.StockData; 36 | 37 | import java.util.List; 38 | 39 | public class Main { 40 | 41 | public static void main(String[] args) { 42 | Quants quants = new Quants(); 43 | //获取股票列表 44 | List list = quants.data.stockList(); 45 | 46 | for (String stock : list) { 47 | //StockData代表一个时间片的数据,例如日线级别,每个StockData为一天收盘后的股票数据 48 | List stockDatas = quants.data.dailyData(stock); 49 | //剔除交易数据小于60天的股票 50 | if (stockDatas == null || stockDatas.size() < 60) { 51 | continue; 52 | } 53 | //indicator包含常用指标的计算 54 | quants.indicator.macd(stockDatas); 55 | //获取5、10、20、30、40、60均线,也可以通过sma(stockDatas,ma)获取指定时间间隔的均线 56 | quants.indicator.sma(stockDatas); 57 | quants.indicator.boll(stockDatas); 58 | quants.indicator.kdj(stockDatas); 59 | //strategy包含简单的策略计算,例如macd金叉 60 | quants.strategy.macdCross(stockDatas); 61 | quants.strategy.kdjCross(stockDatas); 62 | quants.strategy.goldenSpider(stockDatas); 63 | quants.strategy.bollThroat(stockDatas); 64 | for (StockData stockData : stockDatas) { 65 | System.out.println(stockData); 66 | } 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | # API 73 | 74 | ## 初始化 75 | 76 | > Quants quants = new Quants(); 77 | 78 | `StockData`表示股票的一个时间片的数据,继承自LinkedHashMap,存储属性名称和double类型数值。 79 | 80 | ```java 81 | StockData stockData = Provider.realtimeData("000001"); 82 | System.out.println("股票名称:" + stockData.name); 83 | System.out.println("股票代码:" + stockData.symbol); 84 | for(Map.Entry data : stockData.entrySet()){ 85 | System.out.println(data.getKey() + "=" + data.getValue()); 86 | } 87 | ``` 88 | 89 | 不同Provider返回的StockData的数据项不同,具体信息查询[字段说明](https://github.com/eryk/JQuant/wiki/StockData%E5%AD%97%E6%AE%B5%E8%AF%B4%E6%98%8E) 90 | 91 | ## 股票数据 quants.data 92 | 93 | net.jquant.provider包提供了股票相关的数据获取类 94 | 95 | ### Provider列表如下: 96 | 97 | * DailyDataProvider:日线级别数据 98 | * MinuteDataProvider:分钟级别股票数据,可获得5、15、30、60分钟级别股票数据 99 | * RealTimeDataProvider:实时股票数据 100 | * StockIndexDataProvider:指数实时行情数据 101 | * TickDataProvider:股票逐笔数据 102 | * TopListDataProvider:龙虎榜数据 103 | * ReportDataProvider:研报数据 104 | * ReferenceDataProvider:分红数据 105 | * MoneyFlowDataProvider:股票资金流数据 106 | * FinanceDataProvider:个股财务报表数据 107 | 108 | > net.jquant.provider.Provider类里的static方法汇总了全部provider方法,一般情况,使用Provider类就可以满足数据查询需求。 109 | 110 | ## 股票列表 quants.stocks 111 | 112 | `StockList` 用于获取股票列表,并提供一些过滤和处理接口对股票进行筛选。 113 | 114 | ## 指标计算 quants.indicator 115 | 116 | `net.jquant。Indicators` 是对[Ta-lib](http://ta-lib.org/function.html)库的封装,提供常用指标计算,返回StockData list对象。同时也提供了一些Ta-lib没有的指标计算。 117 | 118 | 支持的指标包括: 119 | 120 | * sma:简单移动平均线 121 | * ema:指数移动平均线 122 | * dma:平均线差 123 | * macd:指数平滑异同平均线 124 | * boll:布林线 125 | * kdj:随机指标 126 | * rsi:强弱指标 127 | * sar:抛物线指标或停损转向操作点指标 128 | * adx:平均趋向指数 129 | * adxr:趋向指标 130 | * cci:顺势指标 131 | * mfi:资金流量指标 132 | * obv:能量潮又称为平衡交易量 133 | * roc:变动率指标 134 | * rocP:Rate of change Percentage: (price-prevPrice)/prevPrice 135 | * trix:三重指数平滑平均线 136 | * willR:威廉指标 137 | * ad:收集派发摆荡指标 138 | * aroon:阿隆指标 139 | * aroonOsc:Aroon Oscillator 140 | * bop:均势指标 141 | * kama:适应性移动平均线 142 | * trima:三角移动平均线 143 | 144 | ## 策略计算 quants.strategy 145 | 146 | * `StragegyUtils` 常用策略 147 | * `TDXFunction` 通达信常用指标 148 | -------------------------------------------------------------------------------- /bin/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import anyjson as json 4 | import click 5 | import dill 6 | 7 | import easytrader 8 | 9 | ACCOUNT_OBJECT_FILE = 'account.session' 10 | 11 | 12 | @click.command() 13 | @click.option('--use', help='指定券商 [ht, yjb, yh]') 14 | @click.option('--prepare', type=click.Path(exists=True), help='指定登录账户文件路径') 15 | @click.option('--get', help='调用 easytrader 中对应的变量') 16 | @click.option('--do', help='调用 easytrader 中对应的函数名') 17 | @click.option('--debug', default=False, help='是否输出 easytrader 的 debug 日志') 18 | @click.argument('params', nargs=-1) 19 | def main(prepare, use, do, get, params, debug): 20 | if get is not None: 21 | do = get 22 | if prepare is not None and use in ['ht', 'yjb', 'yh', 'gf', 'xq']: 23 | user = easytrader.use(use, debug) 24 | user.prepare(prepare) 25 | with open(ACCOUNT_OBJECT_FILE, 'wb') as f: 26 | dill.dump(user, f) 27 | if do is not None: 28 | with open(ACCOUNT_OBJECT_FILE, 'rb') as f: 29 | user = dill.load(f) 30 | 31 | if len(params) > 0: 32 | result = getattr(user, do)(*params) 33 | else: 34 | result = getattr(user, do) 35 | 36 | json_result = json.dumps(result) 37 | click.echo(json_result) 38 | 39 | 40 | if __name__ == '__main__': 41 | main() -------------------------------------------------------------------------------- /bin/trader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #coding:utf-8 3 | 4 | import easytrader 5 | 6 | user = easytrader.use('yjb',debug=False) 7 | user.prepare('yjb.json') 8 | 9 | 10 | def balance(): 11 | # 获取资金状况 12 | b = user.balance 13 | return b[0] 14 | 15 | def position(): 16 | # 获取持仓 17 | p = user.position 18 | return p[0] 19 | 20 | def entrust(): 21 | # 获取今日委托单 22 | e = user.entrust 23 | return e[0] 24 | 25 | def buy(code,price,amount): 26 | # 买入 27 | ret = user.buy(code, price=price, amount=amount) 28 | return ret[0] 29 | 30 | def sell(code,price,amount): 31 | # 卖出 32 | ret = user.sell(code, price=price, amount=amount) 33 | return ret[0] 34 | 35 | def cancel_entrust(entrust_no,stock_code): 36 | # 撤单 37 | ret = user.cancel_entrust(entrust_no,stock_code) 38 | return ret[0] 39 | 40 | def deal(): 41 | # 查询当日成交 42 | ret = user.current_deal 43 | return ret[0] 44 | 45 | def ipo_limit(stock_code): 46 | # 查询新股申购额度申购上限 47 | ret = user.get_ipo_limit(stock_code) 48 | return ret -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.jquant 8 | JQuant 9 | 0.2 10 | 11 | JQuant 12 | quant tool 13 | https://github.com/eryk/JQuant 14 | 15 | 16 | 17 | The Apache License, Version 2.0 18 | http://www.apache.org/licenses/LICENSE-2.0.txt 19 | 20 | 21 | 22 | 23 | 24 | xuqi 25 | xuqi86@gmail.com 26 | 27 | 28 | 29 | 30 | https://github.com/eryk/JQuant 31 | git@github.com:eryk/JQuant.git 32 | git@github.com:eryk/JQuant.git 33 | 34 | 35 | 36 | GitHub Issues 37 | https://github.com/eryk/JQuant/issues 38 | 39 | 40 | 41 | 42 | ossrh 43 | https://oss.sonatype.org/content/repositories/snapshots 44 | 45 | 46 | ossrh 47 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 48 | 49 | 50 | 51 | 52 | 1.8 53 | 1.8 54 | 1.8 55 | 1.8 56 | true 57 | true 58 | 59 | 60 | 61 | 62 | junit 63 | junit 64 | 4.12 65 | test 66 | 67 | 68 | org.mockito 69 | mockito-core 70 | 1.10.19 71 | test 72 | 73 | 74 | com.google.guava 75 | guava 76 | 19.0 77 | 78 | 79 | 80 | org.slf4j 81 | slf4j-api 82 | 1.7.21 83 | 84 | 85 | 86 | org.slf4j 87 | slf4j-log4j12 88 | 1.7.21 89 | 90 | 91 | 92 | org.jsoup 93 | jsoup 94 | 1.8.3 95 | 96 | 97 | joda-time 98 | joda-time 99 | 2.8.1 100 | 101 | 102 | com.tictactec 103 | ta-lib 104 | 0.4.0 105 | 106 | 107 | com.mashape.unirest 108 | unirest-java 109 | 1.4.6 110 | 111 | 112 | com.google.code.gson 113 | gson 114 | 2.7 115 | 116 | 117 | org.seleniumhq.selenium 118 | selenium-java 119 | 2.47.1 120 | 121 | 122 | commons-io 123 | commons-io 124 | 2.5 125 | 126 | 127 | org.yaml 128 | snakeyaml 129 | 1.17 130 | 131 | 132 | org.apache.commons 133 | commons-math3 134 | 3.5 135 | 136 | 137 | org.xerial.snappy 138 | snappy-java 139 | 1.1.2.6 140 | 141 | 142 | 143 | 144 | 145 | 146 | org.apache.maven.plugins 147 | maven-gpg-plugin 148 | 1.5 149 | 150 | 151 | sign-artifacts 152 | verify 153 | 154 | sign-and-deploy-file 155 | 156 | 157 | 158 | 159 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 160 | ossrh 161 | pom.xml 162 | target/JQuant-0.1-javadoc.jar 163 | target/JQuant-0.1.jar 164 | target/JQuant-0.1-sources.jar 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-source-plugin 170 | 2.2.1 171 | 172 | 173 | attach-sources 174 | 175 | jar-no-fork 176 | 177 | 178 | 179 | 180 | 181 | org.apache.maven.plugins 182 | maven-javadoc-plugin 183 | 2.9.1 184 | 185 | 186 | attach-javadocs 187 | 188 | jar 189 | 190 | 191 | 192 | 193 | 194 | org.apache.maven.plugins 195 | maven-compiler-plugin 196 | 3.0 197 | 198 | 1.8 199 | 1.8 200 | 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/Main.java: -------------------------------------------------------------------------------- 1 | package net.jquant; 2 | 3 | import net.jquant.common.StockDataParseException; 4 | import net.jquant.model.StockData; 5 | 6 | import java.util.List; 7 | 8 | public class Main { 9 | 10 | public static void main(String[] args) throws StockDataParseException { 11 | Quants quants = new Quants(); 12 | //获取股票列表 13 | List list = quants.data.stockList(); 14 | 15 | for (String stock : list) { 16 | //StockData代表一个时间片的数据,例如日线级别,每个StockData为一天收盘后的股票数据 17 | List stockDatas = quants.data.dailyData(stock); 18 | //剔除交易数据小于60天的股票 19 | if (stockDatas == null || stockDatas.size() < 60) { 20 | continue; 21 | } 22 | //indicator包含常用指标的计算 23 | quants.indicator.macd(stockDatas); 24 | //获取5、10、20、30、40、60均线,也可以通过sma(stockDatas,ma)获取指定时间间隔的均线 25 | quants.indicator.sma(stockDatas); 26 | quants.indicator.boll(stockDatas); 27 | quants.indicator.kdj(stockDatas); 28 | //strategy包含简单的策略计算,例如macd金叉 29 | quants.strategy.macdCross(stockDatas); 30 | quants.strategy.kdjCross(stockDatas); 31 | quants.strategy.goldenSpider(stockDatas); 32 | quants.strategy.bollThroat(stockDatas); 33 | for (StockData stockData : stockDatas) { 34 | System.out.println(stockData); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/net/jquant/Quants.java: -------------------------------------------------------------------------------- 1 | package net.jquant; 2 | 3 | import net.jquant.provider.Provider; 4 | import net.jquant.tools.StockList; 5 | import net.jquant.strategy.StrategyUtils; 6 | 7 | import java.util.Map; 8 | 9 | public class Quants { 10 | 11 | /** 12 | * 获取股票数据接口 13 | */ 14 | public final Provider data = new Provider(); 15 | 16 | /** 17 | * 指标计算接口 18 | */ 19 | public final Indicators indicator = new Indicators(); 20 | 21 | /** 22 | * 策略计算接口 23 | */ 24 | public final StrategyUtils strategy = new StrategyUtils(); 25 | 26 | public final StockList stocks = new StockList(); 27 | /** 28 | * 使用外部存储,支持: 29 | * 文件系统(csv) 30 | * redis 31 | * hbase 32 | * 33 | * @param config config 34 | * @return quants instance 35 | */ 36 | public Quants db(Map config) { 37 | //TODO 38 | return this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/Constants.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | /** 6 | * Created by eryk on 2015/7/20. 7 | */ 8 | public class Constants { 9 | public static final Charset UTF8 = Charset.forName("UTF-8"); 10 | 11 | public static final String DATABASE_POOL_SIZE = "database.pool.size"; 12 | public static final String SCHEDULE_POOL_SIZE = "schedule.pool.size"; 13 | public static final String THREAD_POOL_SIZE = "thread.pool.size"; 14 | 15 | public static final String BROKER_POOL_SIZE = "broker.pool.size"; 16 | public static final String BROKER_CHECK_INTERVAL = "broker.check.interval"; 17 | 18 | public static final String TRADER_ACCOUNT_ID = "trader.account.id"; 19 | public static final String TRADER_TRADING_DAY_COUNT = "trader.trading.day.count"; 20 | public static final String TRADER_POOL_SIZE = "trader.pool.size"; 21 | public static final String TRADER_EXCEL_BASE_DIR = "trader.execl.base.dir"; 22 | 23 | public static String NETEASE_DATE_STYLE = "yyyy-MM-dd"; 24 | 25 | public static String IFENG_DATE_STYLE = "yyyy-MM-dd"; 26 | 27 | public static String BISNESS_DATA_FORMAT = "yyyyMMdd"; 28 | 29 | public static final String MINUTE_ROWKEY_DATA_FORMAT = "yyyyMMddHHmm"; 30 | 31 | public static final String SECOND_ROWKEY_DATA_FORMAT = "yyyyMMddHHmmss"; 32 | 33 | public static final String MARKET_START_DATE = "19901219"; 34 | 35 | public static final String SCHEDULER_JOBS = "jobs"; 36 | 37 | public static final String SCHEDULER_JOB_CLASSNAME = "classname"; 38 | 39 | public static final String SCHEDULER_JOB_CRON = "cron"; 40 | 41 | public static int SECOND = 1 * 1000; 42 | public static int MINUTE = 60 * SECOND; 43 | public static int MINUTES_5 = 5 * MINUTE; 44 | public static int MINUTES_15 = 15 * MINUTE; 45 | public static int MINUTES_30 = 30 * MINUTE; 46 | public static int MINUTES_60 = 60 * MINUTE; 47 | public static int DAY = 1440 * MINUTE; 48 | public static int WEEK = 7 * 1440 * MINUTE; 49 | 50 | /** 51 | * table definition 52 | */ 53 | public static final String TABLE_STOCK_5_MINUTES = "stocks_data_5mins"; 54 | 55 | public static final String TABLE_STOCK_15_MINUTES = "stocks_data_15mins"; 56 | 57 | public static final String TABLE_STOCK_30_MINUTES = "stocks_data_30mins"; 58 | 59 | public static final String TABLE_STOCK_60_MINUTES = "stocks_data_60mins"; 60 | 61 | public static final String TABLE_STOCK_DAILY = "stocks_data_daily"; 62 | 63 | public static final String TABLE_STOCK_WEEK = "stocks_data_week"; 64 | 65 | public static final String TABLE_STOCK_MONTH = "stocks_data_month"; 66 | 67 | public static final String TABLE_STOCK_TICK = "stocks_tick_data"; 68 | 69 | public static final byte[] TABLE_CF_DATA = "d".getBytes(); 70 | 71 | public static final String TABLE_STOCK_INFO = "stocks_info"; 72 | 73 | public static final byte[] TABLE_CF_INFO = "i".getBytes(); 74 | 75 | public static final String TABLE_ARTICLE = "stocks_article"; 76 | 77 | public static final byte[] TABLE_CF_ARTICLE = "a".getBytes(); 78 | 79 | public static final String[] ACCOUNT_HEADER = {"交易时间","起始资产","期末资产","交易盈亏","收益率","夏普比率","索提诺比率","操作正确率","最大单笔盈利","最大单笔亏损","平均每笔盈利","基准收益额","基准收益率","最大资产","最小资产","最大回撤","交易次数","盈利次数","亏损次数","持仓总时间","平均持仓时间"}; 80 | 81 | public static final String[] ACCOUNT_COLUMN = {"date","start","end","pnl","pnlRate","sharpe","sortino","accuracy","maxEarnPerOp","maxLossPerOp","meanEarnPerOp","benchmarkBenfit","benchmarkBenfitPercent","max","min","drawdown","totalOperate","earnOperate","lossOperate","totalPositionDays","avgPositionDays"}; 82 | 83 | public static final String[] POSITION_HEADER = {"代码","买入日期","卖出日期","成交量","成交额","买入价","卖出价","盈亏额"}; 84 | 85 | public static final String[] POSITION_COLUMN = {"symbol","buyDate","sellDate","volume","amount","price","sellPrice","pnl"}; 86 | 87 | /** 88 | * stock column 89 | */ 90 | public static final byte[] CLOSE = "close".getBytes(); 91 | public static final byte[] HIGH = "high".getBytes(); 92 | public static final byte[] LOW = "low".getBytes(); 93 | public static final byte[] OPEN = "open".getBytes(); 94 | public static final byte[] LAST_CLOSE = "lastClose".getBytes(); 95 | public static final byte[] CHANGE_AMOUNT = "changeAmount".getBytes(); 96 | public static final byte[] CHANGE = "change".getBytes(); 97 | public static final byte[] TURNOVER_RATE = "turnoverRate".getBytes(); 98 | public static final byte[] VOLUME = "volume".getBytes(); 99 | public static final byte[] AMOUNT = "amount".getBytes(); 100 | public static final byte[] TOTAL_VALUE = "totalValue".getBytes(); 101 | public static final byte[] MARKET_VALUE = "marketValue".getBytes(); 102 | public static final byte[] AMPLITUDE = "amplitude".getBytes(); 103 | public static final byte[] NAME = "name".getBytes(); 104 | public static final byte[] STATUS = "status".getBytes(); 105 | 106 | public static final byte[] AVG_COST = "avgCost".getBytes(); 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/Context.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | import net.jquant.tools.StockList; 4 | 5 | public class Context { 6 | private StockList stockList; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/DateRange.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.joda.time.DateTime; 5 | 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | /** 10 | * start:今天往前的n个交易日,步长为天 11 | * stop:明天,因为hbase的stoprow是开区间 12 | */ 13 | public class DateRange { 14 | 15 | private int dayCount; 16 | 17 | private DateRange() { 18 | } 19 | 20 | private DateRange(int dayCount) { 21 | this.dayCount = dayCount; 22 | } 23 | 24 | public static DateRange getRange(int dayCount) { 25 | return new DateRange(dayCount); 26 | } 27 | 28 | public static DateRange getRange(){ 29 | return new DateRange(0); 30 | } 31 | 32 | public String start() { 33 | return start("yyyyMMdd"); 34 | } 35 | 36 | public String start(String format) { 37 | DateTime dt = new DateTime(); 38 | dt = addDays(dt, dayCount); //[) 39 | return dt.toString(format); 40 | } 41 | 42 | public Date startDate(String format){ 43 | return Utils.str2Date(start(),format); 44 | } 45 | 46 | public Date startDate(){ 47 | return startDate("yyyyMMdd"); 48 | } 49 | 50 | private DateTime addDays(DateTime dateTime, int days) { 51 | DateTime dt = dateTime; 52 | for (int i = 0; i < days; i++) { 53 | dt = dt.plusDays(-1); 54 | if (dt.getDayOfWeek() == 7) { 55 | dt = dt.plusDays(-2); 56 | } 57 | } 58 | return dt; 59 | } 60 | 61 | public String stop() { 62 | return stop("yyyyMMdd"); 63 | } 64 | 65 | public String stop(String format) { 66 | DateTime dt = new DateTime(); 67 | dt = dt.plusDays(1); 68 | return dt.toString(format); 69 | } 70 | 71 | public Date stopDate(String format){ 72 | return Utils.str2Date(stop(),format); 73 | } 74 | 75 | public Date stopDate(){ 76 | return stopDate("yyyyMMdd"); 77 | } 78 | 79 | /** 80 | * 获取从开始时间到结束时间的交易日期 81 | * @param format format style 82 | * @return date list 83 | */ 84 | public List getDateList(String format){ 85 | List dateList = Lists.newArrayListWithCapacity(dayCount+1); 86 | DateTime tmpDate = new DateTime(startDate()); 87 | DateTime stop = new DateTime(stopDate()); 88 | while(tmpDate.toDate().getTime() <=stop.toDate().getTime()){ 89 | //只添加周一到周五 90 | if(tmpDate.getDayOfWeek()>=1 && tmpDate.getDayOfWeek()<=5){ 91 | dateList.add(tmpDate.toString(format)); 92 | } 93 | tmpDate = tmpDate.plusDays(1); 94 | } 95 | return dateList; 96 | } 97 | 98 | public List getDateList(){ 99 | return getDateList("yyyyMMdd"); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/Pair.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | public class Pair { 4 | private K key; 5 | private V val; 6 | 7 | public Pair(K key, V val) { 8 | this.key = key; 9 | this.val = val; 10 | } 11 | 12 | public K getKey() { 13 | return key; 14 | } 15 | 16 | public V getVal() { 17 | return val; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "Pair{" + 23 | "key=" + key + 24 | ", val=" + val + 25 | '}'; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (!(o instanceof Pair)) return false; 32 | 33 | Pair pair = (Pair) o; 34 | 35 | if (!key.equals(pair.key)) return false; 36 | if (!val.equals(pair.val)) return false; 37 | 38 | return true; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | int result = key.hashCode(); 44 | result = 31 * result + val.hashCode(); 45 | return result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/ParallelProcesser.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | import com.google.common.util.concurrent.MoreExecutors; 4 | 5 | import java.util.concurrent.*; 6 | 7 | public class ParallelProcesser { 8 | 9 | static volatile boolean isInit = false; 10 | 11 | static ScheduledExecutorService executorService; 12 | 13 | static ExecutorService threadPool; 14 | 15 | private static int executor_pool_size = 1; 16 | private static int schedule_pool_size = 1; 17 | 18 | public static synchronized void init(int scheduledPoolSize,int threadPoolSize) { 19 | if (executorService == null) { 20 | executorService = Executors.newScheduledThreadPool(scheduledPoolSize); 21 | } 22 | if(threadPool == null){ 23 | threadPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threadPoolSize)); 24 | } 25 | isInit = true; 26 | } 27 | 28 | public static void close() { 29 | Utils.closeThreadPool(executorService); 30 | Utils.closeThreadPool(threadPool); 31 | isInit = false; 32 | } 33 | 34 | public static void process(Runnable task){ 35 | if(threadPool == null){ 36 | init(schedule_pool_size,executor_pool_size); 37 | } 38 | threadPool.execute(task); 39 | } 40 | 41 | public static void schedule(Runnable runnable, int start, int period){ 42 | if(executorService == null){ 43 | init(schedule_pool_size,executor_pool_size); 44 | } 45 | executorService.scheduleAtFixedRate(runnable,start,period, TimeUnit.MINUTES); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/StockConstants.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * author: eryk 9 | * mail: xuqi86@gmail.com 10 | * date: 15-8-30. 11 | */ 12 | public class StockConstants { 13 | public static String CLOSE = "close"; //最新价,收盘价 14 | public static String HIGH = "high"; //最高价 15 | public static String LOW = "low"; //最低价 16 | public static String OPEN = "open"; //开盘价 17 | public static String LAST_CLOSE = "lastClose"; //昨日收盘价 18 | public static String CHANGE_AMOUNT = "changeAmount"; //涨跌额 今天收盘价-昨天收盘价 19 | public static String CHANGE = "change"; //涨跌幅 20 | public static String TURNOVER_RATE = "turnoverRate"; //换手率 21 | public static String VOLUME = "volume"; //成交量,单位:手 22 | public static String AMOUNT = "amount"; //成交额,单位:万 23 | public static String TOTAL_VALUE = "totalValue"; //总市值,单位:亿 24 | public static String MARKET_VALUE = "marketValue"; //流通市值,单位:亿 25 | public static String AMPLITUDE = "amplitude"; //振幅 26 | public static String FACTOR = "factor"; //复权因子 27 | 28 | public static String PE = "PE"; //动态市盈率 29 | 30 | public static String POSITION="position"; 31 | //macd指标 32 | public static String DIF = "dif"; 33 | public static String DEA = "dea"; 34 | public static String MACD = "macd"; 35 | public static String MACD_CROSS = "macd_cross"; 36 | //布林线指标 37 | public static String UPPER = "upper"; 38 | public static String MID = "mid"; 39 | public static String LOWER = "lower"; 40 | public static String BOLL_SHRINK = "boll_shrink"; 41 | //均线指标 42 | public static String CLOSE_MA5 = "close_ma5"; 43 | public static String CLOSE_MA10 = "close_ma10"; 44 | public static String CLOSE_MA13 = "close_ma13"; 45 | public static String CLOSE_MA20 = "close_ma20"; 46 | public static String CLOSE_MA30 = "close_ma30"; 47 | public static String CLOSE_MA34 = "close_ma34"; 48 | public static String CLOSE_MA40 = "close_ma40"; 49 | public static String CLOSE_MA55 = "close_ma55"; 50 | public static String CLOSE_MA60 = "close_ma60"; 51 | public static String CLOSE_MA120 = "close_ma120"; 52 | 53 | public static String MAX = "max"; 54 | public static String MIN = "min"; 55 | 56 | public static String AVERAGE_BOND = "average_bond"; 57 | 58 | public static String GOLDEN_SPIDER = "golden_spider"; 59 | public static String GOLDEN_SPIDER_RANGE = "golden_spider_range"; 60 | 61 | public static String DATE_OF_DECLARATION = "dateOfDeclaration"; //公告日期 62 | public static String ANNUAL = "annual"; //分红年度 63 | public static String DATE_OF_RECORD = "dateOfRecord"; //股权登记日 64 | public static String DATE_OF_EX_DIVIDEND = "dateOfExDividend"; //除权除息日 65 | 66 | public static String STOCK_SHARES = "stockShares"; //送股 67 | public static String STOCK_TRANSFERRED = "stockTransferred"; //转增 68 | public static String STOCK_DIVIDEND = "stockDividend"; //派息 69 | 70 | public static String TYPE = "type"; 71 | 72 | public static List DAILY = Lists.newArrayList( 73 | "close", //最新价,收盘价 74 | "high", //最高价 75 | "low", //最低价 76 | "open", //开盘价 77 | "lastClose", //昨日收盘价 78 | "changeAmount", //涨跌额 79 | "change", //涨跌幅 80 | "turnoverRate", //换手率 81 | "volume", //成交量,单位:手 82 | "amount", //成交额,单位:万 83 | "totalValue", //总市值,单位:亿 84 | "marketValue", //流通市值,单位:亿 85 | "amplitude" //振幅 86 | ); 87 | 88 | public static int DAILY_SIZE = 15; 89 | 90 | 91 | public static List REALTIME = Lists.newArrayList( 92 | "marketType", //0 市场类型,沪市:1,深市:2 93 | "code", //1 证券代码 94 | "name", //2 证券名称 95 | "buy1", //3 买一 96 | "buy2", //4 买二 97 | "buy3", //5 买三 98 | "buy4", //6 买四 99 | "buy5", //7 买五 100 | "sell1", //8 卖一 101 | "sell2", //9 卖二 102 | "sell3", //10 卖三 103 | "sell4", //11 卖四 104 | "sell5", //12 卖五 105 | "buy1Volume", //13 买一手数 106 | "buy2Volume", //14 买二手数 107 | "buy3Volume", //15 买三手数 108 | "buy4Volume", //16 买四手数 109 | "buy5Volume", //17 买五手数 110 | "sell1Volume", //18 卖一手数 111 | "sell2Volume", //19 卖二手数 112 | "sell3Volume", //20 卖三手数 113 | "sell4Volume", //21 卖四手数 114 | "sell5Volume", //22 卖五手数 115 | "limitUp", //23 涨停价 116 | "limitDown", //24 跌停价 117 | "close", //25 最新价,收盘价 118 | "avgCost", //26 均价 119 | "changeAmount", //27 涨跌额 120 | "open", //28 开盘价 121 | "change", //29 涨跌幅 122 | "high", //30 最高价 123 | "volume", //31 成交量,单位:手 124 | "low", //32 最低价 125 | "", //33 未知 126 | "lastClose", //34 昨日收盘价 127 | "amount", //35 成交额,单位:万 128 | "quantityRelative", //36 量比 129 | "turnoverRate", //37 换手率 130 | "PE", //38 市盈率 131 | "outerDisc", //39 外盘,主动买,单位:手 132 | "innerDisc", //40 内盘,主动卖,单位:手 133 | "committeeThan", //41 委比,百分比 134 | "committeeSent", //42 委差 135 | "PB", //43 市净率 136 | "", //44 未知 137 | "marketValue", //45 流通市值,单位:亿 138 | "totalValue", //46 总市值,单位:亿 139 | "", //47 未知 140 | "", //48 未知 141 | "date", //49 时间 142 | "", //50 未知 143 | "", //51 未知 144 | "" //52 未知 145 | ); 146 | 147 | public static List MONEYFLOW = Lists.newArrayList( 148 | "今日主力净流入", //0 今日主力净流入 149 | "主力净比", //1 主力净比 150 | "今日超大单净流入", //2 今日超大单净流入 151 | "超大单净比", //3 超大单净比 152 | "今日大单净流入", //4 今日大单净流入 153 | "大单净比", //5 大单净比 154 | "今日中单净流入", //6 今日中单净流入 155 | "中单净比", //7 中单净比 156 | "今日小单净流入", //8 今日小单净流入 157 | "小单净比", //9 小单净比 158 | "未知1", //10 未知 159 | "未知2", //11 未知 160 | "超大单:流入", //12 超大单:流入 161 | "超大单:流出", //13 超大单:流出 162 | "大单:流入", //14 大单:流入 163 | "大单:流出", //15 大单:流出 164 | "中单:流入", //16 中单:流入 165 | "中单:流出", //17 中单:流出 166 | "小单:流入", //17 小单:流入 167 | "小单:流出", //18 小单:流出 168 | "未知3", //19 未知 169 | "未知4" //20 未知 170 | 171 | ); 172 | 173 | public static List MONEYFLOW_HIS = Lists.newArrayList( 174 | "date", //0 日期 175 | "close", //1 收盘价 176 | "change", //2 涨跌幅 177 | "主力净流入-净额", //3 主力净流入-净额 178 | "主力净流入-净占比", //4 主力净流入-净占比 179 | "超大单净流入-净额", //5 超大单净流入-净额 180 | "超大单净流入-净占比", //6 超大单净流入-净占比 181 | "大单净流入-净额", //7 大单净流入-净额 182 | "大单净流入-净占比", //8 大单净流入-净占比 183 | "中单净流入-净额", //9 中单净流入-净额 184 | "中单净流入-净占比", //10 中单净流入-净占比 185 | "小单净流入-净额", //11 小单净流入-净额 186 | "小单净流入-净占比" //12 小单净流入-净占比 187 | ); 188 | 189 | public static List DAPAN_MONEYFLOW_HIS = Lists.newArrayList( 190 | "date", //0 日期 191 | "sh-close", //1 上证-收盘价 192 | "sh-change", //2 上证-涨跌幅 193 | "sz-close", //3 深证-收盘价 194 | "sz-change", //4 深证-涨跌幅 195 | "主力净流入-净额", //5 主力净流入-净额 196 | "主力净流入-净占比", //6 主力净流入-净占比 197 | "超大单净流入-净额", //7 超大单净流入-净额 198 | "超大单净流入-净占比", //8 超大单净流入-净占比 199 | "大单净流入-净额", //9 大单净流入-净额 200 | "大单净流入-净占比", //10 大单净流入-净占比 201 | "中单净流入-净额", //11 中单净流入-净额 202 | "中单净流入-净占比", //12 中单净流入-净占比 203 | "小单净流入-净额", //13 小单净流入-净额 204 | "小单净流入-净占比" //14 小单净流入-净占比 205 | ); 206 | 207 | public static List INDUSTRY_MONEYFLOW = Lists.newArrayList( 208 | "num", //0 序号 209 | "symbol", //1 日期 210 | "name", //2 名称 211 | "change", //3 涨跌幅 212 | "主力净流入-净额", //4 主力净流入-净额 213 | "主力净流入-净占比", //5 主力净流入-净占比 214 | "超大单净流入-净额", //6 超大单净流入-净额 215 | "超大单净流入-净占比", //7 超大单净流入-净占比 216 | "大单净流入-净额", //8 大单净流入-净额 217 | "大单净流入-净占比", //9 大单净流入-净占比 218 | "中单净流入-净额", //10 中单净流入-净额 219 | "中单净流入-净占比", //11 中单净流入-净占比 220 | "小单净流入-净额", //12 小单净流入-净额 221 | "小单净流入-净占比" //13 小单净流入-净占比 222 | ); 223 | } 224 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/common/StockDataParseException.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | public class StockDataParseException extends Exception { 4 | public StockDataParseException() { 5 | } 6 | 7 | public StockDataParseException(String message) { 8 | super(message); 9 | } 10 | 11 | public StockDataParseException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public StockDataParseException(Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | public StockDataParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 20 | super(message, cause, enableSuppression, writableStackTrace); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/downloader/AjaxDownloader.java: -------------------------------------------------------------------------------- 1 | package net.jquant.downloader; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.firefox.FirefoxDriver; 5 | 6 | /** 7 | * author: eryk 8 | * mail: xuqi86@gmail.com 9 | * date: 15-8-31. 10 | */ 11 | public class AjaxDownloader{ 12 | 13 | public static String download(String url) { 14 | WebDriver driver = new FirefoxDriver(); 15 | driver.get(url); 16 | String source = driver.getPageSource(); 17 | driver.close(); 18 | return source; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/downloader/BasicDownloader.java: -------------------------------------------------------------------------------- 1 | package net.jquant.downloader; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.collect.Maps; 5 | import com.mashape.unirest.http.HttpResponse; 6 | import com.mashape.unirest.http.Unirest; 7 | import com.mashape.unirest.http.exceptions.UnirestException; 8 | import org.apache.commons.io.IOUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | import java.net.URL; 17 | import java.net.URLConnection; 18 | import java.nio.charset.Charset; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * author: eryk 24 | * mail: xuqi86@gmail.com 25 | * date: 15-8-15. 26 | */ 27 | public class BasicDownloader { 28 | private static final Logger LOG = LoggerFactory.getLogger(BasicDownloader.class); 29 | 30 | private static int RETRY_COUNT = 1; 31 | 32 | public static String download(String url,Map header){ 33 | int retry = 0; 34 | while(retry<=RETRY_COUNT){ 35 | try { 36 | HttpResponse response = null; 37 | if(header.size() > 0){ 38 | response = Unirest.get(url).headers(header).asString(); 39 | }else{ 40 | response = Unirest.get(url).asString(); 41 | } 42 | if(response.getStatus()==200){ 43 | return response.getBody(); 44 | }else{ 45 | LOG.warn("status="+response.getStatus()+",url="+url); 46 | } 47 | retry++; 48 | } catch (UnirestException e) { 49 | retry++; 50 | LOG.error("retry="+retry+",fail to download from " + url); 51 | try { 52 | Thread.sleep(1000); 53 | } catch (InterruptedException e1) { 54 | LOG.error("fail to download from" + url); 55 | } 56 | } 57 | } 58 | return ""; 59 | } 60 | 61 | public static String download(String url) { 62 | return download(url, Maps.newHashMap()); 63 | } 64 | 65 | public static InputStream downloadStream(String url) { 66 | int retry = 0; 67 | while(retry<=RETRY_COUNT){ 68 | try { 69 | HttpResponse response = Unirest.get(url).asString(); 70 | if(response.getStatus()==200){ 71 | return response.getRawBody(); 72 | }else{ 73 | LOG.warn("status="+response.getStatus()+",url="+url); 74 | } 75 | retry++; 76 | } catch (UnirestException e) { 77 | retry++; 78 | LOG.error("fail to download from " + url); 79 | } 80 | } 81 | return null; 82 | } 83 | 84 | public static String download(String url,String encoding){ 85 | InputStream inputStream = downloadStream(url); 86 | if(inputStream!=null){ 87 | try { 88 | List strings = IOUtils.readLines(inputStream, encoding); 89 | return Joiner.on("\n").join(strings); 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | return ""; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/downloader/Downloader.java: -------------------------------------------------------------------------------- 1 | package net.jquant.downloader; 2 | 3 | /** 4 | * author: eryk 5 | * mail: xuqi86@gmail.com 6 | * date: 15-8-15. 7 | */ 8 | public class Downloader { 9 | 10 | public static String download(String url){ 11 | return BasicDownloader.download(url); 12 | } 13 | 14 | public static String download(String url,String encoding){ 15 | return BasicDownloader.download(url,encoding); 16 | } 17 | 18 | public static String downloadAjaxData(String url){ 19 | return AjaxDownloader.download(url); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/downloader/JsonpParser.java: -------------------------------------------------------------------------------- 1 | package net.jquant.downloader; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.gson.Gson; 5 | 6 | import java.util.Map; 7 | 8 | public class JsonpParser { 9 | 10 | public static Map parser(String jsonStr) { 11 | if (Strings.isNullOrEmpty(jsonStr)) { 12 | return null; 13 | } 14 | String json = jsonStr.substring(jsonStr.indexOf("(") + 1 , jsonStr.length() -1); 15 | Gson gson = new Gson(); 16 | return gson.fromJson(json, Map.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/downloader/THSJSDownloader.java: -------------------------------------------------------------------------------- 1 | package net.jquant.downloader; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.util.Map; 6 | 7 | public class THSJSDownloader { 8 | 9 | public static Map download(String urlBase, String symbol){ 10 | return download(String.format(urlBase,symbol)); 11 | } 12 | 13 | public static Map download(String url){ 14 | Map header = Maps.newHashMap(); 15 | header.put("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36"); 16 | String value = BasicDownloader.download(url,header); 17 | Map ret = JsonpParser.parser(value); 18 | if (ret.size() == 0) { 19 | return null; 20 | } 21 | return ret; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/ArticleType.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | 4 | import net.jquant.common.Utils; 5 | 6 | /** 7 | * author: eryk 8 | * mail: xuqi86@gmail.com 9 | * date: 15-8-19. 10 | */ 11 | public enum ArticleType { 12 | FINANCIAL_STATEMENTS(0), //财务报表 13 | RESEARCH_REPORT(1), //研究报告 14 | NOTICE(2), //公告 15 | COMMENT(3), //评论 16 | NEWS(4); //新闻 17 | 18 | private byte[] type; 19 | 20 | ArticleType(int type){ 21 | this.type = Utils.toBytes(type); 22 | } 23 | 24 | public byte[] getType(){ 25 | return this.type; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/Bar.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * author: eryk 7 | * mail: xuqi86@gmail.com 8 | * date: 15-11-17. 9 | */ 10 | public class Bar { 11 | private long id; 12 | private Date date; //close time 13 | private double open; 14 | private double high; 15 | private double low; 16 | private double close; 17 | private int volume; 18 | private double amount; 19 | 20 | public Bar(long id,Tick tick){ 21 | this.id = id; 22 | this.date = tick.date; 23 | this.open = tick.price; 24 | this.high = tick.price; 25 | this.low = tick.price; 26 | this.close = tick.price; 27 | this.volume = tick.volume; 28 | this.amount = tick.amount; 29 | } 30 | 31 | public void addTick(Tick tick){ 32 | this.date = tick.date; 33 | if(this.high tick.price){ 37 | this.low = tick.price; 38 | } 39 | this.close = tick.price; 40 | this.volume += tick.volume; 41 | this.amount += tick.amount; 42 | } 43 | 44 | public void appendBar(Bar bar){ 45 | 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "Bar{" + 51 | "id=" + id + 52 | ", date=" + date + 53 | ", open=" + open + 54 | ", high=" + high + 55 | ", low=" + low + 56 | ", close=" + close + 57 | ", volume=" + volume + 58 | ", amount=" + amount + 59 | '}'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/BoardType.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | /** 4 | * author: eryk 5 | * mail: xuqi86@gmail.com 6 | * date: 15-8-12. 7 | */ 8 | public enum BoardType { 9 | MAIN, //主板 10 | SME, //中小板 11 | GEM, //创业板 12 | UNKNOW; 13 | 14 | public static BoardType getType(String symbol){ 15 | if(symbol.startsWith("6") || symbol.startsWith("000")){ 16 | return MAIN; 17 | }else if(symbol.startsWith("002")){ 18 | return SME; 19 | }else if(symbol.startsWith("3")){ 20 | return GEM; 21 | }else{ 22 | return UNKNOW; 23 | } 24 | } 25 | 26 | public boolean isMatchType(String symbol){ 27 | if(this.equals(MAIN)){ 28 | return symbol.startsWith("6") || symbol.startsWith("000"); 29 | }else if(this.equals(SME)){ 30 | return symbol.startsWith("002"); 31 | }else if(this.equals(GEM)){ 32 | return symbol.startsWith("3"); 33 | } 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/PeriodType.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | 4 | import net.jquant.common.Constants; 5 | 6 | /** 7 | * author: eryk 8 | * mail: xuqi86@gmail.com 9 | * date: 15-8-9. 10 | */ 11 | public enum PeriodType { 12 | ONE_MIN("1"), 13 | FIVE_MIN("5"), 14 | FIFTEEN_MIN("15"), 15 | THIRTY_MIN("30"), 16 | SIXTY_MIN("60"), 17 | DAY("daily"), 18 | WEEK("week"), 19 | MONTH("month"), 20 | YEAR("year"); 21 | 22 | private String type; 23 | 24 | private PeriodType(String type){ 25 | this.type = type; 26 | } 27 | 28 | public String getType() { 29 | return type; 30 | } 31 | 32 | public int getIntValue(){ 33 | int val = 0; 34 | switch (this) { 35 | case ONE_MIN: 36 | val = Constants.MINUTE; 37 | break; 38 | case FIVE_MIN: 39 | val = Constants.MINUTES_5; 40 | break; 41 | case FIFTEEN_MIN: 42 | val = Constants.MINUTES_15; 43 | break; 44 | case THIRTY_MIN: 45 | val = Constants.MINUTES_30; 46 | break; 47 | case SIXTY_MIN: 48 | val = Constants.MINUTES_60; 49 | break; 50 | case DAY: 51 | val = Constants.DAY; 52 | break; 53 | case WEEK: 54 | val = Constants.WEEK; 55 | break; 56 | } 57 | return val; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/StockBlock.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 股票版块 9 | */ 10 | public class StockBlock { 11 | 12 | //http://quote.eastmoney.com/center/list.html#28002458_0_2, id=28002458 13 | public String id; 14 | 15 | public String name; 16 | 17 | public String type; //版块类型:概念,地域,行业 18 | 19 | public String url; 20 | 21 | public List symbolList = Lists.newLinkedList(); 22 | 23 | public void add(String symbol){ 24 | symbolList.add(symbol); 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "StockBlock{" + 30 | "url='" + url + '\'' + 31 | ", name='" + name + '\'' + 32 | ", id='" + id + '\'' + 33 | '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/StockData.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | import com.google.common.collect.Maps; 4 | import net.jquant.common.Utils; 5 | 6 | import java.util.Date; 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * author: eryk 12 | * mail: xuqi86@gmail.com 13 | * date: 15-8-15. 14 | */ 15 | public class StockData extends LinkedHashMap implements Comparable { 16 | 17 | /** 18 | * 股票代码 19 | */ 20 | public String symbol; 21 | 22 | /** 23 | * 股票名称 24 | */ 25 | public String name; 26 | 27 | /** 28 | * 数据时间点 29 | */ 30 | public Date date; 31 | 32 | /** 33 | * 版块信息:主版,中小板,创业板 34 | */ 35 | public BoardType boardType; 36 | 37 | /** 38 | * 市场:深市,沪市 39 | */ 40 | public StockMarketType stockMarketType; 41 | 42 | /** 43 | * 属性值 44 | */ 45 | public Map attribute = Maps.newHashMap(); 46 | 47 | public StockData() { 48 | } 49 | 50 | public StockData(String symbol) { 51 | this.symbol = symbol; 52 | this.stockMarketType = StockMarketType.getType(symbol); 53 | this.boardType = BoardType.getType(symbol); 54 | } 55 | 56 | public StockData(Map map) { 57 | this.putAll(map); 58 | } 59 | 60 | public void attr(String key, String val) { 61 | attribute.put(key, val); 62 | } 63 | 64 | public String attr(String key) { 65 | return attribute.get(key); 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "StockData{" + 71 | "symbol='" + symbol + '\'' + 72 | ", name='" + name + '\'' + 73 | ", date=" + Utils.formatDate(date, "yyyy-MM-dd HH:mm:ss") + 74 | ", boardType=" + boardType + 75 | ", stockMarketType=" + stockMarketType + 76 | ", stockData=" + Utils.map2Json(this) + 77 | ", stockAttribute= " + Utils.map2Json(attribute) + 78 | '}'; 79 | } 80 | 81 | @Override 82 | public int compareTo(StockData stockData) { 83 | int compare = this.symbol.compareTo(stockData.symbol); 84 | if (compare != 0) { 85 | return compare; 86 | } 87 | return this.date.compareTo(stockData.date); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/StockMarketType.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | /** 4 | * author: eryk 5 | * mail: xuqi86@gmail.com 6 | * date: 15-8-12. 7 | */ 8 | public enum StockMarketType { 9 | SH,SZ,UNKNOW; 10 | 11 | public static StockMarketType getType(String symbol){ 12 | if(symbol.startsWith("6")){ 13 | return SH; 14 | }else if(symbol.startsWith("0") || symbol.startsWith("3")){ 15 | return SZ; 16 | }else{ 17 | return UNKNOW; 18 | } 19 | } 20 | 21 | public boolean isMatchType(String symbol){ 22 | if(this.equals(SH)){ 23 | return symbol.startsWith("6"); 24 | } 25 | if(this.equals(SZ)){ 26 | return symbol.startsWith("0") || symbol.startsWith("3"); 27 | } 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/model/StockSlice.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import net.jquant.common.Constants; 6 | import net.jquant.common.Utils; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * author: eryk 13 | * mail: xuqi86@gmail.com 14 | * date: 15-8-9. 15 | */ 16 | public class StockSlice { 17 | public String symbol; 18 | public String startDate; 19 | public String stopDate; 20 | public PeriodType type; 21 | 22 | public List stocks; 23 | 24 | 25 | public Map> points; 26 | 27 | public static StockSlice getSlice(String symbol,List stocks, String start, String stop) { 28 | return new StockSlice(symbol,stocks, start, stop); 29 | } 30 | 31 | private StockSlice(String symbol,List stocks, String start, String stop) { 32 | this.symbol = symbol; 33 | this.startDate = start; 34 | this.stopDate = stop; 35 | this.stocks = stocks; 36 | points = Maps.newHashMapWithExpectedSize(stocks.size()); 37 | setClosePrice(); 38 | setVolumes(); 39 | } 40 | 41 | public List getStocks(){ 42 | return stocks; 43 | } 44 | 45 | public void setClosePrice() { 46 | List closes = points.get(Utils.toString(Constants.CLOSE)); 47 | if(closes==null){ 48 | closes = Lists.newLinkedList(); 49 | } 50 | for (StockData stock : stocks) { 51 | closes.add(stock.get("close")); 52 | } 53 | points.put(Utils.toString(Constants.CLOSE), closes); 54 | } 55 | 56 | public double[] getClose(){ 57 | return getValues(Utils.toString(Constants.CLOSE)); 58 | } 59 | 60 | public void setVolumes(){ 61 | List volumes = points.get(Utils.toString(Constants.VOLUME)); 62 | if(volumes==null){ 63 | volumes = Lists.newLinkedList(); 64 | } 65 | for (StockData stock : stocks) { 66 | volumes.add(stock.get("volume")); 67 | } 68 | points.put(Utils.toString(Constants.VOLUME), volumes); 69 | } 70 | 71 | public double[] getVolumes(){ 72 | return getValues(Utils.toString(Constants.VOLUME)); 73 | } 74 | 75 | public double[] getValues(String property) { 76 | List values = points.get(property); 77 | if (values == null) { 78 | values = Lists.newArrayList(); 79 | } 80 | double[] result = new double[values.size()]; 81 | for(int i=0;i getDailyDataWithURL(String symbol,String url){ 41 | List stocks = Lists.newLinkedList(); 42 | try { 43 | String data = Downloader.download(url, "gb2312"); 44 | String[] lines = data.split("\n"); 45 | 46 | for (int i = 1; i < lines.length; i++) { //第一行是标题,跳过 47 | String[] line = lines[i].split(","); 48 | //股票数据15列,指数数据13列,少最后两列 49 | if ((line.length == 15 || line.length == 13) && !lines[i].contains("None")) { 50 | try { 51 | StockData stock = new StockData(line[1].replace("'", "")); 52 | 53 | stock.date = Utils.str2Date(line[0], Constants.NETEASE_DATE_STYLE); 54 | stock.name = line[2]; 55 | for (int j = 0; j < StockConstants.DAILY.size() - 1; j++) { 56 | if(line.length>j+3){ 57 | stock.put(StockConstants.DAILY.get(j), Utils.str2Double(line[j + 3])); 58 | } 59 | } 60 | stock.put("amplitude", Utils.formatDouble((stock.get("high") - stock.get("low")) / stock.get("lastClose"))); 61 | changeUnit(stock); 62 | stocks.add(stock); 63 | } catch (Exception e) { 64 | LOG.warn(String.format("stock %s convert error %s", symbol,lines[i]),e); 65 | } 66 | } 67 | } 68 | 69 | } catch (Exception e) { 70 | LOG.error(String.format("stock %s collect error", symbol), e); 71 | } 72 | //按照时间从最早到最新 73 | return Lists.reverse(stocks); 74 | } 75 | 76 | /** 77 | * 获取指数日线数据 78 | * 上证综指:0000001 79 | * 深证成指:1399001 80 | * 深证综指:1399106 81 | * 沪深300:0000300 82 | * 创业板指:1399006 83 | * 创业板综:1399102 84 | * 中小板指:1399005 85 | * 中小板综:1399101 86 | * @param symbol stock symbol 87 | * @param startDate yyyyMMdd 88 | * @param stopDate yyyyMMdd 89 | * @return data list 90 | */ 91 | public static List getIndex(String symbol, String startDate, String stopDate){ 92 | String url; 93 | if(symbol.startsWith("3")){ 94 | url = getPath(symbol,startDate,stopDate); 95 | }else{ 96 | url = String.format(DAILY_DATA_URL,"0"+symbol,startDate,stopDate); 97 | } 98 | return getDailyDataWithURL(symbol,url); 99 | } 100 | 101 | /** 102 | * 获取未复权数据 103 | * @param symbol stock symbol 104 | * @param startDate yyyyMMdd 105 | * @param stopDate yyyyMMdd 106 | * @return data list 107 | */ 108 | public static List get(String symbol,String startDate,String stopDate) throws StockDataParseException { 109 | String lastUrl = "http://d.10jqka.com.cn/v2/line/hs_%s/01/last.js"; 110 | Map stringObjectMap = THSJSDownloader.download(lastUrl, symbol); 111 | Map years = (Map) stringObjectMap.get("year"); 112 | String yearUrl = "http://d.10jqka.com.cn/v2/line/hs_%s/01/%s.js"; 113 | List stockDatas = Lists.newLinkedList(); 114 | for (String year : years.keySet()) { 115 | Map tmp = THSJSDownloader.download(String.format(yearUrl, symbol, year)); 116 | if (tmp == null || tmp.size() == 0){ 117 | throw new StockDataParseException(); 118 | } 119 | String data = (String) tmp.get("data"); 120 | if(data == null){ 121 | throw new StockDataParseException(); 122 | } 123 | String[] array = data.split(";"); 124 | for(String line:array){ 125 | StockData stockData = parseDailyData(symbol,line); 126 | if(stockData != null){ 127 | stockDatas.add(stockData); 128 | }else{ 129 | throw new StockDataParseException(); 130 | } 131 | } 132 | } 133 | if(stockDatas.size() > 0){ 134 | StockData stockData = stockDatas.get(stockDatas.size()-1); 135 | if(!Utils.isToday(stockData.date)){ 136 | stockDatas.add(today(symbol)); 137 | } 138 | } 139 | return stockDatas; 140 | } 141 | 142 | private static StockData today(String symbol) throws StockDataParseException{ 143 | String url = "http://d.10jqka.com.cn/v2/line/hs_%s/01/today.js"; 144 | Map download = THSJSDownloader.download(url, symbol); 145 | if(download == null || download.size() == 0){ 146 | throw new StockDataParseException(); 147 | } 148 | Map values = (Map) download.get("hs_" + symbol); 149 | if(values == null || values.size() == 0){ 150 | throw new StockDataParseException(); 151 | } 152 | StockData stockData = new StockData(symbol); 153 | stockData.date = DateTimeFormat.forPattern("yyyyMMddHHmm").parseLocalDateTime( 154 | String.valueOf(values.get("1")) + String.valueOf(values.get("dt"))).toDate(); 155 | stockData.put("open",Double.parseDouble((String) values.get("7"))); 156 | stockData.put("high",Double.parseDouble((String) values.get("8"))); 157 | stockData.put("low",Double.parseDouble((String) values.get("9"))); 158 | stockData.put("close",Double.parseDouble((String) values.get("11"))); 159 | stockData.put("volume", (Double) values.get("13") /100); 160 | stockData.put("amount",Double.parseDouble((String) values.get("19")) /10000); 161 | stockData.put("turnover",Double.parseDouble((String) values.get("1968584"))); 162 | return stockData; 163 | } 164 | 165 | private static StockData parseDailyData(String symbol,String line){ 166 | String[] fields = line.split(","); 167 | if(fields.length != 8){ 168 | return null; 169 | } 170 | DateTime dateTime = DateTimeFormat.forPattern("yyyyMMdd").parseDateTime(fields[0]); 171 | StockData stockData = new StockData(symbol); 172 | stockData.date = dateTime.toDate(); 173 | stockData.put("open",Double.parseDouble(fields[1])); 174 | stockData.put("high",Double.parseDouble(fields[2])); 175 | stockData.put("low",Double.parseDouble(fields[3])); 176 | stockData.put("close",Double.parseDouble(fields[4])); 177 | stockData.put("volume",Double.parseDouble(fields[5])/100); 178 | if(!Strings.isNullOrEmpty(fields[6])){ 179 | stockData.put("amount",Double.parseDouble(fields[6])/10000); 180 | }else{ 181 | stockData.put("amount",0d); 182 | } 183 | stockData.put("turnover",Double.parseDouble(fields[7])); 184 | return stockData; 185 | } 186 | 187 | //http://d.10jqka.com.cn/v2/line/hs_600133/01/2015.js 188 | public static String FQ_URL = "http://d.10jqka.com.cn/v2/line/hs_%s/01/%s.js"; 189 | 190 | private static void changeUnit(StockData stockData) { 191 | stockData.put(StockConstants.VOLUME, stockData.get(StockConstants.VOLUME) / 100); //成交量,单位:手 192 | stockData.put(StockConstants.AMOUNT, stockData.get(StockConstants.AMOUNT) / 10000); //成交金额,单位:万 193 | //指数数据没有下面两项 194 | if(stockData.get(StockConstants.TOTAL_VALUE)!=null){ 195 | stockData.put(StockConstants.TOTAL_VALUE, stockData.get(StockConstants.TOTAL_VALUE) / 100000000); //总市值,单位:亿 196 | } 197 | if(stockData.get(StockConstants.MARKET_VALUE)!=null){ 198 | stockData.put(StockConstants.MARKET_VALUE, stockData.get(StockConstants.MARKET_VALUE) / 100000000); //流通市值,单位:亿 199 | } 200 | } 201 | 202 | /** 203 | * @param symbol stock symbol 204 | * @param startDate yyyyMMdd 205 | * @param stopDate yyyyMMdd 206 | * @return url path with start and stop date 207 | */ 208 | public static String getPath(String symbol, String startDate, String stopDate) { 209 | return String.format(DAILY_DATA_URL, Symbol.getSymbol(symbol, DAILY_DATA_URL), startDate, stopDate); 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/FinanceDataProvider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Maps; 6 | 7 | import org.joda.time.DateTime; 8 | import net.jquant.common.DateRange; 9 | import net.jquant.common.Utils; 10 | import net.jquant.downloader.Downloader; 11 | import net.jquant.model.StockData; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * author: eryk 18 | * mail: xuqi86@gmail.com 19 | * date: 15-8-30. 20 | * 财务报表数据 21 | */ 22 | public class FinanceDataProvider { 23 | 24 | //主要财务指标 25 | private static String mainFinanceReport = "http://quotes.money.163.com/service/zycwzb_%s.html?type=report"; 26 | //盈利能力 27 | private static String profitReport = "http://quotes.money.163.com/service/zycwzb_%s.html?type=report&part=ylnl"; 28 | //偿还能力 29 | private static String debtReport = "http://quotes.money.163.com/service/zycwzb_%s.html?type=report&part=chnl"; 30 | //成长能力 31 | private static String growReport = "http://quotes.money.163.com/service/zycwzb_%s.html?type=report&part=cznl"; 32 | //营运能力 33 | private static String operateReport = "http://quotes.money.163.com/service/zycwzb_%s.html?type=report&part=yynl"; 34 | //财务报表摘要 35 | private static String abstractFinanceReport = "http://quotes.money.163.com/service/cwbbzy_%s.html"; 36 | 37 | public static List get(String symbol, String startDate, String stopDate) { 38 | Map> reports = collect(symbol); 39 | List puts = Lists.newLinkedList(); 40 | for(Map.Entry> entry:reports.entrySet()){ 41 | StockData stockData = new StockData(symbol); 42 | stockData.date = Utils.str2Date(entry.getKey(), "yyyy-MM-dd"); 43 | 44 | if(Utils.isInRange(stockData.date,startDate,stopDate)){ 45 | for(Map.Entry kv:entry.getValue().entrySet()){ 46 | if(Utils.isDouble(kv.getValue())){ 47 | stockData.put(kv.getKey(),Double.parseDouble(kv.getValue())); 48 | }else{ 49 | stockData.put(kv.getKey(),Double.MIN_VALUE); 50 | } 51 | } 52 | puts.add(stockData); 53 | } 54 | } 55 | return puts; 56 | } 57 | 58 | public static List getYear(String symbol){ 59 | DateRange range = DateRange.getRange(365*20); 60 | List stockDataList = get(symbol,range.start(),range.stop()); 61 | List stockDataYearList = Lists.newArrayList(); 62 | for(StockData stockData:stockDataList){ 63 | DateTime time = new DateTime(stockData.date); 64 | if(time.getMonthOfYear()==12){ 65 | stockDataYearList.add(stockData); 66 | } 67 | } 68 | return stockDataYearList; 69 | } 70 | 71 | private static Map> collect(String symbol) { 72 | Map> report = Maps.newTreeMap(); 73 | 74 | String[] lines = Downloader.download(String.format(mainFinanceReport, symbol)).split("\n"); 75 | toReport(lines, report); 76 | lines = Downloader.download(String.format(profitReport, symbol)).split("\n"); 77 | toReport(lines, report); 78 | lines = Downloader.download(String.format(debtReport, symbol)).split("\n"); 79 | toReport(lines, report); 80 | lines = Downloader.download(String.format(growReport, symbol)).split("\n"); 81 | toReport(lines, report); 82 | lines = Downloader.download(String.format(operateReport, symbol)).split("\n"); 83 | toReport(lines, report); 84 | lines = Downloader.download(String.format(abstractFinanceReport, symbol)).split("\n"); 85 | toReport(lines, report); 86 | 87 | return report; 88 | } 89 | 90 | private static void toReport(String[] lines, Map> report) { 91 | if(lines.length<=1){ 92 | return; 93 | } 94 | List columns = Lists.newArrayList(); 95 | for(int i=0;i maps = Maps.newTreeMap(); 104 | //行 105 | for(int j=1;j get(String symbol, String startDate, String stopDate, String type){ 41 | Map> results = collect(symbol, Utils.str2Date(startDate,"yyyyMMdd"),Utils.str2Date(stopDate,"yyyyMMdd"),type); 42 | 43 | List stockList = Lists.newLinkedList(); 44 | for(Map.Entry> entry:results.entrySet()) { 45 | StockData stockData = new StockData(); 46 | stockData.stockMarketType = StockMarketType.getType(symbol); 47 | stockData.boardType = BoardType.getType(symbol); 48 | stockData.symbol = symbol; 49 | stockData.date = Utils.str2Date(entry.getValue().get("day"), "yyyy-MM-dd HH:mm:ss"); 50 | 51 | stockData.put("open",Utils.str2Double(entry.getValue().get("open"))); 52 | stockData.put("high",Utils.str2Double(entry.getValue().get("high"))); 53 | stockData.put("low",Utils.str2Double(entry.getValue().get("low"))); 54 | stockData.put("close",Utils.str2Double(entry.getValue().get("close"))); 55 | stockData.put("volume",Utils.str2Double(entry.getValue().get("volume"))/100); //成交量 单位:手 56 | 57 | stockList.add(stockData); 58 | } 59 | return stockList; 60 | } 61 | 62 | private static Map> collect(String symbol,Date startDate,Date stopDate,String type) { 63 | String url = getPath(symbol,type); 64 | Map> stocks = Maps.newTreeMap(); 65 | String data = Downloader.download(url); 66 | 67 | Pattern pattern = Pattern.compile("\\{([\\w|\"|,|:|\\s|.|-]*)\\}"); 68 | Matcher matcher = pattern.matcher(data.trim()); 69 | Gson gson = new Gson(); 70 | while (matcher.find()) { 71 | //{day:"2015-08-13 13:55:00",open:"16.300",high:"16.320",low:"16.270",close:"16.290",volume:"390800"} 72 | Map map = gson.fromJson(matcher.group(), Map.class); 73 | Date date = Utils.str2Date(map.get("day"), "yyyy-MM-dd HH:mm:ss"); 74 | if (date.getTime() >= startDate.getTime() && date.getTime() <=stopDate.getTime()) { 75 | stocks.put(Utils.toString(Utils.getRowkeyWithMd5PrefixAndDateSuffix(symbol, Utils.formatDate(date, "yyyyMMddHHmm"))), map); 76 | } 77 | } 78 | 79 | return stocks; 80 | } 81 | 82 | public static String getPath(String symbol,String type) { 83 | return String.format(minuteDataURL, Symbol.getSymbol(symbol, minuteDataURL), type); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/Provider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import net.jquant.common.StockDataParseException; 4 | import net.jquant.common.Utils; 5 | import net.jquant.model.StockBlock; 6 | import net.jquant.model.StockData; 7 | import net.jquant.model.Tick; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import net.jquant.common.DateRange; 11 | import net.jquant.tools.StockCategory; 12 | import net.jquant.tools.StockList; 13 | 14 | import java.util.*; 15 | 16 | /** 17 | * 成交量,单位:手 18 | * 成交金额,单位:万 19 | * 总市值,单位:亿 20 | * 流通市值,单位:亿 21 | * 外盘,单位:手 22 | * 内盘,单位:手 23 | */ 24 | public class Provider { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(Provider.class); 27 | 28 | /** 29 | * 获取指定时间段内的日线股票数据 30 | * 31 | * @param symbol stock symbol 32 | * @param startDate 格式:yyyyMMdd 33 | * @param stopDate 格式:yyyyMMdd 34 | * @return stock data list 35 | */ 36 | public static List dailyData(String symbol, String startDate, String stopDate) throws StockDataParseException { 37 | return DailyDataProvider.get(symbol, startDate, stopDate); 38 | } 39 | 40 | /** 41 | * 获取日线级别最近250天历史数据 42 | * 43 | * @param symbol stock symbol 44 | * @return stock data list 45 | */ 46 | public static List dailyData(String symbol) throws StockDataParseException { 47 | DateRange range = DateRange.getRange(250); 48 | return dailyData(symbol, range.start(), range.stop()); 49 | } 50 | 51 | /** 52 | * 获取指数日线数据 53 | * 上证综指:0000001 54 | * 深证成指:1399001 55 | * 深证综指:1399106 56 | * 沪深300:0000300 57 | * 创业板指:1399006 58 | * 创业板综:1399102 59 | * 中小板指:1399005 60 | * 中小板综:1399101 61 | * 62 | * @param symbol index symbol 63 | * @param startDate yyyyMMdd 64 | * @param stopDate yyyyMMdd 65 | * @return stock data list 66 | */ 67 | public static List dailyDataZS(String symbol, String startDate, String stopDate) throws StockDataParseException { 68 | return DailyDataProvider.getIndex(symbol, startDate, stopDate); 69 | } 70 | 71 | public static List dailyDataZS(String symbol, int period) throws StockDataParseException { 72 | DateRange dateRange = DateRange.getRange(period); 73 | return dailyDataZS(symbol, dateRange.start(), dateRange.stop()); 74 | } 75 | 76 | public static List dailyDataZS(String symbol) throws StockDataParseException { 77 | DateRange dateRange = DateRange.getRange(250); 78 | return dailyDataZS(symbol, dateRange.start(), dateRange.stop()); 79 | } 80 | 81 | /** 82 | * 获取实时数据股票数据 83 | * 84 | * @param symbol stock symbol 85 | * @return stock data list 86 | */ 87 | public static StockData realtimeData(String symbol) throws StockDataParseException { 88 | return RealTimeDataProvider.get(symbol); 89 | } 90 | 91 | /** 92 | * 获取最新10天股票分钟级别数据 93 | * 94 | * @param symbol stock symbol 95 | * @param type 参数值:5,15,30,60 96 | * @return stock data list 97 | */ 98 | public static List minuteData(String symbol, String type) throws StockDataParseException { 99 | DateRange range = DateRange.getRange(120);//数据源不够30天 100 | List stockDataList = minuteData(symbol, range.start(), range.stop(), type); 101 | return stockDataList; 102 | } 103 | 104 | /** 105 | * 获取指定时间段内历史分钟级别数据,受数据源限制 106 | * 107 | * @param symbol stock symbol 108 | * @param startDate 格式:yyyyMMdd 109 | * @param stopDate 格式:yyyyMMdd 110 | * @param type 5,15,30,60 111 | * @return stock data list 112 | */ 113 | public static List minuteData(String symbol, String startDate, String stopDate, String type) throws StockDataParseException { 114 | return MinuteDataProvider.get(symbol, startDate, stopDate, type); 115 | } 116 | 117 | /** 118 | * 获取个股当日资金流数据 119 | * 120 | * @param symbol stock symbol 121 | * @return stock data list 122 | */ 123 | public static StockData moneyFlowData(String symbol) { 124 | return MoneyFlowDataProvider.get(symbol); 125 | } 126 | 127 | /** 128 | * 获取个股指定时间段内资金流数据,受数据源限制 129 | * 130 | * @param symbol stock symbol 131 | * @param startDate 格式:yyyyMMdd 132 | * @param stopDate 格式:yyyyMMdd 133 | * @return stock data list 134 | */ 135 | public static List moneyFlowData(String symbol, String startDate, String stopDate) throws StockDataParseException { 136 | return MoneyFlowDataProvider.get(symbol, startDate, stopDate); 137 | } 138 | 139 | /** 140 | * 大盘资金流向历史数据 141 | * 142 | * @return stock data list 143 | */ 144 | public static List moneyFlowDapanData() throws StockDataParseException { 145 | return MoneyFlowDataProvider.getDapan(); 146 | } 147 | 148 | /** 149 | * 获取今天、5日、10日行业版块资金流数据 150 | * 151 | * @param type 1,5,10 152 | * @return stock data list 153 | */ 154 | public static List moneyFlowIndustryData(String type) throws StockDataParseException { 155 | return MoneyFlowDataProvider.getIndustry(type); 156 | } 157 | 158 | /** 159 | * 获取今天、5日、10日行业版块资金流数据 160 | * 161 | * @param type 输入值:1,5,10 162 | * @return stock data list 163 | */ 164 | public static List moneyFlowConceptData(String type) throws StockDataParseException { 165 | return MoneyFlowDataProvider.getConcept(type); 166 | } 167 | 168 | /** 169 | * 获取今天、5日、10日行业版块资金流数据 170 | * 171 | * @param type 1,5,10 172 | * @return stock data list 173 | */ 174 | public static List moneyFlowRegionData(String type) throws StockDataParseException { 175 | return MoneyFlowDataProvider.getRegion(type); 176 | } 177 | 178 | /** 179 | * 历史财报 180 | * 181 | * @param symbol stock symbol 182 | * @param startDate 格式:yyyyMMdd 183 | * @param stopDate 格式:yyyyMMdd 184 | * @return stock data list 185 | */ 186 | public static List financeData(String symbol, String startDate, String stopDate) throws StockDataParseException { 187 | return FinanceDataProvider.get(symbol, startDate, stopDate); 188 | } 189 | 190 | /** 191 | * 最新一期财报数据 192 | * 193 | * @param symbol stock symbol 194 | * @return stock data list 195 | */ 196 | public static StockData financeData(String symbol) throws StockDataParseException { 197 | DateRange range = DateRange.getRange(365); 198 | List stockDataList = FinanceDataProvider.get(symbol, range.start(), range.stop()); 199 | if (stockDataList.size() >= 1) { 200 | return stockDataList.get(stockDataList.size() - 1); 201 | } else { 202 | return new StockData(symbol); 203 | } 204 | } 205 | 206 | /** 207 | * 股票年报数据 208 | * 209 | * @param symbol stock symbol 210 | * @return stock data list 211 | */ 212 | public static List financeYearData(String symbol) throws StockDataParseException { 213 | return FinanceDataProvider.getYear(symbol); 214 | } 215 | 216 | /** 217 | * 获取最新一天股票逐笔数据 218 | * 219 | * @param symbol stock symbol 220 | * @return tick list 221 | */ 222 | public static List tickData(String symbol) throws StockDataParseException { 223 | return TickDataProvider.get(symbol); 224 | } 225 | 226 | /** 227 | * 获取指定日期逐笔股票数据 228 | * 229 | * @param symbol stock symbol 230 | * @param date 格式: yyyyMMdd 231 | * @return tick data list 232 | */ 233 | public static List tickData(String symbol, String date) throws StockDataParseException { 234 | String _date = Utils.formatDate(Utils.str2Date(date, "yyyyMMdd"), "yyyy-MM-dd"); 235 | return TickDataProvider.get(symbol, _date); 236 | } 237 | 238 | /** 239 | * 获取股票版块数据 240 | * map key: 股票的版块分类名称,包含三项:概念,地区,行业 241 | * value: list是版块分类下的版块,每个版块包含一个股票列表 242 | * 243 | * @return stock data list 244 | */ 245 | public static Map> stockBlock() throws StockDataParseException { 246 | return StockCategory.getCategory(); 247 | } 248 | 249 | /** 250 | * 获取某个股票在大分类下的具体分类:概念,行业,地域 251 | * 252 | * @param type category 253 | * @return stock data list 254 | */ 255 | public static Map> stockCategory(String type) throws StockDataParseException { 256 | return StockCategory.getStockCategory(type); 257 | } 258 | 259 | /** 260 | * 获取股票列表 261 | * 262 | * @return stock data list 263 | */ 264 | public static List stockList() throws StockDataParseException { 265 | return StockList.getSymbols(); 266 | } 267 | 268 | //计算是今天第几个bar,从1开始 269 | private long getTimeSlot(long curTime, long startTime, int interval) { 270 | return (curTime - startTime) / interval; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/RealTimeDataProvider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.Maps; 5 | import com.google.gson.Gson; 6 | 7 | import net.jquant.common.StockConstants; 8 | import net.jquant.common.Utils; 9 | import net.jquant.downloader.Downloader; 10 | import net.jquant.downloader.THSJSDownloader; 11 | import net.jquant.model.StockData; 12 | import net.jquant.model.Symbol; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.Date; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | 21 | /** 22 | * author: eryk 23 | * mail: xuqi86@gmail.com 24 | * date: 15-8-30. 25 | */ 26 | public class RealTimeDataProvider { 27 | private static final Logger LOG = LoggerFactory.getLogger(RealTimeDataProvider.class); 28 | 29 | private static String realTimeDateURL = "http://nuff.eastmoney.com/EM_Finance2015TradeInterface/JS.ashx?id=%s&_=%s"; 30 | 31 | private static Map> collect(String symbol) { 32 | String data = Downloader.download(getPath(symbol)); 33 | if (Strings.isNullOrEmpty(data)) { 34 | LOG.error("fail to get real time data from " + getPath(symbol)); 35 | return Maps.newHashMap(); 36 | } 37 | data = data.substring(9, data.length() - 1); 38 | Gson gson = new Gson(); 39 | Map> map = gson.fromJson(data, Map.class); 40 | return map; 41 | } 42 | 43 | /** 44 | * 获取实时股票交易数据 45 | * @param symbol stock symbol 46 | * @return stock data list 47 | */ 48 | public static StockData get(String symbol){ 49 | 50 | Map> map = collect(symbol); 51 | 52 | if(map.size()==0){ 53 | return new StockData(); 54 | } 55 | List columns = map.get("Value"); 56 | if (columns.size() != 53) { 57 | LOG.warn(String.format("stock [%s] data is not format",symbol)); 58 | return new StockData(); 59 | } 60 | 61 | StockData stockData = new StockData(symbol); 62 | stockData.name = columns.get(2); 63 | stockData.date = Utils.str2Date(columns.get(49), "yyyy-MM-dd HH:mm:ss"); 64 | 65 | for (int i = 3; i < columns.size() - 1; i++) { 66 | if (getColumnName(i).equals("amount")) { 67 | stockData.put(getColumnName(i), Utils.getAmount(columns.get(i))); 68 | } else if (!getColumnName(i).equals("")) { 69 | if (Utils.isDouble(columns.get(i))) { 70 | stockData.put(getColumnName(i), Double.parseDouble(columns.get(i))); 71 | } 72 | } 73 | } 74 | changeUnit(stockData); 75 | return stockData; 76 | } 77 | 78 | private static void changeUnit(StockData stockData) { 79 | stockData.put(StockConstants.AMOUNT,stockData.get(StockConstants.AMOUNT)/10000); //成交金额,单位:万 80 | stockData.put(StockConstants.TOTAL_VALUE,stockData.get(StockConstants.TOTAL_VALUE)/100000000); //总市值,单位:亿 81 | stockData.put(StockConstants.MARKET_VALUE, stockData.get(StockConstants.MARKET_VALUE) / 100000000); //流通市值,单位:亿 82 | } 83 | 84 | /** 85 | * 获取原始数据对应的列名称 86 | * @param i column id 87 | * @return column name 88 | */ 89 | private static String getColumnName(int i) { 90 | return StockConstants.REALTIME.get(i); 91 | } 92 | 93 | public static String getPath(String symbol) { 94 | Date date = new Date(); 95 | return String.format(realTimeDateURL, Symbol.getSymbol(symbol, realTimeDateURL), date.getTime()); 96 | } 97 | 98 | /** 99 | * http://d.10jqka.com.cn/v2/fiverange/hs_300033/last.js 100 | * 101 | * @return 五挡买卖数据 102 | */ 103 | private StockData fiveRange(String symbol) { 104 | String url = "http://d.10jqka.com.cn/v2/fiverange/hs_%s/last.js"; 105 | Map result = THSJSDownloader.download(url, symbol); 106 | Map parser = (Map) result.get("items"); 107 | StockData stockData = new StockData(symbol); 108 | if (parser != null) { 109 | if (parser.size() > 0) { 110 | stockData.put("buy1", Double.parseDouble(parser.get("24"))); 111 | stockData.put("buy1Volume", Double.parseDouble(parser.get("25")) / 100); 112 | stockData.put("buy2", Double.parseDouble(parser.get("26"))); 113 | stockData.put("buy2Volume", Double.parseDouble(parser.get("27")) / 100); 114 | stockData.put("buy3", Double.parseDouble(parser.get("28"))); 115 | stockData.put("buy3Volume", Double.parseDouble(parser.get("29")) / 100); 116 | stockData.put("buy4", Double.parseDouble(parser.get("150"))); 117 | stockData.put("buy4Volume", Double.parseDouble(parser.get("151")) / 100); 118 | stockData.put("buy5", Double.parseDouble(parser.get("154"))); 119 | stockData.put("buy5Volume", Double.parseDouble(parser.get("155")) / 100); 120 | stockData.put("sell1", Double.parseDouble(parser.get("30"))); 121 | stockData.put("sell1Volume", Double.parseDouble(parser.get("31")) / 100); 122 | stockData.put("sell2", Double.parseDouble(parser.get("32"))); 123 | stockData.put("sell2Volume", Double.parseDouble(parser.get("33")) / 100); 124 | stockData.put("sell3", Double.parseDouble(parser.get("34"))); 125 | stockData.put("sell3Volume", Double.parseDouble(parser.get("35")) / 100); 126 | stockData.put("sell4", Double.parseDouble(parser.get("152"))); 127 | stockData.put("sell4Volume", Double.parseDouble(parser.get("153")) / 100); 128 | stockData.put("sell5", Double.parseDouble(parser.get("156"))); 129 | stockData.put("sell5Volume", Double.parseDouble(parser.get("157")) / 100); 130 | return stockData; 131 | } 132 | } 133 | return stockData; 134 | } 135 | 136 | public static void main(String[] args) { 137 | StockData stock = RealTimeDataProvider.get("000001"); 138 | System.out.println(stock); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/ReportDataProvider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.Lists; 5 | import com.google.gson.Gson; 6 | import net.jquant.common.Utils; 7 | import net.jquant.downloader.Downloader; 8 | import net.jquant.model.StockData; 9 | 10 | import java.util.Date; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * author: eryk 16 | * mail: xuqi86@gmail.com 17 | * date: 15-9-22. 18 | * 分析师研究报告数据,数据来源 19 | * http://data.eastmoney.com/report/ 20 | */ 21 | public class ReportDataProvider { 22 | 23 | /** 24 | * 个股研究报告地址 25 | * p=页数,一页200条 26 | */ 27 | private static String STOCK_REPORT_URL = "http://datainterface.eastmoney.com//EM_DataCenter/js.aspx?type=SR&sty=GGSR&ps=200&p=%s&mkt=0&stat=0&cmd=2&rt=48097671"; 28 | 29 | /** 30 | * 盈利预期数据 http://data.eastmoney.com/report/ylyc.html 31 | * param1=版块,全部=_A 32 | */ 33 | private static String EXPECT_EARNINGS_URL = "http://nufm.dfcfw.com/EM_Finance2014NumericApplication/JS.aspx?type=CT&cmd=C._A&sty=GEMCPF&st=(AllNum)&sr=-1&p=2&ps=5000&cb=&token=3a965a43f705cf1d9ad7e1a3e429d622&rt=48090871"; 34 | 35 | private static String EXPECT_EARNINGS_PAGE_URL = "http://data.eastmoney.com/report/ylyc.html"; 36 | 37 | 38 | /** 39 | * 个股研究报告 40 | * @param startDate 报告起始时间,格式:yyyyMMdd 41 | * @return stock data list 42 | */ 43 | public static List getStockReportData(String startDate){ 44 | Date start = Utils.str2Date(startDate,"yyyyMMdd"); 45 | 46 | List stockDataList = Lists.newLinkedList(); 47 | int pageCount = 1; 48 | while(pageCount<2){ 49 | String url = String.format(STOCK_REPORT_URL, pageCount); 50 | String data = Downloader.download(url); 51 | Gson gson = new Gson(); 52 | List> list = gson.fromJson(data.substring(1, data.length() - 1), List.class); 53 | for(Map record:list){ 54 | Date date = Utils.str2Date(String.valueOf(record.get("datetime")).replaceAll("T", " "), "yyyy-MM-dd HH:mm:ss"); 55 | if(date.getTime() < start.getTime()){ 56 | break; 57 | } 58 | StockData stockData = new StockData(String.valueOf(record.get("secuFullCode")).substring(0,6)); 59 | stockData.name = String.valueOf(record.get("secuName")); 60 | stockData.date = date; 61 | stockData.attr("上次评级",String.valueOf(record.get("sratingName"))); 62 | stockData.attr("评级",String.valueOf(record.get("rate"))); 63 | stockData.attr("评级变动",String.valueOf(record.get("change"))); 64 | stockData.attr("title",String.valueOf(record.get("title"))); 65 | stockData.attr("机构名称",String.valueOf(record.get("insName"))); 66 | stockData.attr("机构评级",String.valueOf(record.get("insStar"))); //1-5,5是最好 67 | stockData.attr("author",String.valueOf(record.get("author"))); 68 | 69 | fillRecord(record, stockData ,"jlrs","净利润"); 70 | fillRecord(record, stockData ,"sys","每股收益"); 71 | fillRecord(record, stockData ,"syls","市盈率"); 72 | 73 | stockDataList.add(stockData); 74 | } 75 | pageCount++; 76 | } 77 | return stockDataList; 78 | } 79 | 80 | private static void fillRecord(Map record, StockData stockData,String recordKeyName,String stockAttrName) { 81 | List list = (List) record.get(recordKeyName); 82 | for(int i =0;i getExpectEarnings(){ 95 | List stockDataList = Lists.newLinkedList(); 96 | 97 | String data = Downloader.download(EXPECT_EARNINGS_URL); 98 | 99 | Gson gson = new Gson(); 100 | List records = gson.fromJson(data.substring(1, data.length() - 1), List.class); 101 | for(String record:records){ 102 | String[] fields = record.split(",",21); 103 | double yanbaoCount = Double.parseDouble(fields[5]); 104 | if(yanbaoCount<=0){ 105 | break; 106 | } 107 | StockData stockData = new StockData(fields[1]); 108 | stockData.name = fields[2]; 109 | stockData.put("close",checkValueIsDouble(fields[3])); 110 | stockData.put("change",checkValueIsDouble(fields[4].replace("%",""))); 111 | stockData.put("研报数",yanbaoCount); 112 | stockData.put("机构投资评级(近六个月)_买入",checkValueIsDouble(fields[6])); 113 | stockData.put("机构投资评级(近六个月)_增持",checkValueIsDouble(fields[7])); 114 | stockData.put("机构投资评级(近六个月)_中性",checkValueIsDouble(fields[8])); 115 | stockData.put("机构投资评级(近六个月)_减持",checkValueIsDouble(fields[9])); 116 | stockData.put("机构投资评级(近六个月)_卖出",checkValueIsDouble(fields[10])); 117 | stockData.put(Utils.getYear(-1) + "实际_收益",checkValueIsDouble(fields[11])); 118 | stockData.put(Utils.getYear(0) + "预测_收益",checkValueIsDouble(fields[12])); 119 | stockData.put(Utils.getYear(0) + "预测_市盈率",checkValueIsDouble(fields[13])); 120 | stockData.put(Utils.getYear(1) + "预测_收益",checkValueIsDouble(fields[14])); 121 | stockData.put(Utils.getYear(1) + "预测_市盈率",checkValueIsDouble(fields[15])); 122 | stockData.put(Utils.getYear(2) + "预测_收益",checkValueIsDouble(fields[16])); 123 | stockData.put(Utils.getYear(2) + "预测_市盈率",checkValueIsDouble(fields[17])); 124 | stockDataList.add(stockData); 125 | } 126 | return stockDataList; 127 | } 128 | 129 | 130 | private static Double checkValueIsDouble(String field){ 131 | if(Utils.isDouble(field)){ 132 | return Double.parseDouble(field); 133 | } 134 | return Double.MIN_VALUE; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/StockIndexDataProvider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import net.jquant.common.Utils; 6 | import net.jquant.downloader.Downloader; 7 | 8 | import java.util.Date; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * 获取股票指标数据 14 | * 上证指数 15 | * 深证成指 16 | * 创业板指 17 | * 中小板指 18 | * 上证50 19 | * 中证500 20 | * 沪深300 21 | * 纳斯达克 22 | * 道琼斯 23 | * 标普指数 24 | */ 25 | public class StockIndexDataProvider { 26 | 27 | private static String stockIndexURL = "http://hq.sinajs.cn/rn=%s&list=s_sh000001,s_sz399001,s_sz399006,s_sz399005,s_sh000016,s_sh000905,s_sz399300"; //纳斯达克:gb_ixic,道琼斯:gb_dji,标普500:gb_inx 28 | 29 | public static List> get(){ 30 | String url = String.format(stockIndexURL,new Date().getTime()); 31 | String data = Downloader.download(url); 32 | List> result = Lists.newArrayList(); 33 | String[] lines = data.split("\n"); 34 | for(String line:lines){ 35 | String[] fields = line.substring(line.indexOf("=\"")+1,line.length()-2).split(","); 36 | Map map = Maps.newHashMap(); 37 | map.put("name",fields[0]); 38 | map.put("close", fields[1]); 39 | map.put("changeAmount", fields[2]); 40 | map.put("change", fields[3]); 41 | map.put("volume", fields[4]); 42 | map.put("amount",fields[5]); //单位:万元 43 | result.add(map); 44 | } 45 | return result; 46 | } 47 | 48 | public static void main(String[] args) { 49 | List> maps = StockIndexDataProvider.get(); 50 | for(Map map:maps){ 51 | Utils.printMapStr(map); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/TickDataProvider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.io.CharStreams; 6 | import net.jquant.common.Utils; 7 | import net.jquant.downloader.BasicDownloader; 8 | import net.jquant.model.Symbol; 9 | import net.jquant.model.Tick; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | import java.util.Date; 17 | import java.util.List; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | 21 | /** 22 | * author: eryk 23 | * mail: xuqi86@gmail.com 24 | * date: 15-9-3. 25 | * 逐笔数据 26 | */ 27 | public class TickDataProvider { 28 | private static final Logger LOG = LoggerFactory.getLogger(TickDataProvider.class); 29 | //http://vip.stock.finance.sina.com.cn/quotes_service/view/vMS_tradehistory.php?symbol=sh600199&date=2015-11-16 30 | private static String tickHisDataURL = "http://market.finance.sina.com.cn/downxls.php?date=%s&symbol=%s"; 31 | 32 | private static String tickRTDataURL = "http://vip.stock.finance.sina.com.cn/quotes_service/view/CN_TransListV2.php?num=10000&symbol=%s&rn=%s"; 33 | 34 | /** 35 | * 36 | * @param symbol stock symbol 37 | * @param date yyyy-MM-dd 38 | * @return tick data list 39 | */ 40 | public static List get(String symbol, String date){ 41 | String url = String.format(tickHisDataURL, date, Symbol.getSymbol(symbol, tickHisDataURL)); 42 | InputStream input = BasicDownloader.downloadStream(url); 43 | 44 | List ticks = Lists.newLinkedList(); 45 | try { 46 | List lines = CharStreams.readLines(new InputStreamReader(input, "gbk")); 47 | for(int i = 1;i get(String symbol){ 66 | String url = String.format(tickRTDataURL,Symbol.getSymbol(symbol,tickRTDataURL),new Date().getTime()); 67 | String data = BasicDownloader.download(url); 68 | Pattern pattern = Pattern.compile("(\\(.*\\))"); 69 | Matcher matcher = pattern.matcher(data); 70 | List ticks = Lists.newLinkedList(); 71 | while(matcher.find()){ 72 | String line = matcher.group().replaceAll("[(|)|'| ]",""); 73 | if(!Strings.isNullOrEmpty(line)){ 74 | String[] fields = line.split(",",4); 75 | Tick tick = new Tick(); 76 | tick.date = Utils.str2Date(Utils.getNow("yyyyMMdd") + fields[0],"yyyyMMddHH:mm:ss"); 77 | tick.volume = Utils.getInt(fields[1])/100; 78 | tick.price = Utils.getDouble(fields[2]); 79 | tick.amount = Utils.formatDouble(tick.volume * tick.price * 100,"#.##"); 80 | tick.type = getTickType(fields[3]); 81 | ticks.add(tick); 82 | } 83 | } 84 | return ticks; 85 | } 86 | 87 | private static Tick.Type getTickType(String type){ 88 | if(type.equals("买盘") || type.equals("UP")){ 89 | return Tick.Type.BUY; 90 | }else if(type.equals("卖盘") || type.equals("DOWN")){ 91 | return Tick.Type.SELL; 92 | }else{ 93 | return Tick.Type.MID; 94 | } 95 | } 96 | 97 | public static void main(String[] args) throws IOException { 98 | List ticks = get("600376","2015-11-16"); 99 | for(Tick tick:ticks){ 100 | System.out.println(tick); 101 | } 102 | 103 | 104 | 105 | // String date = "2015-09-02"; 106 | // 107 | // List stockList = StockMap.getStockListWithConditions(); 108 | // for(String symbol : stockList){ 109 | // StockData stockData = RealTimeDataProvider.get(symbol); 110 | // if(stockData.get("change")< -9.5){ 111 | // continue; 112 | // } 113 | // 114 | // List ticks = TickDataProvider.get(symbol, date); 115 | // 116 | // double avgVolume = 0; 117 | // for(Tick tick:ticks){ 118 | // avgVolume += tick.volume; 119 | // } 120 | // avgVolume = avgVolume/ticks.size(); 121 | // 122 | // int count = 0; 123 | // for(int i = 1 ;i avgVolume * 30 && !tick.date.contains("15:00:") && !tick.date.contains("09:30:")){ 127 | //// bufferedWriter.write(ticks.get(i) + "\n"); 128 | // System.out.println(ticks.get(i)); 129 | // count++; 130 | // } 131 | // } 132 | // if(count>1){ 133 | //// bufferedWriter.write(symbol + ",平均每笔成交量:" + Utils.formatDouble(avgVolume)+ "\n"); 134 | // System.out.println(symbol + ",平均每笔成交量:" + Utils.formatDouble(avgVolume)); 135 | // } 136 | // } 137 | //// bufferedWriter.close(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/provider/TopListDataProvider.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.Gson; 5 | import net.jquant.common.Utils; 6 | import net.jquant.downloader.BasicDownloader; 7 | import net.jquant.downloader.Downloader; 8 | import net.jquant.model.StockData; 9 | import org.jsoup.Jsoup; 10 | import org.jsoup.nodes.Document; 11 | import org.jsoup.nodes.Element; 12 | import org.jsoup.select.Elements; 13 | 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | /** 18 | * 龙虎榜数据 19 | * 数据来源 20 | * http://data.eastmoney.com/stock/stockstatistic.html 21 | * http://vip.stock.finance.sina.com.cn/q/go.php/vInvestConsult/kind/lhb/index.phtml 22 | * author: eryk 23 | * mail: xuqi86@gmail.com 24 | * date: 15-9-16. 25 | */ 26 | public class TopListDataProvider { 27 | /** 28 | * 每日龙虎榜详情 29 | * param1=yyyy-MM-dd 30 | */ 31 | public static String dailyTopURL = "http://data.eastmoney.com/stock/lhb/%s.html"; 32 | 33 | /** 34 | * 东方财富网 个股龙虎榜统计 35 | * stat,月份,1,3,6,12或其他 36 | * st,按列排序: 37 | * 2=龙虎榜成交金额(万) 38 | * 3=上榜次数 39 | * 4=买入额(万) 40 | * 5=卖出额(万) 41 | * 6=净额(万) 42 | * rt=8位随机数,貌似是30秒间隔 43 | */ 44 | public static String stockHistoryTopURL = "http://datainterface.eastmoney.com/EM_DataCenter/JS.aspx?type=LHB&sty=GGTJ&" + 45 | "stat=%&st=%s&ps=5000&rt=%s"; 46 | /** 47 | * 新浪网 个股龙虎榜统计 48 | * last,天数,5,10,30,60 49 | * p:页数 50 | */ 51 | public static String sinaStockHistoryTopURL = "http://vip.stock.finance.sina.com.cn/q/go.php/vLHBData/kind/ggtj/index.phtml?last=%s&p=%s"; 52 | 53 | /** 54 | * 营业部上榜统计 55 | */ 56 | public static String sinaBusinessHistoryTopURL = "http://vip.stock.finance.sina.com.cn/q/go.php/vLHBData/kind/yytj/index.phtml?last=5&p=1"; 57 | 58 | /** 59 | * 机构席位追踪 60 | * last:天数,5,10,30,60 61 | * p:页数 62 | */ 63 | public static String sinaOrganizationTopURL = "http://vip.stock.finance.sina.com.cn/q/go.php/vLHBData/kind/jgzz/index.phtml?last=%s&p=%s"; 64 | 65 | /** 66 | * 机构席位成交明细 67 | * ps:最近多少条 68 | */ 69 | public static String eastMoneyOrganizationDetail = "http://datainterface.eastmoney.com/EM_DataCenter/JS.aspx?type=LHB&sty=JGXWMX&p=1&ps=%s&rt=48090807"; 70 | 71 | /** 72 | * 每日龙虎榜详情 73 | * @param date yyyyMMdd 74 | * @return stock data list 75 | */ 76 | public static List getDailyTopList(String date){ 77 | Date day = Utils.str2Date(date,"yyyyMMdd"); 78 | 79 | List stockDataList = Lists.newArrayListWithExpectedSize(50); 80 | String url = String.format(dailyTopURL,Utils.formatDate(day,"yyyy-MM-dd")); 81 | String htmlSource = Downloader.download(url); 82 | Elements table = Jsoup.parse(htmlSource).getElementById("dt_1").getElementsByTag("tbody").get(0).select("tr[class^=all]"); 83 | Elements tmpLine = null; 84 | for(Element tr:table){ 85 | Elements td = tr.getElementsByTag("td"); 86 | if(td.size()==11){ 87 | tmpLine = td; 88 | StockData stockData = new StockData(td.get(1).text()); 89 | stockData.name = td.get(2).text(); 90 | stockData.date = Utils.str2Date(date, "yyyy-MM-dd"); 91 | stockData.attr("change",td.get(4).text()); 92 | stockData.attr("龙虎榜成交额(万)",td.get(5).text()); 93 | stockData.attr("买入额(万)",td.get(6).text()); 94 | stockData.attr("买入额占总成交比例",td.get(7).text()); 95 | stockData.attr("卖出额(万)",td.get(8).text()); 96 | stockData.attr("卖出额占总成交比例",td.get(9).text()); 97 | stockData.attr("reason",td.get(10).text()); 98 | stockDataList.add(stockData); 99 | }else if(td.size()==6){ 100 | StockData stockData = new StockData(tmpLine.get(1).text()); 101 | stockData.name = tmpLine.get(2).text(); 102 | stockData.date = Utils.str2Date(date, "yyyy-MM-dd"); 103 | stockData.attr("change",tmpLine.get(4).text()); 104 | stockData.attr("龙虎榜成交额(万)",td.get(5).text()); 105 | stockData.attr("买入额(万)",td.get(0).text()); 106 | stockData.attr("买入额占总成交比例",td.get(1).text()); 107 | stockData.attr("卖出额(万)",td.get(2).text()); 108 | stockData.attr("卖出额占总成交比例",td.get(3).text()); 109 | stockData.attr("reason",td.get(4).text()); 110 | stockDataList.add(stockData); 111 | } 112 | } 113 | return stockDataList; 114 | } 115 | 116 | /** 117 | * 个股龙虎榜统计 118 | * @param dayCount 取值:5,10,30,60 119 | * @return stock data list 120 | */ 121 | public static List getStockRanking(int dayCount){ 122 | int pageCount = getPageCount(sinaStockHistoryTopURL,dayCount); 123 | String url; 124 | String data; 125 | Document doc; 126 | List stockDataList = Lists.newLinkedList(); 127 | for(int i=1;i<=pageCount;i++){ 128 | url =String.format(sinaStockHistoryTopURL,dayCount,i); 129 | data = BasicDownloader.download(url,"gb2312"); 130 | doc = Jsoup.parse(data); 131 | 132 | Elements trList = doc.getElementById("dataTable").getElementsByTag("tbody").get(0).getElementsByTag("tr"); 133 | for(Element tr:trList){ 134 | Elements tdList = tr.getElementsByTag("td"); 135 | StockData stockData = new StockData(tdList.get(0).text()); 136 | stockData.name = tdList.get(1).text(); 137 | stockData.put("上榜次数",Double.parseDouble(tdList.get(2).text())); 138 | stockData.put("累积购买额(万)",Double.parseDouble(tdList.get(3).text())); 139 | stockData.put("累积卖出额(万)",Double.parseDouble(tdList.get(4).text())); 140 | stockData.put("净额(万)",Double.parseDouble(tdList.get(5).text())); 141 | stockData.put("买入席位数",Double.parseDouble(tdList.get(6).text())); 142 | stockData.put("卖出席位数",Double.parseDouble(tdList.get(7).text())); 143 | stockDataList.add(stockData); 144 | } 145 | } 146 | return stockDataList; 147 | } 148 | 149 | public static List getOrganizationRanking(int dayCount){ 150 | int pageCount = getPageCount(sinaOrganizationTopURL,dayCount); 151 | String url; 152 | String data; 153 | Document doc; 154 | List stockDataList = Lists.newLinkedList(); 155 | for(int i=1;i<=pageCount;i++){ 156 | url =String.format(sinaOrganizationTopURL,dayCount,i); 157 | data = BasicDownloader.download(url,"gb2312"); 158 | doc = Jsoup.parse(data); 159 | 160 | Elements trList = doc.getElementById("dataTable").getElementsByTag("tbody").get(0).getElementsByTag("tr"); 161 | for(Element tr:trList){ 162 | Elements tdList = tr.getElementsByTag("td"); 163 | System.out.println(tdList.html()); 164 | StockData stockData = new StockData(tdList.get(0).text()); 165 | stockData.name = tdList.get(1).text(); 166 | // stockData.put("close",Double.parseDouble(tdList.get(2).text())); 167 | // stockData.put("change",Double.parseDouble(tdList.get(3).text())); 168 | stockData.put("累积买入额(万)",Double.parseDouble(tdList.get(4).text())); 169 | stockData.put("买入次数",Double.parseDouble(tdList.get(5).text())); 170 | stockData.put("累积卖出额(万)",Double.parseDouble(tdList.get(6).text())); 171 | stockData.put("卖出次数",Double.parseDouble(tdList.get(7).text())); 172 | stockData.put("净额(万)",Double.parseDouble(tdList.get(8).text())); 173 | stockDataList.add(stockData); 174 | } 175 | } 176 | return stockDataList; 177 | } 178 | 179 | /** 180 | * 机构席位成交明细 181 | * @param count count 182 | * @return stock data list 183 | */ 184 | public static List getOrganizationDetailRanking(int count){ 185 | List stockDataList = Lists.newLinkedList(); 186 | String url = String.format(eastMoneyOrganizationDetail, count); 187 | String data = Downloader.download(url); 188 | Gson gson = new Gson(); 189 | List records = gson.fromJson(data.substring(1, data.length() - 1), List.class); 190 | for(String record:records){ 191 | String[] fields = record.split(",",6); 192 | StockData stockData= new StockData(fields[2].substring(0,6)); 193 | stockData.name = fields[4]; 194 | stockData.date = Utils.str2Date(fields[5],"yyyy-MM-dd"); 195 | 196 | stockData.attr("reason",fields[0]); 197 | stockData.put("机构席位卖出",Double.parseDouble(fields[1])); 198 | stockData.put("机构席位买入",Double.parseDouble(fields[3])); 199 | stockDataList.add(stockData); 200 | } 201 | return stockDataList; 202 | } 203 | 204 | private static int getPageCount(String baseURL,int dayCount) { 205 | String url =String.format(baseURL,dayCount,1); 206 | String data = BasicDownloader.download(url, "gb2312"); 207 | Document doc = Jsoup.parse(data); 208 | return doc.select("div[class=pages]").get(0).getElementsByTag("a").size()-2; 209 | } 210 | 211 | public static void main(String[] args) { 212 | List stockDataList = TopListDataProvider.getDailyTopList("2015-09-17"); 213 | for(StockData stockData:stockDataList){ 214 | System.out.println(stockData.toString()); 215 | Utils.printMapStr(stockData.attribute); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/strategy/KellyFormula.java: -------------------------------------------------------------------------------- 1 | package net.jquant.strategy; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.jquant.common.Utils; 5 | 6 | /** 7 | * 公式为: f* = ( bp – q ) / b 8 | p = 胜率 9 | q = 败率 = 1 – p 10 | b = 平均获利 / 平均损失 11 | 12 | 这个定义的公式经过移项后可以换成比较容易记忆的: 胜率 – (败率 / 盈亏比) 13 | KELLY = p – ( q / b ) 14 | 15 | */ 16 | public class KellyFormula { 17 | 18 | private double p = 0; 19 | private double q = 0; 20 | private double b = 0; 21 | private double f = 0; 22 | 23 | /** 24 | * @param p 胜率 25 | * @param b 赔率 26 | */ 27 | public KellyFormula(double p, double b) { 28 | Preconditions.checkArgument(p > 0 && p < 1); 29 | Preconditions.checkArgument(b > 1); 30 | this.p = p; 31 | this.q = 1 - p; 32 | this.b = b; 33 | f = Utils.formatDouble(compute(p, b)); 34 | } 35 | 36 | /** 37 | * 38 | * @param p 胜率 39 | * @param profit profit 40 | * @param loss 盈亏比,赔率,盈亏比平均赢的金额/平均输的金额 41 | */ 42 | public KellyFormula(double p, double profit, double loss) { 43 | Preconditions.checkArgument(p > 0 && p < 1); 44 | Preconditions.checkArgument(profit > 0 && loss > 0 && profit > loss); 45 | this.p = p; 46 | this.q = 1 - p; 47 | this.b = profit / loss; 48 | this.f = Utils.formatDouble(p - q / b); 49 | } 50 | 51 | //胜率 – (败率 / 盈亏比) 52 | public static double compute(double p, double b) { 53 | return p - (1 - p) / b; 54 | } 55 | 56 | public static double compute(double p, double profit, double loss) { 57 | return p - (1 - p) / (profit / loss); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/strategy/Strategy.java: -------------------------------------------------------------------------------- 1 | package net.jquant.strategy; 2 | 3 | import net.jquant.common.Context; 4 | import net.jquant.model.StockData; 5 | 6 | public interface Strategy { 7 | 8 | void init(Context context); 9 | 10 | void handleData(StockData stockData); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/strategy/StrategyUtils.java: -------------------------------------------------------------------------------- 1 | package net.jquant.strategy; 2 | 3 | import com.google.common.collect.Lists; 4 | import net.jquant.Indicators; 5 | import net.jquant.common.StockConstants; 6 | import net.jquant.common.Utils; 7 | import net.jquant.model.StockData; 8 | import net.jquant.tools.Conditions; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 策略公共方法 14 | */ 15 | public class StrategyUtils { 16 | 17 | private static TDXFunction function = new TDXFunction(); 18 | 19 | /** 20 | * 计算macd指标金叉和死叉 21 | * 指标名称:macd_cross 22 | * 状态: 23 | * 1:金叉 24 | * 0:相交 25 | * -1:死叉 26 | * @param stockDatas stock data list 27 | * @return stock data list 28 | */ 29 | public static List macdCross(List stockDatas){ 30 | if(stockDatas == null || stockDatas.size() == 0){ 31 | return stockDatas; 32 | } 33 | if(stockDatas.size() > 0){ 34 | if(stockDatas.get(0).get("dif") == null || stockDatas.get(0).get("dea") == null){ 35 | return stockDatas; 36 | } 37 | } 38 | 39 | for(int i =0;i< stockDatas.size();i++){ 40 | if(stockDatas.get(i).get("dif") > stockDatas.get(i).get("dea")){ 41 | stockDatas.get(i).put("macd_cross",1d); 42 | }else if(stockDatas.get(i).get("dif") < stockDatas.get(i).get("dea")) { 43 | stockDatas.get(i).put("macd_cross", -1d); 44 | }else{ 45 | stockDatas.get(i).put("macd_cross", 0d); 46 | } 47 | } 48 | return stockDatas; 49 | } 50 | 51 | /** 52 | * 判断最近n个时间周期内是否出现金叉,或者是当前的MACD值在(-0.1,0.1)之间并且dif,dea持续缩短 53 | * 54 | * @param stockDataList stock data list 55 | * @param period period 56 | * @return 如果是返回true 57 | */ 58 | public static boolean isMACDGoldenCrossIn(List stockDataList, int period) { 59 | int count = stockDataList.size(); 60 | for (int i = count - 1; i > 0; i--) { 61 | StockData stockData = stockDataList.get(i); 62 | Double cross = stockData.get(StockConstants.MACD_CROSS); 63 | if (cross != null && count - i <= period && cross == 1 && stockDataList.get(i).get(StockConstants.DIF) < 0.1){ 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | public static List kdjCross(List stockDatas){ 71 | if(stockDatas == null || stockDatas.size() == 0){ 72 | return stockDatas; 73 | } 74 | if(stockDatas.size() > 0){ 75 | if(stockDatas.get(0).get("kdj_d") == null || stockDatas.get(0).get("kdj_d") == null){ 76 | return stockDatas; 77 | } 78 | } 79 | 80 | for(int i =0;i< stockDatas.size();i++){ 81 | if(stockDatas.get(i).get("kdj_j") > stockDatas.get(i).get("kdj_d")){ 82 | stockDatas.get(i).put("kdj_cross",1d); 83 | }else if(stockDatas.get(i).get("kdj_j") < stockDatas.get(i).get("kdj_d")) { 84 | stockDatas.get(i).put("kdj_cross", -1d); 85 | }else{ 86 | stockDatas.get(i).put("kdj_cross", 0d); 87 | } 88 | } 89 | return stockDatas; 90 | } 91 | 92 | /** 93 | * 布林线上下轨之差小于中轨的10%,标记为1 94 | * @param stockDatas stock data list 95 | * @return stock data list 96 | */ 97 | public static List bollThroat(List stockDatas){ 98 | if(stockDatas == null || stockDatas.size() == 0){ 99 | return stockDatas; 100 | } 101 | if(stockDatas.size() > 0){ 102 | if(stockDatas.get(0).get("boll_upper") == null || stockDatas.get(0).get("boll_lower") == null){ 103 | return stockDatas; 104 | } 105 | } 106 | 107 | for(int i =0;i< stockDatas.size();i++){ 108 | if((stockDatas.get(i).get("boll_upper") - stockDatas.get(i).get("boll_lower")) < (stockDatas.get(i).get("boll_lower") * 0.1)){ 109 | stockDatas.get(i).put("boll_throat",1d); 110 | }else{ 111 | stockDatas.get(i).put("boll_throat",0d); 112 | } 113 | } 114 | return stockDatas; 115 | } 116 | 117 | /** 118 | * 判断收盘价两条线是否相交,快线从下上传慢线叫金叉,快慢线名称请参考StockConstants类 119 | * 120 | * @param stockDataList stock data list 121 | * @param maFast 例如:close_ma5 122 | * @param maSlow 例如:close_ma10 123 | * @param period period 124 | * @return stock data list 125 | */ 126 | public static boolean isGoldenCrossIn(List stockDataList, String maFast, String maSlow, int period) { 127 | int size = stockDataList.size() - 1; 128 | double[] maFastArr = Utils.getArrayFrom(stockDataList, maFast); 129 | double[] maSlowArr = Utils.getArrayFrom(stockDataList, maSlow); 130 | for (int i = size; i > 0; i--) { 131 | if (size - i > period) { 132 | break; 133 | } 134 | if (maFastArr[i] > maSlowArr[i] && maFastArr[i-1] < maSlowArr[i-1]) { 135 | return true; 136 | } 137 | } 138 | return false; 139 | } 140 | 141 | public static double goldenCrossPrice(List stockDataList, String maFast, String maSlow, int period){ 142 | int size = stockDataList.size() - 1; 143 | double[] maFastArr = Utils.getArrayFrom(stockDataList, maFast); 144 | double[] maSlowArr = Utils.getArrayFrom(stockDataList, maSlow); 145 | for (int i = size; i > 0; i--) { 146 | if (size - i < period) { 147 | break; 148 | } 149 | if (maFastArr[i] > maSlowArr[i] && maFastArr[i-1] < maSlowArr[i-1]) { 150 | return stockDataList.get(i).get(StockConstants.CLOSE); 151 | } 152 | } 153 | return -1; 154 | } 155 | 156 | /** 157 | * 判断最近n个时间周期内是否出现死叉 158 | * 159 | * @param stockDataList stock data list 160 | * @param period period 161 | * @return 如果是返回true 162 | */ 163 | public static boolean isMACDDiedCrossIn(List stockDataList, int period) { 164 | int count = stockDataList.size(); 165 | for (int i = count - 1; i > 0; i--) { 166 | StockData stockData = stockDataList.get(i); 167 | Double cross = stockData.get(StockConstants.MACD_CROSS); 168 | if (cross != null && count - i <= period && cross == 0) 169 | return true; 170 | } 171 | return false; 172 | } 173 | 174 | /** 175 | * 计算有几条均线粘合,粘合数加入到AVERAGE_BOND参数中 176 | * @param stockDataList stock data list 177 | * @param threshold threshold 178 | * @return stock data list 179 | */ 180 | public static List averageBond(List stockDataList, double threshold) { 181 | List result = Lists.newLinkedList(); 182 | for (int i = 0; i < stockDataList.size(); i++) { 183 | StockData stockData = stockDataList.get(i); 184 | double ma5 = stockData.get(StockConstants.CLOSE_MA5); 185 | double ma10 = stockData.get(StockConstants.CLOSE_MA10); 186 | double ma20 = stockData.get(StockConstants.CLOSE_MA20); 187 | double ma30 = stockData.get(StockConstants.CLOSE_MA30); 188 | double ma40 = stockData.get(StockConstants.CLOSE_MA40); 189 | double ma60 = stockData.get(StockConstants.CLOSE_MA60); 190 | if (ma5 == 0 || ma10 == 0 || ma20 == 0 || ma30 == 0 || ma40 == 0 || ma60 == 0) { 191 | continue; 192 | } 193 | double maCount = 0; 194 | double[] maArr = {ma5, ma10, ma20, ma30, ma40, ma60}; 195 | for (int j = 0; j < 5; j++) { 196 | for (int k = j + 1; k < 6; k++) { 197 | if (Math.abs(maArr[j] / maArr[k] - 1) < threshold) { 198 | maCount++; 199 | } 200 | } 201 | } 202 | stockData.put(StockConstants.AVERAGE_BOND, maCount); 203 | result.add(stockData); 204 | } 205 | return result; 206 | } 207 | 208 | /** 209 | * 金蜘蛛策略 210 | * @param stockDataList stock data list 211 | * @return stock data list 212 | */ 213 | public static List goldenSpider(List stockDataList) { 214 | Indicators indicators = new Indicators(); 215 | List result = Lists.newLinkedList(); 216 | double[] closes = Utils.getArrayFrom(stockDataList, StockConstants.CLOSE); 217 | double[] volume = Utils.getArrayFrom(stockDataList, StockConstants.VOLUME); 218 | double[] ma2 = indicators.ema(closes, 2); 219 | double[] ma5 = indicators.ema(closes, 5); 220 | double[] ma13 = indicators.ema(closes, 13); 221 | double[] ma34 = indicators.ema(closes, 34); 222 | double[] ma55 = indicators.ema(closes, 55); 223 | double[][] macd = indicators.macd(closes); 224 | double[] volume5 = indicators.ema(volume,5); 225 | 226 | for (int i = 0; i < stockDataList.size(); i++) { 227 | StockData stockData = stockDataList.get(i); 228 | stockData.put(StockConstants.GOLDEN_SPIDER, 0d); 229 | stockData.put(StockConstants.GOLDEN_SPIDER_RANGE,0d); 230 | if (i > 0 && ma5[i] > ma5[i - 1]) { 231 | double close = stockData.get(StockConstants.CLOSE); 232 | double open = stockData.get(StockConstants.OPEN); 233 | double max1 = function.max(ma5[i], ma13[i], ma34[i]); 234 | double min1 = function.min(ma5[i], ma13[i], ma34[i]); 235 | double max2 = function.max(ma5[i], ma13[i], ma34[i], ma55[i]); 236 | double min2 = function.min(ma5[i], ma13[i], ma34[i], ma55[i]); 237 | if (max1 < close && open < min1 && ma2[i] > ma2[i - 1]) { 238 | if(!Utils.isNearTradingTime()){//如果是盘后,则计算成交量是否大于前一天成交量,并且今天成交量大于当天volume5 239 | if(stockData.get(StockConstants.VOLUME) > stockDataList.get(i-1).get(StockConstants.VOLUME)*1.2 && stockData.get(StockConstants.VOLUME) > volume5[i]){ 240 | stockData.put(StockConstants.GOLDEN_SPIDER, 3d); 241 | } 242 | }else{ 243 | stockData.put(StockConstants.GOLDEN_SPIDER, 3d); 244 | } 245 | } else if (max2 < close && open < min2 && ma2[i] > ma2[i - 1]) { 246 | if(!Utils.isNearTradingTime()){//如果是盘后,则计算成交量是否大于前一天成交量,并且今天成交量大于当天volume5 247 | if(stockData.get(StockConstants.VOLUME) > stockDataList.get(i-1).get(StockConstants.VOLUME)*1.2 && stockData.get(StockConstants.VOLUME) > volume5[i]){ 248 | stockData.put(StockConstants.GOLDEN_SPIDER, 4d); 249 | }else{ 250 | stockData.put(StockConstants.GOLDEN_SPIDER, 0d); 251 | } 252 | }else{ 253 | stockData.put(StockConstants.GOLDEN_SPIDER, 4d); 254 | } 255 | } else if(macd[2][i]>0 && macd[2][i] < 1){ //macd金叉,并且三天内出现金蜘蛛 256 | double lastClose = stockData.get(StockConstants.CLOSE); 257 | for(int j=i-1;j >= 0 && j >i-3;j--){ 258 | Double spider = result.get(j).get(StockConstants.GOLDEN_SPIDER); 259 | if(spider!=null){ 260 | if(lastClose > result.get(j).get(StockConstants.CLOSE) && lastClose < result.get(j).get(StockConstants.CLOSE) * 1.1 && spider>0){ 261 | stockData.put(StockConstants.GOLDEN_SPIDER_RANGE,1d); 262 | } 263 | } 264 | } 265 | } 266 | } 267 | result.add(stockData); 268 | } 269 | return result; 270 | } 271 | 272 | /** 273 | * 计算最近count天内每天涨跌幅是否出现过conditions 274 | * @param stockDataList stock data list 275 | * @param count n day 276 | * @param conditions conditions 277 | * @return true if conditions is true 278 | */ 279 | public static boolean change(List stockDataList,int count,Conditions conditions){ 280 | int size = stockDataList.size(); 281 | for(int i=size-1;i>=size-count;i--){ 282 | StockData stockData = stockDataList.get(i); 283 | if(conditions.check(stockData)){ 284 | return true; 285 | } 286 | } 287 | return false; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/strategy/TDXFunction.java: -------------------------------------------------------------------------------- 1 | package net.jquant.strategy; 2 | 3 | import com.tictactec.ta.lib.Core; 4 | import com.tictactec.ta.lib.MInteger; 5 | import com.tictactec.ta.lib.RetCode; 6 | 7 | /** 8 | * author: eryk 9 | * mail: xuqi86@gmail.com 10 | * date: 15-8-12. 11 | */ 12 | public class TDXFunction { 13 | Core core; 14 | 15 | public TDXFunction() { 16 | core = new Core(); 17 | } 18 | 19 | /** 20 | * 计算一段时间周期内的最大值 21 | * 22 | * @param prices prices 23 | * @param period period 24 | * @return hhv 25 | */ 26 | public double[] hhv(double[] prices, int period) { 27 | double[] output = new double[prices.length]; 28 | double[] tempOutPut = new double[prices.length]; 29 | MInteger begin = new MInteger(); 30 | MInteger length = new MInteger(); 31 | begin.value = -1; 32 | length.value = -1; 33 | 34 | RetCode max = core.max(0, prices.length - 1, prices, period, begin, length, tempOutPut); 35 | 36 | for (int i = 0; i < period; i++) { 37 | output[i] = 0; 38 | } 39 | for (int i = period; 0 < i && i < (prices.length); i++) { 40 | output[i] = tempOutPut[i - period]; 41 | } 42 | return output; 43 | } 44 | 45 | public double[] llv(double[] prices, int period) { 46 | double[] output = new double[prices.length]; 47 | double[] tempOutPut = new double[prices.length]; 48 | MInteger begin = new MInteger(); 49 | MInteger length = new MInteger(); 50 | begin.value = -1; 51 | length.value = -1; 52 | 53 | RetCode max = core.min(0, prices.length - 1, prices, period, begin, length, tempOutPut); 54 | 55 | for (int i = 0; i < period; i++) { 56 | output[i] = 0; 57 | } 58 | for (int i = period; 0 < i && i < (prices.length); i++) { 59 | output[i] = tempOutPut[i - period]; 60 | } 61 | return output; 62 | } 63 | 64 | /** 65 | * 计算A和B两条线的金叉和死叉 66 | * 当A在B线以下,返回-1 67 | * 当A向上穿过B(金叉),返回0 68 | * 当A在B线以上,返回1 69 | * 当A向下穿过B(死叉),返回0 70 | * 71 | * @param lineA lineA 72 | * @param lineB lineB 73 | * @return crossPoint 74 | */ 75 | public double[] crossPoint(double[] lineA, double[] lineB) { 76 | if (lineA != null && lineB != null & lineA.length != lineB.length && lineA.length != 0) { 77 | return new double[0]; 78 | } 79 | int length = lineA.length; 80 | double[] output = new double[length]; 81 | for (int i = 0; i < length; i++) { 82 | if (lineA[i] < lineB[i]) { 83 | output[i] = -1; 84 | } else if (lineA[i] > lineB[i]) { 85 | output[i] = 1; 86 | } else { 87 | output[i] = 0; 88 | } 89 | } 90 | return output; 91 | } 92 | 93 | /** 94 | * 判断a和b是否在最后一个周期内相交 95 | * 96 | * @param lineA lineA 97 | * @param lineB lineB 98 | * @return cross 99 | */ 100 | public boolean cross(double[] lineA, double[] lineB) { 101 | if (lineA != null && lineB != null & lineA.length != lineB.length && lineA.length != 0) { 102 | return false; 103 | } 104 | if (lineA[lineA.length - 2] < lineB[lineB.length - 2] && lineA[lineA.length - 1] > lineB[lineB.length - 1]) { 105 | return true; 106 | } 107 | return false; 108 | } 109 | 110 | public boolean crossBetween(double[] lineA, double[] lineB, int period) { 111 | if (lineA != null && lineB != null & lineA.length != lineB.length && lineA.length != 0) { 112 | return false; 113 | } 114 | int length = lineA.length; 115 | for (int i = length - period; i < length; i++) { 116 | if (lineA[i - 1] < lineB[i - 1] && lineA[i] > lineB[i]) { 117 | return true; 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | public double max(double... values) { 124 | double tmp = Double.MIN_VALUE; 125 | for (double value : values) { 126 | if (value > tmp) { 127 | tmp = value; 128 | } 129 | } 130 | return tmp; 131 | } 132 | 133 | public double min(double... values) { 134 | double tmp = Double.MAX_VALUE; 135 | for (double value : values) { 136 | if (value < tmp && value >0) { 137 | tmp = value; 138 | } 139 | } 140 | return tmp; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/strategy/TStragegy.java: -------------------------------------------------------------------------------- 1 | package net.jquant.strategy; 2 | 3 | import com.google.common.collect.Lists; 4 | import net.jquant.Quants; 5 | import net.jquant.common.ParallelProcesser; 6 | import net.jquant.common.StockDataParseException; 7 | import net.jquant.model.StockData; 8 | import net.jquant.provider.Provider; 9 | import net.jquant.tools.Sleeper; 10 | 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | public class TStragegy { 15 | 16 | public static void t(String symbol) throws StockDataParseException { 17 | Quants quants = new Quants(); 18 | List stockDatas = quants.data.minuteData(symbol, "15"); 19 | System.out.println(stockDatas.size()); 20 | stockDatas = quants.indicator.macd(stockDatas); 21 | stockDatas = quants.indicator.kdj(stockDatas); 22 | stockDatas = quants.indicator.boll(stockDatas); 23 | 24 | for (StockData stock : stockDatas) { 25 | System.out.println(stock); 26 | } 27 | } 28 | 29 | public static void print(List symbolList) throws StockDataParseException { 30 | while (true) { 31 | for(String symbol:symbolList){ 32 | 33 | StockData stockData = Provider.realtimeData(symbol); 34 | // System.out.println(stockData.get("buy5") + "\t"+ stockData.get("sell5") + "\t" + (stockData.get("buy5Volume") - stockData.get("sell5Volume"))); 35 | // System.out.println(stockData.get("buy4") + "\t"+ stockData.get("sell4") + "\t"+ (stockData.get("buy4Volume") - stockData.get("sell4Volume"))); 36 | // System.out.println(stockData.get("buy3") + "\t"+ stockData.get("sell3") + "\t"+ (stockData.get("buy3Volume") - stockData.get("sell3Volume"))); 37 | // System.out.println(stockData.get("buy2") + "\t"+ stockData.get("sell2") + "\t"+ (stockData.get("buy2Volume") - stockData.get("sell2Volume"))); 38 | System.out.println("-------" + stockData.name + "(" + stockData.symbol + ")" + "-------"); 39 | System.out.println(stockData.get("buy1") + "\t"+ stockData.get("sell1") + "\t"+ (stockData.get("buy1Volume") - stockData.get("sell1Volume"))); 40 | } 41 | System.out.println(new Date()); 42 | Sleeper.sleep(30000); 43 | } 44 | } 45 | 46 | public static void main(String[] args) throws StockDataParseException { 47 | ParallelProcesser.init(1, 2); 48 | TStragegy.print(Lists.newArrayList("002121","600444","600152","002627","600605","600561")); 49 | ParallelProcesser.close(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/Analyzer.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.base.Strings; 4 | import net.jquant.Quants; 5 | import net.jquant.common.ParallelProcesser; 6 | import net.jquant.common.StockDataParseException; 7 | import net.jquant.model.StockData; 8 | 9 | import java.io.IOException; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | public class Analyzer { 15 | 16 | Quants quant; 17 | 18 | public Analyzer(){ 19 | quant = new Quants(); 20 | } 21 | 22 | public void analyze() throws IOException, StockDataParseException { 23 | List stockDatas = new StockList().create().get(); 24 | 25 | Map> gn = StockCategory.getStockCategory("概念"); 26 | Map> hy = StockCategory.getStockCategory("行业"); 27 | 28 | System.out.println("名称,代码,流通市值,市盈率,换手率,行业,概念"); 29 | for(StockData stockData:stockDatas){ 30 | if(!Strings.isNullOrEmpty(stockData.symbol)) { 31 | StockData realtimeData = quant.data.realtimeData(stockData.symbol); 32 | if(realtimeData.get("change") <9.95){ 33 | continue; 34 | } 35 | if(realtimeData.get("close") > 40){ 36 | continue; 37 | } 38 | List stockDatas1 = quant.data.dailyData(stockData.symbol); 39 | if(stockDatas1.size() < 60){ 40 | continue; 41 | } 42 | System.out.println(stockData.name + "," 43 | + stockData.symbol + "," 44 | + stockData.get("marketValue") + "," 45 | + stockData.get("PE") + "," 46 | + stockData.get("turnoverRate") + "," 47 | + hy.get(stockData.symbol) + "," 48 | + gn.get(stockData.symbol)); 49 | } 50 | } 51 | } 52 | 53 | public static void main(String[] args) throws IOException, StockDataParseException { 54 | ParallelProcesser.init(5,10); 55 | Analyzer analyzer = new Analyzer(); 56 | analyzer.analyze(); 57 | ParallelProcesser.close(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/Conditions.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.collect.HashBasedTable; 4 | import com.google.common.collect.Table; 5 | import net.jquant.model.StockData; 6 | 7 | import java.util.Set; 8 | 9 | public class Conditions { 10 | public enum Operation{ 11 | GT,LT,EQ,NGT,NLT 12 | } 13 | 14 | //指标名称:操作类型(大于,小于,等于):比较值 15 | private Table conditions; 16 | 17 | public Conditions(){ 18 | conditions = HashBasedTable.create(); 19 | } 20 | 21 | public void addCondition(String name,Operation op,Double value){ 22 | conditions.put(name,op,value); 23 | } 24 | 25 | public boolean check(StockData stockData){ 26 | Set> cells = conditions.cellSet(); 27 | for(Table.Cell cell:cells){ 28 | Double val = stockData.get(cell.getRowKey()); 29 | if(val!=null){ 30 | 31 | switch (cell.getColumnKey()){ 32 | case GT: 33 | if (val<=cell.getValue().doubleValue()){ 34 | return false; 35 | } 36 | break; 37 | case LT: 38 | if(val >= cell.getValue().doubleValue()){ 39 | return false; 40 | } 41 | break; 42 | case EQ: 43 | if(val!=cell.getValue().doubleValue()){ 44 | return false; 45 | } 46 | break; 47 | case NGT: 48 | if(val>cell.getValue().doubleValue()){ 49 | return false; 50 | } 51 | break; 52 | case NLT: 53 | if(val returns, double rf) { 23 | SummaryStatistics ss = new SummaryStatistics(); 24 | returns.forEach((xx) -> ss.addValue(xx - rf)); 25 | 26 | return ss.getMean() / ss.getStandardDeviation(); 27 | } 28 | 29 | public static double value(List returns) { 30 | return value(returns, 0); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/Sleeper.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * author: eryk 10 | * mail: xuqi86@gmail.com 11 | * date: 15-10-29. 12 | */ 13 | public class Sleeper { 14 | private static Logger LOG = LoggerFactory.getLogger(Sleeper.class); 15 | 16 | public static void sleep(int ms){ 17 | try { 18 | TimeUnit.MILLISECONDS.sleep(ms); 19 | } catch (InterruptedException e) { 20 | LOG.error("fail to sleep"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/SortinoRatio.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import org.apache.commons.math3.stat.descriptive.SummaryStatistics; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * author: eryk 9 | * mail: xuqi86@gmail.com 10 | * date: 15-10-21. 11 | */ 12 | public class SortinoRatio { 13 | /** 14 | * 所提诺比率 = (平均收益 - 国债利率)/损失波动率 15 | * Computes the Sortino ratio for a list of returns. 16 | * 17 | * @param returns The returns 18 | * @param rf The risk free average return 19 | * @param multiplier Mainly used to compute the Sortino ratio 20 | * for the opposite returns, i.e. short strategy. 21 | * 22 | * @return The Sortino ratio. Double.MAX_VALUE is returned if there 23 | * are no negative returns after applying the multiplier (i.e. 24 | * the divisor is 0). 25 | */ 26 | public static double value(List returns, double rf, double multiplier) { 27 | SummaryStatistics fullStats = new SummaryStatistics(); 28 | SummaryStatistics downStats = new SummaryStatistics(); 29 | for(int ii = 0; ii < returns.size(); ++ii) { 30 | double dd = (returns.get(ii) - rf) * multiplier; 31 | fullStats.addValue(dd); 32 | if(dd < rf) downStats.addValue(dd); 33 | else downStats.addValue(0); 34 | } 35 | 36 | if(downStats.getN() == 0) return Double.MAX_VALUE; 37 | 38 | return fullStats.getMean() / downStats.getStandardDeviation(); 39 | } 40 | 41 | public static double value(List returns) { 42 | return value(returns, 0, 1); 43 | } 44 | 45 | public static double value(List returns, double rf) { 46 | return value(returns, rf, 1); 47 | } 48 | 49 | /** 50 | * Computes the Sortino ratio for the short strategy. 51 | * 52 | * @param returns The returns 53 | * 54 | * @return The Sortino ratio. Double.MAX_VALUE is returned if there 55 | * are no negative returns after applying the multiplier (i.e. 56 | * the divisor is 0). 57 | */ 58 | public static double reverseValue(List returns) { 59 | return value(returns, 0, -1); 60 | } 61 | 62 | /** 63 | * Computes the Sortino ratio for the short strategy. 64 | * 65 | * @param returns The returns 66 | * @param rf The risk free average return 67 | * 68 | * @return The Sortino ratio. Double.MAX_VALUE is returned if there 69 | * are no negative returns after applying the multiplier (i.e. 70 | * the divisor is 0). 71 | */ 72 | public static double reverseValue(List returns, double rf) { 73 | return value(returns, rf, -1); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/StockCategory.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.base.CharMatcher; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Maps; 6 | import com.google.common.collect.Sets; 7 | import com.google.gson.Gson; 8 | import net.jquant.common.Pair; 9 | import net.jquant.common.Utils; 10 | import net.jquant.downloader.Downloader; 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.nodes.Document; 13 | import org.jsoup.nodes.Element; 14 | import org.jsoup.select.Elements; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import net.jquant.model.StockBlock; 18 | 19 | import javax.annotation.PostConstruct; 20 | import java.io.IOException; 21 | import java.nio.charset.Charset; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.concurrent.CountDownLatch; 27 | import java.util.concurrent.ExecutorService; 28 | import java.util.concurrent.Executors; 29 | import java.util.regex.Matcher; 30 | import java.util.regex.Pattern; 31 | 32 | /** 33 | * author: eryk 34 | * mail: xuqi86@gmail.com 35 | * date: 15-8-16. 36 | */ 37 | public class StockCategory { 38 | private static final Logger LOG = LoggerFactory.getLogger(StockCategory.class); 39 | 40 | private static String tagsURL = "http://quote.eastmoney.com/center/BKList.html#notion_0_0?sortRule=0"; 41 | 42 | private static String blockBaseURL = "http://quote.eastmoney.com/center/"; 43 | 44 | private static String blockStockListURL = "http://hqdigi2.eastmoney.com/EM_Quote2010NumericApplication/index.aspx?type=s&sortType=C&sortRule=-1&pageSize=500&page=1&style=%s&token=44c9d251add88e27b65ed86506f6e5da"; 45 | 46 | private static int RETRY_TIMES = 3; 47 | private static int SLEEP_INTERVAL_MS = 3000; 48 | 49 | private static ExecutorService threadPool = Executors.newFixedThreadPool(30); 50 | 51 | private static Map> category = Maps.newConcurrentMap(); 52 | 53 | @PostConstruct 54 | public void init(){ 55 | category = getCategory(); 56 | } 57 | 58 | //获取概念版块、行业版块、地域版块分类 59 | //key:版块分类:概念,行业,地域 60 | //value:List具体版块信息 61 | public static Map> getCategory() { 62 | if(category.size()!=0){ 63 | return category; 64 | } 65 | final Map> blockMap = Maps.newConcurrentMap(); 66 | 67 | int retryTimes = 0; 68 | while (retryTimes < RETRY_TIMES) { 69 | 70 | try { 71 | List> taskList = Lists.newLinkedList(); 72 | 73 | Document document = Jsoup.connect(tagsURL).get(); 74 | Elements elements = document.select("li[class=node-sub-sub]"); 75 | for (Element element : elements) { 76 | String html = new String(element.html().getBytes(Charset.forName("utf8"))); 77 | Elements items = element.select("ul li"); 78 | 79 | final String type; 80 | if (html.contains("概念板块")) { 81 | type = "概念"; 82 | } else if (html.contains("行业板块")) { 83 | type = "行业"; 84 | } else if (html.contains("地域板块")) { 85 | type = "地域"; 86 | } else { 87 | type = ""; 88 | } 89 | for (final Element item : items) { 90 | taskList.add(new Pair(type,item)); 91 | } 92 | } 93 | 94 | blockMap.put("概念",new LinkedList<>()); 95 | blockMap.put("行业",new LinkedList<>()); 96 | blockMap.put("地域",new LinkedList<>()); 97 | 98 | final CountDownLatch countDownLatch = new CountDownLatch(taskList.size()); 99 | for(final Pair block:taskList){ 100 | threadPool.execute(() -> { 101 | StockBlock stockBlock = new StockBlock(); 102 | stockBlock.name = new String(block.getVal().select("span[class=text]").text().getBytes(Charset.forName("utf8"))); 103 | stockBlock.url = blockBaseURL+block.getVal().select("a").attr("href"); 104 | String _id = CharMatcher.DIGIT.retainFrom(stockBlock.url); 105 | stockBlock.id = _id.substring(0,_id.length()-2); 106 | stockBlock.symbolList.addAll(getBlockStockList(stockBlock.id)); 107 | stockBlock.type = block.getKey(); 108 | blockMap.get(block.getKey()).add(stockBlock); 109 | countDownLatch.countDown(); 110 | }); 111 | } 112 | countDownLatch.await(); 113 | Utils.closeThreadPool(threadPool); 114 | LOG.info("概念:" + blockMap.get("概念").size() + ",行业:" + blockMap.get("行业").size() + ",地域:" + blockMap.get("地域").size()); 115 | return blockMap; 116 | } catch (IOException e) { 117 | LOG.error("fail to get stock list",e); 118 | retryTimes++; 119 | try { 120 | Thread.sleep(SLEEP_INTERVAL_MS); 121 | } catch (InterruptedException e1) { 122 | LOG.error("fail to sleep "+ SLEEP_INTERVAL_MS + "ms"); 123 | } 124 | } catch (InterruptedException e) { 125 | LOG.error("fail to stop thread pool",e); 126 | } 127 | } 128 | category.putAll(blockMap); 129 | return blockMap; 130 | } 131 | 132 | /** 133 | * 获取股票某个大分类下的具体分类:概念,行业,地域 134 | * @param type type 135 | * @return stock category 136 | */ 137 | public static Map> getStockCategory(String type){ 138 | if(category.size() == 0){ 139 | category = getCategory(); 140 | } 141 | List stockBlocks = category.get(type); 142 | Map> symbolMap = Maps.newTreeMap(); 143 | if(stockBlocks!=null){ 144 | for(StockBlock stockBlock :stockBlocks){ 145 | for(String symbol:stockBlock.symbolList){ 146 | Set category = symbolMap.get(symbol); 147 | if(category == null){ 148 | category = Sets.newHashSet(); 149 | } 150 | category.add(stockBlock.name); 151 | symbolMap.put(symbol, category); 152 | } 153 | } 154 | }else{ 155 | LOG.warn("get stock category failed"); 156 | } 157 | return symbolMap; 158 | } 159 | 160 | /** 161 | * 获取股票的全部类别,包含概念,行业,地域 162 | * @return stock category 163 | */ 164 | public static Map> getStockCategory(){ 165 | Map> stockCategory = category; 166 | Map> symbolMap = Maps.newTreeMap(); 167 | for(List stockBlocks:stockCategory.values()){ 168 | for(StockBlock stockBlock :stockBlocks){ 169 | for(String symbol:stockBlock.symbolList){ 170 | List category = symbolMap.get(symbol); 171 | if(category == null){ 172 | category = Lists.newLinkedList(); 173 | } 174 | category.add(stockBlock.name); 175 | symbolMap.put(symbol, category); 176 | } 177 | } 178 | } 179 | return symbolMap; 180 | } 181 | 182 | /** 183 | * 获取某个板块下的全部股票 184 | */ 185 | private static List getBlockStockList(String id) { 186 | String url = String.format(blockStockListURL,id); 187 | String data = Downloader.download(url); 188 | Pattern pattern = Pattern.compile("(\\[.*\\])"); 189 | Matcher matcher = pattern.matcher(data); 190 | if(matcher.find()){ 191 | List blockStockList = Lists.newLinkedList(); 192 | String group = matcher.group(); 193 | List lines = new Gson().fromJson(group,List.class); 194 | for(String line:lines){ 195 | String[] fields = line.split(","); 196 | blockStockList.add(fields[1]); 197 | } 198 | return blockStockList; 199 | } 200 | return Lists.newLinkedList(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/StockList.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.base.Predicate; 4 | import com.google.common.base.Strings; 5 | import com.google.common.collect.*; 6 | import net.jquant.common.Utils; 7 | import net.jquant.downloader.Downloader; 8 | import net.jquant.model.StockData; 9 | import org.jsoup.Jsoup; 10 | import org.jsoup.nodes.Document; 11 | import org.jsoup.nodes.Element; 12 | import org.jsoup.select.Elements; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import net.jquant.common.ParallelProcesser; 16 | import net.jquant.provider.RealTimeDataProvider; 17 | 18 | import java.io.IOException; 19 | import java.util.*; 20 | import java.util.concurrent.CountDownLatch; 21 | import java.util.concurrent.ExecutorService; 22 | import java.util.concurrent.Executors; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | 27 | /** 28 | * 非线程安全 29 | */ 30 | public class StockList { 31 | private static final Logger LOG = LoggerFactory.getLogger(StockList.class); 32 | 33 | private static String stockURL = "http://quote.eastmoney.com/stocklist.html"; 34 | 35 | private static String stockDetailURL = "http://hqchart.eastmoney.com/hq20/js/%s.js?%s"; 36 | 37 | private static String marginTradingURL = "http://app.finance.ifeng.com/hq/trade/rzrq_list.php?type=day"; 38 | 39 | private static int RETRY_TIMES = 3; 40 | private static int SLEEP_INTERVAL_MS = 3000; 41 | 42 | private Map stockMap; 43 | private List stockDataList; 44 | private List blackList = Lists.newLinkedList(); 45 | 46 | public StockList(){ 47 | } 48 | 49 | public StockList create() { 50 | if (stockMap == null) { 51 | stockMap = getStockName(); 52 | } 53 | if (stockDataList == null){ 54 | stockDataList = getStockDataList(Lists.newArrayList(stockMap.keySet())); 55 | } 56 | blackList = Lists.newLinkedList(); 57 | return this; 58 | } 59 | 60 | /** 61 | * @param column column name 62 | * @param order 取值为asc或desc 63 | * @return stocklist 64 | */ 65 | public StockList orderBy(String column, String order) { 66 | if ("asc".equals("order") || "desc".equals("order")) { 67 | Ordering ordering = new Ordering() { 68 | @Override 69 | public int compare(StockData sd1, StockData sd2) { 70 | if (sd1.get(column) != null && sd2.get(column) != null) { 71 | return sd1.get(column).compareTo(sd2.get(column)); 72 | } else { 73 | return 0; 74 | } 75 | } 76 | }; 77 | 78 | List results = Lists.newLinkedList(); 79 | if (order.equals("desc")) { 80 | results.addAll(ordering.reverse().sortedCopy(stockDataList)); 81 | } else { 82 | results.addAll(ordering.sortedCopy(stockDataList)); 83 | } 84 | stockDataList = results; 85 | } 86 | return this; 87 | } 88 | 89 | public StockList blacklist(String symbol){ 90 | blackList.add(symbol); 91 | return this; 92 | } 93 | 94 | public StockList blacklist(List symbols){ 95 | blackList.addAll(symbols); 96 | return this; 97 | } 98 | 99 | public StockList filter(List filters) { 100 | List stockDatas = Lists.newLinkedList(); 101 | for(StockData stockData:stockDataList){ 102 | if(filter(stockData,filters)){ 103 | stockDatas.add(stockData); 104 | } 105 | } 106 | stockDataList = stockDatas; 107 | return this; 108 | } 109 | 110 | public StockList condition(Conditions conditions) { 111 | List stockDatas = Collections.synchronizedList(new LinkedList()); 112 | for (StockData stockData : stockDataList) { 113 | if (conditions.check(stockData)) { 114 | stockDatas.add(stockData); 115 | } 116 | } 117 | stockDataList = stockDatas; 118 | return this; 119 | } 120 | 121 | public List get() { 122 | return stockDataList; 123 | } 124 | 125 | public List get(List stockDatas){ 126 | List stockList = Lists.newLinkedList(); 127 | for(StockData stockData : stockDatas){ 128 | if(Strings.isNullOrEmpty(stockData.symbol)){ 129 | continue; 130 | } 131 | stockList.add(stockData.symbol); 132 | } 133 | return stockList; 134 | } 135 | 136 | private boolean filter(StockData stockData,List filters){ 137 | for(String filter:filters){ 138 | if("st".equals(filter.toLowerCase().trim())){ 139 | if(Strings.isNullOrEmpty(stockData.name)){ 140 | LOG.error(stockData.toString()); 141 | continue; 142 | }else if(stockData.name.toLowerCase().trim().contains("st")){ 143 | return false; 144 | } 145 | } 146 | } 147 | return true; 148 | } 149 | 150 | /** 151 | * 获取股票状态,分为三种情况:已退市,停牌中,交易中 152 | * @param symbol stock symbol 153 | * @throws IOException IOException 154 | * @return stock status 155 | */ 156 | public static String getStockStatus(String symbol) throws IOException { 157 | Random random = new Random(); 158 | String url = String.format(stockDetailURL, symbol, random.nextInt(999999)); 159 | String content = Downloader.download(url); 160 | if (Strings.isNullOrEmpty(content)) { 161 | return "delisted"; //退市 162 | } else { 163 | Pattern pattern = Pattern.compile("data:\"(.*)\",update"); 164 | Matcher matcher = pattern.matcher(content); 165 | if (matcher.find() && !matcher.group(1).contains("-")) { 166 | return "suspended"; //停牌 167 | } 168 | } 169 | return "trading"; //交易中 170 | } 171 | 172 | public static List getSymbols() { 173 | return Lists.newArrayList(getStockName().keySet()); 174 | } 175 | 176 | 177 | /** 178 | * key:symbol 179 | * value:stock name 180 | */ 181 | private static Map getStockName() { 182 | int zxb = 0; 183 | int sh = 0; 184 | int sz = 0; 185 | int cyb = 0; 186 | int other = 0; 187 | //key:code,val:name 188 | Map stockMap = Maps.newTreeMap(); 189 | int retryTimes = 0; 190 | while (retryTimes < RETRY_TIMES) { 191 | try { 192 | Document doc = Jsoup.connect(stockURL).get(); 193 | Elements stocks = doc.select("div[id=quotesearch] li a"); 194 | for (Element stock : stocks) { 195 | String url = stock.attr("href"); 196 | if (url.contains("sh6")) { 197 | ++sh; 198 | } else if (url.contains("sz000")) { 199 | ++sz; 200 | } else if (url.contains("sz002")) { 201 | ++zxb; 202 | } else if (url.contains("sz300")) { 203 | ++cyb; 204 | } else { 205 | ++other; 206 | continue; 207 | } 208 | String[] stockArr = stock.text().split("\\("); 209 | //key:name,value:symbol 210 | if (stockArr.length == 2) { 211 | stockMap.put(stockArr[1].replaceAll("\\)", ""), stockArr[0]); 212 | } else { 213 | LOG.error("can't split:" + stock.text()); 214 | } 215 | } 216 | LOG.info("6:" + sh + ",000:" + sz + ",002:" + zxb + ",300:" + cyb + ",other:" + other); 217 | LOG.info("total:" + (sh + sz + zxb + cyb)); 218 | return stockMap; 219 | } catch (IOException e) { 220 | LOG.error("fail to get stock list", e); 221 | retryTimes++; 222 | try { 223 | Thread.sleep(SLEEP_INTERVAL_MS); 224 | } catch (InterruptedException e1) { 225 | LOG.error("fail to sleep " + SLEEP_INTERVAL_MS + "ms"); 226 | } 227 | } 228 | } 229 | return Maps.newLinkedHashMap(); 230 | } 231 | 232 | /** 233 | * 获取中小板股票列表 234 | */ 235 | private List getSMEStockList() { 236 | return getSMEStockList(getSymbols()); 237 | } 238 | 239 | private List getSMEStockList(List stockList) { 240 | Collection list = Collections2.filter(stockList, (String input) -> input.startsWith("002")); 241 | return Lists.newArrayList(list); 242 | } 243 | 244 | /** 245 | * 获取创业板股票列表 246 | */ 247 | private List getGEMStockList() { 248 | Collection list = Collections2.filter(getSymbols(), (String input) -> input.startsWith("300")); 249 | return Lists.newArrayList(list); 250 | } 251 | 252 | private List getSTStockList() { 253 | Map map = getStockName(); 254 | List list = Lists.newArrayList(); 255 | for (Map.Entry entry : map.entrySet()) { 256 | if (entry.getValue().toLowerCase().contains("st")) 257 | list.add(entry.getKey()); 258 | } 259 | return list; 260 | } 261 | 262 | /** 263 | * 获取股票列表中“交易中”的股票,即未退市和停牌的股票列表 264 | * @return stock list 265 | */ 266 | public static List getTradingStockList() { 267 | return getTradingStockList(getSymbols()); 268 | } 269 | 270 | public static List getTradingStockList(Conditions conditions) { 271 | return getStockListWithConditions(getTradingStockList(), conditions); 272 | } 273 | 274 | public static List getStockListWithConditions(List stockList, Conditions conditions) { 275 | ExecutorService service = Executors.newFixedThreadPool(10); 276 | 277 | List tradingStockList = stockList; 278 | CountDownLatch countDownLatch = new CountDownLatch(tradingStockList.size()); 279 | List stocks = Collections.synchronizedList(new LinkedList()); 280 | for (String stock : tradingStockList) { 281 | service.execute(() -> { 282 | StockData stockData = RealTimeDataProvider.get(stock); 283 | if (conditions.check(stockData)) { 284 | stocks.add(stock); 285 | } 286 | countDownLatch.countDown(); 287 | }); 288 | } 289 | try { 290 | countDownLatch.await(); 291 | } catch (InterruptedException e) { 292 | e.printStackTrace(); 293 | } 294 | Utils.closeThreadPool(service); 295 | return stocks; 296 | } 297 | 298 | /** 299 | * 按照代码获取股票最新状态 300 | * @param stockList stock list 301 | * @return stock list 302 | */ 303 | private static List getStockDataList(List stockList) { 304 | final List stockDatas = Collections.synchronizedList(new LinkedList()); 305 | CountDownLatch countDownLatch = new CountDownLatch(stockList.size()); 306 | for (String stock : stockList) { 307 | ParallelProcesser.process(() -> { 308 | StockData stockData = RealTimeDataProvider.get(stock); 309 | if (stockData != null) { 310 | stockDatas.add(stockData); 311 | } 312 | countDownLatch.countDown(); 313 | }); 314 | } 315 | try { 316 | countDownLatch.await(); 317 | } catch (InterruptedException e) { 318 | e.printStackTrace(); 319 | } 320 | return stockDatas; 321 | } 322 | 323 | /** 324 | * 融资融券股票列表 325 | * @return stock list 326 | */ 327 | public static List getMarginTradingStockList() { 328 | List marginTradingStockList = Lists.newLinkedList(); 329 | String data = Downloader.download(marginTradingURL); 330 | Elements element = Jsoup.parse(data).select("div[class=tab01]").get(0).getElementsByTag("table").get(0).getElementsByTag("tr"); 331 | for (int i = 1; i < element.size(); i++) { 332 | String symbol = element.get(i).getElementsByTag("td").get(0).text(); 333 | marginTradingStockList.add(symbol); 334 | } 335 | 336 | return marginTradingStockList; 337 | } 338 | 339 | private List getMarginTradingStockList(List list) { 340 | Set marginTradingSet = Sets.newHashSet(getMarginTradingStockList()); 341 | Collection results = Collections2.filter(list, new Predicate() { 342 | @Override 343 | public boolean apply(String symbol) { 344 | return marginTradingSet.contains(symbol); 345 | } 346 | }); 347 | return Lists.newArrayList(results); 348 | } 349 | 350 | /** 351 | * 获取指定股票列表中交易中的股票 352 | * @param list stock list 353 | * @return stock list 354 | */ 355 | public static List getTradingStockList(List list) { 356 | final List stockList = Collections.synchronizedList(new LinkedList()); 357 | ExecutorService threadPool = Executors.newFixedThreadPool(30); 358 | final CountDownLatch countDownLatch = new CountDownLatch(list.size()); 359 | for (final String symbol : list) { 360 | threadPool.execute(() -> { 361 | try { 362 | if (getStockStatus(symbol).equals("trading")) { 363 | stockList.add(symbol); 364 | } 365 | } catch (IOException e) { 366 | e.printStackTrace(); 367 | } 368 | countDownLatch.countDown(); 369 | }); 370 | } 371 | try { 372 | countDownLatch.await(); 373 | } catch (InterruptedException e) { 374 | e.printStackTrace(); 375 | } 376 | Utils.closeThreadPool(threadPool); 377 | return stockList; 378 | } 379 | 380 | } 381 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/StockPool.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.collect.Lists; 4 | import net.jquant.common.StockDataParseException; 5 | import net.jquant.model.StockBlock; 6 | import net.jquant.provider.Provider; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * author: eryk 12 | * mail: xuqi86@gmail.com 13 | * date: 15-11-21. 14 | */ 15 | public class StockPool { 16 | 17 | private static final String REDIS_KEY_PREFIX = "stocklist:"; 18 | 19 | public StockPool() { 20 | } 21 | 22 | public String getKeyWithPrefix(String key) { 23 | return REDIS_KEY_PREFIX + key; 24 | } 25 | 26 | /** 27 | * stockpool添加list到redis存储 28 | * 29 | * @param name name 30 | * @param stockList stocklist 31 | * @param timeout Unit: Second 32 | */ 33 | public void add(String name, List stockList, long timeout) { 34 | // redisTemplate.delete(getKeyWithPrefix(name)); 35 | // String[] valueList = Utils.toArray(stockList); 36 | // redisTemplate.opsForList().rightPushAll(getKeyWithPrefix(name), valueList); 37 | // redisTemplate.expire(getKeyWithPrefix(name), timeout, TimeUnit.SECONDS); 38 | } 39 | 40 | public List get(String key) { 41 | // return redisTemplate.opsForList().range(getKeyWithPrefix(key), 0, 5000); 42 | return null; 43 | } 44 | 45 | /** 46 | * 获取交易中的股票列表 47 | * @return stock list 48 | */ 49 | public List tradingStock(){ 50 | String key = "trade"; 51 | List stockList = Lists.newLinkedList(); 52 | if(stockList==null || stockList.size()==0){ 53 | stockList = StockList.getTradingStockList(); 54 | add(key,stockList,86400); 55 | } 56 | return stockList; 57 | } 58 | 59 | /** 60 | * 获取股票列表中交易中的股票列表 61 | * @param stockList stock list 62 | * @return stock list 63 | */ 64 | public List tradingStock(List stockList) { 65 | List tradingStockList = Lists.newArrayList(tradingStock()); 66 | stockList.retainAll(tradingStockList); 67 | return stockList; 68 | } 69 | 70 | /** 71 | * 获取融资融券股票列表 72 | * @return stock list 73 | */ 74 | public List marginTradingStock() { 75 | String key = "margin"; 76 | List stockList = Lists.newLinkedList(); 77 | if(stockList==null){ 78 | stockList = StockList.getMarginTradingStockList(); 79 | add(key,stockList,86400); 80 | } 81 | return stockList; 82 | } 83 | 84 | /** 85 | * 全部股票列表 86 | * @return stock list 87 | */ 88 | public List stockList(){ 89 | try { 90 | return getList("all",86400); 91 | } catch (StockDataParseException e) { 92 | e.printStackTrace(); 93 | } 94 | return null; 95 | } 96 | 97 | /** 98 | * 获取某个大分类下的版块名称 99 | * 100 | * @param category 概念,行业,地域 101 | * @param name name 102 | * @return stock category list 103 | */ 104 | public List listByCategory(String category, String name) { 105 | List stockBlocks = StockCategory.getCategory().get(category); 106 | for (StockBlock stockBlock : stockBlocks) { 107 | if (stockBlock.name.equals(name)) { 108 | return tradingStock(stockBlock.symbolList); 109 | } 110 | } 111 | return Lists.newArrayList(); 112 | } 113 | 114 | public List listByConditions(Conditions conditions) { 115 | List tradingStockList = tradingStock(); 116 | return StockList.getStockListWithConditions(tradingStockList, conditions); 117 | } 118 | 119 | private List getList(String key,long timeout) throws StockDataParseException { 120 | List stockList = Lists.newLinkedList(); 121 | if(stockList==null || stockList.size()==0){ 122 | stockList = Provider.stockList(); 123 | } 124 | return stockList; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/Suggest.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.Gson; 5 | import net.jquant.downloader.Downloader; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * author: eryk 12 | * mail: xuqi86@gmail.com 13 | * date: 15-8-15. 14 | */ 15 | public class Suggest { 16 | 17 | private static final String suggestURL = "http://app.finance.ifeng.com/hq/suggest_v2.php?t=stock&q=%s"; 18 | 19 | public static final String NAME = "n"; 20 | public static final String CODE = "s"; 21 | public static final String PIN_YIN = "p"; 22 | public static final String TYPE = "t"; 23 | 24 | /** 25 | * 输入中文或者英文片段查询股票,例如:中国,282等关键词 26 | * @param query query field 27 | * @return stock name and code 28 | */ 29 | public static List> suggest(String query) { 30 | //例如:[{"c":"sz000009","s":"000009","n":"\u4e2d\u56fd\u5b9d\u5b89","p":"ZGBA","t":"stock"},{"c":"sz000035","s":"000035","n":"\u4e2d\u56fd\u5929\u6979","p":"ZGTY","t":"stock"},{"c":"sz000797","s":"000797","n":"\u4e2d\u56fd\u6b66\u5937","p":"ZGWY","t":"stock"},{"c":"sz000951","s":"000951","n":"\u4e2d\u56fd\u91cd\u6c7d","p":"ZGZQ","t":"stock"},{"c":"sz000996","s":"000996","n":"\u4e2d\u56fd\u4e2d\u671f","p":"ZGZQ","t":"stock"},{"c":"sz002116","s":"002116","n":"\u4e2d\u56fd\u6d77\u8bda","p":"ZGHC","t":"stock"},{"c":"sh600007","s":"600007","n":"\u4e2d\u56fd\u56fd\u8d38","p":"ZGGM","t":"stock"},{"c":"sh600028","s":"600028","n":"\u4e2d\u56fd\u77f3\u5316","p":"ZGSH","t":"stock"},{"c":"sh600050","s":"600050","n":"\u4e2d\u56fd\u8054\u901a","p":"ZGLT","t":"stock"},{"c":"sh600056","s":"600056","n":"\u4e2d\u56fd\u533b\u836f","p":"ZGYY","t":"stock"}] 31 | String data = Downloader.download(getPath(query)); 32 | if(!data.contains("[")){ 33 | return Lists.newLinkedList(); 34 | } 35 | data = data.substring(data.indexOf("[") - 1, data.indexOf("]")+1).trim(); 36 | Gson gson = new Gson(); 37 | List> results = gson.fromJson(data,List.class); 38 | return results; 39 | } 40 | 41 | public static String getPath(String query) { 42 | return String.format(suggestURL, query); 43 | } 44 | 45 | public static void main(String[] args) { 46 | List> results = Suggest.suggest("282"); 47 | System.out.println(results.size()); 48 | for(Map map:results){ 49 | System.out.println(map.get(NAME) + ":"+map.get(CODE)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/tableformat/AbstractTableFormatter.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools.tableformat; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * author: eryk 8 | * mail: xuqi86@gmail.com 9 | * date: 15-9-2. 10 | */ 11 | public abstract class AbstractTableFormatter implements TableFormatter { 12 | protected class CellData { 13 | private List lines; 14 | private int align; 15 | private int valign; 16 | 17 | private CellData() { 18 | super(); 19 | } 20 | 21 | public CellData(int align, int valign) { 22 | this(); 23 | this.lines = new ArrayList(); 24 | this.align = align; 25 | this.valign = valign; 26 | } 27 | 28 | public void addLine(String text) { 29 | lines.add(text); 30 | } 31 | 32 | public int getLineCount() { 33 | return lines.size(); 34 | } 35 | 36 | public List getLines() { 37 | return new ArrayList(lines); 38 | } 39 | 40 | public int getAlign() { 41 | return align; 42 | } 43 | 44 | public int getVAlign() { 45 | return valign; 46 | } 47 | } 48 | 49 | ; 50 | 51 | private int columnCount = 0; 52 | 53 | private List maxColWidths = new ArrayList(); 54 | private List maxRowHeights = new ArrayList(); 55 | 56 | private List tableData = new ArrayList(); 57 | 58 | /** 59 | * Constructor 60 | */ 61 | protected AbstractTableFormatter() { 62 | super(); 63 | } 64 | 65 | /* 66 | * (non-Javadoc) 67 | * @see com.inamik.utils.TableFormatter#addLine() 68 | */ 69 | public final TableFormatter addLine() { 70 | return addLine(null); 71 | } 72 | 73 | /* 74 | * (non-Javadoc) 75 | * @see com.inamik.utils.TableFormatter#addLine(java.lang.String) 76 | */ 77 | public final TableFormatter addLine(String text) { 78 | if (tableData.size() <= 0) { 79 | throw new IllegalStateException("tableData.size()"); 80 | } 81 | 82 | // Get current (last) row 83 | List row = (List) tableData.get(tableData.size() - 1); 84 | 85 | if (row.size() <= 0) { 86 | throw new IllegalStateException("Cannot addLine() to empty row. Use nextCell() first."); 87 | } 88 | 89 | CellData cell = (CellData) row.get(row.size() - 1); 90 | 91 | // Add line to cell 92 | cell.addLine(text); 93 | 94 | // 95 | // Update max column width 96 | // 97 | 98 | if (maxColWidths.size() != columnCount) { 99 | throw new IllegalStateException("maxColWidths.size()"); 100 | } 101 | 102 | int currentCol = row.size() - 1; 103 | 104 | int maxColWidth = ((Integer) maxColWidths.get(currentCol)).intValue(); 105 | 106 | if (text != null && text.length() > maxColWidth) { 107 | // Pattern pattern = Pattern.compile("^[\\u4E00-\\u9FFF]+$"); 108 | // Matcher matcher = pattern.matcher(text); 109 | // if(matcher.matches()){ 110 | // 111 | // }else{ 112 | // maxColWidths.set(currentCol, new Integer(text.length()*2)); 113 | // } 114 | maxColWidths.set(currentCol, new Integer(text.length())); 115 | } 116 | 117 | // 118 | // Update max row height 119 | // 120 | 121 | if (maxRowHeights.size() != tableData.size()) { 122 | throw new IllegalStateException("maxRowHeights.size()"); 123 | } 124 | 125 | int currentRow = tableData.size() - 1; 126 | 127 | int maxRowHeight = ((Integer) maxRowHeights.get(currentRow)).intValue(); 128 | 129 | if (cell.getLineCount() > maxRowHeight) { 130 | maxRowHeights.set(currentRow, new Integer(cell.getLineCount())); 131 | } 132 | 133 | return this; 134 | } 135 | 136 | /* 137 | * (non-Javadoc) 138 | * @see com.inamik.utils.TableFormatter#nextCell() 139 | */ 140 | public final TableFormatter nextCell() { 141 | return nextCell(ALIGN_DEFAULT, VALIGN_DEFAULT); 142 | } 143 | 144 | /* 145 | * (non-Javadoc) 146 | * @see com.inamik.utils.TableFormatter#nextCell(int, int) 147 | */ 148 | public final TableFormatter nextCell(int align, int valign) { 149 | if ( 150 | (align != ALIGN_DEFAULT) 151 | && (align != ALIGN_LEFT) 152 | && (align != ALIGN_CENTER) 153 | && (align != ALIGN_RIGHT) 154 | ) { 155 | throw new IllegalArgumentException("align"); 156 | } 157 | 158 | if ( 159 | (valign != VALIGN_DEFAULT) 160 | && (valign != VALIGN_TOP) 161 | && (valign != VALIGN_CENTER) 162 | && (valign != VALIGN_BOTTOM) 163 | ) { 164 | throw new IllegalArgumentException("valign"); 165 | } 166 | 167 | if (tableData.size() <= 0) { 168 | throw new IllegalStateException("tableData.size()"); 169 | } 170 | 171 | List row = (List) tableData.get(tableData.size() - 1); 172 | 173 | CellData cell = new CellData(align, valign); 174 | 175 | row.add(cell); 176 | 177 | // Update column count 178 | if (row.size() > columnCount) { 179 | // Should only be off by 1 180 | if ((row.size() - 1) != columnCount) { 181 | throw new IllegalStateException("columnCount"); 182 | } 183 | 184 | columnCount = row.size(); 185 | 186 | maxColWidths.add(new Integer(0)); 187 | } 188 | 189 | return this; 190 | } 191 | 192 | /* 193 | * (non-Javadoc) 194 | * @see com.inamik.utils.TableFormatter#nextRow() 195 | */ 196 | public final TableFormatter nextRow() { 197 | tableData.add(new ArrayList(columnCount)); 198 | 199 | maxRowHeights.add(new Integer(0)); 200 | 201 | return this; 202 | } 203 | 204 | /* 205 | * (non-Javadoc) 206 | * @see com.inamik.utils.TableFormatter#getColumnCount() 207 | */ 208 | public int getColumnCount() { 209 | return columnCount; 210 | } 211 | 212 | /* 213 | * (non-Javadoc) 214 | * @see com.inamik.utils.TableFormatter#getRowCount() 215 | */ 216 | public final int getRowCount() { 217 | if (tableData.size() <= 0) { 218 | throw new IllegalStateException("tableData.size()"); 219 | } 220 | 221 | return tableData.size(); 222 | } 223 | 224 | /* 225 | * (non-Javadoc) 226 | * @see com.inamik.utils.TableFormatter#getColumnWidth(int) 227 | */ 228 | public final int getColumnWidth(int columnIndex) { 229 | if (columnIndex < 0 || columnIndex >= columnCount) { 230 | throw new IllegalArgumentException("columnIndex"); 231 | } 232 | 233 | return ((Integer) maxColWidths.get(columnIndex)).intValue(); 234 | } 235 | 236 | /* 237 | * (non-Javadoc) 238 | * @see com.inamik.utils.TableFormatter#getRowHeight(int) 239 | */ 240 | public final int getRowHeight(int rowIndex) { 241 | if (rowIndex < 0 || rowIndex >= tableData.size()) { 242 | throw new IllegalArgumentException("rowIndex"); 243 | } 244 | 245 | return ((Integer) maxRowHeights.get(rowIndex)).intValue(); 246 | } 247 | 248 | /* 249 | * (non-Javadoc) 250 | * @see com.inamik.utils.TableFormatter#getFormattedCell(int, int) 251 | */ 252 | public final String[] getFormattedCell(int rowIndex, int columnIndex) { 253 | if (rowIndex < 0 || rowIndex >= tableData.size()) { 254 | throw new IllegalArgumentException("rowIndex"); 255 | } 256 | 257 | if (columnIndex < 0 || columnIndex >= columnCount) { 258 | throw new IllegalArgumentException("columnIndex"); 259 | } 260 | 261 | List lines; 262 | int align; 263 | int valign; 264 | 265 | int cellWidth = getColumnWidth(columnIndex); 266 | int cellHeight = getRowHeight(rowIndex); 267 | 268 | List row = (List) tableData.get(rowIndex); 269 | 270 | // Is there a cell at the specified row/col? 271 | if (row.size() > columnIndex) { 272 | CellData cell = (CellData) row.get(columnIndex); 273 | 274 | lines = cell.getLines(); 275 | align = cell.getAlign(); 276 | valign = cell.getVAlign(); 277 | } else { 278 | lines = new ArrayList(); 279 | align = ALIGN_DEFAULT; 280 | valign = VALIGN_DEFAULT; 281 | } 282 | 283 | int vpadding = cellHeight - lines.size(); 284 | 285 | int topPad; 286 | int bottomPad; 287 | 288 | switch (valign) { 289 | case VALIGN_CENTER: { 290 | int carry = vpadding % 2; 291 | 292 | vpadding = vpadding - carry; 293 | 294 | topPad = bottomPad = vpadding / 2; 295 | 296 | bottomPad += carry; 297 | 298 | break; 299 | } 300 | case VALIGN_BOTTOM: { 301 | topPad = vpadding; 302 | bottomPad = 0; 303 | break; 304 | } 305 | // Deafault - Top 306 | default: { 307 | topPad = 0; 308 | bottomPad = vpadding; 309 | break; 310 | } 311 | } 312 | 313 | List result = new ArrayList(cellHeight); 314 | 315 | for (int i = 0; i < topPad; ++i) { 316 | result.add(getFormattedLine("", cellWidth, align)); 317 | } 318 | 319 | for (int i = 0; i < lines.size(); ++i) { 320 | result.add(getFormattedLine((String) lines.get(i), cellWidth, align)); 321 | } 322 | 323 | for (int i = 0; i < bottomPad; ++i) { 324 | result.add(getFormattedLine("", cellWidth, align)); 325 | } 326 | 327 | if (result.size() != cellHeight) { 328 | throw new IllegalStateException("result.size()"); 329 | } 330 | 331 | return (String[]) result.toArray(new String[result.size()]); 332 | } 333 | 334 | /** 335 | * getFormattedLine 336 | */ 337 | private final String getFormattedLine(String text, int lineLength, int align) { 338 | if (text == null) { 339 | text = ""; 340 | } 341 | 342 | int padding = lineLength - text.length(); 343 | 344 | int leftPad; 345 | int rightPad; 346 | 347 | switch (align) { 348 | case ALIGN_CENTER: { 349 | int carry = padding % 2; 350 | 351 | padding = padding - carry; 352 | 353 | leftPad = rightPad = padding / 2; 354 | 355 | rightPad += carry; 356 | 357 | break; 358 | } 359 | case ALIGN_RIGHT: { 360 | leftPad = padding; 361 | rightPad = 0; 362 | break; 363 | } 364 | // Deafault - Left 365 | default: { 366 | leftPad = 0; 367 | rightPad = padding; 368 | break; 369 | } 370 | } 371 | 372 | StringBuffer result = new StringBuffer(); 373 | 374 | for (int i = 0; i < leftPad; ++i) { 375 | result.append(' '); 376 | } 377 | 378 | result.append(text); 379 | 380 | for (int i = 0; i < rightPad; ++i) { 381 | result.append(' '); 382 | } 383 | 384 | return result.toString(); 385 | } 386 | 387 | /* 388 | * (non-Javadoc) 389 | * @see com.inamik.utils.TableFormatter#getTableWidth() 390 | */ 391 | public int getTableWidth() { 392 | if (maxColWidths.size() != getColumnCount()) { 393 | throw new IllegalStateException("maxColWidths.size()"); 394 | } 395 | 396 | int width = 0; 397 | 398 | for (int i = 0; i < columnCount; ++i) { 399 | width += ((Integer) maxColWidths.get(i)).intValue(); 400 | } 401 | 402 | return width; 403 | } 404 | 405 | /* 406 | * (non-Javadoc) 407 | * @see com.inamik.utils.TableFormatter#getTableHeight() 408 | */ 409 | public int getTableHeight() { 410 | if (maxRowHeights.size() != getRowCount()) { 411 | throw new IllegalStateException("maxRowHeights.size()"); 412 | } 413 | 414 | int height = 0; 415 | 416 | for (int i = 0, size = maxRowHeights.size(); i < size; i++) { 417 | height += ((Integer) maxRowHeights.get(i)).intValue(); 418 | } 419 | 420 | return height; 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/tableformat/SimpleTableFormatter.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools.tableformat; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * author: eryk 8 | * mail: xuqi86@gmail.com 9 | * date: 15-9-2. 10 | */ 11 | public final class SimpleTableFormatter extends AbstractTableFormatter implements TableFormatter 12 | { 13 | private boolean border = false; 14 | 15 | /** 16 | * Constructor 17 | */ 18 | public SimpleTableFormatter() 19 | { 20 | super(); 21 | } 22 | 23 | /** 24 | * Constructor 25 | * @param border Print rows/tables with borders 26 | */ 27 | public SimpleTableFormatter(boolean border) 28 | { 29 | this(); 30 | 31 | this.border = border; 32 | } 33 | 34 | /* 35 | * (non-Javadoc) 36 | * @see com.inamik.utils.TableFormatter#getTableWidth() 37 | */ 38 | public int getTableWidth() 39 | { 40 | int width = super.getTableWidth(); 41 | 42 | if (border == true) 43 | { 44 | width += 2; 45 | 46 | if (getColumnCount() > 1) 47 | { 48 | width += getColumnCount() - 1; 49 | } 50 | } 51 | 52 | return width; 53 | } 54 | 55 | /* 56 | * (non-Javadoc) 57 | * @see com.inamik.utils.TableFormatter#getTableHeight() 58 | */ 59 | public int getTableHeight() 60 | { 61 | int height = super.getTableHeight(); 62 | 63 | if (border == true) 64 | { 65 | height += 2; 66 | 67 | if (getRowCount() > 1) 68 | { 69 | height += getRowCount() - 1; 70 | } 71 | } 72 | 73 | return height; 74 | } 75 | 76 | /* 77 | * (non-Javadoc) 78 | * @see com.inamik.utils.TableFormatter#getFormattedRow(int) 79 | */ 80 | public String[] getFormattedRow(int rowIndex) 81 | { 82 | if (rowIndex < 0 || rowIndex >= getRowCount()) 83 | { 84 | throw new IllegalArgumentException("rowIndex"); 85 | } 86 | 87 | int cellHeight = getRowHeight(rowIndex); 88 | 89 | List rowLines = new ArrayList(cellHeight); 90 | 91 | for (int i = 0; i < cellHeight; ++i) 92 | { 93 | StringBuffer buffer = new StringBuffer(); 94 | 95 | if (border == true) 96 | { 97 | buffer.append('|'); 98 | } 99 | 100 | rowLines.add(buffer); 101 | } 102 | 103 | if (rowLines.size() != cellHeight) 104 | { 105 | throw new IllegalStateException("rowLines.size()"); 106 | } 107 | 108 | for (int columnIndex = 0, columnCount = getColumnCount(); columnIndex < columnCount; ++columnIndex) 109 | { 110 | String[] cell = getFormattedCell(rowIndex, columnIndex); 111 | 112 | if (cell.length != cellHeight) 113 | { 114 | throw new IllegalStateException("cell.size()"); 115 | } 116 | 117 | for (int i = 0; i < cellHeight; ++i) 118 | { 119 | StringBuffer buffer = (StringBuffer)rowLines.get(i); 120 | 121 | if (columnIndex > 0) 122 | { 123 | if (border == true) 124 | { 125 | buffer.append('|'); 126 | } 127 | // else 128 | // { 129 | // buffer.append(' '); 130 | // } 131 | } 132 | 133 | buffer.append(cell[i]); 134 | } 135 | } 136 | 137 | if (border == true) 138 | { 139 | for (int i = 0; i < cellHeight; ++i) 140 | { 141 | StringBuffer buffer = (StringBuffer)rowLines.get(i); 142 | 143 | buffer.append('|'); 144 | } 145 | } 146 | 147 | if (rowLines.size() != cellHeight) 148 | { 149 | throw new IllegalStateException("rowLines.size()"); 150 | } 151 | 152 | List result = new ArrayList(cellHeight); 153 | 154 | for (int i = 0; i < cellHeight; ++i) 155 | { 156 | StringBuffer buffer = (StringBuffer)rowLines.get(i); 157 | 158 | result.add(buffer.toString()); 159 | } 160 | 161 | if (result.size() != cellHeight) 162 | { 163 | throw new IllegalStateException("result.size()"); 164 | } 165 | 166 | return (String[])result.toArray(new String[result.size()]); 167 | } 168 | 169 | /* 170 | * (non-Javadoc) 171 | * @see com.inamik.utils.TableFormatter#getFormattedTable() 172 | */ 173 | public String[] getFormattedTable() 174 | { 175 | List result = new ArrayList(); 176 | 177 | String borderText = null; 178 | 179 | if (border == true) 180 | { 181 | borderText = getFormattedBorder(); 182 | result.add(borderText); 183 | } 184 | 185 | for (int rowIndex = 0, rowCount = getRowCount(); rowIndex < rowCount; ++rowIndex) 186 | { 187 | if (rowIndex > 0) 188 | { 189 | if (border == true) 190 | { 191 | result.add(borderText); 192 | } 193 | } 194 | 195 | String[] row = getFormattedRow(rowIndex); 196 | 197 | for (int i = 0, size=row.length; i < size; ++i) 198 | { 199 | result.add(row[i]); 200 | } 201 | } 202 | 203 | if (border == true) 204 | { 205 | result.add(borderText); 206 | } 207 | 208 | return (String[])result.toArray(new String[result.size()]); 209 | } 210 | 211 | /** 212 | * getFormattedBorder 213 | */ 214 | private String getFormattedBorder() 215 | { 216 | StringBuffer result = new StringBuffer(); 217 | 218 | result.append('+'); 219 | 220 | for (int columnIndex = 0, columnCount = getColumnCount(); columnIndex < columnCount; ++columnIndex) 221 | { 222 | if (columnIndex > 0) 223 | { 224 | result.append('+'); 225 | } 226 | 227 | for (int i = 0; i < getColumnWidth(columnIndex); ++i) 228 | { 229 | result.append('-'); 230 | } 231 | } 232 | 233 | result.append('+'); 234 | 235 | return result.toString(); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/tools/tableformat/TableFormatter.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools.tableformat; 2 | 3 | /** 4 | * author: eryk 5 | * mail: xuqi86@gmail.com 6 | * date: 15-9-2. 7 | */ 8 | public interface TableFormatter { 9 | static final int ALIGN_DEFAULT = 0; 10 | static final int ALIGN_LEFT = 1; 11 | static final int ALIGN_CENTER = 2; 12 | static final int ALIGN_RIGHT = 3; 13 | 14 | static final int VALIGN_DEFAULT = 4; 15 | static final int VALIGN_TOP = 5; 16 | static final int VALIGN_CENTER = 6; 17 | static final int VALIGN_BOTTOM = 7; 18 | 19 | TableFormatter nextRow(); 20 | TableFormatter nextCell(); 21 | TableFormatter nextCell(int align, int valign); 22 | TableFormatter addLine(); 23 | TableFormatter addLine(String text); 24 | int getColumnCount(); 25 | int getRowCount(); 26 | int getColumnWidth(int columnIndex); 27 | int getRowHeight(int rowIndex); 28 | int getTableWidth(); 29 | int getTableHeight(); 30 | String[] getFormattedCell(int rowIndex, int columnIndex); 31 | String[] getFormattedRow(int rowIndex); 32 | String[] getFormattedTable(); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/BackTester.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import net.jquant.common.Context; 4 | import org.joda.time.DateTime; 5 | 6 | public class BackTester { 7 | private Context context; 8 | 9 | private DateTime start; 10 | private DateTime end; 11 | private String frequency; //day,minute,tick 12 | 13 | public void init(Context context){ 14 | this.context = context; 15 | } 16 | 17 | 18 | 19 | public void handleDate(){ 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Commission.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | //交易手续费 4 | public class Commission { 5 | private final double buyCost; //买入时手续费 6 | private final double sellCost; //卖出时手续费 7 | private final double minCost; //卖出时手续费 8 | 9 | //每笔交易时的手续费是, 买入时万分之三,卖出时万分之三加千分之一印花税, 每笔交易最低扣5块钱 10 | public Commission(){ 11 | buyCost = 0.0003; 12 | sellCost = 0.0013; 13 | minCost = 5; 14 | } 15 | 16 | public Commission(double buyCost,double sellCost,double minCost){ 17 | this.buyCost = buyCost; 18 | this.sellCost = sellCost; 19 | this.minCost = minCost; 20 | } 21 | 22 | public double getBuyCost() { 23 | return buyCost; 24 | } 25 | 26 | public double getSellCost() { 27 | return sellCost; 28 | } 29 | 30 | public double getMinCost() { 31 | return minCost; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/FixedSlippage.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | //固定值滑点 4 | public class FixedSlippage implements Slippage{ 5 | 6 | private double slip = 0.02; 7 | 8 | public FixedSlippage(){} 9 | 10 | public FixedSlippage(double slip){ 11 | this.slip = slip; 12 | } 13 | 14 | @Override 15 | public double slip(OrderType type,double price) { 16 | if(type == OrderType.BUY){ 17 | return price + slip; 18 | }else{ 19 | return price - slip; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Order.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import org.joda.time.DateTime; 4 | 5 | //买卖订单 6 | public class Order { 7 | private OrderStatus status; //状态, 一个OrderStatus值 8 | private DateTime add_time; //订单添加时间, datetime.datetime对象 9 | private OrderType orderType; //买还是卖 10 | private long amount; //下单数量, 不管是买还是卖, 都是正数 11 | private long filled; //已经成交的股票数量, 正数 12 | private String symbol; //股票代码 13 | private String order_id; //订单ID 14 | private double price; //平均成交价格, 已经成交的股票的平均成交价格(一个订单可能分多次成交) 15 | private double avg_cost; //卖出时表示下卖单前的此股票的持仓成本, 用来计算此次卖出的收益. 买入时表示此次买入的均价(等同于price). 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | public enum OrderStatus { 4 | //未成交 5 | open(0), 6 | //部分成交 7 | filled(1), 8 | //已撤销 9 | canceled(2), 10 | //交易所已拒绝 11 | rejected(3), 12 | //全部成交 13 | held(4); 14 | 15 | Integer status; 16 | 17 | OrderStatus(int status){ 18 | this.status = status; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/OrderStyle.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | public enum OrderStyle { 4 | //市价单: 不论价格, 直接下单, 直到交易全部完成 5 | MarketOrderStyle, 6 | //限价单: 指定一个价格, 买入时不能高于它, 卖出时不能低于它, 如果不满足, 则等待满足后再交易 7 | LimitOrderStyle; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/OrderType.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | public enum OrderType { 4 | BUY,SELL; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Portfolio.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.Map; 7 | import java.util.TreeMap; 8 | 9 | public class Portfolio { 10 | public static final String DEFAULT_NAME = "JQuant"; 11 | private String name; 12 | 13 | private Record status = new Record(LocalDateTime.MIN); 14 | //需要在初始化时赋值 15 | private RiskAnalysis riskAnalysis = new RiskAnalysis(); 16 | //记录各个时间点账户状态 17 | private TreeMap records = new TreeMap(); 18 | //最后更新record的时间点 19 | private LocalDateTime ts; 20 | //记录账户当前持仓情况 21 | private Map positions = Maps.newConcurrentMap(); 22 | 23 | public Portfolio(String name, LocalDateTime startDate){ 24 | this.name = name; 25 | this.ts = startDate; 26 | records.put(ts, new Record(ts)); 27 | } 28 | 29 | public Portfolio(){ 30 | this(DEFAULT_NAME, LocalDateTime.MIN); 31 | } 32 | 33 | public Portfolio(String name){ 34 | this(name, LocalDateTime.MIN); 35 | } 36 | 37 | public Record getStatus() { 38 | return status; 39 | } 40 | 41 | public void setStatus(Record status) { 42 | this.status = status; 43 | } 44 | 45 | public Map getPositions() { 46 | return positions; 47 | } 48 | 49 | public void setPositions(Map positions){ 50 | this.positions = positions; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Position.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | //持有一只股票详情 6 | public class Position { 7 | public String stockName; //证券名称 8 | public LocalDateTime ts; //更新时间戳 9 | public String symbol; //证券代码 10 | public int amount; //证券数量 11 | public int canSell; //可卖数量 12 | public double costPrice; //成本价 13 | public double floatPnl; //浮动盈亏 14 | public double pnlRatio; //盈亏比例 15 | public double latestValue; //最新市值 16 | public double close; //当前价 17 | public double buyAmount; //今买数量 18 | public double sellAmount; //今卖数量 19 | 20 | public Position(){} 21 | 22 | public Position(String symbol,LocalDateTime ts,int amount,double price,double close){ 23 | this.symbol = symbol; 24 | this.ts = ts; 25 | this.amount = amount; 26 | this.canSell = amount; 27 | this.costPrice = price; 28 | this.floatPnl = amount*(price-close); 29 | this.pnlRatio = 0; 30 | this.latestValue = amount * price; 31 | this.close = close; 32 | this.buyAmount = amount; 33 | this.sellAmount = 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/PriceRelatedSlippage.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | //百分比滑点 4 | public class PriceRelatedSlippage implements Slippage{ 5 | private double slip = 0.002; 6 | 7 | public PriceRelatedSlippage(double slip){ 8 | this.slip = slip; 9 | } 10 | 11 | @Override 12 | public double slip(OrderType type,double price) { 13 | if(type == OrderType.BUY){ 14 | return price * (1 + slip); 15 | }else{ 16 | return price * (1 - slip); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Record.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.Map; 5 | 6 | /** 7 | * 记录股票交易状态,持仓情况,以及风险分析指标 8 | */ 9 | public class Record { 10 | private Map positions; 11 | 12 | private RiskAnalysis riskAnalysis; 13 | 14 | private double todayBuy = 0; //当日买入额 15 | private double todaySell = 0; //当日卖出额 16 | private double todayEarn = 0; //当日盈利,不包括亏损数额 17 | private double todayLoss = 0; //当日亏损 18 | 19 | private double txnFees = 0; //税费总计 20 | 21 | private LocalDateTime ts; 22 | 23 | public Record(LocalDateTime ts) { 24 | this.ts = ts; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/RiskAnalysis.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | public class RiskAnalysis { 4 | public double start = 0; //起始资产 5 | public double end = 0; //期末资产 6 | 7 | public double pnl = 0; //交易盈亏 8 | public double pnlRate = 0; //收益率=交易盈亏/起始资产 9 | public double annualReturn = 0; //年化收益率= 交易盈亏 / (平均持股天数/交易总天数) 10 | 11 | public int avgPositionDays = 0; //平均每只股票持仓天数 12 | public int totalPositionDays = 0; //持仓总天数 13 | 14 | public double maxEarnPerOp = 0; //最大单笔盈利 15 | public double maxLossPerOp = 0; //最大单笔亏损 16 | public double meanEarnPerOp = 0; //平均每笔盈利 17 | 18 | public double continuousEarnOp = 0; //连续盈利次数 19 | public double continuousLossOp = 0; //连续亏损次数 20 | 21 | public int totalOperate = 0; //总交易次数 22 | public int earnOperate = 0; //总盈利次数 23 | public int lossOperate = 0; //总亏损交易次数 24 | public double accuracy = 0; //操作正确率=总盈利次数/总交易次数 25 | 26 | public double sharpe = 0; //夏普率 27 | public double sortino = 0; //所提诺比率 28 | 29 | public double benchmarkBenfit = 0; //基准收益额,同期股价涨跌额,单位:元 30 | public double benchmarkBenfitPercent = 0; //基准收益百分比 31 | 32 | public double strategyBenfit = 0; //策略收益额 33 | public double strategyBenfitPercent = 0; //策略收益率 34 | 35 | public double marketIndexPercent = 0; //大盘涨跌幅,同期大盘涨跌百分比 36 | public double max = 0; //最大资产 37 | public double min = 0; //最小资产 38 | public double maxDrawdown = 0; //最大回撤 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Slippage.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | public interface Slippage { 4 | 5 | double slip(OrderType type,double price); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/Trade.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import org.joda.time.DateTime; 4 | 5 | //订单的一次交易记录,一个订单可能分多次交易 6 | public class Trade { 7 | private DateTime time; //交易时间, datetime.datetime对象 8 | private long amount; //交易数量 9 | private double price; //交易价格 10 | private String trade_id; //交易记录id 11 | private String order_id; //对应的订单id 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/jquant/trade/TradingSystem.java: -------------------------------------------------------------------------------- 1 | package net.jquant.trade; 2 | 3 | import net.jquant.common.Context; 4 | import net.jquant.tools.StockList; 5 | 6 | public class TradingSystem { 7 | 8 | private Context context; 9 | private StockList stockList; 10 | private Commission commission = new Commission(); 11 | private Slippage slippage = new FixedSlippage(); 12 | 13 | public void setContext(Context context){ 14 | this.context = context; 15 | } 16 | 17 | public void setStockList(StockList stockList){ 18 | this.stockList = stockList; 19 | } 20 | 21 | public void setCommission(Commission commission){ 22 | this.commission = commission; 23 | } 24 | 25 | public void setSlippage(Slippage slippage){ 26 | this.slippage = slippage; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=WARN, stdout, R 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | 5 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %F(%L) - %m%n 6 | log4j.appender.R=org.apache.log4j.RollingFileAppender 7 | log4j.appender.R.File=jQuant.log 8 | log4j.appender.R.MaxFileSize=100KB 9 | # Keep one backup file 10 | log4j.appender.R.MaxBackupIndex=1 11 | log4j.appender.R.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n 13 | # Print only messages of level WARN or above in the package com.foo. 14 | log4j.logger.com.foo=WARN -------------------------------------------------------------------------------- /src/test/java/net/jquant/common/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package net.jquant.common; 2 | 3 | import org.joda.time.DateTime; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import java.util.Date; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | public class UtilsTest { 12 | @Test 13 | public void isToday() throws Exception { 14 | DateTime dt = new DateTime(); 15 | Assert.assertTrue(Utils.isToday(dt.toDate())); 16 | dt = dt.plusDays(1); 17 | Assert.assertFalse(Utils.isToday(dt.toDate())); 18 | dt = dt.plusDays(-1); 19 | Assert.assertTrue(Utils.isToday(dt.toDate())); 20 | dt = dt.plusDays(-1); 21 | Assert.assertFalse(Utils.isToday(dt.toDate())); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/test/java/net/jquant/model/StockDataTest.java: -------------------------------------------------------------------------------- 1 | package net.jquant.model; 2 | 3 | import net.jquant.common.StockDataParseException; 4 | import net.jquant.provider.Provider; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | import java.util.Map; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | public class StockDataTest { 13 | 14 | @Test 15 | public void testStockData() throws StockDataParseException { 16 | StockData stockData = Provider.realtimeData("000001"); 17 | System.out.println("股票名称:" + stockData.name); 18 | System.out.println("股票代码:" + stockData.symbol); 19 | for(Map.Entry data : stockData.entrySet()){ 20 | System.out.println(data.getKey() + "=" + data.getValue()); 21 | } 22 | System.out.println(stockData); 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /src/test/java/net/jquant/provider/FinanceDataProviderTest.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import net.jquant.model.StockData; 4 | import org.junit.Test; 5 | 6 | import java.util.List; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class FinanceDataProviderTest { 11 | @Test 12 | public void getYear() throws Exception { 13 | List data = FinanceDataProvider.getYear("002121"); 14 | for(StockData stockData:data){ 15 | System.out.println(stockData); 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/test/java/net/jquant/provider/ProviderTest.java: -------------------------------------------------------------------------------- 1 | package net.jquant.provider; 2 | 3 | import net.jquant.common.StockDataParseException; 4 | import net.jquant.model.StockData; 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | 9 | public class ProviderTest { 10 | @Test 11 | public void dailyData() throws Exception { 12 | 13 | } 14 | 15 | @Test 16 | public void dailyDataZS() throws Exception { 17 | List stockDatas = Provider.dailyDataZS("000001"); 18 | for(StockData stockData:stockDatas){ 19 | System.out.println(stockData); 20 | } 21 | } 22 | 23 | @Test 24 | public void dailyDataZS1() throws Exception { 25 | 26 | } 27 | 28 | @Test 29 | public void dailyDataZS2() throws Exception { 30 | 31 | } 32 | 33 | @Test 34 | public void realtimeData() throws Exception { 35 | 36 | } 37 | 38 | @Test 39 | public void minuteData() throws Exception { 40 | 41 | } 42 | 43 | @Test 44 | public void minuteData1() throws Exception { 45 | 46 | } 47 | 48 | @Test 49 | public void moneyFlowData() throws Exception { 50 | 51 | } 52 | 53 | @Test 54 | public void moneyFlowData1() throws Exception { 55 | 56 | } 57 | 58 | @Test 59 | public void moneyFlowDapanData() throws Exception { 60 | 61 | } 62 | 63 | @Test 64 | public void moneyFlowIndustryData() throws Exception { 65 | 66 | } 67 | 68 | @Test 69 | public void moneyFlowConceptData() throws Exception { 70 | 71 | } 72 | 73 | @Test 74 | public void moneyFlowRegionData() throws Exception { 75 | 76 | } 77 | 78 | @Test 79 | public void financeData() throws Exception { 80 | 81 | } 82 | 83 | @Test 84 | public void financeData1() throws Exception { 85 | 86 | } 87 | 88 | @Test 89 | public void financeYearData() throws Exception { 90 | 91 | } 92 | 93 | @Test 94 | public void tickData() throws Exception { 95 | 96 | } 97 | 98 | @Test 99 | public void tickData1() throws Exception { 100 | 101 | } 102 | 103 | @Test 104 | public void stockBlock() throws Exception { 105 | 106 | } 107 | 108 | @Test 109 | public void stockCategory() throws Exception { 110 | 111 | } 112 | 113 | @Test 114 | public void stockList() throws Exception { 115 | 116 | } 117 | 118 | @Test 119 | public void testRealTimeData(){ 120 | StockData stockData = null; 121 | try { 122 | stockData = Provider.realtimeData("000403"); 123 | } catch (StockDataParseException e) { 124 | e.printStackTrace(); 125 | } 126 | System.out.println(stockData); 127 | } 128 | } -------------------------------------------------------------------------------- /src/test/java/net/jquant/tools/StockListTest.java: -------------------------------------------------------------------------------- 1 | package net.jquant.tools; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.junit.Test; 5 | import net.jquant.common.ParallelProcesser; 6 | import net.jquant.model.StockData; 7 | 8 | import java.util.List; 9 | 10 | public class StockListTest { 11 | 12 | @Test 13 | public void get() throws Exception { 14 | ParallelProcesser.init(1,16); 15 | List stockList = new StockList().create().filter(Lists.newArrayList("st")).get(); 16 | for(StockData stockData:stockList){ 17 | System.out.println(stockData.name + "\t" + stockData.symbol); 18 | } 19 | } 20 | 21 | } --------------------------------------------------------------------------------