├── .gitignore ├── README.md ├── commands ├── __init__.py ├── abort.py ├── clear.py ├── download.py ├── env.py ├── filter.py ├── get.py ├── help.py ├── indicator.py ├── jobs.py ├── quit.py ├── suggest.py ├── test.py ├── update.py └── viewport.py ├── images ├── console.png ├── other.png ├── question.png └── star.png ├── indicators ├── __init__.py ├── amo.py ├── boll.py ├── kd.py ├── ma.py ├── macd.py └── madiff.py ├── modules ├── __init__.py ├── chart.py ├── cmdedit.py ├── db.py ├── globals.py ├── init.py ├── mainwindow.py ├── output.py ├── scrollbar.py ├── stock.py ├── stockfilterresultdlg.py ├── stockinfodlg.py ├── stockwatchdlg.py └── utils.py ├── screenshot.png └── stockconsole.py /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | .DS_Store 3 | Thumbs.db 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | py-stockconsole 2 | --- 3 | 4 | 基于Python3.x, PyQt5写的股票分析软件 5 | 6 | 安装,设置,运行: 7 | 8 | 1. 需要[Python3.x](https://www.python.org/download)和[PyQt5](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 9 | 10 | 11 | 2. 需要安装通达信股票软件(日线数据来源)和钱龙经典版(除权数据来源) 12 | 13 | 3. 打开modules/globals.py编辑钱龙和通达信数据目录 14 | 15 | 4. 运行主程序stockconsole.py,命令窗口输入"?"显示所有命令列表,输入"viewport show sh01"显示上证指数k线图,命令窗口支持tab自动补全 16 | 17 | 18 | ![screenshot](https://github.com/yeejlan/py-stockconsole/raw/master/screenshot.png) -------------------------------------------------------------------------------- /commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeejlan/py-stockconsole/87ddbaeec811bbf244a8f92d5ae23e6991d33797/commands/__init__.py -------------------------------------------------------------------------------- /commands/abort.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import os 4 | import sys 5 | import queue 6 | import threading 7 | import time 8 | import urllib.request 9 | 10 | from PyQt5.QtCore import * 11 | 12 | from modules import db 13 | from modules import globals 14 | from modules import stock 15 | from modules import utils 16 | 17 | desc = 'abort subcommand 终止数据下载' 18 | 19 | subCommands ={ 20 | 'stockhisdata' : 'stockhisdata 终止股票历史数据的下载', 21 | } 22 | 23 | def run(subcmd, params): 24 | if(subcmd == 'stockhisdata'): 25 | while(True): 26 | if(cancelDownload('download_stockhisdata')): 27 | break 28 | 29 | time.sleep(0.01) 30 | QCoreApplication.processEvents() 31 | 32 | return 33 | 34 | else: 35 | utils.output('未知命令', 'red') 36 | return 37 | 38 | 39 | #cancel download 40 | def cancelDownload(threadName): 41 | 42 | def stopThread(threadName): 43 | threadList = [] 44 | try: 45 | threadList = globals.data[threadName+'_threadlist'] 46 | globals.data[threadName+'_threadlist'] = None 47 | globals.data[threadName+'_running'] = False 48 | except: 49 | pass 50 | 51 | if(threadList): 52 | for t in threadList: 53 | t.stop() 54 | 55 | 56 | threadLeft = 0 57 | stopThread(threadName) 58 | for t in threading.enumerate(): 59 | if(t.getName() == threadName): 60 | threadLeft +=1 61 | 62 | if(threadLeft > 0): 63 | tipstr = '终止下载中, 目前剩余' 64 | outstr = '{} {} 线程'.format(tipstr, threadLeft) 65 | utils.overwrite(outstr, tipstr) 66 | return False 67 | else: 68 | utils.output('所有下载线程已终止') 69 | return True -------------------------------------------------------------------------------- /commands/clear.py: -------------------------------------------------------------------------------- 1 | 2 | from modules import globals 3 | 4 | desc = 'clear 清屏' 5 | 6 | subCommands = {} 7 | 8 | def run(subcmd, params): 9 | globals.mainwin.output.clear() -------------------------------------------------------------------------------- /commands/download.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import os 4 | import sys 5 | import queue 6 | import threading 7 | import time 8 | import urllib.request 9 | 10 | from PyQt5.QtCore import * 11 | 12 | from modules import db 13 | from modules import globals 14 | from modules import stock 15 | from modules import utils 16 | 17 | desc = 'download subcommand 下载数据' 18 | 19 | subCommands ={ 20 | 'stockhisdata' : 'stockhisdata 下载股票的历史数据', 21 | } 22 | 23 | def run(subcmd, params): 24 | if(subcmd == 'stockhisdata'): 25 | return downloadStockHisData() 26 | 27 | 28 | else: 29 | utils.output('未知命令', 'red') 30 | return 31 | 32 | 33 | def downloadStockHisData(): 34 | 35 | threadName = 'download_stockhisdata' 36 | globals.data[threadName+'_threadlist'] = None 37 | globals.data[threadName+'_running'] = True 38 | 39 | threadCount = 10 40 | start_time = datetime.datetime.now() 41 | 42 | stockStartUrl = 'http://money.finance.sina.com.cn/corp/go.php/vMS_FuQuanMarketHistory/stockid/{}.phtml' 43 | indexStartUrl = 'http://vip.stock.finance.sina.com.cn/corp/go.php/vMS_MarketHistory/stockid/{}/type/S.phtml' 44 | baseStockUrl = 'http://money.finance.sina.com.cn/corp/go.php/vMS_FuQuanMarketHistory/stockid/{code}.phtml?year={year}&jidu={jidu}' 45 | baseIndexUrl = 'http://vip.stock.finance.sina.com.cn/corp/go.php/vMS_MarketHistory/stockid/{code}/type/S.phtml?year={year}&jidu={jidu}' 46 | 47 | stocklist = stock.getStockList() 48 | if((not stocklist) or len(stocklist)<10): 49 | utils.output('获取股票列表失败', 'red') 50 | return False 51 | 52 | utils.output('获取股票列表成功, 数目: {}'.format(len(stocklist))) 53 | 54 | #add index 上证50 and 上证综指 55 | stocklist['sh000001'] = '上证综指' 56 | stocklist['sh000016'] = '上证50' 57 | 58 | #build fetch list 59 | in_queue = queue.Queue() 60 | 61 | stockcodes = list(stocklist.keys()) 62 | stockcodes.sort() 63 | for scode in stockcodes: 64 | stype = 'stock' 65 | startUrl = stockStartUrl.format(scode[2:]) 66 | if(scode[:3] == 'sh0'): #大盘指数 67 | startUrl = indexStartUrl.format(scode[2:]) 68 | stype = 'index' 69 | 70 | #check diskcache before put to download queue 71 | in_queue.put(startUrl) 72 | 73 | def downloadData(in_queue): 74 | out_queue = queue.Queue() 75 | page = 1 76 | totalpage = in_queue.qsize() 77 | #start multi-thread fetching 78 | threadList = utils.getWebContentMT(in_queue, out_queue, threadCount, threadName) 79 | globals.data[threadName+'_threadlist'] = threadList 80 | 81 | #page downloading 82 | downloadErrorCnt = 0 83 | while(globals.data[threadName+'_running'] and (page <= totalpage)): 84 | try: 85 | err, u, content = out_queue.get(False) 86 | if(err): 87 | utils.output(err, 'red') 88 | downloadErrorCnt +=1 89 | continue 90 | 91 | end_time = datetime.datetime.now() 92 | diffStr = '{}'.format(end_time - start_time) 93 | tipstr = '{} 下载'.format(threadName) 94 | outstr = '{}({}线程) 第{}/{}页 ({})'.format(tipstr, threadCount, page, totalpage, diffStr) 95 | utils.overwrite(outstr, tipstr) 96 | 97 | page = page +1 98 | 99 | except queue.Empty: 100 | time.sleep(0.01) 101 | QCoreApplication.processEvents() 102 | 103 | return downloadErrorCnt 104 | 105 | downloadErrorCnt = downloadData(in_queue) 106 | 107 | if(not globals.data[threadName+'_running']): 108 | utils.output('下载已被终止', 'red') 109 | return 110 | if(downloadErrorCnt != 0): 111 | utils.output('下载出错的页面数: {}, 请重新运行命令下载出错的页面'.format(downloadErrorCnt)) 112 | return 113 | 114 | utils.output('下载完成') 115 | 116 | 117 | 118 | def checkDiskCacheForHisData(stockcode, year=None, jidu=None): 119 | if(year ==None): 120 | year, jidu = getYearAndJidu(datetime.date.today().isoformat()) 121 | 122 | filename = 'stockhis_{}_{}.html'.format(year, jidu) 123 | filepath = globals.cachepath + '/' + filename 124 | print(filepath) 125 | 126 | 127 | 128 | def checkStockHisDiskData(scode, year=None, jidu=None): 129 | if(not Year): 130 | year, jidu = getYearAndJidu(datetime.date.today().isoformat()) 131 | 132 | 133 | def getYearAndJidu(dateStr): 134 | dArr = dateStr.split('-') 135 | year = dArr[0] 136 | jidu = '1' 137 | if(int(dArr[1])>3): 138 | jidu = '2' 139 | elif(int(dArr[1])>6): 140 | jidu = '3' 141 | elif(int(dArr[1])>9): 142 | jidu = '4' 143 | return (year, jidu) 144 | -------------------------------------------------------------------------------- /commands/env.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | 4 | from modules import globals 5 | from modules import stock 6 | from modules import utils 7 | 8 | desc = 'env subcommand 列出/设置全局变量' 9 | 10 | subCommands = { 11 | 'list' : 'list 列出所有变量', 12 | 'realtime' : 'realtime [on]|off 实时/盘后 状态切换', 13 | } 14 | 15 | 16 | 17 | def run(subcmd, params): 18 | 19 | if(subcmd == 'list'): 20 | setting = {} 21 | for k in subCommands.keys(): 22 | if(k != 'list'): 23 | setting[k] = globals.__dict__.get(k) 24 | 25 | utils.output(setting) 26 | 27 | elif(subcmd == 'realtime'): 28 | if(len(params) < 1): 29 | msg = 'realtime on' 30 | globals.realtime = True 31 | utils.output(msg) 32 | elif(len(params) == 1 and (params[0]=='on' or params[0]=='off')): 33 | if(params[0]=='on'): 34 | msg = 'realtime on' 35 | globals.realtime = True 36 | else: 37 | msg = 'realtime off' 38 | globals.realtime = False 39 | utils.output(msg) 40 | else: 41 | utils.output('参数错误', 'red') 42 | utils.output(subCommands['realtime']) 43 | 44 | 45 | if(not globals.realtime): 46 | chart = globals.mainwin.chart 47 | hqinfo = chart.rt_data.get('data') 48 | if(hqinfo and chart.rt_data.get('status') == 'realtime'): 49 | chart.rt_data['status'] = '' 50 | if(chart.kdata[-1]['date'] == hqinfo['date']): 51 | chart.kdata = chart.kdata[:-1] 52 | 53 | globals.mainwin.scrollbar.setMaximum(len(chart.kdata)) 54 | utils.update() 55 | -------------------------------------------------------------------------------- /commands/filter.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import os 4 | import sys 5 | import queue 6 | import threading 7 | import time 8 | import struct 9 | import urllib.request 10 | import pickle 11 | 12 | from PyQt5.QtCore import * 13 | 14 | import indicators 15 | from modules import db 16 | from modules import globals 17 | from modules import stock 18 | from modules import utils 19 | from modules import stockfilterresultdlg 20 | 21 | desc = 'filter subcommand 过滤股票' 22 | 23 | subCommands ={ 24 | 'di' : 'di 使用di条件', 25 | } 26 | 27 | def run(subcmd, params): 28 | chart = globals.mainwin.chart 29 | stocklist = stock.getStockList() 30 | 31 | if(subcmd == 'di'): 32 | filterResults = [] 33 | di = indicators.di.Di() 34 | 35 | utils.output('开始di条件过滤') 36 | prefixstr = 'filter di条件过滤:' 37 | totalstock = len(stocklist) 38 | cnt = 0 39 | start_time = datetime.datetime.now() 40 | for stockcode in stocklist: 41 | cnt +=1 42 | stockcode = stock.normalizeStockCode(stockcode) 43 | end_time = datetime.datetime.now() 44 | diffStr = '{}'.format(end_time - start_time) 45 | outstr = '{} {}/{} 当前代码:{} 符合条件:{} ({})'.format(prefixstr, cnt, totalstock, stockcode, len(filterResults), diffStr) 46 | utils.overwrite(outstr, prefixstr) 47 | stockdata = stock.getStockDayDataQianFuQuan(stockcode) 48 | stockdata = stockdata[-500:] 49 | didata = di.calculateData(stockdata) 50 | QCoreApplication.processEvents() 51 | if(didata[-1] and didata[-1].get('tip')): 52 | filterResults.append({'code':stockcode, 'date':didata[-1]['date']}) 53 | 54 | 55 | dataArr = [] 56 | 57 | for row in filterResults: 58 | scode = row['code'] 59 | sdate = row['date'] 60 | row=[] 61 | row.append(scode) 62 | row.append(stock.getStockName(scode)) 63 | row.append(sdate) 64 | dataArr.append(row) 65 | 66 | #write to file begin 67 | filename = globals.datapath + '/lastFilterResult.dat' 68 | try: 69 | f = open(filename, 'wb') 70 | except IOError: 71 | exc_type, exc_value = sys.exc_info()[:2] 72 | errmsg = '{}: {}'.format(exc_type.__name__, exc_value) 73 | output(errmsg, 'red') 74 | return False 75 | 76 | pickle.dump(dataArr, f) 77 | f.close() 78 | #write to file end 79 | 80 | tablemodel = stockfilterresultdlg.FilterResultModel(dataArr, None) 81 | tbResults = globals.mainwin.stockfilterresultdlg.tbResults 82 | tbResults.setModel(tablemodel) 83 | globals.mainwin.windowStockFilterResult() -------------------------------------------------------------------------------- /commands/get.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime 3 | import queue 4 | import random 5 | import re 6 | import threading 7 | import time 8 | 9 | 10 | from modules import stock 11 | from modules import utils 12 | 13 | subCommands = { 14 | 'hq' : 'hq stock_code 取得当前的股票行情', 15 | 'shanghai_a_count' : 'shanghai_a_count 得到沪A的股票总数' 16 | } 17 | 18 | 19 | desc = 'get subcommand 获取数据' 20 | 21 | def run(subcmd, params): 22 | 23 | if(subcmd == 'shanghai_a_count'): 24 | return _getShanghaiACount() 25 | 26 | 27 | if(subcmd == 'hq'): 28 | if(len(params) != 1): 29 | utils.output('请给出股票代码', 'red') 30 | utils.output(subCommands['hq']) 31 | return 32 | 33 | stock_code = stock.normalizeStockCode(params[0]) 34 | if(not stock_code): 35 | utils.output('股票代码错误', 'red') 36 | utils.output(subCommands['hq']) 37 | return 38 | 39 | return _getHq(stock_code) 40 | 41 | 42 | 43 | def _getShanghaiACount(): 44 | 45 | url = 'http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeStockCount?node=sh_a' 46 | 47 | err, u, content = utils.getWebContent(url) 48 | 49 | if(err): 50 | utils.output(err, 'red') 51 | return False 52 | 53 | content = content.decode('gbk') 54 | cnt = re.findall(r'"([0-9]+)"', content) 55 | utils.output(cnt) 56 | 57 | 58 | def _getHq(stock_code): 59 | info = stock.getHq(stock_code) 60 | if(info): 61 | str = '{} {} {} {:-.2f}%'.format(info['code'], info['name'], info['price'], info['price_chg']) 62 | color = '' 63 | if info['price_chg'] >0 : 64 | color = 'red' 65 | elif info['price_chg'] < 0 : 66 | color = 'green' 67 | 68 | utils.output(str, color) -------------------------------------------------------------------------------- /commands/help.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from modules import utils 5 | 6 | desc = '[command] [subcommand] help|? 显示帮助信息' 7 | 8 | subCommands = {} 9 | 10 | 11 | def run(subcmd, params): 12 | 13 | cmdList = utils.getCmdList() 14 | utils.output(desc) 15 | utils.output(cmdList) 16 | -------------------------------------------------------------------------------- /commands/indicator.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import os 4 | import sys 5 | import queue 6 | import threading 7 | import time 8 | import struct 9 | import urllib.request 10 | 11 | import indicators 12 | from modules import db 13 | from modules import globals 14 | from modules import stock 15 | from modules import utils 16 | 17 | desc = 'indicator subcommand 更换指标' 18 | 19 | subCommands ={ 20 | 'clear' : 'clear 清除主图指标', 21 | 'ma' : 'ma 显示MA(60, 120)指标', 22 | 'ma250' : 'ma250 显示MA(120, 250)指标', 23 | 'ma60' : 'ma60 显示MA(20, 60)指标', 24 | 'ma20' : 'ma20 显示MA(10, 20)指标', 25 | 'ma10' : 'ma10 显示MA(3, 10)指标', 26 | 'boll' : 'boll 显示boll指标', 27 | 'di' : 'di 显示di指标', 28 | 'macd' : 'macd 显示macd指标', 29 | 'kd' : 'kd 显示kd指标', 30 | 'madiff' : 'madiff 显示madiff指标', 31 | 'amo' : 'amo 显示amo指标', 32 | } 33 | 34 | def run(subcmd, params): 35 | chart = globals.mainwin.chart 36 | if(subcmd == 'clear'): 37 | chart.changeMainIndicator(None) 38 | 39 | elif(subcmd == 'ma'): 40 | chart.changeMainIndicator(indicators.ma.Ma([60, 120])) 41 | 42 | elif(subcmd == 'ma250'): 43 | chart.changeMainIndicator(indicators.ma.Ma([120, 250])) 44 | 45 | elif(subcmd == 'ma60'): 46 | chart.changeMainIndicator(indicators.ma.Ma([20, 60])) 47 | 48 | elif(subcmd == 'ma20'): 49 | chart.changeMainIndicator(indicators.ma.Ma([10, 20])) 50 | 51 | elif(subcmd == 'ma10'): 52 | chart.changeMainIndicator(indicators.ma.Ma([3, 10])) 53 | 54 | elif(subcmd == 'boll'): 55 | chart.changeMainIndicator(indicators.boll.Boll(20, 2)) 56 | 57 | elif(subcmd == 'di'): 58 | chart.changeMainIndicator(indicators.di.Di()) 59 | 60 | elif(subcmd == 'macd'): 61 | chart.changeIndicator(indicators.macd.Macd()) 62 | 63 | elif(subcmd == 'kd'): 64 | chart.changeIndicator(indicators.kd.Kd()) 65 | 66 | elif(subcmd == 'madiff'): 67 | chart.changeIndicator(indicators.madiff.MaDiff()) 68 | 69 | elif(subcmd == 'amo'): 70 | chart.changeIndicator(indicators.amo.Amo()) -------------------------------------------------------------------------------- /commands/jobs.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import threading 4 | 5 | from modules import utils 6 | 7 | desc = 'jobs 列出当前所有活动线程' 8 | 9 | subCommands = {} 10 | 11 | def run(subcmd, params): 12 | utils.output(threading.enumerate()) -------------------------------------------------------------------------------- /commands/quit.py: -------------------------------------------------------------------------------- 1 | 2 | from modules import globals 3 | 4 | desc = 'quit 退出程序' 5 | 6 | subCommands = {} 7 | 8 | def run(subcmd, params): 9 | globals.mainwin.quit() -------------------------------------------------------------------------------- /commands/suggest.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | import sys 4 | import urllib.parse 5 | 6 | from modules import utils 7 | 8 | desc = 'suggest subcommand 给出建议' 9 | 10 | subCommands ={ 11 | 'code' : 'code stock_name 根据拼音给出股票代码' 12 | } 13 | 14 | 15 | def run(subcmd, params): 16 | if(subcmd == 'code'): 17 | if(len(params) != 1): 18 | utils.output('参数错误', 'red') 19 | utils.output(subCommands['code']) 20 | return 21 | 22 | return _suggestCode(params[0]) 23 | 24 | 25 | 26 | 27 | def _suggestCode(text): 28 | escapeTxt = urllib.parse.quote(text) 29 | url = 'http://suggest3.sinajs.cn/suggest/type=&key={}'.format(escapeTxt) 30 | err, u, content = utils.getWebContent(url) 31 | if(err): 32 | utils.output(err, 'red') 33 | return 'eee' 34 | 35 | content = content.decode('gbk') 36 | codeArr = [] 37 | resultArr = re.findall(r'"(.*)"', content)[0].split(';') 38 | for line in resultArr: 39 | cArr = line.split(',') 40 | if(len(cArr) == 6 and cArr[1]=='11'): #cArr[1]==11 A股 41 | codestr = '{} {}'.format(cArr[3], cArr[4]) 42 | codeArr.append(codestr) 43 | 44 | if(len(codeArr)<1): 45 | utils.output('nothing') 46 | else: 47 | utils.output(codeArr) 48 | return 49 | 50 | -------------------------------------------------------------------------------- /commands/test.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import os 4 | import sys 5 | import queue 6 | import threading 7 | import time 8 | import struct 9 | import urllib.request 10 | 11 | import indicators 12 | from modules import db 13 | from modules import globals 14 | from modules import stock 15 | from modules import utils 16 | 17 | desc = 'test 临时测试命令' 18 | 19 | subCommands = {} 20 | 21 | 22 | def run(subcmd, params): 23 | 24 | stockcode = 'sh600603' 25 | wd = stock.getStockWeightData(stockcode) 26 | print(wd) 27 | #stockdata = stock.getStockDayDataQianFuQuan(stockcode) 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /commands/update.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import queue 4 | import re 5 | import threading 6 | import time 7 | 8 | from PyQt5.QtCore import * 9 | 10 | from modules import db 11 | from modules import globals 12 | from modules import stock 13 | from modules import utils 14 | 15 | desc = 'update subcommand 更新数据状态' 16 | 17 | subCommands = { 18 | 'stockinfo' : 'stocklist 更新股票基本信息', 19 | 'stocklist' : 'stocklist 更新股票列表数据', 20 | 'stockdata' : 'stockdata [full] 更新股票日线数据' 21 | } 22 | 23 | 24 | def run(subcmd, params): 25 | 26 | if(subcmd == 'stocklist'): 27 | return _updateStockList() 28 | elif(subcmd == 'stockdata'): 29 | return _updateStockData() 30 | 31 | def _updateStockList(): 32 | 33 | threadName = 'update_stocklist' 34 | threadCount = 10 35 | start_time = datetime.datetime.now() 36 | 37 | #terminal all thread from threadList 38 | def stopThread(threadList): 39 | for t in threadList: 40 | t.stop() 41 | 42 | partUrl = 'http://static.sse.com.cn/sseportal/webapp/datapresent/SSEQueryStockInfoAct?reportName=BizCompStockInfoRpt&PRODUCTID=&PRODUCTJP=&PRODUCTNAME=&keyword=&tab_flg=&CURSOR=' 43 | page = 1 44 | 45 | url = '{}{}'.format(partUrl, ((page-1)*50+1)) 46 | err, u, content = utils.getWebContent(url) 47 | if(err): 48 | utils.output(err, 'red') 49 | return False 50 | 51 | #get basic info 52 | content = content.decode('gbk') 53 | totalpagelist = re.findall(r'共([0-9]+)页', content) 54 | if(len(totalpagelist)<1): 55 | utils.output('取得总页数(totalpage)出错', 'red') 56 | return False 57 | 58 | totalstocklist = re.findall(r'第1条到第50条,共([0-9]+)条', content) 59 | if(len(totalstocklist)<1): 60 | err = '取得总股票数(totalstock)出错' 61 | utils.output(err, 'red') 62 | return False 63 | 64 | totalpage = int(totalpagelist[0]) 65 | totalstock = int(totalstocklist[0]) 66 | utils.output('共{}页, {}只股票'.format(totalpage, totalstock)) 67 | 68 | #get real stock list data 69 | in_queue = queue.Queue() 70 | out_queue = queue.Queue() 71 | for i in range(1, totalpage+1): 72 | url = '{}{}'.format(partUrl, ((i-1)*50+1)) 73 | in_queue.put(url) 74 | 75 | #start multi-thread fetching 76 | threadList = utils.getWebContentMT(in_queue, out_queue, threadCount, threadName) 77 | 78 | pattern = '(.*?)([0-9]+)(.*?)(.*?)(.*?)' 79 | myre = re.compile(pattern, re.DOTALL) 80 | 81 | stocklist = [] 82 | tipstr = 'update stocklist 下载' 83 | #page downloading 84 | while(page <= totalpage): 85 | try: 86 | err, u, content = out_queue.get(False) 87 | if(err): 88 | utils.output(err, 'red') 89 | stopThread(threadList) 90 | return False 91 | 92 | outstr = '{}({}线程) 第{}/{}页'.format(tipstr, threadCount, page, totalpage) 93 | if(utils.getLastCmdeditLine()[0:len(tipstr)] != tipstr): 94 | utils.output(outstr) 95 | else: 96 | utils.overwrite(outstr) 97 | 98 | page = page +1 99 | 100 | text = content.decode('gbk') 101 | #find stock list 102 | results = myre.findall(text) 103 | if(not results): 104 | err = '无法提取到股票列表数据' 105 | utils.output(err, 'red') 106 | stopThread(threadList) 107 | return False 108 | 109 | for r in results: 110 | lst = list(r) 111 | line = '{},{}'.format(lst[3], lst[6]) 112 | stocklist.append(line) 113 | 114 | except queue.Empty: 115 | time.sleep(0.01) 116 | QCoreApplication.processEvents() 117 | 118 | #download finish, stock list ready 119 | if(len(stocklist) != totalstock): 120 | err = '取得的股票列表数目(stocklist)出错 {}!={}'.format(len(stocklist), totalstock) 121 | utils.output(err, 'red') 122 | return False 123 | 124 | stocklist.sort() 125 | utils.output('获取股票列表完成: {}只'.format(len(stocklist))) 126 | 127 | #save data 128 | filename = globals.datapath + '/stocklist.txt' 129 | data = "\r\n".join(stocklist) 130 | if(not utils.file_put_contents(filename, data)): 131 | utils.output('数据保存失败', 'red') 132 | return False 133 | 134 | utils.output('数据保存成功') 135 | 136 | end_time = datetime.datetime.now() 137 | return '{}'.format(end_time - start_time) 138 | 139 | 140 | 141 | 142 | def _updateStockData(): 143 | 144 | threadName = 'update_stockdata' 145 | threadCount = 10 146 | start_time = datetime.datetime.now() 147 | 148 | #terminal all thread from threadList 149 | def stopThread(threadList): 150 | for t in threadList: 151 | t.stop() 152 | 153 | stocklist = stock.getStockList() 154 | if((not stocklist) or len(stocklist)<10): 155 | utils.output('获取股票列表失败', 'red') 156 | return False 157 | 158 | utils.output('获取股票列表成功, 数目: {}'.format(len(stocklist))) 159 | 160 | stockStartUrl = 'http://money.finance.sina.com.cn/corp/go.php/vMS_FuQuanMarketHistory/stockid/{}.phtml' 161 | indexStartUrl = 'http://vip.stock.finance.sina.com.cn/corp/go.php/vMS_MarketHistory/stockid/{}/type/S.phtml' 162 | baseStockUrl = 'http://money.finance.sina.com.cn/corp/go.php/vMS_FuQuanMarketHistory/stockid/{code}.phtml?year={year}&jidu={jidu}' 163 | baseIndexUrl = 'http://vip.stock.finance.sina.com.cn/corp/go.php/vMS_MarketHistory/stockid/{code}/type/S.phtml?year={year}&jidu={jidu}' 164 | 165 | #add index 上证50 and 上证综指 166 | stocklist['sh000001'] = '上证综指' 167 | stocklist['sh000016'] = '上证50' 168 | 169 | #re for match stock history data 170 | reStockHisMatch = re.compile("(.+?)", re.S) 171 | 172 | skeys = list(stocklist.keys()) 173 | skeys.sort() 174 | scnt = 1 175 | stotal = len(skeys) 176 | for scode in skeys: 177 | sname = stocklist[scode] 178 | stype = 'stock' 179 | startUrl = stockStartUrl.format(scode[2:]) 180 | if(scode[:3] == 'sh0'): #大盘指数 181 | startUrl = indexStartUrl.format(scode[2:]) 182 | stype = 'index' 183 | 184 | err, u, content = utils.getWebContent(startUrl) 185 | if(err): 186 | utils.output(err, 'red') 187 | return False 188 | 189 | content = content.decode('gbk') 190 | utils.output('开始更新数据: {}/{} {} {}'.format(scnt, stotal, scode, sname)) 191 | scnt +=1 192 | stocksection = re.findall(r'(.+?)', content, re.S) 193 | if(len(stocksection) != 1): 194 | utils.output('截取股票历史数据失败 {}'.format(u), 'red') 195 | return False 196 | 197 | stockyearsecton = re.findall(r'', stocksection[0], re.S) 198 | if(len(stockyearsecton) != 1): 199 | utils.output('截取股票历史数据:年份数据失败 {}'.format(u), 'red') 200 | return False 201 | 202 | yearsdata = re.findall(r'