├── web ├── README.md ├── templates │ ├── common │ │ ├── footer.html │ │ ├── header.html │ │ ├── left_menu.html │ │ └── meta.html │ ├── layout │ │ ├── indicators.html │ │ ├── single_default.html │ │ ├── default.html │ │ ├── indicators-main.html │ │ ├── single_main.html │ │ └── main.html │ ├── index.html │ ├── stock_chart.html │ ├── bokeh_embed.html │ ├── stock_indicators.html │ ├── test2.html │ ├── test.html │ ├── minst_serving.html │ ├── data_editor.html │ └── stock_web.html ├── static │ ├── img │ │ ├── 支付宝--微信支付.jpg │ │ ├── diff-n-bokeh.png │ │ ├── stock-show-01.jpg │ │ └── minst_serving │ │ │ ├── 00000.bmp │ │ │ ├── 00001.bmp │ │ │ ├── 00002.bmp │ │ │ ├── 00003.bmp │ │ │ ├── 00004.bmp │ │ │ ├── 00005.bmp │ │ │ ├── 00006.bmp │ │ │ ├── 00007.bmp │ │ │ ├── 00008.bmp │ │ │ ├── 00009.bmp │ │ │ ├── 00010.bmp │ │ │ ├── 00011.bmp │ │ │ ├── 00012.bmp │ │ │ ├── 00013.bmp │ │ │ ├── 00014.bmp │ │ │ ├── 00015.bmp │ │ │ ├── 00016.bmp │ │ │ ├── 00017.bmp │ │ │ ├── 00018.bmp │ │ │ ├── 00019.bmp │ │ │ ├── 00020.bmp │ │ │ ├── 00021.bmp │ │ │ ├── 00022.bmp │ │ │ ├── 00023.bmp │ │ │ ├── 00024.bmp │ │ │ ├── 00025.bmp │ │ │ ├── 00026.bmp │ │ │ ├── 00027.bmp │ │ │ ├── 00028.bmp │ │ │ └── 00029.bmp │ ├── font-awesome │ │ ├── 4.5.0 │ │ │ └── fonts │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ └── opensans │ │ │ └── v13 │ │ │ ├── DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff │ │ │ └── cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff │ ├── css │ │ ├── fonts.googleapis.com.css │ │ ├── jquery-ui.custom.min.css │ │ ├── bootstrap-timepicker.min.css │ │ ├── bootstrap-colorpicker.min.css │ │ ├── select.dataTables.min.css │ │ ├── daterangepicker.min.css │ │ ├── bootstrap-datetimepicker.min.css │ │ └── buttons.dataTables.min.css │ └── js │ │ ├── bootstrap-datepicker.zh-CN.js │ │ ├── datatables.Chinese.json │ │ ├── buttons.print.min.js │ │ ├── draw.js │ │ ├── buttons.colVis.min.js │ │ ├── autosize.min.js │ │ ├── jquery.dataTables.bootstrap.min.js │ │ ├── grid.locale-en.js │ │ ├── ace-extra.min.js │ │ └── bootbox.js ├── base.py ├── chartHandler.py ├── tornado_bokeh_embed.py ├── minstServingHandler.py ├── test_thread.py ├── main.py ├── test_thread_v2.py ├── dataEditorHandler.py ├── dataTableHandler.py └── demo-chart.py ├── libs ├── __init__.py ├── common.py └── stock_web_dic.py ├── old_jobs ├── README.md ├── guess_indicators_lite_buy_daily_job.py ├── guess_period_daily_job.py ├── guess_indicators_lite_sell_daily_job.py ├── guess_return_daily_job.py └── guess_sklearn_ma_daily_job.py ├── jobs ├── README.txt ├── cron.hourly │ └── run_hourly ├── cron.monthly │ └── run_monthly ├── restart_web.sh ├── run_jupyter.sh ├── start_mariadb.sh ├── run_web.sh ├── restart_mnist_serving.sh ├── cron.minutely │ └── run_1minute ├── run_init.sh ├── aps_job.py ├── cron.daily │ └── run_daily ├── daily_job.py ├── 18h_daily_job.py ├── quarter_job.py └── basic_job.py ├── docker ├── README.md ├── build.sh └── Dockerfile ├── docker-compose.yml ├── Dockerfile ├── nginx.conf ├── supervisor └── supervisord.conf ├── .gitignore └── startStock.sh /web/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/templates/common/footer.html: -------------------------------------------------------------------------------- 1 | {% block footer %} 2 | {% end %} -------------------------------------------------------------------------------- /old_jobs/README.md: -------------------------------------------------------------------------------- 1 | ## 说明 2 | 3 | 4 | 之前测试使用的脚本。执行了一段时间,只是用来进行练习使用的。 5 | -------------------------------------------------------------------------------- /web/static/img/支付宝--微信支付.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/支付宝--微信支付.jpg -------------------------------------------------------------------------------- /jobs/README.txt: -------------------------------------------------------------------------------- 1 | 1,计算每日买全部推荐买。 2 | 2,计算每日全部推荐卖数据。 3 | 3,设置个人账号,设置购买和卖的数据。进行关联查询。 4 | 4,最重要的沪深300,中正500数据。进行大盘股分析。 -------------------------------------------------------------------------------- /web/static/img/diff-n-bokeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/diff-n-bokeh.png -------------------------------------------------------------------------------- /web/static/img/stock-show-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/stock-show-01.jpg -------------------------------------------------------------------------------- /web/templates/layout/indicators.html: -------------------------------------------------------------------------------- 1 | {% extends "../common/meta.html" %} 2 | 3 | {% extends "indicators-main.html" %} -------------------------------------------------------------------------------- /web/static/img/minst_serving/00000.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00000.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00001.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00001.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00002.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00002.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00003.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00003.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00004.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00004.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00005.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00005.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00006.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00006.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00007.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00007.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00008.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00008.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00009.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00009.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00010.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00010.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00011.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00011.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00012.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00012.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00013.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00013.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00014.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00014.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00015.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00015.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00016.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00016.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00017.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00017.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00018.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00018.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00019.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00019.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00020.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00020.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00021.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00021.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00022.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00022.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00023.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00023.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00024.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00024.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00025.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00025.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00026.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00026.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00027.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00027.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00028.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00028.bmp -------------------------------------------------------------------------------- /web/static/img/minst_serving/00029.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/img/minst_serving/00029.bmp -------------------------------------------------------------------------------- /jobs/cron.hourly/run_hourly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p /data/logs 4 | DATE=`date +%Y-%m-%d:%H:%M:%S` 5 | echo $DATE >> /data/logs/hourly.log 6 | 7 | -------------------------------------------------------------------------------- /jobs/cron.monthly/run_monthly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p /data/logs 4 | DATE=`date +%Y-%m-%d:%H:%M:%S` 5 | echo $DATE >> /data/logs/monthly.log 6 | 7 | -------------------------------------------------------------------------------- /web/static/font-awesome/4.5.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/font-awesome/4.5.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /web/static/font-awesome/4.5.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/font-awesome/4.5.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /web/static/font-awesome/4.5.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/font-awesome/4.5.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /jobs/restart_web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ps -ef | grep python3 | grep '/data/stock/web/main.py' | awk '{print$2}' | xargs kill -9 4 | echo "restart web ... " > /data/logs/tornado.log 5 | -------------------------------------------------------------------------------- /web/templates/layout/single_default.html: -------------------------------------------------------------------------------- 1 | {% extends "../common/meta.html" %} 2 | {% extends "../common/header.html" %} 3 | {% extends "../common/footer.html" %} 4 | 5 | {% extends "single_main.html" %} -------------------------------------------------------------------------------- /web/static/font-awesome/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/font-awesome/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff -------------------------------------------------------------------------------- /web/static/font-awesome/opensans/v13/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanghyw/stock/HEAD/web/static/font-awesome/opensans/v13/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff -------------------------------------------------------------------------------- /jobs/run_jupyter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p /data/notebooks 4 | 5 | /usr/local/bin/jupyter notebook --NotebookApp.notebook_dir='/data/notebooks' --ip=0.0.0.0 \ 6 | --allow-root >> /data/logs/jupyter-notebook.log 7 | -------------------------------------------------------------------------------- /web/templates/layout/default.html: -------------------------------------------------------------------------------- 1 | {% extends "../common/meta.html" %} 2 | {% extends "../common/header.html" %} 3 | {% extends "../common/footer.html" %} 4 | {% extends "../common/left_menu.html" %} 5 | 6 | {% extends "main.html" %} -------------------------------------------------------------------------------- /jobs/start_mariadb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DATE=`date +%Y-%m-%d:%H:%M:%S` 4 | echo $DATE 5 | 6 | if [ ! -d "/data/mariadb" ]; then 7 | mkdir -p /data/mariadb 8 | /usr/bin/mysql_install_db 9 | fi 10 | 11 | 12 | /usr/bin/mysqld_safe >> /data/logs/start_mariadb.log -------------------------------------------------------------------------------- /jobs/run_web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PYTHONIOENCODING=utf-8 4 | export LANG=zh_CN.UTF-8 5 | export PYTHONPATH=/data/stock 6 | export LC_CTYPE=zh_CN.UTF-8 7 | 8 | echo "" > /data/logs/web.log 9 | /usr/local/bin/python3 /data/stock/web/main.py -log_file_prefix=/data/logs/web.log -------------------------------------------------------------------------------- /web/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/default.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 |

欢迎使用股票系统。

7 | 8 |
9 |
10 |
11 | {% end %} -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | 2 | # python 基础镜像 3 | 4 | 基础镜像升级到 2020年7月的版本 5 | 6 | 保证运行的最少基础环境,基础环境使用python3.6的版本。安装了超级多的lib库。非常的好用。 7 | 8 | mysqlclient 9 | sqlalchemy 10 | requests 11 | numpy 12 | tushare 13 | tornado torndb 14 | bokeh 15 | stockstats 16 | ta-lib 17 | jupyter 18 | sklearn 19 | 20 | # -------------------------------------------------------------------------------- /jobs/restart_mnist_serving.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ps -ef | grep 'tensorflow_model_server' | grep -v grep | awk '{print$2}' | xargs kill -9 4 | echo "" > /data/logs/mnist_serving.log 5 | nohup tensorflow_model_server --model_name=mnist --model_base_path=/data/mnist_model >> /data/logs/mnist_serving.log & -------------------------------------------------------------------------------- /jobs/cron.minutely/run_1minute: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p /data/logs 4 | DATE=`date +%Y-%m-%d:%H:%M:%S` 5 | echo $DATE >> /data/logs/1min.log 6 | echo MYSQL_HOST $MYSQL_HOST >> /data/logs/1min.log 7 | echo MYSQL_USER $MYSQL_USER >> /data/logs/1min.log 8 | echo MYSQL_DB $MYSQL_DB >> /data/logs/1min.log 9 | -------------------------------------------------------------------------------- /web/templates/stock_chart.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/default.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 |

欢迎使用股票系统。

