Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
| 1 |
import yfinance as yf
|
| 2 |
import numpy as np
|
| 3 |
import pandas as pd
|
| 4 |
-
import matplotlib.pyplot as plt
|
| 5 |
import streamlit as st
|
| 6 |
from sklearn.linear_model import RANSACRegressor, LinearRegression
|
| 7 |
from scipy.stats import linregress
|
| 8 |
import plotly.graph_objects as go
|
| 9 |
from plotly.subplots import make_subplots
|
|
|
|
| 10 |
|
| 11 |
# Helper function to fetch stock data
|
| 12 |
def fetch_stock_data(ticker: str, start_date: str, end_date: str) -> pd.DataFrame:
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# Helper function to plot rolling volatility and volatility of volatility
|
| 16 |
def plot_rolling_volatility(data: pd.DataFrame, window: int) -> go.Figure:
|
|
@@ -50,11 +54,12 @@ def plot_rolling_treynor(data: pd.DataFrame, market_data: pd.DataFrame, window:
|
|
| 50 |
|
| 51 |
# Align indices
|
| 52 |
market_data = market_data.reindex(data.index, method='ffill')
|
|
|
|
| 53 |
|
| 54 |
-
covariance =
|
| 55 |
-
variance =
|
| 56 |
rolling_beta = covariance / variance
|
| 57 |
-
avg_rolling_returns =
|
| 58 |
data['Rolling_Treynor_Ratio'] = (avg_rolling_returns - risk_free_rate) / rolling_beta
|
| 59 |
|
| 60 |
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
|
@@ -69,8 +74,8 @@ def plot_rolling_treynor(data: pd.DataFrame, market_data: pd.DataFrame, window:
|
|
| 69 |
|
| 70 |
# Helper function to calculate and plot rolling beta
|
| 71 |
def plot_rolling_beta(data: pd.DataFrame, market_data: pd.DataFrame, window: int) -> go.Figure:
|
| 72 |
-
data['Return'] = data['Close'].pct_change()
|
| 73 |
-
market_data['Market_Return'] = market_data['Close'].pct_change()
|
| 74 |
|
| 75 |
# Align dates and remove rows with missing data
|
| 76 |
aligned_data = pd.concat([data['Return'], market_data['Market_Return']], axis=1).dropna()
|
|
@@ -80,7 +85,7 @@ def plot_rolling_beta(data: pd.DataFrame, market_data: pd.DataFrame, window: int
|
|
| 80 |
rolling_beta_ransac = []
|
| 81 |
rolling_beta_ols = []
|
| 82 |
|
| 83 |
-
for i in range(len(aligned_data) - window):
|
| 84 |
X = aligned_data['Market_Return'].iloc[i:i+window].values.reshape(-1, 1)
|
| 85 |
y = aligned_data['Return'].iloc[i:i+window].values
|
| 86 |
|
|
@@ -93,8 +98,8 @@ def plot_rolling_beta(data: pd.DataFrame, market_data: pd.DataFrame, window: int
|
|
| 93 |
rolling_beta_ols.append(beta_ols)
|
| 94 |
|
| 95 |
# Convert lists to series with appropriate index
|
| 96 |
-
rolling_beta_ransac = pd.Series(rolling_beta_ransac, index=aligned_data.index[window:])
|
| 97 |
-
rolling_beta_ols = pd.Series(rolling_beta_ols, index=aligned_data.index[window:])
|
| 98 |
|
| 99 |
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 100 |
subplot_titles=('Close Price', 'Benchmark Price', 'Rolling Beta (RANSAC & OLS)'))
|
|
@@ -112,17 +117,20 @@ def plot_rolling_alpha(data: pd.DataFrame, market_data: pd.DataFrame, window: in
|
|
| 112 |
data['Return'] = data['Close'].pct_change()
|
| 113 |
market_data['Return'] = market_data['Close'].pct_change()
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
| 120 |
beta = window_stock.cov(window_market) / window_market.var()
|
| 121 |
expected_return = risk_free_rate + beta * (window_market.mean() - risk_free_rate)
|
| 122 |
alpha = window_stock.mean() - expected_return
|
| 123 |
rolling_alpha.append(alpha)
|
| 124 |
|
| 125 |
-
rolling_alpha = pd.Series(rolling_alpha, index=
|
| 126 |
|
| 127 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 128 |
subplot_titles=('Close Price', 'Rolling Jensen\'s Alpha'))
|
|
@@ -156,8 +164,8 @@ def plot_rolling_cvar(data: pd.DataFrame, window: int) -> go.Figure:
|
|
| 156 |
data['Return'] = data['Close'].pct_change()
|
| 157 |
|
| 158 |
def conditional_var(x, alpha=0.05):
|
| 159 |
-
var = np.percentile(x, alpha * 100)
|
| 160 |
-
return np.mean(x[x < var])
|
| 161 |
|
| 162 |
rolling_cvar_95 = data['Return'].rolling(window).apply(conditional_var, raw=True).dropna()
|
| 163 |
rolling_cvar_90 = data['Return'].rolling(window).apply(lambda x: conditional_var(x, alpha=0.1), raw=True).dropna()
|
|
@@ -177,7 +185,7 @@ def plot_rolling_cvar(data: pd.DataFrame, window: int) -> go.Figure:
|
|
| 177 |
# Helper function to plot rolling Tail Ratio
|
| 178 |
def plot_rolling_tail_ratio(data: pd.DataFrame, window: int) -> go.Figure:
|
| 179 |
data['Return'] = data['Close'].pct_change()
|
| 180 |
-
tail_ratio = data['Return'].rolling(window).apply(lambda x: np.abs(np.percentile(x, 95)) / np.abs(np.percentile(x, 5))).dropna()
|
| 181 |
|
| 182 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 183 |
subplot_titles=('Close Price', 'Rolling Tail Ratio'))
|
|
@@ -193,7 +201,7 @@ def plot_rolling_tail_ratio(data: pd.DataFrame, window: int) -> go.Figure:
|
|
| 193 |
def plot_rolling_omega(data: pd.DataFrame, window: int) -> go.Figure:
|
| 194 |
data['Return'] = data['Close'].pct_change()
|
| 195 |
MAR = 0 # Minimum Acceptable Return
|
| 196 |
-
omega_ratio = data['Return'].rolling(window).apply(lambda x: np.sum(x[x > MAR] - MAR) / np.sum(MAR - x[x < MAR])).dropna()
|
| 197 |
|
| 198 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 199 |
subplot_titles=('Close Price', 'Rolling Omega Ratio'))
|
|
@@ -208,7 +216,7 @@ def plot_rolling_omega(data: pd.DataFrame, window: int) -> go.Figure:
|
|
| 208 |
# Helper function to plot rolling Sortino Ratio
|
| 209 |
def plot_rolling_sortino(data: pd.DataFrame, window: int, MAR: float) -> go.Figure:
|
| 210 |
data['Return'] = data['Close'].pct_change()
|
| 211 |
-
sortino_ratio = data['Return'].rolling(window).apply(lambda x: np.mean(x - MAR) / np.sqrt(np.mean(np.minimum(0, x - MAR) ** 2))).dropna()
|
| 212 |
|
| 213 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 214 |
subplot_titles=('Close Price', 'Rolling Sortino Ratio'))
|
|
@@ -222,7 +230,7 @@ def plot_rolling_sortino(data: pd.DataFrame, window: int, MAR: float) -> go.Figu
|
|
| 222 |
# Helper function to plot rolling Calmar Ratio
|
| 223 |
def plot_rolling_calmar(data: pd.DataFrame, window: int) -> go.Figure:
|
| 224 |
data['Return'] = data['Close'].pct_change()
|
| 225 |
-
calmar_ratio = data['Return'].rolling(window).apply(lambda x: (1 + x).
|
| 226 |
|
| 227 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 228 |
subplot_titles=('Close Price', 'Rolling Calmar Ratio'))
|
|
@@ -236,7 +244,7 @@ def plot_rolling_calmar(data: pd.DataFrame, window: int) -> go.Figure:
|
|
| 236 |
# Helper function to plot rolling stability
|
| 237 |
def plot_rolling_stability(data: pd.DataFrame, window: int) -> go.Figure:
|
| 238 |
data['Return'] = data['Close'].pct_change()
|
| 239 |
-
stability = data['Return'].rolling(window).apply(lambda x: np.std(np.log1p(x).cumsum() - linregress(np.arange(len(x)), np.log1p(x).cumsum()).intercept - linregress(np.arange(len(x)), np.log1p(x).cumsum()).slope * np.arange(len(x)))).dropna()
|
| 240 |
|
| 241 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 242 |
subplot_titles=('Close Price', 'Rolling Stability'))
|
|
@@ -270,6 +278,7 @@ def plot_rolling_capture(data: pd.DataFrame, market_data: pd.DataFrame, window:
|
|
| 270 |
|
| 271 |
# Align indices
|
| 272 |
market_data = market_data.reindex(data.index, method='ffill')
|
|
|
|
| 273 |
|
| 274 |
def calculate_capture(stock_returns, market_returns, is_upside=True):
|
| 275 |
if is_upside:
|
|
@@ -278,7 +287,7 @@ def plot_rolling_capture(data: pd.DataFrame, market_data: pd.DataFrame, window:
|
|
| 278 |
else:
|
| 279 |
relevant_returns = stock_returns[market_returns < 0]
|
| 280 |
relevant_market_returns = market_returns[market_returns < 0]
|
| 281 |
-
return relevant_returns.sum() / relevant_market_returns.sum()
|
| 282 |
|
| 283 |
def compute_rolling_captures(stock_returns, market_returns, window):
|
| 284 |
upside_captures = []
|
|
@@ -298,15 +307,15 @@ def plot_rolling_capture(data: pd.DataFrame, market_data: pd.DataFrame, window:
|
|
| 298 |
upside_captures.append(upside)
|
| 299 |
downside_captures.append(downside)
|
| 300 |
|
| 301 |
-
# Padding the initial values with NaNs to
|
| 302 |
nan_padding = [np.nan] * (window - 1)
|
| 303 |
-
upside_captures = nan_padding + upside_captures
|
| 304 |
-
downside_captures = nan_padding + downside_captures
|
| 305 |
|
| 306 |
return upside_captures, downside_captures
|
| 307 |
|
| 308 |
data['rolling_upside_capture'], data['rolling_downside_capture'] = compute_rolling_captures(
|
| 309 |
-
|
| 310 |
)
|
| 311 |
|
| 312 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
|
@@ -325,7 +334,7 @@ def plot_rolling_pain_index(data: pd.DataFrame, window: int) -> go.Figure:
|
|
| 325 |
cumulative_return = (1 + data['Return']).cumprod()
|
| 326 |
running_max = cumulative_return.cummax()
|
| 327 |
drawdown = (cumulative_return - running_max) / running_max
|
| 328 |
-
pain_index = drawdown.rolling(window).apply(lambda x: np.mean(x[x < 0])).dropna()
|
| 329 |
|
| 330 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 331 |
subplot_titles=('Close Price', 'Rolling Pain Index'))
|
|
@@ -342,9 +351,6 @@ st.title('Dynamic Risk Management Indicators')
|
|
| 342 |
|
| 343 |
st.sidebar.title("Input Parameters")
|
| 344 |
|
| 345 |
-
# Sidebar for method selection
|
| 346 |
-
import datetime
|
| 347 |
-
|
| 348 |
# Setting today's date plus one day
|
| 349 |
today_plus_one = pd.to_datetime(datetime.datetime.now().date() + pd.Timedelta(days=1))
|
| 350 |
|
|
@@ -367,588 +373,470 @@ with st.sidebar.expander("Select Method", expanded=True):
|
|
| 367 |
window_size = st.sidebar.number_input('Rolling Window Size (Days)', min_value=1, value=252,
|
| 368 |
help="Enter the number of days to use for the rolling window in the selected risk indicator calculation.")
|
| 369 |
|
| 370 |
-
|
| 371 |
# Fetch data
|
| 372 |
if 'data' not in st.session_state or st.sidebar.button('Fetch Data'):
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
""
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
"""
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
st.
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
st.
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
st.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
st.markdown("""
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
'
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
st.
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
st.markdown("""
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
""
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
""
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
""
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
st.
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
st.markdown("""
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
""")
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
fig = plot_rolling_stability(data, window_size)
|
| 837 |
-
st.plotly_chart(fig)
|
| 838 |
-
|
| 839 |
-
elif selected == "Rolling Maximum Drawdown":
|
| 840 |
-
st.markdown("""
|
| 841 |
-
### Rolling Maximum Drawdown
|
| 842 |
-
|
| 843 |
-
This method calculates the rolling maximum drawdown.
|
| 844 |
-
""")
|
| 845 |
-
|
| 846 |
-
with st.expander("Methodology", expanded=False):
|
| 847 |
-
st.markdown("""
|
| 848 |
-
1. **Calculate Returns:**
|
| 849 |
-
- Compute the daily returns of the stock:
|
| 850 |
-
""")
|
| 851 |
-
st.latex(r'''
|
| 852 |
-
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 853 |
-
''')
|
| 854 |
-
st.markdown("""
|
| 855 |
-
where `(P_t)` is the closing price at time `(t)`.
|
| 856 |
-
|
| 857 |
-
2. **Calculate Rolling Maximum Drawdown:**
|
| 858 |
-
- Compute the cumulative returns and the maximum drawdown over a specified window:
|
| 859 |
-
""")
|
| 860 |
-
st.latex(r'''
|
| 861 |
-
\text{Cumulative Return}_t = \prod_{i=1}^{t} (1 + \text{Return}_i)
|
| 862 |
-
''')
|
| 863 |
-
st.latex(r'''
|
| 864 |
-
\text{Max Drawdown}_t = \frac{\text{Cumulative Return}_t - \text{Rolling Max Cumulative Return}_t}{\text{Rolling Max Cumulative Return}_t}
|
| 865 |
-
''')
|
| 866 |
-
st.markdown("""
|
| 867 |
-
where the Rolling Max Cumulative Return is the maximum cumulative return over the window.
|
| 868 |
-
""")
|
| 869 |
-
|
| 870 |
-
fig = plot_rolling_drawdown(data, window_size)
|
| 871 |
-
st.plotly_chart(fig)
|
| 872 |
-
|
| 873 |
-
elif selected == "Rolling Capture Ratios":
|
| 874 |
-
st.markdown("""
|
| 875 |
-
### Rolling Capture Ratios
|
| 876 |
-
|
| 877 |
-
This method calculates the rolling upside and downside capture ratios.
|
| 878 |
-
""")
|
| 879 |
-
|
| 880 |
-
with st.expander("Methodology", expanded=False):
|
| 881 |
-
st.markdown("""
|
| 882 |
-
1. **Calculate Returns:**
|
| 883 |
-
- Compute the daily returns of the stock and the benchmark:
|
| 884 |
-
""")
|
| 885 |
-
st.latex(r'''
|
| 886 |
-
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 887 |
-
''')
|
| 888 |
-
st.markdown("""
|
| 889 |
-
where `(P_t)` is the closing price at time `(t)`.
|
| 890 |
-
|
| 891 |
-
2. **Calculate Upside Capture Ratio:**
|
| 892 |
-
- Compute the ratio of the sum of the stock's returns to the sum of the benchmark's returns during periods when the benchmark's returns are positive:
|
| 893 |
-
""")
|
| 894 |
-
st.latex(r'''
|
| 895 |
-
\text{Upside Capture Ratio}_t = \frac{\sum_{i=1}^{t} \text{Return}_{\text{stock}, i}}{\sum_{i=1}^{t} \text{Return}_{\text{benchmark}, i}}, \quad \text{if } \text{Return}_{\text{benchmark}, i} > 0
|
| 896 |
-
''')
|
| 897 |
-
st.markdown("""
|
| 898 |
-
|
| 899 |
-
3. **Calculate Downside Capture Ratio:**
|
| 900 |
-
- Compute the ratio of the sum of the stock's returns to the sum of the benchmark's returns during periods when the benchmark's returns are negative:
|
| 901 |
-
""")
|
| 902 |
-
st.latex(r'''
|
| 903 |
-
\text{Downside Capture Ratio}_t = \frac{\sum_{i=1}^{t} \text{Return}_{\text{stock}, i}}{\sum_{i=1}^{t} \text{Return}_{\text{benchmark}, i}}, \quad \text{if } \text{Return}_{\text{benchmark}, i} < 0
|
| 904 |
-
''')
|
| 905 |
-
|
| 906 |
-
fig = plot_rolling_capture(data, market_data, window_size)
|
| 907 |
-
st.plotly_chart(fig)
|
| 908 |
-
|
| 909 |
-
elif selected == "Rolling Pain Index":
|
| 910 |
-
st.markdown("""
|
| 911 |
-
### Rolling Pain Index
|
| 912 |
-
|
| 913 |
-
This method calculates the rolling pain index.
|
| 914 |
-
""")
|
| 915 |
-
|
| 916 |
-
with st.expander("Methodology", expanded=False):
|
| 917 |
-
st.markdown("""
|
| 918 |
-
1. **Calculate Returns:**
|
| 919 |
-
- Compute the daily returns of the stock:
|
| 920 |
-
""")
|
| 921 |
-
st.latex(r'''
|
| 922 |
-
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 923 |
-
''')
|
| 924 |
-
st.markdown("""
|
| 925 |
-
where `(P_t)` is the closing price at time `(t)`.
|
| 926 |
-
|
| 927 |
-
2. **Calculate Cumulative Returns:**
|
| 928 |
-
- Compute the cumulative returns over time:
|
| 929 |
-
""")
|
| 930 |
-
st.latex(r'''
|
| 931 |
-
\text{Cumulative Return}_t = \prod_{i=1}^{t} (1 + \text{Return}_i)
|
| 932 |
-
''')
|
| 933 |
-
st.markdown("""
|
| 934 |
-
|
| 935 |
-
3. **Calculate Drawdowns:**
|
| 936 |
-
- Determine the drawdowns by comparing the cumulative returns to their running maximum:
|
| 937 |
-
""")
|
| 938 |
-
st.latex(r'''
|
| 939 |
-
\text{Drawdown}_t = \frac{\text{Cumulative Return}_t - \text{Running Max Cumulative Return}_t}{\text{Running Max Cumulative Return}_t}
|
| 940 |
-
''')
|
| 941 |
-
st.markdown("""
|
| 942 |
-
|
| 943 |
-
4. **Calculate Rolling Pain Index:**
|
| 944 |
-
- Compute the average drawdown over a specified window where the drawdown is negative:
|
| 945 |
-
""")
|
| 946 |
-
st.latex(r'''
|
| 947 |
-
\text{Pain Index} = \frac{1}{n} \sum_{i=1}^{n} \text{Drawdown}_i \quad \text{for } \text{Drawdown}_i < 0
|
| 948 |
-
''')
|
| 949 |
-
|
| 950 |
-
fig = plot_rolling_pain_index(data, window_size)
|
| 951 |
-
st.plotly_chart(fig)
|
| 952 |
|
| 953 |
hide_streamlit_style = """
|
| 954 |
<style>
|
|
@@ -956,4 +844,4 @@ hide_streamlit_style = """
|
|
| 956 |
footer {visibility: hidden;}
|
| 957 |
</style>
|
| 958 |
"""
|
| 959 |
-
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
|
|
|
| 1 |
import yfinance as yf
|
| 2 |
import numpy as np
|
| 3 |
import pandas as pd
|
|
|
|
| 4 |
import streamlit as st
|
| 5 |
from sklearn.linear_model import RANSACRegressor, LinearRegression
|
| 6 |
from scipy.stats import linregress
|
| 7 |
import plotly.graph_objects as go
|
| 8 |
from plotly.subplots import make_subplots
|
| 9 |
+
import datetime
|
| 10 |
|
| 11 |
# Helper function to fetch stock data
|
| 12 |
def fetch_stock_data(ticker: str, start_date: str, end_date: str) -> pd.DataFrame:
|
| 13 |
+
"""Fetch stock data from Yahoo Finance."""
|
| 14 |
+
data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False)
|
| 15 |
+
if isinstance(data.columns, pd.MultiIndex):
|
| 16 |
+
data.columns = data.columns.get_level_values(0)
|
| 17 |
+
return data
|
| 18 |
|
| 19 |
# Helper function to plot rolling volatility and volatility of volatility
|
| 20 |
def plot_rolling_volatility(data: pd.DataFrame, window: int) -> go.Figure:
|
|
|
|
| 54 |
|
| 55 |
# Align indices
|
| 56 |
market_data = market_data.reindex(data.index, method='ffill')
|
| 57 |
+
aligned_data = pd.concat([data['Return'], market_data['Return']], axis=1).dropna()
|
| 58 |
|
| 59 |
+
covariance = aligned_data['Return'].rolling(window=window).cov(aligned_data['Return'].rename('Market_Return'))
|
| 60 |
+
variance = aligned_data['Return'].rolling(window=window).var()
|
| 61 |
rolling_beta = covariance / variance
|
| 62 |
+
avg_rolling_returns = aligned_data['Return'].rolling(window=window).mean()
|
| 63 |
data['Rolling_Treynor_Ratio'] = (avg_rolling_returns - risk_free_rate) / rolling_beta
|
| 64 |
|
| 65 |
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
|
|
|
| 74 |
|
| 75 |
# Helper function to calculate and plot rolling beta
|
| 76 |
def plot_rolling_beta(data: pd.DataFrame, market_data: pd.DataFrame, window: int) -> go.Figure:
|
| 77 |
+
data['Return'] = data['Close'].pct_change()
|
| 78 |
+
market_data['Market_Return'] = market_data['Close'].pct_change()
|
| 79 |
|
| 80 |
# Align dates and remove rows with missing data
|
| 81 |
aligned_data = pd.concat([data['Return'], market_data['Market_Return']], axis=1).dropna()
|
|
|
|
| 85 |
rolling_beta_ransac = []
|
| 86 |
rolling_beta_ols = []
|
| 87 |
|
| 88 |
+
for i in range(len(aligned_data) - window + 1):
|
| 89 |
X = aligned_data['Market_Return'].iloc[i:i+window].values.reshape(-1, 1)
|
| 90 |
y = aligned_data['Return'].iloc[i:i+window].values
|
| 91 |
|
|
|
|
| 98 |
rolling_beta_ols.append(beta_ols)
|
| 99 |
|
| 100 |
# Convert lists to series with appropriate index
|
| 101 |
+
rolling_beta_ransac = pd.Series(rolling_beta_ransac, index=aligned_data.index[window-1:])
|
| 102 |
+
rolling_beta_ols = pd.Series(rolling_beta_ols, index=aligned_data.index[window-1:])
|
| 103 |
|
| 104 |
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 105 |
subplot_titles=('Close Price', 'Benchmark Price', 'Rolling Beta (RANSAC & OLS)'))
|
|
|
|
| 117 |
data['Return'] = data['Close'].pct_change()
|
| 118 |
market_data['Return'] = market_data['Close'].pct_change()
|
| 119 |
|
| 120 |
+
# Align indices
|
| 121 |
+
market_data = market_data.reindex(data.index, method='ffill')
|
| 122 |
+
aligned_data = pd.concat([data['Return'], market_data['Return']], axis=1).dropna()
|
| 123 |
|
| 124 |
+
rolling_alpha = []
|
| 125 |
+
for i in range(len(aligned_data) - window + 1):
|
| 126 |
+
window_stock = aligned_data['Return'].iloc[i:i + window]
|
| 127 |
+
window_market = aligned_data['Return'].iloc[i:i + window]
|
| 128 |
beta = window_stock.cov(window_market) / window_market.var()
|
| 129 |
expected_return = risk_free_rate + beta * (window_market.mean() - risk_free_rate)
|
| 130 |
alpha = window_stock.mean() - expected_return
|
| 131 |
rolling_alpha.append(alpha)
|
| 132 |
|
| 133 |
+
rolling_alpha = pd.Series(rolling_alpha, index=aligned_data.index[window-1:])
|
| 134 |
|
| 135 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 136 |
subplot_titles=('Close Price', 'Rolling Jensen\'s Alpha'))
|
|
|
|
| 164 |
data['Return'] = data['Close'].pct_change()
|
| 165 |
|
| 166 |
def conditional_var(x, alpha=0.05):
|
| 167 |
+
var = np.percentile(x.dropna(), alpha * 100)
|
| 168 |
+
return np.mean(x[x < var]) if len(x[x < var]) > 0 else np.nan
|
| 169 |
|
| 170 |
rolling_cvar_95 = data['Return'].rolling(window).apply(conditional_var, raw=True).dropna()
|
| 171 |
rolling_cvar_90 = data['Return'].rolling(window).apply(lambda x: conditional_var(x, alpha=0.1), raw=True).dropna()
|
|
|
|
| 185 |
# Helper function to plot rolling Tail Ratio
|
| 186 |
def plot_rolling_tail_ratio(data: pd.DataFrame, window: int) -> go.Figure:
|
| 187 |
data['Return'] = data['Close'].pct_change()
|
| 188 |
+
tail_ratio = data['Return'].rolling(window).apply(lambda x: np.abs(np.percentile(x.dropna(), 95)) / np.abs(np.percentile(x.dropna(), 5)) if len(x.dropna()) > 0 else np.nan).dropna()
|
| 189 |
|
| 190 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 191 |
subplot_titles=('Close Price', 'Rolling Tail Ratio'))
|
|
|
|
| 201 |
def plot_rolling_omega(data: pd.DataFrame, window: int) -> go.Figure:
|
| 202 |
data['Return'] = data['Close'].pct_change()
|
| 203 |
MAR = 0 # Minimum Acceptable Return
|
| 204 |
+
omega_ratio = data['Return'].rolling(window).apply(lambda x: np.sum(x[x > MAR] - MAR) / np.sum(MAR - x[x < MAR]) if np.sum(x[x < MAR]) != 0 else np.inf).dropna()
|
| 205 |
|
| 206 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 207 |
subplot_titles=('Close Price', 'Rolling Omega Ratio'))
|
|
|
|
| 216 |
# Helper function to plot rolling Sortino Ratio
|
| 217 |
def plot_rolling_sortino(data: pd.DataFrame, window: int, MAR: float) -> go.Figure:
|
| 218 |
data['Return'] = data['Close'].pct_change()
|
| 219 |
+
sortino_ratio = data['Return'].rolling(window).apply(lambda x: np.sqrt(252) * np.mean(x - MAR) / np.sqrt(np.mean(np.minimum(0, x - MAR) ** 2)) if np.mean(np.minimum(0, x - MAR) ** 2) > 0 else np.inf).dropna()
|
| 220 |
|
| 221 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 222 |
subplot_titles=('Close Price', 'Rolling Sortino Ratio'))
|
|
|
|
| 230 |
# Helper function to plot rolling Calmar Ratio
|
| 231 |
def plot_rolling_calmar(data: pd.DataFrame, window: int) -> go.Figure:
|
| 232 |
data['Return'] = data['Close'].pct_change()
|
| 233 |
+
calmar_ratio = data['Return'].rolling(window).apply(lambda x: (1 + x).prod() ** (252.0 / len(x)) / np.abs(np.min((1 + x).cumprod() / (1 + x).cumprod().cummax()) - 1) if np.min((1 + x).cumprod() / (1 + x).cumprod().cummax()) < 1 else np.inf).dropna()
|
| 234 |
|
| 235 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 236 |
subplot_titles=('Close Price', 'Rolling Calmar Ratio'))
|
|
|
|
| 244 |
# Helper function to plot rolling stability
|
| 245 |
def plot_rolling_stability(data: pd.DataFrame, window: int) -> go.Figure:
|
| 246 |
data['Return'] = data['Close'].pct_change()
|
| 247 |
+
stability = data['Return'].rolling(window).apply(lambda x: np.std(np.log1p(x.dropna()).cumsum() - linregress(np.arange(len(x.dropna())), np.log1p(x.dropna()).cumsum()).intercept - linregress(np.arange(len(x.dropna())), np.log1p(x.dropna()).cumsum()).slope * np.arange(len(x.dropna()))) if len(x.dropna()) > 1 else np.nan).dropna()
|
| 248 |
|
| 249 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 250 |
subplot_titles=('Close Price', 'Rolling Stability'))
|
|
|
|
| 278 |
|
| 279 |
# Align indices
|
| 280 |
market_data = market_data.reindex(data.index, method='ffill')
|
| 281 |
+
aligned_data = pd.concat([data['Return'], market_data['Return']], axis=1).dropna()
|
| 282 |
|
| 283 |
def calculate_capture(stock_returns, market_returns, is_upside=True):
|
| 284 |
if is_upside:
|
|
|
|
| 287 |
else:
|
| 288 |
relevant_returns = stock_returns[market_returns < 0]
|
| 289 |
relevant_market_returns = market_returns[market_returns < 0]
|
| 290 |
+
return relevant_returns.sum() / relevant_market_returns.sum() if len(relevant_market_returns) > 0 else np.nan
|
| 291 |
|
| 292 |
def compute_rolling_captures(stock_returns, market_returns, window):
|
| 293 |
upside_captures = []
|
|
|
|
| 307 |
upside_captures.append(upside)
|
| 308 |
downside_captures.append(downside)
|
| 309 |
|
| 310 |
+
# Padding the initial values with NaNs to match index length
|
| 311 |
nan_padding = [np.nan] * (window - 1)
|
| 312 |
+
upside_captures = pd.Series(nan_padding + upside_captures, index=stock_returns.index)
|
| 313 |
+
downside_captures = pd.Series(nan_padding + downside_captures, index=stock_returns.index)
|
| 314 |
|
| 315 |
return upside_captures, downside_captures
|
| 316 |
|
| 317 |
data['rolling_upside_capture'], data['rolling_downside_capture'] = compute_rolling_captures(
|
| 318 |
+
aligned_data['Return'], aligned_data['Return'].rename('Market_Return'), window
|
| 319 |
)
|
| 320 |
|
| 321 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
|
|
|
| 334 |
cumulative_return = (1 + data['Return']).cumprod()
|
| 335 |
running_max = cumulative_return.cummax()
|
| 336 |
drawdown = (cumulative_return - running_max) / running_max
|
| 337 |
+
pain_index = drawdown.rolling(window).apply(lambda x: np.mean(x[x < 0]) if len(x[x < 0]) > 0 else 0).dropna()
|
| 338 |
|
| 339 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1,
|
| 340 |
subplot_titles=('Close Price', 'Rolling Pain Index'))
|
|
|
|
| 351 |
|
| 352 |
st.sidebar.title("Input Parameters")
|
| 353 |
|
|
|
|
|
|
|
|
|
|
| 354 |
# Setting today's date plus one day
|
| 355 |
today_plus_one = pd.to_datetime(datetime.datetime.now().date() + pd.Timedelta(days=1))
|
| 356 |
|
|
|
|
| 373 |
window_size = st.sidebar.number_input('Rolling Window Size (Days)', min_value=1, value=252,
|
| 374 |
help="Enter the number of days to use for the rolling window in the selected risk indicator calculation.")
|
| 375 |
|
|
|
|
| 376 |
# Fetch data
|
| 377 |
if 'data' not in st.session_state or st.sidebar.button('Fetch Data'):
|
| 378 |
+
data = fetch_stock_data(ticker, start_date, end_date)
|
| 379 |
+
if data.empty:
|
| 380 |
+
st.error(f"No data returned for {ticker} from {start_date} to {end_date}")
|
| 381 |
+
else:
|
| 382 |
+
st.session_state.data = data
|
| 383 |
+
|
| 384 |
+
if 'data' in st.session_state and not st.session_state.data.empty:
|
| 385 |
+
data = st.session_state.data
|
| 386 |
+
|
| 387 |
+
# Additional input for methods requiring benchmark or risk-free rate
|
| 388 |
+
if selected in ["Rolling Treynor Ratio", "Rolling Beta", "Rolling Jensen's Alpha", "Rolling Capture Ratios"]:
|
| 389 |
+
benchmark_ticker = st.sidebar.text_input('Enter Benchmark Ticker', '^GSPC',
|
| 390 |
+
help="Enter the ticker symbol for the benchmark index (e.g., ^GSPC for S&P 500).")
|
| 391 |
+
if 'market_data' not in st.session_state or st.sidebar.button('Fetch Market Data'):
|
| 392 |
+
market_data = fetch_stock_data(benchmark_ticker, start_date, end_date)
|
| 393 |
+
if market_data.empty:
|
| 394 |
+
st.error(f"No data returned for {benchmark_ticker} from {start_date} to {end_date}")
|
| 395 |
+
else:
|
| 396 |
+
st.session_state.market_data = market_data
|
| 397 |
+
if 'market_data' in st.session_state and not st.session_state.market_data.empty:
|
| 398 |
+
market_data = st.session_state.market_data
|
| 399 |
+
|
| 400 |
+
if selected in ["Rolling Sharpe Ratio", "Rolling Treynor Ratio", "Rolling Jensen's Alpha", "Rolling Sortino Ratio"]:
|
| 401 |
+
risk_free_rate = st.sidebar.number_input('Risk-Free Rate (as a decimal)', min_value=0.0, value=0.0,
|
| 402 |
+
help="Enter the risk-free rate as a decimal (e.g., 0.01 for 1%).")
|
| 403 |
+
|
| 404 |
+
if selected == "Rolling Sortino Ratio":
|
| 405 |
+
MAR = st.sidebar.number_input('Minimum Acceptable Return (MAR, as a decimal)', min_value=0.0, value=0.0,
|
| 406 |
+
help="Enter the Minimum Acceptable Return (MAR) as a decimal (e.g., 0.02 for 2%).")
|
| 407 |
+
|
| 408 |
+
# Display results based on the selected method
|
| 409 |
+
if selected == "Rolling Volatility":
|
| 410 |
+
st.markdown("""
|
| 411 |
+
### Rolling Volatility
|
| 412 |
+
This method calculates the rolling volatility and the volatility of volatility.
|
| 413 |
+
""")
|
| 414 |
+
with st.expander("Methodology", expanded=False):
|
| 415 |
+
st.markdown("""
|
| 416 |
+
1. **Calculate Returns:**
|
| 417 |
+
- Compute the daily returns of the stock:
|
| 418 |
+
""")
|
| 419 |
+
st.latex(r'''
|
| 420 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 421 |
+
''')
|
| 422 |
+
st.markdown("""
|
| 423 |
+
where `(P_t)` is the closing price at time `(t)`.
|
| 424 |
+
2. **Calculate Rolling Volatility:**
|
| 425 |
+
- Compute the rolling standard deviation of the returns over a specified window and annualize it:
|
| 426 |
+
""")
|
| 427 |
+
st.latex(r'''
|
| 428 |
+
\text{Rolling Volatility}_t = \sqrt{252} \times \text{std}(\text{Return}_{t-n:t})
|
| 429 |
+
''')
|
| 430 |
+
st.markdown("""
|
| 431 |
+
where `(n)` is the window size.
|
| 432 |
+
3. **Calculate Volatility of Volatility:**
|
| 433 |
+
- Compute the rolling standard deviation of the rolling volatility over the specified window:
|
| 434 |
+
""")
|
| 435 |
+
st.latex(r'''
|
| 436 |
+
\text{Volatility of Volatility}_t = \text{std}(\text{Rolling Volatility}_{t-n:t})
|
| 437 |
+
''')
|
| 438 |
+
fig = plot_rolling_volatility(data, window_size)
|
| 439 |
+
st.plotly_chart(fig)
|
| 440 |
+
|
| 441 |
+
elif selected == "Rolling Sharpe Ratio":
|
| 442 |
+
st.markdown("""
|
| 443 |
+
### Rolling Sharpe Ratio
|
| 444 |
+
This method calculates the rolling Sharpe Ratio.
|
| 445 |
+
""")
|
| 446 |
+
with st.expander("Methodology", expanded=False):
|
| 447 |
+
st.markdown("""
|
| 448 |
+
1. **Calculate Returns:**
|
| 449 |
+
- Compute the daily returns of the stock:
|
| 450 |
+
""")
|
| 451 |
+
st.latex(r'''
|
| 452 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 453 |
+
''')
|
| 454 |
+
st.markdown("""
|
| 455 |
+
where `(P_t)` is the closing price at time `(t)`.
|
| 456 |
+
2. **Calculate Rolling Average Return:**
|
| 457 |
+
- Compute the rolling mean of the returns over the specified window:
|
| 458 |
+
""")
|
| 459 |
+
st.latex(r'''
|
| 460 |
+
\text{Rolling Average Return}_t = \text{mean}(\text{Return}_{t-n:t})
|
| 461 |
+
''')
|
| 462 |
+
st.markdown("""
|
| 463 |
+
where `(n)` is the window size.
|
| 464 |
+
3. **Calculate Rolling Standard Deviation:**
|
| 465 |
+
- Compute the rolling standard deviation of the returns over the specified window:
|
| 466 |
+
""")
|
| 467 |
+
st.latex(r'''
|
| 468 |
+
\text{Rolling Std Dev}_t = \text{std}(\text{Return}_{t-n:t})
|
| 469 |
+
''')
|
| 470 |
+
st.markdown("""
|
| 471 |
+
4. **Calculate Rolling Sharpe Ratio:**
|
| 472 |
+
- Annualize the Sharpe Ratio:
|
| 473 |
+
""")
|
| 474 |
+
st.latex(r'''
|
| 475 |
+
\text{Rolling Sharpe Ratio}_t = \frac{\text{Rolling Average Return}_t - R_f}{\text{Rolling Std Dev}_t} \times \sqrt{252}
|
| 476 |
+
''')
|
| 477 |
+
st.markdown("""
|
| 478 |
+
where `(R_f)` is the risk-free rate.
|
| 479 |
+
""")
|
| 480 |
+
fig = plot_rolling_sharpe(data, window_size, risk_free_rate)
|
| 481 |
+
st.plotly_chart(fig)
|
| 482 |
+
|
| 483 |
+
elif selected == "Rolling Treynor Ratio" and 'market_data' in st.session_state:
|
| 484 |
+
st.markdown("""
|
| 485 |
+
### Rolling Treynor Ratio
|
| 486 |
+
This method calculates the rolling Treynor Ratio.
|
| 487 |
+
""")
|
| 488 |
+
with st.expander("Methodology", expanded=False):
|
| 489 |
+
st.markdown("""
|
| 490 |
+
1. **Calculate Returns:**
|
| 491 |
+
- Compute the daily returns of the stock and the benchmark:
|
| 492 |
+
""")
|
| 493 |
+
st.latex(r'''
|
| 494 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 495 |
+
''')
|
| 496 |
+
st.markdown("""
|
| 497 |
+
2. **Calculate Beta:**
|
| 498 |
+
- Compute the rolling covariance between the stock and benchmark returns, and divide by the rolling variance of the benchmark returns to get the rolling beta:
|
| 499 |
+
""")
|
| 500 |
+
st.latex(r'''
|
| 501 |
+
\beta_t = \frac{\text{Cov}(\text{Return}_{\text{stock}, t}, \text{Return}_{\text{benchmark}, t})}{\text{Var}(\text{Return}_{\text{benchmark}, t})}
|
| 502 |
+
''')
|
| 503 |
+
st.markdown("""
|
| 504 |
+
3. **Calculate Average Rolling Returns:**
|
| 505 |
+
- Compute the rolling mean of the stock returns over the same window:
|
| 506 |
+
""")
|
| 507 |
+
st.latex(r'''
|
| 508 |
+
\text{Average Rolling Return}_t = \frac{1}{n} \sum_{i=t-n+1}^{t} \text{Return}_{\text{stock}, i}
|
| 509 |
+
''')
|
| 510 |
+
st.markdown("""
|
| 511 |
+
4. **Calculate Treynor Ratio:**
|
| 512 |
+
- Compute the Treynor Ratio using the risk-free rate `(R_f)`:
|
| 513 |
+
""")
|
| 514 |
+
st.latex(r'''
|
| 515 |
+
\text{Treynor Ratio}_t = \frac{\text{Average Rolling Return}_t - R_f}{\beta_t}
|
| 516 |
+
''')
|
| 517 |
+
fig = plot_rolling_treynor(data, market_data, window_size, risk_free_rate)
|
| 518 |
+
st.plotly_chart(fig)
|
| 519 |
+
|
| 520 |
+
elif selected == "Rolling Beta" and 'market_data' in st.session_state:
|
| 521 |
+
st.markdown("""
|
| 522 |
+
### Rolling Beta
|
| 523 |
+
This method calculates the rolling beta of a stock's returns against a benchmark using RANSAC and OLS methods.
|
| 524 |
+
""")
|
| 525 |
+
with st.expander("Methodology", expanded=False):
|
| 526 |
+
st.markdown("""
|
| 527 |
+
1. **Calculate Returns:**
|
| 528 |
+
- Compute the daily returns of the stock and the benchmark:
|
| 529 |
+
""")
|
| 530 |
+
st.latex(r'''
|
| 531 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 532 |
+
''')
|
| 533 |
+
st.markdown("""
|
| 534 |
+
2. **Calculate Rolling Beta using OLS:**
|
| 535 |
+
- Perform a linear regression of the stock returns against the benchmark returns over a specified window:
|
| 536 |
+
""")
|
| 537 |
+
st.latex(r'''
|
| 538 |
+
\beta_{OLS} = \frac{\text{Cov}(\text{Return}_{\text{stock}}, \text{Return}_{\text{benchmark}})}{\text{Var}(\text{Return}_{\text{benchmark}})}
|
| 539 |
+
''')
|
| 540 |
+
st.markdown("""
|
| 541 |
+
3. **Calculate Rolling Beta using RANSAC:**
|
| 542 |
+
- Use the RANSAC algorithm to robustly estimate the beta, reducing the influence of outliers:
|
| 543 |
+
""")
|
| 544 |
+
st.latex(r'''
|
| 545 |
+
\beta_{RANSAC} = \text{RANSAC}(\text{Return}_{\text{benchmark}}, \text{Return}_{\text{stock}})
|
| 546 |
+
''')
|
| 547 |
+
fig = plot_rolling_beta(data, market_data, window_size)
|
| 548 |
+
st.plotly_chart(fig)
|
| 549 |
+
|
| 550 |
+
elif selected == "Rolling Jensen's Alpha" and 'market_data' in st.session_state:
|
| 551 |
+
st.markdown("""
|
| 552 |
+
### Rolling Jensen's Alpha
|
| 553 |
+
This method calculates the rolling Jensen's Alpha.
|
| 554 |
+
""")
|
| 555 |
+
with st.expander("Methodology", expanded=False):
|
| 556 |
+
st.markdown("""
|
| 557 |
+
1. **Calculate Returns:**
|
| 558 |
+
- Compute the daily returns of the stock and the benchmark:
|
| 559 |
+
""")
|
| 560 |
+
st.latex(r'''
|
| 561 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 562 |
+
''')
|
| 563 |
+
st.markdown("""
|
| 564 |
+
2. **Calculate Beta:**
|
| 565 |
+
- Compute the rolling beta of the stock returns against the benchmark returns over a specified window:
|
| 566 |
+
""")
|
| 567 |
+
st.latex(r'''
|
| 568 |
+
\beta_t = \frac{\text{Cov}(\text{Return}_{\text{stock}, t}, \text{Return}_{\text{benchmark}, t})}{\text{Var}(\text{Return}_{\text{benchmark}, t})}
|
| 569 |
+
''')
|
| 570 |
+
st.markdown("""
|
| 571 |
+
3. **Calculate Expected Return:**
|
| 572 |
+
- Compute the expected return of the stock based on the CAPM model:
|
| 573 |
+
""")
|
| 574 |
+
st.latex(r'''
|
| 575 |
+
\text{Expected Return}_t = R_f + \beta_t (\text{Return}_{\text{benchmark}} - R_f)
|
| 576 |
+
''')
|
| 577 |
+
st.markdown("""
|
| 578 |
+
4. **Calculate Jensen's Alpha:**
|
| 579 |
+
- Compute the Jensen's Alpha as the difference between the actual return and the expected return:
|
| 580 |
+
""")
|
| 581 |
+
st.latex(r'''
|
| 582 |
+
\alpha_t = \text{Return}_{\text{stock}, t} - \text{Expected Return}_t
|
| 583 |
+
''')
|
| 584 |
+
fig = plot_rolling_alpha(data, market_data, window_size, risk_free_rate)
|
| 585 |
+
st.plotly_chart(fig)
|
| 586 |
+
|
| 587 |
+
elif selected == "Rolling Value at Risk":
|
| 588 |
+
st.markdown("""
|
| 589 |
+
### Rolling Value at Risk (VaR)
|
| 590 |
+
This method calculates the rolling Value at Risk (VaR) at different confidence levels.
|
| 591 |
+
""")
|
| 592 |
+
with st.expander("Methodology", expanded=False):
|
| 593 |
+
st.markdown("""
|
| 594 |
+
1. **Calculate Returns:**
|
| 595 |
+
- Compute the daily returns of the stock:
|
| 596 |
+
""")
|
| 597 |
+
st.latex(r'''
|
| 598 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 599 |
+
''')
|
| 600 |
+
st.markdown("""
|
| 601 |
+
2. **Calculate Rolling VaR:**
|
| 602 |
+
- Compute the rolling quantile of the returns over a specified window for different confidence levels:
|
| 603 |
+
""")
|
| 604 |
+
st.latex(r'''
|
| 605 |
+
\text{VaR}_{\alpha, t} = \text{Quantile}_{\alpha}(\text{Return}_{t-n:t})
|
| 606 |
+
''')
|
| 607 |
+
fig = plot_rolling_var(data, window_size)
|
| 608 |
+
st.plotly_chart(fig)
|
| 609 |
+
|
| 610 |
+
elif selected == "Rolling Conditional VaR":
|
| 611 |
+
st.markdown("""
|
| 612 |
+
### Rolling Conditional Value at Risk (CVaR)
|
| 613 |
+
This method calculates the rolling Conditional Value at Risk (CVaR) at different confidence levels.
|
| 614 |
+
""")
|
| 615 |
+
with st.expander("Methodology", expanded=False):
|
| 616 |
+
st.markdown("""
|
| 617 |
+
1. **Calculate Returns:**
|
| 618 |
+
- Compute the daily returns of the stock:
|
| 619 |
+
""")
|
| 620 |
+
st.latex(r'''
|
| 621 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 622 |
+
''')
|
| 623 |
+
st.markdown("""
|
| 624 |
+
2. **Calculate Rolling CVaR:**
|
| 625 |
+
- Compute the average of the returns that are below the VaR threshold over a specified window:
|
| 626 |
+
""")
|
| 627 |
+
st.latex(r'''
|
| 628 |
+
\text{CVaR}_{\alpha, t} = \frac{1}{n} \sum_{i=1}^{n} \text{Return}_{i} \text{ where } \text{Return}_{i} < \text{VaR}_{\alpha, t}
|
| 629 |
+
''')
|
| 630 |
+
fig = plot_rolling_cvar(data, window_size)
|
| 631 |
+
st.plotly_chart(fig)
|
| 632 |
+
|
| 633 |
+
elif selected == "Rolling Tail Ratio":
|
| 634 |
+
st.markdown("""
|
| 635 |
+
### Rolling Tail Ratio
|
| 636 |
+
This method calculates the rolling Tail Ratio.
|
| 637 |
+
""")
|
| 638 |
+
with st.expander("Methodology", expanded=False):
|
| 639 |
+
st.markdown("""
|
| 640 |
+
1. **Calculate Returns:**
|
| 641 |
+
- Compute the daily returns of the stock:
|
| 642 |
+
""")
|
| 643 |
+
st.latex(r'''
|
| 644 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 645 |
+
''')
|
| 646 |
+
st.markdown("""
|
| 647 |
+
2. **Calculate Tail Ratio:**
|
| 648 |
+
- Compute the rolling Tail Ratio over a specified window:
|
| 649 |
+
""")
|
| 650 |
+
st.latex(r'''
|
| 651 |
+
\text{Tail Ratio}_t = \frac{|\text{Quantile}_{95}(\text{Return}_{t-n:t})|}{|\text{Quantile}_{5}(\text{Return}_{t-n:t})|}
|
| 652 |
+
''')
|
| 653 |
+
fig = plot_rolling_tail_ratio(data, window_size)
|
| 654 |
+
st.plotly_chart(fig)
|
| 655 |
+
|
| 656 |
+
elif selected == "Rolling Omega Ratio":
|
| 657 |
+
st.markdown("""
|
| 658 |
+
### Rolling Omega Ratio
|
| 659 |
+
This method calculates the rolling Omega Ratio.
|
| 660 |
+
""")
|
| 661 |
+
with st.expander("Methodology", expanded=False):
|
| 662 |
+
st.markdown("""
|
| 663 |
+
1. **Calculate Returns:**
|
| 664 |
+
- Compute the daily returns of the stock:
|
| 665 |
+
""")
|
| 666 |
+
st.latex(r'''
|
| 667 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 668 |
+
''')
|
| 669 |
+
st.markdown("""
|
| 670 |
+
2. **Calculate Omega Ratio:**
|
| 671 |
+
- Compute the rolling Omega Ratio over a specified window:
|
| 672 |
+
""")
|
| 673 |
+
st.latex(r'''
|
| 674 |
+
\text{Omega Ratio}_t = \frac{\sum (\text{Return}_{i} > MAR) (\text{Return}_{i} - MAR)}{\sum (\text{Return}_{i} < MAR) (MAR - \text{Return}_{i})}
|
| 675 |
+
''')
|
| 676 |
+
fig = plot_rolling_omega(data, window_size)
|
| 677 |
+
st.plotly_chart(fig)
|
| 678 |
+
|
| 679 |
+
elif selected == "Rolling Sortino Ratio":
|
| 680 |
+
st.markdown("""
|
| 681 |
+
### Rolling Sortino Ratio
|
| 682 |
+
This method calculates the rolling Sortino Ratio.
|
| 683 |
+
""")
|
| 684 |
+
with st.expander("Methodology", expanded=False):
|
| 685 |
+
st.markdown("""
|
| 686 |
+
1. **Calculate Returns:**
|
| 687 |
+
- Compute the daily returns of the stock:
|
| 688 |
+
""")
|
| 689 |
+
st.latex(r'''
|
| 690 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 691 |
+
''')
|
| 692 |
+
st.markdown("""
|
| 693 |
+
2. **Calculate Rolling Sortino Ratio:**
|
| 694 |
+
- Compute the rolling Sortino Ratio over a specified window:
|
| 695 |
+
""")
|
| 696 |
+
st.latex(r'''
|
| 697 |
+
\text{Sortino Ratio}_t = \frac{\sqrt{252} \cdot \text{Mean}(\text{Return}_{t-n:t} - MAR)}{\sqrt{\text{Mean}(\min(0, \text{Return}_{t-n:t} - MAR)^2)}}
|
| 698 |
+
''')
|
| 699 |
+
fig = plot_rolling_sortino(data, window_size, MAR)
|
| 700 |
+
st.plotly_chart(fig)
|
| 701 |
+
|
| 702 |
+
elif selected == "Rolling Calmar Ratio":
|
| 703 |
+
st.markdown("""
|
| 704 |
+
### Rolling Calmar Ratio
|
| 705 |
+
This method calculates the rolling Calmar Ratio.
|
| 706 |
+
""")
|
| 707 |
+
with st.expander("Methodology", expanded=False):
|
| 708 |
+
st.markdown("""
|
| 709 |
+
1. **Calculate Returns:**
|
| 710 |
+
- Compute the daily returns of the stock:
|
| 711 |
+
""")
|
| 712 |
+
st.latex(r'''
|
| 713 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 714 |
+
''')
|
| 715 |
+
st.markdown("""
|
| 716 |
+
2. **Calculate Rolling Calmar Ratio:**
|
| 717 |
+
- Compute the rolling Calmar Ratio over a specified window:
|
| 718 |
+
""")
|
| 719 |
+
st.latex(r'''
|
| 720 |
+
\text{Calmar Ratio}_t = \frac{\text{CAGR}_{t-n:t}}{\text{Max Drawdown}_{t-n:t}}
|
| 721 |
+
''')
|
| 722 |
+
fig = plot_rolling_calmar(data, window_size)
|
| 723 |
+
st.plotly_chart(fig)
|
| 724 |
+
|
| 725 |
+
elif selected == "Rolling Stability":
|
| 726 |
+
st.markdown("""
|
| 727 |
+
### Rolling Stability
|
| 728 |
+
This method calculates the rolling stability of returns.
|
| 729 |
+
""")
|
| 730 |
+
with st.expander("Methodology", expanded=False):
|
| 731 |
+
st.markdown("""
|
| 732 |
+
1. **Calculate Returns:**
|
| 733 |
+
- Compute the daily returns of the stock:
|
| 734 |
+
""")
|
| 735 |
+
st.latex(r'''
|
| 736 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 737 |
+
''')
|
| 738 |
+
st.markdown("""
|
| 739 |
+
2. **Calculate Rolling Stability:**
|
| 740 |
+
- Compute the rolling stability over a specified window:
|
| 741 |
+
""")
|
| 742 |
+
st.latex(r'''
|
| 743 |
+
\text{Stability}_t = \sqrt{\frac{1}{n-1} \sum_{i=1}^{n} (\log(1 + \text{Return}_{t-i}) - \overline{\log(1 + \text{Return})})^2}
|
| 744 |
+
''')
|
| 745 |
+
fig = plot_rolling_stability(data, window_size)
|
| 746 |
+
st.plotly_chart(fig)
|
| 747 |
+
|
| 748 |
+
elif selected == "Rolling Maximum Drawdown":
|
| 749 |
+
st.markdown("""
|
| 750 |
+
### Rolling Maximum Drawdown
|
| 751 |
+
This method calculates the rolling maximum drawdown.
|
| 752 |
+
""")
|
| 753 |
+
with st.expander("Methodology", expanded=False):
|
| 754 |
+
st.markdown("""
|
| 755 |
+
1. **Calculate Returns:**
|
| 756 |
+
- Compute the daily returns of the stock:
|
| 757 |
+
""")
|
| 758 |
+
st.latex(r'''
|
| 759 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 760 |
+
''')
|
| 761 |
+
st.markdown("""
|
| 762 |
+
2. **Calculate Rolling Maximum Drawdown:**
|
| 763 |
+
- Compute the cumulative returns and the maximum drawdown over a specified window:
|
| 764 |
+
""")
|
| 765 |
+
st.latex(r'''
|
| 766 |
+
\text{Cumulative Return}_t = \prod_{i=1}^{t} (1 + \text{Return}_i)
|
| 767 |
+
''')
|
| 768 |
+
st.latex(r'''
|
| 769 |
+
\text{Max Drawdown}_t = \frac{\text{Cumulative Return}_t - \text{Rolling Max Cumulative Return}_t}{\text{Rolling Max Cumulative Return}_t}
|
| 770 |
+
''')
|
| 771 |
+
fig = plot_rolling_drawdown(data, window_size)
|
| 772 |
+
st.plotly_chart(fig)
|
| 773 |
+
|
| 774 |
+
elif selected == "Rolling Capture Ratios" and 'market_data' in st.session_state:
|
| 775 |
+
st.markdown("""
|
| 776 |
+
### Rolling Capture Ratios
|
| 777 |
+
This method calculates the rolling upside and downside capture ratios.
|
| 778 |
+
""")
|
| 779 |
+
with st.expander("Methodology", expanded=False):
|
| 780 |
+
st.markdown("""
|
| 781 |
+
1. **Calculate Returns:**
|
| 782 |
+
- Compute the daily returns of the stock and the benchmark:
|
| 783 |
+
""")
|
| 784 |
+
st.latex(r'''
|
| 785 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 786 |
+
''')
|
| 787 |
+
st.markdown("""
|
| 788 |
+
2. **Calculate Upside Capture Ratio:**
|
| 789 |
+
- Compute the ratio of the sum of the stock's returns to the sum of the benchmark's returns during periods when the benchmark's returns are positive:
|
| 790 |
+
""")
|
| 791 |
+
st.latex(r'''
|
| 792 |
+
\text{Upside Capture Ratio}_t = \frac{\sum_{i=1}^{t} \text{Return}_{\text{stock}, i}}{\sum_{i=1}^{t} \text{Return}_{\text{benchmark}, i}}, \quad \text{if } \text{Return}_{\text{benchmark}, i} > 0
|
| 793 |
+
''')
|
| 794 |
+
st.markdown("""
|
| 795 |
+
3. **Calculate Downside Capture Ratio:**
|
| 796 |
+
- Compute the ratio of the sum of the stock's returns to the sum of the benchmark's returns during periods when the benchmark's returns are negative:
|
| 797 |
+
""")
|
| 798 |
+
st.latex(r'''
|
| 799 |
+
\text{Downside Capture Ratio}_t = \frac{\sum_{i=1}^{t} \text{Return}_{\text{stock}, i}}{\sum_{i=1}^{t} \text{Return}_{\text{benchmark}, i}}, \quad \text{if } \text{Return}_{\text{benchmark}, i} < 0
|
| 800 |
+
''')
|
| 801 |
+
fig = plot_rolling_capture(data, market_data, window_size)
|
| 802 |
+
st.plotly_chart(fig)
|
| 803 |
+
|
| 804 |
+
elif selected == "Rolling Pain Index":
|
| 805 |
+
st.markdown("""
|
| 806 |
+
### Rolling Pain Index
|
| 807 |
+
This method calculates the rolling pain index.
|
| 808 |
+
""")
|
| 809 |
+
with st.expander("Methodology", expanded=False):
|
| 810 |
+
st.markdown("""
|
| 811 |
+
1. **Calculate Returns:**
|
| 812 |
+
- Compute the daily returns of the stock:
|
| 813 |
+
""")
|
| 814 |
+
st.latex(r'''
|
| 815 |
+
\text{Return}_t = \frac{P_t - P_{t-1}}{P_{t-1}}
|
| 816 |
+
''')
|
| 817 |
+
st.markdown("""
|
| 818 |
+
2. **Calculate Cumulative Returns:**
|
| 819 |
+
- Compute the cumulative returns over time:
|
| 820 |
+
""")
|
| 821 |
+
st.latex(r'''
|
| 822 |
+
\text{Cumulative Return}_t = \prod_{i=1}^{t} (1 + \text{Return}_i)
|
| 823 |
+
''')
|
| 824 |
+
st.markdown("""
|
| 825 |
+
3. **Calculate Drawdowns:**
|
| 826 |
+
- Determine the drawdowns by comparing the cumulative returns to their running maximum:
|
| 827 |
+
""")
|
| 828 |
+
st.latex(r'''
|
| 829 |
+
\text{Drawdown}_t = \frac{\text{Cumulative Return}_t - \text{Running Max Cumulative Return}_t}{\text{Running Max Cumulative Return}_t}
|
| 830 |
+
''')
|
| 831 |
+
st.markdown("""
|
| 832 |
+
4. **Calculate Rolling Pain Index:**
|
| 833 |
+
- Compute the average drawdown over a specified window where the drawdown is negative:
|
| 834 |
+
""")
|
| 835 |
+
st.latex(r'''
|
| 836 |
+
\text{Pain Index} = \frac{1}{n} \sum_{i=1}^{n} \text{Drawdown}_i \quad \text{for } \text{Drawdown}_i < 0
|
| 837 |
+
''')
|
| 838 |
+
fig = plot_rolling_pain_index(data, window_size)
|
| 839 |
+
st.plotly_chart(fig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
|
| 841 |
hide_streamlit_style = """
|
| 842 |
<style>
|
|
|
|
| 844 |
footer {visibility: hidden;}
|
| 845 |
</style>
|
| 846 |
"""
|
| 847 |
+
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|