SeanChenTaipei 2 years ago
commit f56b681026
  1. 103
      main.py
  2. 3
      portfolio_builder.py
  3. 2
      sql_script/create_strategy.sql
  4. 8
      static/js/addStock.js
  5. 14
      templates/404.html
  6. 4
      templates/base.html
  7. 43
      templates/result.html
  8. 15
      templates/result_view.html
  9. 163
      templates/strategy_tw.html

@ -21,8 +21,8 @@ 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)
} }
@ -33,13 +33,13 @@ SQL_CONFIG = dict(
port=os.getenv("PGPORT"), port=os.getenv("PGPORT"),
password=os.getenv("PGPASSWORD") password=os.getenv("PGPASSWORD")
) )
SQL_CONFIG = dict( # SQL_CONFIG = dict(
database="railway", # database="railway",
user="postgres", # user="postgres",
host="containers-us-west-103.railway.app", # host="containers-us-west-103.railway.app",
port="5913", # port="5913",
password="gv5Mh7cPjCm9YTjAmsYD" # password="gv5Mh7cPjCm9YTjAmsYD"
) # )
# SQL_CONFIG = {@ # SQL_CONFIG = {@
# 'database': "tpm", # 'database': "tpm",
# 'user': "hsienchen", # 'user': "hsienchen",
@ -251,11 +251,13 @@ def submit_stock_list():
## Query DB ## Query DB
conn = psycopg2.connect(**SQL_CONFIG) conn = psycopg2.connect(**SQL_CONFIG)
port = get_stock(conn, stock_list, session['tw']) port = get_stock(conn, stock_list, session['tw'])
if len(port.index) > 750: if len(port.index) > 908:
port = port.iloc[-750:, :] port = port.iloc[-908:, :]
conn.close() conn.close()
port = port.iloc[::3, :]
port = port/port.iloc[0, :]
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'] = {}
# 序列化 # 序列化
@ -277,7 +279,7 @@ def buildPort():
if not 'tw' in session: if not 'tw' in session:
return redirect(url_for('index')) return redirect(url_for('index'))
# Stop frequently building strategy # Stop frequently building strategy
if time.time() - session['lastCreateTime'] < 10: if time.time() - session['lastCreateTime'] < 60:
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'])
@ -295,8 +297,8 @@ def buildPort():
# Opt Parameters # Opt Parameters
comp = request.form.get('comp') comp = request.form.get('comp')
# ts = int(request.form.get('ts')) ts = request.form.get('ts')
ts = datetime.now().strftime("%Y-%m-%d, %H:%M:%S") # 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'))
@ -305,8 +307,6 @@ def buildPort():
stock_list = json.loads(request.form.get('stockList')) stock_list = json.loads(request.form.get('stockList'))
ratio=0.7
# Algorithm MVO # Algorithm MVO
print("-"*10) print("-"*10)
print("Enter Algorithms") print("Enter Algorithms")
@ -325,9 +325,9 @@ def buildPort():
n = len(port.index) n = len(port.index)
if n < lookback+backtest+63: if n < lookback+backtest+63:
return f'''<span>投資組合無法建立,資料長度與所選參數不符。</span>''' return f'''<span>投資組合無法建立,資料長度與所選參數不符。</span>'''
elif n > 757+lookback: elif n > 909+lookback:
port = port.iloc[-(757+lookback):, :] port = port.iloc[-(909+lookback):, :]
market = market.iloc[-757:] market = market.iloc[-909:]
else: else:
market = market.iloc[lookback:] market = market.iloc[lookback:]
@ -368,23 +368,56 @@ def custom():
return render_template('custom.html', message='No') return render_template('custom.html', message='No')
@app.route('/result') @app.route('/result', methods=['GET', 'POST'])
def result(): def result():
if login_required(): if login_required():
pass pass
else: else:
flash('使用投組功能請先登入。', 'warning') flash('使用投組功能請先登入。', 'warning')
return redirect(url_for('login')) return redirect(url_for('login'))
if request.method=='GET':
sql="""select id, date, name, username, annual_ret, vol, annual_sr, mdd\ conn = psycopg2.connect(**SQL_CONFIG)
from strategy order by id desc limit 50;""" with conn:
conn = psycopg2.connect(**SQL_CONFIG) with conn.cursor() as curs:
with conn: sql="select id, date, name, username, annual_ret, vol, annual_sr, mdd\
with conn.cursor() as curs: from strategy order by id desc limit 50"
curs.execute(sql) curs.execute(sql)
data= curs.fetchall() data= curs.fetchall()
conn.close() conn.close()
return render_template('result.html', strategy_data=data) return render_template('result.html', strategy_data=data)
elif request.method=='POST':
role = request.form.get('role')
comp = request.form.get('competition')
if role in ['id', 'annual_ret', 'annual_sr', 'vol']:
pass
else:
role='id'
if comp == 'none':
comp=None
print("result", type(role), type(comp))
conn = psycopg2.connect(**SQL_CONFIG)
with conn:
with conn.cursor() as curs:
if comp is None:
if role is None:
sql="select id, date, name, username, annual_ret, vol, annual_sr, mdd\
from strategy order by id desc limit 50"
curs.execute(sql)
elif role=='vol':
sql=f"select id, date, name, username, annual_ret, vol, annual_sr, mdd\
from strategy order by {role} asc limit 50"
curs.execute(sql)
else:
sql=f"select id, date, name, username, annual_ret, vol, annual_sr, mdd\
from strategy order by {role} desc limit 50"
curs.execute(sql)
else:
sql=f"select id, date, name, username, annual_ret, vol, annual_sr, mdd\
from strategy where competition=%s order by {role} desc limit 50;"
curs.execute(sql, (comp, ))
data= curs.fetchall()
conn.close()
return render_template('result.html', strategy_data=data)
@app.route('/result_view') @app.route('/result_view')
def result_view(): def result_view():
@ -428,10 +461,10 @@ def result_view():
data['bar'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) data['bar'] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
return render_template('result_view.html', data=data) return render_template('result_view.html', data=data)
# handle login failed @app.errorhandler(404)
# @app.errorhandler(401) def page_not_found(e):
# def page_not_found(e): # note that we set the 404 status explicitly
# return response('<p>Failed</p>') return render_template('404.html'), 404

