├── README.md ├── analysis ├── __init__.py ├── annual_report.py ├── cluster_data_service.py ├── cluster_data_service.py.bak ├── config.py ├── downloadindex.py ├── financial_data_service.py ├── get_value_4_business.py ├── getvaluation.py ├── history_data_service.py ├── industry_cluster.py ├── instruments.py ├── long_short_predict.py ├── models.py ├── price_change_analysis.py ├── quotation.py ├── quotation.py.old ├── realtime_quot_updater.py ├── roe_selection.py ├── rsi_analysis.py ├── schedule_job.py ├── schedule_manager.py ├── start_job.sh ├── start_schedule.sh ├── strategy_test.py ├── technical_analysis.sh ├── technical_analysis_service.py ├── test.py └── text.py ├── app ├── __init__.py ├── analyzer.py ├── annual_report │ ├── __init__.py │ ├── __init__.pyc │ ├── views.py │ └── views.pyc ├── business │ ├── __init__.py │ └── views.py ├── cluster_analysis │ ├── __init__.py │ └── views.py ├── config.py ├── config │ └── production.cfg ├── decorators.py ├── industry_analysis │ ├── __init__.py │ └── views.py ├── main │ ├── __init__.py │ └── views.py ├── models.py ├── myemail.py ├── redis_op.py ├── self_selected_stock │ ├── __init__.py │ └── views.py ├── sendmail.py ├── static │ ├── css │ │ └── industry.css │ ├── images │ │ └── default.jpg │ └── js │ │ ├── bar_chart.js │ │ ├── basic_table.js │ │ ├── bootstrap-typeahead.js │ │ ├── candlestick_chart.js │ │ ├── echarts.min.js │ │ ├── echarts.simple.min.js │ │ ├── my_app_code.js │ │ └── self_selected_stock_table.js ├── stock │ ├── __init__.py │ └── views.py ├── strategy_analysis │ ├── __init__.py │ └── views.py ├── technical_analysis │ ├── __init__.py │ └── views.py ├── templates │ ├── _base.html │ ├── annual_report │ │ └── annual_report.html │ ├── base.html │ ├── business │ │ ├── business.html │ │ ├── business.html.bak │ │ └── business2.html │ ├── cluster_analysis │ │ ├── cluster.html │ │ └── industry_cluster.html │ ├── common │ │ ├── candlestick.html │ │ └── candlestick.html.bak │ ├── errors │ │ ├── 400.html │ │ ├── 403.html │ │ ├── 404.html │ │ └── 500.html │ ├── filter.html │ ├── industry_analysis │ │ └── industry.html │ ├── main │ │ ├── index.html │ │ ├── result.html │ │ └── welcome.html │ ├── navigation.html │ ├── self_selected_stock │ │ └── self_selected_stock.html │ ├── stock │ │ └── rank.html │ ├── strategy_analysis │ │ └── rank.html │ ├── technical_analysis │ │ ├── filter.html │ │ └── rank.html │ └── user │ │ ├── activate.html │ │ ├── forgot.html │ │ ├── forgot_new.html │ │ ├── login.html │ │ ├── profile.html │ │ ├── register.html │ │ ├── reset.html │ │ └── unconfirmed.html ├── token.py ├── user │ ├── __init__.py │ ├── forms.py │ └── views.py └── util.py ├── bin ├── analysis_report.py ├── belowmastrategy.py ├── commondatadef.py ├── datamonitor.py ├── download15khistdata.py ├── downloaddata.py ├── downloaddata4lstm.py ├── downloaddaydata.py ├── downloadindex.py ├── downloadindex4lstm.py ├── downloadmindata.py ├── fileconverter.py ├── generate_business_report.py ├── get_growth_valuation.py ├── getfinancialreport.py ├── getreportdata.py ├── mafilter.py ├── makedataset.py ├── predict_stock_price.py ├── receivemsg.py ├── returntop10.py ├── stock_predict.py ├── stock_predict_2.py ├── stock_utility.py ├── timertask.py ├── ts_downloaddaydata.py └── upmastrategy.py ├── clean.sh ├── create.sh ├── manage.py ├── requirements.txt ├── start.sh ├── start_dev.sh ├── start_uwsgi.sh ├── utils ├── __init__.py ├── __init__.pyc ├── emailer.py ├── mail_informer.py ├── sms_informer.py ├── util.py └── util.pyc └── uwsgiconfig.ini /README.md: -------------------------------------------------------------------------------- 1 | 本仓库包括一个基于python和flask框架开发的股票分析平台,该平台利用tushare库获取股市的实时行情、历史行情以及个股相关财务数据,结合scrapy爬取雪球的个股年报财务数据,通过pandas库对数据进行统计分析,得到个股的以下信息: 2 | 1. 基本面信息,如市盈率、市净率、净资产收益率和现金收益率排名; 3 | 2. 技术面信息,筛选历史新高、新低,均线多头、突破20日均线以及年线之上的个股; 4 | 3. 板块分类,通过scikit learn的聚类算法对上证50、中证500和沪深300进行聚类分析; 5 | 4. 行业分析,通过以上相同的聚类算法对行业个股进行基本面和技术面的聚类分析; 6 | 7 | 另外还包含个股查询,如查看个股的基本面数据和K线数据。 8 | 访问网站: www.twelvewin.com 9 | 10 | -------------------------------------------------------------------------------- /analysis/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | -------------------------------------------------------------------------------- /analysis/config.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import os 4 | import configparser 5 | 6 | config_parser = configparser.ConfigParser() 7 | 8 | env = os.getenv('TW_ANALYSIS_ENV', 'debug') 9 | config_file_path = os.getenv('TW_ANALYSIS_CONFIG_FILE', 'config.ini') 10 | 11 | print('env: {}, config file: {}'.format(env, config_file_path)) 12 | 13 | config_parser.read(config_file_path, encoding='UTF-8') 14 | 15 | config = dict() 16 | 17 | config['DEBUG'] = config_parser.getboolean(env, 'DEBUG') 18 | config['SECRET_KEY'] = config_parser.get(env, 'SECRET_KEY') 19 | config['SECURITY_PASSWORD_SALT'] = config_parser.get(env, 'SECRET_KEY') 20 | 21 | # mail settings 22 | config['MAIL_SERVER'] = config_parser.get(env, 'MAIL_SERVER') 23 | config['MAIL_PORT'] = config_parser.getint(env, 'MAIL_PORT') 24 | config['MAIL_USE_TLS'] = config_parser.getboolean(env, 'MAIL_USE_TLS') 25 | config['MAIL_USE_SSL'] = config_parser.getboolean(env, 'MAIL_USE_SSL') 26 | 27 | # mail authentication and sender 28 | config['MAIL_USERNAME'] = config_parser.get(env, 'MAIL_USERNAME') 29 | config['MAIL_PASSWORD'] = config_parser.get(env, 'MAIL_PASSWORD') 30 | config['MAIL_DEFAULT_SENDER'] = config_parser.get(env, 'MAIL_DEFAULT_SENDER') 31 | 32 | # database URI 33 | config['SQLALCHEMY_DATABASE_URI'] = config_parser.get(env, 'SQLALCHEMY_DATABASE_URI') 34 | 35 | # stripe keys 36 | config['STRIPE_SECRET_KEY'] = config_parser.get(env, 'STRIPE_SECRET_KEY') 37 | config['STRIPE_PUBLISHABLE_KEY'] = config_parser.get(env, 'STRIPE_PUBLISHABLE_KEY') 38 | 39 | config['DAY_FILE_PATH'] = config_parser.get(env, 'DAY_FILE_PATH') 40 | config['RESULT_FILE_PATH'] = config_parser.get(env, 'RESULT_FILE_PATH') 41 | 42 | my_headers = [ 43 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", 44 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", 45 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0", 46 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14", 47 | "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)", 48 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11', 49 | 'Opera/9.25 (Windows NT 5.1; U; en)', 50 | 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', 51 | 'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)', 52 | 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12', 53 | 'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9', 54 | "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7", 55 | "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 " 56 | ] 57 | 58 | print(config) -------------------------------------------------------------------------------- /analysis/downloadindex.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 下载指数的历史数据 7 | ''' 8 | 9 | import tushare as ts 10 | import pandas as pd 11 | import numpy as np 12 | import datetime 13 | import os 14 | import sys 15 | from config import config 16 | import time 17 | 18 | reload(sys) 19 | sys.setdefaultencoding('utf8') 20 | 21 | print ts.__version__ 22 | 23 | #indexes = ['000001', '399001', '000300', '399005', '399006'] 24 | 25 | indexes = ['000905'] 26 | 27 | for index in indexes: 28 | filepath = "%s/%s.csv" % (config.INDEX_FILE_PATH, index) 29 | 30 | dfs = [] 31 | for y in range(1991,2019): 32 | start = "%s-01-01" % y 33 | end = "%s-12-31" % y 34 | 35 | df = ts.get_h_data(index, index=True, start=start, end=end) 36 | 37 | df.sort_index(inplace=True) 38 | 39 | dfs.append(df) 40 | 41 | time.sleep(300) 42 | 43 | df_total = pd.concat(dfs, axis=0) 44 | 45 | df_total.reset_index(inplace=True) 46 | 47 | df_total.to_csv(filepath, index=False) 48 | 49 | 50 | -------------------------------------------------------------------------------- /analysis/financial_data_service.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | """ 6 | 获取历史数据的服务,以增量方式对历史数据文件进行添加,每天收盘后运行一次 7 | """ 8 | 9 | from config import config 10 | import tushare as ts 11 | import numpy as np 12 | import pandas as pd 13 | from models import Report, Session 14 | import time 15 | 16 | 17 | # 财务数据服务 18 | class FinancialDataService: 19 | def __init__(self, instrument_filename, day_file_path): 20 | self.instrument_filename = instrument_filename 21 | self.day_file_path = day_file_path 22 | 23 | def _value_2_float(self, value): 24 | if np.isnan(value): 25 | return None 26 | else: 27 | return float(value) 28 | 29 | def _download_report(self, year, season): 30 | print "download report %d:%d" % (year, season) 31 | 32 | df = pd.DataFrame() 33 | 34 | for i in range(3): 35 | try: 36 | df = ts.get_report_data(year, season) 37 | break 38 | except IOError: 39 | time.sleep(600) 40 | continue 41 | 42 | if (df is not None) and (not df.empty): 43 | df.drop_duplicates(inplace=True) 44 | 45 | # df = df.where(pd.notnull(df), None) 46 | 47 | session = Session() 48 | 49 | for index, row in df.iterrows(): 50 | 51 | if session.query(Report).filter(and_(Report.code==row['code'], Report.year=year, Report.season=season) is not None: 52 | continue 53 | 54 | item = Report(row['code'], eps=self._value_2_float(row['eps']), 55 | eps_yoy=self._value_2_float(row['eps_yoy']), 56 | bvps=self._value_2_float(row['bvps']), 57 | roe=self._value_2_float(row['roe']), epcf=self._value_2_float(row['epcf']), 58 | net_profits=self._value_2_float(row['net_profits']), 59 | profits_yoy=self._value_2_float(row['profits_yoy']), 60 | report_date=row['report_date'], year=year, season=season) 61 | 62 | session.add(item) 63 | 64 | session.commit() 65 | 66 | 67 | # 获取业绩报告 68 | def run(self): 69 | 70 | #self._download_report(1999, 4) 71 | 72 | #time.sleep(120) 73 | 74 | #for year in range(2000, 2019, 1): 75 | for year in range(2018, 2019): 76 | for season in range(1, 5): 77 | self._download_report(year, season) 78 | 79 | if __name__ == '__main__': 80 | financial_data_service = FinancialDataService(instrument_filename=config.INSTRUMENT_FILENAME, 81 | day_file_path=config.DAY_FILE_PATH) 82 | financial_data_service.run() 83 | 84 | -------------------------------------------------------------------------------- /analysis/get_value_4_business.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding:utf-8 -*- 3 | __author__ = 'jerry' 4 | 5 | import pandas as pd 6 | import tushare as ts 7 | import datetime 8 | from config import config 9 | import numpy as np 10 | import time 11 | 12 | # 设置精度 13 | pd.set_option('precision', 2) 14 | 15 | # 财务年度 16 | YEAR = 2017 17 | 18 | # 财务季度 19 | SEASON = 4 20 | 21 | key = ["code"] 22 | 23 | today = datetime.date.today() 24 | 25 | # 导出html表格 26 | def export_report(dest, title): 27 | outputfile_date = "%s/%s_%s.csv" % (config.RESULT_PATH, title, today.strftime("%Y%m%d"),) 28 | 29 | dest.to_csv(outputfile_date, encoding='utf-8', index=False, float_format = '%.2f') 30 | 31 | outputfile = "%s/%s.csv" % (config.RESULT_PATH, title,) 32 | 33 | dest.to_csv(outputfile, encoding='utf-8', index=False, float_format = '%.2f') 34 | 35 | 36 | # 交换值 37 | def swap_value(x, y): 38 | return x if x!=0.00 else y 39 | 40 | 41 | # 获取股票的均线 42 | ''' 43 | def get_stock_ma(code, ma1, ma2, ma3): 44 | print "get data for " + code 45 | 46 | now = datetime.datetime.now() 47 | 48 | dt = now - datetime.timedelta(days=(max(ma1,ma2,ma3)*7+10)) 49 | 50 | df = ts.get_k_data(code=code, ktype='W', autype='qfq', start=dt.strftime('%Y-%m-%d'), end=now.strftime('%Y-%m-%d')) 51 | 52 | if (df.empty): 53 | return 0 54 | 55 | df.reset_index(inplace=True) 56 | 57 | df['ma'+str(ma1)] = df['close'].rolling(window=ma1).mean() 58 | df['ma'+str(ma2)] = df['close'].rolling(window=ma2).mean() 59 | df['ma'+str(ma3)] = df['close'].rolling(window=ma3).mean() 60 | 61 | price = [] 62 | if (df.shape[0] > 0): 63 | ma1_value = df.iat[df.shape[0]-1, df.shape[1]-3] 64 | ma2_value = df.iat[df.shape[0]-1, df.shape[1]-2] 65 | ma3_value = df.iat[df.shape[0]-1, df.shape[1]-1] 66 | 67 | price.append(ma1_value) 68 | price.append(ma1_value if ma2_value is None else ma2_value) 69 | price.append(ma1_value if ma3_value is None else ma3_value) 70 | 71 | return price 72 | ''' 73 | 74 | def get_stock_ma(code, ma1, ma2, ma3): 75 | print "get data for " + code 76 | 77 | now = datetime.datetime.now() 78 | 79 | dt = now - datetime.timedelta(days=(max(ma1,ma2,ma3)*7+10)) 80 | 81 | df = ts.get_hist_data(code=code, ktype='W', start=dt.strftime('%Y-%m-%d'), end=now.strftime('%Y-%m-%d')) 82 | 83 | if (df.empty): 84 | return 0 85 | 86 | df.sort_index(inplace=True) 87 | 88 | df.reset_index(inplace=True) 89 | 90 | df.drop(labels=['ma5', 'ma10', 'ma20'], axis=1, inplace=True) 91 | 92 | df['ma'+str(ma1)] = df['close'].rolling(window=ma1).mean() 93 | df['ma'+str(ma2)] = df['close'].rolling(window=ma2).mean() 94 | df['ma'+str(ma3)] = df['close'].rolling(window=ma3).mean() 95 | 96 | price = [] 97 | if (df.shape[0] > 0): 98 | ma1_value = df.iat[df.shape[0]-1, df.shape[1]-3] 99 | ma2_value = df.iat[df.shape[0]-1, df.shape[1]-2] 100 | ma3_value = df.iat[df.shape[0]-1, df.shape[1]-1] 101 | 102 | price.append(ma1_value) 103 | price.append(ma1_value if ma2_value is None else ma2_value) 104 | price.append(ma1_value if ma3_value is None else ma3_value) 105 | 106 | return price 107 | 108 | 109 | 110 | def get_profit_report(): 111 | df_quots = pd.DataFrame() 112 | 113 | for i in range(0,3): 114 | try: 115 | #获取最新股价 116 | df_quots = ts.get_today_all() 117 | 118 | if df_quots is not None: 119 | break 120 | except: 121 | time.sleep(10*60) # 等待十分钟重试 122 | continue 123 | 124 | df_quots['roe'] = df_quots['pb']*100/df_quots['per'] 125 | df_quots['close'] = map(swap_value, df_quots['trade'], df_quots['settlement']) 126 | 127 | df_quots = df_quots[(df_quots['roe']>=5) & (df_quots['turnoverratio']>=2) & (df_quots['nmc']>=300000) & (df_quots['nmc']<=3000000) & (df_quots['close']<=50)] 128 | 129 | df_quots.reset_index(inplace=True) 130 | df_quots = df_quots.drop(['index','changepercent','trade','open','high','low','settlement','volume','amount','mktcap'], axis=1) 131 | 132 | temp1 = np.zeros(df_quots.shape[0]) 133 | temp2 = np.zeros(df_quots.shape[0]) 134 | temp3 = np.zeros(df_quots.shape[0]) 135 | 136 | ma10 = 10 137 | ma30 = 30 138 | ma60 = 60 139 | 140 | index = 0 141 | for code in df_quots['code']: 142 | price = get_stock_ma(code, ma1=ma10, ma2=ma30, ma3=ma60) 143 | 144 | temp1[index] = price[0] 145 | temp2[index] = price[1] 146 | temp3[index] = price[2] 147 | index += 1 148 | 149 | df_quots.insert(df_quots.shape[1], 'wma'+str(ma10), temp1) 150 | 151 | df_quots.insert(df_quots.shape[1], 'wma'+str(ma30), temp2) 152 | 153 | df_quots.insert(df_quots.shape[1], 'wma'+str(ma60), temp3) 154 | 155 | df_quots = df_quots.dropna(how='any') 156 | 157 | df_quots = df_quots[(df_quots['close']>df_quots['wma10']) & (df_quots['close']df_quots['wma30']) & (df_quots['close']>df_quots['wma60']) & (df_quots['wma10']>=df_quots['wma30'])] 160 | 161 | # 按现金股息率排行 162 | export_report(df_quots, title="stock_business") 163 | 164 | 165 | if __name__=='__main__': 166 | get_profit_report() 167 | -------------------------------------------------------------------------------- /analysis/getvaluation.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding:utf-8 -*- 3 | __author__ = 'jerry' 4 | 5 | import pandas as pd 6 | import tushare as ts 7 | import datetime 8 | from config import config 9 | 10 | # 设置精度 11 | pd.set_option('precision', 2) 12 | 13 | # 财务年度 14 | YEAR = 2017 15 | 16 | # 财务季度 17 | SEASON = 4 18 | 19 | key = ["code"] 20 | 21 | today = datetime.date.today() 22 | 23 | # 导出html表格 24 | def export_report(dest, title): 25 | outputfile_date = "%s/%s_%s.csv" % (config.RESULT_PATH, title, today.strftime("%Y%m%d"),) 26 | 27 | dest.to_csv(outputfile_date, encoding='utf-8', index=False, float_format = '%.2f') 28 | 29 | outputfile = "%s/%s.csv" % (config.RESULT_PATH, title,) 30 | 31 | dest.to_csv(outputfile, encoding='utf-8', index=False, float_format = '%.2f') 32 | 33 | 34 | # 交换值 35 | def swap_value(x, y): 36 | return x if x!=0.00 else y 37 | 38 | 39 | def get_valuation_report(): 40 | # 获取股票列表 41 | df_basic = ts.get_stock_basics() 42 | 43 | df_basic['roe'] = df_basic['esp']*100/df_basic['bvps'] 44 | 45 | df_basic['peg'] = df_basic['pe']/df_basic['profit'] 46 | #df_basic['value_increase'] = df_basic['esp']*(8.5+2*df_basic['profit']/100) 47 | 48 | df_basic.reset_index(inplace=True) 49 | df_basic = df_basic.drop(['area', 'outstanding', 'totalAssets', 'liquidAssets', 'fixedAssets', 'reserved', 'reservedPerShare', 'undp', 'perundp', 'rev', 'profit', 'gpr', 'npr', 'holders'], axis=1) 50 | 51 | ''' 52 | code,代码 53 | name,名称 54 | industry,所属行业 55 | area,地区 56 | pe,市盈率 57 | outstanding,流通股本(亿) 58 | totals,总股本(亿) 59 | totalAssets,总资产(万) 60 | liquidAssets,流动资产 61 | fixedAssets,固定资产 62 | reserved,公积金 63 | reservedPerShare,每股公积金 64 | esp,每股收益 65 | bvps,每股净资 66 | pb,市净率 67 | timeToMarket,上市日期 68 | undp,未分利润 69 | perundp, 每股未分配 70 | rev,收入同比(%) 71 | profit,利润同比(%) 72 | gpr,毛利率(%) 73 | npr,净利润率(%) 74 | holders,股东人数 75 | ''' 76 | 77 | #获取最新股价 78 | df_quots = ts.get_today_all() 79 | 80 | ''' 81 | code:代码 82 | name:名称 83 | changepercent:涨跌幅 84 | trade:现价 85 | open:开盘价 86 | high:最高价 87 | low:最低价 88 | settlement:昨日收盘价 89 | volume:成交量 90 | turnoverratio:换手率 91 | amount:成交量 92 | per:市盈率 93 | pb:市净率 94 | mktcap:总市值 95 | nmc:流通市值 96 | ''' 97 | 98 | df_quots['close'] = map(swap_value, df_quots['trade'], df_quots['settlement']) 99 | 100 | df_quots.reset_index(inplace=True) 101 | df_quots = df_quots.drop(['index','name','changepercent','trade','open','high','low','settlement','volume','turnoverratio','amount','per','pb','nmc'], axis=1) 102 | 103 | df = pd.merge(df_basic, df_quots, how='left', on=key) 104 | 105 | df['value'] = df['esp']/0.08 106 | 107 | df['rate'] = (df['value']-df['close'])*100/df['close'] 108 | 109 | #df['rate_inc'] = (df['value_increase']-df['close'])*100/df['close'] 110 | 111 | df = df.sort_values(["rate"], ascending=False) 112 | 113 | df.drop_duplicates(inplace=True) 114 | 115 | columns = {} 116 | columns['name'] = ['代码', '名称', '所属行业', '市盈率', '总股本(亿)', '每股收益', '每股净资产', '市净率', '上市日期', '净资产收益率', 'PEG', '总市值', '现价', '估值', '估值差(%)'] 117 | columns['index'] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 118 | 119 | export_report(df, columns=columns, title="stockvaluation") 120 | 121 | 122 | # 获取股票的均线 123 | def get_stock_ma(code, ma): 124 | df = ts.get_k_data(code=code, ktype='W', autype='qfq') 125 | 126 | df['ma'+str(ma)] = df['close'].rolling(window=ma).mean() 127 | 128 | return df 129 | 130 | 131 | def get_profit_report(): 132 | df = ts.profit_data(top=4000) 133 | 134 | df = df.sort_values('divi', ascending=False) 135 | 136 | #获取最新股价 137 | df_quots = ts.get_today_all() 138 | 139 | df_quots['roe'] = df_quots['pb']*100/df_quots['per'] 140 | #df_basic['peg'] = df_basic['pe']/df_basic['profit'] 141 | df_quots['close'] = map(swap_value, df_quots['trade'], df_quots['settlement']) 142 | 143 | 144 | df_quots.reset_index(inplace=True) 145 | df_quots = df_quots.drop(['index','name','changepercent','trade','open','high','low','settlement','volume','turnoverratio','amount','nmc'], axis=1) 146 | 147 | df = pd.merge(df, df_quots, how='left', on=key) 148 | 149 | df['rate'] = df['divi']/10*100/df['close'] 150 | 151 | df['valueprice'] = df['roe']*(df['close']/df['pb'])/15 152 | 153 | df = df.sort_values('rate', ascending=False) 154 | 155 | #df['value'] = df['esp']/0.08 156 | 157 | #df['rate'] = (df['value']-df['close'])*100/df['close'] 158 | 159 | df = df[df['per']>0] 160 | 161 | df = df.sort_values('report_date', ascending=False) 162 | 163 | df = df.drop_duplicates(['name']) 164 | 165 | 166 | # 按现金股息率排行 167 | df = df.sort_values('rate', ascending=False) 168 | export_report(df, title="stock_dividence") 169 | 170 | # 按roe排行 171 | df = df.sort_values('roe', ascending=False) 172 | export_report(df, title="stock_roe") 173 | 174 | # 按市盈率 175 | df = df.sort_values('per', ascending=True) 176 | export_report(df, title="stock_pe") 177 | 178 | # 按市净率 179 | df = df.sort_values('pb', ascending=True) 180 | export_report(df, title="stock_pb") 181 | 182 | # 不排序 183 | df = df.sort_values('mktcap', ascending=True) 184 | export_report(df, title="stock_value") 185 | 186 | # 按peg排行 187 | #export_report(df, columns=columns, title="stock_pb") 188 | 189 | if __name__ == '__main__': 190 | get_profit_report() 191 | -------------------------------------------------------------------------------- /analysis/history_data_service.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | """ 6 | 获取历史数据的服务,以增量方式对历史数据文件进行添加,每天收盘后运行一次 7 | """ 8 | 9 | import os 10 | from datetime import timedelta, datetime, date 11 | 12 | import pandas as pd 13 | from config import config 14 | from quotation import get_history_data 15 | from models import Instrument, Session 16 | from instruments import get_all_instrument_codes 17 | 18 | # 设置精度 19 | pd.set_option('precision', 2) 20 | 21 | 22 | # 23 | class HistoryDataService: 24 | def __init__(self, start_date='1990-12-1'): 25 | self.day_file_path = config['DAY_FILE_PATH'] 26 | self.start_date = start_date 27 | 28 | def run(self): 29 | 30 | codes = get_all_instrument_codes() 31 | 32 | today = date.today() 33 | 34 | for code in codes: 35 | data_filename = "%s%s.csv" % (self.day_file_path, code) # 日线数据文件名 36 | 37 | print("starting download %s, file path: %s" % (code, data_filename)) 38 | 39 | start_date = self.start_date 40 | 41 | if os.path.exists(data_filename): 42 | try: 43 | df = pd.read_csv(data_filename, index_col=['date']) 44 | last_date = datetime.strptime(df.index[-1], "%Y-%m-%d") + timedelta(days=1) 45 | start_date = last_date.strftime("%Y-%m-%d") 46 | print("file %s exists, download data from %s" % (data_filename, start_date)) 47 | except pd.errors.EmptyDataError as pderror: 48 | print(repr(pderror)) 49 | continue 50 | except Exception as e: 51 | print(repr(e)) 52 | continue 53 | 54 | #end_date = (today + timedelta(days=1)).strftime("%Y-%m-%d") 55 | end_date = today.strftime("%Y-%m-%d") 56 | 57 | if start_date == end_date: 58 | continue 59 | 60 | print("download data, code: %s, startdate: %s, enddate: %s" % (code, start_date, end_date)) 61 | 62 | try: 63 | df_download = get_history_data(str(code), start=start_date, end=end_date, autype='qfq', ktype='D') 64 | except Exception as e: 65 | print("download failure, code: %s, exception: %s" % (code, repr(e))) 66 | continue 67 | 68 | if df_download is not None: 69 | # 70 | df_download.sort_index(inplace=True) 71 | 72 | if os.path.exists(data_filename): 73 | df_download.to_csv(data_filename, mode='a', header=None, index=False, float_format='%.2f') 74 | else: 75 | df_download.to_csv(data_filename, index=False, float_format='%.2f') 76 | 77 | 78 | if __name__ == '__main__': 79 | history_data_service = HistoryDataService('2019-1-1') 80 | history_data_service.run() 81 | 82 | -------------------------------------------------------------------------------- /analysis/industry_cluster.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | """ 6 | 对股票进行分类 7 | """ 8 | 9 | import pandas as pd 10 | from datetime import timedelta, datetime, date 11 | from config import config 12 | from bin.quotation import get_history_data 13 | import tushare as ts 14 | import numpy as np 15 | from sklearn import cluster, covariance, manifold 16 | import os 17 | from sqlalchemy import create_engine 18 | from models import StockCluster, StockClusterItem, Session 19 | from sklearn.cluster import KMeans, AffinityPropagation 20 | import cluster_data_service as cds 21 | 22 | if __name__ == '__main__': 23 | cds.instruments_cluster('银行') 24 | 25 | 26 | -------------------------------------------------------------------------------- /analysis/instruments.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | """ 4 | 股票代码相关函数 5 | """ 6 | 7 | __author__ = 'Administrator' 8 | 9 | import numpy as np 10 | import tushare as ts 11 | from sqlalchemy import and_, cast, Integer 12 | from models import Instrument, Session 13 | 14 | 15 | # 字符串转换为浮点 16 | def value_2_float(value): 17 | if np.isnan(value): 18 | return None 19 | else: 20 | return float(value) 21 | 22 | # nan转换为None 23 | def nan_2_none(value): 24 | if value != value: 25 | return None 26 | else: 27 | return value 28 | 29 | 30 | def get_instrument_list(): 31 | """ 32 | 获取所有股票列表并保存到数据库 33 | """ 34 | try: 35 | # 下载数据 36 | df = ts.get_stock_basics() 37 | 38 | # 按照代码排序 39 | df.sort_index(inplace=True) 40 | 41 | session = Session() 42 | 43 | codes = [item[0] for item in session.query(Instrument.code).all()] 44 | 45 | for index, row in df.iterrows(): 46 | if index in codes: 47 | continue 48 | 49 | item = Instrument(index, name=row['name'], industry=nan_2_none(row['industry']), area=nan_2_none(row['area']), 50 | pe=value_2_float(row['pe']), outstanding=value_2_float(row['outstanding']), 51 | totals=value_2_float(row['totals']), total_assets=value_2_float(row['totalAssets']), 52 | liquid_assets=value_2_float(row['liquidAssets']), 53 | fixed_assets=value_2_float(row['fixedAssets']), reserved=value_2_float(row['reserved']), 54 | reserved_per_share=value_2_float(row['reservedPerShare']), esp=value_2_float(row['esp']), 55 | bvps=value_2_float(row['bvps']), pb=value_2_float(row['pb']), 56 | time_2_market=value_2_float(row['timeToMarket']), undp=value_2_float(row['undp']), 57 | perundp=value_2_float(row['perundp']), rev=value_2_float(row['rev']), 58 | profit=value_2_float(row['profit']), gpr=value_2_float(row['gpr']), 59 | npr=value_2_float(row['npr']), holders=value_2_float(row['holders'])) 60 | 61 | session.add(item) 62 | 63 | session.commit() 64 | 65 | session.close() 66 | except Exception as e: 67 | print('Exception: {}'.format(repr(e))) 68 | 69 | # 发送异常通知 70 | #text.send_text("处理股票代码数据失败") 71 | 72 | return df 73 | 74 | 75 | def get_all_instrument_codes(): 76 | """ 77 | 获取所有股票的代码 78 | """ 79 | session = Session() 80 | 81 | codes = [item[0] for item in session.query(Instrument.code).all()] 82 | 83 | session.close() 84 | 85 | return codes 86 | 87 | 88 | def get_all_instrument_codes_before(timeToMarket): 89 | """ 90 | 获取所有股票的代码 91 | """ 92 | session = Session() 93 | 94 | codes = [item[0] for item in session.query(Instrument.code).filter( 95 | and_(cast(Instrument.time_2_market, Integer) < timeToMarket, Instrument.time_2_market != '0', 96 | Instrument.time_2_market != None)).all()] 97 | 98 | session.close() 99 | 100 | return codes 101 | 102 | 103 | if __name__ == '__main__': 104 | get_instrument_list() -------------------------------------------------------------------------------- /analysis/price_change_analysis.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | ''' 3 | 股票涨跌幅计算 4 | ''' 5 | 6 | import pandas as pd 7 | import os 8 | from config import config 9 | import sys 10 | import numpy as np 11 | import datetime 12 | from instruments import get_all_instrument_codes_before 13 | 14 | 15 | # 设置精度 16 | pd.set_option('precision', 2) 17 | 18 | 19 | class PriceChangePeriod(object): 20 | """ 21 | 涨跌幅计算的一个时间段 22 | """ 23 | def __init__(self): 24 | self.begin_date = "" 25 | self.end_date = "" 26 | self.title = "" 27 | 28 | 29 | def price_change(df, begin_date, end_date): 30 | 31 | df = df[(df['date'] >= begin_date) & (df['date'] <= end_date)] 32 | 33 | rate = -9999 34 | update_date = None 35 | close = -9999 36 | if df.shape[0] > 0: 37 | first_row = df.iloc[0] 38 | last_row = df.iloc[-1] 39 | rate = (last_row['close'] - first_row['close'])*100/first_row['close'] 40 | close = first_row['close'] 41 | update_date = last_row['date'] 42 | 43 | return rate, close, update_date 44 | 45 | 46 | def price_amplitude(df, begin_date, end_date): 47 | result = df[(df['date'] >= begin_date) & (df['date'] <= end_date)] 48 | 49 | result['amplitude'] = (result['high'] - result['low'])*100 / result['low'] 50 | 51 | return result 52 | 53 | 54 | def compute_all_instruments(instrument_filename, day_file_path, result_file_path, periods): 55 | instruments = pd.read_csv(instrument_filename, index_col=False, dtype={'code': object}) 56 | 57 | if instruments is None: 58 | print("Could not find any instruments, exit") 59 | return 60 | 61 | instruments['close'] = None 62 | instruments['update_time'] = None 63 | 64 | for period in periods: 65 | instruments[period.title] = -9999 66 | 67 | for code in instruments['code']: 68 | try: 69 | file_path = day_file_path + '/' + code + '.csv' 70 | df = pd.read_csv(file_path) 71 | 72 | for period in periods: 73 | rate, close, update_time = price_change(df, period.begin_date, period.end_date) 74 | 75 | instruments.loc[instruments['code'] == code, period.title] = rate 76 | instruments.loc[instruments['code'] == code, 'close'] = close 77 | instruments.loc[instruments['code'] == code, 'update_time'] = update_time 78 | except Exception as e: 79 | print(str(e)) 80 | continue 81 | 82 | result_filename_date = result_file_path + "/price_change_" + datetime.date.today().strftime('%Y-%m-%d') + ".csv" 83 | 84 | result_filename = result_file_path + "/price_change.csv" 85 | 86 | instruments.to_csv(result_filename_date, index=False, float_format='%.2f') 87 | 88 | instruments.to_csv(result_filename, index=False, float_format='%.2f') 89 | 90 | return instruments 91 | 92 | 93 | def compute_all_instruments_amplitude(period): 94 | instruments = get_all_instrument_codes_before(20190101) 95 | 96 | if instruments is None: 97 | print("Could not find any instruments, exit") 98 | return 99 | 100 | result = pd.DataFrame() 101 | result['close'] = None 102 | result['update_time'] = None 103 | result[period.title] = -9999 104 | result['amp_std'] = None 105 | result['code'] = instruments 106 | 107 | for code in instruments: 108 | try: 109 | file_path = config['DAY_FILE_PATH'] + code + '.csv' 110 | df = pd.read_csv(file_path) 111 | 112 | df_amp = price_amplitude(df, period.begin_date, period.end_date) 113 | 114 | result.loc[result['code'] == code, 'close'] = df.iloc[df.shape[0]-1, 2] 115 | result.loc[result['code'] == code, 'update_time'] = df.iloc[df.shape[0]-1, 0] 116 | result.loc[result['code'] == code, period.title] = df_amp['amplitude'].mean() 117 | result.loc[result['code'] == code, 'amp_std'] = df_amp['amplitude'].std() 118 | except Exception as e: 119 | print(str(e)) 120 | continue 121 | 122 | result_filename_date = config['RESULT_FILE_PATH'] + "price_amplitude_" + datetime.date.today().strftime('%Y-%m-%d') + ".csv" 123 | 124 | result_filename = config['RESULT_FILE_PATH'] + "price_amplitude.csv" 125 | 126 | result.to_csv(result_filename_date, index=False, float_format='%.2f') 127 | 128 | result.to_csv(result_filename, index=False, float_format='%.2f') 129 | 130 | return result 131 | 132 | 133 | if __name__ == '__main__': 134 | today = datetime.date.today() 135 | 136 | # 计算的周期, 近七天、一个月、三个月、六个月和一年 137 | days_list = [7, 30, 30*3, 30*6, 30*12] 138 | 139 | periods = [] 140 | for days in days_list: 141 | period = PriceChangePeriod() 142 | period.begin_date = (datetime.date.today() + datetime.timedelta(days=-days)).strftime('%Y-%m-%d') 143 | period.end_date = today.strftime('%Y-%m-%d') 144 | period.title = 'rate' + str(days) 145 | periods.append(period) 146 | 147 | result = compute_all_instruments(config.INSTRUMENT_FILENAME, config.DAY_FILE_PATH, config.RESULT_PATH, periods) 148 | 149 | print(result) 150 | -------------------------------------------------------------------------------- /analysis/quotation.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | """ 4 | 获取行情数据的接口 5 | """ 6 | 7 | __author__ = 'Administrator' 8 | 9 | import tushare as ts 10 | import text 11 | import pandas as pd 12 | 13 | 14 | """ 15 | 请求参数: 16 | code: 证券代码:支持沪深A、B股 支持全部指数 支持ETF基金 17 | ktype: 数据类型:默认为D日线数据 D=日k线 W=周 M=月 5=5分钟 15=15分钟 30=30分钟 60=60分钟 18 | autype: 复权类型:qfq-前复权 hfq-后复权 None-不复权,默认为qfq 19 | index: 是否为指数:默认为False 设定为True时认为code为指数代码 20 | start: 开始日期 format:YYYY-MM-DD 为空时取当前日期 21 | end: 结束日期 format:YYYY-MM-DD 22 | 23 | 返回: 24 | date 日期和时间 低频数据时为:YYYY-MM-DD 高频数为:YYYY-MM-DD HH:MM 25 | open 开盘价 26 | close 收盘价 27 | high 最高价 28 | low 最低价 29 | volume 成交量 30 | code 证券代码 31 | """ 32 | # 获取历史行情数据 33 | def get_history_data(code, start, end, ktype='D', autype='qfq', index=False): 34 | df = pd.DataFrame() 35 | 36 | try: 37 | df = ts.get_k_data(code=code, ktype=ktype, autype=autype, index=index, start=start, end=end) 38 | except Exception as e: 39 | print('Exception:', repr(e)) 40 | 41 | # 发送异常通知 42 | text.send_text("获取历史数据失败, %s" % code) 43 | 44 | return df 45 | 46 | 47 | # 获取实时行情 48 | def get_realtime_data(): 49 | pass 50 | 51 | -------------------------------------------------------------------------------- /analysis/quotation.py.old: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | """ 4 | 获取行情数据的接口 5 | """ 6 | 7 | __author__ = 'Administrator' 8 | 9 | import tushare as ts 10 | import text 11 | import pandas as pd 12 | 13 | 14 | """ 15 | 请求参数: 16 | code: 证券代码:支持沪深A、B股 支持全部指数 支持ETF基金 17 | ktype: 数据类型:默认为D日线数据 D=日k线 W=周 M=月 5=5分钟 15=15分钟 30=30分钟 60=60分钟 18 | autype: 复权类型:qfq-前复权 hfq-后复权 None-不复权,默认为qfq 19 | index: 是否为指数:默认为False 设定为True时认为code为指数代码 20 | start: 开始日期 format:YYYY-MM-DD 为空时取当前日期 21 | end: 结束日期 format:YYYY-MM-DD 22 | 23 | 返回: 24 | date 日期和时间 低频数据时为:YYYY-MM-DD 高频数为:YYYY-MM-DD HH:MM 25 | open 开盘价 26 | close 收盘价 27 | high 最高价 28 | low 最低价 29 | volume 成交量 30 | code 证券代码 31 | """ 32 | # 获取历史行情数据 33 | def get_history_data(code, start, end, ktype='D', autype='qfq', index=False): 34 | df = pd.DataFrame() 35 | 36 | try: 37 | #df = ts.get_k_data(code=code, ktype=ktype, autype=autype, index=index, start=start, end=end) 38 | df = ts.get_h_data(code=code, autype=autype, index=index, start=start, end=end, retry_count=3, pause=10) 39 | 40 | if not df.empty: 41 | close = df['close'] 42 | 43 | df.drop(labels=['close', 'amount'], axis=1, inplace=True) 44 | 45 | df.insert(1, 'close', close) 46 | 47 | df['code'] = code 48 | 49 | df.sort_index(inplace=True) 50 | 51 | df['volume'] = df['volume']/100 52 | 53 | df.reset_index(inplace=True) 54 | 55 | print(df) 56 | except Exception, e: 57 | print 'Exception:', repr(e) 58 | 59 | # 发送异常通知 60 | text.send_text("获取历史数据失败, %s" % code) 61 | 62 | return df 63 | 64 | 65 | # 获取实时行情 66 | def get_realtime_data(): 67 | pass 68 | 69 | -------------------------------------------------------------------------------- /analysis/realtime_quot_updater.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | __author__ = 'Administrator' 5 | 6 | """ 7 | 按时间段更新行情并写入到redis 8 | """ 9 | 10 | import redis 11 | import tushare as ts 12 | import time 13 | import random 14 | import sys #reload()之前必须要引入模块 15 | reload(sys) 16 | sys.setdefaultencoding('utf-8') 17 | 18 | REALTIME_QUOTATION_KEY = 'realtime_quotation' 19 | 20 | """ 21 | code:代码 22 | name:名称 23 | changepercent:涨跌幅 24 | trade:现价 25 | open:开盘价 26 | high:最高价 27 | low:最低价 28 | settlement:昨日收盘价 29 | volume:成交量 30 | turnoverratio:换手率 31 | amount:成交量 32 | per:市盈率 33 | pb:市净率 34 | mktcap:总市值 35 | nmc:流通市值 36 | """ 37 | field_list = ["code", "name", "changepercent", "trade", "open", "high", "low", "settlement", "volume", "turnoverratio", "amount", "per", "pb", "mktcap", "nmc"] 38 | 39 | 40 | def update(): 41 | print "start updating" 42 | try: 43 | redis_db = redis.StrictRedis(host='localhost', port=8081, password='tw!@#$1234', decode_responses=False) # host是redis主机,需要redis服务端和客户端都启动 redis默认端口是6379 44 | 45 | df = ts.get_today_all() 46 | 47 | #data = df.to_msgpack(compress='zlib') # 直接转成msg写入redis 48 | #redis_db.set(REALTIME_QUOTATION_KEY, data) # key是"realtime_quotation" value是data 将键值对存入redis缓存 49 | 50 | for index, row in df.iterrows(): 51 | code = row["code"] 52 | 53 | values = {} 54 | for field in field_list: 55 | values[field] = row[field] 56 | 57 | values["update_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 58 | 59 | redis_db.hmset(code, values) 60 | 61 | ''' 62 | cached_data = redis_db.get(REALTIME_QUOTATION_KEY) 63 | 64 | df = pd.read_msgpack(cached_data) 65 | 66 | print df 67 | ''' 68 | except Exception as e: 69 | print str(e) 70 | 71 | def update_on_timer(): 72 | while True: 73 | current_time = time.localtime(time.time()) 74 | if (current_time.tm_hour <= 15) and (current_time.tm_hour >= 9) and (current_time.tm_wday >= 0) and (current_time.tm_wday <= 6): 75 | update() 76 | time.sleep(60*random.randint(10, 30)) 77 | else: 78 | time.sleep(60) 79 | 80 | 81 | if __name__ == '__main__': 82 | #update() 83 | 84 | update_on_timer() 85 | -------------------------------------------------------------------------------- /analysis/schedule_job.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | """ 6 | 定时任务管理器 7 | """ 8 | 9 | from datetime import datetime 10 | from history_data_service import HistoryDataService 11 | from technical_analysis_service import highest_in_history, ma_long_history, above_ma, break_ma, lowest_in_history 12 | from instruments import get_instrument_list 13 | import logging 14 | import os 15 | import sys 16 | sys.path.append("..") 17 | from utils.util import string_to_obj 18 | from strategy_test import PEMAStrategy 19 | 20 | 21 | # 输出时间 22 | def job(): 23 | #print(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) 24 | 25 | service_config = string_to_obj(os.environ['SERVICE_SETTINGS']) 26 | 27 | get_instrument_list(service_config) 28 | 29 | print "start downloading history data, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 30 | history_data_service = HistoryDataService(instrument_filename=service_config.INSTRUMENT_FILENAME, 31 | day_file_path=service_config.DAY_FILE_PATH) 32 | history_data_service.run() 33 | 34 | print "compute equities that price is highest in history, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 35 | highest_in_history(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 36 | result_file_path=service_config.RESULT_PATH) 37 | 38 | print "compute equities that price is lowest in history, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 39 | lowest_in_history(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, result_file_path=service_config.RESULT_PATH) 40 | 41 | print "compute equities that ma is long, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 42 | ma_long_history(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 43 | result_file_path=service_config.RESULT_PATH, ma1=5, ma2=10, ma3=20) 44 | 45 | print "compute equities that break ma, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 46 | break_ma(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 47 | result_file_path=service_config.RESULT_PATH, ma1=20) 48 | 49 | print "compute equities that above ma, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 50 | above_ma(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 51 | result_file_path=service_config.RESULT_PATH, ma1=250) 52 | 53 | strategy = PEMAStrategy(service_config.DAY_FILE_PATH) 54 | 55 | buy_list, sell_list = strategy.run() 56 | 57 | print buy_list, sell_list 58 | 59 | if __name__ == '__main__': 60 | job() 61 | -------------------------------------------------------------------------------- /analysis/schedule_manager.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | """ 6 | 定时任务管理器 7 | """ 8 | 9 | from apscheduler.schedulers.blocking import BlockingScheduler 10 | from datetime import datetime 11 | from history_data_service import HistoryDataService 12 | from technical_analysis_service import highest_in_history, lowest_in_history, ma_long_history, above_ma, break_ma 13 | import logging 14 | import os 15 | import sys 16 | sys.path.append("..") 17 | 18 | import getvaluation as gv 19 | import get_value_4_business as gv4b 20 | from instruments import get_instrument_list 21 | from utils.util import string_to_obj 22 | from strategy_test import PEMAStrategy 23 | 24 | 25 | # 输出时间 26 | def job(): 27 | #print(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) 28 | 29 | service_config = string_to_obj(os.environ['SERVICE_SETTINGS']) 30 | 31 | get_instrument_list(service_config) 32 | 33 | print "start downloading history data, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 34 | history_data_service = HistoryDataService(instrument_filename=service_config.INSTRUMENT_FILENAME, 35 | day_file_path=service_config.DAY_FILE_PATH) 36 | history_data_service.run() 37 | 38 | print "compute equities that price is highest in history, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 39 | highest_in_history(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 40 | result_file_path=service_config.RESULT_PATH) 41 | 42 | print "compute equities that price is lowest in history, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 43 | lowest_in_history(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 44 | result_file_path=service_config.RESULT_PATH) 45 | 46 | print "compute equities that ma is long, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 47 | ma_long_history(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 48 | result_file_path=service_config.RESULT_PATH, ma1=5, ma2=10, ma3=20) 49 | 50 | print "compute equities that break ma, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 51 | break_ma(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 52 | result_file_path=service_config.RESULT_PATH, ma1=20) 53 | 54 | print "compute equities that above ma, %s" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) 55 | above_ma(instrument_filename=service_config.INSTRUMENT_FILENAME, day_file_path=service_config.DAY_FILE_PATH, 56 | result_file_path=service_config.RESULT_PATH, ma1=250) 57 | 58 | strategy = PEMAStrategy(service_config.DAY_FILE_PATH) 59 | 60 | buy_list, sell_list = strategy.run() 61 | 62 | print buy_list, sell_list 63 | 64 | #gv.get_profit_report() 65 | 66 | #gv4b.get_profit_report() 67 | 68 | if __name__ == '__main__': 69 | 70 | ''' 71 | 72 | log = logging.getLogger('apscheduler.executors.default') 73 | log.setLevel(logging.INFO) # DEBUG 74 | 75 | fmt = logging.Formatter('%(levelname)s:%(name)s:%(message)s') 76 | h = logging.StreamHandler() 77 | h.setFormatter(fmt) 78 | log.addHandler(h) 79 | ''' 80 | logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', 81 | datafmt='%a, %d %b %Y %H:%M:%S', filename='/tmp/log.txt', filemode='a') 82 | 83 | # BlockingScheduler 84 | scheduler = BlockingScheduler() 85 | scheduler.add_job(job, 'cron', day_of_week='tue-sat', hour=00, minute=00) 86 | scheduler.start() 87 | -------------------------------------------------------------------------------- /analysis/start_job.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SERVICE_SETTINGS=analysis.config.ProductionConfig 4 | 5 | python schedule_job.py 6 | -------------------------------------------------------------------------------- /analysis/start_schedule.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SERVICE_SETTINGS=analysis.config.ProductionConfig 4 | 5 | nohup python schedule_manager.py & 6 | -------------------------------------------------------------------------------- /analysis/strategy_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | ''' 3 | 股票涨跌幅计算 4 | ''' 5 | 6 | import pandas as pd 7 | import os 8 | from config import config 9 | import sys 10 | import numpy as np 11 | import datetime 12 | import tushare as ts 13 | from models import StrategyResultInfo, Session 14 | from sqlalchemy import desc 15 | 16 | # 设置精度 17 | pd.set_option('precision', 2) 18 | 19 | 20 | # 选股策略 21 | class Strategy(object): 22 | def __init__(self, day_file_path): 23 | self._securities = None 24 | self._buy_list = [] 25 | self._sell_list = [] 26 | self._day_file_path = day_file_path 27 | 28 | def _handle_data(self): 29 | pass 30 | 31 | def _get_securities(self): 32 | pass 33 | 34 | def run(self): 35 | self._securities = self._get_securities() 36 | 37 | self._buy_list, self._sell_list = self._handle_data() 38 | 39 | return self._buy_list, self._sell_list 40 | 41 | 42 | # PE和均线选股策略 43 | class PEMAStrategy(Strategy): 44 | def __init__(self, day_file_path): 45 | super(PEMAStrategy, self).__init__(day_file_path) 46 | 47 | def _get_securities(self): 48 | securities = ts.get_hs300s() 49 | 50 | return securities['code'] 51 | 52 | def __get_sell_list(self, under_ma20_list): 53 | session = Session() 54 | 55 | ret = session.query(StrategyResultInfo).filter(StrategyResultInfo.name == self.__class__.__name__).order_by( 56 | desc(StrategyResultInfo.create_time)).first() 57 | 58 | sell_list = [] 59 | last_buy_list = [] 60 | 61 | if ret: 62 | last_buy_list = ret.buy_list.split(',') 63 | 64 | sell_list = [secId for secId in last_buy_list if secId in under_ma20_list] 65 | 66 | session.close() 67 | 68 | return last_buy_list, sell_list 69 | 70 | def _handle_data(self): 71 | ma_window_size = 20 72 | above_ma20_list = [] 73 | under_ma20_list = [] 74 | 75 | # 获取均线数据 76 | for security in self._securities: 77 | file_path = self._day_file_path + '/' + security + '.csv' 78 | 79 | try: 80 | df = pd.read_csv(file_path) 81 | 82 | if df.shape[0] < ma_window_size: 83 | continue 84 | 85 | df['pre_close'] = df['close'] 86 | df['pre_close'] = df['pre_close'].shift(1) 87 | df['ma20'] = df['close'].rolling(window=ma_window_size, center=False).mean() 88 | 89 | last_row = df.iloc[-1] 90 | 91 | if (last_row['ma20'] <= last_row['close']) and (last_row['ma20'] > last_row['pre_close']): 92 | above_ma20_list.append(security) 93 | 94 | if last_row['ma20'] > last_row['close']: 95 | under_ma20_list.append(security) 96 | except Exception as e: 97 | print "Exception: %s" % repr(e) 98 | 99 | # 获取当天的pe 100 | df = ts.get_stock_basics() 101 | 102 | df.reset_index(inplace=True) 103 | 104 | df = df[df['code'].isin(above_ma20_list)] 105 | 106 | df = df.sort_values(by='pe', ascending=True) 107 | 108 | pe_list = df['code'].values.tolist() 109 | 110 | last_buy_list, sell_list = self.__get_sell_list(under_ma20_list) 111 | 112 | buy_list = list(set(pe_list if len(pe_list) < 5 else pe_list[:5]).union(set(last_buy_list))) 113 | 114 | strategy_result_info = StrategyResultInfo(self.__class__.__name__, ','.join(buy_list), ','.join(sell_list)) 115 | 116 | session = Session() 117 | session.add(strategy_result_info) 118 | session.commit() 119 | session.close() 120 | 121 | return buy_list, sell_list 122 | 123 | 124 | if __name__ == '__main__': 125 | strategy = PEMAStrategy(config.DAY_FILE_PATH) 126 | 127 | buy_list, sell_list = strategy.run() 128 | 129 | print buy_list, sell_list 130 | -------------------------------------------------------------------------------- /analysis/technical_analysis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python history_data_service.py 4 | python technical_analysis_service.py 5 | -------------------------------------------------------------------------------- /analysis/test.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | """ 4 | 测试文件 5 | """ 6 | 7 | __author__ = 'Administrator' 8 | 9 | 10 | import numpy as np 11 | 12 | import sys 13 | reload(sys) 14 | sys.setdefaultencoding('utf-8') 15 | 16 | #x1 = np.array([1, 2, 3, 1, 5, 6, 5, 5, 6, 7, 8, 9, 9]) 17 | #x2 = np.array([1, 3, 2, 2, 8, 6, 7, 6, 7, 1, 2, 1, 3]) 18 | #x = np.array(list(zip(x1, x2))).reshape(len(x1), 2) 19 | 20 | import pandas as pd 21 | 22 | df = pd.read_csv("e:/sz50.csv") 23 | 24 | df = df.fillna(0.1) 25 | 26 | x = df.values 27 | 28 | print np.isnan(x).any() 29 | 30 | print x 31 | 32 | 33 | ''' 34 | from sklearn.cluster import KMeans 35 | kmeans=KMeans(n_clusters=8) #n_clusters:number of cluster 36 | kmeans.fit(x) 37 | print kmeans.labels_ 38 | 39 | 40 | df = pd.read_csv("e:/sz50_symbol.csv", encoding='gbk') 41 | 42 | df.set_index('code', inplace=True) 43 | 44 | names = df['name'] 45 | 46 | i = 0 47 | for name in names.values: 48 | print 'name: %s, label: %d' % (name, kmeans.labels_[i]) 49 | i += 1 50 | 51 | ''' 52 | 53 | from sklearn.cluster import AffinityPropagation 54 | from sklearn import metrics 55 | from sklearn.datasets.samples_generator import make_blobs 56 | 57 | # ############################################################################# 58 | # Generate sample data 59 | ''' 60 | centers = [[1, 1], [-1, -1], [1, -1]] 61 | X, labels_true = make_blobs(n_samples=300, centers=centers, cluster_std=0.5, 62 | random_state=0) 63 | 64 | print X 65 | ''' 66 | 67 | # ############################################################################# 68 | # Compute Affinity Propagation 69 | '''precomputed, euclidean''' 70 | af = AffinityPropagation(affinity='precomputed').fit(x) 71 | cluster_centers_indices = af.cluster_centers_indices_ 72 | labels = af.labels_ 73 | 74 | n_clusters_ = len(cluster_centers_indices) 75 | 76 | 77 | print('Estimated number of clusters: %d' % n_clusters_) 78 | print labels 79 | 80 | df = pd.read_csv("e:/sz50_symbol.csv") 81 | 82 | df.set_index('code', inplace=True) 83 | 84 | names = df['name'] 85 | 86 | #print names 87 | 88 | i = 0 89 | d = {} 90 | for name in names.values: 91 | if d.has_key(str(labels[i])): 92 | d[str(labels[i])].append(name) 93 | else: 94 | d[str(labels[i])] = [] 95 | d[str(labels[i])].append(name) 96 | 97 | i += 1 98 | 99 | for key, values in d.items(): 100 | names = u'' 101 | for value in values: 102 | names += value 103 | names += ',' 104 | print key, names 105 | 106 | ''' 107 | print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels_true, labels)) 108 | print("Completeness: %0.3f" % metrics.completeness_score(labels_true, labels)) 109 | print("V-measure: %0.3f" % metrics.v_measure_score(labels_true, labels)) 110 | print("Adjusted Rand Index: %0.3f" 111 | % metrics.adjusted_rand_score(labels_true, labels)) 112 | print("Adjusted Mutual Information: %0.3f" 113 | % metrics.adjusted_mutual_info_score(labels_true, labels)) 114 | print("Silhouette Coefficient: %0.3f" 115 | % metrics.silhouette_score(x, labels, metric='sqeuclidean')) 116 | ''' 117 | -------------------------------------------------------------------------------- /analysis/text.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | 3 | def send_text(msg): 4 | print(msg) 5 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | ################# 5 | #### imports #### 6 | ################# 7 | 8 | import os 9 | 10 | from flask import Flask, render_template 11 | from flask_login import LoginManager 12 | from flask_bcrypt import Bcrypt 13 | from flask_mail import Mail 14 | from flask_debugtoolbar import DebugToolbarExtension 15 | from flask_sqlalchemy import SQLAlchemy 16 | from flask_bootstrap import Bootstrap 17 | from redis_op import RedisOP 18 | import logging 19 | 20 | 21 | 22 | ################ 23 | #### config #### 24 | ################ 25 | 26 | def _check_config_variables_are_set(config): 27 | assert config['MAIL_USERNAME'] is not None,\ 28 | 'MAIL_USERNAME is not set, set the env variable APP_MAIL_USERNAME '\ 29 | 'or MAIL_USERNAME in the production config file.' 30 | assert config['MAIL_PASSWORD'] is not None,\ 31 | 'MAIL_PASSWORD is not set, set the env variable APP_MAIL_PASSWORD '\ 32 | 'or MAIL_PASSWORD in the production config file.' 33 | 34 | assert config['SECRET_KEY'] is not None,\ 35 | 'SECRET_KEY is not set, set it in the production config file.' 36 | assert config['SECURITY_PASSWORD_SALT'] is not None,\ 37 | 'SECURITY_PASSWORD_SALT is not set, '\ 38 | 'set it in the production config file.' 39 | 40 | assert config['SQLALCHEMY_DATABASE_URI'] is not None,\ 41 | 'SQLALCHEMY_DATABASE_URI is not set, '\ 42 | 'set it in the production config file.' 43 | 44 | if os.environ['APP_SETTINGS'] == 'project.config.ProductionConfig': 45 | assert config['STRIPE_SECRET_KEY'] is not None,\ 46 | 'STRIPE_SECRET_KEY is not set, '\ 47 | 'set it in the production config file.' 48 | assert config['STRIPE_PUBLISHABLE_KEY'] is not None,\ 49 | 'STRIPE_PUBLISHABLE_KEY is not set, '\ 50 | 'set it in the production config file.' 51 | 52 | 53 | app = Flask(__name__) 54 | 55 | 56 | print(os.environ['APP_SETTINGS']) 57 | 58 | app.config.from_object(os.environ['APP_SETTINGS']) 59 | #_check_config_variables_are_set(app.config) 60 | 61 | handler = logging.FileHandler('flask.log', encoding='UTF-8') 62 | handler.setLevel(logging.DEBUG) 63 | logging_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s') 64 | handler.setFormatter(logging_format) 65 | app.logger.addHandler(handler) 66 | 67 | log = app.logger 68 | 69 | # 创建redis连接池 70 | RedisOP.create_pool(app.config) 71 | 72 | 73 | #################### 74 | #### extensions #### 75 | #################### 76 | 77 | login_manager = LoginManager(app) 78 | bcrypt = Bcrypt(app) 79 | mail = Mail(app) 80 | toolbar = DebugToolbarExtension(app) 81 | db = SQLAlchemy(app) 82 | bootstrap = Bootstrap(app) 83 | 84 | from analyzer import Analyzer 85 | analyzer = Analyzer(app, db) 86 | 87 | #################### 88 | #### blueprints #### 89 | #################### 90 | 91 | from app.main.views import main_blueprint 92 | from app.user.views import user_blueprint 93 | from app.stock.views import stock_blueprint 94 | from app.business.views import business_blueprint 95 | from app.strategy_analysis.views import strategy_analysis_blueprint 96 | from app.technical_analysis.views import technical_analysis_blueprint 97 | from app.self_selected_stock.views import self_selected_stock_blueprint 98 | from app.industry_analysis.views import industry_analysis_blueprint 99 | from app.cluster_analysis.views import cluster_analysis_blueprint 100 | from app.annual_report.views import annual_report_blueprint 101 | app.register_blueprint(main_blueprint) 102 | app.register_blueprint(user_blueprint) 103 | app.register_blueprint(stock_blueprint) 104 | app.register_blueprint(business_blueprint) 105 | app.register_blueprint(strategy_analysis_blueprint) 106 | app.register_blueprint(technical_analysis_blueprint) 107 | app.register_blueprint(self_selected_stock_blueprint) 108 | app.register_blueprint(industry_analysis_blueprint) 109 | app.register_blueprint(cluster_analysis_blueprint) 110 | app.register_blueprint(annual_report_blueprint) 111 | 112 | 113 | #################### 114 | #### flask-login #### 115 | #################### 116 | 117 | from app.models import User 118 | 119 | login_manager.login_view = "user.login" 120 | login_manager.login_message_category = "danger" 121 | 122 | 123 | @login_manager.user_loader 124 | def load_user(user_id): 125 | return User.query.filter(User.id == int(user_id)).first() 126 | 127 | 128 | ######################## 129 | #### error handlers #### 130 | ######################## 131 | 132 | @app.errorhandler(403) 133 | def forbidden_page(error): 134 | return render_template("errors/403.html"), 403 135 | 136 | 137 | @app.errorhandler(404) 138 | def page_not_found(error): 139 | return render_template("errors/404.html"), 404 140 | 141 | 142 | @app.errorhandler(500) 143 | def server_error_page(error): 144 | return render_template("errors/500.html"), 500 145 | -------------------------------------------------------------------------------- /app/annual_report/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | -------------------------------------------------------------------------------- /app/annual_report/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/annual_report/__init__.pyc -------------------------------------------------------------------------------- /app/annual_report/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from flask import request, jsonify, Blueprint, render_template, current_app 5 | from flask_login import login_required, current_user 6 | import csv 7 | import os 8 | import time 9 | import json 10 | from app import db 11 | from app.models import SelfSelectedStock 12 | from app.decorators import check_confirmed 13 | from app.util import model_to_json 14 | 15 | import sys #reload()之前必须要引入模块 16 | reload(sys) 17 | sys.setdefaultencoding('utf-8') 18 | 19 | annual_report_blueprint = Blueprint('annual_report', __name__,) 20 | 21 | 22 | @annual_report_blueprint.route('/annual_report/', methods=['POST', 'GET']) 23 | #@login_required 24 | def index(path): 25 | print("annual report: " + path) 26 | 27 | return render_template('annual_report/annual_report.html', year=path) 28 | 29 | 30 | # 31 | @annual_report_blueprint.route('/annual_report_stock///', methods=['POST', 'GET']) 32 | #@login_required 33 | def get_stock_report(year, amporchange, highorlow): 34 | print("annual report for stock: {}, {}, {}".format(year, amporchange, highorlow)) 35 | 36 | data = [] 37 | 38 | pic_path = current_app.config['RESULT_PATH'] + '/' + "annual_technique_report_" + year + ".csv" 39 | 40 | filemt = time.localtime(os.stat(pic_path).st_mtime) 41 | #print time.strftime("%Y-%m-%d", filemt) 42 | 43 | # 读取csv至字典 44 | csvFile = open(pic_path, "r") 45 | 46 | field_types = [('change_rate', float), ('amplitude', float)] 47 | 48 | index = 1 49 | for row in csv.DictReader(csvFile): 50 | row['id'] = index 51 | row['updateTime'] = time.strftime("%Y-%m-%d", filemt) 52 | row.update((key, conversion(row[key])) for key, conversion in field_types) 53 | index += 1 54 | data.append(row) 55 | 56 | if int(amporchange) == 0: 57 | field = 'change_rate' 58 | else: 59 | field = 'amplitude' 60 | 61 | data.sort(key=lambda e: e.__getitem__(field), reverse=True) 62 | 63 | if int(highorlow) == 0: 64 | return jsonify({'total': 10, 'rows': data[0:10]}) 65 | else: 66 | return jsonify({'total': 10, 'rows': data[:-11:-1]}) 67 | 68 | 69 | # 70 | @annual_report_blueprint.route('/annual_report_industry///', methods=['POST', 'GET']) 71 | #@login_required 72 | def get_industry_report(year, amporchange, highorlow): 73 | print("annual report for industry: " + year) 74 | 75 | data = [] 76 | 77 | pic_path = current_app.config['RESULT_PATH'] + '/' + "annual_industry_report_" + year + ".csv" 78 | 79 | filemt = time.localtime(os.stat(pic_path).st_mtime) 80 | #print time.strftime("%Y-%m-%d", filemt) 81 | 82 | field_types = [('avg_change_rate', float), ('avg_amplitude', float)] 83 | 84 | # 读取csv至字典 85 | csvFile = open(pic_path, "r") 86 | 87 | index = 1 88 | for row in csv.DictReader(csvFile): 89 | row['id'] = index 90 | row['updateTime'] = time.strftime("%Y-%m-%d", filemt) 91 | index += 1 92 | row.update((key, conversion(row[key])) for key, conversion in field_types) 93 | data.append(row) 94 | 95 | if int(amporchange) == 0: 96 | field = 'avg_change_rate' 97 | else: 98 | field = 'avg_amplitude' 99 | 100 | data.sort(key=lambda e: e.__getitem__(field), reverse=True) 101 | 102 | if int(highorlow) == 0: 103 | return jsonify({'total': 10, 'rows': data[0:10]}) 104 | else: 105 | return jsonify({'total': 10, 'rows': data[:-11:-1]}) 106 | 107 | 108 | -------------------------------------------------------------------------------- /app/annual_report/views.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/annual_report/views.pyc -------------------------------------------------------------------------------- /app/business/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/business/__init__.py -------------------------------------------------------------------------------- /app/business/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from flask import jsonify, request, render_template, Blueprint 5 | import csv 6 | import json 7 | from app.models import StockLabels 8 | from app import db 9 | from flask_login import login_required 10 | from flask import current_app 11 | 12 | import sys # reload()之前必须要引入模块 13 | 14 | reload(sys) 15 | sys.setdefaultencoding('utf-8') 16 | 17 | business_blueprint = Blueprint('business', __name__) 18 | 19 | 20 | class LabelResult: 21 | code = '' 22 | labels = '' 23 | 24 | def __init__(self, code, labels): 25 | self.code = code 26 | self.labels = labels 27 | 28 | 29 | # 处理精选排行 30 | def handle_business(labels): 31 | pic_path = current_app.config['RESULT_PATH'] + '/stock_business.csv' 32 | 33 | # 读取csv至字典 34 | csvFile = open(pic_path, "r") 35 | 36 | stockdata = csv.DictReader(csvFile) 37 | 38 | labelset = labels.split() 39 | 40 | resultList = [] 41 | 42 | # 不需要过滤 43 | if len(labelset) <= 0: 44 | for item in stockdata: 45 | stockLabels = db.session.query(StockLabels).filter_by(code=item['code']).first() 46 | 47 | if stockLabels is not None: 48 | item['labels'] = stockLabels.labels 49 | 50 | resultList.append(item) 51 | 52 | else: 53 | for item in stockdata: 54 | stockLabels = db.session.query(StockLabels).filter_by(code=item['code']).first() 55 | 56 | if stockLabels is None: 57 | continue 58 | 59 | item['labels'] = stockLabels.labels 60 | 61 | combineset = list(set(stockLabels.labels.split()).intersection(set(labelset))) 62 | if combineset is not None and len(combineset) > 0: 63 | resultList.append(item) 64 | 65 | template_filename = "business.html" 66 | 67 | return render_template(template_filename, title='精选股票', stockdata=resultList) 68 | 69 | 70 | # 获取精选排行数据 71 | def create_business_data(labels): 72 | pic_path = current_app.config['RESULT_PATH'] + '/stock_business.csv' 73 | 74 | # 读取csv至字典 75 | csvFile = open(pic_path, "r") 76 | 77 | stockdata = csv.DictReader(csvFile) 78 | 79 | labelset = labels.split() 80 | 81 | resultList = [] 82 | 83 | index = 1 84 | 85 | # 不需要过滤 86 | if len(labelset) <= 0: 87 | for item in stockdata: 88 | stockLabels = db.session.query(StockLabels).filter_by(code=item['code']).first() 89 | 90 | if stockLabels is not None: 91 | item['labels'] = stockLabels.labels 92 | 93 | item['id'] = index 94 | 95 | index += 1 96 | 97 | resultList.append(item) 98 | 99 | else: 100 | for item in stockdata: 101 | stockLabels = db.session.query(StockLabels).filter_by(code=item['code']).first() 102 | 103 | if stockLabels is None: 104 | continue 105 | 106 | item['labels'] = stockLabels.labels 107 | 108 | combineset = list(set(stockLabels.labels.split()).intersection(set(labelset))) 109 | if combineset is not None and len(combineset) > 0: 110 | item['id'] = index 111 | index += 1 112 | resultList.append(item) 113 | 114 | return resultList 115 | 116 | 117 | # 处理精选排行的ajax数据请求 118 | @business_blueprint.route('/data', methods=['POST', 'GET']) 119 | @login_required 120 | def get_business_data(): 121 | print("get business data") 122 | 123 | info = request.values 124 | limit = info.get('limit', 10) # 每页显示的条数 125 | offset = info.get('offset', 0) # 分片数,(页码-1)*limit,它表示一段数据的起点 126 | labels = info.get('labels', '') 127 | 128 | data = create_business_data(labels) 129 | # return jsonify({'total': len(data), 'rows': data[int(offset):(int(offset) + int(limit))]}) 130 | return jsonify({'total': len(data), 'rows': data}) 131 | 132 | 133 | -------------------------------------------------------------------------------- /app/cluster_analysis/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | -------------------------------------------------------------------------------- /app/cluster_analysis/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from flask import request, jsonify, Blueprint, render_template 5 | from flask_login import login_required, current_user 6 | import csv 7 | import os 8 | import time 9 | import json 10 | from app import db 11 | from app.models import StockCluster, StockClusterItem 12 | from app.decorators import check_confirmed 13 | from app.util import model_to_json 14 | 15 | 16 | import sys #reload()之前必须要引入模块 17 | reload(sys) 18 | sys.setdefaultencoding('utf-8') 19 | 20 | BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 21 | sys.path.append(BASE_DIR) 22 | 23 | from analysis.cluster_data_service import instruments_cluster 24 | 25 | cluster_analysis_blueprint = Blueprint('cluster', __name__,) 26 | 27 | 28 | # 处理行业分类的ajax数据请求 29 | @cluster_analysis_blueprint.route('/cluster//data', methods=['POST', 'GET']) 30 | #@login_required 31 | def get_data(path): 32 | print("get_cluster_data") 33 | 34 | industry = request.values.get('industry', path) 35 | 36 | if industry =='all': 37 | return jsonify({'total': 0, 'rows': []}) 38 | 39 | if path != 'sz50' and path != 'zz500' and path != 'hs300': 40 | print("compute industry cluster") 41 | instruments_cluster(industry) 42 | 43 | clusters = db.session.query(StockCluster).filter_by(section=industry).all() 44 | 45 | cluster_items = db.session.query(StockClusterItem).filter_by(section=industry).all() 46 | 47 | data = [] 48 | 49 | index = 1 50 | for cluster in clusters: 51 | rsp_cluster = {} 52 | rsp_cluster['id'] = index 53 | rsp_cluster['code'] = cluster.code 54 | rsp_cluster['name'] = cluster.name 55 | rsp_cluster['items'] = [] 56 | 57 | items = [] 58 | 59 | rsp_cluster_item = {} 60 | rsp_cluster_item['code'] = cluster.code 61 | rsp_cluster_item['name'] = cluster.name 62 | rsp_cluster_item['corr'] = 1.0 63 | 64 | items.append(rsp_cluster_item) 65 | 66 | for cluster_item in cluster_items: 67 | if cluster.code == cluster_item.parent_code: 68 | rsp_cluster_item = {} 69 | rsp_cluster_item['code'] = cluster_item.code 70 | rsp_cluster_item['name'] = cluster_item.name 71 | rsp_cluster_item['corr'] = cluster_item.corr 72 | items.append(rsp_cluster_item) 73 | 74 | rsp_cluster['items'] = items 75 | 76 | data.append(rsp_cluster) 77 | 78 | index += 1 79 | 80 | return jsonify({'total': len(data), 'rows': data}) 81 | 82 | 83 | # 处理首页的导航 84 | @cluster_analysis_blueprint.route('/cluster/', methods=['GET', 'POST']) 85 | #@login_required 86 | #@check_confirmed 87 | def index(path): 88 | template_filename = 'cluster_analysis/cluster.html' 89 | 90 | title = '' 91 | if path == 'sz50': 92 | title = '上证50' 93 | elif path == 'hs300': 94 | title = '沪深300' 95 | elif path == 'zz500': 96 | title = '中证500' 97 | else: 98 | template_filename = 'cluster_analysis/industry_cluster.html' 99 | title = '全部股票' 100 | 101 | return render_template(template_filename, title=title, path=path) 102 | 103 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | # project/config.py 2 | 3 | import os 4 | try: 5 | # Python 2.7 6 | import ConfigParser as configparser 7 | except ImportError: 8 | # Python 3 9 | import configparser 10 | 11 | basedir = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | 14 | def _get_bool_env_var(varname, default=None): 15 | 16 | value = os.environ.get(varname, default) 17 | 18 | if value is None: 19 | return False 20 | elif isinstance(value, str) and value.lower() == 'false': 21 | return False 22 | elif bool(value) is False: 23 | return False 24 | else: 25 | return bool(value) 26 | 27 | 28 | class BaseConfig(object): 29 | """Base configuration.""" 30 | 31 | # main config 32 | SECRET_KEY = 'my_precious' 33 | SECURITY_PASSWORD_SALT = 'my_precious_two' 34 | DEBUG = False 35 | BCRYPT_LOG_ROUNDS = 13 36 | WTF_CSRF_ENABLED = True 37 | DEBUG_TB_ENABLED = False 38 | DEBUG_TB_INTERCEPT_REDIRECTS = False 39 | 40 | # mail settings 41 | # defaults are: 42 | # - MAIL_SERVER = 'smtp.googlemail.com' 43 | # - MAIL_PORT = 465 44 | # - MAIL_USE_TLS = False 45 | # - MAIL_USE_SSL = True 46 | MAIL_SERVER = os.environ.get('APP_MAIL_SERVER', 'smtp.googlemail.com') 47 | MAIL_PORT = int(os.environ.get('APP_MAIL_PORT', 465)) 48 | MAIL_USE_TLS = _get_bool_env_var('APP_MAIL_USE_TLS', False) 49 | MAIL_USE_SSL = _get_bool_env_var('APP_MAIL_USE_SSL', True) 50 | 51 | # mail authentication 52 | MAIL_USERNAME = os.environ.get('APP_MAIL_USERNAME', None) 53 | MAIL_PASSWORD = os.environ.get('APP_MAIL_PASSWORD', None) 54 | 55 | # mail accounts 56 | MAIL_DEFAULT_SENDER = 'from@example.com' 57 | 58 | 59 | class DevelopmentConfig(BaseConfig): 60 | """Development configuration.""" 61 | DEBUG = True 62 | WTF_CSRF_ENABLED = False 63 | #SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'dev.sqlite') 64 | DEBUG_TB_ENABLED = True 65 | 66 | 67 | SQLALCHEMY_DATABASE_URI = 'mysql://root:password@127.0.0.1/stock?charset=utf8' 68 | 69 | 70 | class TestingConfig(BaseConfig): 71 | """Testing configuration.""" 72 | LOGIN_DISABLED=False 73 | TESTING = True 74 | DEBUG = False 75 | BCRYPT_LOG_ROUNDS = 1 76 | WTF_CSRF_ENABLED = False 77 | DEBUG_TB_ENABLED = False 78 | SQLALCHEMY_DATABASE_URI = 'sqlite://' 79 | 80 | 81 | class ProductionConfig(BaseConfig): 82 | """Production configuration.""" 83 | DEBUG = False 84 | DEBUG_TB_ENABLED = False 85 | 86 | SECRET_KEY = None 87 | SECURITY_PASSWORD_SALT = None 88 | 89 | STRIPE_SECRET_KEY = None 90 | STRIPE_PUBLISHABLE_KEY = None 91 | 92 | SQLALCHEMY_DATABASE_URI = None 93 | 94 | # production config takes precedence over env variables 95 | 96 | # production config file at ./project/config/production.cfg 97 | config_path = os.path.join(basedir, 'config', 'production.cfg') 98 | 99 | # if config file exists, read it: 100 | if os.path.isfile(config_path): 101 | config = configparser.ConfigParser() 102 | 103 | with open(config_path) as configfile: 104 | config.readfp(configfile) 105 | 106 | SECRET_KEY = config.get('keys', 'SECRET_KEY') 107 | SECURITY_PASSWORD_SALT = config.get('keys', 'SECRET_KEY') 108 | 109 | # mail settings 110 | MAIL_SERVER = config.get('mail', 'MAIL_SERVER') 111 | MAIL_PORT = config.getint('mail', 'MAIL_PORT') 112 | MAIL_USE_TLS = config.getboolean('mail', 'MAIL_USE_TLS') 113 | MAIL_USE_SSL = config.getboolean('mail', 'MAIL_USE_SSL') 114 | 115 | # mail authentication and sender 116 | MAIL_USERNAME = config.get('mail', 'MAIL_USERNAME') 117 | MAIL_PASSWORD = config.get('mail', 'MAIL_PASSWORD') 118 | MAIL_DEFAULT_SENDER = config.get('mail', 'MAIL_DEFAULT_SENDER') 119 | 120 | # database URI 121 | SQLALCHEMY_DATABASE_URI = config.get('db', 'SQLALCHEMY_DATABASE_URI') 122 | 123 | # stripe keys 124 | STRIPE_SECRET_KEY = config.get('stripe', 'STRIPE_SECRET_KEY') 125 | STRIPE_PUBLISHABLE_KEY = config.get('stripe', 'STRIPE_PUBLISHABLE_KEY') 126 | -------------------------------------------------------------------------------- /app/config/production.cfg: -------------------------------------------------------------------------------- 1 | # This is a sample config file. 2 | # Create from this template a config named: 3 | # production.cfg 4 | # 5 | # for your production settings. 6 | # 7 | # You don't need quotes for strings. 8 | # Lines starting with # are comments and are ignored 9 | 10 | [keys] 11 | SECRET_KEY = SECRET_KEY 12 | SECURITY_PASSWORD_SALT = SECURITY_PASSWORD_SALT 13 | 14 | [mail] 15 | MAIL_SERVER = smtp.gmail.com 16 | MAIL_PORT = 465 17 | MAIL_USE_TLS = False 18 | MAIL_USE_SSL = True 19 | 20 | # mail authentication 21 | MAIL_USERNAME = xxx@gmail.com 22 | MAIL_PASSWORD = 123456 23 | 24 | # mail sender 25 | MAIL_DEFAULT_SENDER = xxx@gmail.com 26 | 27 | [db] 28 | # the database URL is specified as follows: 29 | # dialect+driver://username:password@host:port/database 30 | 31 | SQLALCHEMY_DATABASE_URI = mysql://root:password@127.0.0.1:3306/stock?charset=utf8 32 | 33 | [stripe] 34 | STRIPE_SECRET_KEY = foo 35 | STRIPE_PUBLISHABLE_KEY = bar 36 | -------------------------------------------------------------------------------- /app/decorators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # project/decorators.py 3 | 4 | 5 | from functools import wraps 6 | 7 | from flask import flash, redirect, url_for 8 | from flask_login import current_user 9 | 10 | 11 | def check_confirmed(func): 12 | @wraps(func) 13 | def decorated_function(*args, **kwargs): 14 | if current_user.confirmed is False: 15 | return redirect(url_for('user.unconfirmed')) 16 | return func(*args, **kwargs) 17 | 18 | return decorated_function 19 | -------------------------------------------------------------------------------- /app/industry_analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/industry_analysis/__init__.py -------------------------------------------------------------------------------- /app/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/main/__init__.py -------------------------------------------------------------------------------- /app/myemail.py: -------------------------------------------------------------------------------- 1 | # project/email.py 2 | 3 | from flask_mail import Message 4 | 5 | from app import app, mail 6 | 7 | from sendmail import send_mail 8 | 9 | def send_email(to, subject, template): 10 | ''' 11 | msg = Message( 12 | subject, 13 | recipients=[to], 14 | html=template, 15 | sender=app.config['MAIL_DEFAULT_SENDER'] 16 | ) 17 | mail.send(msg) 18 | ''' 19 | send_mail(subject=subject, mailto=to, content=template, attachments=None) 20 | -------------------------------------------------------------------------------- /app/redis_op.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'Administrator' 4 | 5 | 6 | import redis 7 | import re 8 | 9 | 10 | class RedisOP(object): 11 | """ 12 | redis操作类,封装了redis连接池 13 | """ 14 | def __init__(self): 15 | if not hasattr(RedisOP, 'pool'): 16 | raise Exception('No connection pool') 17 | self.conn = redis.Redis(connection_pool=RedisOP.pool) 18 | 19 | @staticmethod 20 | def create_pool(config): 21 | url = config['REDIS_URL'] 22 | host = '' 23 | port = 6793 24 | password = '' 25 | db = 0 26 | if url is not None: 27 | match_result = re.match(r'redis://:(.*)@(.*):(.*)/(.?)', url, re.M|re.I) 28 | 29 | if match_result: 30 | password = match_result.group(1) 31 | host = match_result.group(2) 32 | port = int(match_result.group(3)) 33 | db = int(match_result.group(4)) 34 | 35 | RedisOP.pool = redis.ConnectionPool(host=host, password=password, port=port, db=db) 36 | 37 | """ 38 | string类型 {'key':'value'} redis操作 39 | """ 40 | def set_value(self, key, value, time=None): 41 | if time: 42 | res = self.conn.setex(key, value, time) 43 | else: 44 | res = self.conn.set(key, value) 45 | return res 46 | 47 | def get_value(self, key): 48 | res = self.conn.get(key).decode() 49 | return res 50 | 51 | def del_key(self, key): 52 | res = self.conn.delete(key) 53 | return res 54 | 55 | """ 56 | hash类型,{'name':{'key':'value'}} redis操作 57 | """ 58 | def set_hash(self, name, key, value): 59 | res = self.conn.hset(name, key, value) 60 | return res 61 | 62 | # 设置多个field-value, mapping为字典格式 63 | def set_m_hash(self, name, mapping): 64 | res = self.conn.hmset(name, mapping) 65 | return res 66 | 67 | def get_hash(self, name, key=None): 68 | # 判断key是否我为空,不为空,获取指定name内的某个key的value; 为空则获取name对应的所有value 69 | if key: 70 | res = self.conn.hget(name, key) 71 | else: 72 | res = self.conn.hgetall(name) 73 | return res 74 | 75 | def del_hash_key(self, name, key=None): 76 | if key: 77 | res = self.conn.hdel(name, key) 78 | else: 79 | res = self.conn.delete(name) 80 | return res -------------------------------------------------------------------------------- /app/self_selected_stock/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | -------------------------------------------------------------------------------- /app/sendmail.py: -------------------------------------------------------------------------------- 1 | # send mail 2 | # -*- coding:utf-8 -*- 3 | __author__ = 'jerry' 4 | 5 | from email.mime.text import MIMEText 6 | from email.mime.multipart import MIMEMultipart 7 | from email import Encoders 8 | from email import Utils 9 | import os 10 | import smtplib 11 | 12 | 13 | #"突破60天均线的股票列表" 14 | 15 | def send_mail(subject, mailto, content, attachments): 16 | 17 | #创建一个带附件的实例 18 | msg = MIMEMultipart() 19 | 20 | textmsg = MIMEText(content, _subtype='html', _charset='utf-8') 21 | msg.attach(textmsg) 22 | 23 | #构造附件1 24 | if not attachments == None: 25 | for attachment in attachments: 26 | att1 = MIMEText(open(attachment, 'rb').read(), 'base64', 'utf-8') 27 | att1['Content-Type'] = 'application/octet-stream' 28 | filename = os.path.basename(attachment) 29 | description = 'attachment; filename=%s' % (filename,) 30 | att1['Content-Disposition'] = description #这里的filename可以任意写,写什么名字,邮件中显示什么名字 31 | msg.attach(att1) 32 | 33 | 34 | #加邮件头 35 | msg['to'] = ",".join(mailto) 36 | msg['from'] = 'xxx@163.com' 37 | msg['subject'] = subject 38 | 39 | #发送邮件 40 | try: 41 | server = smtplib.SMTP() 42 | #server.set_debuglevel(1) 43 | server.connect('smtp.163.com') 44 | server.login('xxx', 'xxxxx')#XXX为用户名,XXXXX为密码 45 | server.sendmail(msg['from'], mailto, msg.as_string()) 46 | server.quit() 47 | print '发送成功' 48 | except Exception, e: 49 | print str(e) 50 | 51 | #sendmail('突破60天均线股票列表', content='突破60天均线股票列表', attachments=['C:/Stock/data/mafilter_60_20151128.txt']) 52 | -------------------------------------------------------------------------------- /app/static/css/industry.css: -------------------------------------------------------------------------------- 1 | .color_cluster1 { 2 | color: #FF0088; 3 | } 4 | 5 | .color_cluster2 { 6 | color: #FF0000; 7 | } 8 | 9 | .color_cluster3 { 10 | color: #FF5511; 11 | } 12 | 13 | .color_cluster4 { 14 | color: #FF8800; 15 | } 16 | 17 | .color_cluster5 { 18 | color: #FFBB00; 19 | } 20 | 21 | .color_cluster6 { 22 | color: #5F9F9F; 23 | } 24 | 25 | .color_cluster7 { 26 | color: #B87333 27 | } 28 | 29 | .color_cluster8 { 30 | color: #42426F; 31 | } 32 | 33 | .color_cluster9 { 34 | color: #9932CD; 35 | } 36 | 37 | .color_cluster10 { 38 | color: #855E42; 39 | } 40 | 41 | .color_cluster11 { 42 | color: #D19275; 43 | } 44 | 45 | .color_cluster12 { 46 | color: #238E23; 47 | } 48 | 49 | .color_cluster13 { 50 | color: #00BBFF; 51 | } 52 | 53 | .color_cluster14 { 54 | color: #0066FF; 55 | } 56 | 57 | .color_cluster15 { 58 | color: #0000FF; 59 | } 60 | 61 | .color_cluster16 { 62 | color: #5500FF; 63 | } 64 | 65 | .color_cluster17 { 66 | color: #7700FF; 67 | } 68 | 69 | .color_cluster18 { 70 | color: #9900FF; 71 | } 72 | 73 | .color_cluster19 { 74 | color: #CC00FF; 75 | } 76 | 77 | .color_cluster20 { 78 | color: #FF00FF; 79 | } 80 | 81 | .color_up { 82 | color: #DD2200; 83 | } 84 | 85 | .color_down { 86 | color: #009933; 87 | } 88 | 89 | html { 90 | position: relative; 91 | min-height: 100%; 92 | } 93 | 94 | body { 95 | /* Margin bottom by footer height */ 96 | margin-bottom: 60px; 97 | } 98 | 99 | .wrapper { 100 | position: relative; 101 | min-height: 100%; 102 | padding-bottom: 50px; 103 | box-sizing: border-box; 104 | } 105 | 106 | .footer { 107 | width: 100%; 108 | height:50px; /* footer的高度一定要是固定值*/ 109 | position:absolute; 110 | bottom:0px; 111 | left:0px; 112 | } 113 | 114 | 115 | -------------------------------------------------------------------------------- /app/static/images/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/static/images/default.jpg -------------------------------------------------------------------------------- /app/static/js/bar_chart.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Administrator on 2018/7/10. 3 | */ 4 | 5 | function show_bar(title, sub_title, id, code, field) { 6 | var dom = document.getElementById(id); 7 | var myChart = echarts.init(dom); 8 | var app = {}; 9 | app.title = code; 10 | 11 | var option = { 12 | title: { 13 | text: title, 14 | subtext: sub_title 15 | }, 16 | color: ['#3398DB'], 17 | tooltip: { 18 | trigger: 'axis', 19 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 20 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 21 | } 22 | }, 23 | grid: { 24 | left: '3%', 25 | right: '4%', 26 | bottom: '3%', 27 | containLabel: true 28 | }, 29 | xAxis: [ 30 | { 31 | type: 'category', 32 | data: [], 33 | axisTick: { 34 | alignWithLabel: true 35 | } 36 | } 37 | ], 38 | yAxis: [ 39 | { 40 | type: 'value' 41 | } 42 | ], 43 | series: [ 44 | { 45 | name: title, 46 | type: 'bar', 47 | barWidth: '60%', 48 | data: [] 49 | } 50 | ] 51 | }; 52 | 53 | if (option && typeof option === "object") { 54 | myChart.setOption(option, true); 55 | } 56 | 57 | function splitData(rawData) { 58 | var categoryData = []; 59 | var values = []; 60 | for (var i = 0; i < rawData.length; i++) { 61 | categoryData.push(rawData[i][0]); 62 | values.push(rawData[i][1]) 63 | } 64 | return { 65 | categoryData: categoryData, 66 | values: values 67 | }; 68 | } 69 | 70 | // 异步加载数据 71 | $.get('/bar/' + code + '/' + field).done(function (rawData1) { 72 | var rawData = splitData(rawData1.rows) 73 | 74 | // 填入数据 75 | myChart.setOption({ 76 | xAxis: { 77 | data: rawData.categoryData 78 | }, 79 | series: [ 80 | { 81 | data: rawData.values 82 | } 83 | ] 84 | }); 85 | }); 86 | } -------------------------------------------------------------------------------- /app/static/js/basic_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Administrator on 2018/7/9. 3 | */ 4 | (function () { 5 | function init(table,url,params,titles,formatters,hasCheckbox,toolbar) { 6 | $(table).bootstrapTable({ 7 | url: url, //请求后台的URL(*) 8 | method: "get", 9 | dataType: "json", 10 | pagination: true, //前端处理分页 11 | singleSelect: false,//是否只能单选 12 | search: false, //显示搜索框,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大 13 | toolbar: toolbar, //工具按钮用哪个容器 14 | cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*) 15 | pageNumber: 1, //初始化加载第10页,默认第一页 16 | pageSize: 20, //每页的记录行数(*) 17 | pageList: [10, 20, 50, 100], //可供选择的每页的行数(*) 18 | strictSearch: true,//设置为 true启用 全匹配搜索,false为模糊搜索 19 | showColumns: true, //显示内容列下拉框 20 | showRefresh: true, //显示刷新按钮 21 | minimumCountColumns: 2, //当列数小于此值时,将隐藏内容列下拉框 22 | clickToSelect: true, //设置true, 将在点击某行时,自动勾选radiobox 和 checkbox 23 | uniqueId: "id", //每一行的唯一标识,一般为主键列 24 | cardView: false, //是否显示详细视图 25 | sidePagination: "client", //分页方式:client客户端分页,server服务端分页(*) 26 | responseHandler: function(data){ 27 | window.jsonData = data; 28 | return data.rows; 29 | }, 30 | 31 | queryParams: function(params) { 32 | params["labels"] = $("#filterLabels").val(); 33 | return params; 34 | }, 35 | 36 | formatLoadingMessage: function(){ 37 | return "请稍等,正在加载中。。。"; 38 | }, 39 | 40 | formatNoMatches: function(){ 41 | return "没有相关的匹配结果"; 42 | }, 43 | 44 | columns: createCols(params,titles,formatters,hasCheckbox) 45 | }); 46 | } 47 | 48 | function createCols(params,titles,formatters,hasCheckbox) { 49 | if(params.length!=titles.length) 50 | return null; 51 | var arr = []; 52 | if(hasCheckbox) { 53 | var objc = {}; 54 | objc.checkbox = true; 55 | arr.push(objc); 56 | } 57 | for(var i = 0;ipageSize,offset->pageNumber,search->searchText,sort->sortName(字段),order->sortOrder('asc'或'desc') 72 | function queryParams(params) { 73 | return { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 74 | limit: params.limit, //页面大小 75 | offset: params.offset //页码 76 | //name: $("#txt_name").val()//关键字查询 77 | }; 78 | } 79 | 80 | // 传'#table' 81 | createBootstrapTable = function (table,url,params,titles,formatters,hasCheckbox,toolbar) { 82 | init(table,url,params,titles,formatters,hasCheckbox,toolbar); 83 | } 84 | 85 | })(); 86 | -------------------------------------------------------------------------------- /app/static/js/candlestick_chart.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Administrator on 2018/7/3. 3 | */ 4 | function show_chart(code) { 5 | var dom = document.getElementById("candlestick"); 6 | var myChart = echarts.init(dom); 7 | var app = {}; 8 | app.title = code; 9 | 10 | function calculateMA(dayCount, data) { 11 | var result = []; 12 | for (var i = 0, len = data.length; i < len; i++) { 13 | if (i < dayCount) { 14 | result.push('-'); 15 | continue; 16 | } 17 | var sum = 0; 18 | for (var j = 0; j < dayCount; j++) { 19 | sum += data[i - j][1]; 20 | } 21 | var avg = sum / dayCount 22 | result.push(avg.toFixed(2)); 23 | } 24 | return result; 25 | } 26 | 27 | var option = { 28 | backgroundColor: '#21202D', 29 | legend: { 30 | data: ['日K'], 31 | inactiveColor: '#777', 32 | textStyle: { 33 | color: '#fff' 34 | } 35 | }, 36 | tooltip: { 37 | trigger: 'axis', 38 | axisPointer: { 39 | animation: false, 40 | type: 'cross', 41 | lineStyle: { 42 | color: '#376df4', 43 | width: 2, 44 | opacity: 1 45 | } 46 | } 47 | }, 48 | xAxis: { 49 | type: 'category', 50 | data: [], 51 | axisLine: {lineStyle: {color: '#8392A5'}} 52 | }, 53 | yAxis: { 54 | scale: true, 55 | axisLine: {lineStyle: {color: '#8392A5'}}, 56 | splitLine: {show: false} 57 | }, 58 | grid: { 59 | bottom: 80 60 | }, 61 | animation: false, 62 | series: [ 63 | { 64 | type: 'candlestick', 65 | name: '日K', 66 | data: [], 67 | itemStyle: { 68 | normal: { 69 | color: '#FD1050', 70 | color0: '#0CF49B', 71 | borderColor: '#FD1050', 72 | borderColor0: '#0CF49B' 73 | } 74 | } 75 | }, 76 | { 77 | name: 'MA5', 78 | type: 'line', 79 | data: [], 80 | smooth: true, 81 | showSymbol: false, 82 | lineStyle: { 83 | normal: { 84 | width: 1 85 | } 86 | } 87 | }, 88 | { 89 | name: 'MA10', 90 | type: 'line', 91 | data: [], 92 | smooth: true, 93 | showSymbol: false, 94 | lineStyle: { 95 | normal: { 96 | width: 1 97 | } 98 | } 99 | }, 100 | { 101 | name: 'MA20', 102 | type: 'line', 103 | data: [], 104 | smooth: true, 105 | showSymbol: false, 106 | lineStyle: { 107 | normal: { 108 | width: 1 109 | } 110 | } 111 | } 112 | ] 113 | }; 114 | 115 | 116 | if (option && typeof option === "object") { 117 | myChart.setOption(option, true); 118 | } 119 | 120 | function splitData(rawData) { 121 | var categoryData = []; 122 | var values = []; 123 | for (var i = 0; i < rawData.length; i++) { 124 | categoryData.push(rawData[i].splice(0, 1)[0]); 125 | values.push(rawData[i]) 126 | } 127 | return { 128 | categoryData: categoryData, 129 | values: values 130 | }; 131 | } 132 | 133 | // 异步加载数据 134 | $.get('/candlestick/' + code + '/hq').done(function (rawData1) { 135 | var rawData = splitData(rawData1.rows); 136 | 137 | // 填入数据 138 | myChart.setOption({ 139 | xAxis: { 140 | data: rawData.categoryData 141 | }, 142 | dataZoom: [{ 143 | type: 'inside', 144 | startValue: Math.max(rawData.categoryData.length-100, 0), 145 | endValue: rawData.categoryData.length 146 | }, { 147 | show: true, 148 | type: 'slider', 149 | y: '90%', 150 | startValue: Math.max(rawData.categoryData.length-100, 0), 151 | endValue: rawData.categoryData.length 152 | }], 153 | series: [ 154 | { 155 | name: '日K', 156 | data: rawData.values 157 | }, 158 | { 159 | name: 'MA5', 160 | data: calculateMA(5, rawData.values) 161 | }, 162 | { 163 | name: 'MA10', 164 | data: calculateMA(10, rawData.values) 165 | }, 166 | { 167 | name: 'MA20', 168 | data: calculateMA(20, rawData.values) 169 | } 170 | ] 171 | }); 172 | }); 173 | } 174 | -------------------------------------------------------------------------------- /app/static/js/my_app_code.js: -------------------------------------------------------------------------------- 1 | var $td = null; 2 | 3 | function editInfo(obj){ 4 | $td= $(obj).parents('tr').children('td'); 5 | var paramName = $td.eq(2).text(); 6 | var paramCode = $td.eq(1).text(); 7 | var paramLabels = $td.eq(10).text(); 8 | 9 | //向模态框中传值 10 | $('#code').text(paramCode) 11 | 12 | $('#labels').val(paramLabels); 13 | 14 | $('#myModal').modal('show'); 15 | } 16 | 17 | //提交更改 18 | function update() { 19 | //获取模态框数据 20 | var code = $('#code').text(); 21 | var labels = $('#labels').val(); 22 | 23 | if ($td != null) { 24 | $td.eq(10).text(labels); 25 | } 26 | /* 27 | $.ajax({ 28 | type: "post", 29 | url: "/dataFromAjax", 30 | data: "code=" + code + "&labels=" + labels, 31 | dataType: 'json', 32 | success: function(result) { 33 | $('#myModal').modal('hide'); 34 | } 35 | }); 36 | */ 37 | $.post("/dataFromAjax", {"code":code,"labels":labels}, function(data){ 38 | $('#myModal').modal('hide'); 39 | },"json"); 40 | } 41 | 42 | function filterInfo(){ 43 | 44 | $('filterModal').modal('show'); 45 | } 46 | 47 | //提交过滤 48 | function filter() { 49 | //获取模态框数据 50 | var labels = $('#labels').val(); 51 | 52 | 53 | } 54 | 55 | 56 | 57 | //删除行;(obj代表连接对象) 58 | function removeParam(obj) { 59 | alert("delte tr"); 60 | var $td= $(obj).parents('tr').children('td'); 61 | var paramName = $td.eq(0).text(); 62 | var paramCode = $td.eq(1).text(); 63 | //在js端删除一整行数据 64 | $(obj).parent().parent().remove(); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /app/static/js/self_selected_stock_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Administrator on 2018/7/9. 3 | */ 4 | var $td = null; 5 | var curr_index = null; 6 | 7 | function editLabel(obj, index) { 8 | $td = $(obj).parents('tr').children('td'); 9 | var paramName = $td.eq(2).text(); 10 | var paramCode = $td.eq(1).text(); 11 | var paramLabels = $td.eq(13).text(); 12 | 13 | curr_index = index; 14 | 15 | //向模态框中传值 16 | $('#stock_code').text(paramCode) 17 | 18 | $('#stock_name').text(paramName) 19 | 20 | $('#labels').val(paramLabels); 21 | 22 | $('#myModal').modal('show'); 23 | } 24 | 25 | function chart_formatter(value, row, index) { 26 | var d = '' + value + ' '; 27 | return d; 28 | } 29 | 30 | function labels_formatter(value, row, index, field) { 31 | var a = null; 32 | if (row.labels == null || row.labels == "") 33 | a = '' 34 | else 35 | a = '' + row.labels + '' 36 | return a; 37 | } 38 | 39 | function operations_formatter(value, row, index, field) { 40 | var b = ' '; 41 | return b; 42 | } 43 | 44 | $(function () { 45 | $('#updateButton').click(function () { 46 | var code = $('#stock_code').text(); 47 | var labels = $('#labels').val(); 48 | 49 | $('#table').bootstrapTable('updateCell', { 50 | index: curr_index, 51 | field: 'labels', 52 | value: labels 53 | }); 54 | 55 | $.post("/self_selected_stock/update_labels", {"code": code, "labels": labels}, function (data) { 56 | $('#myModal').modal('hide'); 57 | }, "json"); 58 | 59 | }); 60 | }) 61 | 62 | 63 | function deleteItem(obj, index) { 64 | $td = $(obj).parents('tr').children('td'); 65 | var paramCode = $td.eq(1).text(); 66 | 67 | if (confirm('确认删除自选?')) { 68 | $.post("/self_selected_stock/delete", {"code": paramCode}, function (data) { 69 | $('#table').bootstrapTable('remove', { 70 | field: 'code', 71 | values: [paramCode] 72 | }); 73 | }, "json"); 74 | } 75 | } 76 | 77 | function show(code, name) { 78 | $('#instrument').html('' + code + '' + name + ''); 79 | 80 | $('#chartModel').modal('show'); 81 | 82 | show_chart(code) 83 | } 84 | -------------------------------------------------------------------------------- /app/stock/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/stock/__init__.py -------------------------------------------------------------------------------- /app/stock/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from flask import request, jsonify, Blueprint, render_template, current_app 5 | from flask_login import login_required, current_user 6 | import csv 7 | import os 8 | import time 9 | import json 10 | from app import db 11 | from app.models import SelfSelectedStock 12 | from app.decorators import check_confirmed 13 | from app import analyzer 14 | from app.util import model_to_json 15 | 16 | import sys #reload()之前必须要引入模块 17 | reload(sys) 18 | sys.setdefaultencoding('utf-8') 19 | 20 | stock_blueprint = Blueprint('stock', __name__,) 21 | 22 | 23 | # 处理市盈率排行的ajax数据请求 24 | @stock_blueprint.route('//data', methods=['POST', 'GET']) 25 | #@login_required 26 | def get_stock_data(path): 27 | print("get_stock_data") 28 | 29 | info = request.values 30 | limit = info.get('limit', 10) # 每页显示的条数 31 | offset = info.get('offset', 0) # 分片数,(页码-1)*limit,它表示一段数据的起点 32 | 33 | data = [] 34 | 35 | if (path == 'pe'): 36 | csv_filename = "stock_pe.csv" 37 | title = "市盈率排名" 38 | elif (path == 'pb'): 39 | csv_filename = "stock_pb.csv" 40 | title = "市净率排名" 41 | elif (path == 'roe'): 42 | csv_filename = "stock_roe.csv" 43 | title = "净资产收益率排名" 44 | elif (path == 'divi'): 45 | csv_filename = "stock_dividence.csv" 46 | title = "股息率排名" 47 | else: 48 | return jsonify({'total': len(data), 'rows': data}) 49 | 50 | pic_path = current_app.config['RESULT_PATH'] + '/' + csv_filename 51 | 52 | filemt = time.localtime(os.stat(pic_path).st_mtime) 53 | #print time.strftime("%Y-%m-%d", filemt) 54 | 55 | # 读取csv至字典 56 | csvFile = open(pic_path, "r") 57 | 58 | index = 1 59 | for row in csv.DictReader(csvFile): 60 | row['id'] = index 61 | row['updateTime'] = time.strftime("%Y-%m-%d", filemt) 62 | index += 1 63 | data.append(row) 64 | 65 | if current_user.is_anonymous and index > 20: 66 | break 67 | 68 | #return jsonify({'total': len(data), 'rows': data[int(offset):(int(offset) + int(limit))]}) 69 | return jsonify({'total': len(data), 'rows': data}) 70 | 71 | 72 | # 处理首页的导航 73 | @stock_blueprint.route('/', methods=['GET', 'POST']) 74 | #@login_required 75 | #@check_confirmed 76 | def list_stock(path): 77 | template_filename = 'stock/rank.html' 78 | 79 | title = "团赢数据" 80 | if path == 'pe': 81 | title = "市盈率排名" 82 | elif path == 'pb': 83 | title = "市净率排名" 84 | elif path == 'roe': 85 | title = "净资产收益率排名" 86 | elif path == 'divi': 87 | title = "股息率排名" 88 | elif path == 'business': 89 | title = "精选股票" 90 | template_filename = "business/business.html" 91 | else: 92 | return render_template('errors/404.html') 93 | 94 | return render_template(template_filename, title=title, path=path) 95 | 96 | 97 | # 处理k线图 98 | @stock_blueprint.route('/candlestick/', methods=['GET', 'POST']) 99 | def show_stock(code): 100 | template_filename = 'common/candlestick.html' 101 | 102 | return render_template(template_filename, title=code) 103 | 104 | 105 | # 获取历史行情数据 106 | @stock_blueprint.route('/candlestick//hq', methods=['GET', 'POST']) 107 | def get_history_quotation(code): 108 | print("get_history_quotation: " + code) 109 | data = [] 110 | 111 | pic_path = current_app.config['DAY_FILE_PATH'] + '/' + code + '.csv' 112 | 113 | filemt = time.localtime(os.stat(pic_path).st_mtime) 114 | #print time.strftime("%Y-%m-%d", filemt) 115 | 116 | # 读取csv至字典 117 | csv_file = open(pic_path, "r") 118 | 119 | for row in csv.DictReader(csv_file): 120 | item = [] 121 | item.append(row['date']) 122 | try: 123 | item.append(float(row['open'])) 124 | item.append(float(row['close'])) 125 | item.append(float(row['low'])) 126 | item.append(float(row['high'])) 127 | except ValueError: 128 | continue 129 | data.append(item) 130 | 131 | quot = analyzer.get_quotation(code) 132 | 133 | if quot: 134 | d = quot['update_time'].split()[0] 135 | 136 | # 如果当天的k先不存在,则取实时行情补上 137 | if len(data) > 0 and data[len(data)-1][0] != d: 138 | last_item = [] 139 | last_item.append(d) 140 | last_item.append(float(quot['open'])) 141 | last_item.append(float(quot['trade'])) 142 | last_item.append(float(quot['low'])) 143 | last_item.append(float(quot['high'])) 144 | 145 | data.append(last_item) 146 | 147 | #del data[:-100] 148 | 149 | return jsonify({'rows': data, 'updateTime': time.strftime("%Y-%m-%d", filemt)}) 150 | 151 | 152 | # 增加自选股 153 | @stock_blueprint.route('/stock/add_self_selected_stock', methods=['POST']) 154 | @login_required 155 | def add_self_selected_stock(): 156 | code = request.form.get('code') 157 | 158 | email = current_user.email 159 | 160 | stock = db.session.query(SelfSelectedStock).filter_by(email=email, code=code).first() 161 | 162 | if stock is None: 163 | stock = SelfSelectedStock(current_user.email, code=code, labels='') 164 | 165 | db.session.add(stock) 166 | else: 167 | stock.deleted = False 168 | 169 | db.session.commit() 170 | 171 | myClassJson = stock.to_json() 172 | 173 | return jsonify(myClassJson) 174 | 175 | -------------------------------------------------------------------------------- /app/strategy_analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/strategy_analysis/__init__.py -------------------------------------------------------------------------------- /app/strategy_analysis/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from flask import request, jsonify, Blueprint, render_template, current_app 5 | from flask_login import login_required, current_user 6 | import csv 7 | 8 | import sys #reload()之前必须要引入模块 9 | reload(sys) 10 | sys.setdefaultencoding('utf-8') 11 | 12 | strategy_analysis_blueprint = Blueprint('strategy_analysis', __name__,) 13 | 14 | 15 | # 处理市盈率排行的ajax数据请求 16 | @strategy_analysis_blueprint.route('//data', methods=['POST', 'GET']) 17 | #@login_required 18 | def get_data(path): 19 | print("get stock data") 20 | 21 | info = request.values 22 | limit = info.get('limit', 10) # 每页显示的条数 23 | offset = info.get('offset', 0) # 分片数,(页码-1)*limit,它表示一段数据的起点 24 | 25 | data = [] 26 | 27 | if (path == 'pe'): 28 | csv_filename = "stock_pe.csv" 29 | title = "市盈率排名" 30 | elif (path == 'pb'): 31 | csv_filename = "stock_pb.csv" 32 | title = "市净率排名" 33 | elif (path == 'roe'): 34 | csv_filename = "stock_roe.csv" 35 | title = "净资产收益率排名" 36 | elif (path == 'divi'): 37 | csv_filename = "stock_dividence.csv" 38 | title = "股息率排名" 39 | else: 40 | return jsonify({'total': len(data), 'rows': data}) 41 | 42 | pic_path = current_app.config['RESULT_PATH'] + '/' + csv_filename 43 | 44 | # 读取csv至字典 45 | csvFile = open(pic_path, "r") 46 | 47 | index = 1 48 | for row in csv.DictReader(csvFile): 49 | row['id'] = index 50 | index += 1 51 | data.append(row) 52 | 53 | #return jsonify({'total': len(data), 'rows': data[int(offset):(int(offset) + int(limit))]}) 54 | return jsonify({'total': len(data), 'rows': data}) 55 | 56 | # 处理首页的导航 57 | @strategy_analysis_blueprint.route('/strategy', methods=['GET', 'POST']) 58 | def index(): 59 | return render_template('errors/400.html') 60 | 61 | -------------------------------------------------------------------------------- /app/technical_analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/technical_analysis/__init__.py -------------------------------------------------------------------------------- /app/technical_analysis/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from flask import request, jsonify, Blueprint, render_template, flash, current_app 5 | from flask_login import current_user 6 | import csv 7 | import os 8 | import time 9 | import datetime 10 | from flask_login import login_required 11 | from app.decorators import check_confirmed 12 | 13 | 14 | import sys #reload()之前必须要引入模块 15 | reload(sys) 16 | sys.setdefaultencoding('utf-8') 17 | 18 | 19 | BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | sys.path.append(BASE_DIR) 21 | 22 | technical_analysis_blueprint = Blueprint('technical_analysis', __name__,) 23 | 24 | 25 | # 突破历史最高价 26 | @technical_analysis_blueprint.route('/tech//data', methods=['POST', 'GET']) 27 | #@login_required 28 | def get_data(path): 29 | print("get technical analysis data") 30 | 31 | info = request.values 32 | limit = info.get('limit',10) # 每页显示的条数 33 | offset = info.get('offset',0) # 分片数,(页码-1)*limit,它表示一段数据的起点 34 | 35 | data = [] 36 | 37 | if path == 'highest': 38 | csv_filename = "highest_in_history.csv" 39 | elif path == 'lowest': 40 | csv_filename = "lowest_in_history.csv" 41 | elif path == 'ma_long': 42 | csv_filename = "ma_long.csv" 43 | elif path == 'break_ma': 44 | csv_filename = "break_ma.csv" 45 | elif path == 'above_ma': 46 | csv_filename = "above_ma.csv" 47 | else: 48 | return jsonify({'total': len(data), 'rows': data}) 49 | 50 | pic_path = current_app.config['RESULT_PATH'] + '/' + csv_filename 51 | 52 | filemt = time.localtime(os.stat(pic_path).st_mtime) 53 | 54 | # 读取csv至字典 55 | csvFile = open(pic_path, "r") 56 | 57 | index = 1 58 | for row in csv.DictReader(csvFile): 59 | row['id']= index 60 | row['updateTime'] = time.strftime("%Y-%m-%d", filemt) 61 | index += 1 62 | data.append(row) 63 | 64 | if current_user.is_anonymous and index > 10: 65 | break 66 | 67 | #return jsonify({'total': len(data), 'rows': data[int(offset):(int(offset) + int(limit))]}) 68 | return jsonify({'total': len(data), 'rows': data}) 69 | 70 | 71 | # 处理首页的导航 72 | @technical_analysis_blueprint.route('/tech/', methods=['GET', 'POST']) 73 | #@login_required 74 | #@check_confirmed 75 | def index(path): 76 | if path == 'highest': 77 | flash('股价创历史新高的股票列表','历史新高') 78 | title = "历史新高" 79 | elif path == 'lowest': 80 | flash('股价创历史新低的股票列表', '历史新低') 81 | title = "历史新低" 82 | elif path == 'ma_long': 83 | flash('5日、10日、20日均线呈多头排列的个股列表','均线多头') 84 | title = "均线多头" 85 | elif path == 'break_ma': 86 | flash('突破20日均线的个股列表','突破均线') 87 | title = "突破均线" 88 | elif path == 'above_ma': 89 | flash('年线以上的个股列表','年线以上') 90 | title = "年线以上" 91 | elif path == 'filter': 92 | title = "涨跌幅分析" 93 | return render_template('technical_analysis/filter.html', title=title, path=path) 94 | else: 95 | return render_template('errors/400.html') 96 | 97 | return render_template('technical_analysis/rank.html', title=title, path=path) 98 | 99 | 100 | @technical_analysis_blueprint.route('/tech/filter/data', methods=['POST', 'GET']) 101 | #@login_required 102 | def get_filter_data(): 103 | print("get filter analysis data") 104 | 105 | info = request.values 106 | 107 | print info 108 | 109 | PERIODS = {u'近一周': 7, u'近一月': 30, u'近三月': 30*3, u'近半年': 30*6, u'近一年': 30*12} 110 | 111 | begin_date = request.values.get('begin_date', '2018-01-01') 112 | end_date = request.values.get('end_date', '2018-12-01') 113 | 114 | days = "".join(request.values.get('days', u'近一周').split()) 115 | if days is None or len(days) <= 0: 116 | days = u'近一周' 117 | 118 | period = PERIODS.get(days, 7) 119 | 120 | low = request.values.get('low', -30) 121 | high = request.values.get('high', 0) 122 | 123 | print "begin date: {}, end date: {}, days: {}, low: {}, high: {}".format(begin_date, end_date, days, low, high) 124 | 125 | data = [] 126 | 127 | pic_path = current_app.config['RESULT_PATH'] + '/price_change.csv' 128 | 129 | filemt = time.localtime(os.stat(pic_path).st_mtime) 130 | 131 | # 读取csv至字典 132 | csvFile = open(pic_path, "r") 133 | 134 | index = 1 135 | for row in csv.DictReader(csvFile): 136 | rate = float(row['rate'+str(period)]) 137 | if (rate > -9999) and (rate >= float(low)) and (rate <= float(high)): 138 | row['id'] = index 139 | row['rate'] = rate 140 | row['updateTime'] = time.strftime("%Y-%m-%d", filemt) 141 | index += 1 142 | data.append(row) 143 | 144 | return jsonify({'total': len(data), 'rows': data}) 145 | -------------------------------------------------------------------------------- /app/templates/_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 团赢数据 6 | 7 | 8 | 9 | 10 | {% block css %}{% endblock %} 11 | 12 | 13 | 14 | {% include "navigation.html" %} 15 | 16 |
17 | 18 |
19 | 20 | 21 | {% with messages = get_flashed_messages(with_categories=true) %} 22 | {% if messages %} 23 |
24 |
25 | {% for category, message in messages %} 26 |
27 | × 28 | {{message}} 29 |
30 | {% endfor %} 31 |
32 |
33 | {% endif %} 34 | {% endwith %} 35 | 36 | 37 | {% block content %}{% endblock %} 38 | 39 | {% block page_content %}{% endblock %} 40 | 41 |
42 | 43 | 44 | {% if error %} 45 |

