I am trying to add a chatbot for the backtesting platform built with Flask and there is a button on the right side that toggles the sidebar. If the user clicks the button, it shows the sidebar with a chatbot.
The user should be able to type texts into the chatbot user input field.
Currently unable to type texts into the chatbot user input field.
const promptInput = document.getElementById("userInput");
const chatContainer = document.getElementById("chatContainer");
const typingIndicator = document.getElementById("typingIndicator");
const sidebar = document.getElementById("sidebar");
const sidebarContent = document.getElementById("sidebarContent");
async function sendMessage() {
const prompt = promptInput.value.trim();
if (!prompt) {
alert("Please enter a message.");
return;
}
addMessage(prompt, 'user');
promptInput.value = "";
showTypingIndicator();
const generatedText = await generateText(prompt);
addMessage(generatedText, 'bot');
hideTypingIndicator();
}
async function generateText(prompt) {
try {
const response = await fetch("http://127.0.0.1:5000/generate_text_stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt }),
});
if (!response.ok) {
console.error("Error:", response.statusText);
return "Error occurred while generating response.";
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let isFinished = false;
let generatedTextContent = "";
while (!isFinished) {
const { done, value } = await reader.read();
if (done) {
isFinished = true;
break;
}
generatedTextContent += decoder.decode(value, {stream: true});
}
return generatedTextContent;
} catch (error) {
console.error("Error:", error);
return "An error occurred.";
}
}
function addMessage(text, type) {
const messageDiv = document.createElement("div");
messageDiv.className = `message ${type}`;
messageDiv.innerHTML = `<div class="message-bubble fadeIn">${text}</div>`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
hideTypingIndicator();
}
let typingTimeout;
function showTypingIndicator() {
clearTimeout(typingTimeout);
typingIndicator.style.display = "inline-block";
}
function hideTypingIndicator() {
typingTimeout = setTimeout(() => {
typingIndicator.style.display = "none";
}, 1000);
}
function handleKeyPress(event) {
if (event.key === "Enter") {
sendMessage();
}
}
function toggleSidebar() {
if (sidebar.style.width === "500px") {
sidebar.style.width = "0";
sidebarContent.style.display = "none";
} else {
sidebar.style.width = "500px";
sidebarContent.style.display = "block";
}
}
window.onload = () => addMessage("Hello! How can I assist you today?", 'bot');
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
text-align: center;
background-color: #f4f4f4;
}
.container {
margin-top: 0;
width: 90%;
max-width: 450px;
margin: 10px auto 0;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: all 0.3s;
}
.chat {
overflow-y: auto;
height: 400px;
margin-bottom: 20px;
border-bottom: 2px solid #e2e2e2;
}
.message {
display: flex;
margin-bottom: 12px;
}
.message.user {
justify-content: flex-end;
}
.message-bubble {
padding: 12px 18px;
max-width: 70%;
border-radius: 20px;
line-height: 1.6;
font-size: 0.95rem;
}
.message.user .message-bubble {
background-color: #3182ce;
color: white;
}
.message.bot .message-bubble {
background-color: #e2e2e2;
color: #333;
}
input[type="text"] {
width: calc(100% - 110px);
padding: 12px 18px;
border: 2px solid #e2e2e2;
border-radius: 8px 0 0 8px;
font-size: 1rem;
outline: none;
z-index: 2; /* Ensure it's above other elements */
}
.send-button {
width: 110px;
background-color: #3182ce;
color: white;
padding: 12px 18px;
border: none;
border-radius: 0 8px 8px 0;
cursor: pointer;
transition: background-color 0.3s;
}
.send-button:hover {
background-color: #2c5282;
}
.footer {
text-align: center;
padding: 15px 0;
font-size: 0.9rem;
color: #666;
position: static;
border-top: 1px solid #e2e2e2;
background-color: #fff;
position: fixed;
bottom: 0;
width: 100%;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fadeIn {
animation: fadeIn 1s;
}
@media (max-width: 600px) {
.container {
width: 95%;
margin: 10px auto 0;
}
.chat {
height: 300px;
}
input[type="text"],
.send-button {
padding: 10px 14px;
font-size: 0.9rem;
}
.footer {
font-size: 0.8rem;
margin-top: 30px;
}
}
.typing-indicator {
display: none;
align-items: center;
justify-content: flex-end;
margin-top: 8px;
width: 10px;
height: 10px;
background-color: #333;
border-radius: 50%;
margin-left: 4px;
animation: typing 1s infinite;
}
@keyframes typing {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.7;
}
}
/* Sidebar Styles */
.sidebar {
position: fixed;
right: 0;
top: 0;
height: 100%;
width: 100%;
max-width: 500px;
background-color: #f4f4f4;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
color: white;
}
.sidebar-content {
display: none;
}
.sidebar-content h2 {
text-align: center;
}
.sidebar-content p {
padding: 10px;
}
.toggle-button {
position: fixed;
right: 0;
top: 0;
padding: 15px;
background-color: #3182ce;
color: white;
border: none;
cursor: pointer;
}
.toggle-button:hover {
background-color: #2c5282;
}
h1 {
color: rgb(0, 0, 0);
padding: 20px 0;
margin: 0 0 20px 0;
}
form {
display: inline-block;
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
select {
width: 100%;
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: #4CAF50;
color: white;
font-size: 16px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
img {
max-width: 90%;
margin: 20px 0;
border: 1px solid #ddd;
border-radius: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Backtesting Engine</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}"
/>
</head>
<body>
<h1 class="text-6xl font-bold mb-4 text-center">Backtesting Engine</h1>
<div class="sidebar" id="sidebar">
<button class="toggle-button" onclick="toggleSidebar()">☰</button>
<div class="sidebar-content" id="sidebarContent">
<div class="container bg-white rounded-lg shadow-md">
<h1 class="text-3xl font-bold mb-4 text-center">ChatBot</h1>
<div class="chat" id="chatContainer"></div>
<div class="flex">
<input
type="text"
id="userInput"
placeholder="Type your message here..."
class="outline-none"
onkeyup="handleKeyPress(event)"
/>
<button class="send-button" onclick="sendMessage()">Send</button>
</div>
<div class="typing-indicator" id="typingIndicator"></div>
</div>
</div>
</div>
<form method="post">
<label for="cs_model">Cross-Sectional Model:</label>
<select name="cs_model" id="cs_model">
<option value="EW">EW</option>
<option value="MSR">MSR</option>
<option value="GMV">GMV</option>
<option value="MDP">MDP</option>
<option value="EMV">EMV</option>
<option value="RP">RP</option></select
><br /><br />
<label for="ts_model">Time-Series Model:</label>
<select name="ts_model" id="ts_model">
<option value="VT">VT</option>
<option value="CVT">CVT</option>
<option value="KL">KL</option>
<option value="CPPI">CPPI</option>
<option value="">None</option></select
><br /><br />
<button type="submit">Run Backtest</button>
</form>
{% if plot_url1 and plot_url2 and plot_url3 %}
<h2>Backtesting Results</h2>
<img src="data:image/png;base64,{{ plot_url1 }}" alt="Portfolio Weights" />
<img
src="data:image/png;base64,{{ plot_url2 }}"
alt="Underlying Asset Performance"
/>
<img
src="data:image/png;base64,{{ plot_url3 }}"
alt="Portfolio Performance"
/>
{% endif %}
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>
from flask import Flask, render_template, request
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
import quantstats as qs
from scipy.optimize import minimize
from scipy.stats import norm
from io import BytesIO
import base64
app = Flask(__name__)
# Function to get ETF price data
def get_etf_price_data():
tickers = ['AAPL', 'MSFT', 'NVDA', 'GOOG', 'XLK']
etf = yf.Tickers(tickers)
data = etf.history(start='2010-01-01', actions=False)
data.drop(['Open', 'High', 'Low', 'Volume'], inplace=True, axis=1)
data = data.droplevel(0, axis=1)
data.ffill(inplace=True)
df = data.resample('W').last()
return df
df = get_etf_price_data()
# Portfolio Backtesting Engine Class
class GEMTU772:
# Initialization Function
def __init__(self, price, param=52):
# Annualization Parameter
self.param = param
# Intraday Return Rate
self.rets = price.pct_change().dropna()
# Expected Rate of Return
self.er = np.array(self.rets * self.param)
# Volatility
self.vol = np.array(self.rets.rolling(self.param).std() * np.sqrt(self.param))
# Covariance Matrix
cov = self.rets.rolling(self.param).cov().dropna() * self.param
# Transaction Cost per Unit
self.cov = cov.values.reshape(int(cov.shape[0]/cov.shape[1]), cov.shape[1], cov.shape[1])
self.cost = 0.0005
# Cross-Sectional Risk Models Class
class CrossSectional:
#EW
def ew(self, er):
noa = er.shape[0]
weights = np.ones_like(er) * (1/noa)
return weights
def msr(self, er, cov):
noa = er.shape[0]
init_guess = np.repeat(1/noa, noa)
bounds = ((0.0, 1.0), ) * noa
weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}
def neg_sharpe(weights, er, cov):
r = weights.T @ er # @ means multiplication
vol = np.sqrt(weights.T @ cov @ weights)
return - r / vol
weights = minimize(neg_sharpe, init_guess, args=(er, cov), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
return weights.x
#GMV
def gmv(self, cov):
noa = cov.shape[0]
init_guess = np.repeat(1/noa, noa)
bounds = ((0.0, 1.0), ) * noa
weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}
def port_vol(weights, cov):
vol = np.sqrt(weights.T @ cov @ weights)
return vol
weights = minimize(port_vol, init_guess, args=(cov,), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
return weights.x
#MDP
def mdp(self, vol, cov):
noa = vol.shape[0]
init_guess = np.repeat(1/noa, noa)
bounds = ((0.0, 1.0), ) * noa
weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}
def neg_div_ratio(weights, vol, cov):
weighted_vol = weights.T @ vol
port_vol = np.sqrt(weights.T @ cov @ weights)
return - weighted_vol / port_vol
weights = minimize(neg_div_ratio, init_guess, args=(vol, cov), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
return weights.x
#RP
def rp(self, cov):
noa = cov.shape[0]
init_guess = np.repeat(1/noa, noa)
bounds = ((0.0, 1.0), ) * noa
target_risk = np.repeat(1/noa, noa)
weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}
def msd_risk(weights, target_risk, cov):
port_var = weights.T @ cov @ weights
marginal_contribs = cov @ weights
risk_contribs = np.multiply(marginal_contribs, weights.T) / port_var
w_contribs = risk_contribs
return ((w_contribs - target_risk)**2).sum()
weights = minimize(msd_risk, init_guess, args=(target_risk, cov), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
return weights.x
#EMV
def emv(self, vol):
inv_vol = 1 / vol
weights = inv_vol / inv_vol.sum()
return weights
# Time-Series Risk Models Class
class TimeSeries:
#VT
def vt(self, port_rets, param, vol_target=0.1):
vol = port_rets.rolling(param).std().fillna(0) * np.sqrt(param)
weights = (vol_target / vol).replace([np.inf, -np.inf], 0).shift(1).fillna(0)
weights[weights > 1] = 1
return weights
#CVT
def cvt(self, port_rets, param, delta=0.01, cvar_target=0.05):
def calculate_CVaR(rets, delta=0.01):
VaR = rets.quantile(delta)
return rets[rets <= VaR].mean()
rolling_CVaR = -port_rets.rolling(param).apply(calculate_CVaR, args=(delta,)).fillna(0)
weights = (cvar_target / rolling_CVaR).replace([np.inf, -np.inf], 0).shift(1).fillna(0)
weights[weights > 1] = 1
return weights
#KL
def kl(self, port_rets, param):
sharpe_ratio = (port_rets.rolling(param).mean() * np.sqrt(param) / port_rets.rolling(param).std())
weights = pd.Series(2 * norm.cdf(sharpe_ratio) - 1, index=port_rets.index).fillna(0)
weights[weights < 0] = 0
weights = weights.shift(1).fillna(0)
return weights
#CPPI
def cppi(self, port_rets, m=3, floor=0.7, init_val=1):
n_steps = len(port_rets)
port_value = init_val
floor_value = init_val * floor
peak = init_val
port_history = pd.Series(dtype=np.float64).reindex_like(port_rets)
weight_history = pd.Series(dtype=np.float64).reindex_like(port_rets)
floor_history = pd.Series(dtype=np.float64).reindex_like(port_rets)
for step in range(n_steps):
peak = np.maximum(peak, port_value)
floor_value = peak * floor
cushion = (port_value - floor_value) / port_value
weight = m * cushion
risky_alloc = port_value * weight
safe_alloc = port_value * (1 - weight)
port_value = risky_alloc * (1 + port_rets.iloc[step]) + safe_alloc
port_history.iloc[step] = port_value
weight_history.iloc[step] = weight
floor_history.iloc[step] = floor_value
return weight_history.shift(1).fillna(0)
# Transaction Cost Function (Compound rate of return method assuming reinvestment)
def transaction_cost(self, weights_df, rets_df, cost=0.0005):
prev_weights_df = (weights_df.shift(1).fillna(0) * (1 + rets_df.iloc[self.param-1:,:])) \
.div((weights_df.shift(1).fillna(0) * (1 + rets_df.iloc[self.param-1:,:])).sum(axis=1), axis=0)
# Investment Weight of Previous Period (The backslash ('\') in Python is used as a line continuation character.)
cost_df = abs(weights_df - prev_weights_df) * cost
cost_df.fillna(0, inplace=True)
return cost_df
# Backtesting Execution Function
def run(self, cs_model, ts_model, cost):
# Empty Dictionary
backtest_dict = {}
# Intraday Return Rate DataFrame
rets = self.rets
# Select and Run Cross-Sectional Risk Models
for i, index in enumerate(rets.index[self.param-1:]):
if cs_model == 'EW':
backtest_dict[index] = self.CrossSectional().ew(self.er[i])
elif cs_model == 'MSR':
backtest_dict[index] = self.CrossSectional().msr(self.er[i], self.cov[i])
elif cs_model == 'GMV':
backtest_dict[index] = self.CrossSectional().gmv(self.cov[i])
elif cs_model == 'MDP':
backtest_dict[index] = self.CrossSectional().mdp(self.vol[i], self.cov[i])
elif cs_model == 'EMV':
backtest_dict[index] = self.CrossSectional().emv(self.vol[i])
elif cs_model == 'RP':
backtest_dict[index] = self.CrossSectional().rp(self.cov[i])
# Cross-Sectional Weights DataFrame
cs_weights = pd.DataFrame(list(backtest_dict.values()), index=backtest_dict.keys(), columns=rets.columns)
cs_weights.fillna(0, inplace=True)
# Cross-Sectional Risk Models Return on Assets
cs_rets = cs_weights.shift(1) * rets.iloc[self.param-1:,:]
# Cross-Sectional Risk Models Portfolio Return
cs_port_rets = cs_rets.sum(axis=1)
# Select and Run Time-Series Risk Models
if ts_model == 'VT':
ts_weights = self.TimeSeries().vt(cs_port_rets, self.param)
elif ts_model == 'CVT':
ts_weights = self.TimeSeries().cvt(cs_port_rets, self.param)
elif ts_model == 'KL':
ts_weights = self.TimeSeries().kl(cs_port_rets, self.param)
elif ts_model == 'CPPI':
ts_weights = self.TimeSeries().cppi(cs_port_rets)
elif ts_model == None:
ts_weights = 1
# Final Portfolio Investment Weights
port_weights = cs_weights.multiply(ts_weights, axis=0)
# Transaction Cost DataFrame
cost = self.transaction_cost(port_weights, rets)
# Final Portfolio Return by Assets
port_asset_rets = port_weights.shift() * rets - cost
# Final Portfolio Return
port_rets = port_asset_rets.sum(axis=1)
port_rets.index = pd.to_datetime(port_rets.index).strftime("%Y-%m-%d")
return port_weights, port_asset_rets, port_rets
def performance_analytics(self, port_weights, port_asset_rets, port_rets):
img = BytesIO()
plt.figure(figsize=(12, 7))
port_weights['Cash'] = 1 - port_weights.sum(axis=1)
plt.stackplot(port_weights.index, port_weights.T, labels=port_weights.columns)
plt.title('Portfolio Weights')
plt.xlabel('Date')
plt.ylabel('Weights')
plt.legend(loc='upper left')
plt.savefig(img, format='png')
plt.close()
img.seek(0)
plot_url1 = base64.b64encode(img.getvalue()).decode('utf8')
img = BytesIO()
plt.figure(figsize=(12, 7))
plt.plot((1 + port_asset_rets).cumprod() - 1)
plt.title('Underlying Asset Performance')
plt.xlabel('Date')
plt.ylabel('Returns')
plt.legend(port_asset_rets.columns, loc='upper left')
plt.savefig(img, format='png')
plt.close()
img.seek(0)
plot_url2 = base64.b64encode(img.getvalue()).decode('utf8')
img = BytesIO()
plt.figure(figsize=(12, 7))
plt.plot((1 + port_rets).cumprod() - 1)
plt.title('Portfolio Performance')
plt.xlabel('Date')
plt.ylabel('Returns')
plt.savefig(img, format='png')
plt.close()
img.seek(0)
plot_url3 = base64.b64encode(img.getvalue()).decode('utf8')
return plot_url1, plot_url2, plot_url3
#Route function
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
cs_model = request.form.get('cs_model') # cs model selection
ts_model = request.form.get('ts_model') # ts model selection
engine = GEMTU772(df) # Run backtesting
res = engine.run(cs_model=cs_model, ts_model=ts_model, cost=0.0005) # Run method
port_weights = res[0]
port_asset_rets = res[1]
port_rets = res[2]
plot_url1, plot_url2, plot_url3 = engine.performance_analytics(port_weights, port_asset_rets, port_rets)
return render_template('index.html', plot_url1=plot_url1, plot_url2=plot_url2, plot_url3=plot_url3) # Rendering
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Looks to me like you are able to type in the input. Only issue is you input is not visible due to color. just adding color to input[type="text"] css should make it visible.
input[type="text"] {
color: black;
}
const promptInput = document.getElementById("userInput");
const chatContainer = document.getElementById("chatContainer");
const typingIndicator = document.getElementById("typingIndicator");
const sidebar = document.getElementById("sidebar");
const sidebarContent = document.getElementById("sidebarContent");
async function sendMessage() {
const prompt = promptInput.value.trim();
if (!prompt) {
alert("Please enter a message.");
return;
}
addMessage(prompt, 'user');
promptInput.value = "";
showTypingIndicator();
const generatedText = await generateText(prompt);
addMessage(generatedText, 'bot');
hideTypingIndicator();
}
async function generateText(prompt) {
try {
const response = await fetch("http://127.0.0.1:5000/generate_text_stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt }),
});
if (!response.ok) {
console.error("Error:", response.statusText);
return "Error occurred while generating response.";
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let isFinished = false;
let generatedTextContent = "";
while (!isFinished) {
const { done, value } = await reader.read();
if (done) {
isFinished = true;
break;
}
generatedTextContent += decoder.decode(value, {stream: true});
}
return generatedTextContent;
} catch (error) {
console.error("Error:", error);
return "An error occurred.";
}
}
function addMessage(text, type) {
const messageDiv = document.createElement("div");
messageDiv.className = `message ${type}`;
messageDiv.innerHTML = `<div class="message-bubble fadeIn">${text}</div>`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
hideTypingIndicator();
}
let typingTimeout;
function showTypingIndicator() {
clearTimeout(typingTimeout);
typingIndicator.style.display = "inline-block";
}
function hideTypingIndicator() {
typingTimeout = setTimeout(() => {
typingIndicator.style.display = "none";
}, 1000);
}
function handleKeyPress(event) {
if (event.key === "Enter") {
sendMessage();
}
}
function toggleSidebar() {
if (sidebar.style.width === "500px") {
sidebar.style.width = "0";
sidebarContent.style.display = "none";
} else {
sidebar.style.width = "500px";
sidebarContent.style.display = "block";
}
}
window.onload = () => addMessage("Hello! How can I assist you today?", 'bot');
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
text-align: center;
background-color: #f4f4f4;
}
.container {
margin-top: 0;
width: 90%;
max-width: 450px;
margin: 10px auto 0;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: all 0.3s;
}
.chat {
overflow-y: auto;
height: 400px;
margin-bottom: 20px;
border-bottom: 2px solid #e2e2e2;
}
.message {
display: flex;
margin-bottom: 12px;
}
.message.user {
justify-content: flex-end;
}
.message-bubble {
padding: 12px 18px;
max-width: 70%;
border-radius: 20px;
line-height: 1.6;
font-size: 0.95rem;
}
.message.user .message-bubble {
background-color: #3182ce;
color: white;
}
.message.bot .message-bubble {
background-color: #e2e2e2;
color: #333;
}
input[type="text"] {
width: calc(100% - 110px);
padding: 12px 18px;
border: 2px solid #e2e2e2;
border-radius: 8px 0 0 8px;
font-size: 1rem;
outline: none;
color: black;
z-index: 2; /* Ensure it's above other elements */
}
.send-button {
width: 110px;
background-color: #3182ce;
color: white;
padding: 12px 18px;
border: none;
border-radius: 0 8px 8px 0;
cursor: pointer;
transition: background-color 0.3s;
}
.send-button:hover {
background-color: #2c5282;
}
.footer {
text-align: center;
padding: 15px 0;
font-size: 0.9rem;
color: #666;
position: static;
border-top: 1px solid #e2e2e2;
background-color: #fff;
position: fixed;
bottom: 0;
width: 100%;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fadeIn {
animation: fadeIn 1s;
}
@media (max-width: 600px) {
.container {
width: 95%;
margin: 10px auto 0;
}
.chat {
height: 300px;
}
input[type="text"],
.send-button {
padding: 10px 14px;
font-size: 0.9rem;
}
.footer {
font-size: 0.8rem;
margin-top: 30px;
}
}
.typing-indicator {
display: none;
align-items: center;
justify-content: flex-end;
margin-top: 8px;
width: 10px;
height: 10px;
background-color: #333;
border-radius: 50%;
margin-left: 4px;
animation: typing 1s infinite;
}
@keyframes typing {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.7;
}
}
/* Sidebar Styles */
.sidebar {
position: fixed;
right: 0;
top: 0;
height: 100%;
width: 100%;
max-width: 500px;
background-color: #f4f4f4;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
color: white;
}
.sidebar-content {
display: none;
}
.sidebar-content h2 {
text-align: center;
}
.sidebar-content p {
padding: 10px;
}
.toggle-button {
position: fixed;
right: 0;
top: 0;
padding: 15px;
background-color: #3182ce;
color: white;
border: none;
cursor: pointer;
}
.toggle-button:hover {
background-color: #2c5282;
}
h1 {
color: rgb(0, 0, 0);
padding: 20px 0;
margin: 0 0 20px 0;
}
form {
display: inline-block;
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
select {
width: 100%;
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: #4CAF50;
color: white;
font-size: 16px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
img {
max-width: 90%;
margin: 20px 0;
border: 1px solid #ddd;
border-radius: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Backtesting Engine</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}"
/>
</head>
<body>
<h1 class="text-6xl font-bold mb-4 text-center">Backtesting Engine</h1>
<div class="sidebar" id="sidebar">
<button class="toggle-button" onclick="toggleSidebar()">☰</button>
<div class="sidebar-content" id="sidebarContent">
<div class="container bg-white rounded-lg shadow-md">
<h1 class="text-3xl font-bold mb-4 text-center">ChatBot</h1>
<div class="chat" id="chatContainer"></div>
<div class="flex">
<input
type="text"
id="userInput"
placeholder="Type your message here..."
class="outline-none"
onkeyup="handleKeyPress(event)"
/>
<button class="send-button" onclick="sendMessage()">Send</button>
</div>
<div class="typing-indicator" id="typingIndicator"></div>
</div>
</div>
</div>
<form method="post">
<label for="cs_model">Cross-Sectional Model:</label>
<select name="cs_model" id="cs_model">
<option value="EW">EW</option>
<option value="MSR">MSR</option>
<option value="GMV">GMV</option>
<option value="MDP">MDP</option>
<option value="EMV">EMV</option>
<option value="RP">RP</option></select
><br /><br />
<label for="ts_model">Time-Series Model:</label>
<select name="ts_model" id="ts_model">
<option value="VT">VT</option>
<option value="CVT">CVT</option>
<option value="KL">KL</option>
<option value="CPPI">CPPI</option>
<option value="">None</option></select
><br /><br />
<button type="submit">Run Backtest</button>
</form>
{% if plot_url1 and plot_url2 and plot_url3 %}
<h2>Backtesting Results</h2>
<img src="data:image/png;base64,{{ plot_url1 }}" alt="Portfolio Weights" />
<img
src="data:image/png;base64,{{ plot_url2 }}"
alt="Underlying Asset Performance"
/>
<img
src="data:image/png;base64,{{ plot_url3 }}"
alt="Portfolio Performance"
/>
{% endif %}
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>
Hope this helps! If not let me know what i missed. Do accept the answer if it helps.