@ -49,7 +49,7 @@ class MVO(object):
@staticmethod @staticmethod
def sharpe_ratio(w, ret): def sharpe_ratio(w, ret):
cov = np.cov(ret.T) cov = np.cov(ret.T)
print(cov.shape, w.shape) # print(cov.shape, w.shape)
retPort = ret@w # T-dimensional array retPort = ret@w # T-dimensional array
stdPort = np.std(retPort) stdPort = np.std(retPort)
return np.mean(retPort)/stdPort return np.mean(retPort)/stdPort
@ -138,7 +138,6 @@ class MVO(object):
result = minimize(loss, init, method="SLSQP",\ result = minimize(loss, init, method="SLSQP",\
options=opts, bounds=bnds, tol = None, jac = grad, constraints=cons) options=opts, bounds=bnds, tol = None, jac = grad, constraints=cons)
sol = result['x'] sol = result['x']
print(sol)
return np.round(sol, 2) return np.round(sol, 2)

@ -1,7 +1,7 @@
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 VARCHAR(24) NOT NULL, date VARCHAR(64) NOT NULL,
name VARCHAR(32) NOT NULL, name VARCHAR(32) NOT NULL,
username VARCHAR(32) NOT NULL, username VARCHAR(32) NOT NULL,
competition VARCHAR(32) NOT NULL, competition VARCHAR(32) NOT NULL,