Error: {{ error }}

46 | {% endif %} 47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | {% block js %}{% endblock %} 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | {% block title %}团赢数据{% endblock %} 3 | 4 | 5 | {% block styles %} 6 | {{super()}} 7 | 8 | {% endblock styles %} 9 | 10 | 11 | {% block navbar %} 12 | {% include "navigation.html" %} 13 | {% endblock navbar %} 14 | 15 | {% block content %} 16 |
17 | {% with messages = get_flashed_messages() %} 18 | {% if messages %} 19 |
    20 | {% for message in messages %} 21 |
  • {{ message }}
  • 22 | {% endfor %} 23 |
24 | {% endif %} 25 | {% endwith %} 26 | 27 | {% block page_content %}{% endblock %} 28 | 29 | 30 | 31 |
32 |
Copyright © 2018 33 | 团赢数据 All Rights Reserved. 34 |
35 |
36 | 37 |
38 | {% endblock %} 39 | 40 | 41 | 42 | {% block scripts %} 43 | 44 | {{ super() }} 45 | 49 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/business/business.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} {{ title }} {% endblock %} 3 | 4 | {% block page_content %} 5 | {% if current_user.is_authenticated %} 6 | 7 |
8 | 9 | 21 | {% else %} 22 |

登录后查看

23 | {% endif %} 24 | {% endblock %} 25 | 26 | {% block scripts %} 27 | {{super()}} 28 | 29 | 30 | 31 | 32 | 33 | 64 | 65 | {% endblock %} 66 | -------------------------------------------------------------------------------- /app/templates/business/business2.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} {{ title }} {% endblock %} 3 | 4 | {% block page_content %} 5 |
6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 |
15 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
序号代码名称换手率市盈率市净率流通市值(万元)ROE股价10周线支撑位标签操作
47 | 48 | 66 | {% endblock %} 67 | 68 | {% block scripts %} 69 | {{super()}} 70 | 71 | 72 | 73 | 161 | {% endblock %} 162 | -------------------------------------------------------------------------------- /app/templates/cluster_analysis/cluster.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} {{ title }} {% endblock %} 3 | 4 | {% block page_content %} 5 | 6 | 7 |
8 | 9 | {% endblock %} 10 | 11 | {% block scripts %} 12 | {{super()}} 13 | 14 | 15 | 16 | 17 | 18 | 57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /app/templates/cluster_analysis/industry_cluster.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} {{ title }} {% endblock %} 3 | 4 | {% block page_content %} 5 |
6 |
7 |
8 | 9 |
10 |
11 | 13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 | 23 | {% endblock %} 24 | 25 | {% block scripts %} 26 | {{super()}} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 95 | 96 | 97 | {% endblock %} 98 | -------------------------------------------------------------------------------- /app/templates/common/candlestick.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block page_content %} 4 |
5 | {% endblock %} 6 | 7 | {% block scripts %} 8 | {{super()}} 9 | 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/errors/400.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block page_content %} 3 |

