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

# -*- 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)