@ -4,7 +4,7 @@ let currentList = [];
const layout={'autosize': true, 'markers':true, const layout={'autosize': true, 'markers':true,
'title': {'text': ''}, 'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'rangeslider': {'visible': true}}, 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'rangeslider': {'visible': true}},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'fixedrange': false}, 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0]},
'legend': {'yanchor': 'top', 'y': 1.1, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'}, 'legend': {'yanchor': 'top', 'y': 1.1, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'},
'margin': {'l': 25, 'r': 5, 't': 10, 'b': 5}, 'margin': {'l': 25, 'r': 5, 't': 10, 'b': 5},
} }
@ -100,7 +100,7 @@ $sendPort.click(function(event) {
method: 'POST', method: 'POST',
data: { data: {
name: $('input[name=portName]').val(), name: $('input[name=portName]').val(),
ts: Date.now(), ts: Date(Date.now()),
comp: $('#competition').val(), comp: $('#competition').val(),
lookback: $('#lookback').val(), lookback: $('#lookback').val(),
frequency: $('#opt-frequency').val(), frequency: $('#opt-frequency').val(),
@ -110,7 +110,7 @@ $sendPort.click(function(event) {
stockList: JSON.stringify(stockList) stockList: JSON.stringify(stockList)
}, },
success: function(response) { success: function(response) {
console.log(response); // console.log(response);
// var res = JSON.parse(response); // var res = JSON.parse(response);
event.preventDefault(); event.preventDefault();
// $('#modalTitle').text('完成建立') // $('#modalTitle').text('完成建立')
@ -151,7 +151,7 @@ $submitBtn.click(function(event) {
success: function(response) { success: function(response) {
$('#graph').html('') $('#graph').html('')
var graphs = JSON.parse(response); var graphs = JSON.parse(response);
console.log(graphs.data); // console.log(graphs.data);
Plotly.newPlot("graph", Plotly.newPlot("graph",
graphs.data, layout, {responsive: true}); graphs.data, layout, {responsive: true});
// console.log(response.layout); // console.log(response.layout);

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body style="background-image: linear-gradient(to bottom right, #5d9faa , #c4e0e5);">
<div class="container-fluid justify-content-center">
<h1 class="font-bold text-4xl">404 Error</h1>
</div>
</body>
</html>

@ -23,8 +23,8 @@
{% endblock %} {% endblock %}
<style> <style>
body { body {
// padding-bottom: 10px; /* // padding-bottom: 10px; */
font-family: Georgia, Arial, Geneva, Helvetica, serif !important; font-family: Georgia, Arial, Helvetica, serif !important;
background-color: #eee; background-color: #eee;
height:95vh; height:95vh;
} }

@ -20,39 +20,36 @@
</div> </div>
<hr class="my-3 px-5"> <hr class="my-3 px-5">
<form method="POST">
<div class='d-flex justify-content-end'> <div class="input-group mb-1">
<div class="input-group mb-3">
<span class="input-group-text" id="in">選擇競賽</span> <span class="input-group-text" id="in">選擇競賽</span>
<select id="competition" class="form-select" size="1" aria-label="size 5 select example"> <select id="competition" class="form-select" size="1" name="competition">
{% include 'competitions.html' %} {% include 'competitions.html' %}
</select> </select>
</div> </div>
<div> <div class="input-group mb-3">
<button id="changeComp" type="button" class="btn btn-secondary"><i class="bi bi-arrow-right-square-fill"></i></button> <span class="input-group-text">排序方式</span>
<select id="rrr" class="form-select" size="1" name="role">
<option value="id">時間排序</option>
<option value="annual_ret">報酬率排序</option>
<option value="annual_sr">夏普率排序</option>
<option value="vol">波動率排序</option>
</select>
</div> </div>
</div> <div class="d-flex flex-column-reverse">
<div class="ms-auto">
<button id="changeComp" type="submit" class="btn btn-secondary"><i class="bi bi-arrow-right-square-fill"></i></button>
</div>
</div>
</form>
</div> </div>
<div class="card m-3"> <div class="card m-3">
<div card="card p-3 m-3"> <div card="card p-3 m-3">
<div class="card-header"> <div class="card-header">
<div class="d-flex"> <div class="d-flex">
<div class="py-2 font-bold text-xl"> Hello
<span class='m-2'>策略顯示</span>
</div>
<div class="dropdown ms-auto py-2">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
排行方式
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">我的策略</a></li>
<li><a class="dropdown-item" href="#">時間排行</a></li>
<li><a class="dropdown-item" href="#">報酬排行</a></li>
<li><a class="dropdown-item" href="#">SR排行</a></li>
<li><a class="dropdown-item" href="#">波動率排行</a></li>
</ul>
</div>
</div> </div>

@ -71,12 +71,12 @@
</div> </div>
<div class="col-6"> <div class="col-6">
{% for a in data.assets %} {% for a in data.assets %}
<span class="badge text-bg-secondary">{{ a|safe }}</span> <span class="badge text-bg-warning">{{ a|safe }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="row"> <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 class="btn btn-secondary mt-3" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="false" aria-controls="collapseExample">
詳細資訊 詳細資訊
</button> </button>
<div class="collapse" id="collapse1"> <div class="collapse" id="collapse1">
@ -126,11 +126,11 @@
<div class="row justify-content-center font-bold text-xl"> <div class="row justify-content-center font-bold text-xl">
投組價值走勢 投組價值走勢
</div> </div>
<div class="mb-4" id="price" style="max-height:40vh"></div> <div class="mb-4" id="price" style="max-height:60vh"></div>
<div class="row justify-content-center font-bold text-xl"> <div class="row justify-content-center font-bold text-xl">
投組季報酬率 投組季報酬率
</div> </div>
<div class="mb-4" id="bar" style="max-height:40vh"></div> <div class="mb-4" id="bar" style="max-height:60vh"></div>
</div> </div>
</div> </div>
@ -161,9 +161,10 @@
const rlayout = { const rlayout = {
'autosize': true, 'autosize': true,
'title': {'text': ''}, 'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':''}, 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':'', 'rangeslider': {'visible': true}
},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''}, 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''},
'margin': {'l': 20, 'r': 10, 't': 50, 'b': 50}, 'margin': {'l': 40, 'r': 20, 't': 30, 'b': 50},
'legend': {'yanchor': 'top', 'y': 1.3, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'} 'legend': {'yanchor': 'top', 'y': 1.3, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'}
}; };
const blayout = { const blayout = {
@ -171,7 +172,7 @@
'title': {'text': ''}, 'title': {'text': ''},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':''}, 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title':''},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''}, 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title':''},
'margin': {'l': 40, 'r': 10, 't': 50, 'b': 70}, 'margin': {'l': 40, 'r': 20, 't': 50, 'b': 70},
'legend': {'yanchor': 'top', 'y': 1.3, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'} 'legend': {'yanchor': 'top', 'y': 1.3, 'xanchor': 'left', 'x': 0.01, 'orientation':'h'}
}; };
var w = {{ data.weight|safe }}; var w = {{ data.weight|safe }};

@ -38,48 +38,86 @@ div.card{
{% endblock style %} {% endblock style %}
{% block content %} {% block content %}
<div class="container-fliud"> <div class="container-fluid" style="background-color: #ffffff;">
<div class="card p-3"> <div class="container-fluid py-2">
<div class="card mb-3" style="border-radius:10px;border:0.5rem outset #eee;"> <div class="alert alert-dark m-0" role="alert">
<!-- <img src="{{ url_for("static", filename="img/stock.jpeg") }}" class="card-img-top" alt="..."> --> <ul class="fa-ul">
<div class="card-body"> <li><span class="fa-li"><i class="fa-solid fa-scroll"></i></span>兩次建立投資組合時間需大於60秒。</li>
<li><span class="fa-li"><i class="fa-solid fa-scroll"></i></span>資產數量大於1檔才會開始建立投資組合。</li>
<h5 class="card-title text-xl font-bold accordion-header"> <li><span class="fa-li"><i class="fa-solid fa-scroll"></i></span>未輸入投資組合名稱則會由系統隨機生成。</li>
{% if session.tw == 1 %}台股{% endif %}投資組合建立指南 <i class="bi bi-caret-left-fill" style="animation: cursor 1s 3;"></i></h5> </ul>
<ol class="list-group list-group-flush list-group-numbered" id="collapseExample">
<li class="list-group-item"><span class="ps-2">輸入投資組合名稱</span></li>
<li class="list-group-item"><span class="ps-2">選擇所參加的課程或競賽</span></li>
<li class="list-group-item"><span class="ps-2">選擇資產後按下<span class="badge bg-secondary">加入</span></span></li>
<li class="list-group-item"><span class="ps-2">確認資產後按下<span class="badge bg-info">確認資產</span>後, 查看資產價格動態圖表</span></li>
<li class="list-group-item"><span class="ps-2">選擇建立策略相關參數</span></li>
<li class="list-group-item"><span class="ps-2">確認後按下<span class="badge bg-danger">確認建立</span>後, 查看回傳訊息</span></li>
</ol>
</div>
</div> </div>
<div class="card" id="buildName"> <div class="row">
<div class="card-header font-bold text-lg"> <div class="col-lg-6 col-md-6 col-sm-12">
投資組合名稱 <div class="card my-2 font-bold" style="border-radius: 7px;">
</div> <div class="card-header">
<div class="card-body"> <h5 class="card-title text-xl font-bold pt-2">
<input if="imput1" name="portName" type="text" class="form-control" placeholder="EX. 韓總 No.1" required> {% if session.tw == 1 %}台股{% endif %}投資組合策略建立指南</h5>
</div> </div>
<div class="card-header font-bold text-lg"> <div class="card-body mb-0">
請選擇所參加的課程/競賽 <ol class="list-group list-group-flush list-group-numbered">
<li class="list-group-item">
<span class="ps-2">輸入投資組合名稱。</span>
<input if="imput1" name="portName" type="text" class="form-control" placeholder="EX. 韓總 No.1" required>
</li>
<li class="list-group-item">
<span class="ps-2">選擇所參加的課程或競賽。</span>
<select id="competition" class="form-select" size="1" name="competition">
{% include 'competitions.html' %}
</select>
</li>
<li class="list-group-item"><span class="ps-2">選擇資產後按下 <span class="badge bg-secondary">加入</span></li>
<li class="list-group-item"><span class="ps-2">按下 <button type="button" class="btn btn-outline-primary btn-sm" disabled>確認資產</button> 後查看資產價格動態圖表。</span></li>
<li class="list-group-item"><span class="ps-2">刪除不加入投資組合的資產。</span></li>
<li class="list-group-item"><span class="ps-2">選擇建立策略相關參數。</span></li>
<li class="list-group-item"><span class="ps-2">按下 <button type="button" class="btn btn-outline-danger btn-sm" disabled>確認建立</button> 並查看回傳訊息。</span></li>
</ol>
</div>
</div>
</div> </div>
<div class="card-body"> <div class="col-lg-6 col-md-6 col-sm-12">
<select id="competition" class="form-select" size="1"> <div class="card my-2" style="border-radius: 7px;">
{% include 'competitions.html' %} <div class="card-header d-flex">
</select> <div class="py-2 font-bold text-lg">
已選擇的資產
</div>
</div>
<div class="card-body d-flex mb-0">
<input name="assetSelect" class="form-control" list="datalistOptions" id="stockAll" placeholder="輸入資產名稱...">
<datalist id="datalistOptions">
{% if session.tw==0 %}
{% for key, data in data_us.items() -%}
<option value="{{ key|e }}">{{ key|e }} | {{ data|e }}</option>
{% endfor %}
{% endif %}
{% for key, data in data_tw.items() -%}
<option value="{{ key|e }}">{{ key|e }} | {{ data|e }} </option>
{% endfor %}
</datalist>
<button class="btn btn-secondary btn-sm"
type="button"
id="addStockBtn">
加入
</button>
</div>
<div>
<ol class="list-group list-group-numbered px-3 pb-3" id="stock-list" type="1">
<li class="list-group-item">
<span class="px-2">2330.TW</span>
<a class="btn btn-sm btn-danger float-right delete-btn">
<i class="fas fa-trash-alt"></i>
</a>
</li>
</ol>
</div>
</div>
</div> </div>
</div> </div>
<hr class="my-3"> <div class="card" style="border-radius: 7px;">
<div class="card">
<div class="card-header d-flex"> <div class="card-header d-flex">
<div class="py-2 font-bold text-lg"> <div class="py-2 font-bold text-lg">
已選擇的資產 價格動態圖表
</div> </div>
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
<button type="button" class="btn btn-outline-primary btn-sm" id="submit-btn"> <button type="button" class="btn btn-outline-primary btn-sm" id="submit-btn">
@ -87,59 +125,22 @@ div.card{
</button> </button>
</div> </div>
</div>
<div class="card-body d-flex mb-0">
<input name="assetSelect" class="form-control" list="datalistOptions" id="stockAll" placeholder="輸入資產名稱...">
<datalist id="datalistOptions">
{% if session.tw==0 %}
{% for key, data in data_us.items() -%}
<option value="{{ key|e }}">{{ key|e }} | {{ data|e }}</option>
{% endfor %}
{% endif %}
{% for key, data in data_tw.items() -%}
<option value="{{ key|e }}">{{ key|e }} | {{ data|e }} </option>
{% endfor %}
</datalist>
<button class="btn btn-secondary btn-sm"
type="button"
id="addStockBtn">
加入
</button>
</div>
<div>
<ol class="list-group list-group-numbered px-3 pb-3" id="stock-list" type="1">
<li class="list-group-item">
<span class="px-2">2330.TW</span>
<a class="btn btn-sm btn-danger float-right delete-btn">
<i class="fas fa-trash-alt"></i>
</a>
</li>
</ol>
</div>
<div class="card-header d-flex">
<div class="py-2 font-bold text-lg">
價格動態圖表
</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="graph" style="max-height:50vh"> <div id="graph" style="max-height:50vh">
<span> <span>
按下 圖表將在此渲染。
<button type="button" class="btn btn-outline-primary btn-sm" disabled>確認資產</button>
後,圖表將在此渲染。
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<hr>
<div class="card mt-3"> <div class="card mt-3">
<div class="d-flex p-2"> <div class="d-flex p-2">
<div class="p-2 font-bold text-lg"> <div class="p-2 font-bold text-lg">
投組最佳化配置 投組最佳化配置
</div> </div>
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
<button type="button" class="btn btn-outline-danger btn-sm" id="submit-port"> <button type="button p-0" class="btn btn-outline-danger btn-sm" id="submit-port">
確認建立 確認建立
</button> </button>
</div> </div>
@ -184,13 +185,14 @@ div.card{
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title font-bold text-xl">確認建立投資組合</h5> <h5 class="modal-title font-bold text-xl py-2">確認建立投資組合</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<span> <ol class="list-group list-group-flush list-group-numbered">
確認後將會暫時關閉建立按鈕,請等待完成訊息! <li class="list-group-item">確認後將會暫時關閉建立功能,請等待完成訊息,勿頻繁提交建立請求。</li>
</span> <li class="list-group-item">兩次投資組合建立時間需大於60秒。</li>
</ol>
<div class="input-group mt-3"> <div class="input-group mt-3">
<span class="input-group-text">輸入筆記</span> <span class="input-group-text">輸入筆記</span>
<textarea id="commentPort" class="form-control" aria-label="With textarea"></textarea> <textarea id="commentPort" class="form-control" aria-label="With textarea"></textarea>
@ -203,9 +205,6 @@ div.card{
</div> </div>
</div> </div>
</div> </div>
<div class="modal" id="confirmModal" tabindex="-1"> <div class="modal" id="confirmModal" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

Loading…
Cancel
Save