页面正在开发中,敬请期待......

4 |

返回主页?

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/templates/errors/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block page_content %} 3 |

403

4 |

错误

5 |

返回主页?

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /app/templates/errors/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block page_content %} 3 |

404

4 |

找不到页面

5 |

返回 主页?

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /app/templates/errors/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block page_content %} 3 |

500

4 |

服务端错误

5 |

返回 主页?

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /app/templates/main/result.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}欢迎来到团赢数据{% endblock %} 3 | 4 | {% block page_content %} 5 |

{{ quot.code }} {{ quot.name }}

6 |

{{ quot.trade }} {{ "%.2f"|format(quot.trade|float - quot.settlement|float) }} {{ "%.2f"|format(quot.changepercent|float) }}%

7 |

更新时间: {{ quot.update_time }}

8 | 9 | 10 | 11 | 12 | 13 | 14 |
最高:{{ quot.high }}今开:{{ quot.open }}成交量:{{ "%d"|format(quot.volume|int/100) }}手
最低:{{ quot.low }}昨收:{{ quot.settlement }}成交额:{{ "%.2f"|format(quot.amount|int/10000) }}万
换手:{{ "%.2f"|format(quot.turnoverratio|float) }}%市盈率:{{ "%.2f"|format(quot.per|float) }}市净率:{{ "%.2f"|format(quot.pb|float) }}
流通市值:{{ "%.2f"|format(quot.mktcap|int/10000) }}亿总市值:{{ "%.2f"|format(quot.nmc|int/10000) }}亿
15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | {% endblock %} 23 | 24 | {% block scripts %} 25 | {{super()}} 26 | 27 | 28 | 29 | 38 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/main/welcome.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}欢迎来到团赢数据{% endblock %} 3 | {% block menubar %} 4 | 11 | 12 | {% endblock %} 13 | 14 | {% block page_content %} 15 | 16 | 17 | 18 | {% endblock %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/templates/navigation.html: -------------------------------------------------------------------------------- 1 | 2 | 87 | -------------------------------------------------------------------------------- /app/templates/self_selected_stock/self_selected_stock.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} {{ title }} {% endblock %} 3 | 4 | {% block page_content %} 5 | {% if current_user.is_authenticated %} 6 |
7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 | 37 | 38 | 50 | {% else %} 51 |