7 | 8 |
9 |
10 | 11 |
12 | {% end %} -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | NOW_MONTH=$(date "+%Y-%m") 5 | 6 | DOCKER_TAG=pythonstock/pythonstock:base-${NOW_MONTH} 7 | 8 | echo " docker build -f Dockerfile -t ${DOCKER_TAG} ." 9 | docker build -f Dockerfile -t ${DOCKER_TAG} . 10 | echo "#################################################################" 11 | echo " docker push ${DOCKER_TAG} " 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/templates/common/header.html: -------------------------------------------------------------------------------- 1 | {% block header %} 2 | 12 | {% end %} -------------------------------------------------------------------------------- /web/static/css/fonts.googleapis.com.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 300; 5 | src: local('Open Sans Light'), local('OpenSans-Light'), url(/static/font-awesome/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff) format('woff'); 6 | } 7 | @font-face { 8 | font-family: 'Open Sans'; 9 | font-style: normal; 10 | font-weight: 400; 11 | src: local('Open Sans'), local('OpenSans'), url(/static/font-awesome/opensans/v13/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff) format('woff'); 12 | } 13 | -------------------------------------------------------------------------------- /web/templates/bokeh_embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Embedding a Bokeh Server With {{ framework }} 7 | 8 | 9 | 10 |
11 | This Bokeh app below served by a Bokeh server that has been embedded 12 | in another web app framework. For more information see the section 13 | 15 | Embedding Bokeh Server as a Library 16 | in the User's Guide. 17 |
18 | {{ script|safe }} 19 | 20 | -------------------------------------------------------------------------------- /web/templates/layout/indicators-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 股票系统 7 | {% block meta %}{% end %} 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 | {% block main_content %}{% end %} 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /web/static/js/bootstrap-datepicker.zh-CN.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplified Chinese translation for bootstrap-datepicker 3 | * Yuan Cheung 4 | */ 5 | ;(function($){ 6 | $.fn.datepicker.dates['zh-CN'] = { 7 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"], 8 | daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"], 9 | daysMin: ["日", "一", "二", "三", "四", "五", "六"], 10 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 11 | monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"], 12 | today: "今日", 13 | clear: "清除", 14 | format: "yyyy年mm月dd日", 15 | titleFormat: "yyyy年mm月", 16 | weekStart: 1 17 | }; 18 | }(jQuery)); -------------------------------------------------------------------------------- /web/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import tornado.web 5 | import libs.stock_web_dic as stock_web_dic 6 | 7 | #基础handler,主要负责检查mysql的数据库链接。 8 | class BaseHandler(tornado.web.RequestHandler): 9 | @property 10 | def db(self): 11 | try: 12 | # check every time。 13 | self.application.db.query("SELECT 1 ") 14 | except Exception as e: 15 | print(e) 16 | self.application.db.reconnect() 17 | return self.application.db 18 | 19 | class LeftMenu: 20 | def __init__(self, url): 21 | self.leftMenuList = stock_web_dic.STOCK_WEB_DATA_LIST 22 | self.current_url = url 23 | 24 | # 获得左菜单。 25 | def GetLeftMenu(url): 26 | return LeftMenu(url) 27 | -------------------------------------------------------------------------------- /web/static/js/datatables.Chinese.json: -------------------------------------------------------------------------------- 1 | { 2 | "sProcessing": "处理中...", 3 | "sLengthMenu": "显示 _MENU_ 项结果", 4 | "sZeroRecords": "没有匹配结果", 5 | "sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项", 6 | "sInfoEmpty": "显示第 0 至 0 项结果,共 0 项", 7 | "sInfoFiltered": "(由 _MAX_ 项结果过滤)", 8 | "sInfoPostFix": "", 9 | "sSearch": "搜索:", 10 | "sUrl": "", 11 | "sEmptyTable": "表中数据为空", 12 | "sLoadingRecords": "载入中...", 13 | "sInfoThousands": ",", 14 | "oPaginate": { 15 | "sFirst": "首页", 16 | "sPrevious": "上页", 17 | "sNext": "下页", 18 | "sLast": "末页" 19 | }, 20 | "oAria": { 21 | "sSortAscending": ": 以升序排列此列", 22 | "sSortDescending": ": 以降序排列此列" 23 | } 24 | } -------------------------------------------------------------------------------- /jobs/run_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export PYTHONIOENCODING=utf-8 4 | export LANG=zh_CN.UTF-8 5 | export PYTHONPATH=/data/stock 6 | export LC_CTYPE=zh_CN.UTF-8 7 | 8 | mkdir -p /data/logs/tensorflow 9 | 10 | 11 | 12 | DATE=`date +%Y-%m-%d:%H:%M:%S` 13 | 14 | echo $DATE >> /data/logs/run_init.log 15 | 16 | echo "wait 120 second , mysqldb is starting ." >> /data/logs/run_init.log 17 | sleep 120 18 | 19 | /usr/local/bin/python3 /data/stock/jobs/basic_job.py >> /data/logs/run_init.log 20 | 21 | # https://stackoverflow.com/questions/27771781/how-can-i-access-docker-set-environment-variables-from-a-cron-job 22 | # 解决环境变量输出问题。 23 | printenv | grep -v "no_proxy" >> /etc/environment 24 | 25 | # 第一次后台执行日数据。 26 | nohup bash /data/stock/jobs/cron.daily/run_daily & 27 | 28 | #启动cron服务。在前台 29 | /usr/sbin/cron -f -------------------------------------------------------------------------------- /jobs/aps_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from pytz import utc 4 | from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore 5 | from apscheduler.schedulers.blocking import BlockingScheduler 6 | 7 | from apscheduler.executors.pool import ProcessPoolExecutor 8 | import libs.common as common 9 | 10 | # doc : http://apscheduler.readthedocs.io/en/latest/modules/jobstores/sqlalchemy.html 11 | jobstores = { 12 | 'default': SQLAlchemyJobStore(url=common.MYSQL_CONN_URL, tablename='apscheduler_jobs') 13 | } 14 | executors = { 15 | 'default': {'type': 'threadpool', 'max_workers': 20}, 16 | 'processpool': ProcessPoolExecutor(max_workers=5) 17 | } 18 | job_defaults = { 19 | 'coalesce': False, 20 | 'max_instances': 3 21 | } 22 | scheduler = BlockingScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc) 23 | scheduler.start() 24 | print("start ...") 25 | -------------------------------------------------------------------------------- /web/templates/layout/single_main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block meta %}{% end %} 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 | {% block main_content %}{% end %} 18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /jobs/cron.daily/run_daily: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p /data/logs 4 | DATETIME=`date +%Y-%m-%d:%H:%M:%S` 5 | 6 | DATE=`date +%Y-%m-%d` 7 | 8 | export PYTHONIOENCODING=utf-8 9 | export LANG=zh_CN.UTF-8 10 | export PYTHONPATH=/data/stock 11 | export LC_CTYPE=zh_CN.UTF-8 12 | 13 | 14 | echo "###################"$DATETIME"###################" >> /data/logs/daily.${DATE}.log 15 | #增加每日,龙虎榜数据。 16 | /usr/local/bin/python3 /data/stock/jobs/daily_job.py >> /data/logs/daily.${DATE}.log 17 | 18 | echo "###################"$DATETIME"###################" >> /data/logs/daily.${DATE}.log 19 | #增加获得今日全部数据和大盘数据 20 | /usr/local/bin/python3 /data/stock/jobs/18h_daily_job.py >> /data/logs/daily.${DATE}.log 21 | 22 | echo "###################"$DATETIME"###################" >> /data/logs/daily.${DATE}.log 23 | #使用股票指标预测。 24 | /usr/local/bin/python3 /data/stock/jobs/guess_indicators_daily_job.py >> /data/logs/daily.${DATE}.log 25 | 26 | #清除前3天数据。 27 | DATE_20=`date -d '-20 days' +%Y-%m-%d` 28 | MONTH_20=`date -d '-20 days' +%Y-%m` 29 | echo "rm -f /data/cache/hist_data_cache/${MONTH_20}/${DATETIME_20}" 30 | rm -f /data/cache/hist_data_cache/${MONTH_20}/${DATETIME_20} -------------------------------------------------------------------------------- /web/templates/layout/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 股票系统 7 | {% block meta %}{% end %} 8 | 9 | 10 | 11 | 12 | {% block header %}{% end %} 13 | 14 |
15 | 18 | 19 | {% block left_menu %}{% end %} 20 | 21 |
22 |
23 |
24 |
25 |
26 | {% block main_content %}{% end %} 27 |
28 |
29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | stock: 4 | image: pythonstock/pythonstock:latest 5 | container_name: stock 6 | ports: 7 | - "8888:8888" 8 | - "9999:9999" 9 | volumes: 10 | - "./data/notebooks:/data/notebooks" 11 | - "./data/logs:/data/logs" 12 | environment: 13 | MYSQL_HOST: mysqldb 14 | MYSQL_USER: root 15 | MYSQL_PWD: mysqldb 16 | MYSQL_DB: stock_data 17 | LANG: en_US.UTF-8 18 | LC_ALL: en_US.UTF-8 19 | # 测试使用,自己需注册,申请:https://tushare.pro/user/token 20 | TUSHARE_TOKEN: 007b2f24bc3afb5ff5c604b0aee583956840210348169bc2436bddf9 21 | links: 22 | - mysqldb:mysqldb 23 | restart: always 24 | mysqldb: 25 | image: mysql:5.7 26 | container_name: mysqldb 27 | ports: 28 | - "3306:3306" 29 | volumes: 30 | - "./data/mysqldb/data:/var/lib/mysql" 31 | environment: 32 | MYSQL_ROOT_PASSWORD: mysqldb 33 | MYSQL_DATABASE: stock_data 34 | TZ: Asia/Shanghai 35 | restart: always 36 | 37 | -------------------------------------------------------------------------------- /web/templates/stock_indicators.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/indicators.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | {% for index,element in enumerate(comp_list) %} 23 |

{{ element["title"] }}

24 |
{{ element["desc"] }}
25 |
26 | {% raw element["div"] %} 27 | {% raw element["script"] %} 28 |
29 | {% end %} 30 | 31 | {% end %} -------------------------------------------------------------------------------- /jobs/daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import sys 7 | import os 8 | import time 9 | import pandas as pd 10 | import tushare as ts 11 | from sqlalchemy.types import NVARCHAR 12 | from sqlalchemy import inspect 13 | import datetime 14 | import shutil 15 | 16 | 17 | ####### 使用 5.pdf,先做 基本面数据 的数据,然后在做交易数据。 18 | # 19 | def stat_all(tmp_datetime): 20 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 21 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 22 | 23 | cache_dir = common.bash_stock_tmp % (datetime_str[0:7], datetime_str) 24 | if os.path.exists(cache_dir): 25 | shutil.rmtree(cache_dir) 26 | print("remove cache dir force :", cache_dir) 27 | 28 | print("datetime_str:", datetime_str) 29 | print("datetime_int:", datetime_int) 30 | data = ts.top_list(datetime_str) 31 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 32 | # 33 | if not data is None and len(data) > 0: 34 | # 插入数据库。 35 | # del data["reason"] 36 | data["date"] = datetime_int # 修改时间成为int类型。 37 | data = data.drop_duplicates(subset="code", keep="last") 38 | data.head(n=1) 39 | common.insert_db(data, "ts_top_list", False, "`date`,`code`") 40 | else: 41 | print("no data .") 42 | 43 | print(datetime_str) 44 | 45 | 46 | # main函数入口 47 | if __name__ == '__main__': 48 | # 使用方法传递。 49 | tmp_datetime = common.run_with_args(stat_all) 50 | -------------------------------------------------------------------------------- /web/chartHandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from tornado import gen 6 | import libs.stock_web_dic as stock_web_dic 7 | import web.base as webBase 8 | import logging 9 | import tornado.web 10 | import matplotlib 11 | matplotlib.use('Agg') 12 | import matplotlib.pyplot as plt 13 | import numpy as np 14 | import io 15 | 16 | def GenImage(freq): 17 | t = np.linspace(0, 10, 500) 18 | y = np.sin(t * freq * 2 * 3.141) 19 | fig1 = plt.figure() 20 | plt.plot(t, y) 21 | plt.xlabel('Time [s]') 22 | memdata = io.BytesIO() 23 | plt.grid(True) 24 | plt.savefig(memdata, format='png') 25 | image = memdata.getvalue() 26 | return image 27 | 28 | 29 | class ImageHandler(tornado.web.RequestHandler): 30 | @gen.coroutine 31 | def get(self): 32 | image = GenImage(0.5) 33 | self.set_header('Content-type', 'image/png') 34 | self.set_header('Content-length', len(image)) 35 | self.write(image) 36 | 37 | # 获得页面数据。 38 | class GetChartHtmlHandler(webBase.BaseHandler): 39 | @gen.coroutine 40 | def get(self): 41 | name = self.get_argument("table_name", default=None, strip=False) 42 | #stockWeb = stock_web_dic.STOCK_WEB_DATA_MAP[name] 43 | # self.uri_ = ("self.request.url:", self.request.uri) 44 | # print self.uri_ 45 | logging.info("chart...") 46 | self.render("stock_chart.html", entries="", leftMenu=webBase.GetLeftMenu(self.request.uri)) 47 | 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 拆分基础镜像: docker/dockerfile 2 | 3 | 4 | # 基础镜像,按照季度,月度更新。不影响应用镜像的构建。 5 | 6 | FROM pythonstock/pythonstock:base-2020-07 7 | 8 | WORKDIR /data 9 | 10 | #add cron sesrvice. 11 | #每分钟,每小时1分钟,每天1点1分,每月1号执行 12 | RUN mkdir -p /etc/cron.minutely && mkdir -p /etc/cron.hourly && mkdir -p /etc/cron.monthly && \ 13 | echo "SHELL=/bin/sh \n\ 14 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin \n\ 15 | # min hour day month weekday command \n\ 16 | */1 * * * * /bin/run-parts /etc/cron.minutely \n\ 17 | 10 * * * * /bin/run-parts /etc/cron.hourly \n\ 18 | 30 16 * * * /bin/run-parts /etc/cron.daily \n\ 19 | 30 17 1,10,20 * * /bin/run-parts /etc/cron.monthly \n" > /var/spool/cron/crontabs/root && \ 20 | chmod 600 /var/spool/cron/crontabs/root 21 | 22 | 23 | #增加服务端口就两个 6006 8500 9001 24 | EXPOSE 8888 9999 25 | 26 | #经常修改放到最后: 27 | ADD jobs /data/stock/jobs 28 | ADD libs /data/stock/libs 29 | ADD web /data/stock/web 30 | ADD supervisor /data/supervisor 31 | 32 | ADD jobs/cron.minutely /etc/cron.minutely 33 | ADD jobs/cron.hourly /etc/cron.hourly 34 | ADD jobs/cron.daily /etc/cron.daily 35 | ADD jobs/cron.monthly /etc/cron.monthly 36 | 37 | RUN mkdir -p /data/logs && ls /data/stock/ && chmod 755 /data/stock/jobs/run_* && \ 38 | chmod 755 /etc/cron.minutely/* && chmod 755 /etc/cron.hourly/* && \ 39 | chmod 755 /etc/cron.daily/* && chmod 755 /etc/cron.monthly/* 40 | 41 | ENTRYPOINT ["supervisord","-n","-c","/data/supervisor/supervisord.conf"] -------------------------------------------------------------------------------- /web/templates/test2.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/default.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 |

欢迎使用股票系统。

7 | 8 |
9 |
10 |
11 | 12 |
13 | 17 |
18 | 19 | 20 | 21 | 22 | 34 | 35 | 43 | 44 | {% end %} -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | # 设置nginx启动 2 | # systemctl enable nginx 3 | server { 4 | listen 8080; 5 | server_name www.pythonstock.com; 6 | root /usr/share/nginx/html; 7 | 8 | error_page 404 /404.html; 9 | error_page 500 502 503 504 /50x.html; 10 | 11 | # 开启gzip 12 | gzip on; 13 | # 启用gzip压缩的最小文件;小于设置值的文件将不会被压缩 14 | gzip_min_length 1k; 15 | # gzip 压缩级别 1-10 16 | gzip_comp_level 2; 17 | # 进行压缩的文件类型。 18 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 19 | # 是否在http header中添加Vary: Accept-Encoding,建议开启 20 | gzip_vary on; 21 | 22 | access_log /var/log/nginx/stock.access.log main; 23 | error_log /var/log/nginx/stock.error.log warn; 24 | 25 | # https://medium.com/aviabird/413-414-request-url-entity-too-large-error-nginx-b6dcece6f5dd 26 | # 解决GET 参数过长问题。 27 | client_max_body_size 10M; 28 | large_client_header_buffers 4 20k; 29 | 30 | 31 | location ^~ /static/ { 32 | access_log off; 33 | # 需要把源代码,再下载一遍。 34 | root /data/pythonstock/stock/web; 35 | expires 30d; # 设置30天超时 36 | # 参考 https://www.cnblogs.com/kevingrace/p/10459429.html 37 | # add_header Cache-Control max-age=360000; 38 | } 39 | 40 | location / { 41 | proxy_next_upstream http_500 http_502 http_503 http_504 error timeout invalid_header; 42 | proxy_set_header Host $host; 43 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 44 | proxy_pass http://127.0.0.1:9999; 45 | expires 0; 46 | } 47 | } -------------------------------------------------------------------------------- /web/static/js/buttons.print.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Print button for Buttons and DataTables. 3 | * 2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | !function(a){"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(b){return a(b,window,document)}):"object"==typeof exports?module.exports=function(b,c){return b||(b=window),c&&c.fn.dataTable||(c=require("datatables.net")(b,c).$),c.fn.dataTable.Buttons||require("datatables.net-buttons")(b,c),a(c,b,b.document)}:a(jQuery,window,document)}(function(a,b,c,d){"use strict";var e=a.fn.dataTable,f=c.createElement("a"),g=function(b){var c,d=a(b).clone()[0];return"link"===d.nodeName.toLowerCase()&&(f.href=d.href,c=f.host,-1===c.indexOf("/")&&0!==f.pathname.indexOf("/")&&(c+="/"),d.href=f.protocol+"//"+c+f.pathname+f.search),d.outerHTML};return e.ext.buttons.print={className:"buttons-print",text:function(a){return a.i18n("buttons.print","Print")},action:function(c,d,e,f){var h=d.buttons.exportData(f.exportOptions),i=function(a,b){for(var c="",d=0,e=a.length;e>d;d++)c+="<"+b+">"+a[d]+"";return c+""},j='';f.header&&(j+=""+i(h.header,"th")+""),j+="";for(var k=0,l=h.body.length;l>k;k++)j+=i(h.body[k],"td");j+="",f.footer&&(j+=""+i(h.footer,"th")+"");var m=b.open("",""),n=f.title;"function"==typeof n&&(n=n()),-1!==n.indexOf("*")&&(n=n.replace("*",a("title").text())),m.document.close();var o=""+n+"";a("style, link").each(function(){o+=g(this)}),a(m.document.head).html(o),a(m.document.body).html("

"+n+"

"+f.message+"
"+j),f.customize&&f.customize(m),setTimeout(function(){f.autoPrint&&(m.print(),m.close())},250)},title:"*",message:"",exportOptions:{},header:!0,footer:!1,autoPrint:!0,customize:null},e.Buttons}); -------------------------------------------------------------------------------- /supervisor/supervisord.conf: -------------------------------------------------------------------------------- 1 | [unix_http_server] 2 | file=/tmp/supervisor.sock ; the path to the socket file 3 | 4 | [inet_http_server] ; inet (TCP) server disabled by default 5 | port=*:9001 ; ip_address:port specifier, *:port for all iface 6 | ;username=user ; default is no username (open server) 7 | ;password=123 ; default is no password (open server) 8 | 9 | [supervisord] 10 | logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log 11 | logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB 12 | logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 13 | loglevel=info ; log level; default info; others: debug,warn,trace 14 | pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid 15 | nodaemon=false ; start in foreground if true; default false 16 | minfds=1024 ; min. avail startup file descriptors; default 1024 17 | minprocs=200 ; min. avail process descriptors;default 200 18 | 19 | [rpcinterface:supervisor] 20 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 21 | 22 | [supervisorctl] 23 | serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket 24 | 25 | [program:stock-web] 26 | command=/data/stock/jobs/run_web.sh 27 | autostart=true 28 | autorestart=true 29 | startsecs=20 30 | priority=1 31 | stopasgroup=true 32 | killasgroup=true 33 | 34 | [program:init_and_cron] 35 | command=/data/stock/jobs/run_init.sh 36 | autostart=true 37 | autorestart=true 38 | startsecs=20 39 | priority=1 40 | stopasgroup=true 41 | killasgroup=true 42 | 43 | 44 | [program:jupyter] 45 | command=/data/stock/jobs/run_jupyter.sh 46 | autostart=true 47 | autorestart=true 48 | startsecs=20 49 | priority=1 50 | stopasgroup=true 51 | killasgroup=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | data 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | notebooks 31 | nohup.out 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # dotenv 88 | .env 89 | 90 | # virtualenv 91 | .venv 92 | venv/ 93 | ENV/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | .idea 109 | *.iml 110 | .DS_Store 111 | *.zip 112 | *.log 113 | *.pyc 114 | doc 115 | /bin 116 | pkg 117 | *.tmp -------------------------------------------------------------------------------- /web/templates/test.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/default.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 |

欢迎使用股票系统。

7 | 8 |
9 |
10 |
11 | 12 |
13 | 17 |
18 | 19 | 20 | 21 | 22 | 34 | 35 | 51 | 52 | {% end %} -------------------------------------------------------------------------------- /web/static/js/draw.js: -------------------------------------------------------------------------------- 1 | 2 | var drawing = false; 3 | 4 | var context; 5 | 6 | var offset_left = 0; 7 | var offset_top = 0; 8 | 9 | 10 | function start_canvas () 11 | { 12 | var scribbler = document.getElementById ("the_stage"); 13 | context = scribbler.getContext ("2d"); 14 | scribbler.onmousedown = function (event) {mousedown(event)}; 15 | scribbler.onmousemove = function (event) {mousemove(event)}; 16 | scribbler.onmouseup = function (event) {mouseup(event)}; 17 | for (var o = scribbler; o ; o = o.offsetParent) { 18 | offset_left += (o.offsetLeft - o.scrollLeft); 19 | offset_top += (o.offsetTop - o.scrollTop); 20 | } 21 | draw(); 22 | } 23 | 24 | function getPosition(evt) 25 | { 26 | evt = (evt) ? evt : ((event) ? event : null); 27 | var left = 0; 28 | var top = 0; 29 | var scribbler = document.getElementById("the_stage"); 30 | 31 | if (evt.pageX) { 32 | left = evt.pageX; 33 | top = evt.pageY; 34 | } else if (document.documentElement.scrollLeft) { 35 | left = evt.clientX + document.documentElement.scrollLeft; 36 | top = evt.clientY + document.documentElement.scrollTop; 37 | } else { 38 | left = evt.clientX + document.body.scrollLeft; 39 | top = evt.clientY + document.body.scrollTop; 40 | } 41 | left -= offset_left; 42 | top -= offset_top; 43 | 44 | return {x : left, y : top}; 45 | } 46 | 47 | function 48 | mousedown(event) 49 | { 50 | drawing = true; 51 | var location = getPosition(event); 52 | context.lineWidth = 20.0; 53 | context.strokeStyle="#000000"; 54 | context.beginPath(); 55 | context.moveTo(location.x,location.y); 56 | } 57 | 58 | 59 | function 60 | mousemove(event) 61 | { 62 | if (!drawing) 63 | return; 64 | var location = getPosition(event); 65 | context.lineTo(location.x,location.y); 66 | context.stroke(); 67 | } 68 | 69 | 70 | 71 | function 72 | mouseup(event) 73 | { 74 | if (!drawing) 75 | return; 76 | mousemove(event); 77 | drawing = false; 78 | } 79 | 80 | function draw() 81 | { 82 | 83 | context.fillStyle = '#ffffff'; 84 | context.fillRect(0, 0, 400, 400); 85 | 86 | } 87 | 88 | function clearCanvas() 89 | { 90 | context.clearRect (0, 0, 400, 400); 91 | draw(); 92 | document.getElementById("rec_result").innerHTML = ""; 93 | } 94 | 95 | onload = start_canvas; 96 | 97 | -------------------------------------------------------------------------------- /web/tornado_bokeh_embed.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, FileSystemLoader 2 | 3 | from tornado.web import RequestHandler 4 | 5 | from bokeh.application import Application 6 | from bokeh.application.handlers import FunctionHandler 7 | from bokeh.embed import server_document 8 | from bokeh.layouts import column 9 | from bokeh.models import ColumnDataSource, Slider 10 | from bokeh.plotting import figure 11 | from bokeh.server.server import Server 12 | from bokeh.themes import Theme 13 | 14 | from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature 15 | 16 | env = Environment(loader=FileSystemLoader('templates')) 17 | server_url = "http://localhost:9999/" 18 | 19 | 20 | class IndexHandler(RequestHandler): 21 | def get(self): 22 | print("index ...") 23 | template = env.get_template('bokeh_embed.html') 24 | script = server_document(server_url + 'bkapp') 25 | print(script) 26 | self.write(template.render(script=script, template="Tornado")) 27 | # self.write(html_content) 28 | 29 | 30 | def modify_doc(doc): 31 | df = sea_surface_temperature.copy() 32 | source = ColumnDataSource(data=df) 33 | 34 | plot = figure(x_axis_type='datetime', y_range=(0, 25), y_axis_label='Temperature (Celsius)', 35 | title="Sea Surface Temperature at 43.18, -70.43") 36 | plot.line('time', 'temperature', source=source) 37 | 38 | def callback(attr, old, new): 39 | if new == 0: 40 | data = df 41 | else: 42 | data = df.rolling('{0}D'.format(new)).mean() 43 | source.data = ColumnDataSource(data=data).data 44 | 45 | slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days") 46 | slider.on_change('value', callback) 47 | 48 | doc.add_root(column(slider, plot)) 49 | 50 | # doc.theme = Theme(filename="theme.yaml") 51 | 52 | 53 | bokeh_app = Application(FunctionHandler(modify_doc)) 54 | 55 | # Setting num_procs here means we can't touch the IOLoop before now, we must 56 | # let Server handle that. If you need to explicitly handle IOLoops then you 57 | # will need to use the lower level BaseServer class. 58 | server = Server( 59 | {'/bkapp': bokeh_app}, num_procs=1, port=9999, 60 | extra_patterns=[('/', IndexHandler)] 61 | ) 62 | server.start() 63 | 64 | if __name__ == '__main__': 65 | from bokeh.util.browser import view 66 | 67 | print('Opening Tornado app with embedded Bokeh application on ' + server_url) 68 | 69 | server.io_loop.add_callback(view, server_url) 70 | server.io_loop.start() 71 | -------------------------------------------------------------------------------- /web/templates/minst_serving.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/single_default.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 | 7 |

手写图片识别演示

8 | 9 |
10 |
11 | your browser don't support canvas! 12 |
13 | 14 | 15 |
16 |
17 |
18 |

result:

19 |

20 |
21 |
22 | 23 |

测试图片识别演示

24 |
25 | {% for image in image_array %} 26 |
27 | 28 |
29 | {% end %} 30 |
31 |
32 |

结果:

33 |

"-1"

34 |
35 | 36 | 37 | 44 | 80 | {% end %} -------------------------------------------------------------------------------- /startStock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PWD=`pwd` 4 | 5 | DB_IS_RUN=`docker ps --filter "name=mariadb" --filter "status=running" | wc -l ` 6 | if [ $DB_IS_RUN -lt 2 ]; then 7 | 8 | #判断文件夹存在不。 9 | if [ ! -d "/data/mysqldb/data" ]; then 10 | mkdir -p /data/mysqldb/data 11 | fi 12 | 13 | HAS_DB=`docker images mysql:5.7 | wc -l ` 14 | if [ $HAS_DB -ne 2 ];then 15 | docker pull mysql:5.7 16 | fi 17 | 18 | ####################### 启动数据库 ####################### 19 | #检查mysqldb是否启动 20 | DB_IS_RUN=`docker ps --filter "name=mysqldb" --filter "status=running" | wc -l ` 21 | 22 | if [ $DB_IS_RUN -ne 2 ]; then 23 | docker run --name mysqldb -v ${PWD}/data/mysqldb/data:/var/lib/mysql --restart=always \ 24 | -e MYSQL_ROOT_PASSWORD=mysqldb -e MYSQL_DATABASE=stock_data -e TZ=Asia/Shanghai \ 25 | -p 3306:3306 -d mysql:5.7 26 | echo "starting mysqldb ..." 27 | else 28 | echo "mysqldb is running !!!" 29 | fi 30 | 31 | ####################### 创建数据库 ####################### 32 | echo "wait 120 second , mysqldb is starting ." 33 | sleep 120 34 | #检查mysqldb是否启动,等待5秒钟,再次检查mysqldb启动 35 | DB_IS_RUN=`docker ps --filter "name=mysqldb" --filter "status=running" | wc -l ` 36 | if [ $DB_IS_RUN -ne 2 ]; then 37 | echo "mysqldb is not running !!!" 38 | exit 1; 39 | fi 40 | fi 41 | 42 | #检查stock启动 43 | STOCK_IS_RUN=`docker ps --filter "name=stock" --filter "status=running" | wc -l ` 44 | if [ $STOCK_IS_RUN -ge 2 ]; then 45 | echo "stop & rm stock ..." 46 | docker stop stock && docker rm stock 47 | fi 48 | 49 | sleep 1 50 | 51 | echo "starting stock ..." 52 | # 1 是开发环境。映射本地代码。 53 | if [ $# == 1 ] ; then 54 | echo "############# run dev ############# " 55 | # /data/stock 是代码目录 -v /data/stock:/data/stock 是开发模式。 56 | mkdir -p notebooks 57 | 58 | docker run -itd --link=mysqldb --name stock \ 59 | -p 8888:8888 -p 9999:9999 --restart=always \ 60 | -v ${PWD}/jobs:/data/stock/jobs \ 61 | -v ${PWD}/libs:/data/stock/libs \ 62 | -v ${PWD}/web:/data/stock/web \ 63 | -v ${PWD}/supervisor:/data/supervisor \ 64 | -v ${PWD}/notebooks:/data/notebooks \ 65 | -v ${PWD}/data/logs:/data/logs \ 66 | pythonstock/pythonstock:latest 67 | exit 1; 68 | else 69 | echo "############# run online ############# " 70 | # /data/stock 是代码目录 -v /data/stock:/data/stock 是开发模式。 71 | docker run -itd --link=mysqldb --name stock \ 72 | -p 8888:8888 -p 9999:9999 --restart=always \ 73 | pythonstock/pythonstock:latest 74 | exit 1; 75 | fi 76 | 77 | -------------------------------------------------------------------------------- /jobs/18h_daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import sys 7 | import time 8 | import pandas as pd 9 | import tushare as ts 10 | from sqlalchemy.types import NVARCHAR 11 | from sqlalchemy import inspect 12 | import datetime 13 | 14 | """ 15 | 交易数据 16 | 17 | http://tushare.org/trading.html#id2 18 | 19 | 股市交易时间为每周一到周五上午时段9:30-11:30,下午时段13:00-15:00。 周六、周日上海证券交易所、深圳证券交易所公告的休市日不交易。 20 | 21 | """ 22 | 23 | def stat_index_all(tmp_datetime): 24 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 25 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 26 | print("datetime_str:", datetime_str) 27 | print("datetime_int:", datetime_int) 28 | 29 | 30 | data = ts.get_index() 31 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 32 | if not data is None and len(data) > 0: 33 | # 插入数据库。 34 | # del data["reason"] 35 | data["date"] = datetime_int # 修改时间成为int类型。 36 | data = data.drop_duplicates(subset="code", keep="last") 37 | data.head(n=1) 38 | common.insert_db(data, "ts_index_all", False, "`date`,`code`") 39 | else: 40 | print("no data .") 41 | 42 | print(datetime_str) 43 | 44 | def stat_today_all(tmp_datetime): 45 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 46 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 47 | print("datetime_str:", datetime_str) 48 | print("datetime_int:", datetime_int) 49 | data = ts.get_today_all() 50 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 51 | if not data is None and len(data) > 0: 52 | # 插入数据库。 53 | # del data["reason"] 54 | data["date"] = datetime_int # 修改时间成为int类型。 55 | data = data.drop_duplicates(subset="code", keep="last") 56 | data.head(n=1) 57 | common.insert_db(data, "ts_today_all", False, "`date`,`code`") 58 | else: 59 | print("no data .") 60 | 61 | time.sleep(5) # 停止5秒 62 | 63 | data = ts.get_index() 64 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 65 | if not data is None and len(data) > 0: 66 | # 插入数据库。 67 | # del data["reason"] 68 | data["date"] = datetime_int # 修改时间成为int类型。 69 | data = data.drop_duplicates(subset="code", keep="last") 70 | data.head(n=1) 71 | common.insert_db(data, "ts_index_all", False, "`date`,`code`") 72 | else: 73 | print("no data .") 74 | 75 | print(datetime_str) 76 | 77 | 78 | # main函数入口 79 | if __name__ == '__main__': 80 | # 使用方法传递。 81 | tmp_datetime = common.run_with_args(stat_index_all) 82 | time.sleep(5) # 停止5秒 83 | tmp_datetime = common.run_with_args(stat_today_all) 84 | -------------------------------------------------------------------------------- /web/minstServingHandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import os.path 4 | import json 5 | import subprocess 6 | import torndb 7 | import tornado.escape 8 | from tornado import gen 9 | import tornado.httpserver 10 | import tornado.ioloop 11 | import tornado.options 12 | import tornado.web 13 | import web.base as webBase 14 | import logging 15 | import numpy as np 16 | from PIL import Image 17 | from PIL import ImageOps 18 | import base64 19 | import io #python2 import StringIO 20 | 21 | work_dir = "/data/stock/tf/minst_serving/input_data" 22 | out_dir = "/static/img/minst_serving/%s.bmp" 23 | 24 | 25 | # 获得页面数据。 26 | class GetMinstServingHtmlHandler(webBase.BaseHandler): 27 | @gen.coroutine 28 | def get(self): 29 | # print self.uri_ 30 | arr = np.arange(30) 31 | image_array = [] 32 | for idx in arr: 33 | out_file = out_dir % ("%05d" % idx) 34 | print(out_file) 35 | image_array.append(out_file) 36 | self.render("minst_serving.html", image_array=image_array) 37 | 38 | 39 | # 获得股票数据内容。 40 | class GetPredictionDataHandler(webBase.BaseHandler): 41 | def get(self): 42 | # 获得分页参数。 43 | img_url = self.get_argument("img_url", default=0, strip=False) 44 | print(img_url) 45 | img_obj = Image.open("/data/stock/web" + img_url) 46 | print("img_obj", img_obj) 47 | server = "0.0.0.0:8500" 48 | prediction = do_inference(server, img_obj) 49 | print('######### prediction : ', prediction) 50 | self.write(json.dumps(prediction)) 51 | 52 | 53 | # 获得股票数据内容。 54 | class GetPrediction2DataHandler(webBase.BaseHandler): 55 | def post(self): 56 | # 获得分页参数。 57 | imgStr = self.get_argument("txt", default="", strip=False) 58 | # imgStr.replace(" ", "+") 59 | imgStr = base64.b64decode(imgStr) 60 | print("imgStr:", type(imgStr)) 61 | image = Image.open(io.StringIO(imgStr)) 62 | image.thumbnail((28, 28), Image.ANTIALIAS) 63 | image = image.convert('L') 64 | image = ImageOps.invert(image) 65 | image.save(work_dir + "/web-tmp.bmp", format="BMP") #保存看看,是否 66 | #print(image) 67 | # img_url = self.get_argument("img_url", default=0, strip=False) 68 | # print(img_url) 69 | server = "0.0.0.0:8500" 70 | prediction = do_inference(server, image) 71 | print('######### prediction : ', prediction) 72 | self.write(json.dumps(prediction)) 73 | 74 | 75 | 76 | # 调用 grpc 代码,将图片转换成数组,让后放到 grpc 调用。 77 | def do_inference(hostport, img_obj): 78 | 79 | print("############", hostport) 80 | 81 | -------------------------------------------------------------------------------- /web/static/js/buttons.colVis.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Column visibility buttons for Buttons and DataTables. 3 | * 2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | !function(a){"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(b){return a(b,window,document)}):"object"==typeof exports?module.exports=function(b,c){return b||(b=window),c&&c.fn.dataTable||(c=require("datatables.net")(b,c).$),c.fn.dataTable.Buttons||require("datatables.net-buttons")(b,c),a(c,b,b.document)}:a(jQuery,window,document)}(function(a,b,c,d){"use strict";var e=a.fn.dataTable;return a.extend(e.ext.buttons,{colvis:function(a,b){return{extend:"collection",text:function(a){return a.i18n("buttons.colvis","Column visibility")},className:"buttons-colvis",buttons:[{extend:"columnsToggle",columns:b.columns}]}},columnsToggle:function(a,b){var c=a.columns(b.columns).indexes().map(function(a){return{extend:"columnToggle",columns:a}}).toArray();return c},columnToggle:function(a,b){return{extend:"columnVisibility",columns:b.columns}},columnsVisibility:function(a,b){var c=a.columns(b.columns).indexes().map(function(a){return{extend:"columnVisibility",columns:a,visibility:b.visibility}}).toArray();return c},columnVisibility:{columns:d,text:function(a,b,c){return c._columnText(a,c.columns)},className:"buttons-columnVisibility",action:function(a,b,c,e){var f=b.columns(e.columns),g=f.visible();f.visible(e.visibility!==d?e.visibility:!(g.length?g[0]:!1))},init:function(a,b,c){var d=this,e=a.column(c.columns);a.on("column-visibility.dt"+c.namespace,function(a,b,e,f){b.bDestroying||e!==c.columns||d.active(f)}).on("column-reorder.dt"+c.namespace,function(b,e,f){if(1===a.columns(c.columns).count()){"number"==typeof c.columns&&(c.columns=f.mapping[c.columns]);var g=a.column(c.columns);d.text(c._columnText(a,c.columns)),d.active(g.visible())}}),this.active(e.visible())},destroy:function(a,b,c){a.off("column-visibility.dt"+c.namespace).off("column-reorder.dt"+c.namespace)},_columnText:function(a,b){var c=a.column(b).index();return a.settings()[0].aoColumns[c].sTitle.replace(/\n/g," ").replace(/<.*?>/g,"").replace(/^\s+|\s+$/g,"")}},colvisRestore:{className:"buttons-colvisRestore",text:function(a){return a.i18n("buttons.colvisRestore","Restore visibility")},init:function(a,b,c){c._visOriginal=a.columns().indexes().map(function(b){return a.column(b).visible()}).toArray()},action:function(a,b,c,d){b.columns().every(function(a){var c=b.colReorder&&b.colReorder.transpose?b.colReorder.transpose(a,"toOriginal"):a;this.visible(d._visOriginal[c])})}},colvisGroup:{className:"buttons-colvisGroup",action:function(a,b,c,d){b.columns(d.show).visible(!0),b.columns(d.hide).visible(!1)},show:[],hide:[]}}),e.Buttons}); -------------------------------------------------------------------------------- /web/templates/common/left_menu.html: -------------------------------------------------------------------------------- 1 | {% block left_menu %} 2 | 64 | {% end %} -------------------------------------------------------------------------------- /web/templates/common/meta.html: -------------------------------------------------------------------------------- 1 | {% block meta %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 68 | 71 | {% end %} -------------------------------------------------------------------------------- /web/static/css/jquery-ui.custom.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.11.4 - 2015-09-20 2 | * http://jqueryui.com 3 | * Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, slider.css 4 | * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:after,.ui-helper-clearfix:before{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-autohide .ui-resizable-handle,.ui-resizable-disabled .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted #000}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0} -------------------------------------------------------------------------------- /web/static/css/bootstrap-timepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Timepicker Component for Twitter Bootstrap 3 | * 4 | * Copyright 2013 Joris de Wit 5 | * 6 | * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */.bootstrap-timepicker{position:relative}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu{left:auto;right:0}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before{left:auto;right:12px}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after{left:auto;right:13px}.bootstrap-timepicker .input-group-addon{cursor:pointer}.bootstrap-timepicker .input-group-addon i{display:inline-block;width:16px;height:16px}.bootstrap-timepicker-widget.dropdown-menu{padding:4px}.bootstrap-timepicker-widget.dropdown-menu.open{display:inline-block}.bootstrap-timepicker-widget.dropdown-menu:before{border-bottom:7px solid rgba(0,0,0,.2);border-left:7px solid transparent;border-right:7px solid transparent;content:"";display:inline-block;position:absolute}.bootstrap-timepicker-widget.dropdown-menu:after{border-bottom:6px solid #FFF;border-left:6px solid transparent;border-right:6px solid transparent;content:"";display:inline-block;position:absolute}.bootstrap-timepicker-widget.timepicker-orient-left:before{left:6px}.bootstrap-timepicker-widget.timepicker-orient-left:after{left:7px}.bootstrap-timepicker-widget.timepicker-orient-right:before{right:6px}.bootstrap-timepicker-widget.timepicker-orient-right:after{right:7px}.bootstrap-timepicker-widget.timepicker-orient-top:before{top:-7px}.bootstrap-timepicker-widget.timepicker-orient-top:after{top:-6px}.bootstrap-timepicker-widget.timepicker-orient-bottom:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.bootstrap-timepicker-widget.timepicker-orient-bottom:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.bootstrap-timepicker-widget a.btn,.bootstrap-timepicker-widget input{border-radius:4px}.bootstrap-timepicker-widget table{width:100%;margin:0}.bootstrap-timepicker-widget table td{text-align:center;height:30px;margin:0;padding:2px}.bootstrap-timepicker-widget table td:not(.separator){min-width:30px}.bootstrap-timepicker-widget table td span{width:100%}.bootstrap-timepicker-widget table td a{border:1px solid transparent;width:100%;display:inline-block;margin:0;padding:8px 0;outline:0;color:#333}.bootstrap-timepicker-widget table td a:hover{text-decoration:none;background-color:#eee;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-color:#ddd}.bootstrap-timepicker-widget table td a i{margin-top:2px;font-size:18px}.bootstrap-timepicker-widget table td input{width:25px;margin:0;text-align:center}.bootstrap-timepicker-widget .modal-content{padding:4px}@media (min-width:767px){.bootstrap-timepicker-widget.modal{width:200px;margin-left:-100px}}@media (max-width:767px){.bootstrap-timepicker,.bootstrap-timepicker .dropdown-menu{width:100%}} -------------------------------------------------------------------------------- /web/test_thread.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import time 4 | from tornado.httpserver import HTTPServer 5 | from tornado.ioloop import IOLoop 6 | from tornado.web import Application, asynchronous, RequestHandler 7 | from multiprocessing.pool import ThreadPool 8 | from multiprocessing.pool import ApplyResult 9 | from tornado import gen 10 | 11 | # https://gist.github.com/methane/2185380 参考 12 | 13 | 14 | 15 | html_content = """ 16 | 17 | 18 | 19 | 20 | 21 | 22 |

任务测试


23 | 24 | 25 | 26 | 57 | """ 58 | 59 | 60 | class MainPage(RequestHandler): 61 | def get(self): 62 | self.write(html_content) 63 | 64 | 65 | _workers = ThreadPool(10) 66 | _result = {} 67 | 68 | 69 | # 后台任务。 70 | def blocking_task(n, tid): 71 | time.sleep(n) 72 | print(tid) 73 | _result[tid] = {"finish"} 74 | 75 | 76 | class AddJobHandler(RequestHandler): 77 | @gen.coroutine 78 | def get(self): 79 | tid = str(int(time.time() * 10000)) 80 | _workers.apply_async(blocking_task, (10, tid)) # 传递参数 10 秒。 81 | self.write(tid) 82 | self.finish() # 先finish 掉,然后在后台执行。 83 | 84 | 85 | class JobCheckHandler(RequestHandler): 86 | def get(self): 87 | tid = self.get_argument("tid") 88 | if tid in _result.keys(): 89 | out = _result[tid] # 结果 90 | del _result[tid] # 删除tid的数据。 91 | self.write(str(out)) 92 | else: 93 | self.write("") 94 | 95 | 96 | # main 启动。 97 | if __name__ == "__main__": 98 | HTTPServer(Application([ 99 | ("/", MainPage), 100 | ("/add_job", AddJobHandler), 101 | ("/job_check", JobCheckHandler) 102 | ], debug=True)).listen(9999) 103 | print("start web .") 104 | IOLoop.instance().start() 105 | -------------------------------------------------------------------------------- /web/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import torndb 6 | import tornado.escape 7 | from tornado import gen 8 | import tornado.httpserver 9 | import tornado.ioloop 10 | import tornado.options 11 | import libs.common as common 12 | import libs.stock_web_dic as stock_web_dic 13 | import web.dataTableHandler as dataTableHandler 14 | import web.dataEditorHandler as dataEditorHandler 15 | import web.dataIndicatorsHandler as dataIndicatorsHandler 16 | import web.base as webBase 17 | 18 | class Application(tornado.web.Application): 19 | def __init__(self): 20 | handlers = [ 21 | # 设置路由 22 | (r"/", HomeHandler), 23 | (r"/test", TestHandler),# 测试页面,做写js 测试。 24 | (r"/test2", Test2Handler),# 测试页面,做写js 测试。 25 | # 使用datatable 展示报表数据模块。 26 | (r"/stock/api_data", dataTableHandler.GetStockDataHandler), 27 | (r"/stock/data", dataTableHandler.GetStockHtmlHandler), 28 | # 数据修改dataEditor。 29 | (r"/data/editor", dataEditorHandler.GetEditorHtmlHandler), 30 | (r"/data/editor/save", dataEditorHandler.SaveEditorHandler), 31 | # 获得股票指标数据。 32 | (r"/data/indicators", dataIndicatorsHandler.GetDataIndicatorsHandler), 33 | ] 34 | settings = dict( # 配置 35 | template_path=os.path.join(os.path.dirname(__file__), "templates"), 36 | static_path=os.path.join(os.path.dirname(__file__), "static"), 37 | xsrf_cookies=False, # True, 38 | # cookie加密 39 | cookie_secret="027bb1b670eddf0392cdda8709268a17b58b7", 40 | debug=True, 41 | ) 42 | super(Application, self).__init__(handlers, **settings) 43 | # Have one global connection to the blog DB across all handlers 44 | self.db = torndb.Connection( 45 | charset="utf8", max_idle_time=3600, connect_timeout=1000, 46 | host=common.MYSQL_HOST, database=common.MYSQL_DB, 47 | user=common.MYSQL_USER, password=common.MYSQL_PWD) 48 | 49 | 50 | # 首页handler。 51 | class HomeHandler(webBase.BaseHandler): 52 | @gen.coroutine 53 | def get(self): 54 | self.render("index.html", entries="hello", leftMenu=webBase.GetLeftMenu(self.request.uri)) 55 | class TestHandler(webBase.BaseHandler): 56 | @gen.coroutine 57 | def get(self): 58 | self.render("test.html", entries="hello", leftMenu=webBase.GetLeftMenu(self.request.uri)) 59 | class Test2Handler(webBase.BaseHandler): 60 | @gen.coroutine 61 | def get(self): 62 | self.render("test2.html", entries="hello", leftMenu=webBase.GetLeftMenu(self.request.uri)) 63 | 64 | def main(): 65 | tornado.options.parse_command_line() 66 | http_server = tornado.httpserver.HTTPServer(Application()) 67 | port = 9999 68 | http_server.listen(port) 69 | # tornado.options.options.logging = "debug" 70 | tornado.options.parse_command_line() 71 | 72 | tornado.ioloop.IOLoop.current().start() 73 | 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /web/test_thread_v2.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import time 4 | from tornado.httpserver import HTTPServer 5 | from tornado.ioloop import IOLoop 6 | from tornado.web import Application, asynchronous, RequestHandler 7 | from tornado import gen 8 | from tornado.concurrent import run_on_executor 9 | from concurrent.futures import ThreadPoolExecutor 10 | 11 | # `pip install futures` for python2 12 | 13 | # https://gist.github.com/methane/2185380 参考 14 | 15 | html_content = """ 16 | 17 | 18 | 19 | 20 | 21 | 22 |

任务测试


23 | 24 | 25 | 26 | 57 | """ 58 | 59 | 60 | class MainPage(RequestHandler): 61 | def get(self): 62 | self.write(html_content) 63 | 64 | 65 | MAX_WORKERS = 4 66 | _result = {} 67 | 68 | 69 | class AddJobHandler(RequestHandler): 70 | # 必须定义一个executor的属性,然后run_on_executor 注解才管用。 71 | executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) 72 | 73 | @run_on_executor # 标记成后台程序执行。 74 | def background_task(self, tid): 75 | time.sleep(10) # 传递参数 10 秒。 76 | _result[tid] = {"finish"} 77 | 78 | @gen.coroutine 79 | def get(self): 80 | tid = str(int(time.time() * 10000)) 81 | self.background_task(tid) 82 | self.write(tid) 83 | 84 | 85 | class JobCheckHandler(RequestHandler): 86 | def get(self): 87 | tid = self.get_argument("tid") 88 | if tid in _result.keys(): 89 | out = _result[tid] # 结果 90 | del _result[tid] # 删除tid的数据。 91 | self.write(str(out)) 92 | else: 93 | self.write("") 94 | 95 | 96 | # main 启动。 97 | if __name__ == "__main__": 98 | HTTPServer(Application([ 99 | ("/", MainPage), 100 | ("/add_job", AddJobHandler), 101 | ("/job_check", JobCheckHandler) 102 | ], debug=True)).listen(9999) 103 | print("start web .") 104 | IOLoop.instance().start() 105 | -------------------------------------------------------------------------------- /web/static/js/autosize.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Autosize 3.0.15 3 | license: MIT 4 | http://www.jacklmoore.com/autosize 5 | */ 6 | !function(a,b){if("function"==typeof define&&define.amd)define(["exports","module"],b);else if("undefined"!=typeof exports&&"undefined"!=typeof module)b(exports,module);else{var c={exports:{}};b(c.exports,c),a.autosize=c.exports}}(this,function(a,b){"use strict";function c(a){function b(){var b=window.getComputedStyle(a,null);n=b.overflowY,"vertical"===b.resize?a.style.resize="none":"both"===b.resize&&(a.style.resize="horizontal"),m="content-box"===b.boxSizing?-(parseFloat(b.paddingTop)+parseFloat(b.paddingBottom)):parseFloat(b.borderTopWidth)+parseFloat(b.borderBottomWidth),isNaN(m)&&(m=0),e()}function c(b){var c=a.style.width;a.style.width="0px",a.offsetWidth,a.style.width=c,n=b,l&&(a.style.overflowY=b),d()}function d(){var b=window.pageYOffset,c=document.body.scrollTop,d=a.style.height;a.style.height="auto";var e=a.scrollHeight+m;return 0===a.scrollHeight?void(a.style.height=d):(a.style.height=e+"px",o=a.clientWidth,document.documentElement.scrollTop=b,void(document.body.scrollTop=c))}function e(){var b=a.style.height;d();var e=window.getComputedStyle(a,null);if(e.height!==a.style.height?"visible"!==n&&c("visible"):"hidden"!==n&&c("hidden"),b!==a.style.height){var f=g("autosize:resized");a.dispatchEvent(f)}}var h=void 0===arguments[1]?{}:arguments[1],i=h.setOverflowX,j=void 0===i?!0:i,k=h.setOverflowY,l=void 0===k?!0:k;if(a&&a.nodeName&&"TEXTAREA"===a.nodeName&&!f.has(a)){var m=null,n=null,o=a.clientWidth,p=function(){a.clientWidth!==o&&e()},q=function(b){window.removeEventListener("resize",p,!1),a.removeEventListener("input",e,!1),a.removeEventListener("keyup",e,!1),a.removeEventListener("autosize:destroy",q,!1),a.removeEventListener("autosize:update",e,!1),f["delete"](a),Object.keys(b).forEach(function(c){a.style[c]=b[c]})}.bind(a,{height:a.style.height,resize:a.style.resize,overflowY:a.style.overflowY,overflowX:a.style.overflowX,wordWrap:a.style.wordWrap});a.addEventListener("autosize:destroy",q,!1),"onpropertychange"in a&&"oninput"in a&&a.addEventListener("keyup",e,!1),window.addEventListener("resize",p,!1),a.addEventListener("input",e,!1),a.addEventListener("autosize:update",e,!1),f.add(a),j&&(a.style.overflowX="hidden",a.style.wordWrap="break-word"),b()}}function d(a){if(a&&a.nodeName&&"TEXTAREA"===a.nodeName){var b=g("autosize:destroy");a.dispatchEvent(b)}}function e(a){if(a&&a.nodeName&&"TEXTAREA"===a.nodeName){var b=g("autosize:update");a.dispatchEvent(b)}}var f="function"==typeof Set?new Set:function(){var a=[];return{has:function(b){return Boolean(a.indexOf(b)>-1)},add:function(b){a.push(b)},"delete":function(b){a.splice(a.indexOf(b),1)}}}(),g=function(a){return new Event(a)};try{new Event("test")}catch(h){g=function(a){var b=document.createEvent("Event");return b.initEvent(a,!0,!1),b}}var i=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?(i=function(a){return a},i.destroy=function(a){return a},i.update=function(a){return a}):(i=function(a,b){return a&&Array.prototype.forEach.call(a.length?a:[a],function(a){return c(a,b)}),a},i.destroy=function(a){return a&&Array.prototype.forEach.call(a.length?a:[a],d),a},i.update=function(a){return a&&Array.prototype.forEach.call(a.length?a:[a],e),a}),b.exports=i}); -------------------------------------------------------------------------------- /jobs/quarter_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import sys 7 | import time 8 | import pandas as pd 9 | import tushare as ts 10 | from sqlalchemy.types import NVARCHAR 11 | from sqlalchemy import inspect 12 | import datetime 13 | 14 | 15 | # 增加一个新quarter列,用来存储季度信息。 16 | def concat_quarter(year, quarter, data_array): 17 | print(len(data_array)) 18 | quarter_str = str(year) + str("%02d" % quarter) # 格式化季度数据。2位。 19 | # 增加到列。 20 | quarter_col = pd.DataFrame([quarter_str for _ in range(len(data_array))], columns=["quarter"]) 21 | return pd.concat([quarter_col, data_array], axis=1) 22 | 23 | 24 | #############################基本面数据 http://tushare.org/fundamental.html 25 | def stat_all(tmp_datetime): 26 | # 返回 31 天前的数据,做上个季度数据统计。 27 | tmp_datetime_1month = tmp_datetime + datetime.timedelta(days=-31) 28 | year = int((tmp_datetime_1month).strftime("%Y")) 29 | quarter = int(pd.Timestamp(tmp_datetime_1month).quarter) # 获得上个季度的数据。 30 | print("############ year %d, quarter %d", year, quarter) 31 | # 业绩报告(主表) 32 | data = ts.get_report_data(year, quarter) 33 | # 增加季度字段。 34 | data = concat_quarter(year, quarter, data) 35 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 36 | data = data.drop_duplicates(subset="code", keep="last") 37 | # 插入数据库。 38 | common.insert_db(data, "ts_report_data", False, "`quarter`,`code`") 39 | 40 | # 盈利能力 41 | data = ts.get_profit_data(year, quarter) 42 | # 增加季度字段。 43 | data = concat_quarter(year, quarter, data) 44 | # 处理重复数据,保存最新一条数据。 45 | data = data.drop_duplicates(subset="code", keep="last") 46 | # 插入数据库。 47 | common.insert_db(data, "ts_profit_data", False, "`quarter`,`code`") 48 | 49 | # 营运能力 50 | data = ts.get_operation_data(year, quarter) 51 | # 增加季度字段。 52 | data = concat_quarter(year, quarter, data) 53 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 54 | data = data.drop_duplicates(subset="code", keep="last") 55 | # 插入数据库。 56 | common.insert_db(data, "ts_operation_data", False, "`quarter`,`code`") 57 | 58 | # 成长能力 59 | data = ts.get_growth_data(year, quarter) 60 | # 增加季度字段。 61 | data = concat_quarter(year, quarter, data) 62 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 63 | data = data.drop_duplicates(subset="code", keep="last") 64 | # 插入数据库。 65 | common.insert_db(data, "ts_growth_data", False, "`quarter`,`code`") 66 | 67 | # 偿债能力 68 | data = ts.get_debtpaying_data(year, quarter) 69 | # 增加季度字段。 70 | data = concat_quarter(year, quarter, data) 71 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 72 | data = data.drop_duplicates(subset="code", keep="last") 73 | # 插入数据库。 74 | common.insert_db(data, "ts_debtpaying_data", False, "`quarter`,`code`") 75 | 76 | # 现金流量 77 | data = ts.get_cashflow_data(year, quarter) 78 | # 增加季度字段。 79 | data = concat_quarter(year, quarter, data) 80 | # 处理重复数据,保存最新一条数据。最后一步处理,否则concat有问题。 81 | data = data.drop_duplicates(subset="code", keep="last") 82 | # 插入数据库。 83 | common.insert_db(data, "ts_cashflow_data", False, "`quarter`,`code`") 84 | 85 | 86 | # main函数入口 87 | if __name__ == '__main__': 88 | # 使用方法传递。 89 | tmp_datetime = common.run_with_args(stat_all) 90 | -------------------------------------------------------------------------------- /web/static/css/bootstrap-colorpicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Colorpicker 3 | * http://mjolnic.github.io/bootstrap-colorpicker/ 4 | * 5 | * Originally written by (c) 2012 Stefan Petre 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0.txt 8 | * 9 | */.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url(../images/bootstrap-colorpicker/saturation.png)}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-alpha,.colorpicker-hue{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-alpha i,.colorpicker-hue i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url(../images/bootstrap-colorpicker/hue.png)}.colorpicker-alpha,.colorpicker-color{background-image:url(../images/bootstrap-colorpicker/alpha.png)}.colorpicker-alpha{display:none}.colorpicker:after,.colorpicker:before{position:absolute;display:inline-block;content:''}.colorpicker-alpha,.colorpicker-hue,.colorpicker-saturation{background-size:contain}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:after,.colorpicker:before{line-height:0}.colorpicker:before{top:-7px;left:6px;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2)}.colorpicker:after{clear:both;top:-6px;left:7px;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{float:left;width:10px;height:10px;cursor:pointer}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .add-on i,.colorpicker-element .input-group-addon i{display:inline-block;width:16px;height:16px;vertical-align:text-top;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;z-index:auto;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-alpha,.colorpicker.colorpicker-horizontal .colorpicker-hue{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-alpha i,.colorpicker.colorpicker-horizontal .colorpicker-hue i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:none}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url(../images/bootstrap-colorpicker/hue-horizontal.png)}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url(../images/bootstrap-colorpicker/alpha-horizontal.png)}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}.colorpicker-right:before{right:6px;left:auto}.colorpicker-right:after{right:7px;left:auto} -------------------------------------------------------------------------------- /web/static/js/jquery.dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /* Set the defaults for DataTables initialisation */ 2 | $.extend(!0,$.fn.dataTable.defaults,{sDom:"<'row'<'col-xs-6'l><'col-xs-6'f>r>t<'row'<'col-xs-6'i><'col-xs-6'p>>",oLanguage:{sLengthMenu:"Display _MENU_ records"}}),$.extend($.fn.dataTableExt.oStdClasses,{sWrapper:"dataTables_wrapper form-inline",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"}),$.fn.dataTable.Api?($.fn.dataTable.defaults.renderer="bootstrap",$.fn.dataTable.ext.renderer.pageButton.bootstrap=function(a,b,c,d,e,f){var g,h,i=new $.fn.dataTable.Api(a),j=a.oClasses,k=a.oLanguage.oPaginate,l=function(b,d){var m,n,o,p,q=function(a){return a.preventDefault(),$(a.target).parent().hasClass("disabled")?!1:void("ellipsis"!==a.data.action&&i.page(a.data.action).draw(!1))};for(m=0,n=d.length;n>m;m++)if(p=d[m],$.isArray(p))l(b,p);else{switch(g="",h="",p){case"ellipsis":g="…",h="disabled";break;case"first":g=k.sFirst,h=p+(e>0?"":" disabled");break;case"previous":g=k.sPrevious,h=p+(e>0?"":" disabled");break;case"next":g=k.sNext,h=p+(f-1>e?"":" disabled");break;case"last":g=k.sLast,h=p+(f-1>e?"":" disabled");break;default:g=p+1,h=e===p?"active":""}g&&(o=$("
  • ",{"class":j.sPageButton+" "+h,"aria-controls":a.sTableId,tabindex:a.iTabIndex,id:0===c&&"string"==typeof p?a.sTableId+"_"+p:null}).append($("",{href:"#"}).html(g)).appendTo(b),a.oApi._fnBindAction(o,{action:p},q))}};l($(b).empty().html('
  • 21 | 22 | 23 | 24 |
    25 | 26 | 27 |
    28 |
    29 | 30 | 31 | 32 | 33 | {% for column_name in stockWeb.column_names %} 34 | 35 | {% end %} 36 | 37 | 38 |
    {{ column_name }}
    39 |
    40 | 41 | 42 | 157 | {% end %} -------------------------------------------------------------------------------- /old_jobs/guess_indicators_lite_buy_daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import pandas as pd 7 | import numpy as np 8 | import math 9 | import datetime 10 | import heapq 11 | 12 | 13 | ### 对每日指标数据,进行筛选。将符合条件的。二次筛选出来。 14 | def stat_all_lite(tmp_datetime): 15 | # 要操作的数据库表名称。 16 | table_name = "guess_indicators_lite_buy_daily" 17 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 18 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 19 | print("datetime_str:", datetime_str) 20 | print("datetime_int:", datetime_int) 21 | 22 | # try: 23 | # # 删除老数据。guess_indicators_lite_buy_daily 是一张单表,没有日期字段。 24 | # del_sql = " DELETE FROM `stock_data`.`%s` WHERE `date`= '%s' " % (table_name, datetime_int) 25 | # print("del_sql:", del_sql) 26 | # common.insert(del_sql) 27 | # print("del_sql") 28 | # except Exception as e: 29 | # print("error :", e) 30 | 31 | sql_1 = """ 32 | SELECT `date`, `code`, `name`, `changepercent`, `trade`,`turnoverratio`, `pb` ,`kdjj`,`rsi_6`,`cci` 33 | FROM stock_data.guess_indicators_lite_daily WHERE `date` = %s 34 | and `changepercent` > 2 and `pb` > 0 35 | """ 36 | # and `changepercent` > 2 and `pb` > 0 and `turnoverratio` > 5 去除掉换手率参数。 37 | data = pd.read_sql(sql=sql_1, con=common.engine(), params=[datetime_int]) 38 | data = data.drop_duplicates(subset="code", keep="last") 39 | print("######## len data ########:", len(data)) 40 | # del data["name"] 41 | # print(data) 42 | data["trade_float32"] = data["trade"].astype('float32', copy=True) 43 | # 输入 date 用作历史数据查询。 44 | stock_merge = pd.DataFrame({ 45 | "date": data["date"], "code": data["code"], "wave_mean": data["trade"], 46 | "wave_crest": data["trade"], "wave_base": data["trade"]}, index=data.index.values) 47 | print(stock_merge.head(1)) 48 | 49 | stock_merge = stock_merge.apply(apply_merge, axis=1) # , axis=1) 50 | del stock_merge["date"] # 合并前删除 date 字段。 51 | # 合并数据 52 | data_new = pd.merge(data, stock_merge, on=['code'], how='left') 53 | 54 | # 使用 trade_float32 参加计算。 55 | data_new = data_new[data_new["trade_float32"] > data_new["wave_base"]] # 交易价格大于波谷价格。 56 | data_new = data_new[data_new["trade_float32"] < data_new["wave_crest"]] # 小于波峰价格 57 | 58 | # wave_base wave_crest wave_mean 59 | data_new["wave_base"] = data_new["wave_base"].round(2) # 数据保留2位小数 60 | data_new["wave_crest"] = data_new["wave_crest"].round(2) # 数据保留2位小数 61 | data_new["wave_mean"] = data_new["wave_mean"].round(2) # 数据保留2位小数 62 | 63 | data_new["up_rate"] = (data_new["wave_mean"].sub(data_new["trade_float32"])).div(data_new["wave_crest"]).mul(100) 64 | data_new["up_rate"] = data_new["up_rate"].round(2) # 数据保留2位小数 65 | 66 | data_new["buy"] = 1 67 | data_new["sell"] = 0 68 | data_new["today_trade"] = data_new["trade"] 69 | data_new["income"] = 0 70 | # 重命名 date 71 | data_new.columns.values[0] = "buy_date" 72 | del data_new["trade_float32"] 73 | 74 | try: 75 | common.insert_db(data_new, table_name, False, "`code`") 76 | print("insert_db") 77 | except Exception as e: 78 | print("error :", e) 79 | # 重命名 80 | del data_new["name"] 81 | print(data_new) 82 | 83 | 84 | def apply_merge(tmp): 85 | date = tmp["date"] 86 | code = tmp["code"] 87 | date_end = datetime.datetime.strptime(date, "%Y%m%d") 88 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 89 | date_end = date_end.strftime("%Y-%m-%d") 90 | print(code, date_start, date_end) 91 | 92 | # open, high, close, low, volume, price_change, p_change, ma5, ma10, ma20, v_ma5, v_ma10, v_ma20, turnover 93 | # 使用缓存方法。加快计算速度。 94 | stock = common.get_hist_data_cache(code, date_start, date_end) 95 | # 增加空判断,如果是空返回 0 数据。 96 | if stock is None: 97 | return list([code, date, 0, 0, 0]) 98 | 99 | stock = pd.DataFrame({"close": stock["close"]}, index=stock.index.values) 100 | stock = stock.sort_index(0) # 将数据按照日期排序下。 101 | 102 | # print(stock.head(10)) 103 | arr = pd.Series(stock["close"].values) 104 | # print(df_arr) 105 | wave_mean = arr.mean() 106 | max_point = 3 # 获得最高的几个采样点。 107 | # 计算股票的波峰值。 108 | wave_crest = heapq.nlargest(max_point, enumerate(arr), key=lambda x: x[1]) 109 | wave_crest_mean = pd.DataFrame(wave_crest).mean() 110 | 111 | # 输出元祖第一个元素是index,第二元素是比较的数值 计算数据的波谷值 112 | wave_base = heapq.nsmallest(max_point, enumerate(arr), key=lambda x: x[1]) 113 | wave_base_mean = pd.DataFrame(wave_base).mean() 114 | # 输出数据 115 | print("##############", len(stock)) 116 | if len(stock) > 180: 117 | # code date wave_base wave_crest wave_mean 顺序必须一致。返回的是行数据,然后填充。 118 | return list([code, date, wave_base_mean[1], wave_crest_mean[1], wave_mean]) 119 | else: 120 | return list([code, date, 0, 0, 0]) 121 | 122 | 123 | # main函数入口 124 | if __name__ == '__main__': 125 | # 二次筛选数据。 126 | tmp_datetime = common.run_with_args(stat_all_lite) 127 | -------------------------------------------------------------------------------- /old_jobs/guess_period_daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import sys 7 | import time 8 | import pandas as pd 9 | import tushare as ts 10 | from sqlalchemy.types import NVARCHAR 11 | from sqlalchemy import inspect 12 | import datetime 13 | import heapq 14 | 15 | """ 16 | SELECT `date`, `code`, `name`, `changepercent`, `trade`, `open`, `high`, `low`, 17 | `settlement`, `volume`, `turnoverratio`, `amount`, `per`, `pb`, `mktcap`, `nmc` 18 | FROM stock_data.ts_today_all where `date` = 20171106 and trade > 0 and trade <= 20 19 | and `code` not like '002%' and `code` not like '300%' and `name` not like '%st%' 20 | 21 | """ 22 | 23 | 24 | def stat_index_all(tmp_datetime): 25 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 26 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 27 | print("datetime_str:", datetime_str) 28 | print("datetime_int:", datetime_int) 29 | 30 | # 查询今日满足股票数据。剔除数据:创业板股票数据,中小板股票数据,所有st股票 31 | # #`code` not like '002%' and `code` not like '300%' and `name` not like '%st%' 32 | sql_1 = """ 33 | SELECT `date`, `code`, `name`, `changepercent`, `trade`, `open`, `high`, `low`, 34 | `settlement`, `volume`, `turnoverratio`, `amount`, `per`, `pb`, `mktcap`, `nmc` 35 | FROM stock_data.ts_today_all WHERE `date` = %s and `trade` > 0 and `open` > 0 and trade <= 20 36 | and `code` not like %s and `code` not like %s and `name` not like %s 37 | """ 38 | print(sql_1) 39 | data = pd.read_sql(sql=sql_1, con=common.engine(), params=[datetime_int, '002%', '300%', '%st%']) 40 | print(type(data)) 41 | data = data.drop_duplicates(subset="code", keep="last") 42 | print(data["trade"]) 43 | data["trade_float32"] = data["trade"].astype('float32', copy=False) 44 | print(len(data)) 45 | print("########data[trade]########:") 46 | print(data["trade"]) 47 | 48 | # 使用 trade 填充数据 49 | stock_guess = pd.DataFrame({ 50 | "date": data["date"], "code": data["code"], "wave_mean": data["trade"], 51 | "wave_crest": data["trade"], "wave_base": data["trade"]}, index=data.index.values) 52 | print(stock_guess.head()) 53 | stock_guess = stock_guess.apply(apply_guess, axis=1) # , axis=1) 54 | print(stock_guess.head()) 55 | # stock_guess.astype('float32', copy=False) 56 | stock_guess.drop('date', axis=1, inplace=True) # 删除日期字段,然后和原始数据合并。 57 | stock_guess = stock_guess.round(2) # 数据保留2位小数 58 | print(stock_guess["wave_base"]) 59 | 60 | data_new = pd.merge(data, stock_guess, on=['code'], how='left') 61 | print("#############") 62 | 63 | # 使用pandas 函数 : https://pandas.pydata.org/pandas-docs/stable/api.html#id4 64 | data_new["up_rate"] = (data_new["trade_float32"].sub(data_new["wave_mean"])).div(data_new["wave_crest"]).mul(100) 65 | data_new["up_rate"] = data_new["up_rate"].round(2) # 数据保留2位小数 66 | data_new.drop('trade_float32', axis=1, inplace=True) # 删除计算字段。 67 | 68 | # 删除老数据。 69 | del_sql = " DELETE FROM `stock_data`.`guess_period_daily` WHERE `date`= '%s' " % datetime_int 70 | common.insert(del_sql) 71 | # print(data_new.head()) 72 | # data_new["down_rate"] = (data_new["trade"] - data_new["wave_mean"]) / data_new["wave_base"] 73 | common.insert_db(data_new, "guess_period_daily", False, "`date`,`code`") 74 | 75 | # 进行左连接. 76 | # tmp = pd.merge(tmp, tmp2, on=['company_id'], how='left') 77 | 78 | 79 | def apply_guess(tmp): 80 | date = tmp["date"] 81 | code = tmp["code"] 82 | date_end = datetime.datetime.strptime(date, "%Y%m%d") 83 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 84 | date_end = date_end.strftime("%Y-%m-%d") 85 | print(code, date_start, date_end) 86 | 87 | # open, high, close, low, volume, price_change, p_change, ma5, ma10, ma20, v_ma5, v_ma10, v_ma20, turnover 88 | # 使用缓存方法。加快计算速度。 89 | stock = common.get_hist_data_cache(code, date_start, date_end) 90 | # 增加空判断,如果是空返回 0 数据。 91 | if stock is None: 92 | return pd.Series([date, code, 0.0, 0.0, 0.0], 93 | index=['date', 'code', 'wave_mean', 'wave_crest', 'wave_base']) 94 | 95 | stock = pd.DataFrame({"close": stock["close"]}, index=stock.index.values) 96 | stock = stock.sort_index(0) # 将数据按照日期排序下。 97 | 98 | # print(stock.head(10)) 99 | arr = pd.Series(stock["close"].values) 100 | # print(df_arr) 101 | wave_mean = arr.mean() 102 | # 计算股票的波峰值。 103 | wave_crest = heapq.nlargest(5, enumerate(arr), key=lambda x: x[1]) 104 | wave_crest_mean = pd.DataFrame(wave_crest).mean() 105 | 106 | # 输出元祖第一个元素是index,第二元素是比较的数值 计算数据的波谷值 107 | wave_base = heapq.nsmallest(5, enumerate(arr), key=lambda x: x[1]) 108 | wave_base_mean = pd.DataFrame(wave_base).mean() 109 | # 输出数据 110 | # print("##############") 111 | # code date wave_base wave_crest wave_mean 顺序必须一致。返回的是行数据,然后填充。 112 | return pd.Series([date, code, wave_base_mean[1], wave_crest_mean[1], wave_mean], 113 | index=['date','code','wave_mean','wave_crest','wave_base']) 114 | 115 | 116 | # main函数入口 117 | if __name__ == '__main__': 118 | # 使用方法传递。 119 | tmp_datetime = common.run_with_args(stat_index_all) 120 | -------------------------------------------------------------------------------- /web/static/css/daterangepicker.min.css: -------------------------------------------------------------------------------- 1 | .daterangepicker{position:absolute;background:#fff;top:100px;left:20px;padding:4px;margin-top:1px;border-radius:4px;width:278px}.daterangepicker.opensleft:before{position:absolute;top:-7px;right:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2);content:''}.daterangepicker.opensleft:after{position:absolute;top:-6px;right:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.daterangepicker.openscenter:after,.daterangepicker.openscenter:before{left:0;right:0;width:0;margin-left:auto;margin-right:auto;display:inline-block;content:'';position:absolute}.daterangepicker.openscenter:before{top:-7px;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2)}.daterangepicker.openscenter:after{top:-6px;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent}.daterangepicker.opensright:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2);content:''}.daterangepicker.opensright:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.daterangepicker.dropup{margin-top:-5px}.daterangepicker.dropup:before{top:initial;bottom:-7px;border-bottom:initial;border-top:7px solid #ccc}.daterangepicker.dropup:after{top:initial;bottom:-6px;border-bottom:initial;border-top:6px solid #fff}.daterangepicker.dropdown-menu{max-width:none;z-index:3000}.daterangepicker.single .calendar,.daterangepicker.single .ranges{float:none}.daterangepicker .calendar{display:none;max-width:270px;margin:4px}.daterangepicker.show-calendar .calendar{display:block}.daterangepicker .calendar.single .calendar-table{border:none}.daterangepicker .calendar td,.daterangepicker .calendar th{white-space:nowrap;text-align:center;min-width:32px}.daterangepicker .calendar-table{border:1px solid #ddd;padding:4px;border-radius:4px;background:#fff}.daterangepicker table{width:100%;margin:0}.daterangepicker td,.daterangepicker th{text-align:center;width:20px;height:20px;border-radius:4px;white-space:nowrap;cursor:pointer}.daterangepicker td.off,.daterangepicker td.off.end-date,.daterangepicker td.off.in-range,.daterangepicker td.off.start-date{color:#999;background:#fff}.daterangepicker option.disabled,.daterangepicker td.disabled{color:#999;cursor:not-allowed;text-decoration:line-through}.daterangepicker td.available:hover,.daterangepicker th.available:hover{background:#eee}.daterangepicker td.in-range{background:#ebf4f8;border-radius:0}.daterangepicker td.start-date{border-radius:4px 0 0 4px}.daterangepicker td.end-date{border-radius:0 4px 4px 0}.daterangepicker td.start-date.end-date{border-radius:4px}.daterangepicker td.active,.daterangepicker td.active:hover{background-color:#357ebd;border-color:#3071a9;color:#fff}.daterangepicker td.week,.daterangepicker th.week{font-size:80%;color:#ccc}.daterangepicker select.monthselect,.daterangepicker select.yearselect{font-size:12px;padding:1px;height:auto;margin:0;cursor:default}.daterangepicker select.monthselect{margin-right:2%;width:56%}.daterangepicker select.yearselect{width:40%}.daterangepicker select.ampmselect,.daterangepicker select.hourselect,.daterangepicker select.minuteselect,.daterangepicker select.secondselect{width:50px;margin-bottom:0}.daterangepicker th.month{width:auto}.daterangepicker .input-mini{border:1px solid #ccc;border-radius:4px;color:#555;display:block;height:30px;line-height:30px;vertical-align:middle;margin:0 0 5px;padding:0 6px 0 28px;width:100%}.daterangepicker .input-mini.active{border:1px solid #357ebd}.daterangepicker .daterangepicker_input i{position:absolute;left:8px;top:8px}.daterangepicker .daterangepicker_input{position:relative}.daterangepicker .calendar-time{text-align:center;margin:5px auto;line-height:30px;position:relative;padding-left:28px}.daterangepicker .calendar-time select.disabled{color:#ccc;cursor:not-allowed}.daterangepicker .ranges{font-size:11px;float:none;margin:4px;text-align:left}.daterangepicker .ranges ul{list-style:none;margin:0 auto;padding:0;width:100%}.daterangepicker .ranges li{font-size:13px;background:#f5f5f5;border:1px solid #f5f5f5;color:#08c;padding:3px 12px;margin-bottom:8px;border-radius:5px;cursor:pointer}.daterangepicker .ranges li.active,.daterangepicker .ranges li:hover{background:#08c;border:1px solid #08c;color:#fff}@media (min-width:564px){.daterangepicker .calendar,.daterangepicker .ranges,.daterangepicker.single .calendar,.daterangepicker.single .ranges{float:left}.daterangepicker{width:auto}.daterangepicker .ranges ul{width:160px}.daterangepicker.single .ranges ul{width:100%}.daterangepicker .calendar.left .calendar-table{border-right:none;border-top-right-radius:0;border-bottom-right-radius:0}.daterangepicker .calendar.right .calendar-table{border-left:none;border-top-left-radius:0;border-bottom-left-radius:0}.daterangepicker .calendar.left{clear:left;margin-right:0}.daterangepicker.single .calendar.left{clear:none}.daterangepicker .calendar.right{margin-left:0}.daterangepicker .calendar.left .calendar-table,.daterangepicker .left .daterangepicker_input{padding-right:12px}}@media (min-width:730px){.daterangepicker .ranges{width:auto;float:left}.daterangepicker .calendar.left{clear:none}} -------------------------------------------------------------------------------- /old_jobs/guess_indicators_lite_sell_daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import pandas as pd 7 | import numpy as np 8 | import math 9 | import datetime 10 | import heapq 11 | import stockstats 12 | 13 | 14 | # code date today_trade 15 | def apply_merge(tmp): 16 | date = tmp["date"] 17 | code = tmp["code"] 18 | date_end = datetime.datetime.strptime(date, "%Y%m%d") 19 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 20 | date_end = date_end.strftime("%Y-%m-%d") 21 | print(code, date_start, date_end) 22 | 23 | # open, high, close, low, volume, price_change, p_change, ma5, ma10, ma20, v_ma5, v_ma10, v_ma20, turnover 24 | # 使用缓存方法。加快计算速度。 25 | stock = common.get_hist_data_cache(code, date_start, date_end) 26 | # 增加空判断,如果是空返回 0 数据。 27 | if stock is None: 28 | return list([code, date, 0.0]) 29 | print("########") 30 | # print(stock.tail(1)) 31 | close = stock.tail(1)["close"].values[0] 32 | print("close: ", close) 33 | print("########") 34 | return list([code, date, close]) 35 | 36 | 37 | # buy code date sell sell_cci sell_kdjj sell_rsi_6 38 | def apply_merge_sell(tmp): 39 | date = tmp["date"] 40 | code = tmp["code"] 41 | date_end = datetime.datetime.strptime(date, "%Y%m%d") 42 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 43 | date_end = date_end.strftime("%Y-%m-%d") 44 | print(code, date_start, date_end) 45 | 46 | # open, high, close, low, volume, price_change, p_change, ma5, ma10, ma20, v_ma5, v_ma10, v_ma20, turnover 47 | # 使用缓存方法。加快计算速度。 48 | stock = common.get_hist_data_cache(code, date_start, date_end) 49 | # 增加空判断,如果是空返回 0 数据。 50 | if stock is None: 51 | return list([1, code, date, 0, 0, 0, 0]) 52 | print("########") 53 | # J大于100时为超买,小于10时为超卖。 54 | # 强弱指标保持高于50表示为强势市场,反之低于50表示为弱势市场。 55 | # 1、当CCI指标从下向上突破﹢100线而进入非常态区间时,表明股价脱离常态而进入异常波动阶段, 56 | # 2、当CCI指标从上向下突破﹣100线而进入另一个非常态区间时,表明股价的盘整阶段已经结束, 57 | stockStat = stockstats.StockDataFrame.retype(stock) 58 | kdjj = int(stockStat["kdjj"].tail(1).values[0]) 59 | rsi_6 = int(stockStat["rsi_6"].tail(1).values[0]) 60 | cci = int(stockStat["cci"].tail(1).values[0]) 61 | print("kdjj:", kdjj, "rsi_6:", rsi_6, "cci:", cci) 62 | # and kdjj > 80 and rsi_6 > 55 and cci > 100 判断卖出时刻。也就是买入时刻的反面。发现有波动就卖了。 63 | # if kdjj <= 10 and rsi_6 <= 50 and cci <= 100: old 64 | if kdjj <= 80 or rsi_6 <= 55 or cci <= 100: 65 | return list([0, code, date, 1, cci, kdjj, rsi_6]) 66 | else: 67 | return list([1, code, date, 0, cci, kdjj, rsi_6]) 68 | 69 | 70 | # 增加 收益计算。 71 | def stat_index_calculate(tmp_datetime): 72 | # 要操作的数据库表名称。 73 | table_name = "guess_indicators_lite_sell_daily" 74 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 75 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 76 | print("datetime_str:", datetime_str) 77 | print("datetime_int:", datetime_int) 78 | 79 | sql_1 = """ 80 | SELECT `buy_date`, `code`, `name`, `changepercent`, `trade`, `turnoverratio`, `pb`, `kdjj`, `rsi_6`, 81 | `cci`, `wave_base`, `wave_crest`, `wave_mean`, `up_rate` 82 | FROM guess_indicators_lite_buy_daily where `buy_date` <= """ + datetime_int 83 | print(sql_1) 84 | data = pd.read_sql(sql=sql_1, con=common.engine(), params=[]) 85 | data = data.drop_duplicates(subset="code", keep="last") 86 | print(data["trade"]) 87 | data["trade_float32"] = data["trade"].astype('float32', copy=False) 88 | print(len(data)) 89 | data["date"] = datetime_int 90 | 91 | stock_merge = pd.DataFrame({ 92 | "date": data["date"], "code": data["code"], "today_trade": data["trade"]}, index=data.index.values) 93 | print(stock_merge.head(1)) 94 | 95 | stock_merge = stock_merge.apply(apply_merge, axis=1) # , axis=1) 96 | 97 | del stock_merge["date"] # 合并前删除 date 字段。 98 | # 合并数据 99 | data_new = pd.merge(data, stock_merge, on=['code'], how='left') 100 | data_new["income"] = (data_new["today_trade"] - data_new["trade_float32"]) * 100 101 | data_new["income"] = data_new["income"].round(4) # 保留4位小数。 102 | 103 | # 增加售出列。看看是否需要卖出。 104 | stock_sell_merge = pd.DataFrame({ 105 | "date": data["date"], "code": data["code"], "sell": 0, "buy": 0, "sell_kdjj": 0, "sell_rsi_6": 0, 106 | "sell_cci": 0}, 107 | index=data.index.values) 108 | print(stock_sell_merge.head(1)) 109 | 110 | merge_sell_data = stock_sell_merge.apply(apply_merge_sell, axis=1) # , axis=1) 111 | # 重命名 112 | del merge_sell_data["date"] # 合并前删除 date 字段。 113 | # 合并数据 114 | data_new = pd.merge(data_new, merge_sell_data, on=['code'], how='left') 115 | 116 | # 删除老数据。 117 | try: 118 | del_sql = " DELETE FROM `stock_data`.`" + table_name + "` WHERE `date`= '%s' " % datetime_int 119 | common.insert(del_sql) 120 | print("insert_db") 121 | except Exception as e: 122 | print("error :", e) 123 | del data_new["trade_float32"] 124 | try: 125 | common.insert_db(data_new, table_name, False, "`date`,`code`") 126 | print("insert_db") 127 | except Exception as e: 128 | print("error :", e) 129 | # 重命名 130 | del data_new["name"] 131 | print(data_new) 132 | 133 | 134 | # main函数入口 135 | if __name__ == '__main__': 136 | # 计算买卖。 137 | tmp_datetime = common.run_with_args(stat_index_calculate) 138 | -------------------------------------------------------------------------------- /web/static/js/ace-extra.min.js: -------------------------------------------------------------------------------- 1 | !function(){"ace"in window||(window.ace={}),ace.config={storage_method:0,cookie_expiry:604800,cookie_path:""},"vars"in window.ace||(window.ace.vars={}),ace.vars.very_old_ie=!("querySelector"in document.documentElement),ace.settings={saveState:function(a,b,c,d){if(!a||"string"==typeof a&&!(a=document.getElementById(a))||!a.hasAttribute("id"))return!1;if(!ace.hasClass(a,"ace-save-state"))return!1;var b=b||"class",e=a.getAttribute("id"),f=ace.data.get("state","id-"+e)||{};if("string"==typeof f)try{f=JSON.parse(f)}catch(g){f={}}var h,i="undefined"!=typeof c,j=!1,k=/class/i,l=/checked|disabled|readonly|value/i;l.test(b)?h=i?c:a[b]:a.hasAttribute(b)?h=i?c:a.getAttribute(b):i||(j=!0),j?delete f[b]:k.test(b)?(f.hasOwnProperty(b)||(f[b]={}),d===!0?f[b][h]=1:d===!1?f[b][h]=-1:f[b].className=h):f[b]=h,ace.data.set("state","id-"+e,JSON.stringify(f))},loadState:function(a,b){if(!a||"string"==typeof a&&!(a=document.getElementById(a))||!a.hasAttribute("id"))return!1;var c=a.getAttribute("id"),d=ace.data.get("state","id-"+c)||{};if("string"==typeof d)try{d=JSON.parse(d)}catch(e){d={}}var f=function(a,b,c){var d=/class/i,e=/checked|disabled|readonly|value/i;if(d.test(b)){if("object"==typeof c){"className"in c&&a.setAttribute("class",c.className);for(var f in c)if(c.hasOwnProperty(f)){var g=c[f];1==g?ace.addClass(a,f):-1==g&&ace.removeClass(a,f)}}}else e.test(b)?a[b]=c:a.setAttribute(b,c)};if(void 0!==b)d.hasOwnProperty(b)&&null!==d[b]&&f(a,b,d[b]);else for(var g in d)d.hasOwnProperty(g)&&null!==d[g]&&f(a,g,d[g])},clearState:function(a){var b=null;"string"==typeof a?b=a:"hasAttribute"in a&&a.hasAttribute("id")&&(b=a.getAttribute("id")),b&&ace.data.remove("state","id-"+b)}},function(){var a=function(){var a=!1,b="animation",c="",d="Webkit Moz O ms Khtml".split(" "),e="",f=document.createElement("div");if(void 0!==f.style.animationName&&(a=!0),a===!1)for(var g=0;g-1},ace.addClass=function(a,b){for(var c=b.split(/\s+/),d=0;d0&&!ace.hasClass(a,c[d])){var e=a.className;a.className=e+(e.length?" ":"")+c[d]}},ace.removeClass=function(a,b){for(var c=b.split(/\s+/),d=0;d0&&ace.replaceClass(a,c[d]);ace.replaceClass(a,b)},ace.replaceClass=function(a,b,c){var d=new RegExp("(^|\\s)"+b+"(\\s|$)","i");a.className=a.className.replace(d,function(a,b,d){return c?b+c+d:" "}).replace(/^\s+|\s+$/g,"")},ace.toggleClass=function(a,b){ace.hasClass(a,b)?ace.removeClass(a,b):ace.addClass(a,b)},ace.isHTMlElement=function(a){return window.HTMLElement?a instanceof HTMLElement:"nodeType"in a?1==a.nodeType:!1},ace.data=new ace.data_storage(ace.config.storage_method)}(); -------------------------------------------------------------------------------- /old_jobs/guess_return_daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import sys 7 | import time 8 | import pandas as pd 9 | import numpy as np 10 | import math 11 | import tushare as ts 12 | from sqlalchemy.types import NVARCHAR 13 | from sqlalchemy import inspect 14 | import datetime 15 | import heapq 16 | 17 | """ 18 | SELECT `date`, `code`, `name`, `changepercent`, `trade`, `open`, `high`, `low`, 19 | `settlement`, `volume`, `turnoverratio`, `amount`, `per`, `pb`, `mktcap`, `nmc` 20 | FROM stock_data.ts_today_all where `date` = 20171106 and trade > 0 and trade <= 20 21 | and `code` not like '002%' and `code` not like '300%' and `name` not like '%st%' 22 | 23 | """ 24 | 25 | 26 | def stat_index_all(tmp_datetime): 27 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 28 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 29 | print("datetime_str:", datetime_str) 30 | print("datetime_int:", datetime_int) 31 | 32 | # 查询今日满足股票数据。剔除数据:创业板股票数据,中小板股票数据,所有st股票 33 | # #`code` not like '002%' and `code` not like '300%' and `name` not like '%st%' 34 | sql_1 = """ 35 | SELECT `date`, `code`, `name`, `changepercent`, `trade`, `open`, `high`, `low`, 36 | `settlement`, `volume`, `turnoverratio`, `amount`, `per`, `pb`, `mktcap`, `nmc` 37 | FROM stock_data.ts_today_all WHERE `date` = %s and `trade` > 0 and `open` > 0 and trade <= 20 38 | and `code` not like %s and `code` not like %s and `name` not like %s 39 | """ 40 | print(sql_1) 41 | data = pd.read_sql(sql=sql_1, con=common.engine(), params=[datetime_int, '002%', '300%', '%st%']) 42 | data = data.drop_duplicates(subset="code", keep="last") 43 | print("########data[trade]########:") 44 | # print(data["trade"]) 45 | 46 | # 使用 trade 填充数据 47 | stock_guess = pd.DataFrame({ 48 | "date": data["date"], "code": data["code"], "5d": data["trade"], 49 | "10d": data["trade"], "20d": data["trade"], "60d": data["trade"], "5-10d": data["trade"], 50 | "5-20d": data["trade"], "return": data["trade"], "mov_vol": data["trade"] 51 | }, index=data.index.values) 52 | 53 | stock_guess = stock_guess.apply(apply_guess, axis=1) # , axis=1) 54 | # print(stock_guess.head()) 55 | # stock_guess.astype('float32', copy=False) 56 | stock_guess.drop('date', axis=1, inplace=True) # 删除日期字段,然后和原始数据合并。 57 | 58 | # print(stock_guess["5d"]) 59 | 60 | data_new = pd.merge(data, stock_guess, on=['code'], how='left') 61 | print("#############") 62 | 63 | # 使用pandas 函数 : https://pandas.pydata.org/pandas-docs/stable/api.html#id4 64 | data_new["return"] = data_new["return"].mul(100) # 扩大100 倍方便观察 65 | data_new["mov_vol"] = data_new["mov_vol"].mul(100) 66 | 67 | data_new = data_new.round(2) # 数据保留2位小数 68 | 69 | # 删除老数据。 70 | del_sql = " DELETE FROM `stock_data`.`guess_return_daily` WHERE `date`= '%s' " % datetime_int 71 | common.insert(del_sql) 72 | 73 | # data_new["down_rate"] = (data_new["trade"] - data_new["wave_mean"]) / data_new["wave_base"] 74 | common.insert_db(data_new, "guess_return_daily", False, "`date`,`code`") 75 | 76 | # 进行左连接. 77 | # tmp = pd.merge(tmp, tmp2, on=['company_id'], how='left') 78 | 79 | 80 | def apply_guess(tmp): 81 | date = tmp["date"] 82 | code = tmp["code"] 83 | date_end = datetime.datetime.strptime(date, "%Y%m%d") 84 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 85 | date_end = date_end.strftime("%Y-%m-%d") 86 | print(code, date_start, date_end) 87 | # open, high, close, low, volume, price_change, p_change, ma5, ma10, ma20, v_ma5, v_ma10, v_ma20, turnover 88 | # 使用缓存方法。加快计算速度。 89 | stock = common.get_hist_data_cache(code, date_start, date_end) 90 | # 增加空判断,如果是空返回 0 数据。 91 | if stock is None: 92 | return pd.Series([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, code, date, 0.0, 0.0], 93 | index=['10d', '20d', '5-10d', '5-20d', '5d', '60d', 'code', 'date', 'mov_vol', 'return']) 94 | 95 | stock = pd.DataFrame({"close": stock["close"]}, index=stock.index.values) 96 | stock = stock.sort_index(0) # 将数据按照日期排序下。 97 | # print(stock.head(10)) 98 | # 5周期、10周期、20周期和60周期 99 | # 周线、半月线、月线和季度线 100 | stock["5d"] = stock["close"].rolling(window=5).mean() # 周线 101 | stock["10d"] = stock["close"].rolling(window=10).mean() # 半月线 102 | stock["20d"] = stock["close"].rolling(window=20).mean() # 月线 103 | stock["60d"] = stock["close"].rolling(window=60).mean() # 季度线 104 | # 计算日期差。 105 | stock["5-10d"] = (stock["5d"] - stock["10d"]) * 100 / stock["10d"] # 周-半月线差 106 | stock["5-20d"] = (stock["5d"] - stock["20d"]) * 100 / stock["20d"] # 周-月线差 107 | # 计算股票的收益价格 108 | stock["return"] = np.log(stock["close"] / stock["close"].shift(1)) 109 | 110 | # print(stock["return"]) 111 | # 计算股票的【收益率的移动历史标准差】 112 | mov_day = int(len(stock) / 20) 113 | # print("mov_day:", mov_day, len(stock)) 114 | stock["mov_vol"] = stock["return"].rolling(window=mov_day).std() * math.sqrt(mov_day) 115 | # print(stock["mov_vol"].tail()) 116 | # print(stock["return"].tail()) 117 | # print("stock[10d].tail(1)", stock["10d"].tail(1).values[0]) 118 | # 10d 20d 5-10d 5-20d 5d 60d code date mov_vol return 119 | tmp = pd.Series([stock["10d"].tail(1).values[0], stock["20d"].tail(1).values[0], stock["5-10d"].tail(1).values[0], 120 | stock["5-20d"].tail(1).values[0], stock["5d"].tail(1).values[0], stock["60d"].tail(1).values[0], 121 | code, date, stock["mov_vol"].tail(1).values[0], stock["return"].tail(1).values[0]], 122 | index=['10d', '20d', '5-10d', '5-20d', '5d', '60d', 'code', 'date', 'mov_vol', 'return']) 123 | # print(tmp) 124 | return tmp 125 | 126 | 127 | # main函数入口 128 | if __name__ == '__main__': 129 | # 使用方法传递。 130 | tmp_datetime = common.run_with_args(stat_index_all) 131 | -------------------------------------------------------------------------------- /web/templates/stock_web.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/default.html" %} 2 | 3 | 4 | {% block main_content %} 5 | 6 |

    {{ stockWeb.name }}

    7 |
    {{ stockWeb.name }}
    8 | 9 |
     
    10 |
    11 | {% for index,element in enumerate(stockWeb.columns) %} 12 | {% if index < 15 %} 13 | {% if element != 'eastmoney_url' %} 14 |
    15 | {{ stockWeb.column_names[index] }}   16 | 17 | 18 | 21 |
    22 | {% end %} 23 | {% end %}{% end %} 24 |
    25 | 26 | 27 | 28 | 29 | 30 |
    31 | 32 | 33 |
    34 |
    35 | 36 | 37 | {% for column_name in stockWeb.column_names %} 38 | 39 | {% end %} 40 | 41 | 42 |
    {{ column_name }}
    43 |
    44 | 45 | 46 | 58 | 59 | 60 | 73 | 74 | 75 | 175 | {% end %} -------------------------------------------------------------------------------- /old_jobs/guess_sklearn_ma_daily_job.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import libs.common as common 6 | import pandas as pd 7 | import numpy as np 8 | import math 9 | import datetime 10 | import sklearn as skl 11 | from sklearn import datasets, linear_model 12 | # https://github.com/udacity/machine-learning/issues/202 13 | # sklearn.cross_validation 这个包不推荐使用了。 14 | from sklearn.model_selection import train_test_split, cross_val_score 15 | from sklearn.neighbors import KNeighborsClassifier 16 | 17 | # 要操作的数据库表名称。 18 | table_name = "guess_sklearn_ma_daily" 19 | 20 | 21 | # 批处理数据。 22 | def stat_all_batch(tmp_datetime): 23 | datetime_str = (tmp_datetime).strftime("%Y-%m-%d") 24 | datetime_int = (tmp_datetime).strftime("%Y%m%d") 25 | print("datetime_str:", datetime_str) 26 | print("datetime_int:", datetime_int) 27 | 28 | try: 29 | # 删除老数据。 30 | del_sql = " DELETE FROM `stock_data`.`%s` WHERE `date`= %s " % (table_name, datetime_int) 31 | print("del_sql:", del_sql) 32 | common.insert(del_sql) 33 | except Exception as e: 34 | print("error :", e) 35 | 36 | sql_count = """ 37 | SELECT count(1) FROM stock_data.ts_today_all WHERE `date` = %s and `trade` > 0 and `open` > 0 and trade <= 20 38 | and `code` not like %s and `name` not like %s 39 | """ 40 | # 修改逻辑,增加中小板块计算。 中小板:002,创业板:300 。and `code` not like %s and `code` not like %s and `name` not like %s 41 | # count = common.select_count(sql_count, params=[datetime_int, '002%', '300%', '%st%']) 42 | count = common.select_count(sql_count, params=[datetime_int, '300%', '%st%']) 43 | print("count :", count) 44 | batch_size = 100 45 | end = int(math.ceil(float(count) / batch_size) * batch_size) 46 | print(end) 47 | # for i in range(0, end, batch_size): 48 | for i in range(0, end, batch_size): 49 | print("loop :", i) 50 | # 查询今日满足股票数据。剔除数据:创业板股票数据,中小板股票数据,所有st股票 51 | # #`code` not like '002%' and `code` not like '300%' and `name` not like '%st%' 52 | sql_1 = """ 53 | SELECT `date`, `code`, `name`, `changepercent`, `trade`, `open`, `high`, `low`, 54 | `settlement`, `volume`, `turnoverratio`, `amount`, `per`, `pb`, `mktcap`, `nmc` 55 | FROM stock_data.ts_today_all WHERE `date` = %s and `trade` > 0 and `open` > 0 and trade <= 20 56 | and `code` not like %s and `name` not like %s limit %s , %s 57 | """ 58 | print(sql_1) 59 | # data = pd.read_sql(sql=sql_1, con=common.engine(), params=[datetime_int, '002%', '300%', '%st%', i, batch_size]) 60 | data = pd.read_sql(sql=sql_1, con=common.engine(), params=[datetime_int, '300%', '%st%', i, batch_size]) 61 | data = data.drop_duplicates(subset="code", keep="last") 62 | print("########data[trade]########:", len(data)) 63 | 64 | # 使用 trade 填充数据 65 | stock_sklearn = pd.DataFrame({ 66 | "date": data["date"], "code": data["code"], "next_close": data["trade"], 67 | "sklearn_score": data["trade"]}, index=data.index.values) 68 | print(stock_sklearn.head()) 69 | stock_sklearn_apply = stock_sklearn.apply(apply_sklearn, axis=1) # , axis=1) 70 | # 重命名 71 | del stock_sklearn_apply["date"] # 合并前删除 date 字段。 72 | # 合并数据 73 | data_new = pd.merge(data, stock_sklearn_apply, on=['code'], how='left') 74 | # for index, row in data.iterrows(): 75 | # next_stock, score = stat_index_all(row, i) 76 | # print(next_stock, score) 77 | data_new["next_close"] = data_new["next_close"].round(2) # 数据保留4位小数 78 | data_new["sklearn_score"] = data_new["sklearn_score"].round(2) # 数据保留2位小数 79 | 80 | data_new["trade_float32"] = data["trade"].astype('float32', copy=False) 81 | data_new["up_rate"] = (data_new["next_close"] - data_new["trade_float32"]) * 100 / data_new["trade_float32"] 82 | data_new["up_rate"] = data_new["up_rate"].round(2) # 数据保留2位小数 83 | del data_new["trade_float32"] 84 | 85 | try: 86 | common.insert_db(data_new, table_name, False, "`date`,`code`") 87 | print("insert_db") 88 | except Exception as e: 89 | print("error :", e) 90 | # 重命名 91 | del data_new["name"] 92 | print(data_new) 93 | 94 | 95 | # code date next_close sklearn_score 96 | def apply_sklearn(data): 97 | # 要操作的数据库表名称。 98 | print("########stat_index_all########:", len(data)) 99 | date = data["date"] 100 | code = data["code"] 101 | print(date, code) 102 | date_end = datetime.datetime.strptime(date, "%Y%m%d") 103 | date_start = (date_end + datetime.timedelta(days=-300)).strftime("%Y-%m-%d") 104 | date_end = date_end.strftime("%Y-%m-%d") 105 | print(code, date_start, date_end) 106 | 107 | # open high close low volume price_change p_change ma5 ma10 ma20 v_ma5 v_ma10 v_ma20 turnover 108 | stock_X = common.get_hist_data_cache(code, date_start, date_end) 109 | # 增加空判断,如果是空返回 0 数据。 110 | if stock_X is None: 111 | return list([code, date, 0.0, 0.0]) 112 | 113 | stock_X = stock_X.sort_index(0) # 将数据按照日期排序下。 114 | stock_y = pd.Series(stock_X["close"].values) # 标签 115 | 116 | stock_X_next = stock_X.iloc[len(stock_X) - 1] 117 | print("########################### stock_X_next date:", stock_X_next) 118 | # 使用今天的交易价格,13 个指标预测明天的价格。偏移股票数据,今天的数据,目标是明天的价格。 119 | stock_X = stock_X.drop(stock_X.index[len(stock_X) - 1]) # 删除最后一条数据 120 | stock_y = stock_y.drop(stock_y.index[0]) # 删除第一条数据 121 | # print("########################### stock_X date:", stock_X) 122 | 123 | # 删除掉close 也就是收盘价格。 124 | del stock_X["close"] 125 | del stock_X_next["close"] 126 | 127 | model = linear_model.LinearRegression() 128 | # model = KNeighborsClassifier() 129 | 130 | model.fit(stock_X.values, stock_y) 131 | # print("############## test & target #############") 132 | # print("############## coef_ & intercept_ #############") 133 | # print(model.coef_) # 系数 134 | # print(model.intercept_) # 截断 135 | next_close = model.predict([stock_X_next.values]) 136 | if len(next_close) == 1: 137 | next_close = next_close[0] 138 | sklearn_score = model.score(stock_X.values, stock_y) 139 | print("score:", sklearn_score) # 评分 140 | return list([code, date, next_close, sklearn_score * 100]) 141 | 142 | 143 | # main函数入口 144 | if __name__ == '__main__': 145 | # 使用方法传递。 146 | tmp_datetime = common.run_with_args(stat_all_batch) 147 | -------------------------------------------------------------------------------- /web/static/css/bootstrap-datetimepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datetimepicker for Bootstrap 3 3 | * version : 4.17.37 4 | * https://github.com/Eonasdan/bootstrap-datetimepicker/ 5 | */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:after,.bootstrap-datetimepicker-widget.dropdown-menu:before{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:700;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action=incrementHours]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action=incrementMinutes]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action=decrementHours]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action=decrementMinutes]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action=showHours]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action=showMinutes]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action=togglePeriod]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action=clear]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action=today]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.new,.bootstrap-datetimepicker-widget table td.old{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em!important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0} -------------------------------------------------------------------------------- /libs/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # apk add py-mysqldb or 5 | 6 | import platform 7 | import datetime 8 | import time 9 | import sys 10 | import os 11 | import MySQLdb 12 | from sqlalchemy import create_engine 13 | from sqlalchemy.types import NVARCHAR 14 | from sqlalchemy import inspect 15 | import tushare as ts 16 | import pandas as pd 17 | import traceback 18 | 19 | # 使用环境变量获得数据库。兼容开发模式可docker模式。 20 | MYSQL_HOST = os.environ.get('MYSQL_HOST') if (os.environ.get('MYSQL_HOST') != None) else "mariadb" 21 | MYSQL_USER = os.environ.get('MYSQL_USER') if (os.environ.get('MYSQL_USER') != None) else "root" 22 | MYSQL_PWD = os.environ.get('MYSQL_PWD') if (os.environ.get('MYSQL_PWD') != None) else "mariadb" 23 | MYSQL_DB = os.environ.get('MYSQL_DB') if (os.environ.get('MYSQL_DB') != None) else "stock_data" 24 | 25 | print("MYSQL_HOST :", MYSQL_HOST, ",MYSQL_USER :", MYSQL_USER, ",MYSQL_DB :", MYSQL_DB) 26 | MYSQL_CONN_URL = "mysql+mysqldb://" + MYSQL_USER + ":" + MYSQL_PWD + "@" + MYSQL_HOST + ":3306/" + MYSQL_DB + "?charset=utf8" 27 | print("MYSQL_CONN_URL :", MYSQL_CONN_URL) 28 | 29 | # 定义 获得 token 方法 30 | def get_tushare_token(): 31 | tushare_token = os.environ.get('TUSHARE_TOKEN') 32 | if tushare_token != None: 33 | return tushare_token 34 | else: 35 | return "" 36 | 37 | def engine(): 38 | engine = create_engine( 39 | MYSQL_CONN_URL, 40 | encoding='utf8', convert_unicode=True) 41 | return engine 42 | 43 | 44 | def engine_to_db(to_db): 45 | MYSQL_CONN_URL_NEW = "mysql+mysqldb://" + MYSQL_USER + ":" + MYSQL_PWD + "@" + MYSQL_HOST + ":3306/" + to_db + "?charset=utf8" 46 | engine = create_engine( 47 | MYSQL_CONN_URL_NEW, 48 | encoding='utf8', convert_unicode=True) 49 | return engine 50 | 51 | 52 | # 通过数据库链接 engine。 53 | def conn(): 54 | try: 55 | db = MySQLdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PWD, MYSQL_DB, charset="utf8") 56 | # db.autocommit = True 57 | except Exception as e: 58 | print("conn error :", e) 59 | db.autocommit(on=True) 60 | return db.cursor() 61 | 62 | 63 | # 定义通用方法函数,插入数据库表,并创建数据库主键,保证重跑数据的时候索引唯一。 64 | def insert_db(data, table_name, write_index, primary_keys): 65 | # 插入默认的数据库。 66 | insert_other_db(MYSQL_DB, data, table_name, write_index, primary_keys) 67 | 68 | 69 | # 增加一个插入到其他数据库的方法。 70 | def insert_other_db(to_db, data, table_name, write_index, primary_keys): 71 | # 定义engine 72 | engine_mysql = engine_to_db(to_db) 73 | # 使用 http://docs.sqlalchemy.org/en/latest/core/reflection.html 74 | # 使用检查检查数据库表是否有主键。 75 | insp = inspect(engine_mysql) 76 | col_name_list = data.columns.tolist() 77 | # 如果有索引,把索引增加到varchar上面。 78 | if write_index: 79 | # 插入到第一个位置: 80 | col_name_list.insert(0, data.index.name) 81 | print(col_name_list) 82 | data.to_sql(name=table_name, con=engine_mysql, schema=to_db, if_exists='append', 83 | dtype={col_name: NVARCHAR(length=255) for col_name in col_name_list}, index=write_index) 84 | # 判断是否存在主键 85 | if insp.get_primary_keys(table_name) == []: 86 | with engine_mysql.connect() as con: 87 | # 执行数据库插入数据。 88 | try: 89 | con.execute('ALTER TABLE `%s` ADD PRIMARY KEY (%s);' % (table_name, primary_keys)) 90 | except Exception as e: 91 | print("################## ADD PRIMARY KEY ERROR :", e) 92 | 93 | 94 | # 插入数据。 95 | def insert(sql, params=()): 96 | with conn() as db: 97 | print("insert sql:" + sql) 98 | try: 99 | db.execute(sql, params) 100 | except Exception as e: 101 | print("error :", e) 102 | 103 | 104 | # 查询数据 105 | def select(sql, params=()): 106 | with conn() as db: 107 | print("select sql:" + sql) 108 | try: 109 | db.execute(sql, params) 110 | except Exception as e: 111 | print("error :", e) 112 | result = db.fetchall() 113 | return result 114 | 115 | 116 | # 计算数量 117 | def select_count(sql, params=()): 118 | with conn() as db: 119 | print("select sql:" + sql) 120 | try: 121 | db.execute(sql, params) 122 | except Exception as e: 123 | print("error :", e) 124 | result = db.fetchall() 125 | # 只有一个数组中的第一个数据 126 | if len(result) == 1: 127 | return int(result[0][0]) 128 | else: 129 | return 0 130 | 131 | 132 | # 通用函数。获得日期参数。 133 | def run_with_args(run_fun): 134 | tmp_datetime_show = datetime.datetime.now() # 修改成默认是当日执行 + datetime.timedelta() 135 | tmp_datetime_str = tmp_datetime_show.strftime("%Y-%m-%d %H:%M:%S.%f") 136 | str_db = "MYSQL_HOST :" + MYSQL_HOST + ", MYSQL_USER :" + MYSQL_USER + ", MYSQL_DB :" + MYSQL_DB 137 | print("\n######################### " + str_db + " ######################### ") 138 | print("\n######################### begin run %s %s #########################" % (run_fun, tmp_datetime_str)) 139 | start = time.time() 140 | # 要支持数据重跑机制,将日期传入。循环次数 141 | if len(sys.argv) == 3: 142 | # python xxx.py 2017-07-01 10 143 | tmp_year, tmp_month, tmp_day = sys.argv[1].split("-") 144 | loop = int(sys.argv[2]) 145 | tmp_datetime = datetime.datetime(int(tmp_year), int(tmp_month), int(tmp_day)) 146 | for i in range(0, loop): 147 | # 循环插入多次数据,重复跑历史数据使用。 148 | # time.sleep(5) 149 | tmp_datetime_new = tmp_datetime + datetime.timedelta(days=i) 150 | try: 151 | run_fun(tmp_datetime_new) 152 | except Exception as e: 153 | print("error :", e) 154 | traceback.print_exc() 155 | elif len(sys.argv) == 2: 156 | # python xxx.py 2017-07-01 157 | tmp_year, tmp_month, tmp_day = sys.argv[1].split("-") 158 | tmp_datetime = datetime.datetime(int(tmp_year), int(tmp_month), int(tmp_day)) 159 | try: 160 | run_fun(tmp_datetime) 161 | except Exception as e: 162 | print("error :", e) 163 | traceback.print_exc() 164 | else: 165 | # tmp_datetime = datetime.datetime.now() + datetime.timedelta(days=-1) 166 | try: 167 | run_fun(tmp_datetime_show) # 使用当前时间 168 | except Exception as e: 169 | print("error :", e) 170 | traceback.print_exc() 171 | print("######################### finish %s , use time: %s #########################" % ( 172 | tmp_datetime_str, time.time() - start)) 173 | 174 | 175 | # 设置基础目录,每次加载使用。 176 | bash_stock_tmp = "/data/cache/hist_data_cache/%s/%s/" 177 | if not os.path.exists(bash_stock_tmp): 178 | os.makedirs(bash_stock_tmp) # 创建多个文件夹结构。 179 | print("######################### init tmp dir #########################") 180 | 181 | 182 | # 增加读取股票缓存方法。加快处理速度。 183 | def get_hist_data_cache(code, date_start, date_end): 184 | cache_dir = bash_stock_tmp % (date_end[0:7], date_end) 185 | # 如果没有文件夹创建一个。月文件夹和日文件夹。方便删除。 186 | # print("cache_dir:", cache_dir) 187 | if not os.path.exists(cache_dir): 188 | os.makedirs(cache_dir) 189 | cache_file = cache_dir + "%s^%s.gzip.pickle" % (date_end, code) 190 | # 如果缓存存在就直接返回缓存数据。压缩方式。 191 | if os.path.isfile(cache_file): 192 | print("######### read from cache #########", cache_file) 193 | return pd.read_pickle(cache_file, compression="gzip") 194 | else: 195 | print("######### get data, write cache #########", code, date_start, date_end) 196 | stock = ts.get_hist_data(code, start=date_start, end=date_end) 197 | if stock is None: 198 | return None 199 | stock = stock.sort_index(0) # 将数据按照日期排序下。 200 | stock.to_pickle(cache_file, compression="gzip") 201 | return stock 202 | -------------------------------------------------------------------------------- /libs/stock_web_dic.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | class StockWebData: 5 | def __init__(self, mode, type, name, table_name, columns, column_names, primary_key, order_by): 6 | self.mode = mode # 模式,query,editor 查询和编辑模式 7 | self.type = type 8 | self.name = name 9 | self.table_name = table_name 10 | self.columns = columns 11 | self.column_names = column_names 12 | self.primary_key = primary_key 13 | self.order_by = order_by 14 | if mode == "query": 15 | self.url = "/stock/data?table_name=" + self.table_name 16 | elif mode == "editor": 17 | self.url = "/data/editor?table_name=" + self.table_name 18 | 19 | 20 | STOCK_WEB_DATA_LIST = [] 21 | 22 | 23 | # http://tushare.org/fundamental.html 24 | # 参考官网网站的文档,是最全的。 25 | STOCK_WEB_DATA_LIST.append( 26 | StockWebData( 27 | mode="query", 28 | type="基本面数据", 29 | name="股票列表", 30 | table_name="ts_stock_basics", 31 | columns=["code", "name", "industry", "area", "pe", "outstanding", "totals", "totalAssets", "liquidAssets", 32 | "fixedAssets", "reserved", "reservedPerShare", "esp", "bvps", "pb", "timeToMarket", 33 | "undp", "perundp", "rev", "profit", "gpr", "npr", "holders"], 34 | column_names=["代码", "名称", "所属行业", "地区", "市盈率", "流通股本(亿)", "总股本(亿)", "总资产(万)", "流动资产", 35 | "固定资产", "公积金", "每股公积金", "每股收益", "每股净资", "市净率", "上市日期", "未分利润", 36 | "每股未分配", "收入同比(%)", "利润同比(%)", "毛利率(%)", "净利润率(%)", "股东人数" 37 | ], 38 | primary_key=[], 39 | order_by=" code asc " 40 | ) 41 | ) 42 | 43 | STOCK_WEB_DATA_LIST.append( 44 | StockWebData( 45 | mode="query", 46 | type="基本面数据", 47 | name="沪深300成份股", 48 | table_name="ts_stock_hs300s", 49 | columns=["code", "name", "weight"], 50 | column_names=["代码", "名称", "权重"], 51 | primary_key=[], 52 | order_by=" code asc " 53 | ) 54 | ) 55 | 56 | STOCK_WEB_DATA_LIST.append( 57 | StockWebData( 58 | mode="query", 59 | type="基本面数据", 60 | name="中证500成份股", 61 | table_name="ts_stock_zz500s", 62 | columns=["code", "name", "weight"], 63 | column_names=["代码", "名称", "权重"], 64 | primary_key=[], 65 | order_by=" code asc " 66 | ) 67 | ) 68 | 69 | # "code", "name: pchange", "amount", "buy", "bratio", "sell", "sratio", "reason", "date" 70 | # 代码 名称 当日涨跌幅 龙虎榜成交额(万) 买入额(万) 买入占总成交比例 卖出额(万) 卖出占总成交比例 上榜原因 日期 71 | 72 | 73 | STOCK_WEB_DATA_LIST.append( 74 | StockWebData( 75 | mode="query", 76 | type="每日数据", 77 | name="龙虎榜", 78 | table_name="ts_top_list", 79 | columns=["date", "code", "name", "pchange", "amount", "buy", "bratio", "sell", "sratio", "reason"], 80 | column_names=["日期", "代码", "名称", "当日涨跌幅", "龙虎榜成交额(万)", "买入额(万)", "买入占总成交比例", "卖出额(万)", 81 | "卖出占总成交比例", "上榜原因"], 82 | primary_key=[], 83 | order_by=" date desc " 84 | ) 85 | ) 86 | # 实时行情 87 | STOCK_WEB_DATA_LIST.append( 88 | StockWebData( 89 | mode="query", 90 | type="每日数据", 91 | name="每日股票数据", 92 | table_name="ts_today_all", 93 | columns=["date", "code", "name", "changepercent", "trade", "open", "high", "low", "settlement", "volume", 94 | "turnoverratio", "amount", "per", "pb", "mktcap", "nmc"], 95 | column_names=["日期", "代码", "名称", "涨跌幅", "现价", "开盘价", "最高价", "最低价", "昨日收盘价", "成交量", 96 | "换手率", "成交金额", "市盈率", "市净率", "总市值", "流通市值"], 97 | primary_key=[], 98 | order_by=" date desc " 99 | ) 100 | ) 101 | # 大盘指数行情列表 102 | STOCK_WEB_DATA_LIST.append( 103 | StockWebData( 104 | mode="query", 105 | type="每日数据", 106 | name="每日大盘指数行情", 107 | table_name="ts_index_all", 108 | columns=["date", "code", "name", "change", "open", "preclose", "close", "high", "low", "volume", "amount"], 109 | column_names=["日期", "代码", "名称", "涨跌幅", "开盘点位", "昨日收盘点位", "收盘点位", "最高点位", "最低点位", "成交量(手)", "成交金额(亿元)"], 110 | primary_key=[], 111 | order_by=" date desc " 112 | ) 113 | ) 114 | 115 | 116 | # 每日股票指标猜想。 117 | STOCK_WEB_DATA_LIST.append( 118 | StockWebData( 119 | mode="query", 120 | type="每日数据猜想", 121 | name="每日股票指标All猜想", 122 | table_name="guess_indicators_daily", 123 | columns=["date", "code", "name", "changepercent", "trade", "open", "high", "low", "settlement", "volume", 124 | "turnoverratio", "amount", "per", "pb", "mktcap", "nmc", 125 | 'adx', 'adxr', 'boll', 'boll_lb', 'boll_ub', 'cci', 'cci_20', 'close_-1_r', 126 | 'close_-2_r', 'code', 'cr', 'cr-ma1', 'cr-ma2', 'cr-ma3', 'date', 'dma', 'dx', 127 | 'kdjd', 'kdjj', 'kdjk', 'macd', 'macdh', 'macds', 'mdi', 'pdi', 128 | 'rsi_12', 'rsi_6', 'trix', 'trix_9_sma', 'vr', 'vr_6_sma', 'wr_10', 'wr_6'], 129 | column_names=["日期", "代码", "名称", 130 | "涨跌幅", "现价", "开盘价", "最高价", "最低价", "昨日收盘价", "成交量", 131 | "换手率", "成交金额", "市盈率", "市净率", "总市值", "流通市值", 132 | 'adx', 'adxr', 'boll', 'boll_lb', 'boll_ub', 'cci', 'cci_20', 'close_-1_r', 133 | 'close_-2_r', 'code', 'cr', 'cr-ma1', 'cr-ma2', 'cr-ma3', 'date', 'dma', 'dx', 134 | 'kdjd', 'kdjj', 'kdjk', 'macd', 'macdh', 'macds', 'mdi', 'pdi', 135 | 'rsi_12', 'rsi_6', 'trix', 'trix_9_sma', 'vr', 'vr_6_sma', 'wr_10', 'wr_6'], 136 | primary_key=[], 137 | order_by=" date desc " 138 | ) 139 | ) 140 | # 每日股票指标lite猜想买入。 141 | STOCK_WEB_DATA_LIST.append( 142 | StockWebData( 143 | mode="query", 144 | type="每日数据猜想", 145 | name="每日股票指标买入猜想", 146 | table_name="guess_indicators_lite_buy_daily", 147 | columns=["date", "code", "name", "changepercent", "trade", "open", "high", "low", "settlement", "volume", 148 | "turnoverratio", "amount", "per", "pb", "mktcap", "nmc", 149 | "kdjj", "rsi_6", "cci"], 150 | column_names=["日期", "代码", "名称", 151 | "涨跌幅", "现价", "开盘价", "最高价", "最低价", "昨日收盘价", "成交量", 152 | "换手率", "成交金额", "市盈率", "市净率", "总市值", "流通市值", 153 | "kdjj", "rsi_6", "cci"], 154 | primary_key=[], 155 | order_by=" buy_date desc " 156 | ) 157 | ) 158 | 159 | # 每日股票指标lite猜想卖出。 160 | STOCK_WEB_DATA_LIST.append( 161 | StockWebData( 162 | mode="query", 163 | type="每日数据猜想", 164 | name="每日股票指标卖出猜想", 165 | table_name="guess_indicators_lite_sell_daily", 166 | columns=["date", "code", "name", "changepercent", "trade", "open", "high", "low", "settlement", "volume", 167 | "turnoverratio", "amount", "per", "pb", "mktcap", "nmc", 168 | "kdjj", "rsi_6", "cci"], 169 | column_names=["日期", "代码", "名称", 170 | "涨跌幅", "现价", "开盘价", "最高价", "最低价", "昨日收盘价", "成交量", 171 | "换手率", "成交金额", "市盈率", "市净率", "总市值", "流通市值", 172 | "kdjj", "rsi_6", "cci"], 173 | primary_key=[], 174 | order_by=" buy_date desc " 175 | ) 176 | ) 177 | 178 | STOCK_WEB_DATA_MAP = {} 179 | WEB_EASTMONEY_URL = "http://quote.eastmoney.com/%s.html" 180 | # 再拼接成Map使用。 181 | for tmp in STOCK_WEB_DATA_LIST: 182 | try: 183 | # 增加columns 字段中的【查看股票】 184 | tmp_idx = tmp.columns.index("code") 185 | tmp.column_names.insert(tmp_idx + 1, "查看股票") 186 | except Exception as e: 187 | print("error :", e) 188 | 189 | STOCK_WEB_DATA_MAP[tmp.table_name] = tmp 190 | 191 | if len(tmp.columns) != len(tmp.column_names): 192 | print(u"error:", tmp.table_name, ",columns:", len(tmp.columns), ",column_names:", len(tmp.column_names)) 193 | -------------------------------------------------------------------------------- /web/dataTableHandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | from tornado import gen 6 | import libs.stock_web_dic as stock_web_dic 7 | import web.base as webBase 8 | import logging 9 | import datetime 10 | 11 | # info 蓝色 云财经 12 | # success 绿色 13 | # danger 红色 东方财富 14 | # warning 黄色 15 | WEB_EASTMONEY_URL = u""" 16 | 东财 18 | 19 | 指标 21 | 22 | 东研 24 | 25 | 26 | """ 27 | # 和在dic中的字符串一致。字符串前面都不特别声明是u"" 28 | eastmoney_name = "查看股票" 29 | 30 | 31 | # 获得页面数据。 32 | class GetStockHtmlHandler(webBase.BaseHandler): 33 | @gen.coroutine 34 | def get(self): 35 | name = self.get_argument("table_name", default=None, strip=False) 36 | stockWeb = stock_web_dic.STOCK_WEB_DATA_MAP[name] 37 | # self.uri_ = ("self.request.url:", self.request.uri) 38 | # print self.uri_ 39 | date_now = datetime.datetime.now() 40 | date_now_str = date_now.strftime("%Y%m%d") 41 | # 每天的 16 点前显示昨天数据。 42 | if date_now.hour < 16: 43 | date_now_str = (date_now + datetime.timedelta(days=-1)).strftime("%Y%m%d") 44 | 45 | try: 46 | # 增加columns 字段中的【查看股票 东方财富】 47 | logging.info(eastmoney_name in stockWeb.column_names) 48 | if eastmoney_name in stockWeb.column_names: 49 | tmp_idx = stockWeb.column_names.index(eastmoney_name) 50 | logging.info(tmp_idx) 51 | try: 52 | # 防止重复插入数据。可能会报错。 53 | stockWeb.columns.remove("eastmoney_url") 54 | except Exception as e: 55 | print("error :", e) 56 | stockWeb.columns.insert(tmp_idx, "eastmoney_url") 57 | except Exception as e: 58 | print("error :", e) 59 | logging.info("####################GetStockHtmlHandlerEnd") 60 | self.render("stock_web.html", stockWeb=stockWeb, date_now=date_now_str, 61 | leftMenu=webBase.GetLeftMenu(self.request.uri)) 62 | 63 | 64 | # 获得股票数据内容。 65 | class GetStockDataHandler(webBase.BaseHandler): 66 | def get(self): 67 | 68 | # 获得分页参数。 69 | start_param = self.get_argument("start", default=0, strip=False) 70 | length_param = self.get_argument("length", default=10, strip=False) 71 | print("page param:", length_param, start_param) 72 | 73 | name_param = self.get_argument("name", default=None, strip=False) 74 | type_param = self.get_argument("type", default=None, strip=False) 75 | 76 | stock_web = stock_web_dic.STOCK_WEB_DATA_MAP[name_param] 77 | 78 | # https://datatables.net/manual/server-side 79 | self.set_header('Content-Type', 'application/json;charset=UTF-8') 80 | order_by_column = [] 81 | order_by_dir = [] 82 | # 支持多排序。使用shift+鼠标左键。 83 | for item, val in self.request.arguments.items(): 84 | # logging.info("item: %s, val: %s" % (item, val) ) 85 | if str(item).startswith("order["): 86 | print("order:", item, ",val:", val[0]) 87 | if str(item).startswith("order[") and str(item).endswith("[column]"): 88 | order_by_column.append(int(val[0])) 89 | if str(item).startswith("order[") and str(item).endswith("[dir]"): 90 | order_by_dir.append(val[0].decode("utf-8")) # bytes转换字符串 91 | 92 | search_by_column = [] 93 | search_by_data = [] 94 | 95 | # 返回search字段。 96 | for item, val in self.request.arguments.items(): 97 | # logging.info("item: %s, val: %s" % (item, val)) 98 | if str(item).startswith("columns[") and str(item).endswith("[search][value]"): 99 | logging.info("item: %s, val: %s" % (item, val)) 100 | str_idx = item.replace("columns[", "").replace("][search][value]", "") 101 | int_idx = int(str_idx) 102 | # 找到字符串 103 | str_val = val[0].decode("utf-8") 104 | if str_val != "": # 字符串。 105 | search_by_column.append(stock_web.columns[int_idx]) 106 | search_by_data.append(val[0].decode("utf-8")) # bytes转换字符串 107 | 108 | # 打印日志。 109 | search_sql = "" 110 | search_idx = 0 111 | logging.info(search_by_column) 112 | logging.info(search_by_data) 113 | for item in search_by_column: 114 | val = search_by_data[search_idx] 115 | logging.info("idx: %s, column: %s, value: %s " % (search_idx, item, val)) 116 | # 查询sql 117 | if search_idx == 0: 118 | search_sql = " WHERE `%s` = '%s' " % (item, val) 119 | else: 120 | search_sql = search_sql + " AND `%s` = '%s' " % (item, val) 121 | search_idx = search_idx + 1 122 | 123 | # print("stockWeb :", stock_web) 124 | order_by_sql = "" 125 | # 增加排序。 126 | if len(order_by_column) != 0 and len(order_by_dir) != 0: 127 | order_by_sql = " ORDER BY " 128 | idx = 0 129 | for key in order_by_column: 130 | # 找到排序字段和dir。 131 | col_tmp = stock_web.columns[key] 132 | dir_tmp = order_by_dir[idx] 133 | if idx != 0: 134 | order_by_sql += " ,cast(`%s` as decimal) %s" % (col_tmp, dir_tmp) 135 | else: 136 | order_by_sql += " cast(`%s` as decimal) %s" % (col_tmp, dir_tmp) 137 | idx += 1 138 | # 查询数据库。 139 | limit_sql = "" 140 | if int(length_param) > 0: 141 | limit_sql = " LIMIT %s , %s " % (start_param, length_param) 142 | sql = " SELECT * FROM `%s` %s %s %s " % ( 143 | stock_web.table_name, search_sql, order_by_sql, limit_sql) 144 | count_sql = " SELECT count(1) as num FROM `%s` %s " % (stock_web.table_name, search_sql) 145 | 146 | logging.info("select sql : " + sql) 147 | logging.info("count sql : " + count_sql) 148 | stock_web_list = self.db.query(sql) 149 | 150 | for tmp_obj in (stock_web_list): 151 | logging.info("####################") 152 | if type_param == "editor": 153 | tmp_obj["DT_RowId"] = tmp_obj[stock_web.columns[0]] 154 | # logging.info(tmp_obj) 155 | try: 156 | # 增加columns 字段中的【东方财富】 157 | logging.info("eastmoney_name : %s " % eastmoney_name) 158 | if eastmoney_name in stock_web.column_names: 159 | tmp_idx = stock_web.column_names.index(eastmoney_name) 160 | 161 | code_tmp = tmp_obj["code"] 162 | # 判断上海还是 深圳,东方财富 接口要求。 163 | if code_tmp.startswith("6"): 164 | code_tmp = "SH" + code_tmp 165 | else: 166 | code_tmp = "SZ" + code_tmp 167 | 168 | tmp_url = WEB_EASTMONEY_URL % (tmp_obj["code"], tmp_obj["code"], code_tmp) 169 | tmp_obj["eastmoney_url"] = tmp_url 170 | logging.info(tmp_idx) 171 | logging.info(tmp_obj["eastmoney_url"]) 172 | # logging.info(type(tmp_obj)) 173 | # tmp.column_names.insert(tmp_idx, eastmoney_name) 174 | except Exception as e: 175 | print("error :", e) 176 | 177 | stock_web_size = self.db.query(count_sql) 178 | logging.info("stockWebList size : %s " % stock_web_size) 179 | 180 | obj = { 181 | "draw": 0, 182 | "recordsTotal": stock_web_size[0]["num"], 183 | "recordsFiltered": stock_web_size[0]["num"], 184 | "data": stock_web_list 185 | } 186 | # logging.info("####################") 187 | # logging.info(obj) 188 | self.write(json.dumps(obj)) 189 | -------------------------------------------------------------------------------- /web/static/css/buttons.dataTables.min.css: -------------------------------------------------------------------------------- 1 | @keyframes dtb-spinner{100%{transform:rotate(360deg)}}@-o-keyframes dtb-spinner{100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes dtb-spinner{100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dtb-spinner{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes dtb-spinner{100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}div.dt-button-info{position:fixed;top:50%;left:50%;width:400px;margin-top:-100px;margin-left:-200px;background-color:white;border:2px solid #111;box-shadow:3px 3px 8px rgba(0,0,0,0.3);border-radius:3px;text-align:center;z-index:21}div.dt-button-info h2{padding:0.5em;margin:0;font-weight:normal;border-bottom:1px solid #ddd;background-color:#f3f3f3}div.dt-button-info>div{padding:1em}button.dt-button,div.dt-button,a.dt-button{position:relative;display:inline-block;box-sizing:border-box;margin-right:0.333em;padding:0.5em 1em;border:1px solid #999;border-radius:2px;cursor:pointer;font-size:0.88em;color:black;white-space:nowrap;overflow:hidden;background-color:#e9e9e9;background-image:-webkit-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:-moz-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:-ms-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:-o-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:linear-gradient(to bottom, #fff 0%, #e9e9e9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='white', EndColorStr='#e9e9e9');-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none;outline:none}button.dt-button.disabled,div.dt-button.disabled,a.dt-button.disabled{color:#999;border:1px solid #d0d0d0;cursor:default;background-color:#f9f9f9;background-image:-webkit-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:-moz-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:-ms-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:-o-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:linear-gradient(to bottom, #fff 0%, #f9f9f9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#ffffff', EndColorStr='#f9f9f9')}button.dt-button:active:not(.disabled),button.dt-button.active:not(.disabled),div.dt-button:active:not(.disabled),div.dt-button.active:not(.disabled),a.dt-button:active:not(.disabled),a.dt-button.active:not(.disabled){background-color:#e2e2e2;background-image:-webkit-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:-moz-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:-ms-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:-o-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:linear-gradient(to bottom, #f3f3f3 0%, #e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f3f3f3', EndColorStr='#e2e2e2');box-shadow:inset 1px 1px 3px #999999}button.dt-button:active:not(.disabled):hover:not(.disabled),button.dt-button.active:not(.disabled):hover:not(.disabled),div.dt-button:active:not(.disabled):hover:not(.disabled),div.dt-button.active:not(.disabled):hover:not(.disabled),a.dt-button:active:not(.disabled):hover:not(.disabled),a.dt-button.active:not(.disabled):hover:not(.disabled){box-shadow:inset 1px 1px 3px #999999;background-color:#cccccc;background-image:-webkit-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:-moz-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:-ms-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:-o-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:linear-gradient(to bottom, #eaeaea 0%, #ccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#eaeaea', EndColorStr='#cccccc')}button.dt-button:hover,div.dt-button:hover,a.dt-button:hover{text-decoration:none}button.dt-button:hover:not(.disabled),div.dt-button:hover:not(.disabled),a.dt-button:hover:not(.disabled){border:1px solid #666;background-color:#e0e0e0;background-image:-webkit-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:-moz-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:-ms-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:-o-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:linear-gradient(to bottom, #f9f9f9 0%, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f9f9f9', EndColorStr='#e0e0e0')}button.dt-button:focus:not(.disabled),div.dt-button:focus:not(.disabled),a.dt-button:focus:not(.disabled){border:1px solid #426c9e;text-shadow:0 1px 0 #c4def1;outline:none;background-color:#79ace9;background-image:-webkit-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:-moz-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:-ms-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:-o-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:linear-gradient(to bottom, #bddef4 0%, #79ace9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#bddef4', EndColorStr='#79ace9')}.dt-button embed{outline:none}div.dt-buttons{position:relative;float:left}div.dt-buttons.buttons-right{float:right}div.dt-button-collection{position:absolute;top:0;left:0;width:150px;margin-top:3px;padding:8px 8px 4px 8px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.4);background-color:white;overflow:hidden;z-index:2002;border-radius:5px;box-shadow:3px 3px 5px rgba(0,0,0,0.3);z-index:2002;-webkit-column-gap:8px;-moz-column-gap:8px;-ms-column-gap:8px;-o-column-gap:8px;column-gap:8px}div.dt-button-collection button.dt-button,div.dt-button-collection div.dt-button,div.dt-button-collection a.dt-button{position:relative;left:0;right:0;display:block;float:none;margin-bottom:4px;margin-right:0}div.dt-button-collection button.dt-button:active:not(.disabled),div.dt-button-collection button.dt-button.active:not(.disabled),div.dt-button-collection div.dt-button:active:not(.disabled),div.dt-button-collection div.dt-button.active:not(.disabled),div.dt-button-collection a.dt-button:active:not(.disabled),div.dt-button-collection a.dt-button.active:not(.disabled){background-color:#dadada;background-image:-webkit-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:-moz-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:-ms-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:-o-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f0f0f0', EndColorStr='#dadada');box-shadow:inset 1px 1px 3px #666}div.dt-button-collection.fixed{position:fixed;top:50%;left:50%;margin-left:-75px;border-radius:0}div.dt-button-collection.fixed.two-column{margin-left:-150px}div.dt-button-collection.fixed.three-column{margin-left:-225px}div.dt-button-collection.fixed.four-column{margin-left:-300px}div.dt-button-collection>*{-webkit-column-break-inside:avoid;break-inside:avoid}div.dt-button-collection.two-column{width:300px;padding-bottom:1px;-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2}div.dt-button-collection.three-column{width:450px;padding-bottom:1px;-webkit-column-count:3;-moz-column-count:3;-ms-column-count:3;-o-column-count:3;column-count:3}div.dt-button-collection.four-column{width:600px;padding-bottom:1px;-webkit-column-count:4;-moz-column-count:4;-ms-column-count:4;-o-column-count:4;column-count:4}div.dt-button-background{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);background:-ms-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-moz-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-o-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0,0,0,0.3)), color-stop(1, rgba(0,0,0,0.7)));background:-webkit-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:radial-gradient(ellipse farthest-corner at center, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);z-index:2001}@media screen and (max-width: 640px){div.dt-buttons{float:none !important;text-align:center}}button.dt-button.processing,div.dt-button.processing,a.dt-button.processing{color:rgba(0,0,0,0.2)}button.dt-button.processing:after,div.dt-button.processing:after,a.dt-button.processing:after{position:absolute;top:50%;left:50%;width:16px;height:16px;margin:-8px 0 0 -8px;box-sizing:border-box;display:block;content:' ';border:2px solid #282828;border-radius:50%;border-left-color:transparent;border-right-color:transparent;animation:dtb-spinner 1500ms infinite linear;-o-animation:dtb-spinner 1500ms infinite linear;-ms-animation:dtb-spinner 1500ms infinite linear;-webkit-animation:dtb-spinner 1500ms infinite linear;-moz-animation:dtb-spinner 1500ms infinite linear} 2 | -------------------------------------------------------------------------------- /web/demo-chart.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example demonstrates how to embed matplotlib WebAgg interactive 3 | plotting in your own web application and framework. It is not 4 | necessary to do all this if you merely want to display a plot in a 5 | browser or use matplotlib's built-in Tornado-based server "on the 6 | side". 7 | 8 | The framework being used must support web sockets. 9 | """ 10 | 11 | import io 12 | 13 | try: 14 | import tornado 15 | except ImportError: 16 | raise RuntimeError("This example requires tornado.") 17 | import tornado.web 18 | import tornado.httpserver 19 | import tornado.ioloop 20 | import tornado.websocket 21 | 22 | from matplotlib.backends.backend_webagg_core import ( 23 | FigureManagerWebAgg, new_figure_manager_given_figure) 24 | from matplotlib.figure import Figure 25 | 26 | import numpy as np 27 | 28 | import json 29 | 30 | 31 | def create_figure(): 32 | """ 33 | Creates a simple example figure. 34 | """ 35 | fig = Figure() 36 | a = fig.add_subplot(111) 37 | t = np.arange(0.0, 3.0, 0.01) 38 | s = np.sin(2 * np.pi * t) 39 | a.plot(t, s) 40 | return fig 41 | 42 | 43 | # The following is the content of the web page. You would normally 44 | # generate this using some sort of template facility in your web 45 | # framework, but here we just use Python string formatting. 46 | html_content = """ 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 90 | 91 | matplotlib 92 | 93 | 94 | 95 |
    96 |
    97 | 98 | 99 | """ 100 | 101 | 102 | class MyApplication(tornado.web.Application): 103 | class MainPage(tornado.web.RequestHandler): 104 | """ 105 | Serves the main HTML page. 106 | """ 107 | 108 | def get(self): 109 | manager = self.application.manager 110 | ws_uri = "ws://{req.host}/".format(req=self.request) 111 | content = html_content % { 112 | "ws_uri": ws_uri, "fig_id": manager.num} 113 | self.write(content) 114 | 115 | class MplJs(tornado.web.RequestHandler): 116 | """ 117 | Serves the generated matplotlib javascript file. The content 118 | is dynamically generated based on which toolbar functions the 119 | user has defined. Call `FigureManagerWebAgg` to get its 120 | content. 121 | """ 122 | 123 | def get(self): 124 | self.set_header('Content-Type', 'application/javascript') 125 | js_content = FigureManagerWebAgg.get_javascript() 126 | 127 | self.write(js_content) 128 | 129 | class Download(tornado.web.RequestHandler): 130 | """ 131 | Handles downloading of the figure in various file formats. 132 | """ 133 | 134 | def get(self, fmt): 135 | manager = self.application.manager 136 | 137 | mimetypes = { 138 | 'ps': 'application/postscript', 139 | 'eps': 'application/postscript', 140 | 'pdf': 'application/pdf', 141 | 'svg': 'image/svg+xml', 142 | 'png': 'image/png', 143 | 'jpeg': 'image/jpeg', 144 | 'tif': 'image/tiff', 145 | 'emf': 'application/emf' 146 | } 147 | 148 | self.set_header('Content-Type', mimetypes.get(fmt, 'binary')) 149 | 150 | buff = io.BytesIO() 151 | manager.canvas.print_figure(buff, format=fmt) 152 | self.write(buff.getvalue()) 153 | 154 | class WebSocket(tornado.websocket.WebSocketHandler): 155 | """ 156 | A websocket for interactive communication between the plot in 157 | the browser and the server. 158 | 159 | In addition to the methods required by tornado, it is required to 160 | have two callback methods: 161 | 162 | - ``send_json(json_content)`` is called by matplotlib when 163 | it needs to send json to the browser. `json_content` is 164 | a JSON tree (Python dictionary), and it is the responsibility 165 | of this implementation to encode it as a string to send over 166 | the socket. 167 | 168 | - ``send_binary(blob)`` is called to send binary image data 169 | to the browser. 170 | """ 171 | supports_binary = True 172 | 173 | def open(self): 174 | # Register the websocket with the FigureManager. 175 | manager = self.application.manager 176 | manager.add_web_socket(self) 177 | if hasattr(self, 'set_nodelay'): 178 | self.set_nodelay(True) 179 | 180 | def on_close(self): 181 | # When the socket is closed, deregister the websocket with 182 | # the FigureManager. 183 | manager = self.application.manager 184 | manager.remove_web_socket(self) 185 | 186 | def on_message(self, message): 187 | # The 'supports_binary' message is relevant to the 188 | # websocket itself. The other messages get passed along 189 | # to matplotlib as-is. 190 | 191 | # Every message has a "type" and a "figure_id". 192 | message = json.loads(message) 193 | if message['type'] == 'supports_binary': 194 | self.supports_binary = message['value'] 195 | else: 196 | manager = self.application.manager 197 | manager.handle_json(message) 198 | 199 | def send_json(self, content): 200 | self.write_message(json.dumps(content)) 201 | 202 | def send_binary(self, blob): 203 | if self.supports_binary: 204 | self.write_message(blob, binary=True) 205 | else: 206 | data_uri = "data:image/png;base64,{0}".format( 207 | blob.encode('base64').replace('\n', '')) 208 | self.write_message(data_uri) 209 | 210 | def __init__(self, figure): 211 | self.figure = figure 212 | self.manager = new_figure_manager_given_figure( 213 | id(figure), figure) 214 | 215 | super(MyApplication, self).__init__([ 216 | # Static files for the CSS and JS 217 | (r'/_static/(.*)', 218 | tornado.web.StaticFileHandler, 219 | {'path': FigureManagerWebAgg.get_static_file_path()}), 220 | 221 | # The page that contains all of the pieces 222 | ('/', self.MainPage), 223 | 224 | ('/mpl.js', self.MplJs), 225 | 226 | # Sends images and events to the browser, and receives 227 | # events from the browser 228 | ('/ws', self.WebSocket), 229 | 230 | # Handles the downloading (i.e., saving) of static images 231 | (r'/download.([a-z0-9.]+)', self.Download), 232 | ], debug=True) 233 | 234 | 235 | if __name__ == "__main__": 236 | figure = create_figure() 237 | application = MyApplication(figure) 238 | http_server = tornado.httpserver.HTTPServer(application) 239 | http_server.listen(9999) 240 | 241 | print("http://127.0.0.1:9999/") 242 | print("Press Ctrl+C to quit") 243 | 244 | tornado.ioloop.IOLoop.instance().start() 245 | -------------------------------------------------------------------------------- /web/static/js/bootbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootbox.js [v4.4.0] 3 | * 4 | * http://bootboxjs.com/license.txt 5 | */ 6 | !function(a,b){"use strict";"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d.call(c,a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(2>=d&&f===d-1?e.className="btn-primary":e.className="btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"",header:"",footer:"",closeButton:"",form:"
    ",inputs:{text:"",textarea:"",email:"",select:"",checkbox:"
    ",date:"",time:"",number:"",password:""}},o={locale:"en",backdrop:"static",animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback.call(this):!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,!1)},a.buttons.confirm.callback=function(){return a.callback.call(this,!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback.call(this,c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!b.isArray(k))throw new Error("Please pass an array of input options");if(!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b("").attr("label",d.group)),e=o[d.group]),e.append("")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b("
    "),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),a.maxlength&&h.attr("maxlength",a.maxlength),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var d=b(n.dialog),f=d.find(".modal-dialog"),i=d.find(".modal-body"),j=a.buttons,k="",l={onEscape:a.onEscape};if(b.fn.modal===c)throw new Error("$.fn.modal is not defined; please double check you have included the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ for more details.");if(g(j,function(a,b){k+="",l[a]=b.callback}),i.find(".bootbox-body").html(a.message),a.animate===!0&&d.addClass("fade"),a.className&&d.addClass(a.className),"large"===a.size?f.addClass("modal-lg"):"small"===a.size&&f.addClass("modal-sm"),a.title&&i.before(n.header),a.closeButton){var m=b(n.closeButton);a.title?d.find(".modal-header").prepend(m):m.css("margin-top","-10px").prependTo(i)}return a.title&&d.find(".modal-title").html(a.title),k.length&&(i.after(n.footer),d.find(".modal-footer").html(k)),d.on("hidden.bs.modal",function(a){a.target===this&&d.remove()}),d.on("shown.bs.modal",function(){d.find(".btn-primary:first").focus()}),"static"!==a.backdrop&&d.on("click.dismiss.bs.modal",function(a){d.children(".modal-backdrop").length&&(a.currentTarget=d.children(".modal-backdrop").get(0)),a.target===a.currentTarget&&d.trigger("escape.close.bb")}),d.on("escape.close.bb",function(a){l.onEscape&&e(a,d,l.onEscape)}),d.on("click",".modal-footer button",function(a){var c=b(this).data("bb-handler");e(a,d,l[c])}),d.on("click",".bootbox-close-button",function(a){e(a,d,l.onEscape)}),d.on("keyup",function(a){27===a.which&&d.trigger("escape.close.bb")}),b(a.container).append(d),d.modal({backdrop:a.backdrop?"static":!1,keyboard:!1,show:!1}),a.show&&d.modal("show"),d},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={bg_BG:{OK:"Ок",CANCEL:"Отказ",CONFIRM:"Потвърждавам"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fa:{OK:"قبول",CANCEL:"لغو",CONFIRM:"تایید"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},hu:{OK:"OK",CANCEL:"Mégsem",CONFIRM:"Megerősít"},hr:{OK:"OK",CANCEL:"Odustani",CONFIRM:"Potvrdi"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sq:{OK:"OK",CANCEL:"Anulo",CONFIRM:"Prano"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},th:{OK:"ตกลง",CANCEL:"ยกเลิก",CONFIRM:"ยืนยัน"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.addLocale=function(a,c){return b.each(["OK","CANCEL","CONFIRM"],function(a,b){if(!c[b])throw new Error("Please supply a translation for '"+b+"'")}),q[a]={OK:c.OK,CANCEL:c.CANCEL,CONFIRM:c.CONFIRM},p},p.removeLocale=function(a){return delete q[a],p},p.setLocale=function(a){return p.setDefaults("locale",a)},p.init=function(c){return a(c||b)},p}); --------------------------------------------------------------------------------