我在Heroku上部署了一个Flask应用程序,在Foreman中本地运行得很好,但在部署时会出现问题。
该应用尝试使用GET方法中提供的符号构建股票图表。如果给定的股票代码不存在,我有一个IOError异常集。通常,任何无效的股票代码都将被忽略,任何有效的股票代码都将呈现其图表。
问题在于,虽然这在本地运行得非常好,但是一旦部署了应用程序,every single symbol is considered invalid and never gets rendered。例如,AAPL在本地呈现图表,但在已部署的版本中返回“未找到符号”。我尝试删除异常块并让应用程序崩溃,但我看到的只是500错误页面。
我还尝试添加一些打印输出日志来检查事情何时变得混乱。如果你有兴趣,这是Foreman输出:
18:15:37 web.1 | testing for length of symbol array...
18:15:37 web.1 | building plots...
18:15:37 web.1 | building data...
18:15:38 web.1 | generating data...
18:15:38 web.1 | building RSI plot...
18:15:38 web.1 | building main plot...
18:15:38 web.1 | building MACD plot...
18:15:38 web.1 | building grid...
18:15:38 web.1 | building snippet...
18:15:38 web.1 | creating signals...
18:15:38 web.1 | AAPL SMA signal says:
18:15:38 web.1 | WAIT
18:15:38 web.1 | AAPL Bollinger breakout signal says:
18:15:38 web.1 | WAIT
18:15:38 web.1 | AAPL RSI signal says:
18:15:38 web.1 | WAIT
18:15:38 web.1 | returning snippet...
18:15:38 web.1 | rendering template...
这是Heroku的日志:
2014-01-13T23:38:11.722125+00:00 app[web.1]: testing for length of symbol array...
2014-01-13T23:38:11.722125+00:00 app[web.1]: building plots...
2014-01-13T23:38:11.722125+00:00 app[web.1]: building data...
2014-01-13T23:38:12.226672+00:00 app[web.1]: generating data...
2014-01-13T23:38:12.228213+00:00 app[web.1]: building RSI plot...
2014-01-13T23:38:12.247723+00:00 app[web.1]: building grid...
2014-01-13T23:38:12.248022+00:00 app[web.1]: building snippet...
2014-01-13T23:38:12.236035+00:00 app[web.1]: building main plot...
2014-01-13T23:38:12.243688+00:00 app[web.1]: building MACD plot...
2014-01-13T23:38:12.477185+00:00 app[web.1]: rendering template...
2014-01-13T23:38:12.477185+00:00 app[web.1]: symbol AAPL not found
对于这些日志,真的奇怪的是“未找到符号AAPL”应该在“渲染模板”之前打印,而不是之后,无论有多少符号发现或未找到。另外,在构建JS片段之后创建了两个图。所以事情正在发生。我不知道为什么,除非那只是因为他们的时间戳......但是这会改变什么呢?
我注意到在another question中,如果应用程序崩溃,日志会输出回溯。查看堆栈跟踪将非常有用,因此我可以看到Foreman的输出和Heroku日志之间的区别。但是,我没有在两者中找到任何追溯,所以我不知道究竟是什么打破了。 (这可能也是因为我正在使用的一个名为Bokeh的模块出错了,在这种情况下,由于Heroku安装了该模块本身,所以我无法做任何事情,无论如何它都适用于我的机器。)< / p>
当我部署它时,为什么它不能正常工作,我感到很神秘,但在其他地方工作。如果你想看到它的实际效果,你可以try to pull up Apple's stock page,但是你会得到“找不到符号”的错误。
这是我的所有代码。
routes.py:
from flask import Flask, render_template, request
import plots
app = Flask(__name__)
# Define and add a home page.
@app.route('/') # The base URL for the home page.
def resume():
return render_template('resume.html')
@app.route('/echo/<message>')
def echo(message):
# Echos the message passed to it.
return "%s" % message
@app.route('/stocks')
def lookup():
s = request.args.get('symbol')
print "testing for length of symbol array..."
try:
len(s)
except TypeError:
return render_template('stocks.html',
error='Please request a stock symbol.',
s_length=0,
sd_length=0)
if len(s) == 0:
return render_template('stocks.html',
error='Please request a stock symbol.',
s_length=0,
sd_length=0)
symbols_list = s.split(',')
snippet_dict = {}
print "building plots..."
for i in symbols_list:
try:
snippet_dict[i] = plots.build_plot(i.upper())
except IOError:
print "symbol %s not found" % i
continue
print "rendering template..."
if len(snippet_dict) == 0:
return render_template('stocks.html',
error='No valid symbols found.',
s_length=0,
sd_length=0)
return render_template('stocks.html',
snippet_dict=snippet_dict,
sd_length=len(snippet_dict))
# Run this thing!
if __name__ == '__main__':
app.run(debug=True)
plots.py:
from bokeh.plotting import *
from bokeh.objects import Range1d
import pandas
import pandas.io.data as web
from pyta import *
import numpy as np
# Creates a set of stock charts in Bokeh.
#Trying to add signals to the indicators.
def sma_signal(sma50, sma200):
output_array = []
for i in range(len(sma50)):
if i == 0:
output_array.append('BEGIN')
continue
if pandas.isnull(sma50[i]):
output_array.append('WAIT')
elif sma50[i] > sma200[i]:
if sma50[i-1] < sma200[i-1]:
output_array.append('BUY')
else:
output_array.append('WAIT')
elif sma50[i] < sma200[i]:
if sma50[i-1] > sma200[i-1]:
output_array.append('SELL')
else:
output_array.append('WAIT')
else:
output_array.append('WAIT')
return output_array
def boll_signal(price, upperband, lowerband):
output_array = []
for i in range(len(upperband)):
if price[i] > upperband[i]:
output_array.append('BUY')
elif price[i] < lowerband[i]:
output_array.append('SELL')
else:
output_array.append('WAIT')
return output_array
def rsi_signal(rsi):
output_array = []
for i in range(len(rsi)):
if rsi[i] > 70:
output_array.append('OVERBOUGHT')
elif rsi[i] < 30:
output_array.append('OVERSOLD')
else:
output_array.append('WAIT')
return output_array
# Create a plot for each symbol.
def build_data(symbol):
data = web.DataReader(symbol, 'google')
close = data['Close']
sma50 = sma(close, 50)
data['SMA50'] = sma50
sma200 = sma(close, 200)
data['SMA200'] = sma200
upperband = bollinger_upper(close, sma50, 50)
lowerband = bollinger_lower(close, sma50, 50)
data['Bollinger (upper)'] = upperband
data['Bollinger (lower)'] = lowerband
rsi50 = rsi(close)
data['RSI50'] = rsi50
macd = macd_line(close)
signal = macd_signal(macd)
hist = macd_hist(macd, signal)
data['MACD Line'] = macd
data['MACD Signal'] = signal
data['MACD Histogram'] = hist
return data
def build_plot(symbol):
main_plot_title = symbol
print "building data..."
data = build_data(symbol)
# Generate data.
print "generating data..."
file_name = '%s.html' % symbol
output_file(file_name,
title='How are my stocks doing today?')
close = data['Close']
dates = data.index
# Define plot constants.
plot_width = 1000
# Perform TA on stock data.
# TODO: Make the buy/sell signals actually do something.
# Define SMA 50/200.
sma50 = data['SMA50']
sma200 = data['SMA200']
# Define Bollinger Bands.
upperband = data['Bollinger (upper)']
lowerband = data['Bollinger (lower)']
# Define RSI.
rsi50 = data['RSI50'] # TODO: Figure out a way to translate "crosses under/above line" to a sell signal.
# Define MACD Line, Signal, and Histogram.
macd = data['MACD Line']
signal = data['MACD Signal'] # TODO: Actually use these. Figure out subplots!
hist = data['MACD Histogram']
# Finished with the data? Then it's time to plot!
x = data.index
y = close
# Plot RSI (remember, it goes on top)
# Predefine plot for axis buggery.
print "building RSI plot..."
rsi_plot = line(x, rsi50,
color='#000000',
x_axis_type=None)
# Define RSI axis boundaries.
x_range = Range1d(start=x[0], end=x[-1])
xbounds = [x[0], x[-1]]
curplot().y_range = Range1d(start=0, end=100)
curplot().x_range = x_range
yaxis().bounds = [0, 100]
xaxis().bounds = xbounds
hold()
rsi_plot = line(x, rsi50,
color='#000000',
x_axis_type=None)
line(x, (np.ones(len(rsi50)) * 30),
color='#4daf4a')
line(x, (np.ones(len(rsi50)) * 70),
color='#e41a1c')
# Miscellaneous plot attributes.
rsi_plot.title = main_plot_title
rsi_plot.height = 200
rsi_plot.width = plot_width
rsi_plot.min_border_bottom = 10
grid().grid_line_alpha = 0.4
yaxis().axis_label = 'RSI'
# Remove hold for the main plot.
hold()
print "building main plot..."
# Plot raw stock data (main plot).
main_plot = line(x, y,
color='#1B9E77',
legend='Price at Close',
x_axis_type=None,
title='')
# Hold for the overlays.
hold()
# Plot overlays.
# SMA 50:
line(x, sma50,
color='#D95F02',
legend='50-day SMA')
# SMA 200:
line(x, sma200,
color='#e7298a',
legend='200-day SMA')
# Bollinger shading glyph:
bandprice = stackify(upperband, lowerband) # Reverse the upper band data and append it to the lower band data.
banddates = stackify(dates, dates) # Do the same for the dates.
patch(pandas.to_datetime(banddates), bandprice,
color='#7570B3',
fill_alpha=0.2)
# Remove hold, allow for more plots to be added.
hold()
# Miscellaneous plot attributes.
main_plot.height = 600
main_plot.width = plot_width
yaxis().axis_label = 'Price (USD)'
grid().grid_line_alpha = 0.4
xaxis().bounds = xbounds
curplot().x_range = x_range
# Plot MACD.
print "building MACD plot..."
macd_plot = line(x, macd,
color='#D95F02',
title='',
x_axis_type='datetime')
hold()
line(x, signal,
color='#1B9E77')
hold()
# Attributes.
macd_plot.height = 200
macd_plot.width = plot_width
yaxis().axis_label = 'MACD'
macd_plot.min_border_top = 0
macd_plot.min_border_bottom = 100
# Make a grid and snippet from this.
print "building grid..."
plot_grid = gridplot([[rsi_plot], [main_plot], [macd_plot]])
print "building snippet..."
snippet = plot_grid.create_html_snippet(embed_base_url='../static/js/temp/',
embed_save_loc='./static/js/temp')
# Return signal arrays.
print "creating signals..."
sma_signals = sma_signal(sma50, sma200)
boll_signals = boll_signal(close, upperband, lowerband)
rsi_signals = rsi_signal(rsi50)
print '%s SMA signal says:' % symbol
print sma_signals[-1]
print '%s Bollinger breakout signal says:' % symbol
print boll_signals[-1]
print '%s RSI signal says:' % symbol
print rsi_signals[-1]
print "returning snippet..."
return snippet
令人讨厌的代码:
print "building plots..."
for i in symbols_list:
try:
snippet_dict[i] = plots.build_plot(i.upper())
except IOError:
print "symbol %s not found" % i
continue
Heroku日志中有什么有趣的东西吗?
这就是我得到的全部。
2014-01-14T01:24:13.000718+00:00 heroku[web.1]: Unidling
2014-01-14T01:24:13.001093+00:00 heroku[web.1]: State changed from down to starting
2014-01-14T01:24:17.900834+00:00 heroku[web.1]: Starting process with command `gunicorn routes:app`
2014-01-14T01:24:18.796042+00:00 app[web.1]: 2014-01-14 01:24:18 [2] [INFO] Starting gunicorn 18.0
2014-01-14T01:24:18.796610+00:00 app[web.1]: 2014-01-14 01:24:18 [2] [INFO] Listening at: http://0.0.0.0:25179 (2)
2014-01-14T01:24:18.796681+00:00 app[web.1]: 2014-01-14 01:24:18 [2] [INFO] Using worker: sync
2014-01-14T01:24:18.802306+00:00 app[web.1]: 2014-01-14 01:24:18 [7] [INFO] Booting worker with pid: 7
2014-01-14T01:24:21.078033+00:00 app[web.1]: testing for length of symbol array...
2014-01-14T01:24:21.078033+00:00 app[web.1]: building plots...
2014-01-14T01:24:21.078033+00:00 app[web.1]: building data...
2014-01-14T01:24:21.385022+00:00 app[web.1]: generating data...
2014-01-14T01:24:21.386059+00:00 app[web.1]: building RSI plot...
2014-01-14T01:24:21.390727+00:00 app[web.1]: building main plot...
2014-01-14T01:24:21.398065+00:00 app[web.1]: building MACD plot...
2014-01-14T01:24:21.398388+00:00 app[web.1]: building grid...
2014-01-14T01:24:21.398541+00:00 app[web.1]: building snippet...
2014-01-14T01:24:21.548095+00:00 app[web.1]: symbol AAPL not found
2014-01-14T01:24:21.548270+00:00 app[web.1]: rendering template...
2014-01-14T01:24:21.593435+00:00 heroku[router]: at=info method=GET path=/stocks?symbol=AAPL host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=5ms service=518ms status=200 bytes=1635
2014-01-14T01:24:21.774327+00:00 heroku[router]: at=info method=GET path=/static/css/main.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=4ms service=19ms status=200 bytes=1081
2014-01-14T01:24:21.882153+00:00 heroku[router]: at=info method=GET path=/static/css/continuum.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=5ms status=200 bytes=370
2014-01-14T01:24:21.954590+00:00 heroku[router]: at=info method=GET path=/static/js/bokeh.js host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=1ms service=22ms status=200 bytes=489909
2014-01-14T01:24:21.963913+00:00 heroku[router]: at=info method=GET path=/static/css/normalize.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=33ms service=30ms status=200 bytes=7546
2014-01-14T01:24:23.059117+00:00 heroku[router]: at=info method=GET path=/favicon.ico host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=12ms status=404 bytes=233
2014-01-14T01:24:21.773260+00:00 heroku[router]: at=info method=GET path=/static/css/bootstrap-bokeh-2.0.4.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=19ms status=200 bytes=106102
2014-01-14T01:24:21.861494+00:00 heroku[router]: at=info method=GET path=/static/css/bokeh.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=3ms service=8ms status=200 bytes=2464
2014-01-14T01:24:19.498609+00:00 heroku[web.1]: State changed from starting to up
2014-01-14T01:24:21.903080+00:00 heroku[router]: at=info method=GET path=/static/css/unsemantic.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=3ms service=10ms status=200 bytes=42798
有趣的是,它实际上需要一段时间才能显示最后两三行,即使它们应该同时发生。
删除异常块分别从Foreman和Heroku获取这些日志:
23:53:45 web.1 | started with pid 8846
23:53:45 web.1 | 2014-01-13 23:53:45 [8846] [INFO] Starting gunicorn 18.0
23:53:45 web.1 | 2014-01-13 23:53:45 [8846] [INFO] Listening at: http://0.0.0.0:5000 (8846)
23:53:45 web.1 | 2014-01-13 23:53:45 [8846] [INFO] Using worker: sync
23:53:45 web.1 | 2014-01-13 23:53:45 [8849] [INFO] Booting worker with pid: 8849
23:53:58 web.1 | testing for length of symbol array...
23:53:58 web.1 | building plots...
23:53:58 web.1 | building data...
2014-01-14T04:58:03.316195+00:00 app[web.1]: testing for length of symbol array...
2014-01-14T04:58:03.316195+00:00 app[web.1]: building plots...
2014-01-14T04:58:03.316195+00:00 app[web.1]: building data...
2014-01-14T04:58:05.360351+00:00 heroku[router]: at=info method=GET path=/stocks?symbol=notasymbol host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=1ms service=2054ms status=500 bytes=291
我在Foreman上没有得到500错误,但我在日志中做了。我似乎仍然不清楚为什么我得到了500。显然,这是一个IOError,但对于有效的符号,它不会发生......它会发生。
2014-01-14T04:57:05.540709+00:00 app[web.1]: testing for length of symbol array...
2014-01-14T04:57:05.540709+00:00 app[web.1]: building plots...
2014-01-14T04:57:05.540709+00:00 app[web.1]: building data...
2014-01-14T04:57:06.170323+00:00 app[web.1]: generating data...
2014-01-14T04:57:06.183668+00:00 app[web.1]: building grid...
2014-01-14T04:57:06.171197+00:00 app[web.1]: building RSI plot...
2014-01-14T04:57:06.176253+00:00 app[web.1]: building main plot...
2014-01-14T04:57:06.180958+00:00 app[web.1]: building MACD plot...
2014-01-14T04:57:06.183783+00:00 app[web.1]: building snippet...
2014-01-14T04:57:06.338506+00:00 heroku[router]: at=info method=GET path=/stocks?symbol=AAPL host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=809ms status=500 bytes=291