登录后查看

52 | {% endif %} 53 | {% endblock %} 54 | 55 | {% block scripts %} 56 | {{super()}} 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 94 | {% endblock %} 95 | -------------------------------------------------------------------------------- /app/templates/strategy_analysis/rank.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block page_content %} 4 | 5 |
6 | {% endblock %} 7 | 8 | {% block scripts %} 9 | {{super()}} 10 | 11 | 97 | {% endblock %} 98 | -------------------------------------------------------------------------------- /app/templates/technical_analysis/rank.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block page_content %} 4 |
5 | 6 |
7 |
8 | 20 | {% endblock %} 21 | 22 | {% block scripts %} 23 | {{super()}} 24 | 25 | 26 | 27 | 28 | 134 | {% endblock %} 135 | -------------------------------------------------------------------------------- /app/templates/user/activate.html: -------------------------------------------------------------------------------- 1 |

亲,感谢注册团赢数据!请点击以下链接激活你的账号:

2 |

{{ confirm_url }}

3 |
4 |

合作愉快!

-------------------------------------------------------------------------------- /app/templates/user/forgot.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block page_content %} 5 |
6 |

忘记密码?

7 |
8 | 23 | {{ wtf.quick_form(form) }} 24 |
25 | 29 |
30 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user/forgot_new.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block page_content %} 5 |
6 |

