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

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

@ -1,7 +1,7 @@
DROP TABLE IF EXISTS strategy;
CREATE TABLE strategy (
id SERIAL PRIMARY KEY,
date VARCHAR(24) NOT NULL,
date VARCHAR(64) NOT NULL,
name VARCHAR(32) NOT NULL,
username VARCHAR(32) NOT NULL,
competition VARCHAR(32) NOT NULL,

@ -4,7 +4,7 @@ let currentList = [];
const layout={'autosize': true, 'markers':true,
'title': {'text': ''},
'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'},
'margin': {'l': 25, 'r': 5, 't': 10, 'b': 5},
}
@ -100,7 +100,7 @@ $sendPort.click(function(event) {
method: 'POST',
data: {
name: $('input[name=portName]').val(),
ts: Date.now(),
ts: Date(Date.now()),
comp: $('#competition').val(),
lookback: $('#lookback').val(),
frequency: $('#opt-frequency').val(),
@ -110,7 +110,7 @@ $sendPort.click(function(event) {
stockList: JSON.stringify(stockList)
},
success: function(response) {
console.log(response);
// console.log(response);
// var res = JSON.parse(response);
event.preventDefault();
// $('#modalTitle').text('完成建立')
@ -151,7 +151,7 @@ $submitBtn.click(function(event) {
success: function(response) {
$('#graph').html('')
var graphs = JSON.parse(response);
console.log(graphs.data);
// console.log(graphs.data);
Plotly.newPlot("graph",
graphs.data, layout, {responsive: true});
// 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 %}
<style>
body {
// padding-bottom: 10px;
font-family: Georgia, Arial, Geneva, Helvetica, serif !important;
/* // padding-bottom: 10px; */
font-family: Georgia, Arial, Helvetica, serif !important;
background-color: #eee;
height:95vh;
}

@ -20,39 +20,36 @@
</div>
<hr class="my-3 px-5">
<div class='d-flex justify-content-end'>
<div class="input-group mb-3">
<form method="POST">
<div class="input-group mb-1">
<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' %}
</select>
</div>
<div>
<button id="changeComp" type="button" class="btn btn-secondary"><i class="bi bi-arrow-right-square-fill"></i></button>
<div class="input-group mb-3">
<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 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 class="card m-3">
<div card="card p-3 m-3">
<div class="card-header">
<div class="d-flex">
<div class="py-2 font-bold text-xl">
<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>
Hello
</div>

@ -71,12 +71,12 @@
</div>
<div class="col-6">
{% for a in data.assets %}
<span class="badge text-bg-secondary">{{ a|safe }}</span>
<span class="badge text-bg-warning">{{ a|safe }}</span>
{% endfor %}
</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 class="btn btn-secondary mt-3" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="false" aria-controls="collapseExample">
詳細資訊
</button>
<div class="collapse" id="collapse1">
@ -126,11 +126,11 @@
<div class="row justify-content-center font-bold text-xl">
投組價值走勢
</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>
<div class="mb-4" id="bar" style="max-height:40vh"></div>
<div class="mb-4" id="bar" style="max-height:60vh"></div>
</div>
</div>
@ -161,9 +161,10 @@
const rlayout = {
'autosize': true,
'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':''},
'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'}
};
const blayout = {
@ -171,7 +172,7 @@
'title': {'text': ''},
'xaxis': {'anchor': 'y', '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'}
};
var w = {{ data.weight|safe }};

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

Loading…
Cancel
Save