|
|
|
@ -118,6 +118,8 @@ def index(): |
|
|
|
|
# Login Page |
|
|
|
|
@app.route('/login') |
|
|
|
|
def login(): |
|
|
|
|
if 'username' in session: |
|
|
|
|
return render_template('base.html') |
|
|
|
|
return render_template('login.html') |
|
|
|
|
@app.route('/login', methods=['POST']) |
|
|
|
|
def login_post(): |
|
|
|
@ -325,9 +327,9 @@ def buildPort(): |
|
|
|
|
n = len(port.index) |
|
|
|
|
if n < lookback+backtest+63: |
|
|
|
|
return f'''<span>投資組合無法建立,資料長度與所選參數不符。</span>''' |
|
|
|
|
elif n > 909+lookback: |
|
|
|
|
port = port.iloc[-(909+lookback):, :] |
|
|
|
|
market = market.iloc[-909:] |
|
|
|
|
elif n > 1009+lookback: |
|
|
|
|
port = port.iloc[-(1009+lookback):, :] |
|
|
|
|
market = market.iloc[-1009:] |
|
|
|
|
else: |
|
|
|
|
market = market.iloc[lookback:] |
|
|
|
|
|
|
|
|
@ -341,7 +343,7 @@ def buildPort(): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get portfolio info. |
|
|
|
|
info = MVO.portfolio_info(np.array([1]), rets['Portfolio'].to_numpy().reshape(-1, 1), rets['Equally'].to_numpy()) |
|
|
|
|
info = MVO.portfolio_info(np.array([1]), rets['Portfolio'].to_numpy().reshape(-1, 1), market.pct_change().dropna().to_numpy()) |
|
|
|
|
data = (ts, name, session.get('username').split('@')[0], comp, role, info['annual_ret'], |
|
|
|
|
info['vol'], info['mdd'], info['annual_sr'], |
|
|
|
|
info['beta'], info['alpha'], info['var10'], info['R2'], True, comment, stock_list, json.dumps(weight.to_dict()), json.dumps(rets.to_dict())) |
|
|
|
@ -365,7 +367,61 @@ def custom(): |
|
|
|
|
else: |
|
|
|
|
flash('使用投組功能請先登入。', 'warning') |
|
|
|
|
return redirect(url_for('login')) |
|
|
|
|
return render_template('custom.html', message='No') |
|
|
|
|
return render_template('custom.html') |
|
|
|
|
@app.route('/custom', methods=['POST']) |
|
|
|
|
def custom_post(): |
|
|
|
|
if login_required(): |
|
|
|
|
pass |
|
|
|
|
else: |
|
|
|
|
flash('使用投組功能請先登入。', 'warning') |
|
|
|
|
return redirect(url_for('login')) |
|
|
|
|
port = pd.read_csv(request.files['csv_file'], index_col=0, parse_dates=True) |
|
|
|
|
role = request.form.get('role') |
|
|
|
|
lookback = int(request.form.get('lookback')) |
|
|
|
|
backtest = int(request.form.get('frequency')) |
|
|
|
|
gamma = float(request.form.get('gamma'))/100 |
|
|
|
|
# Optimization |
|
|
|
|
n = len(port.index) |
|
|
|
|
if n < lookback+backtest+63: |
|
|
|
|
return f'''<span>投資組合無法建立,資料長度與所選參數不符。</span>''' |
|
|
|
|
elif n > 1009+lookback: |
|
|
|
|
port = port.iloc[-(1009+lookback):, :] |
|
|
|
|
else: |
|
|
|
|
pass |
|
|
|
|
length, num = port.shape |
|
|
|
|
ret = port.pct_change().dropna() |
|
|
|
|
weight, rets = rolling_optimize(ret, lookback, backtest, role=role, gamma=gamma) |
|
|
|
|
weight.index = weight.index.astype(str) |
|
|
|
|
rets.index = rets.index.astype(str) |
|
|
|
|
rets= rets.round(5) |
|
|
|
|
|
|
|
|
|
info = MVO.portfolio_info(np.array([1]), rets['Portfolio'].to_numpy().reshape(-1, 1), np.zeros(len(ret)-lookback)) |
|
|
|
|
info['username'] = session.get('username').split('@')[0] |
|
|
|
|
info['role'] = role_map[role] |
|
|
|
|
info['id']='使用者自行上傳' |
|
|
|
|
info['name']='使用者自行上傳' |
|
|
|
|
info['date'] = '-' |
|
|
|
|
info['alpha'] = '-' |
|
|
|
|
info['beta'] = '-' |
|
|
|
|
info['r2'] = '-' |
|
|
|
|
info['assets'] = list(port.columns) |
|
|
|
|
# Plotting weight |
|
|
|
|
fig = px.bar(weight) |
|
|
|
|
fig['layout'] = {} |
|
|
|
|
info['weight'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) |
|
|
|
|
# Plotting weight |
|
|
|
|
fig = (rets+1).cumprod().iloc[::5, :].plot() |
|
|
|
|
fig['layout'] = {} |
|
|
|
|
info['ret'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) |
|
|
|
|
# Plotting ret bars |
|
|
|
|
rets.index.name = 'date' |
|
|
|
|
rets.index = pd.to_datetime(rets.index) |
|
|
|
|
ret_hist = rets.to_period('Q').groupby('date').apply(lambda x: (x+1).prod()-1) |
|
|
|
|
ret_hist.index = ret_hist.index.astype(str) |
|
|
|
|
fig = px.bar(ret_hist) |
|
|
|
|
fig['layout'] = {} |
|
|
|
|
info['bar'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) |
|
|
|
|
return render_template('result_view.html', data=info) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/result', methods=['GET', 'POST']) |
|
|
|
|