忘记密码?

7 |
8 | 35 | {{ wtf.quick_form(form) }} 36 |
37 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block page_content %} 5 | 6 |
7 |

请登录

8 |
9 | 43 | {{ wtf.quick_form(form) }} 44 |
45 | 49 |
50 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_content %} 4 | 5 |

你的信息

6 |
7 | 8 | {% if current_user.is_authenticated() %} 9 |

电子邮件地址: {{current_user.email}}

10 | {% endif %} 11 | 12 |

改变密码

13 |
14 |
15 | {{ form.csrf_token }} 16 |

17 | {{ form.password(placeholder="password") }} 18 | 19 | {% if form.password.errors %} 20 | {% for error in form.password.errors %} 21 | {{ error }} 22 | {% endfor %} 23 | {% endif %} 24 | 25 |

26 |

27 | {{ form.confirm(placeholder="confirm") }} 28 | 29 | {% if form.confirm.errors %} 30 | {% for error in form.confirm.errors %} 31 | {{ error }} 32 | {% endfor %} 33 | {% endif %} 34 | 35 |

36 | 37 |
38 | 39 | 40 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block page_content %} 5 |
6 |

请注册

7 |
8 | 45 | {{ wtf.quick_form(form) }} 46 |

47 |

已经有帐号? 登录.

48 |
49 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/user/reset.html: -------------------------------------------------------------------------------- 1 |

亲! 有人请求重置帐号密码 {{ username }}.

2 |

点击以下链接确认重置密码:

3 |

{{ reset_url }}

4 |

如果你没有请求重置密码,请忽略此邮件.

5 |
6 |

合作愉快!

