master
SeanChenTaipei 2 years ago
parent e87b7bafb2
commit 080cb86a2c
  1. 1
      cool.json
  2. 164
      main.py
  3. 16
      sql_script/create_strategy.sql
  4. 14
      static/js/addStock.js
  5. 7
      templates/base.html
  6. 2
      templates/login.html
  7. 4
      templates/result.html
  8. 152
      templates/result_view.html
  9. 14
      templates/strategy_tw.html

File diff suppressed because one or more lines are too long

@ -13,6 +13,7 @@ import string
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import psycopg2 import psycopg2
import psycopg2.extras
import plotly import plotly
import plotly.express as px import plotly.express as px
from portfolio_builder import MVO from portfolio_builder import MVO
@ -20,32 +21,32 @@ pd.options.plotting.backend = "plotly"
# PARAMETERS # PARAMETERS
CONFIGS = { CONFIGS = {
"ENV": "development", # "ENV": "development",
"DEBUG": True, # "DEBUG": True,
"SECRET_KEY": os.urandom(30), # Set the secret key for session authentication "SECRET_KEY": os.urandom(30), # Set the secret key for session authentication
"PERMANENT_SESSION_LIFETIME": timedelta(minutes=60) "PERMANENT_SESSION_LIFETIME": timedelta(minutes=60)
} }
# SQL_CONFIG = dict(
# database= os.getenv("PGDATABASE"),
# user=os.getenv("PGUSER"),
# host=os.getenv("PGHOST"),
# port=os.getenv("PGPORT"),
# password=os.getenv("PGPASSWORD")
# )
SQL_CONFIG = dict( SQL_CONFIG = dict(
database="railway", database= os.getenv("PGDATABASE"),
user="postgres", user=os.getenv("PGUSER"),
host="containers-us-west-103.railway.app", host=os.getenv("PGHOST"),
port="5913", port=os.getenv("PGPORT"),
password="gv5Mh7cPjCm9YTjAmsYD" password=os.getenv("PGPASSWORD")
) )
# SQL_CONFIG = dict(
# database="railway",
# user="postgres",
# host="containers-us-west-103.railway.app",
# port="5913",
# password="gv5Mh7cPjCm9YTjAmsYD"
# )
# SQL_CONFIG = {@ # SQL_CONFIG = {@
# 'database': "tpm", # 'database': "tpm",
# 'user': "hsienchen", # 'user': "hsienchen",
# 'host': "127.0.0.1", # 'host': "127.0.0.1",
# 'port': "5432" # 'port': "5432"
# } # }
role_map = dict(max_sharpe='最大化夏普比率', max_sortino='最大化索提諾比率', min_volatilty='最小化波動率', quadratic_utility='最大化效用函數')
app = Flask(__name__) app = Flask(__name__)
app.config.from_mapping(CONFIGS) app.config.from_mapping(CONFIGS)
@ -68,7 +69,6 @@ def get_stock(conn, stock_list, tw):
with conn: with conn:
with conn.cursor() as curs: with conn.cursor() as curs:
curs.execute(sql, (stock_list, )) curs.execute(sql, (stock_list, ))
# print(curs.mogrify(sql, (stock_list,)))
data= curs.fetchall() data= curs.fetchall()
else: else:
sql1="SELECT ticker, date, price, return FROM stock_price where ticker = ANY(%s)" sql1="SELECT ticker, date, price, return FROM stock_price where ticker = ANY(%s)"
@ -87,6 +87,27 @@ def get_stock(conn, stock_list, tw):
port = pd.concat([g.get_group(t).set_index('date')['price'] for t in stock_list], axis=1, join='inner') port = pd.concat([g.get_group(t).set_index('date')['price'] for t in stock_list], axis=1, join='inner')
port.columns=stock_list port.columns=stock_list
return port return port
def rolling_optimize(ret, lookback=126, backtest=126, role="max_sharpe", gamma=None):
n, num = ret.shape
period = (n - lookback)//backtest+1
weights = []
start = []
rets = []
for i in range(period):
curr = i*backtest+lookback
data_train = ret.iloc[curr-lookback:curr, :].to_numpy()
data_test = ret.iloc[curr:curr+backtest, :]
if len(data_test) == 0:
break
w = MVO.opt(data_train, role=role, gamma=gamma)
start.append(data_test.index[0])
weights.append(w)
rets.append(data_test.to_numpy()@w)
weight = pd.DataFrame(weights, columns=ret.columns, index=pd.to_datetime(start))
rets = np.hstack(rets)
equally_weighted = ret.iloc[lookback:, :].to_numpy()@np.ones(num)/num
rets = pd.DataFrame(np.vstack([rets, equally_weighted]).T, columns=['Portfolio', 'Equally'], index=ret.index[lookback:])
return weight, rets
# Define the route for the index pages # Define the route for the index pages
@ -97,9 +118,6 @@ def index():
# Login Page # Login Page
@app.route('/login') @app.route('/login')
def login(): def login():
# for key in session:
# print(key, session[key])
# print(session.get('username'), session['username'], session.get('username') and session['username'])
return render_template('login.html') return render_template('login.html')
@app.route('/login', methods=['POST']) @app.route('/login', methods=['POST'])
def login_post(): def login_post():
@ -147,7 +165,6 @@ def registration_post():
rep_password = request.form.get('rep-password') rep_password = request.form.get('rep-password')
# check password # check password
if not password is None and password == rep_password: if not password is None and password == rep_password:
print(username, password)
conn = psycopg2.connect(**SQL_CONFIG) conn = psycopg2.connect(**SQL_CONFIG)
## Connect to the database ## Connect to the database
with conn.cursor() as curs: with conn.cursor() as curs:
@ -240,7 +257,6 @@ def submit_stock_list():
fig = port.plot(title='資產價格走勢', labels=dict(index="Date", value="Price", variable="Assets")) fig = port.plot(title='資產價格走勢', labels=dict(index="Date", value="Price", variable="Assets"))
fig['layout'] = {} fig['layout'] = {}
print(type(stock_list))
# 序列化 # 序列化
graphJSON = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) graphJSON = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
@ -264,10 +280,10 @@ def buildPort():
if time.time() - session['lastCreateTime'] < 10: if time.time() - session['lastCreateTime'] < 10:
print("UNTIL: ", time.time()-session['lastCreateTime']) print("UNTIL: ", time.time()-session['lastCreateTime'])
return '''<span>投資組合建立時間間隔(或與登入時間間隔)必須大於60秒!</span>''' return '''<span>投資組合建立時間間隔(或與登入時間間隔)必須大於60秒!</span>'''
print('last_creation', time.time() - session['lastCreateTime']) # print('last_creation', time.time() - session['lastCreateTime'])
session['lastCreateTime'] = time.time() session['lastCreateTime'] = time.time()
print('last_creation', session['lastCreateTime']) # print('last_creation', session['lastCreateTime'])
print("-"*10) # print("-"*10)
for key in request.form: for key in request.form:
print(key, request.form[key], type(request.form[key])) print(key, request.form[key], type(request.form[key]))
@ -276,22 +292,25 @@ def buildPort():
if name == '': if name == '':
prefix=''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) prefix=''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
name= prefix + f"-{round(time.time()%100, 2)}" name= prefix + f"-{round(time.time()%100, 2)}"
# Opt Parameters
comp = request.form.get('comp') comp = request.form.get('comp')
ts = int(request.form.get('ts')) # ts = int(request.form.get('ts'))
ts = datetime.fromtimestamp(ts/1000) ts = datetime.now().strftime("%Y-%m-%d, %H:%M:%S")
role = request.form.get('role') role = request.form.get('role')
lookback = int(request.form.get('lookback')) lookback = int(request.form.get('lookback'))
backtest = int(request.form.get('frequency')) backtest = int(request.form.get('frequency'))
gamma = float(request.form.get('gamma'))/100 gamma = float(request.form.get('gamma'))/100
comment = request.form.get('comment') comment = request.form.get('comment')
stock_list = json.loads(request.form.get('stockList')) stock_list = json.loads(request.form.get('stockList'))
ratio=0.7 ratio=0.7
# Algorithm MVO # Algorithm MVO
print("-"*10) print("-"*10)
print("Enter Algorithms") print("Enter Algorithms")
print("-"*10) print("-"*10)
# time.sleep(20)
# Query DB # Query DB
market_asset = '0050.TW' if session['tw']==1 else 'SPY' market_asset = '0050.TW' if session['tw']==1 else 'SPY'
conn = psycopg2.connect(**SQL_CONFIG) conn = psycopg2.connect(**SQL_CONFIG)
@ -302,36 +321,32 @@ def buildPort():
port = get_stock(conn, stock_list+[market_asset], session['tw']) port = get_stock(conn, stock_list+[market_asset], session['tw'])
market = port[market_asset] market = port[market_asset]
port = port[stock_list] port = port[stock_list]
# Optimization
n = len(port.index)
if n < lookback+backtest+63:
return f'''<span>投資組合無法建立,資料長度與所選參數不符。</span>'''
elif n > 757+lookback:
port = port.iloc[-(757+lookback):, :]
market = market.iloc[-757:]
else:
market = market.iloc[lookback:]
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)
length, num = port.shape # Get portfolio info.
tsize = int(length*ratio) info = MVO.portfolio_info(np.array([1]), rets['Portfolio'].to_numpy().reshape(-1, 1), rets['Equally'].to_numpy())
# time label data = (ts, name, session.get('username').split('@')[0], comp, role, info['annual_ret'],
train_label = port.index[1:][:tsize] info['vol'], info['mdd'], info['annual_sr'],
test_label = port.index[1:][tsize:] info['beta'], info['alpha'], info['var10'], info['R2'], True, comment, stock_list, json.dumps(weight.to_dict()), json.dumps(rets.to_dict()))
# data
data_return = port.pct_change().dropna().to_numpy()
market_return = market.pct_change().dropna().to_numpy()
train = data_return[:tsize, :]
test = data_return[tsize:, :]
train_market = market_return[:tsize]
test_market = market_return[tsize:]
# optimization
sol = MVO.opt(train, role=role)
train_info = MVO.portfolio_info(sol, train, train_market)
test_info = MVO.portfolio_info(sol, test, test_market)
# print(sol, train_info, test_info)
# print("-"*10)
# print(ts, name, session.get('username'), comp,
# role, test_info['annual_ret'], test_info['vol'], test_info['mdd'], test_info['annual_sr'],
# test_info['beta'], test_info['alpha'], test_info['var10'], test_info['R2'], True, comment, stock_list, list(sol), sep=", ")
# print("-"*10)
data = (ts, name, session.get('username').split('@')[0], comp, role, ratio, test_info['annual_ret'],
test_info['vol'], test_info['mdd'], test_info['annual_sr'],
test_info['beta'], test_info['alpha'], test_info['var10'], test_info['R2'], True, comment, stock_list, list(sol))
sql='insert into strategy \ sql='insert into strategy \
(date, name, username, competition, role, ratio, annual_ret, vol, mdd, annual_sr, beta, alpha, var10, R2, tw, comment, assets, assets_position)\ (date, name, username, competition, role, annual_ret, vol, mdd, annual_sr, beta, alpha, var10, R2, tw, notes, assets, weight, ret)\
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) RETURNING id;' values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) RETURNING id;'
with conn: with conn:
with conn.cursor() as curs: with conn.cursor() as curs:
@ -339,7 +354,7 @@ def buildPort():
strategy_id = curs.fetchone()[0] strategy_id = curs.fetchone()[0]
conn.close() conn.close()
print("\n------Write in Success--------\n") print("\n------Write in Success--------\n")
return f'''<span>投資組合已完成建立,請 <a class="badge rounded-pill text-bg-warning" href="/result_view?strategy_id={strategy_id}">{strategy_id}</a>查詢分析結果。</span>''' return f'''<span>投資組合已完成建立,請點擊 <a class="badge rounded-pill text-bg-warning" href="/result_view?strategy_id={strategy_id}">{strategy_id}</a>查詢分析結果。</span>'''
@ -361,8 +376,8 @@ def result():
flash('使用投組功能請先登入。', 'warning') flash('使用投組功能請先登入。', 'warning')
return redirect(url_for('login')) return redirect(url_for('login'))
sql="""select id, date, name, username, annual_ret, vol, annual_sr\ sql="""select id, date, name, username, annual_ret, vol, annual_sr, mdd\
from strategy order by id desc limit 100;""" from strategy order by id desc limit 50;"""
conn = psycopg2.connect(**SQL_CONFIG) conn = psycopg2.connect(**SQL_CONFIG)
with conn: with conn:
with conn.cursor() as curs: with conn.cursor() as curs:
@ -380,9 +395,38 @@ def result_view():
return redirect(url_for('login')) return redirect(url_for('login'))
if not 'strategy_id' in request.args: if not 'strategy_id' in request.args:
return redirect(url_for('index')) return redirect(url_for('index'))
else:
sid = request.args.get('strategy_id')
strategy_id = request.args.get('strategy_id') strategy_id = request.args.get('strategy_id')
print(strategy_id) sql="""select * from strategy where id=%s;"""
return render_template('result_view.html') conn = psycopg2.connect(**SQL_CONFIG)
with conn:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as curs:
curs.execute(sql, (sid, ))
data= curs.fetchone()
conn.close()
# Processing data
data = dict(data)
data['role'] = role_map[data['role']]
w = pd.DataFrame(data['weight'])
r = pd.DataFrame(data['ret'])
# Plotting weight
fig = px.bar(w)
fig['layout'] = {}
data['weight'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
# Plotting weight
fig = (r+1).cumprod().plot()
fig['layout'] = {}
data['ret'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
# Plotting ret bars
r.index.name = 'date'
r.index = pd.to_datetime(r.index)
ret_hist = r.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'] = {}
data['bar'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
return render_template('result_view.html', data=data)
# handle login failed # handle login failed
# @app.errorhandler(401) # @app.errorhandler(401)

@ -1,12 +1,11 @@
DROP TABLE IF EXISTS strategy; DROP TABLE IF EXISTS strategy;
CREATE TABLE strategy ( CREATE TABLE strategy (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
date DATE NOT NULL, date VARCHAR(24) NOT NULL,
name VARCHAR(64) NOT NULL, name VARCHAR(32) NOT NULL,
username VARCHAR(64) NOT NULL, username VARCHAR(32) NOT NULL,
competition VARCHAR(64) NOT NULL, competition VARCHAR(32) NOT NULL,
role VARCHAR(20) NOT NULL, role VARCHAR(20) NOT NULL,
ratio REAL NOT NULL,
annual_ret REAL NOT NULL, annual_ret REAL NOT NULL,
vol REAL NOT NULL, vol REAL NOT NULL,
mdd REAL NOT NULL, mdd REAL NOT NULL,
@ -16,10 +15,11 @@ CREATE TABLE strategy (
var10 REAL NOT NULL, var10 REAL NOT NULL,
R2 REAL NOT NULL, R2 REAL NOT NULL,
tw BOOLEAN DEFAULT TRUE, tw BOOLEAN DEFAULT TRUE,
comment VARCHAR(255), notes VARCHAR(255),
assets TEXT[] NOT NULL, assets TEXT[] NOT NULL,
assets_position REAL[] NOT NULL, weight JSON NOT NULL,
notes TEXT[][] ret JSON NOT NULL,
comments TEXT[][]
); );
CREATE INDEX idx_user ON strategy (username); CREATE INDEX idx_user ON strategy (username);

@ -2,17 +2,11 @@
let stockList = ['2330.TW']; let stockList = ['2330.TW'];
let currentList = []; let currentList = [];
const layout={'autosize': true, 'markers':true, const layout={'autosize': true, 'markers':true,
'title': {'text': 'Assets'}, 'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'rangeslider': {'visible': true}},
'rangeslider': {'visible': true},
'rangeselector':{'rangeselector':
{'buttons': [
{'count': 1, 'label': '1m', 'step': 'month', 'stepmode': 'backward'},
{'count': 6, 'label': '6m', 'step': 'month', 'stepmode': 'backward'},
{'count': 1, 'label': 'YTD', 'step': 'year', 'stepmode': 'todate'},
{'count': 1, 'label': '1y', 'step': 'year', 'stepmode': 'backward'}, {'step': 'all'}]}, 'rangeslider': {'visible': true},'type': 'date'}},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'fixedrange': false}, 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'fixedrange': false},
'legend': {'yanchor': 'top', 'y': 1.8, 'xanchor': 'left', 'x': 0.01}, 'margin': {'l': 25, 'r': 5, 't': 10, 'b': 5}, 'legend': {'yanchor': 'top', 'y': 1.1, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'},
'margin': {'l': 25, 'r': 5, 't': 10, 'b': 5},
} }
// Cache frequently-used DOM elements // Cache frequently-used DOM elements
const $stockForm = $('#stock-form'); const $stockForm = $('#stock-form');

@ -23,11 +23,10 @@
{% endblock %} {% endblock %}
<style> <style>
body { body {
// padding-top: 60px; // padding-bottom: 10px;
padding-bottom: 10px;
font-family: Georgia, Arial, Geneva, Helvetica, serif !important; font-family: Georgia, Arial, Geneva, Helvetica, serif !important;
background-color: #eee; background-color: #eee;
height:100vh; height:95vh;
} }
.navbar { .navbar {
background-image: linear-gradient(to bottom right, #5d9faa , #c4e0e5); background-image: linear-gradient(to bottom right, #5d9faa , #c4e0e5);
@ -62,7 +61,7 @@
('/', 'index', '首頁', 'bi bi-house-fill'), ('/', 'index', '首頁', 'bi bi-house-fill'),
('/strategy', 'strategy', '建立策略', 'fa-solid fa-chart-pie'), ('/strategy', 'strategy', '建立策略', 'fa-solid fa-chart-pie'),
('/strategy_tw', 'strategy_tw', '台股建立策略', 'fa-solid fa-chart-pie'), ('/strategy_tw', 'strategy_tw', '台股建立策略', 'fa-solid fa-chart-pie'),
('/strategy_bl', 'strategy_bl', 'Black-Litterman配置', 'fa-solid fa-chess-knight'), ('/', 'strategy_bl', 'Black-Litterman配置', 'fa-solid fa-chess-knight'),
('/custom', 'custom', '自訂數據建立策略', 'bi bi-database-fill-add'), ('/custom', 'custom', '自訂數據建立策略', 'bi bi-database-fill-add'),
('/result', 'result', '分析結果排行', 'fa-solid fa-chart-simple'), ('/result', 'result', '分析結果排行', 'fa-solid fa-chart-simple'),
('mailto:r10246002@ntu.edu.tw', 'error', '錯誤回報', 'bi bi-bug-fill') ('mailto:r10246002@ntu.edu.tw', 'error', '錯誤回報', 'bi bi-bug-fill')

@ -14,7 +14,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid" style="background-color: #eee;min-height:100%;position:relative;"> <div class="container-fluid" style="background-color: #eee;min-height:92%;position:relative;">
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<div class="row d-flex justify-content-center align-items-center h-100"> <div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-lg-12 col-xl-11"> <div class="col-lg-12 col-xl-11">

@ -7,7 +7,7 @@
{% block title %}Result Page{% endblock%} {% block title %}Result Page{% endblock%}
{% block content %} {% block content %}
<div class="container-fluid" style="background-color: #eee;"> <div class="container-fluid" style="background-color: #eee;;min-height:92%;position:relative;">
<div class="container-fluid px-1 py-4"> <div class="container-fluid px-1 py-4">
<div class="alert alert-secondary p-3 mx-3" role="alert"> <div class="alert alert-secondary p-3 mx-3" role="alert">
<div class="flex-row"> <div class="flex-row">
@ -67,6 +67,7 @@
<th scope="col">報酬率</th> <th scope="col">報酬率</th>
<th scope="col">夏普率</th> <th scope="col">夏普率</th>
<th scope="col">波動率</th> <th scope="col">波動率</th>
<th scope="col">最大回落</th>
<th scope="col">創建時間</th> <th scope="col">創建時間</th>
</tr> </tr>
</thead> </thead>
@ -83,6 +84,7 @@
<td>{{ info[4] }}</td> <td>{{ info[4] }}</td>
<td>{{ info[6] }}</td> <td>{{ info[6] }}</td>
<td>{{ info[5] }}</td> <td>{{ info[5] }}</td>
<td>{{ info[7] }}</td>
<td>{{ info[1] }}</td> <td>{{ info[1] }}</td>
</tr> </tr>
</thead> </thead>

@ -13,20 +13,160 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid" style="background-color: #eee;min-height:100%;position:relative;"> <div class="container-fluid" style="background-color: #eee;min-height:92%;position:relative;">
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<div class="row d-flex justify-content-center align-items-center h-100"> <div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-lg-12 col-xl-11"> <div class="col-lg-12 col-xl-11">
<div class="card text-black mt-3" style="border-radius: 25px;">
<div class="card-body p-md-5" style="border-radius: 25px;"> <div class="alert alert-light text-dark" role="alert">
<div class="row justify-content-center"> <div class="container">
yeah <div class="row">
<div class="col-3">
<strong>策略編號</strong>
</div>
<div class="col-6">
{{ data.id|safe }}
</div>
</div>
<hr>
<div class="row">
<div class="col-3">
<strong>策略名稱</strong>
</div>
<div class="col-6">
{{ data.name|safe }}
</div>
</div>
<hr>
<div class="row">
<div class="col-3">
<strong>建立者</strong>
</div>
<div class="col-6">
{{ data.username|safe }}
</div>
</div>
<hr>
<div class="row">
<div class="col-3">
<strong>建立時間</strong>
</div>
<div class="col-6">
{{ data.date|safe }}
</div>
</div>
<hr>
<div class="row">
<div class="col-3">
<strong>策略目標</strong>
</div>
<div class="col-6">
{{ data.role|safe }}
</div>
</div>
<div class="row">
<button class="btn btn-info mt-3" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="false" aria-controls="collapseExample">
詳細資訊
</button>
<div class="collapse" id="collapse1">
<div card="card card-body p-3 m-3">
<div class="p-3 table-responsive-sm table-responsive-md table-responsive-xl">
<table class="table caption-top">
<thead>
<tr>
<th scope="col">年化報酬率</th>
<th scope="col">年化夏普率</th>
<th scope="col">年化波動率</th>
<th scope="col">最大回落</th>
<th scope="col">Alpha</th>
<th scope="col">Beta</th>
<th scope="col">VaR10</th>
<th scope="col">R^2</th>
</tr>
</thead>
<thead style="font-size: 1vmin'">
<tr>
<td>{{ data.annual_ret }}</td>
<td>{{ data.annual_sr }}</td>
<td>{{ data.vol }}</td>
<td>{{ data.mdd }}</td>
<td>{{ data.alpha }}</td>
<td>{{ data.beta }}</td>
<td>{{ data.var10 }}</td>
<td>{{ data.r2 }}</td>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card mt-3 py-2" style="border-radius: 10px;">
<div class="card-body p-0" style="border-radius: 10px;">
<div class="row justify-content-center font-bold text-xl">
資產權重變化
</div>
<div class="" id="weight" style="max-height:30vh"></div>
<div class="row justify-content-center font-bold text-xl">
投組價值走勢
</div> </div>
<div class="" id="price" style="max-height:30vh"></div>
<div class="row justify-content-center font-bold text-xl">
投組季報酬率
</div>
<div class="" id="bar" style="max-height:30vh"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block script %}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script type="text/javascript">
const wlayout = {
'autosize': true,
'barmode': 'relative',
'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':''},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''},
'margin': {'l': 50, 'r': 50, 't': 50, 'b': 50},
'legend': {'yanchor': 'top', 'y': 1.2, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'}
};
const rlayout = {
'autosize': true,
'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':''},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''},
'margin': {'l': 50, 'r': 50, 't': 50, 'b': 50},
'legend': {'yanchor': 'top', 'y': 1.2, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'}
};
const blayout = {
'autosize': true,
'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':''},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''},
'margin': {'l': 50, 'r': 50, 't': 50, 'b': 50},
'legend': {'yanchor': 'top', 'y': 1.2, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'}
};
var w = {{ data.weight|safe }};
var r = {{ data.ret|safe }};
var b = {{ data.bar|safe }};
Plotly.newPlot("weight", w.data, wlayout, {responsive: true});
Plotly.newPlot("price", r.data, rlayout, {responsive: true});
Plotly.newPlot("bar", b.data, blayout, {responsive: true});
</script>
{% endblock script %}

@ -123,7 +123,7 @@ div.card{
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="graph" style="max-height:60vh"> <div id="graph" style="max-height:50vh">
<span> <span>
按下 按下
<button type="button" class="btn btn-outline-primary btn-sm" disabled>確認資產</button> <button type="button" class="btn btn-outline-primary btn-sm" disabled>確認資產</button>
@ -148,10 +148,10 @@ div.card{
<div class="input-group"> <div class="input-group">
<span class="input-group-text bg-info">輸入數據時長</span> <span class="input-group-text bg-info">輸入數據時長</span>
<select id="lookback" class="form-select"> <select id="lookback" class="form-select">
<option value="21"></option> <option value="21">1個</option>
<option value="63">每季</option> <option value="63">3個月</option>
<option selected value="126">每半年</option> <option selected value="126">6個月</option>
<option value="252">每年</option> <option value="252">12個月</option>
</select> </select>
</div> </div>
<div class="input-group"> <div class="input-group">
@ -169,9 +169,7 @@ div.card{
<option selected value="max_sharpe">最大化夏普比率</option> <option selected value="max_sharpe">最大化夏普比率</option>
<option value="max_sortino">最大化索提諾比率</option> <option value="max_sortino">最大化索提諾比率</option>
<option value="min_volatility">最小化波動率</option> <option value="min_volatility">最小化波動率</option>
<option value="quadratic_utility"> <option value="quadratic_utility">最大化效用函數</option>
最大化效用函數
</option>
</select> </select>
</div> </div>
<div class="input-group" style="display: none;" id="gamma"> <div class="input-group" style="display: none;" id="gamma">

Loading…
Cancel
Save