├── .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 | 
--------------------------------------------------------------------------------
/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'