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.
1347 lines
58 KiB
1347 lines
58 KiB
# -*- coding: utf-8 -*- |
|
|
|
from flask import Flask, Blueprint, render_template, session, jsonify, request, redirect, url_for, flash, g, Markup, abort |
|
# from flask_ipban import IpBan |
|
from flask_login import login_required, current_user, login_user, logout_user |
|
#from flask_environments import Environments |
|
import sqlite3 |
|
from werkzeug.security import generate_password_hash, check_password_hash |
|
from datetime import datetime, timedelta |
|
from dateutil.relativedelta import relativedelta |
|
import os |
|
import pytz |
|
import re |
|
import time |
|
import json |
|
import psycopg2 |
|
import psycopg2.extras |
|
from tickers_sorted import * |
|
from tickers_sorted_tw import * |
|
from black_list import black_list |
|
from postgresql_config import * |
|
|
|
import numpy as np |
|
import pandas as pd |
|
import matplotlib.pyplot as plt |
|
|
|
import math |
|
from cvxopt import matrix, solvers |
|
from cvxopt.blas import dot |
|
from cvxopt.solvers import qp |
|
|
|
import pickle |
|
|
|
UPLOAD_FOLDER = 'static/custom_data/' |
|
ALLOWED_EXTENSIONS = {'csv'} |
|
def allowed_file(filename): |
|
return '.' in filename and \ |
|
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS |
|
|
|
main = Flask(__name__) |
|
|
|
# https://stackoverflow.com/questions/24222220/block-an-ip-address-from-accessing-my-flask-app-on-heroku |
|
ip_ban_list = [] |
|
@main.before_request |
|
def block_method(): |
|
ip = request.environ.get('REMOTE_ADDR') |
|
if ip in ip_ban_list: |
|
abort(403) |
|
|
|
main.config['SECRET_KEY'] = os.urandom(30) |
|
main.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER |
|
|
|
from confidential_competitions import * |
|
confidential_competitions_placeholder = ', '.join(['%s']*len(confidential_competitions_list)) |
|
|
|
# Source: https://uniwebsidad.com/libros/explore-flask/chapter-8/custom-filters |
|
@main.template_filter('my_substitution') |
|
def my_substitution(string): |
|
return re.sub(r'@[a-zA-Z0-9_\-\.]+', r'', string) |
|
|
|
def ten_day_VaR(portfolio_value): |
|
# 95% z value = 1.645 |
|
z = 1.645 |
|
return_value = portfolio_value.pct_change().shift(-1).dropna() |
|
length = len(return_value) |
|
daily_ret = return_value.mean() |
|
daily_vol = return_value.std() |
|
return -1*(daily_ret * 10 - daily_vol * (10**0.5) * z) |
|
|
|
# risk_free_rate = 0.00899 # U.S. 5 Year Treasury at 11:31 PM EDT, Jun 28, 2021 |
|
risk_free_rate = 0 |
|
def Linear_Reg(x,y): |
|
''' |
|
input |
|
x: market excess return, np.ndarray |
|
y: portfolio excexx return, np.ndarray |
|
|
|
output |
|
slope, intercept, R^2 |
|
''' |
|
from sklearn import linear_model |
|
import numpy |
|
model = linear_model.LinearRegression(fit_intercept=True) |
|
X = x[:,numpy.newaxis] |
|
model.fit(X,y) |
|
yhat = model.predict(X) |
|
SSR = sum((y-yhat)**2) |
|
SST = sum((y-numpy.mean(y))**2) |
|
r_squared = 1 - (float(SSR))/SST |
|
return model.coef_[0], model.intercept_, r_squared |
|
# alpha = model.intercept_, beta = model.coef_[0] |
|
|
|
#env.filters['my_substitution'] = my_substitution |
|
|
|
|
|
|
|
|
|
# def connect_db(): |
|
# sql = sqlite3.connect('strategy.db', timeout=50) |
|
# sql.row_factory = sqlite3.Row |
|
# return sql |
|
# |
|
# def get_db(): |
|
# if not hasattr(g, 'sqlite_db'): |
|
# g.sqlite_db = connect_db() |
|
# return g.sqlite_db |
|
# |
|
# @main.teardown_appcontext |
|
# def close_db(error): |
|
# if hasattr(g, 'sqlite_db'): |
|
# g.sqlite_conn.close() |
|
|
|
|
|
@main.route('/') |
|
def index(): |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
|
|
# Number of effective users |
|
cur.execute('select count(b.a) as num_effective_users from (select min(strategy_id) as a from strategy group by author) as b') |
|
num_effective_users = cur.fetchone()['num_effective_users'] |
|
|
|
# Number of effective strategies |
|
cur.execute('select count(strategy_id) as num_effective_strategies from strategy where sharpe_ratio!=0') |
|
num_effective_strategies = cur.fetchone()['num_effective_strategies'] |
|
|
|
cur.close() |
|
conn.close() |
|
|
|
return render_template('index.html', num_effective_users=num_effective_users, num_effective_strategies=num_effective_strategies) |
|
# return render_template('index_temp_0314.html') |
|
|
|
|
|
|
|
|
|
@main.route('/create_strategy', methods=['GET', 'POST']) |
|
def create_strategy(): |
|
if not (session.get('USERNAME') and session['USERNAME']): |
|
flash('使用此功能必須先登入。', 'danger') |
|
return redirect('/login') |
|
if session['USERNAME'] in black_list: |
|
flash('我們已經暫停您建立策略的權利,有疑問請洽finteck@my.nthu.edu.tw', 'danger') |
|
return redirect('/') |
|
print('last_creation_time: ', session['last_creation_time']) |
|
if time.time() - session['last_creation_time'] < 30: |
|
flash('每兩次建立策略須間隔30秒', 'danger') |
|
return redirect('/') |
|
|
|
if request.method == 'GET': |
|
tw = request.values.get('tw') |
|
tw_digit = 1 if tw=='true' else 0 if tw=='false' else None |
|
|
|
if request.method == 'POST': |
|
strategy_name = request.form['strategy_name'] |
|
create_date = datetime.strftime(datetime.now() + timedelta(hours=8), '%Y/%m/%d %H:%M:%S.%f') |
|
session['last_creation_time'] = time.time() |
|
|
|
if strategy_name == '': |
|
flash('請取一個名字', 'danger') |
|
return render_template('create_strategy.html', asset_candidates=asset_candidates if tw=='false' else asset_candidates_tw if tw=='true' else None, tw=tw) |
|
competition = request.form['competition'] |
|
tw = request.form['tw'] |
|
tw_digit = 1 if tw=='true' else 0 if tw=='false' else None |
|
tickers = sorted(list(set(request.form.getlist('asset_ticker')))) |
|
print('The list of assets: ', tickers) |
|
|
|
|
|
# Turn off progress printing |
|
solvers.options['show_progress'] = False |
|
|
|
start_dates = [ datetime(y, m, 1) for y in range(2015, datetime.now().year+1) |
|
for m in [1, 4, 7, 10] |
|
if datetime(y, m, 1)<datetime.now() ] |
|
|
|
if tw=='false': |
|
data_us = pd.read_csv('data_for_trading_platform.csv') |
|
data_1 = pd.read_csv('data_for_trading_platform_tw_1.csv') |
|
data_2 = pd.read_csv('data_for_trading_platform_tw_2.csv') |
|
data_3 = pd.read_csv('data_for_trading_platform_tw_3.csv') |
|
data_4 = pd.read_csv('data_for_trading_platform_tw_4.csv') |
|
data_5 = pd.read_csv('data_for_trading_platform_tw_5.csv') |
|
data_6 = pd.read_csv('data_for_trading_platform_tw_6.csv') |
|
data_7 = pd.read_csv('data_for_trading_platform_tw_7.csv') |
|
data_8 = pd.read_csv('data_for_trading_platform_tw_8.csv') |
|
data_9 = pd.read_csv('data_for_trading_platform_tw_9.csv') |
|
all_data = pd.concat([data_us, data_1, data_2, data_3, data_4, data_5, data_6, data_7, data_8, data_9], ignore_index=True) |
|
|
|
elif tw=='true': |
|
data_1 = pd.read_csv('data_for_trading_platform_tw_1.csv') |
|
data_2 = pd.read_csv('data_for_trading_platform_tw_2.csv') |
|
data_3 = pd.read_csv('data_for_trading_platform_tw_3.csv') |
|
data_4 = pd.read_csv('data_for_trading_platform_tw_4.csv') |
|
data_5 = pd.read_csv('data_for_trading_platform_tw_5.csv') |
|
data_6 = pd.read_csv('data_for_trading_platform_tw_6.csv') |
|
data_7 = pd.read_csv('data_for_trading_platform_tw_7.csv') |
|
data_8 = pd.read_csv('data_for_trading_platform_tw_8.csv') |
|
data_9 = pd.read_csv('data_for_trading_platform_tw_9.csv') |
|
all_data = pd.concat([data_1, data_2, data_3, data_4, data_5, data_6, data_7, data_8, data_9], ignore_index=True) |
|
else: |
|
all_data = None |
|
all_data['Date'] = pd.to_datetime(all_data['Date'], format='%Y-%m-%d') |
|
|
|
def stockpri(ticker, start, end): |
|
data = all_data[ (all_data['Ticker']==ticker) & (all_data['Date']>=start) & (all_data['Date']<=end) ] |
|
data.set_index('Date', inplace=True) |
|
data = data['Adj Close'] |
|
return data |
|
|
|
|
|
portfolio_value = pd.Series([100]) |
|
optimal_weights = None |
|
hist_return_series = pd.DataFrame(columns=['quarter', 'quarterly_returns']) |
|
|
|
index_returns_full = pd.Series() |
|
portfolio_returns_full = pd.Series() |
|
|
|
for i in range(len(start_dates)-3): |
|
|
|
### Take 6 months to backtest ### |
|
|
|
start = start_dates[i] |
|
end = start_dates[i+2] |
|
|
|
data = pd.DataFrame({ ticker: stockpri(ticker, start, end) for ticker in tickers }) |
|
data = data.dropna() |
|
|
|
returns = data.pct_change() + 1 |
|
returns = returns.dropna() |
|
log_returns = np.log(data.pct_change() + 1) |
|
log_returns = log_returns.dropna() |
|
|
|
if log_returns.empty: |
|
continue |
|
|
|
mu = np.exp(log_returns.mean()*252).values |
|
# Markowitz frontier |
|
profit = np.linspace(np.amin(mu), np.amax(mu), 100) |
|
frontier = [] |
|
w = [] |
|
if len(tickers) >= 3: |
|
for p in profit: |
|
# Problem data. |
|
n = len(tickers) |
|
S = matrix(log_returns.cov().values*252) |
|
pbar = matrix(0.0, (n,1)) |
|
# Gx <= h |
|
G = matrix(0.0, (2*n,n)) |
|
G[::(2*n+1)] = 1.0 |
|
G[n::(2*n+1)] = -1.0 |
|
# h = matrix(1.0, (2*n,1)) |
|
h = matrix(np.concatenate((0.5*np.ones((n,1)), -0.03*np.ones((n,1))), axis=0)) |
|
A = matrix(np.concatenate((np.ones((1,n)), mu.reshape((1,n))), axis=0)) |
|
b = matrix([1, p], (2, 1)) |
|
|
|
# Compute trade-off. |
|
res = qp(S, -pbar, G, h, A, b) |
|
|
|
if res['status'] == 'optimal': |
|
res_weight = res['x'] |
|
s = math.sqrt(dot(res_weight, S*res_weight)) |
|
frontier.append(np.array([p, s])) |
|
w.append(res_weight) |
|
elif len(tickers) == 2: |
|
for p in profit: |
|
S = log_returns.cov().values*252 |
|
res_weight = [1 - (p-mu[0])/(mu[1]-mu[0]), (p-mu[0])/(mu[1]-mu[0])] |
|
if (res_weight[0] < 0.03) or (res_weight[0] > 0.97): |
|
continue |
|
s = math.sqrt(np.matmul(res_weight, np.matmul(S, np.transpose(res_weight)))) |
|
frontier.append(np.array([p, s])) |
|
w.append(res_weight) |
|
elif len(tickers) == 1: |
|
res_weight = [1] |
|
w.append(res_weight) |
|
frontier.append(np.array([1, 1])) |
|
|
|
|
|
frontier = np.array(frontier) |
|
if frontier.shape == (0,): |
|
continue |
|
x = np.array(frontier[:, 0]) |
|
y = np.array(frontier[:, 1]) |
|
|
|
frontier_sharpe_ratios = np.divide(x-1, y) |
|
optimal_portfolio_index = np.argmax(frontier_sharpe_ratios) |
|
optimal_weights = w[optimal_portfolio_index] |
|
|
|
|
|
### paper trade on the next three months ### |
|
|
|
start = start_dates[i+2] |
|
end = start_dates[i+3] |
|
|
|
if tw=='true': |
|
index_ticker = '0050.TW' |
|
elif tw=='false': |
|
index_ticker = 'SPY' |
|
|
|
data = pd.DataFrame({ ticker: stockpri(ticker, start, end) for ticker in tickers }) |
|
data['market'] = stockpri(index_ticker, start, end) |
|
data = data.dropna() |
|
|
|
returns = data.pct_change() + 1 |
|
returns = returns.dropna() |
|
log_returns = np.log(data.pct_change() + 1) |
|
log_returns = log_returns.dropna() |
|
|
|
index_returns_3_months = returns['market'] |
|
data = data.drop(columns=['market']) |
|
returns = returns.drop(columns=['market']) |
|
log_returns = log_returns.drop(columns=['market']) |
|
portfolio_returns_3_months = pd.Series(np.dot(returns, optimal_weights).flatten()) |
|
|
|
index_returns_full = index_returns_full.append(index_returns_3_months, ignore_index=True) |
|
portfolio_returns_full = portfolio_returns_full.append(portfolio_returns_3_months, ignore_index=True) |
|
|
|
portfolio_cum_returns = np.dot(returns, optimal_weights).cumprod() |
|
portfolio_value_new_window = portfolio_value.iloc[-1].item() * pd.Series(portfolio_cum_returns) |
|
portfolio_value_new_window.index = pd.to_datetime(returns.index, format='%Y-%m-%d') |
|
portfolio_value = portfolio_value.append(portfolio_value_new_window) |
|
|
|
# produce quarterly return |
|
hist_return_series.loc[len(hist_return_series)] = [str(start.year)+'Q'+str((start.month+2)//3), portfolio_cum_returns[-1]-1] |
|
|
|
if optimal_weights == None: |
|
sharpe_ratio = avg_annual_return = annual_volatility = max_drawdown = alpha = beta = r_squared = ten_day_var = 0 |
|
optimal_weights = [0, ]*len(tickers) |
|
hist_returns = None |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""insert into strategy (strategy_name, |
|
author, |
|
create_date, |
|
sharpe_ratio, |
|
return, |
|
volatility, |
|
max_drawdown, |
|
tw, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var) |
|
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) returning strategy_id;""", |
|
(strategy_name, |
|
session['USERNAME'], |
|
create_date, |
|
sharpe_ratio, |
|
avg_annual_return, |
|
annual_volatility, |
|
max_drawdown, |
|
tw_digit, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var |
|
) ) |
|
strategy_id = cur.fetchone()[0] |
|
conn.commit() |
|
|
|
# record the list of tickers into database |
|
# strategy_id = cur.execute('select * from strategy where create_date=?', [create_date]).fetchone()['strategy_id'] |
|
for i in range(len(tickers)): |
|
cur.execute('insert into assets_in_strategy (strategy_id, asset_ticker, weight) values (%s, %s, %s)', |
|
(strategy_id, tickers[i], optimal_weights[i])) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
|
|
print('Strategy_id ' + str(strategy_id) + ' optimization fails.') |
|
flash('無資料或無法畫出馬可維茲邊界,請換一個組合', 'danger') |
|
return render_template('create_strategy.html', asset_candidates=asset_candidates if tw=='false' else asset_candidates_tw if tw=='true' else None, tw=tw) |
|
|
|
avg_annual_return = np.exp(np.log(portfolio_value.pct_change() + 1).mean() * 252) - 1 |
|
annual_volatility = portfolio_value.pct_change().std() * math.sqrt(252) |
|
sharpe_ratio = avg_annual_return/annual_volatility |
|
max_drawdown = - np.amin(np.divide(portfolio_value, np.maximum.accumulate(portfolio_value)) - 1) |
|
ten_day_var = ten_day_VaR(portfolio_value) |
|
beta, alpha, r_squared = Linear_Reg(index_returns_full.to_numpy().flatten() - risk_free_rate, |
|
portfolio_returns_full.to_numpy().flatten() - risk_free_rate) |
|
|
|
print('Sharpe ratio: ', sharpe_ratio, ', Return: ', avg_annual_return, ', Volatility: ', annual_volatility, ', Maximum Drawdown: ', max_drawdown) |
|
|
|
# hist_return_series.set_index('quarter', inplace=True) |
|
hist_returns = pickle.dumps(hist_return_series) |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""insert into strategy (strategy_name, |
|
author, |
|
create_date, |
|
sharpe_ratio, |
|
return, |
|
volatility, |
|
max_drawdown, |
|
tw, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var) |
|
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) returning strategy_id;""", |
|
(strategy_name, |
|
session['USERNAME'], |
|
create_date, |
|
sharpe_ratio, |
|
avg_annual_return, |
|
annual_volatility, |
|
max_drawdown, |
|
tw_digit, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var |
|
) ) |
|
strategy_id = cur.fetchone()[0] |
|
conn.commit() |
|
|
|
# record the list of tickers into database |
|
# strategy_id = cur.execute('select * from strategy where create_date=?', [create_date]).fetchone()['strategy_id'] |
|
for i in range(len(tickers)): |
|
cur.execute('insert into assets_in_strategy (strategy_id, asset_ticker, weight) values (%s, %s, %s)', |
|
(strategy_id, tickers[i], optimal_weights[i])) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
|
|
# fig, ax = plt.subplots() |
|
# hist_return_series.hist(column='quarterly_returns', by='quarter', ax=ax) |
|
hist_return_plot = hist_return_series.plot.bar(x='quarter', y='quarterly_returns').get_figure() |
|
plt.tight_layout() |
|
# plt.xticks(rotation=45) |
|
hist_return_plot.savefig('static/img/quarterly_returns/'+str(strategy_id)+'.png') |
|
plt.close() |
|
|
|
print(portfolio_value.head()) |
|
print(portfolio_value.tail()) |
|
plt.xticks(rotation=90) |
|
# plt.tight_layout() |
|
plt.plot(portfolio_value.iloc[1:]) |
|
plt.savefig('static/img/portfolio_values/'+str(strategy_id)+'.png', bbox_inches='tight') |
|
plt.close() |
|
print('Strategy_id ' + str(strategy_id) + ' optimization succeeds.') |
|
flash(Markup('回測已完成,詳情請<a href="/post_page?post_id=' + str(strategy_id) + '">點這裡查看</a>。'), 'success') |
|
return render_template('create_strategy.html', asset_candidates=asset_candidates if tw=='false' else asset_candidates_tw if tw=='true' else None, tw=tw) |
|
|
|
|
|
@main.route('/create_strategy_upload', methods=['GET', 'POST']) |
|
def create_strategy_upload(): |
|
if not (session.get('USERNAME') and session['USERNAME']): |
|
flash('使用此功能必須先登入。', 'danger') |
|
return redirect('/login') |
|
if session['USERNAME'] in black_list: |
|
flash('我們已經暫停您建立策略的權利,有疑問請洽finteck@my.nthu.edu.tw', 'danger') |
|
return redirect('/') |
|
print('last_creation_time: ', session['last_creation_time']) |
|
if time.time() - session['last_creation_time'] < 30: |
|
flash('每兩次建立策略須間隔30秒', 'danger') |
|
return redirect('/') |
|
|
|
if request.method == 'POST': |
|
strategy_name = request.form['strategy_name'] |
|
create_date = datetime.strftime(datetime.now() + timedelta(hours=8), '%Y/%m/%d %H:%M:%S.%f') |
|
session['last_creation_time'] = time.time() |
|
|
|
if strategy_name == '': |
|
flash('請取一個名字', 'danger') |
|
return render_template('create_strategy_upload.html') |
|
|
|
f = request.files['fileToUpload'] |
|
filename = create_date.replace('/', '').replace(' ', '').replace(':', '').replace('.', '') + '.csv' |
|
f.save(os.path.join(main.config['UPLOAD_FOLDER'], filename)) |
|
|
|
all_data = pd.read_csv(main.config['UPLOAD_FOLDER'] + filename) |
|
all_data['Date'] = pd.to_datetime(all_data['Date'], format='%Y-%m-%d') |
|
|
|
tickers = list(all_data.columns) |
|
tickers.remove('Date') |
|
print('The list of assets: ', tickers) |
|
|
|
competition = request.form['competition'] |
|
|
|
|
|
# Turn off progress printing |
|
solvers.options['show_progress'] = False |
|
|
|
|
|
def stockpri(ticker, start, end): |
|
data = all_data.loc[start:end, ['Date', ticker]] |
|
data.set_index('Date', inplace=True) |
|
data = data[ticker] |
|
return data |
|
|
|
|
|
portfolio_value = pd.Series([100]) |
|
optimal_weights = None |
|
hist_return_series = pd.DataFrame(columns=['quarter', 'quarterly_returns']) |
|
|
|
start_dates = list(np.arange(0, len(all_data), 63)) + [len(all_data)-1] |
|
|
|
|
|
for i in range(len(start_dates)-3): |
|
|
|
### Take 6 months to backtest ### |
|
|
|
start = start_dates[i] |
|
end = start_dates[i+2]+1 |
|
|
|
data = pd.DataFrame({ ticker: stockpri(ticker, start, end) for ticker in tickers }) |
|
data = data.dropna() |
|
|
|
returns = data.pct_change() + 1 |
|
returns = returns.dropna() |
|
log_returns = np.log(data.pct_change() + 1) |
|
log_returns = log_returns.dropna() |
|
|
|
if log_returns.empty: |
|
continue |
|
|
|
mu = np.exp(log_returns.mean()*252).values |
|
# Markowitz frontier |
|
profit = np.linspace(np.amin(mu), np.amax(mu), 100) |
|
frontier = [] |
|
w = [] |
|
if len(tickers) >= 3: |
|
for p in profit: |
|
# Problem data. |
|
n = len(tickers) |
|
S = matrix(log_returns.cov().values*252) |
|
pbar = matrix(0.0, (n,1)) |
|
# Gx <= h |
|
G = matrix(0.0, (2*n,n)) |
|
G[::(2*n+1)] = 1.0 |
|
G[n::(2*n+1)] = -1.0 |
|
# h = matrix(1.0, (2*n,1)) |
|
h = matrix(np.concatenate((0.5*np.ones((n,1)), -0.03*np.ones((n,1))), axis=0)) |
|
A = matrix(np.concatenate((np.ones((1,n)), mu.reshape((1,n))), axis=0)) |
|
b = matrix([1, p], (2, 1)) |
|
|
|
# Compute trade-off. |
|
res = qp(S, -pbar, G, h, A, b) |
|
|
|
if res['status'] == 'optimal': |
|
res_weight = res['x'] |
|
s = math.sqrt(dot(res_weight, S*res_weight)) |
|
frontier.append(np.array([p, s])) |
|
w.append(res_weight) |
|
elif len(tickers) == 2: |
|
for p in profit: |
|
S = log_returns.cov().values*252 |
|
res_weight = [1 - (p-mu[0])/(mu[1]-mu[0]), (p-mu[0])/(mu[1]-mu[0])] |
|
if (res_weight[0] < 0.03) or (res_weight[0] > 0.97): |
|
continue |
|
s = math.sqrt(np.matmul(res_weight, np.matmul(S, np.transpose(res_weight)))) |
|
frontier.append(np.array([p, s])) |
|
w.append(res_weight) |
|
|
|
|
|
frontier = np.array(frontier) |
|
if frontier.shape == (0,): |
|
continue |
|
x = np.array(frontier[:, 0]) |
|
y = np.array(frontier[:, 1]) |
|
|
|
frontier_sharpe_ratios = np.divide(x-1, y) |
|
optimal_portfolio_index = np.argmax(frontier_sharpe_ratios) |
|
optimal_weights = w[optimal_portfolio_index] |
|
|
|
|
|
### paper trade on the next three months ### |
|
|
|
start = start_dates[i+2] |
|
end = start_dates[i+3]+1 |
|
|
|
data = pd.DataFrame({ ticker: stockpri(ticker, start, end) for ticker in tickers }) |
|
data = data.dropna() |
|
|
|
returns = data.pct_change() + 1 |
|
returns = returns.dropna() |
|
log_returns = np.log(data.pct_change() + 1) |
|
log_returns = log_returns.dropna() |
|
|
|
|
|
portfolio_cum_returns = np.dot(returns, optimal_weights).cumprod() |
|
portfolio_value_new_window = portfolio_value.iloc[-1].item() * pd.Series(portfolio_cum_returns) |
|
portfolio_value_new_window.index = pd.to_datetime(returns.index, format='%Y-%m-%d') |
|
portfolio_value = portfolio_value.append(portfolio_value_new_window) |
|
# portfolio_value.append(portfolio_value_new_window) |
|
|
|
# produce quarterly return |
|
hist_return_series.loc[len(hist_return_series)] = [str(start), portfolio_cum_returns[-1]-1] |
|
|
|
|
|
if len(tickers) == 1: |
|
optimal_weights = [1] |
|
portfolio_value = stockpri(tickers[0], 0, len(all_data)) |
|
for i in range(len(start_dates)-1): |
|
hist_return_series.loc[i] = [ str(start_dates[i]), portfolio_value[start_dates[i+1]] - portfolio_value[start_dates[i]] ] |
|
|
|
if optimal_weights == None: |
|
sharpe_ratio = avg_annual_return = annual_volatility = max_drawdown = ten_day_var = 0 |
|
optimal_weights = [0, ]*len(tickers) |
|
hist_returns = None |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""insert into strategy (strategy_name, |
|
author, |
|
create_date, |
|
sharpe_ratio, |
|
return, |
|
volatility, |
|
max_drawdown, |
|
tw, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var) |
|
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) returning strategy_id;""", |
|
(strategy_name, |
|
session['USERNAME'], |
|
create_date, |
|
sharpe_ratio, |
|
avg_annual_return, |
|
annual_volatility, |
|
max_drawdown, |
|
0, # tw_digit, |
|
competition, |
|
hist_returns, |
|
0, # alpha, |
|
0, # beta, |
|
0, # r_squared, |
|
ten_day_var |
|
) ) |
|
strategy_id = cur.fetchone()[0] |
|
conn.commit() |
|
|
|
# record the list of tickers into database |
|
# strategy_id = cur.execute('select * from strategy where create_date=?', [create_date]).fetchone()['strategy_id'] |
|
for i in range(len(tickers)): |
|
cur.execute('insert into assets_in_strategy (strategy_id, asset_ticker, weight) values (%s, %s, %s)', |
|
(strategy_id, tickers[i], optimal_weights[i])) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
print('Strategy_id ' + str(strategy_id) + ' optimization fails.') |
|
flash('無資料或無法畫出馬可維茲邊界,請換一個組合', 'danger') |
|
return render_template('create_strategy_upload.html') |
|
|
|
avg_annual_return = np.exp(np.log(portfolio_value.pct_change() + 1).mean() * 252) - 1 |
|
annual_volatility = portfolio_value.pct_change().std() * math.sqrt(252) |
|
sharpe_ratio = avg_annual_return/annual_volatility |
|
max_drawdown = - np.amin(np.divide(portfolio_value, np.maximum.accumulate(portfolio_value)) - 1) |
|
ten_day_var = ten_day_VaR(portfolio_value) |
|
|
|
print('Sharpe ratio: ', sharpe_ratio, ', Return: ', avg_annual_return, ', Volatility: ', annual_volatility, ', Maximum Drawdown: ', max_drawdown) |
|
|
|
# hist_return_series.set_index('quarter', inplace=True) |
|
hist_returns = pickle.dumps(hist_return_series) |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""insert into strategy (strategy_name, |
|
author, |
|
create_date, |
|
sharpe_ratio, |
|
return, |
|
volatility, |
|
max_drawdown, |
|
tw, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) returning strategy_id;""", |
|
(strategy_name, |
|
session['USERNAME'], |
|
create_date, |
|
sharpe_ratio, |
|
avg_annual_return, |
|
annual_volatility, |
|
max_drawdown, |
|
0, # tw_digit, |
|
competition, |
|
hist_returns, |
|
0, # alpha, |
|
0, # beta, |
|
0, # r_squared, |
|
ten_day_var |
|
) ) |
|
strategy_id = cur.fetchone()[0] |
|
conn.commit() |
|
|
|
# record the list of tickers into database |
|
# strategy_id = cur.execute('select * from strategy where create_date=%s', (create_date,)).fetchone()['strategy_id'] |
|
for i in range(len(tickers)): |
|
cur.execute('insert into assets_in_strategy (strategy_id, asset_ticker, weight) values (%s, %s, %s)', |
|
(strategy_id, tickers[i], optimal_weights[i])) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
|
|
# fig, ax = plt.subplots() |
|
# hist_return_series.hist(column='quarterly_returns', by='quarter', ax=ax) |
|
hist_return_plot = hist_return_series.plot.bar(x='quarter', y='quarterly_returns').get_figure() |
|
plt.tight_layout() |
|
hist_return_plot.savefig('static/img/quarterly_returns/'+str(strategy_id)+'.png') |
|
plt.close() |
|
|
|
print(portfolio_value.head()) |
|
print(portfolio_value.tail()) |
|
plt.xticks(rotation=90) |
|
# plt.tight_layout() |
|
plt.plot(portfolio_value.iloc[1:]) |
|
plt.savefig('static/img/portfolio_values/'+str(strategy_id)+'.png', bbox_inches='tight') |
|
plt.close() |
|
print('Strategy_id ' + str(strategy_id) + ' optimization succeeds.') |
|
flash(Markup('回測已完成,詳情請<a href="/post_page?post_id=' + str(strategy_id) + '">點這裡查看</a>。'), 'success') |
|
return render_template('create_strategy_upload.html') |
|
|
|
|
|
|
|
@main.route('/create_strategy_topic', methods=['GET', 'POST']) |
|
def create_strategy_topic(): |
|
if not (session.get('USERNAME') and session['USERNAME']): |
|
flash('使用此功能必須先登入。', 'danger') |
|
return redirect('/login') |
|
if session['USERNAME'] in black_list: |
|
flash('我們已經暫停您建立策略的權利,有疑問請洽finteck@my.nthu.edu.tw', 'danger') |
|
return redirect('/') |
|
print('last_creation_time: ', session['last_creation_time']) |
|
if time.time() - session['last_creation_time'] < 30: |
|
flash('每兩次建立策略須間隔30秒', 'danger') |
|
return redirect('/') |
|
|
|
if request.method == 'POST': |
|
strategy_name = request.form['strategy_name'] |
|
create_date = datetime.strftime(datetime.now() + timedelta(hours=8), '%Y/%m/%d %H:%M:%S.%f') |
|
session['last_creation_time'] = time.time() |
|
|
|
if strategy_name == '': |
|
flash('請取一個名字', 'danger') |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""select * from strategy where competition='topic' order by strategy_id desc""") |
|
sql_results = cur.fetchall() |
|
cur.close() |
|
conn.close() |
|
return render_template('create_strategy_topic.html', topic_results=sql_results) |
|
|
|
f = request.files['fileToUpload'] |
|
filename = create_date.replace('/', '').replace(' ', '').replace(':', '').replace('.', '') + '.csv' |
|
f.save(os.path.join(main.config['UPLOAD_FOLDER'], filename)) |
|
|
|
all_data = pd.read_csv(main.config['UPLOAD_FOLDER'] + filename) |
|
all_data['Date'] = pd.to_datetime(all_data['Date'], format='%Y-%m-%d') |
|
|
|
tickers = list(all_data.columns) |
|
tickers.remove('Date') |
|
print('The list of assets: ', tickers) |
|
|
|
competition = request.form['competition'] |
|
|
|
|
|
# Turn off progress printing |
|
solvers.options['show_progress'] = False |
|
|
|
|
|
def stockpri(ticker, start, end): |
|
data = all_data.loc[start:end, ['Date', ticker]] |
|
data.set_index('Date', inplace=True) |
|
data = data[ticker] |
|
return data |
|
|
|
|
|
portfolio_value = pd.Series([100]) |
|
optimal_weights = None |
|
hist_return_series = pd.DataFrame(columns=['month', 'monthly_returns']) |
|
|
|
start_dates = list(np.arange(0, len(all_data), 21)) + [len(all_data)-1] |
|
|
|
|
|
for i in range(len(start_dates)-3): |
|
|
|
### Take 6 months to backtest ### |
|
|
|
start = start_dates[i] |
|
end = start_dates[i+2]+1 |
|
|
|
data = pd.DataFrame({ ticker: stockpri(ticker, start, end) for ticker in tickers }) |
|
data = data.dropna() |
|
|
|
returns = data.pct_change() + 1 |
|
returns = returns.dropna() |
|
log_returns = np.log(data.pct_change() + 1) |
|
log_returns = log_returns.dropna() |
|
|
|
if log_returns.empty: |
|
continue |
|
|
|
mu = np.exp(log_returns.mean()*252).values |
|
# Markowitz frontier |
|
profit = np.linspace(np.amin(mu), np.amax(mu), 100) |
|
frontier = [] |
|
w = [] |
|
if len(tickers) >= 3: |
|
for p in profit: |
|
# Problem data. |
|
n = len(tickers) |
|
S = matrix(log_returns.cov().values*252) |
|
pbar = matrix(0.0, (n,1)) |
|
# Gx <= h |
|
G = matrix(0.0, (2*n,n)) |
|
G[::(2*n+1)] = 1.0 |
|
G[n::(2*n+1)] = -1.0 |
|
# h = matrix(1.0, (2*n,1)) |
|
h = matrix(np.concatenate((0.5*np.ones((n,1)), -0.03*np.ones((n,1))), axis=0)) |
|
A = matrix(np.concatenate((np.ones((1,n)), mu.reshape((1,n))), axis=0)) |
|
b = matrix([1, p], (2, 1)) |
|
|
|
# Compute trade-off. |
|
res = qp(S, -pbar, G, h, A, b) |
|
|
|
if res['status'] == 'optimal': |
|
res_weight = res['x'] |
|
s = math.sqrt(dot(res_weight, S*res_weight)) |
|
frontier.append(np.array([p, s])) |
|
w.append(res_weight) |
|
elif len(tickers) == 2: |
|
for p in profit: |
|
S = log_returns.cov().values*252 |
|
res_weight = [1 - (p-mu[0])/(mu[1]-mu[0]), (p-mu[0])/(mu[1]-mu[0])] |
|
if (res_weight[0] < 0.03) or (res_weight[0] > 0.97): |
|
continue |
|
s = math.sqrt(np.matmul(res_weight, np.matmul(S, np.transpose(res_weight)))) |
|
frontier.append(np.array([p, s])) |
|
w.append(res_weight) |
|
|
|
|
|
frontier = np.array(frontier) |
|
if frontier.shape == (0,): |
|
continue |
|
x = np.array(frontier[:, 0]) |
|
y = np.array(frontier[:, 1]) |
|
|
|
frontier_sharpe_ratios = np.divide(x-1, y) |
|
optimal_portfolio_index = np.argmax(frontier_sharpe_ratios) |
|
optimal_weights = w[optimal_portfolio_index] |
|
|
|
|
|
### paper trade on the next three months ### |
|
|
|
start = start_dates[i+2] |
|
end = start_dates[i+3]+1 |
|
|
|
data = pd.DataFrame({ ticker: stockpri(ticker, start, end) for ticker in tickers }) |
|
data = data.dropna() |
|
|
|
returns = data.pct_change() + 1 |
|
returns = returns.dropna() |
|
log_returns = np.log(data.pct_change() + 1) |
|
log_returns = log_returns.dropna() |
|
|
|
|
|
portfolio_cum_returns = np.dot(returns, optimal_weights).cumprod() |
|
portfolio_value_new_window = portfolio_value.iloc[-1].item() * pd.Series(portfolio_cum_returns) |
|
portfolio_value_new_window.index = pd.to_datetime(returns.index, format='%Y-%m-%d') |
|
portfolio_value = portfolio_value.append(portfolio_value_new_window) |
|
|
|
# produce monthly return |
|
hist_return_series.loc[len(hist_return_series)] = [str(start), portfolio_cum_returns[-1]-1] |
|
|
|
if optimal_weights == None: |
|
sharpe_ratio = avg_annual_return = annual_volatility = max_drawdown = ten_day_var = 0 |
|
optimal_weights = [0, ]*len(tickers) |
|
hist_returns = None |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""insert into strategy (strategy_name, |
|
author, |
|
create_date, |
|
explanation, |
|
sharpe_ratio, |
|
return, |
|
volatility, |
|
max_drawdown, |
|
tw, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var) |
|
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) returning strategy_id;""", |
|
(strategy_name, |
|
session['USERNAME'], |
|
create_date, |
|
"", #explanation |
|
sharpe_ratio, |
|
avg_annual_return, |
|
annual_volatility, |
|
max_drawdown, |
|
0, # tw_digit, |
|
competition, |
|
hist_returns, |
|
0, # alpha, |
|
0, # beta, |
|
0, # r_squared, |
|
ten_day_var |
|
) ) |
|
strategy_id = cur.fetchone()[0] |
|
conn.commit() |
|
|
|
# record the list of tickers into database |
|
# strategy_id = cur.execute('select * from strategy where create_date=?', [create_date]).fetchone()['strategy_id'] |
|
for i in range(len(tickers)): |
|
cur.execute('insert into assets_in_strategy (strategy_id, asset_ticker, weight) values (%s, %s, %s)', |
|
(strategy_id, tickers[i], optimal_weights[i])) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
print('Strategy_id ' + str(strategy_id) + ' optimization fails.') |
|
flash('無資料或無法畫出馬可維茲邊界,請換一個組合', 'danger') |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""select * from strategy where competition='topic' order by strategy_id desc""") |
|
sql_results = cur.fetchall() |
|
cur.close() |
|
conn.close() |
|
return render_template('create_strategy_topic.html', topic_results=sql_results) |
|
|
|
avg_annual_return = np.exp(np.log(portfolio_value.pct_change() + 1).mean() * 252) - 1 |
|
annual_volatility = portfolio_value.pct_change().std() * math.sqrt(252) |
|
sharpe_ratio = avg_annual_return/annual_volatility |
|
max_drawdown = - np.amin(np.divide(portfolio_value, np.maximum.accumulate(portfolio_value)) - 1) |
|
ten_day_var = ten_day_VaR(portfolio_value) |
|
|
|
print('Sharpe ratio: ', sharpe_ratio, ', Return: ', avg_annual_return, ', Volatility: ', annual_volatility, ', Maximum Drawdown: ', max_drawdown) |
|
|
|
# hist_return_series.set_index('month', inplace=True) |
|
hist_returns = pickle.dumps(hist_return_series) |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""insert into strategy (strategy_name, |
|
author, |
|
create_date, |
|
explanation, |
|
sharpe_ratio, |
|
return, |
|
volatility, |
|
max_drawdown, |
|
tw, |
|
competition, |
|
hist_returns, |
|
alpha, |
|
beta, |
|
r_squared, |
|
ten_day_var) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) returning strategy_id;""", |
|
(strategy_name, |
|
session['USERNAME'], |
|
create_date, |
|
"", #explanation |
|
sharpe_ratio, |
|
avg_annual_return, |
|
annual_volatility, |
|
max_drawdown, |
|
0, # tw_digit, |
|
competition, |
|
hist_returns, |
|
0, # alpha, |
|
0, # beta, |
|
0, # r_squared, |
|
ten_day_var |
|
) ) |
|
strategy_id = cur.fetchone()[0] |
|
conn.commit() |
|
|
|
# record the list of tickers into database |
|
# strategy_id = cur.execute('select * from strategy where create_date=%s', (create_date,)).fetchone()['strategy_id'] |
|
for i in range(len(tickers)): |
|
cur.execute('insert into assets_in_strategy (strategy_id, asset_ticker, weight) values (%s, %s, %s)', |
|
(strategy_id, tickers[i], optimal_weights[i])) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
|
|
# fig, ax = plt.subplots() |
|
# hist_return_series.hist(column='monthly_returns', by='month', ax=ax) |
|
hist_return_plot = hist_return_series.plot.bar(x='month', y='monthly_returns').get_figure() |
|
plt.tight_layout() |
|
hist_return_plot.savefig('static/img/monthly_returns/'+str(strategy_id)+'.png') |
|
plt.close() |
|
|
|
print(portfolio_value.head()) |
|
print(portfolio_value.tail()) |
|
plt.xticks(rotation=90) |
|
# plt.tight_layout() |
|
plt.plot(portfolio_value.iloc[1:]) |
|
plt.savefig('static/img/portfolio_values/'+str(strategy_id)+'.png', bbox_inches='tight') |
|
plt.close() |
|
print('Strategy_id ' + str(strategy_id) + ' optimization succeeds.') |
|
flash(Markup('回測已完成,詳情請<a href="/post_page?post_id=' + str(strategy_id) + '">點這裡查看</a>。'), 'success') |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""select * from strategy where competition in ('topic_high_sharpe', 'topic_high_esg', 'topic_high_return', 'topic_high_volume') order by strategy_id desc""") |
|
sql_results = cur.fetchall() |
|
cur.close() |
|
conn.close() |
|
return render_template('create_strategy_topic.html', topic_results=sql_results) |
|
|
|
|
|
@main.route('/login') |
|
def login(): |
|
return render_template('login.html') |
|
|
|
|
|
@main.route('/login', methods=['POST']) |
|
def login_post(): |
|
password = request.form.get('password') |
|
username = request.form.get('username') |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute('select * from users where username=%s', (username,)) |
|
sql_result = cur.fetchone() |
|
cur.close() |
|
conn.close() |
|
|
|
if (not sql_result) or (not check_password_hash(sql_result['password'], password)): |
|
flash('使用者代號不對或密碼不對,請再試一次。', 'danger') |
|
return redirect('/login') |
|
|
|
print(sql_result['username'], sql_result['user_id']) |
|
session['login'] = True |
|
session['user_id'] = sql_result['user_id'] |
|
session['USERNAME'] = sql_result['username'] |
|
session['last_creation_time'] = 0 |
|
session['vip'] = sql_result['vip'] |
|
return redirect('/') |
|
|
|
|
|
@main.route('/logout') |
|
#@login_required |
|
def logout(): |
|
#logout_user() |
|
session['user_id'] = -1 |
|
session['USERNAME'] = None |
|
session['login'] = False |
|
session['last_creation_time'] = None |
|
session['vip'] = None |
|
return redirect('/login') |
|
|
|
|
|
@main.route('/signup') |
|
def signup(): |
|
return render_template('signup.html') |
|
|
|
|
|
@main.route('/signup', methods=['POST']) |
|
def signup_post(): |
|
username = request.form.get('username') |
|
password = request.form.get('password') |
|
confirm_password = request.form.get('confirm_password') |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute('select * from users where username=%s', (username,)) |
|
sql_result = cur.fetchone() |
|
if sql_result: # if a user is found, we want to redirect back to signup page so user can try again |
|
cur.close() |
|
conn.close() |
|
flash('這個Email地址已經被使用', 'danger') |
|
return redirect('/signup') |
|
|
|
if not (password == confirm_password): |
|
cur.close() |
|
conn.close() |
|
flash('所輸入兩次密碼不同', 'danger') |
|
return redirect('/signup') |
|
|
|
# create new user with the form data. Hash the password so plaintext version isn't saved. |
|
cur.execute('insert into users (username, password) values (%s, %s)', (username, generate_password_hash(password))) |
|
conn.commit() |
|
cur.close() |
|
conn.close() |
|
|
|
print('registered') |
|
flash('已成功註冊', 'success') |
|
|
|
return redirect('/login') |
|
|
|
|
|
@main.route('/forum') |
|
def forum_index(): |
|
if not (session.get('USERNAME') and session['USERNAME']): |
|
flash('使用此功能必須先登入。', 'danger') |
|
return redirect('/login') |
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute("""select * from strategy |
|
where competition not in (""" + confidential_competitions_placeholder + """) or author=%s |
|
order by strategy_id desc;""", |
|
confidential_competitions_list + (session['USERNAME'],)) |
|
data = cur.fetchall() |
|
cur.close() |
|
conn.close() |
|
|
|
content_list = [] |
|
for d in data: |
|
content_list.append({ |
|
"id": d['strategy_id'], |
|
"time": d['create_date'], |
|
"user_id": None, |
|
"user_email": None, |
|
"user_name": d['author'], |
|
"comment": None, |
|
"title": d['strategy_name'], |
|
"video_id": None |
|
}) |
|
|
|
return_data = { |
|
"count": len(data), |
|
"content": content_list |
|
} |
|
|
|
return render_template('forum.html', forum_data=return_data) |
|
|
|
|
|
@main.route('/post_page', methods=['GET']) |
|
# @login_required |
|
def post_page(): |
|
if not (session.get('USERNAME') and session['USERNAME']): |
|
flash('使用此功能必須先登入。', 'danger') |
|
return redirect('/login') |
|
post_id = int(request.values.get('post_id')) |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
|
|
cur.execute('update strategy set click_count=click_count+1 where strategy_id=%s', (post_id,)) |
|
conn.commit() |
|
|
|
cur.execute('select * from strategy where strategy_id=%s', (post_id,)) |
|
strategy_content_list = cur.fetchone() |
|
cur.execute('select * from assets_in_strategy where strategy_id=%s', (post_id,)) |
|
asset_list = cur.fetchall() |
|
cur.execute('select * from comment where strategy_id=%s', (post_id,)) |
|
comment_list = cur.fetchall() |
|
cur.close() |
|
conn.close() |
|
|
|
print(asset_list) |
|
|
|
with open('latest_trading_data.txt') as all_data: |
|
all_data_close = json.load(all_data) |
|
|
|
return_data = { |
|
"strategy_content": strategy_content_list, |
|
"asset_content": asset_list, |
|
"comment_content": comment_list, |
|
"comment_count": len(comment_list), |
|
"asset_candidates": dict(asset_candidates + asset_candidates_tw), |
|
"all_trading_data": all_data_close |
|
} |
|
|
|
return render_template('post_page.html', data=return_data, strategy_id=str(post_id)) |
|
|
|
@main.route('/comment', methods=['POST']) |
|
#@login_required |
|
def post_comment_data(): |
|
comment = request.form['comment'] |
|
strategy_id = request.form['strategy_id'] |
|
author = session['USERNAME'] |
|
utc = datetime.utcnow() |
|
utc = utc.replace(tzinfo=pytz.utc) |
|
comment_date = datetime.strftime(utc.astimezone(pytz.timezone('Asia/Taipei')), '%Y/%m/%d %H:%M') |
|
# comment_date = datetime.strftime(datetime.utcnow().astimezone(pytz.timezone('Asia/Taipei')), '%Y/%m/%d %H:%M') |
|
# comment_date = datetime.strftime(datetime.utcnow().replace(tzinfo=pytz.timezone('Asia/Taipei')), '%Y/%m/%d %H:%M') |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
cur.execute('insert into comment (author, strategy_id, comment, date) values (%s, %s, %s, %s)', (author, strategy_id, comment, comment_date)) |
|
conn.commit() |
|
|
|
cur.close() |
|
conn.close() |
|
return redirect('/post_page?post_id='+str(strategy_id)) |
|
|
|
|
|
#@main.route('/profile') |
|
#@login_required |
|
#def profile(): |
|
# return render_template('profile.html', name=current_user.name) |
|
|
|
|
|
@main.route('/analysis_result') |
|
#@login_required |
|
def analysis_result(): |
|
if not (session.get('USERNAME') and session['USERNAME']): |
|
flash('使用此功能必須先登入。', 'danger') |
|
return redirect('/login') |
|
|
|
sortby = request.values.get('sortby') |
|
competition = request.values.get('competition') |
|
tw = request.values.get('tw') |
|
tw_digit = 1 if tw=='true' else 0 if tw=='false' else None |
|
|
|
conn = psycopg2.connect(database=POSTGRESQL_DATABASE, user=POSTGRESQL_USER) |
|
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) |
|
if sortby == 'competition': |
|
if competition in confidential_competitions_list: |
|
cur.close() |
|
conn.close() |
|
flash('本競賽暫不開放查詢', 'danger') |
|
return redirect('analysis_result?sortby=default&tw=' + tw +'&competition=none') |
|
cur.execute("""select b.strategy_id, |
|
a.author, |
|
b.create_date, |
|
b.return, |
|
a.sharpe_ratio, |
|
b.max_drawdown, |
|
b.strategy_name, |
|
b.volatility |
|
from (select author, max(sharpe_ratio) as sharpe_ratio from strategy group by author) as a |
|
join strategy as b on a.author=b.author and a.sharpe_ratio=b.sharpe_ratio |
|
where b.competition=%s |
|
order by a.sharpe_ratio desc;""", (competition,)) |
|
#cur.execute("""select strategy_id, |
|
# author, |
|
# create_date, |
|
# return, |
|
# max(sharpe_ratio) as sharpe_ratio, |
|
# max_drawdown, |
|
# strategy_name, |
|
# volatility |
|
# from strategy |
|
# where competition=%s |
|
# group by author |
|
# order by sharpe_ratio desc;""", (competition,)) |
|
sql_results = cur.fetchall() |
|
num_records_hidden = min(5, len(sql_results)) |
|
if session['vip']==False: |
|
for i in range(num_records_hidden): |
|
for key in sql_results[i].keys(): |
|
if key not in ['return', 'sharpe_ratio', 'max_drawdown', 'volatility']: |
|
sql_results[i][key] = '*****' |
|
elif sortby == 'default': |
|
cur.execute("""select * from strategy |
|
where tw=%s and (competition not in (""" + confidential_competitions_placeholder + """) or author=%s) |
|
order by strategy_id desc limit 200;""", |
|
(tw_digit,) + confidential_competitions_list + (session['USERNAME'],)) |
|
sql_results = cur.fetchall() |
|
elif sortby == 'myself': |
|
cur.execute("""select * from strategy |
|
where tw=%s and author=%s |
|
order by strategy_id desc;""", |
|
(tw_digit, session['USERNAME'])) |
|
sql_results = cur.fetchall() |
|
elif sortby == 'return': |
|
cur.execute("""select * from strategy |
|
where tw=%s and (competition not in (""" + confidential_competitions_placeholder + """) or author=%s) |
|
order by return desc limit 1000;""", |
|
(tw_digit,) + confidential_competitions_list + (session['USERNAME'],)) |
|
sql_results = cur.fetchall() |
|
# num_records_hidden = min(5, len(sql_results)) |
|
# if session['vip']==False: |
|
# for i in range(num_records_hidden): |
|
# for key in sql_results[i].keys(): |
|
# if key not in ['return', 'sharpe_ratio', 'max_drawdown', 'volatility']: |
|
# sql_results[i][key] = '*****' |
|
if session['vip']==False: |
|
return_threshold = 1 |
|
for record in sql_results: |
|
if record['return'] > return_threshold: |
|
for key in record.keys(): |
|
if key not in ['return', 'sharpe_ratio', 'max_drawdown', 'volatility']: |
|
record[key] = '*****' |
|
else: |
|
break |
|
elif sortby == 'sharpe': |
|
cur.execute("""select * from strategy |
|
where tw=%s and (competition not in (""" + confidential_competitions_placeholder + """) or author=%s) and sharpe_ratio!='NaN' |
|
order by sharpe_ratio desc limit 1000;""", |
|
(tw_digit,) + confidential_competitions_list + (session['USERNAME'],)) |
|
sql_results = cur.fetchall() |
|
if session['vip']==False: |
|
sharpe_threshold = 5 |
|
for record in sql_results: |
|
if record['sharpe_ratio'] > sharpe_threshold: |
|
for key in record.keys(): |
|
if key not in ['return', 'sharpe_ratio', 'max_drawdown', 'volatility']: |
|
record[key] = '*****' |
|
else: |
|
break |
|
elif sortby == 'vol': |
|
cur.execute("""select * from strategy |
|
where tw=%s and (competition not in (""" + confidential_competitions_placeholder + """) or author=%s) and volatility!=0 |
|
order by volatility asc limit 1000;""", |
|
(tw_digit,) + confidential_competitions_list + (session['USERNAME'],)) |
|
sql_results = cur.fetchall() |
|
if session['vip']==False: |
|
vol_threshold = 0.1 |
|
for record in sql_results: |
|
if record['volatility'] < vol_threshold: |
|
for key in record.keys(): |
|
if key not in ['return', 'sharpe_ratio', 'max_drawdown', 'volatility']: |
|
record[key] = '*****' |
|
else: |
|
break |
|
elif sortby == 'mdd': |
|
cur.execute("""select * from strategy |
|
where tw=%s and (competition not in (""" + confidential_competitions_placeholder + """) or author=%s) and max_drawdown!=0 |
|
order by max_drawdown asc limit 1000;""", |
|
(tw_digit,) + confidential_competitions_list + (session['USERNAME'],)) |
|
sql_results = cur.fetchall() |
|
if session['vip']==False: |
|
mdd_threshold = 0.15 |
|
for record in sql_results: |
|
if record['max_drawdown'] < mdd_threshold: |
|
for key in record.keys(): |
|
if key not in ['return', 'sharpe_ratio', 'max_drawdown', 'volatility']: |
|
record[key] = '*****' |
|
else: |
|
break |
|
cur.close() |
|
conn.close() |
|
return render_template('result.html', results=sql_results, tw=tw) |
|
|
|
|
|
if __name__ == "__main__": |
|
main.run(host='0.0.0.0', port=80)
|
|
|