You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
13 KiB
387 lines
13 KiB
from flask import Flask, render_template, request, redirect, url_for, g, session, flash, jsonify |
|
# from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user |
|
from markupsafe import escape |
|
|
|
from werkzeug.security import generate_password_hash, check_password_hash |
|
from datetime import datetime, date, timedelta |
|
|
|
|
|
import os |
|
import json |
|
import time |
|
import random |
|
import string |
|
import numpy as np |
|
import pandas as pd |
|
import psycopg2 |
|
import plotly |
|
import plotly.express as px |
|
from portfolio_builder import MVO |
|
pd.options.plotting.backend = "plotly" |
|
|
|
# PARAMETERS |
|
CONFIGS = { |
|
"ENV": "development", |
|
"DEBUG": True, |
|
"SECRET_KEY": os.urandom(30), # Set the secret key for session authentication |
|
"PERMANENT_SESSION_LIFETIME": timedelta(minutes=60) |
|
} |
|
SQL_CONFIG = { |
|
'database': "tpm", |
|
'user': "hsienchen", |
|
'host': "127.0.0.1", |
|
'port': "5432" |
|
} |
|
|
|
|
|
app = Flask(__name__) |
|
app.config.from_mapping(CONFIGS) |
|
# Login Manager, Flask_Login Stuff |
|
# login_manager = LoginManager() |
|
# login_manager.init_app(app) |
|
# login_manager.login_view = 'login' |
|
def login_required(): |
|
if not 'username' in session: |
|
return False |
|
else: |
|
return True |
|
def get_stock(conn, stock_list, tw): |
|
## Query DB |
|
if tw==1: |
|
sql="SELECT ticker, date, price, return FROM stock_price_tw where ticker = ANY(%s);" |
|
with conn: |
|
with conn.cursor() as curs: |
|
curs.execute(sql, (stock_list, )) |
|
# print(curs.mogrify(sql, (stock_list,))) |
|
data= curs.fetchall() |
|
else: |
|
sql1="SELECT ticker, date, price, return FROM stock_price where ticker = ANY(%s)" |
|
sql2="SELECT ticker, date, price, return FROM stock_price_tw where ticker = ANY(%s) ;" |
|
with conn: |
|
with conn.cursor() as curs: |
|
curs.execute(sql1, (stock_list,)) |
|
data_us= curs.fetchall() |
|
curs.execute(sql2, (stock_list,)) |
|
data_tw= curs.fetchall() |
|
data = data_us+data_tw |
|
dfStock = pd.DataFrame(data, columns=['ticker', 'date', 'price', 'return']) |
|
dfStock['date'] = pd.to_datetime(dfStock['date']) |
|
dfStock = dfStock.drop_duplicates() |
|
g = dfStock.groupby('ticker') |
|
port = pd.concat([g.get_group(t).set_index('date')['price'] for t in stock_list], axis=1, join='inner') |
|
port.columns=stock_list |
|
return port |
|
|
|
|
|
# Define the route for the index pages |
|
@app.route('/') |
|
def index(): |
|
return render_template('base.html') |
|
|
|
# Login Page |
|
@app.route('/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') |
|
@app.route('/login', methods=['POST']) |
|
def login_post(): |
|
# Get the username and password from the form |
|
username = request.form.get('username') |
|
password = request.form.get('password') |
|
print(username, password) |
|
|
|
|
|
## Connect to the database |
|
conn = psycopg2.connect(**SQL_CONFIG) |
|
with conn: |
|
with conn.cursor() as curs: |
|
curs.execute("select * from users where username = %s;", (username, )) |
|
data = curs.fetchone() |
|
conn.close() |
|
|
|
# Authentication |
|
if (data is None) or (username is None) or (password is None): |
|
flash('使用者代號不對或密碼不對,請再試一次。', 'danger') |
|
return render_template('login.html') |
|
elif check_password_hash(data[2], password): |
|
session['username'] = username |
|
session['user_id'] = data[0] |
|
session['privilege'] = data[-1] |
|
session['update_freq'] = 100 |
|
session['lastCreateTime'] = time.time() |
|
session['tw'] = 1 |
|
return redirect(url_for('index')) |
|
else: |
|
flash('使用者代號不對或密碼不對,請再試一次。', 'danger') |
|
return render_template('login.html') |
|
|
|
# Registration Page |
|
@app.route('/registration') |
|
def registration(): |
|
if login_required(): |
|
return redirect(url_for('index')) |
|
return render_template('registration.html') |
|
@app.route('/registration', methods=['POST']) |
|
def registration_post(): |
|
# Get the username and password from the form |
|
username = request.form.get('username') |
|
password = request.form.get('password') |
|
rep_password = request.form.get('rep-password') |
|
# check password |
|
if not password is None and password == rep_password: |
|
print(username, password) |
|
conn = psycopg2.connect(**SQL_CONFIG) |
|
## Connect to the database |
|
with conn.cursor() as curs: |
|
curs.execute("select * from users where username = %s;", (username, )) |
|
data = curs.fetchone() |
|
if data is None: |
|
with conn: |
|
with conn.cursor() as curs: |
|
curs.execute("insert into users (username, password) values (%s, %s);", (username, generate_password_hash(password))) |
|
# conn.commit() |
|
else: |
|
flash('使用者已存在。', 'warning') |
|
return redirect(url_for('login')) |
|
conn.close() |
|
name = username.split('@')[0] |
|
flash(f'註冊成功! 歡迎您, {name}。', 'success') |
|
return redirect(url_for('login')) |
|
else: |
|
flash('密碼不符合,請再次輸入。', 'warning') |
|
return redirect(url_for('registration')) |
|
|
|
# Logout Page |
|
@app.route('/logout', methods=['GET']) |
|
def logout(): |
|
if login_required(): |
|
pass |
|
else: |
|
flash('請先登入。', 'warning') |
|
return redirect(url_for('login')) |
|
if 'username' in session: |
|
# for key in list(session.keys()): |
|
# print(key, session[key]) |
|
# for key in list(session.keys()): |
|
# session.pop(key) |
|
session.clear() |
|
return redirect(url_for('index')) |
|
|
|
|
|
|
|
@app.route('/strategy') |
|
def strategy(): |
|
if login_required(): |
|
pass |
|
else: |
|
flash('使用投組功能請先登入。', 'warning') |
|
return redirect(url_for('login')) |
|
session['tw'] = 0 |
|
# Load Assets |
|
with open('assets_tw.json') as f: |
|
data_tw = json.load(f) |
|
with open('assets_us.json') as f: |
|
data = json.load(f) |
|
|
|
print(request.args.get('data'), 666) |
|
return render_template('strategy_tw.html', stockOpts={**data, **data_tw}) |
|
|
|
|
|
|
|
|
|
@app.route('/strategy_tw') |
|
def strategy_tw(): |
|
if login_required(): |
|
pass |
|
else: |
|
flash('使用投組功能請先登入。', 'warning') |
|
return redirect(url_for('login')) |
|
session['tw'] = 1 |
|
# Load Assets |
|
with open('assets_tw.json') as f: |
|
data = json.load(f) |
|
|
|
print(request.args.get('data'), 666) |
|
return render_template('strategy_tw.html', stockOpts=data) |
|
|
|
|
|
|
|
|
|
|
|
@app.route('/postStock', methods=['POST']) |
|
def submit_stock_list(): |
|
if login_required(): |
|
pass |
|
else: |
|
print('NOT LOGIN!!') |
|
return redirect(url_for('index')) |
|
if not 'tw' in session: |
|
return redirect(url_for('index')) |
|
# Update Session |
|
print("-"*10, "UPDATE ASSET", "-"*10) |
|
if session['update_freq']==0: |
|
print('update to frquent!') |
|
return 'update to frquent!' |
|
else: |
|
session['update_freq']-=1 |
|
flash('Looks like you have changed your name!', 'warning') |
|
stock_list = request.form.get('stockList') # this is string |
|
stock_list = json.loads(stock_list) # Load stock_list as list |
|
session['currStockList'] = stock_list |
|
|
|
## Query DB |
|
conn = psycopg2.connect(**SQL_CONFIG) |
|
port = get_stock(conn, stock_list, session['tw']) |
|
conn.close() |
|
|
|
fig = port.plot(title = 'Assets in portfolio', |
|
labels=dict(index="Date", value="Price", variable="Assets")) |
|
fig['layout'] = dict( |
|
autosize=True, |
|
legend={'title': {'text': 'Assets'}, 'tracegroupgap': 0}, |
|
title= {'text': 'Assets in portfolio'}, |
|
xaxis= {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'Date'}}, |
|
yaxis= {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Price'}} |
|
) |
|
fig.update_layout(legend=dict( |
|
yanchor="top", |
|
y=0.99, |
|
xanchor="left", |
|
x=0.01 |
|
)) |
|
|
|
print(type(stock_list)) |
|
|
|
# 序列化 |
|
graphJSON = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) |
|
|
|
|
|
for key in request.form: |
|
print(key, request.form[key]) |
|
# Do something with the stock list heres |
|
return graphJSON |
|
# return jsonify({'message': 'Stock list received successfully, NOOOOO'}) |
|
|
|
@app.route('/postPort', methods=['POST']) |
|
def buildPort(): |
|
# Login Required |
|
if login_required(): |
|
pass |
|
else: |
|
print('NOT LOGIN!!') |
|
return redirect(url_for('index')) |
|
if not 'tw' in session: |
|
return redirect(url_for('index')) |
|
# Stop frequently building strategy |
|
if time.time() - session['lastCreateTime'] < 20: |
|
print("UNTIL: ", time.time()-session['lastCreateTime']) |
|
return jsonify({'mes': '投資組合建立時間間隔(或與登入時間間隔)必須大於60秒!'}) |
|
session['lastCreateTime'] = time.time() |
|
|
|
|
|
print("-"*10) |
|
for key in request.form: |
|
print(key, request.form[key], type(request.form[key])) |
|
|
|
# Portfolio Info |
|
name = request.form.get('name') |
|
if name == '': |
|
prefix=''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) |
|
name= prefix + f"-{round(time.time()%100, 2)}" |
|
comp = request.form.get('comp') |
|
ts = int(request.form.get('ts')) |
|
ts = datetime.fromtimestamp(ts/1000) |
|
role = request.form.get('role') |
|
ratio = float(request.form.get('ratio')) |
|
comment = request.form.get('comment') |
|
stock_list = json.loads(request.form.get('stockList')) |
|
|
|
# Algorithm MVO |
|
print("-"*10) |
|
print("Enter Algorithms") |
|
print("-"*10) |
|
# time.sleep(20) |
|
# Query DB |
|
market_asset = '0050.TW' if session['tw']==1 else 'SPY' |
|
conn = psycopg2.connect(**SQL_CONFIG) |
|
if 'market_asset' in stock_list: |
|
port = get_stock(conn, stock_list, session['tw']) |
|
market = port[market_asset] |
|
else: |
|
port = get_stock(conn, stock_list+[market_asset], session['tw']) |
|
market = port[market_asset] |
|
port = port[stock_list] |
|
|
|
length, num = port.shape |
|
tsize = int(length*ratio) |
|
# time label |
|
train_label = port.index[1:][:tsize] |
|
test_label = port.index[1:][tsize:] |
|
|
|
# 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'), 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 \ |
|
(date, name, username, competition, role, ratio, annual_ret, vol, mdd, annual_sr, beta, alpha, var10, R2, tw, comment, assets, assets_position)\ |
|
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);' |
|
with conn: |
|
with conn.cursor() as curs: |
|
curs.execute(sql, data) |
|
conn.close() |
|
print("\n------Write in Success--------\n") |
|
return jsonify({'mes': '投資組合已完成建立,請至<a href"/">gooooooo</a>查詢分析結果。'}) |
|
|
|
@app.route('/custom') |
|
def custom(): |
|
if login_required(): |
|
pass |
|
else: |
|
flash('使用投組功能請先登入。', 'warning') |
|
return redirect(url_for('login')) |
|
return render_template('custom.html', message='No') |
|
@app.route('/result') |
|
def result(): |
|
if login_required(): |
|
pass |
|
else: |
|
flash('使用投組功能請先登入。', 'warning') |
|
return redirect(url_for('login')) |
|
return render_template('result.html') |
|
@app.route('/result_tw') |
|
def result_tw(): |
|
if login_required(): |
|
pass |
|
else: |
|
flash('使用投組功能請先登入。', 'warning') |
|
return redirect(url_for('login')) |
|
return render_template('result_tw.html') |
|
|
|
# handle login failed |
|
@app.errorhandler(401) |
|
def page_not_found(e): |
|
return Response('<p>Failed</p>') |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
app.run(host='0.0.0.0', port=8000)
|
|
|