从无到用写个股票分析APP(一)
前言:再给自己挖个坑吧。
我想写个什么东西呢?
一:可以浏览当下相关资讯,以及大盘指数实时更新。
二:添加自选股票,可以查看该股票的走势图,相关资讯以及基本数据。
三:通过 server 端定义相关指标及常用策略,手机上可以直接添加已定义的技术指标及策略用以组合,然后在在 server 端得到结果,手机端查看。
项目地址:https://github.com/youerning/pstock
所用技术:
● nodejs:socket.io
● golang
● javascript:angularjs,chartjs
● css.
● Python:tushare,PyAlgoTrade,tornado,flask
● 打包:ionic
然后预览一下两天做的 demo
文章目录:
● 一:布局
● 二:部分细节说明
● 三:获取数据
● 四:绘图
● 五:编写策略 //等待填坑
● 六:优化细节 //等待填坑
● 七:美化,收尾 //等待填坑
注:为了使文章不会过于冗长,代码细节可能有所删减,详情参考项目源码:
(一)
1. 环境搭建参考:从无到有写一个运维APP(一)
2. 创建项目
ionic start pstock blank
3. 编写index.html。
4. 创建相应模板文件,结构大致如下
5. 创建路由
app.config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider) {$ionicConfigProvider.tabs.position('bottom');$stateProvider.state("home", {url:"/home",views:{"tab-home":{controller:"homeCtrl",templateUrl: "tpls/home.html"}}});略...
至此,基本结构确定。
(二)
1. 上拉,下拉。
按住屏幕上下拖动,用以刷新数据以及加载数据在 ionic 的 JavaScript 组件已经有现成的了,所以可以直接拿过来用
代码如下:
`n`.`title`
- `n`.`media_name`
然后在相应的 controller 里面定义指定的执行函数 loadNewer(),loadOlder()
2. 自选股票的数据保存。
因为没有打算将自选的股票放在 server 端,所以数据应该保存在本地,即 localStorage 里面
$scope.userCode = angular.fromJson(window.localStorage["userCode"] || "{}");function persist() {window.localStorage["userCode"] = angular.toJson($scope.userCode)};
(三)
1. 获取新闻数据
在国内获取数据时间很难过的事情,为什么难过就不说了,当然可以自己爬,但是那样太不优雅了。
这里我们今日头条的新闻数据(今日头条不是没有公开过自己的API么?)
首先我们打开以下今日头条的网站
然后数据就出现了,就是这么有尿性,其实还有很多网站也这样,大家可以自己试试。
参考:
https://github.com/iMeiji/Toutiao/wiki/%E4%BB%8A%E6%97%A5%E5%A4%B4%E6%9D%A1Api%E5%88%86%E6%9E%90
2. 获取股票数据
这里用 tushare,当然了也可以用其他的 API。
参考:http://tushare.org/trading.html#id2
3. 策略数据(待填坑。。。)
跑 PyAlgoTrade 策略。
其实直接用 tushare 的数据会报错,不过,也就是少了个 Adj Close,加个字段也不会那么难得。。。
4. server端代码
#coding: utf8from flask import Flaskfrom flask import Response, request, abortimport urlparseimport requestsimport jsonimport tushare as tsfrom random import randintfrom bs4 import BeautifulSoupimport pandas as pd# import sys# reload(sys)# sys.setdefaultencoding('utf-8')app = Flask(__name__)# sinaApi = "http://hq.sinajs.cn/list="detailUrl = "http://stockpage.10jqka.com.cn/%s/company/"toutiao = "http://www.toutiao.com/api/article/recent/?source=2&category=%s&as=A105177907376A5&cp=5797C7865AD54E1&count=5&offset=0&_=%s"def getUserAgent():userAgent = ["Mozilla/5.0 (compatible, MSIE 10.0, Windows NT, DigExt)","Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, 360SE)","Mozilla/4.0 (compatible, MSIE 8.0, Windows NT 6.0, Trident/4.0)","Mozilla/5.0 (compatible, MSIE 9.0, Windows NT 6.1, Trident/5.0,","Opera/9.80 (Windows NT 6.1, U, en) Presto/2.8.131 Version/11.11","Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, TencentTraveler 4.0)","Mozilla/5.0 (Windows, U, Windows NT 6.1, en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50","Mozilla/5.0 (Macintosh, Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11","Mozilla/5.0 (Macintosh, U, Intel Mac OS X 10_6_8, en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50","Mozilla/5.0 (Linux, U, Android 3.0, en-us, Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13","Mozilla/5.0 (iPad, U, CPU OS 4_3_3 like Mac OS X, en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5","Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, Trident/4.0, SE 2.X MetaSr 1.0, SE 2.X MetaSr 1.0, .NET CLR 2.0.50727, SE 2.X MetaSr 1.0)","Mozilla/5.0 (iPhone, U, CPU iPhone OS 4_3_3 like Mac OS X, en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5","MQQBrowser/26 Mozilla/5.0 (Linux, U, Android 2.3.7, zh-cn, MB200 Build/GRJ22, CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"]return userAgent[randint(0,len(userAgent)-1)]@app.route("//", methods=["GET","POST"])def index(app):headers = {"User-Agent": getUserAgent()}code = request.args["code"]data = {}error = ""if app == "now":# 获取当前价格code = code.split(",")df = ts.get_realtime_quotes(code)ret = df.to_json()elif app == "stock":# 获取股票历史数据df = ts.get_hist_data(code)df = df.sort_index()df["date"] = df.indexdf.index = range(len(df.index))ret = df.to_json()elif app == "detail":# 获取股票基本数据# 公司名称# 所属地域# 公司简介# 经营范围ret = {}url = detailUrl % codepage = requests.get(url, headers=headers)soup = BeautifulSoup(page.content, "html.parser")name = soup.select("td span")[0].textbussines = soup.select("td span")[3].textregion = soup.select("td span")[1].textintro = soup.select("p.tip.lh34")[-2].text[:-3]ret["name"] = nameret["bussines"] = bussinesret["region"] = regionret["intro"] = introelif app == "bt":ret = [{"status":"ok"}]elif app == "news":# 反向代理今日头条catelog = request.args["catelog"]time = request.args["now"]url = toutiao % (catelog, time)page = requests.get(url, headers=headers)ret = [{"status":"ok"}]else:ret = ""error = "incorrect url"try:data["data"] = json.loads(ret)except Exception as e:data["data"] = retdata["error"] = error# print dataresp = Response(json.dumps(data))if error:abort(500)resp.headers["Content-Type"] = "application/json; charset=UTF-8"resp.headers["access-control-allow-origin"] = "*"return respif __name__ == "__main__":app.run(port=80,debug=True, host="0.0.0.0")
5. client 端代码
$http.get(surl).success(function(resp) {$scope.labelsline = Object.values(resp.data.date);$scope.seriesline = ["ma5", "ma10", "ma20", "close"];$scope.dataline = [Object.values(resp.data.ma5),Object.values(resp.data.ma10),Object.values(resp.data.ma20),Object.values(resp.data.close)];$scope.optionsline = {title: {display:true,text: "趋势图"},elements: {point:{radius: 0}},xAxis: {display:true,axisLabel: 'X Axis',rotateLabels: 90}};
(四)
用 echarts 或者 chartjs,其实这没有技术含量的来着。。。主要查 API。
不过似乎手机端显示有问题,可能数据量过大或者不兼容之类的,待排查。。。
5,6,7 待填坑
自问自答:
Q:明明没用 golang,socket.io,tornado,为毛在所用技术中写出来。
A:我构思了,可是还没写完。
Q:写一个 web 的不也挺好的么。
A:写完了 app 自然会写 web 的。。。
后记:值得一说的事,好像也没想象中的那么简单,预想是三天就写完的来着,在下一篇之前,我应该先写 pyalgotrade 源码解读。
如果觉得不错,并有所收获,请我喝杯茶呗