-------------------------------------------------------------------------------- /app/templates/user/unconfirmed.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_content %} 4 | 5 |

亲,请检查您的注册邮箱, 您应该受到一封帐号确认邮件.

6 |

没有收到邮件? 重发.

7 | 8 | {% endblock %} -------------------------------------------------------------------------------- /app/token.py: -------------------------------------------------------------------------------- 1 | # project/token.py 2 | 3 | from itsdangerous import URLSafeTimedSerializer 4 | 5 | from app import app 6 | 7 | 8 | def generate_confirmation_token(email): 9 | serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) 10 | return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT']) 11 | 12 | 13 | def confirm_token(token, expiration=3600): 14 | serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) 15 | try: 16 | email = serializer.loads( 17 | token, 18 | salt=app.config['SECURITY_PASSWORD_SALT'], 19 | max_age=expiration 20 | ) 21 | except: 22 | return False 23 | return email 24 | -------------------------------------------------------------------------------- /app/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/app/user/__init__.py -------------------------------------------------------------------------------- /app/user/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # project/user/forms.py 3 | 4 | 5 | from flask_wtf import FlaskForm 6 | from wtforms import PasswordField, StringField, BooleanField, SubmitField 7 | from wtforms.validators import DataRequired, Email, Length, EqualTo 8 | 9 | from app.models import User 10 | 11 | 12 | class LoginForm(FlaskForm): 13 | email = StringField('电子邮箱地址', validators=[DataRequired(), Length(1, 64), Email()]) 14 | password = PasswordField('密码', validators=[DataRequired()]) 15 | #remember_me = BooleanField('记住我') 16 | submit = SubmitField('登录') 17 | 18 | 19 | class RegisterForm(FlaskForm): 20 | email = StringField('电子邮箱地址', validators=[DataRequired(), Email(message=None), Length(min=6, max=255)]) 21 | password = PasswordField('密码', validators=[DataRequired(), Length(min=6, max=255)]) 22 | confirm = PasswordField('重复密码', validators=[DataRequired(), EqualTo('password', message='密码不相同')]) 23 | submit = SubmitField('注册') 24 | 25 | def validate(self): 26 | initial_validation = super(RegisterForm, self).validate() 27 | if not initial_validation: 28 | return False 29 | user = User.query.filter_by(email=self.email.data).first() 30 | if user: 31 | self.email.errors.append("邮箱地址已经存在") 32 | return False 33 | return True 34 | 35 | 36 | class ForgotForm(FlaskForm): 37 | email = StringField( 38 | '电子邮箱地址', 39 | validators=[DataRequired(), Email(message=None), Length(min=6, max=255)]) 40 | 41 | submit = SubmitField('发送') 42 | 43 | def validate(self): 44 | initial_validation = super(ForgotForm, self).validate() 45 | if not initial_validation: 46 | return False 47 | user = User.query.filter_by(email=self.email.data).first() 48 | if not user: 49 | self.email.errors.append("该邮箱未注册") 50 | return False 51 | return True 52 | 53 | 54 | class ChangePasswordForm(FlaskForm): 55 | password = PasswordField( 56 | '密码', 57 | validators=[DataRequired(), Length(min=6, max=255)] 58 | ) 59 | confirm = PasswordField( 60 | '重复密码', 61 | validators=[ 62 | DataRequired(), 63 | EqualTo('password', message='密码不相同') 64 | ] 65 | ) 66 | 67 | submit = SubmitField('修改') 68 | 69 | -------------------------------------------------------------------------------- /app/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # project/util.py 4 | 5 | 6 | from app import app, db 7 | from app.models import User 8 | from sqlalchemy.orm import class_mapper 9 | import json 10 | from werkzeug.utils import import_string 11 | 12 | ''' 13 | class BaseTestCase(TestCase): 14 | 15 | def create_app(self): 16 | app.config.from_object('project.config.TestingConfig') 17 | return app 18 | 19 | @classmethod 20 | def setUpClass(self): 21 | db.create_all() 22 | user = User( 23 | email="test@user.com", 24 | password="just_a_test_user", 25 | confirmed=False 26 | ) 27 | db.session.add(user) 28 | db.session.commit() 29 | 30 | @classmethod 31 | def tearDownClass(self): 32 | db.session.remove() 33 | db.drop_all() 34 | ''' 35 | 36 | # sqlalchemy对象转换为json 37 | def model_to_json(model): 38 | """Transforms a model into a dictionary which can be dumped to JSON.""" 39 | # first we get the names of all the columns on your model 40 | columns = [c.key for c in class_mapper(model.__class__).columns] 41 | # then we return their values in a dict 42 | d = dict((c, getattr(model, c)) for c in columns) 43 | return json.dumps(d) 44 | 45 | def string_to_obj(obj): 46 | if isinstance(obj, (str,)): 47 | obj = import_string(obj) 48 | 49 | return obj -------------------------------------------------------------------------------- /bin/analysis_report.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | __author__ = 'Administrator' 3 | 4 | 5 | ''' 6 | 1、所在行业的发展前景; 7 | 2、公司在行业内的发展前景,或垄断地位; 8 | 3、未来3-5年的快速增长势头,且前景明确; 9 | 4、财务稳健,没有疑问点; 10 | 5、分红扩股正常; 11 | 6、资产负债比合理; 12 | 7、现金流量正常; 13 | 8、信息披露充分; 14 | 9、价格有吸引力; 15 | ''' -------------------------------------------------------------------------------- /bin/belowmastrategy.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 1. 读取股票列表 7 | 2. 读取股票列表中每只股票的历史数据, 8 | 3. 计算移动平均线 9 | 4. 将低于移动平均线条件的股票筛选出来保存到文件 10 | 5. 将文件发送邮件到制定邮箱 11 | ''' 12 | 13 | import commondatadef 14 | import pandas as pd 15 | import matplotlib 16 | import datetime 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | from sendmail import sendmail 20 | 21 | import os 22 | import sys 23 | 24 | reload(sys) 25 | sys.setdefaultencoding('utf8') 26 | 27 | matplotlib.style.use('ggplot') 28 | 29 | ma = 60 30 | change_percent = 20 31 | 32 | # get instruments 33 | # instruments = {'code':[2681]} 34 | instruments = pd.read_csv(commondatadef.instrument_file_path, names=['code', 'name', 'industry'], dtype=str) 35 | 36 | # 37 | today = datetime.date.today() 38 | 39 | # 40 | result = pd.DataFrame() 41 | 42 | instruments.index = instruments.index.astype(int) 43 | 44 | print instruments.index 45 | 46 | index = 0 47 | for code in instruments['code']: 48 | name = instruments.loc[index, 'name'] 49 | 50 | index += 1 51 | 52 | filepath = "%s/%s.txt" % (commondatadef.dataPath, code) 53 | 54 | #print filepath 55 | 56 | stock_data = pd.read_csv(filepath, names=['date', 'open', 'high', 'low', 'close', 'volume', 'amount'], 57 | parse_dates=[0], index_col=0) 58 | 59 | col_ma = 'MA_' + str(ma) 60 | 61 | # 计算简单算术移动平均线MA - 注意:stock_data['close']为股票每天的收盘价 62 | stock_data[col_ma] = pd.ewma(stock_data['close'], ma) 63 | 64 | if len(stock_data) < 2: 65 | continue 66 | 67 | lastlast_record = stock_data.iloc[-2] 68 | 69 | last_record = stock_data.iloc[-1] 70 | last_date = stock_data.index[-1] 71 | 72 | p_change = (last_record[col_ma] - last_record['close']) * 100 / last_record['close'] 73 | if p_change > change_percent: 74 | d = today # + datetime.timedelta(days = -1) 75 | if d.strftime("%Y-%m-%d") == last_date.strftime("%Y-%m-%d"): 76 | last_record['date'] = last_date.strftime("%Y-%m-%d") 77 | last_record['name'] = name 78 | last_record['p_change'] = p_change 79 | result[code] = last_record 80 | 81 | result = result.T 82 | 83 | names = result.pop('name') 84 | result.insert(0, 'name', names) 85 | 86 | result = result.sort(['p_change'], ascending=False) 87 | 88 | outputfile = "%s/belowma_%d_%d_%s.csv" % (commondatadef.resultPath, ma, change_percent, today.strftime("%Y%m%d")) 89 | 90 | result.to_csv(outputfile, index=True, index_label=['code'], encoding='gbk') 91 | 92 | subject = "背离%d日线的股票列表" % (ma,) 93 | sendmail(subject, mailto=['38454880@qq.com'], content="Please check attachment", attachments=[outputfile]) 94 | 95 | #result.plot() 96 | #plt.show() 97 | 98 | 99 | # 将数据按照交易日期从近到远排序 100 | #stock_data.sort('date', ascending=False, inplace=True) 101 | 102 | # ========== 将算好的数据输出到csv文件 - 注意:这里请填写输出文件在您电脑中的路径 103 | #outputfile = "%s/%06d_ma_ema.txt" % (commondatadef.dataPath, code) 104 | 105 | #stock_data.to_csv(outputfile, index=False) 106 | 107 | -------------------------------------------------------------------------------- /bin/commondatadef.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | dataPath = '/home/ubuntu/stock/dataset' 6 | minPath = '/home/ubuntu/stock/min' 7 | configPath = '/home/ubuntu/stock' 8 | resultPath = '/home/ubuntu/stock/product' 9 | 10 | data_dayPath = '/home/ubuntu/stock/day' 11 | 12 | 13 | # 获取股票代码 14 | instrument_file_path = configPath + '/instruments.txt' 15 | 16 | code_list_file = configPath + '/instruments.csv' 17 | -------------------------------------------------------------------------------- /bin/datamonitor.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | __author__ = 'jerry' 4 | 5 | ''' 6 | 1. 读取文件中定义的股票代码 7 | 2. 当股票的价格变化比率超过设置的阈值时发送告警列表到制定邮箱 8 | ''' 9 | 10 | from threading import Timer 11 | import time 12 | from datetime import datetime, timedelta 13 | import os 14 | import pandas as pd 15 | import tushare as ts 16 | from sendmail import sendmail 17 | 18 | stock_list = pd.read_csv('c:/stock/monitoring_stocks.csv', encoding='gbk', dtype=str) 19 | 20 | # 21 | 22 | 23 | # 24 | def refreshquots(): 25 | str_result = '' 26 | 27 | quots = ts.get_realtime_quotes(stock_list['code']) 28 | 29 | i = 0 30 | for code in stock_list['code']: 31 | # 32 | price = float(stock_list.loc[i, 'price']) 33 | 34 | trade = float(quots.loc[i, 'price']) 35 | 36 | changeratio = (trade-price)*100/price 37 | 38 | change = stock_list.loc[i, 'change'] 39 | 40 | name = stock_list.loc[i, 'name'] 41 | 42 | if float(changeratio) > float(change): 43 | strTmp = "alarm, code: %s, name: %s, base price: %.2f, current price: %.2f, change: %.2f%%\n" % (code, name, price, trade, changeratio) 44 | str_result = str_result + strTmp 45 | #print str1 46 | else: 47 | print "no alarm, code: %s, name: %s, base price: %.2f, current price: %.2f, change: %.2f%%" % (code, name, price, trade, changeratio) 48 | 49 | i += 1 50 | 51 | # 52 | if not strTmp == '': 53 | sendmail('股票告警列表', mailto=['38454880@qq.com'], content=str_result, attachments=None) 54 | 55 | # 56 | while True: 57 | refreshquots() 58 | time.sleep(60) 59 | -------------------------------------------------------------------------------- /bin/download15khistdata.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'jerry' 4 | 5 | 6 | ''' 7 | 下载15分钟线历史数据并保存到文件 8 | ''' 9 | 10 | import tushare as ts 11 | import os,time,sys,re,datetime 12 | 13 | 14 | #输出CSV文件,其中要进行转码,不然会乱码 15 | def output_csv(df, folder, code): 16 | TODAY = datetime.date.today() 17 | CURRENTDAY=TODAY.strftime('%Y-%m-%d') 18 | reload(sys) 19 | sys.setdefaultencoding('gbk') 20 | df.to_csv(folder + code + '.csv', encoding='gbk')#选择保存 21 | 22 | # 获取股票基本信息 23 | #code,代码 24 | #name,名称 25 | #industry,所属行业 26 | #area,地区 27 | #pe,市盈率 28 | #outstanding,流通股本 29 | #totals,总股本(万) 30 | #totalAssets,总资产(万) 31 | #liquidAssets,流动资产 32 | #fixedAssets,固定资产 33 | #reserved,公积金 34 | #reservedPerShare,每股公积金 35 | #eps,每股收益 36 | #bvps,每股净资 37 | #pb,市净率 38 | #timeToMarket,上市日期 39 | def get_stock_list(): 40 | df = ts.get_stock_basics() 41 | return df 42 | 43 | 44 | # 获取历史数据 45 | def get_hist_data(df_code, ktype, folder, start): 46 | for code in df_code.index: 47 | 48 | df = ts.get_hist_data(code, ktype=ktype, start=start) 49 | 50 | if df is not None: 51 | output_csv(df, folder, code) 52 | 53 | df = get_stock_list() 54 | 55 | output_csv(df, 'C:/Stock/', 'stocklist') 56 | 57 | get_hist_data(df, '15', 'C:/Stock/data/15K/', '2013-01-01') 58 | 59 | -------------------------------------------------------------------------------- /bin/downloaddata.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 1. 读取股票列表 7 | 2. 读取上次更新的时间,从上次更新的时间开始下载股票列表中的每只股票的历史数据 8 | ''' 9 | 10 | import tushare as ts 11 | import pandas as pd 12 | import commondatadef 13 | import numpy as np 14 | import datetime 15 | from datetime import timedelta 16 | import os 17 | import sys 18 | 19 | reload(sys) 20 | sys.setdefaultencoding('utf8') 21 | 22 | print ts.__version__ 23 | 24 | # get instruments 25 | instruments = pd.read_csv(commondatadef.instrument_file_path, names=['code','name','industry'], dtype=str) 26 | 27 | # 28 | today = datetime.date.today() 29 | 30 | # 31 | def downloadstock(instrument, startdate, enddate, filepath): 32 | df = ts.get_h_data(instrument, start=startdate, end=enddate, autype='hfq') 33 | 34 | df.sort_index(inplace=True) 35 | 36 | df.to_csv(filepath, header=None) 37 | 38 | # 39 | for code in instruments['code']: 40 | filepath = "%s/%s.txt" % (commondatadef.dataPath, code) 41 | print filepath 42 | startdate = "2005-01-01" 43 | enddate = today.strftime("%Y-%m-%d") 44 | df = pd.DataFrame() 45 | if os.path.exists(filepath): 46 | df = pd.read_csv(filepath, names=['date', 'open', 'high', 'low', 'close', 'volume', 'amount'], index_col=0) 47 | last_date = datetime.datetime.strptime(df.index[-1], "%Y-%m-%d") + datetime.timedelta(days=1) 48 | startdate = last_date.strftime("%Y-%m-%d") 49 | 50 | if startdate == enddate: 51 | continue 52 | 53 | data_enddate = datetime.datetime.strptime(enddate, "%Y-%m-%d") + timedelta(days=1) 54 | 55 | print "download data, code: %s, startdate: %s, enddate: %s" % (code, startdate, data_enddate.strftime("%Y-%m-%d")) 56 | 57 | df_download = ts.get_h_data(code, start=startdate, end=data_enddate.strftime("%Y-%m-%d"), autype='hfq') 58 | 59 | if df_download is None: 60 | continue 61 | 62 | # 63 | close = df_download.pop('low') 64 | df_download.insert(2, 'low', close) 65 | 66 | df_download.sort_index(inplace=True) 67 | 68 | df_download.to_csv(filepath, header=None, mode='a') 69 | 70 | 71 | -------------------------------------------------------------------------------- /bin/downloaddata4lstm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #coding:utf-8 3 | 4 | ''' 5 | 下载历史数据用于tensorflow lstm算法计算 6 | ''' 7 | 8 | __author__ = 'Administrator' 9 | 10 | import tushare as ts 11 | import sys 12 | import datetime 13 | 14 | if len(sys.argv) < 2: 15 | print "Usage: downloaddata4lstm [code]" 16 | sys.exit(2) 17 | 18 | #代码 19 | code = sys.argv[1] 20 | 21 | df = ts.get_stock_basics() 22 | date = df.ix[code]['timeToMarket'] #上市日期YYYYMMDD 23 | 24 | if date == 0: 25 | print "Invalid timeToMarket" 26 | sys.exit(2) 27 | 28 | # 获取开始时间 29 | startDate = "%04d-%02d-%02d" % (date/10000, date/100%100, date%100) 30 | 31 | # 获得结束时间 32 | now = datetime.datetime.now() # 这是时间数组格式 33 | # 转换为指定的格式: 34 | endDate = now.strftime("%Y-%m-%d") 35 | 36 | # 下载数据 37 | df = ts.get_h_data(code, start=startDate, end=endDate) 38 | 39 | # 改为以date为索引 40 | df.sort_index(ascending=True, inplace=True) 41 | 42 | # 删除code列 43 | #df.pop('code') 44 | 45 | # 计算收盘价的5,10,20,60移动平均线 46 | df['ma5'] = df['close'].rolling(window=5).mean() 47 | df['ma10'] = df['close'].rolling(window=10).mean() 48 | df['ma20'] = df['close'].rolling(window=20).mean() 49 | df['ma60'] = df['close'].rolling(window=60).mean() 50 | 51 | # 计算成交量的5,10,20,60移动平均线 52 | df['va5'] = df['volume'].rolling(window=5).mean() 53 | df['va10'] = df['volume'].rolling(window=10).mean() 54 | df['va20'] = df['volume'].rolling(window=20).mean() 55 | df['va60'] = df['volume'].rolling(window=60).mean() 56 | 57 | # 增加label列 58 | df['label'] = df['close'] 59 | 60 | labels = df['label'] 61 | 62 | for i in range(len(labels)): 63 | if i != len(labels)-1: 64 | labels[i] = labels[i+1] 65 | 66 | filename = "./dataset/%s.csv" % (code) 67 | 68 | df.to_csv(filename) 69 | -------------------------------------------------------------------------------- /bin/downloaddaydata.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'jerry' 4 | 5 | 6 | import tushare as ts 7 | import pandas as pd 8 | import commondatadef 9 | import numpy as np 10 | import datetime 11 | from datetime import timedelta 12 | import os 13 | import sys 14 | import stock_utility as su 15 | import commondatadef as cdd 16 | import pandas as pd 17 | 18 | reload(sys) 19 | sys.setdefaultencoding('utf8') 20 | 21 | print ts.__version__ 22 | 23 | # 下载数据 24 | instruments = su.get_stock_list() 25 | 26 | # 如果下载成功,则保存 27 | if instruments is not None: 28 | print "download instruments success, save to file %s" % (cdd.code_list_file) 29 | instruments.to_csv(cdd.code_list_file) 30 | else: 31 | print "download instruments success, read from file %s" % (cdd.code_list_file) 32 | instruments = pd.read_csv(cdd.code_list_file, index_col=['code']) 33 | 34 | if instruments is None: 35 | print "instruments is empty, exit" 36 | exit() 37 | 38 | # 39 | today = datetime.date.today() 40 | 41 | # 42 | def downloadstock(instrument, startdate, enddate, filepath): 43 | df = ts.get_h_data(instrument, start=startdate, end=enddate, autype='hfq') 44 | 45 | df.sort_index(inplace=True) 46 | 47 | df.to_csv(filepath) 48 | 49 | # 是否数字 50 | def isNum(value): 51 | try: 52 | float(value) + 1 53 | except TypeError: 54 | return False 55 | except ValueError: 56 | return False 57 | else: 58 | return True 59 | 60 | def handleData(df): 61 | # 计算收盘价的5,10,20,60移动平均线 62 | df['ma5'] = df['close'].rolling(window=5).mean() 63 | df['ma10'] = df['close'].rolling(window=10).mean() 64 | df['ma20'] = df['close'].rolling(window=20).mean() 65 | df['ma60'] = df['close'].rolling(window=60).mean() 66 | 67 | # 计算成交量的5,10,20,60移动平均线 68 | df['va5'] = df['volume'].rolling(window=5).mean() 69 | df['va10'] = df['volume'].rolling(window=10).mean() 70 | df['va20'] = df['volume'].rolling(window=20).mean() 71 | df['va60'] = df['volume'].rolling(window=60).mean() 72 | 73 | return df 74 | 75 | # 增加label列 76 | df['label'] = df['close'] 77 | 78 | labels = df['label'] 79 | 80 | for i in range(len(labels)): 81 | if i != len(labels)-1: 82 | labels[i] = labels[i+1] 83 | 84 | 85 | # 86 | for code in instruments.index: 87 | filepath = "%s/%s.csv" % (commondatadef.data_dayPath, code) 88 | print "starting download %s, file path: %s" % (code, filepath) 89 | startdate = "1990-01-01" 90 | enddate = today.strftime("%Y-%m-%d") 91 | df = pd.DataFrame() 92 | if os.path.exists(filepath): 93 | df = pd.read_csv(filepath, index_col=['date']) 94 | last_date = datetime.datetime.strptime(df.index[-1], "%Y-%m-%d") + datetime.timedelta(days=1) 95 | startdate = last_date.strftime("%Y-%m-%d") 96 | print "file %s exists, download data from %s" % (filepath, startdate) 97 | 98 | #modified = False 99 | #dflen = df.shape[0] 100 | #for i in range(0, dflen): 101 | # x = df.iat[i, 1] 102 | # if (i > 1) and (isNum(x) is False): 103 | # print "remove row: %d" % (i, ) 104 | # df.drop('date', inplace=True) 105 | # modified = True 106 | # break 107 | 108 | #if modified is True: 109 | # print "save file %s" % (filepath, ) 110 | # df.to_csv(filepath) 111 | 112 | data_enddate = datetime.datetime.strptime(enddate, "%Y-%m-%d") + timedelta(days=1) 113 | 114 | print "download data, code: %s, startdate: %s, enddate: %s" % (code, startdate, data_enddate.strftime("%Y-%m-%d")) 115 | 116 | df_download = ts.get_h_data(code, start=startdate, end=data_enddate.strftime("%Y-%m-%d"), autype='hfq') 117 | 118 | if df_download is None: 119 | continue 120 | 121 | # 122 | df_download.sort_index(inplace=True) 123 | 124 | if os.path.exists(filepath): 125 | df_download.to_csv(filepath, mode='a', header=None) 126 | else: 127 | df_download.to_csv(filepath) 128 | 129 | 130 | -------------------------------------------------------------------------------- /bin/downloadindex.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 下载指数的历史数据 7 | ''' 8 | 9 | import tushare as ts 10 | import pandas as pd 11 | import commondatadef 12 | import numpy as np 13 | import datetime 14 | import os 15 | import sys 16 | 17 | reload(sys) 18 | sys.setdefaultencoding('utf8') 19 | 20 | print ts.__version__ 21 | 22 | indexes = ['000001', '399001', '000300', '399005', '399006'] 23 | 24 | 25 | for index in indexes: 26 | filepath = "%s//index//%s.txt" % (commondatadef.dataPath, index) 27 | 28 | df = ts.get_h_data(index, index=True) 29 | 30 | df.sort_index(inplace=True) 31 | 32 | df.to_csv(filepath, header=None) 33 | 34 | 35 | -------------------------------------------------------------------------------- /bin/downloadindex4lstm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #coding:utf-8 3 | 4 | __author__ = 'Administrator' 5 | 6 | ''' 7 | 下载指数的历史数据用于lstm计算 8 | ''' 9 | 10 | import tushare as ts 11 | import sys 12 | import datetime 13 | 14 | if len(sys.argv) < 2: 15 | print "Usage: downloaddata4lstm [code]" 16 | sys.exit(2) 17 | 18 | #代码 19 | code = sys.argv[1] 20 | 21 | #df = ts.get_stock_basics() 22 | #date = df.ix[code]['timeToMarket'] #上市日期YYYYMMDD 23 | 24 | #if date == 0: 25 | # print "Invalid timeToMarket" 26 | # sys.exit(2) 27 | 28 | # 获取开始时间 29 | #startDate = "%04d-%02d-%02d" % (date/10000, date/100%100, date%100) 30 | startDate = "2010-01-01" 31 | 32 | # 获得结束时间 33 | now = datetime.datetime.now() # 这是时间数组格式 34 | # 转换为指定的格式: 35 | endDate = now.strftime("%Y-%m-%d") 36 | 37 | # 下载数据 38 | df = ts.get_h_data(code, index=True, start=startDate, end=endDate) 39 | 40 | # 改为以date为索引 41 | df.sort_index(ascending=True, inplace=True) 42 | 43 | # 删除code列 44 | #df.pop('code') 45 | 46 | # 计算收盘价的5,10,20,60移动平均线 47 | df['ma5'] = df['close'].rolling(window=5).mean() 48 | df['ma10'] = df['close'].rolling(window=10).mean() 49 | df['ma20'] = df['close'].rolling(window=20).mean() 50 | df['ma60'] = df['close'].rolling(window=60).mean() 51 | 52 | # 计算成交量的5,10,20,60移动平均线 53 | df['va5'] = df['volume'].rolling(window=5).mean() 54 | df['va10'] = df['volume'].rolling(window=10).mean() 55 | df['va20'] = df['volume'].rolling(window=20).mean() 56 | df['va60'] = df['volume'].rolling(window=60).mean() 57 | 58 | # 增加label列 59 | df['label'] = df['close'] 60 | 61 | labels = df['label'] 62 | 63 | for i in range(len(labels)): 64 | if i < len(labels)-3: 65 | labels[i] = labels[i+3] 66 | 67 | filename = "%s.csv" % (code) 68 | 69 | df.to_csv(filename) 70 | -------------------------------------------------------------------------------- /bin/downloadmindata.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 下载分钟K线的历史数据 7 | ''' 8 | 9 | import tushare as ts 10 | import pandas as pd 11 | import commondatadef 12 | import numpy as np 13 | import datetime 14 | import os 15 | import sys 16 | 17 | reload(sys) 18 | sys.setdefaultencoding('utf8') 19 | 20 | print ts.__version__ 21 | 22 | # 获取股票代码 23 | configfile = commondatadef.configPath + '/instruments.txt' 24 | 25 | instruments = pd.DataFrame() 26 | 27 | if not os.path.exists(configfile): 28 | instruments = ts.get_industry_classified() 29 | instruments.to_csv(configfile, header=None) 30 | else: 31 | instruments = pd.read_csv(configfile, names=['code','name','industry']) 32 | 33 | # 34 | today = datetime.date.today() 35 | 36 | # 37 | def downloadstock(instrument, startdate, enddate, ktype, filepath): 38 | df = ts.get_hist_data(instrument, start=startdate, end=enddate, ktype=ktype) 39 | 40 | df.sort_index(inplace=True) 41 | 42 | df.to_csv(filepath, header=None) 43 | 44 | # 45 | for instrument in instruments['code']: 46 | filepath = "%s/%06d.txt" % (commondatadef.minPath, instrument) 47 | print filepath 48 | startdate = "2005-01-01" 49 | enddate = today.strftime("%Y-%m-%d") 50 | 51 | code = "%06d" % (instrument, ) 52 | df_download = ts.get_hist_data(code, start=startdate, end=enddate, ktype='5') 53 | 54 | df_download.sort_index(inplace=True) 55 | 56 | df_download.to_csv(filepath, mode='a') 57 | 58 | 59 | -------------------------------------------------------------------------------- /bin/fileconverter.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | 6 | import os 7 | import pandas as pd 8 | 9 | def getFiles(path): 10 | # 所有文件 11 | fileList = [] 12 | 13 | # 返回一个列表,其中包含在目录条目的名称(google翻译) 14 | files = os.listdir(path) 15 | 16 | # 先添加目录级别 17 | for f in files: 18 | if(os.path.isfile(path + '/' + f)): 19 | # 添加文件 20 | fileList.append(f) 21 | 22 | return fileList 23 | 24 | dirPath = 'c:/stock/data' 25 | 26 | files = getFiles(dirPath) 27 | 28 | for f in files: 29 | filePath = dirPath + "/" + f 30 | print filePath 31 | df = pd.DataFrame.from_csv(filePath, header=None) 32 | close = df.pop(4) 33 | df.insert(2, 4, close) 34 | df.to_csv(filePath, header=None) 35 | 36 | -------------------------------------------------------------------------------- /bin/generate_business_report.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 10 20:11:08 2018 4 | 5 | @author: Administrator 6 | """ 7 | 8 | 9 | import pandas as pd 10 | import tushare as ts 11 | import datetime 12 | import numpy as np 13 | 14 | 15 | def swap_value(x, y): 16 | return x if x!=0.00 else y 17 | 18 | 19 | def get_price(): 20 | ma = 20 21 | 22 | now = datetime.datetime.now() 23 | 24 | dt = now - datetime.timedelta(days=(ma*7+10)) 25 | 26 | df = ts.get_k_data(code='600000', ktype='W', autype='qfq', start=dt.strftime('%Y-%m-%d'), end=now.strftime('%Y-%m-%d')) 27 | 28 | df.reset_index(inplace=True) 29 | 30 | df['ma'+str(ma)] = df['close'].rolling(window=ma).mean() 31 | 32 | last_row = df.tail(1) 33 | 34 | #price = last_row['ma'+str(ma)] 35 | 36 | price = df.iat[df.shape[0]-1, df.shape[1]-1] 37 | 38 | return price 39 | 40 | 41 | # 获取股票的均线 42 | def get_stock_ma(code, ma): 43 | now = datetime.datetime.now() 44 | 45 | dt = now - datetime.timedelta(days=(ma*7+10)) 46 | 47 | df = ts.get_k_data(code=code, ktype='W', autype='qfq', start=dt.strftime('%Y-%m-%d'), end=now.strftime('%Y-%m-%d')) 48 | 49 | if (df.empty): 50 | return 0 51 | 52 | df.reset_index(inplace=True) 53 | 54 | df['ma'+str(ma)] = df['close'].rolling(window=ma).mean() 55 | 56 | #last_row = df.tail(1) 57 | 58 | #price = last_row['ma'+str(ma)] 59 | 60 | price = 0 61 | if (df.shape[0] > 0): 62 | price = df.iat[df.shape[0]-1, df.shape[1]-1] 63 | 64 | return price 65 | 66 | 67 | ''' 68 | price = get_price() 69 | 70 | df = ts.get_today_all() 71 | 72 | temp = np.zeros(df.shape[0]) 73 | 74 | if (not np.isnan(price)): 75 | temp[0] = price[1] 76 | ''' 77 | 78 | 79 | df_quots = ts.get_today_all() 80 | 81 | df_quots['roe'] = df_quots['pb']*100/df_quots['per'] 82 | df_quots['close'] = map(swap_value, df_quots['trade'], df_quots['settlement']) 83 | 84 | df_quots = df_quots[(df_quots['roe']>=5) & (df_quots['turnoverratio']>=2) & (df_quots['mktcap']>=500000000) & (df_quots['mktcap']>=30000000000) & (df_quots['close']<=50)] 85 | 86 | 87 | temp = np.zeros(df_quots.shape[0]) 88 | 89 | index = 0 90 | for code in df_quots['code']: 91 | price = get_stock_ma(code, 20) 92 | temp[index] = price 93 | index += 1 94 | 95 | df_quots.insert(df_quots.shape[1], 'wma20', temp) 96 | 97 | 98 | print df_quots 99 | 100 | df_quots.to_csv('/home/ubuntu/x.csv', encoding='utf-8', index=False, float_format = '%.2f') 101 | -------------------------------------------------------------------------------- /bin/get_growth_valuation.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding:utf-8 -*- 3 | __author__ = 'jerry' 4 | 5 | import pandas as pd 6 | import tushare as ts 7 | import datetime 8 | import commondatadef 9 | import csv2html as c2h 10 | 11 | # 设置精度 12 | pd.set_option('precision', 2) 13 | 14 | # 财务年度 15 | YEAR = 2017 16 | 17 | # 财务季度 18 | SEASON = 4 19 | 20 | key = ["code"] 21 | 22 | today = datetime.date.today() 23 | 24 | # 导出html表格 25 | def export_report(source, columns, title): 26 | #htmlfile = "/home/ubuntu/web/report/%s_%s.html" % (title, today.strftime("%Y%m%d"),) 27 | htmlfile = "/home/ubuntu/web/bin/index.html" 28 | 29 | outputfile = "%s/finance_report_%s.csv" % (commondatadef.resultPath, today.strftime("%Y%m%d"),) 30 | 31 | dest = source.sort_values(["rate_inc"], ascending=False) 32 | 33 | dest.drop_duplicates(inplace=True) 34 | 35 | dest.to_csv(outputfile, encoding='utf-8', index=False, float_format = '%.2f') 36 | 37 | c2h.csv2html(outputfile, htmlfile, title, columns) 38 | 39 | # 交换值 40 | def swap_value(x, y): 41 | return x if x!=0.00 else y 42 | 43 | # 获取股票列表 44 | df_basic = ts.get_stock_basics() 45 | 46 | df_basic['roe'] = df_basic['esp']*100/df_basic['bvps'] 47 | 48 | df_basic['value_increase'] = df_basic['esp']*(8.5+2*df_basic['profit']/100) 49 | 50 | df_basic.reset_index(inplace=True) 51 | df_basic = df_basic.drop(['area', 'outstanding', 'totalAssets', 'liquidAssets', 'fixedAssets', 'reserved', 'reservedPerShare', 'undp', 'perundp', 'rev', 'profit', 'gpr', 'npr', 'holders'], axis=1) 52 | 53 | ''' 54 | code,代码 55 | name,名称 56 | industry,所属行业 57 | area,地区 58 | pe,市盈率 59 | outstanding,流通股本(亿) 60 | totals,总股本(亿) 61 | totalAssets,总资产(万) 62 | liquidAssets,流动资产 63 | fixedAssets,固定资产 64 | reserved,公积金 65 | reservedPerShare,每股公积金 66 | esp,每股收益 67 | bvps,每股净资 68 | pb,市净率 69 | timeToMarket,上市日期 70 | undp,未分利润 71 | perundp, 每股未分配 72 | rev,收入同比(%) 73 | profit,利润同比(%) 74 | gpr,毛利率(%) 75 | npr,净利润率(%) 76 | holders,股东人数 77 | ''' 78 | 79 | #获取最新股价 80 | df_quots = ts.get_today_all() 81 | 82 | ''' 83 | code:代码 84 | name:名称 85 | changepercent:涨跌幅 86 | trade:现价 87 | open:开盘价 88 | high:最高价 89 | low:最低价 90 | settlement:昨日收盘价 91 | volume:成交量 92 | turnoverratio:换手率 93 | amount:成交量 94 | per:市盈率 95 | pb:市净率 96 | mktcap:总市值 97 | nmc:流通市值 98 | ''' 99 | 100 | df_quots['close'] = map(swap_value, df_quots['trade'], df_quots['settlement']) 101 | 102 | df_quots.reset_index(inplace=True) 103 | df_quots = df_quots.drop(['index','name','changepercent','trade','open','high','low','settlement','volume','turnoverratio','amount','per','pb','nmc'], axis=1) 104 | 105 | df = pd.merge(df_basic, df_quots, how='left', on=key) 106 | 107 | df['value'] = df['esp']/0.04 108 | 109 | df['rate'] = (df['value']-df['close'])*100/df['close'] 110 | 111 | df['rate_inc'] = (df['value_increase']-df['close'])*100/df['close'] 112 | 113 | columns = {} 114 | columns['name'] = ['代码', '名称', '所属行业', '市盈率', '总股本(亿)', '每股收益', '每股净资产', '市净率', '上市日期', '净资产收益率', '成长估值', '总市值', '现价', '估值', '估值差(%)', '成长估值差'] 115 | columns['index'] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] 116 | 117 | export_report(df, columns=columns, title="stockvaluation") 118 | 119 | -------------------------------------------------------------------------------- /bin/getreportdata.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | __author__ = 'jerry' 3 | 4 | import pandas as pd 5 | import tushare as ts 6 | import datetime 7 | from sendmail import sendmail 8 | import commondatadef 9 | 10 | report = ts.get_report_data(2017, 3) 11 | 12 | report.set_index('code') 13 | 14 | #report.sort(columns=['eps'], ascending=False, inplace=True) 15 | 16 | quots = ts.get_today_all() 17 | 18 | quots.set_index('code') 19 | 20 | result = pd.merge(report, quots, on='code') 21 | 22 | result['pe'] = result['trade']/result['eps'] 23 | 24 | result['pb'] = result['pe']*result['roe']/100 25 | 26 | result['peg'] = result['pe']/result['eps_yoy'] 27 | 28 | def get_best_roe(report): 29 | result = report[(report.eps_yoy>30)&(report.roe>30)] 30 | 31 | result.sort_values(by=['roe'], ascending=[0], inplace=True) 32 | 33 | return result 34 | 35 | def get_best_pe(report): 36 | result = report[(report.eps_yoy>0)&(report.pe<15)&(report.pe>0)] 37 | 38 | result.sort_values(by=['pe'], ascending=[1], inplace=True) 39 | 40 | return result 41 | 42 | def get_best_pb(report): 43 | result = report[(report.eps_yoy>0)&(report.pb<3)&(report.pe>0)] 44 | 45 | result.sort_values(by=['pb'], ascending=[1], inplace=True) 46 | 47 | return result 48 | 49 | def get_best_peg(report): 50 | result = report[(report.peg>0)&(report.pe>0)] 51 | 52 | result.sort_values(by=['peg'], ascending=[1], inplace=True) 53 | 54 | return result 55 | 56 | today = datetime.date.today() 57 | outputfile_best_roe = "%s/thebestroe_report_%s.csv" % (commondatadef.resultPath,today.strftime("%Y%m%d"),) 58 | outputfile_best_pe = "%s/thebestpe_report_%s.csv" % (commondatadef.resultPath,today.strftime("%Y%m%d"),) 59 | outputfile_best_pb = "%s/thebestpb_report_%s.csv" % (commondatadef.resultPath,today.strftime("%Y%m%d"),) 60 | outputfile_best_peg = "%s/thebestpeg_report_%s.csv" % (commondatadef.resultPath,today.strftime("%Y%m%d"),) 61 | 62 | roe = get_best_roe(result) 63 | roe.to_csv(outputfile_best_roe, encoding='gbk', index=False) 64 | 65 | pe = get_best_pe(result) 66 | pe.to_csv(outputfile_best_pe, encoding='gbk', index=False) 67 | 68 | pb = get_best_pb(result) 69 | pb.to_csv(outputfile_best_pb, encoding='gbk', index=False) 70 | 71 | peg = get_best_peg(result) 72 | peg.to_csv(outputfile_best_peg, encoding='gbk', index=False) 73 | 74 | subject = "成长股列表" 75 | 76 | sendmail(subject, mailto=['38454880@qq.com'], content="Please check attachment", attachments=[outputfile_best_roe, outputfile_best_pe, outputfile_best_pb, outputfile_best_peg]) 77 | 78 | # roe 〉30 并且 每股收益增长率〉0 79 | #df[(df.roe>30) & (df.eps_yoy>0)] 80 | 81 | #report.to_csv('e:/stock/report.csv', encoding='gbk', index=False) 82 | 83 | -------------------------------------------------------------------------------- /bin/mafilter.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 筛选满足均线条件的股票 7 | ''' 8 | 9 | import commondatadef 10 | import pandas as pd 11 | import matplotlib 12 | import datetime 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from sendmail import sendmail 16 | 17 | import os 18 | import sys 19 | 20 | reload(sys) 21 | sys.setdefaultencoding('utf8') 22 | 23 | matplotlib.style.use('ggplot') 24 | 25 | MAs = [20, 60] 26 | 27 | # get instruments 28 | #instruments = {'code':[2681]} 29 | instruments = pd.read_csv(commondatadef.instrument_file_path, names=['code','name','industry'], dtype=str) 30 | 31 | # 32 | today = datetime.date.today() 33 | 34 | 35 | instruments.index = instruments.index.astype(int) 36 | 37 | print instruments.index 38 | 39 | # 40 | for ma in MAs: 41 | result = pd.DataFrame() 42 | 43 | index = 0 44 | for code in instruments['code']: 45 | name = instruments.loc[index, 'name'] 46 | 47 | index += 1 48 | 49 | filepath = "%s/%s.txt" % (commondatadef.dataPath, code) 50 | 51 | #print filepath 52 | 53 | stock_data = pd.read_csv(filepath, names=['date', 'open', 'high', 'low', 'close', 'volume', 'amount'], parse_dates=[0], index_col=0) 54 | 55 | col_ma = 'MA_' + str(ma) 56 | 57 | # 计算简单算术移动平均线MA - 注意:stock_data['close']为股票每天的收盘价 58 | stock_data[col_ma] = pd.rolling_mean(stock_data['close'], ma) 59 | 60 | if len(stock_data) < 2: 61 | continue 62 | 63 | lastlast_record = stock_data.iloc[-2] 64 | 65 | last_record = stock_data.iloc[-1] 66 | last_date = stock_data.index[-1] 67 | 68 | if (last_record[col_ma] > lastlast_record[col_ma]) and (last_record['close'] > last_record[col_ma]) and (lastlast_record['close'] < last_record[col_ma]): 69 | d = today #+ datetime.timedelta(days = -1) 70 | if d.strftime("%Y-%m-%d") == last_date.strftime("%Y-%m-%d"): 71 | last_record['date'] = last_date.strftime("%Y-%m-%d") 72 | last_record['name'] = name 73 | result[code] = last_record 74 | 75 | result = result.T 76 | 77 | names = result.pop('name') 78 | result.insert(0, 'name', names) 79 | 80 | # 输出到文件 81 | outputfile = "%s/mafilter_%d_%s.csv" % (commondatadef.resultPath, ma, today.strftime("%Y%m%d")) 82 | 83 | result.to_csv(outputfile, index=True, index_label=['code'], encoding='gbk') 84 | 85 | subject = "突破%d日线的股票列表" % (ma,) 86 | 87 | sendmail(subject, mailto=['38454880@qq.com'], content="Please check attachment", attachments=[outputfile]) 88 | 89 | #result.plot() 90 | #plt.show() 91 | 92 | 93 | # 将数据按照交易日期从近到远排序 94 | #stock_data.sort('date', ascending=False, inplace=True) 95 | 96 | # ========== 将算好的数据输出到csv文件 - 注意:这里请填写输出文件在您电脑中的路径 97 | #outputfile = "%s/%06d_ma_ema.txt" % (commondatadef.dataPath, code) 98 | 99 | #stock_data.to_csv(outputfile, index=False) 100 | -------------------------------------------------------------------------------- /bin/makedataset.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'jerry' 4 | 5 | 6 | import tushare as ts 7 | import pandas as pd 8 | import commondatadef 9 | import numpy as np 10 | import datetime 11 | from datetime import timedelta 12 | import os 13 | import sys 14 | import stock_utility as su 15 | import commondatadef as cdd 16 | import pandas as pd 17 | 18 | 19 | 20 | def handleData(code, days): 21 | filename = "%s/%s.csv" % (commondatadef.data_dayPath, code) 22 | 23 | #——————————————————导入数据—————————————————————— 24 | df = pd.read_csv(filename) #读入股票数据 25 | 26 | # 改为以date为索引 27 | #df.sort_index(ascending=True, inplace=True) 28 | 29 | # 计算收盘价的5,10,20,60移动平均线 30 | df['ma5'] = df['close'].rolling(window=5).mean() 31 | df['ma10'] = df['close'].rolling(window=10).mean() 32 | df['ma20'] = df['close'].rolling(window=20).mean() 33 | df['ma60'] = df['close'].rolling(window=60).mean() 34 | 35 | # 计算成交量的5,10,20,60移动平均线 36 | df['va5'] = df['volume'].rolling(window=5).mean() 37 | df['va10'] = df['volume'].rolling(window=10).mean() 38 | df['va20'] = df['volume'].rolling(window=20).mean() 39 | df['va60'] = df['volume'].rolling(window=60).mean() 40 | 41 | labels = df['close'] 42 | 43 | true_labels = 0 44 | 45 | if days < len(labels): 46 | final_labels = labels[days:] 47 | new_index = range(0, len(labels)-days) 48 | 49 | true_labels = pd.Series(final_labels.values, index=new_index) 50 | else: 51 | print "invalid days: %d" % (days) 52 | 53 | df['labels'] = true_labels 54 | 55 | dataset_filename = "%s/%s.csv" % (commondatadef.dataPath, code) 56 | 57 | df.sort_index(ascending=True, inplace=True) 58 | 59 | df.to_csv(dataset_filename, index=False) 60 | 61 | return df 62 | 63 | 64 | if len(sys.argv) < 2: 65 | print "Usage: script [code]" 66 | sys.exit(2) 67 | 68 | #代码 69 | code = sys.argv[1] 70 | 71 | 72 | # 73 | handleData(code, days=3) 74 | -------------------------------------------------------------------------------- /bin/predict_stock_price.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import pandas as pd 5 | import tushare as ts 6 | import sklearn as skl 7 | import numpy as np 8 | from sklearn import datasets,linear_model 9 | # https://github.com/udacity/machine-learning/issues/202 10 | # sklearn.cross_validation 这个包不推荐使用了。 11 | from sklearn.model_selection import train_test_split,cross_val_score 12 | import datetime 13 | 14 | 15 | #################准备股票数据。################# 16 | date_end_str = '2018-01-03' 17 | code = "000001" 18 | 19 | date_end = datetime.datetime.strptime(date_end_str, "%Y-%m-%d") 20 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 21 | date_end = date_end.strftime("%Y-%m-%d") 22 | 23 | # open high close low volume price_change p_change ma5 ma10 ma20 v_ma5 v_ma10 v_ma20 turnover 24 | stock_X = ts.get_hist_data(code, start=date_start, end=date_end) 25 | stock_X = stock_X.sort_index(0) # 将数据按照日期排序下。 26 | stock_y = pd.Series(stock_X["close"].values) #标签 27 | 28 | stock_X_test = stock_X.iloc[len(stock_X)-1] 29 | # 使用今天的交易价格,13 个指标预测明天的价格。偏移股票数据,今天的数据,目标是明天的价格。 30 | stock_X = stock_X.drop(stock_X.index[len(stock_X)-1]) # 删除最后一条数据 31 | stock_y = stock_y.drop(stock_y.index[0]) # 删除第一条数据 32 | #删除掉close 也就是收盘价格。 33 | del stock_X["close"] 34 | del stock_X_test["close"] 35 | 36 | #使用最后一个数据做测试。 37 | stock_y_test = stock_y.iloc[len(stock_y)-1] 38 | 39 | print(stock_X.tail(5)) 40 | print("###########################") 41 | print(stock_y.tail(5)) # 42 | #print(stock_X.values[0]) 43 | 44 | print("###########################") 45 | print(len(stock_X),",",len(stock_y)) 46 | 47 | print("###########################") 48 | print(stock_X_test.values,stock_y_test) 49 | 50 | model = linear_model.LinearRegression() 51 | model.fit(stock_X.values,stock_y) 52 | print("############## test & target #############") 53 | print(model.predict([stock_X_test.values])) 54 | print(stock_y_test) 55 | 56 | print("############## coef_ & intercept_ #############") 57 | print(model.coef_) #系数 58 | print(model.intercept_) #截断 59 | print("score:", model.score(stock_X.values,stock_y)) #评分 60 | -------------------------------------------------------------------------------- /bin/receivemsg.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | import tushare as ts 6 | import pandas as pd 7 | 8 | print ts.__version__ 9 | 10 | # get all industry 11 | #codes = ts.get_industry_classified() 12 | 13 | # 14 | instruments = ts.get_industry_classified() 15 | #instruments = pd.DataFrame([{'code':'000001', 'name':'pingan'}]) 16 | 17 | # 18 | #print codes 19 | 20 | # 21 | for instrument in instruments['code']: 22 | df = ts.get_h_data(instrument, start='2009-01-01', end='2015-11-04', autype='hfq') 23 | 24 | df.sort_index(inplace=True) 25 | 26 | filename = 'C:/Stock/Data/' + instrument + '.txt' 27 | 28 | df.to_csv(filename, header=None) 29 | 30 | #downloadcode('C:/Users/Administrator/Desktop/Day/', code=instrument, startdate='2009-1-1', enddate='2015-10-27', fqtype='hfq') -------------------------------------------------------------------------------- /bin/returntop10.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | __author__ = 'Administrator' 3 | 4 | import pandas as pd 5 | import numpy as np 6 | import datetime 7 | import time 8 | from sendmail import sendmail 9 | from os.path import join, getsize 10 | 11 | import sys 12 | 13 | reload(sys) 14 | sys.setdefaultencoding('utf8') 15 | 16 | 17 | def get_buy_stocks(): 18 | path = "C:/Stock/product/" 19 | 20 | filepath = path + "buy_top5.srf" 21 | 22 | size = getsize(filepath) 23 | 24 | if size > 0: 25 | subject = "买入的股票列表" 26 | sendmail(subject, mailto=['38454880@qq.com'], content="Please check attachment", attachments=[filepath]) 27 | 28 | 29 | def get_sell_stocks(): 30 | path = "C:/Stock/product/" 31 | 32 | filepath = path + "sell_top5.srf" 33 | 34 | size = getsize(filepath) 35 | 36 | if size > 0: 37 | subject = "卖出的股票列表" 38 | sendmail(subject, mailto=['38454880@qq.com'], content="Please check attachment", attachments=[filepath]) 39 | 40 | 41 | def get_stock_trades(): 42 | path = "C:/Stock/product/" 43 | 44 | filepath = path + "strategyresult.srf" 45 | 46 | df = pd.read_csv(filepath, header=None, sep='\t', index_col=[0,1], encoding='GBK') 47 | 48 | trades_col = df[15] 49 | 50 | # 去掉 51 | trades_col = trades_col.dropna() 52 | 53 | stock_trades = {} 54 | 55 | # 56 | for index in trades_col.index: 57 | result = trades_col[index] 58 | trades = result.split(',') 59 | #print trades 60 | stock_trades[index] = [] 61 | for trade in trades: 62 | items = trade.split(':') 63 | if len(items) != 6: 64 | continue 65 | trade_info = {} 66 | trade_info['buy_date'] = items[0] 67 | trade_info['buy_price'] = items[1] 68 | trade_info['sell_date'] = items[2] 69 | trade_info['sell_price'] = items[3] 70 | trade_info['period'] = items[4] 71 | trade_info['ratio'] = items[5] 72 | #print trade_info 73 | 74 | stock_trades[index].append(trade_info) 75 | #stocks[index] = 76 | 77 | #print stock_trades[index] 78 | 79 | return stock_trades 80 | 81 | stock_trades = get_stock_trades() 82 | 83 | #print stock_trades 84 | 85 | hold_trades = {} 86 | 87 | for key in stock_trades.keys(): 88 | data = stock_trades[key] 89 | 90 | if data[len(data)-1]['sell_date'][1:4].isdigit() == False: 91 | hold_trades[key] = data[len(data)-1] 92 | 93 | #print hold_trades 94 | 95 | df = pd.DataFrame(hold_trades) 96 | 97 | df = df.T 98 | 99 | df = df.sort_values(by=['buy_date'], ascending=False) 100 | 101 | # 102 | df['buy_date'] = df['buy_date'].astype(np.datetime64) 103 | df['ratio'] = df['ratio'].astype(np.float64) 104 | 105 | print df['buy_date'].dtype 106 | 107 | t = np.datetime64('2015-10-01') 108 | df = df[df['buy_date'] > t] 109 | 110 | df = df.sort_values(by=['ratio'], ascending=False) 111 | 112 | TODAY = datetime.date.today() 113 | CURRENTDAY = TODAY.strftime('%Y-%m-%d') 114 | 115 | filename = "C:/Stock/product/returnTop10_%s.txt" % (CURRENTDAY,) 116 | 117 | 118 | df.to_csv("C:/Stock/product/returnTop10.txt", columns=['code', 'name', 'buy_date', 'buy_price', 'period', 'ratio', 'sell_date', 'sell_price'], index=True, sep='\t') 119 | df.to_csv(filename, columns=['code', 'name', 'buy_date', 'buy_price', 'period', 'ratio', 'sell_date', 'sell_price'], index=True, sep='\t') 120 | 121 | get_sell_stocks() 122 | 123 | get_buy_stocks() 124 | 125 | print df 126 | 127 | 128 | -------------------------------------------------------------------------------- /bin/stock_utility.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'jerry' 4 | 5 | 6 | import tushare as ts 7 | #import talib as ta 8 | import numpy as np 9 | import pandas as pd 10 | import os,time,sys,re,datetime 11 | import csv 12 | #import scipy 13 | import smtplib 14 | from email.mime.text import MIMEText 15 | from email.MIMEMultipart import MIMEMultipart 16 | 17 | 18 | # 获取股票基本信息 19 | #code,代码 20 | #name,名称 21 | #industry,所属行业 22 | #area,地区 23 | #pe,市盈率 24 | #outstanding,流通股本 25 | #totals,总股本(万) 26 | #totalAssets,总资产(万) 27 | #liquidAssets,流动资产 28 | #fixedAssets,固定资产 29 | #reserved,公积金 30 | #reservedPerShare,每股公积金 31 | #eps,每股收益 32 | #bvps,每股净资 33 | #pb,市净率 34 | #timeToMarket,上市日期 35 | def get_stock_list(): 36 | df = ts.get_stock_basics() 37 | df.sort_index(inplace=True) 38 | return df 39 | 40 | -------------------------------------------------------------------------------- /bin/timertask.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | __author__ = 'jerry' 4 | 5 | ''' 6 | 定时器实现 7 | ''' 8 | 9 | from threading import Timer 10 | import time 11 | from datetime import datetime, timedelta 12 | import os 13 | 14 | SECONDS_PER_DAY = 24 * 60 * 60 15 | 16 | def gettimetowait(hour, minute, second): 17 | curTime = datetime.now() 18 | desTime = curTime.replace(hour=hour, minute=minute, second=second, microsecond=0) 19 | delta = curTime - desTime 20 | skipSeconds = SECONDS_PER_DAY - delta.total_seconds() 21 | #skipSeconds = delta.total_seconds() 22 | print "Must sleep %d seconds" % skipSeconds 23 | return skipSeconds 24 | 25 | timer_interval = 1 26 | 27 | def delayrun(): 28 | print "start running" 29 | os.system('python getvaluation.py') 30 | os.system('python get_value_4_business.py') 31 | print "restart completed" 32 | 33 | print "Running......" 34 | 35 | while True: 36 | #print "waitting......" 37 | current_time = time.localtime(time.time()) 38 | if((current_time.tm_hour == 20) and (current_time.tm_min == 00) and (current_time.tm_sec == 00) 39 | and (current_time.tm_wday != 0) and (current_time.tm_wday != 6)): 40 | delayrun() 41 | time.sleep(1) 42 | 43 | -------------------------------------------------------------------------------- /bin/ts_downloaddaydata.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'jerry' 4 | 5 | ''' 6 | 下载历史数据用于tensorflow数据预测 7 | ''' 8 | 9 | import tushare as ts 10 | import pandas as pd 11 | import commondatadef 12 | import numpy as np 13 | import datetime 14 | from datetime import timedelta 15 | import os 16 | import sys 17 | import stock_utility as su 18 | import commondatadef as cdd 19 | import pandas as pd 20 | import time 21 | 22 | reload(sys) 23 | sys.setdefaultencoding('utf8') 24 | 25 | print ts.__version__ 26 | 27 | # 下载数据 28 | instruments = su.get_stock_list() 29 | 30 | # 如果下载成功,则保存 31 | if not instruments is None: 32 | print "download instruments success, save to file %s" % (cdd.code_list_file) 33 | instruments.to_csv(cdd.code_list_file) 34 | else: 35 | print "download instruments success, read from file %s" % (cdd.code_list_file) 36 | instruments = pd.read_csv(cdd.code_list_file, index_col=['code']) 37 | 38 | if instruments is None: 39 | print "instruments is empty, exit" 40 | exit() 41 | 42 | # 43 | today = datetime.date.today() 44 | 45 | # 46 | def downloadstock(instrument, startdate, enddate, filepath): 47 | df = ts.get_h_data(instrument, start=startdate, end=enddate, autype='hfq') 48 | 49 | df.sort_index(inplace=True) 50 | 51 | df.to_csv(filepath) 52 | 53 | # 是否数字 54 | def isNum(value): 55 | try: 56 | float(value) + 1 57 | except TypeError: 58 | return False 59 | except ValueError: 60 | return False 61 | else: 62 | return True 63 | 64 | def handleData(code, days): 65 | filename = "%s/%s.csv" % (commondatadef.data_dayPath, code) 66 | 67 | #——————————————————导入数据—————————————————————— 68 | df = pd.read_csv(filename) #读入股票数据 69 | 70 | # 改为以date为索引 71 | #df.sort_index(ascending=True, inplace=True) 72 | 73 | # 计算收盘价的5,10,20,60移动平均线 74 | df['ma5'] = df['close'].rolling(window=5).mean() 75 | df['ma10'] = df['close'].rolling(window=10).mean() 76 | df['ma20'] = df['close'].rolling(window=20).mean() 77 | df['ma60'] = df['close'].rolling(window=60).mean() 78 | 79 | # 计算成交量的5,10,20,60移动平均线 80 | df['va5'] = df['volume'].rolling(window=5).mean() 81 | df['va10'] = df['volume'].rolling(window=10).mean() 82 | df['va20'] = df['volume'].rolling(window=20).mean() 83 | df['va60'] = df['volume'].rolling(window=60).mean() 84 | 85 | labels = df['close'] 86 | 87 | true_labels = 0 88 | 89 | if days < len(labels): 90 | final_labels = labels[days:] 91 | new_index = range(0, len(labels)-days) 92 | 93 | true_labels = pd.Series(final_labels.values, index=new_index) 94 | else: 95 | print "invalid days: %d" % (days) 96 | 97 | df['labels'] = true_labels 98 | 99 | dataset_filename = "%s/%s.csv" % (commondatadef.dataPath, code) 100 | 101 | df.sort_index(ascending=True, inplace=True) 102 | 103 | df.to_csv(dataset_filename, index=False) 104 | 105 | return df 106 | 107 | 108 | # 109 | for code in instruments.index: 110 | filepath = "%s/%s.csv" % (commondatadef.data_dayPath, code) 111 | print "starting download %s, file path: %s" % (code, filepath) 112 | startdate = "1990-01-01" 113 | enddate = today.strftime("%Y-%m-%d") 114 | df = pd.DataFrame() 115 | if os.path.exists(filepath): 116 | df = pd.read_csv(filepath, index_col=['date']) 117 | last_date = datetime.datetime.strptime(df.index[-1], "%Y-%m-%d") + datetime.timedelta(days=1) 118 | startdate = last_date.strftime("%Y-%m-%d") 119 | print "file %s exists, download data from %s" % (filepath, startdate) 120 | 121 | data_enddate = datetime.datetime.strptime(enddate, "%Y-%m-%d") + timedelta(days=1) 122 | 123 | print "download data, code: %s, startdate: %s, enddate: %s" % (code, startdate, data_enddate.strftime("%Y-%m-%d")) 124 | 125 | try: 126 | df_download = ts.get_h_data(code, start=startdate, end=data_enddate.strftime("%Y-%m-%d"), autype='hfq') 127 | except: 128 | print "download failure, code: %s" % code 129 | continue 130 | 131 | if not (df_download is None): 132 | # 133 | df_download.sort_index(inplace=True) 134 | 135 | if os.path.exists(filepath): 136 | df_download.to_csv(filepath, mode='a', header=None) 137 | else: 138 | df_download.to_csv(filepath) 139 | 140 | #处理数据,增加ma等指标 141 | handleData(code, 3) 142 | 143 | #等待时间 144 | time.sleep(60) 145 | -------------------------------------------------------------------------------- /bin/upmastrategy.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | __author__ = 'Administrator' 4 | 5 | ''' 6 | 筛选突破某条均线的股票列表 7 | ''' 8 | 9 | import commondatadef 10 | import pandas as pd 11 | import matplotlib 12 | import datetime 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from sendmail import sendmail 16 | 17 | import os 18 | import sys 19 | 20 | reload(sys) 21 | sys.setdefaultencoding('utf8') 22 | 23 | matplotlib.style.use('ggplot') 24 | 25 | ma = 120 26 | change_percent = 5 27 | 28 | # get instruments 29 | #instruments = {'code':[2681]} 30 | instruments = pd.read_csv(commondatadef.instrument_file_path, names=['code','name','industry'], dtype=str) 31 | 32 | # 33 | today = datetime.date.today() 34 | 35 | # 36 | result = pd.DataFrame() 37 | 38 | instruments.index = instruments.index.astype(int) 39 | 40 | print instruments.index 41 | 42 | index = 0 43 | for code in instruments['code']: 44 | name = instruments.loc[index, 'name'] 45 | 46 | index += 1 47 | 48 | filepath = "%s/%s.txt" % (commondatadef.dataPath, code) 49 | 50 | #print filepath 51 | 52 | stock_data = pd.read_csv(filepath, names=['date', 'open', 'high', 'low', 'close', 'volume', 'amount'], parse_dates=[0], index_col=0) 53 | 54 | col_ma = 'MA_' + str(ma) 55 | 56 | # 计算指数移动平均线MA - 注意:stock_data['close']为股票每天的收盘价 57 | stock_data[col_ma] = pd.ewma(stock_data['close'], ma) 58 | 59 | if len(stock_data) < 2: 60 | continue 61 | 62 | lastlast_record = stock_data.iloc[-2] 63 | 64 | last_record = stock_data.iloc[-1] 65 | last_date = stock_data.index[-1] 66 | 67 | if ((last_record['close'] - last_record[col_ma])*100/last_record['close'] > change_percent ): 68 | d = today + datetime.timedelta(days = -1) 69 | if d.strftime("%Y-%m-%d") == last_date.strftime("%Y-%m-%d"): 70 | last_record['date'] = last_date.strftime("%Y-%m-%d") 71 | last_record['name'] = name 72 | result[code] = last_record 73 | 74 | result = result.T 75 | 76 | names = result.pop('name') 77 | result.insert(0, 'name', names) 78 | 79 | 80 | outputfile = "%s/upma_%d_%d_%s.csv" % (commondatadef.resultPath, ma, change_percent, today.strftime("%Y%m%d")) 81 | 82 | result.to_csv(outputfile, index=True, index_label=['code'], encoding='gbk') 83 | 84 | subject = "%d日线之上的股票列表" % (ma,) 85 | sendmail(subject, mailto=['38454880@qq.com'], content="Please check attachment", attachments=[outputfile]) 86 | 87 | #result.plot() 88 | #plt.show() 89 | 90 | 91 | # 将数据按照交易日期从近到远排序 92 | #stock_data.sort('date', ascending=False, inplace=True) 93 | 94 | # ========== 将算好的数据输出到csv文件 - 注意:这里请填写输出文件在您电脑中的路径 95 | #outputfile = "%s/%06d_ma_ema.txt" % (commondatadef.dataPath, code) 96 | 97 | #stock_data.to_csv(outputfile, index=False) 98 | 99 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function clean_env() { 4 | unset APP_MAIL_SERVER 5 | unset APP_MAIL_PORT 6 | unset APP_MAIL_USE_TLS 7 | unset APP_MAIL_USE_SSL 8 | unset APP_MAIL_USERNAME 9 | unset APP_MAIL_PASSWORD 10 | unset APP_MAIL_DEFAULT_SENDER 11 | unset APP_SQLALCHEMY_DATABASE_URI 12 | } 13 | 14 | clean_env 15 | 16 | rm -rf migrations 17 | rm -rf tmp 18 | rm -f project/dev.sqlite 19 | -------------------------------------------------------------------------------- /create.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python manage.py create_db 4 | python manage.py db init 5 | python manage.py db migrate 6 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # manage.py 4 | 5 | 6 | import os 7 | import unittest 8 | import coverage 9 | import datetime 10 | 11 | from flask_script import Manager 12 | from flask_migrate import Migrate, MigrateCommand 13 | 14 | ''' 15 | COV = coverage.coverage( 16 | branch=True, 17 | include='app/*', 18 | omit=['*/__init__.py', '*/config/*'] 19 | ) 20 | COV.start() 21 | ''' 22 | from app import app, db 23 | from app.models import User 24 | 25 | 26 | migrate = Migrate(app, db) 27 | manager = Manager(app) 28 | 29 | # migrations 30 | manager.add_command('db', MigrateCommand) 31 | 32 | 33 | @manager.command 34 | def test(): 35 | """Runs the unit tests without coverage.""" 36 | tests = unittest.TestLoader().discover('tests') 37 | result = unittest.TextTestRunner(verbosity=2).run(tests) 38 | if result.wasSuccessful(): 39 | return 0 40 | else: 41 | return 1 42 | 43 | ''' 44 | @manager.command 45 | def cov(): 46 | """Runs the unit tests with coverage.""" 47 | tests = unittest.TestLoader().discover('tests') 48 | unittest.TextTestRunner(verbosity=2).run(tests) 49 | COV.stop() 50 | COV.save() 51 | print('Coverage Summary:') 52 | COV.report() 53 | basedir = os.path.abspath(os.path.dirname(__file__)) 54 | covdir = os.path.join(basedir, 'tmp/coverage') 55 | COV.html_report(directory=covdir) 56 | print('HTML version: file://%s/index.html' % covdir) 57 | COV.erase() 58 | ''' 59 | 60 | 61 | @manager.command 62 | def create_db(): 63 | """Creates the db tables.""" 64 | db.create_all() 65 | 66 | 67 | @manager.command 68 | def drop_db(): 69 | """Drops the db tables.""" 70 | db.drop_all() 71 | 72 | 73 | @manager.command 74 | def create_admin(): 75 | """Creates the admin user.""" 76 | db.session.add(User( 77 | email="ad@min.com", 78 | password="admin", 79 | admin=True, 80 | confirmed=True, 81 | confirmed_on=datetime.datetime.now()) 82 | ) 83 | db.session.commit() 84 | 85 | 86 | if __name__ == '__main__': 87 | manager.run() 88 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0 2 | Flask-Bcrypt==0.6.0 3 | Flask-DebugToolbar==0.9.0 4 | Flask-Login==0.2.11 5 | Flask-Mail==0.9.1 6 | Flask-Migrate==1.2.0 7 | Flask-SQLAlchemy==2.0 8 | Flask-Script==2.0.5 9 | Flask-Testing==0.4.2 10 | Flask-WTF==0.10.2 11 | Jinja2==2.11.3 12 | Mako==1.2.2 13 | MarkupSafe==0.23 14 | SQLAlchemy==1.3.0 15 | WTForms==2.0.1 16 | Werkzeug==0.15.3 17 | alembic==0.6.7 18 | blinker==1.3 19 | coverage==4.0a1 20 | ecdsa==0.13.3 21 | httplib2==0.19.0 22 | itsdangerous==0.24 23 | paramiko==2.10.1 24 | psycopg2==2.5.4 25 | py-bcrypt==0.4 26 | pycrypto==2.6.1 27 | requests==2.20.0 28 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export APP_SETTINGS=app.config.ProductionConfig 4 | 5 | nohup python manage.py runserver -h 0.0.0.0 -p 8081 & 6 | -------------------------------------------------------------------------------- /start_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export APP_SETTINGS=app.config.DevelopmentConfig 4 | 5 | 6 | python manage.py runserver -h 0.0.0.0 -p 8088 7 | -------------------------------------------------------------------------------- /start_uwsgi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export APP_SETTINGS=app.config.ProductionConfig 4 | 5 | nohup uwsgi uwsgiconfig.ini & 6 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | -------------------------------------------------------------------------------- /utils/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/utils/__init__.pyc -------------------------------------------------------------------------------- /utils/emailer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | @author: Jerry Lee 5 | @license: (C) Copyright 2013-2019, Node Supply Chain Manager Corporation Limited. 6 | @file: emailer.py 7 | @time: 2020/1/2 5:25 下午 8 | @desc: 9 | """ 10 | 11 | import os 12 | import smtplib 13 | from email.mime.multipart import MIMEMultipart 14 | from email.mime.text import MIMEText 15 | 16 | 17 | class EMailer(object): 18 | def __init__(self, account, passwd, smtp_server): 19 | self.account = account 20 | self.passwd = passwd 21 | self.smtp_server = smtp_server 22 | 23 | def send(self, subject, mailto, content, attachments=None): 24 | #创建一个带附件的实例 25 | msg = MIMEMultipart() 26 | 27 | textmsg = MIMEText(content, _subtype='html', _charset='utf-8') 28 | msg.attach(textmsg) 29 | 30 | #构造附件1 31 | if not attachments == None: 32 | for attachment in attachments: 33 | att1 = MIMEText(open(attachment, 'rb').read(), 'base64', 'utf-8') 34 | att1['Content-Type'] = 'application/octet-stream' 35 | filename = os.path.basename(attachment) 36 | description = 'attachment; filename=%s' % (filename,) 37 | att1['Content-Disposition'] = description #这里的filename可以任意写,写什么名字,邮件中显示什么名字 38 | msg.attach(att1) 39 | 40 | 41 | #加邮件头 42 | msg['to'] = ",".join(mailto) 43 | msg['from'] = self.account 44 | msg['subject'] = subject 45 | 46 | #发送邮件 47 | try: 48 | server = smtplib.SMTP_SSL(self.smtp_server, 465) 49 | server.set_debuglevel(1) 50 | # server.connect('smtp.163.com') 51 | server.login(self.account, self.passwd)#XXX为用户名,XXXXX为密码 52 | server.sendmail(msg['from'], mailto, msg.as_string()) 53 | server.quit() 54 | print('发送成功') 55 | except Exception as e: 56 | print(str(e)) 57 | -------------------------------------------------------------------------------- /utils/mail_informer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | @author: Jerry Lee 5 | @license: (C) Copyright 2013-2019, Node Supply Chain Manager Corporation Limited. 6 | @file: mail_informer.py 7 | @time: 2020/1/2 6:34 下午 8 | @desc: 邮件消息通知 9 | """ 10 | 11 | import os 12 | try: 13 | from .emailer import EMailer 14 | except: 15 | from emailer import EMailer 16 | 17 | 18 | class MailInformer(object): 19 | def __init__(self): 20 | self.mail_account = os.environ['MAIL_ACCOUNT'] 21 | self.mail_passwd = os.environ['MAIL_PASSWD'] 22 | self.smtp_server = os.environ['SMTP_SERVER'] 23 | self.mail_address_to = os.environ['MAIL_ADDRESS_TO'] 24 | 25 | self.mailer = EMailer(self.mail_account, self.mail_passwd, self.smtp_server) 26 | 27 | def inform(self, subject, content): 28 | self.mailer.send(subject, mailto=self.mail_address_to, content=content) 29 | 30 | 31 | if __name__ == '__main__': 32 | mailer = MailInformer() 33 | 34 | mailer.inform('测试', 'test') 35 | -------------------------------------------------------------------------------- /utils/sms_informer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | @author: Jerry Lee 5 | @license: (C) Copyright 2013-2019, Node Supply Chain Manager Corporation Limited. 6 | @file: sms_informer.py 7 | @time: 2020/1/17 2:03 下午 8 | @desc: 9 | """ 10 | 11 | from twilio.rest import Client 12 | import os 13 | 14 | 15 | class SMSInformer(object): 16 | def __init__(self): 17 | self.accountSID = os.environ['SMS_ACCOUNT_SID'] 18 | self.authToken = os.environ['SMS_AUTH_TOKEN'] 19 | self.myNumber = os.environ['SMS_MY_NUMBER'] 20 | self.twilioNumber = os.environ['SMS_TWILIO_NUMBER'] 21 | 22 | def inform(self, subject, content): 23 | twilioCli = Client(self.accountSID, self.authToken) 24 | 25 | message = "{} {}".format(subject, content) 26 | twilioCli.messages.create(body=message, from_=self.twilioNumber, to=self.myNumber) 27 | 28 | 29 | if __name__ == '__main__': 30 | sms_sender = SMSInformer() 31 | sms_sender.inform('hell', 'the world') 32 | -------------------------------------------------------------------------------- /utils/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # project/util.py 4 | 5 | 6 | from sqlalchemy.orm import class_mapper 7 | import json 8 | from werkzeug.utils import import_string 9 | 10 | ''' 11 | class BaseTestCase(TestCase): 12 | 13 | def create_app(self): 14 | app.config.from_object('project.config.TestingConfig') 15 | return app 16 | 17 | @classmethod 18 | def setUpClass(self): 19 | db.create_all() 20 | user = User( 21 | email="test@user.com", 22 | password="just_a_test_user", 23 | confirmed=False 24 | ) 25 | db.session.add(user) 26 | db.session.commit() 27 | 28 | @classmethod 29 | def tearDownClass(self): 30 | db.session.remove() 31 | db.drop_all() 32 | ''' 33 | 34 | # sqlalchemy对象转换为json 35 | def model_to_json(model): 36 | """Transforms a model into a dictionary which can be dumped to JSON.""" 37 | # first we get the names of all the columns on your model 38 | columns = [c.key for c in class_mapper(model.__class__).columns] 39 | # then we return their values in a dict 40 | d = dict((c, getattr(model, c)) for c in columns) 41 | return json.dumps(d) 42 | 43 | def string_to_obj(obj): 44 | if isinstance(obj, (str,)): 45 | obj = import_string(obj) 46 | 47 | return obj -------------------------------------------------------------------------------- /utils/util.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrylee529/twelvewin/2425575f2b18f21f22bc9e4c01b8b2b9810047ae/utils/util.pyc -------------------------------------------------------------------------------- /uwsgiconfig.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | 3 | # uwsgi 启动时所使用的地址与端口 4 | socket = 0.0.0.0:8088 5 | 6 | # 指向网站目录 7 | chdir = /home/dev/twelvewin_data 8 | 9 | # python 启动程序文件 10 | wsgi-file = manage.py 11 | 12 | # python 程序内用以启动的 application 变量名 13 | callable = app 14 | 15 | # 处理器数 16 | processes = 4 17 | 18 | # 线程数 19 | threads = 2 20 | 21 | #状态检测地址 22 | stats = 127.0.0.1:9191 23 | --------------------------------------------------------------------------------