|
6 | 6 | import streamlit as st |
7 | 7 |
|
8 | 8 | st.set_page_config(page_title="Quant Research Starter", layout="wide") |
9 | | -st.title("Quant Research Starter Dashboard") |
10 | | - |
| 9 | +st.title("📊 Quant Research Starter Dashboard") |
11 | 10 | output_dir = Path.cwd() / "output" |
12 | 11 | default_results = output_dir / "backtest_results.json" |
13 | 12 | default_factors = output_dir / "factors.csv" |
14 | 13 |
|
15 | | -st.sidebar.header("Inputs") |
| 14 | +# --- Sidebar Inputs --- |
| 15 | +st.sidebar.header("⚙️ Inputs") |
16 | 16 | results_file = st.sidebar.text_input("Backtest results JSON", str(default_results)) |
17 | 17 | factors_file = st.sidebar.text_input("Factors CSV", str(default_factors)) |
18 | 18 |
|
19 | | -col1, col2 = st.columns(2) |
| 19 | +# --- Main Layout --- |
| 20 | +st.markdown("### Overview") |
| 21 | +st.caption("Visualize portfolio performance and factor signals side-by-side.") |
| 22 | + |
| 23 | +# Create two balanced columns |
| 24 | +col1, col2 = st.columns([1, 1], gap="large") |
20 | 25 |
|
| 26 | +# --- Left: Equity Curve --- |
21 | 27 | with col1: |
22 | | - st.subheader("Equity Curve") |
| 28 | + st.markdown("#### 📈 Equity Curve") |
23 | 29 | if Path(results_file).exists(): |
24 | 30 | with open(results_file) as f: |
25 | 31 | data = json.load(f) |
| 32 | + |
26 | 33 | df = pd.DataFrame( |
27 | 34 | { |
28 | 35 | "date": pd.to_datetime(data["dates"]), |
29 | 36 | "portfolio_value": data["portfolio_value"], |
30 | 37 | } |
31 | 38 | ).set_index("date") |
32 | | - fig = px.line(df, y="portfolio_value", title="Portfolio Value") |
| 39 | + |
| 40 | + fig = px.line( |
| 41 | + df, |
| 42 | + y="portfolio_value", |
| 43 | + title="Portfolio Value Over Time", |
| 44 | + labels={"portfolio_value": "Portfolio Value"}, |
| 45 | + ) |
| 46 | + fig.update_layout( |
| 47 | + margin={"l": 30, "r": 30, "t": 40, "b": 30}, |
| 48 | + height=400, |
| 49 | + title_x=0.5, |
| 50 | + ) |
| 51 | + |
33 | 52 | st.plotly_chart(fig, use_container_width=True) |
34 | | - st.json(data.get("metrics", {})) |
| 53 | + |
| 54 | + # Centered metrics section |
| 55 | + st.markdown("#### 🔍 Summary Metrics") |
| 56 | + metrics = data.get("metrics", {}) |
| 57 | + if metrics: |
| 58 | + mcol1, mcol2, mcol3 = st.columns(3) |
| 59 | + items = list(metrics.items()) |
| 60 | + for i, (key, value) in enumerate(items[:3]): |
| 61 | + with [mcol1, mcol2, mcol3][i]: |
| 62 | + st.metric( |
| 63 | + label=key.replace("_", " ").title(), value=round(value, 4) |
| 64 | + ) |
| 65 | + if len(items) > 3: |
| 66 | + st.json(dict(items[3:])) |
| 67 | + else: |
| 68 | + st.info("No metrics found in results.") |
35 | 69 | else: |
36 | | - st.info("Run the CLI backtest to generate results.") |
| 70 | + st.info("⚠️ Run the CLI backtest to generate results.") |
37 | 71 |
|
| 72 | +# --- Right: Factor Signals --- |
38 | 73 | with col2: |
39 | | - st.subheader("Factor Signals") |
| 74 | + st.markdown("#### 📉 Factor Signals") |
40 | 75 | if Path(factors_file).exists(): |
41 | 76 | fdf = pd.read_csv(factors_file, index_col=0, parse_dates=True) |
42 | | - st.dataframe(fdf.tail()) |
43 | | - if "composite" in fdf.columns: |
44 | | - fig2 = px.line(fdf[["composite"]], title="Composite Signal") |
45 | | - st.plotly_chart(fig2, use_container_width=True) |
| 77 | + |
| 78 | + # Tabs for cleaner organization |
| 79 | + tab1, tab2 = st.tabs(["📑 Latest Data", "📊 Composite Signal"]) |
| 80 | + |
| 81 | + with tab1: |
| 82 | + st.dataframe( |
| 83 | + fdf.tail().style.set_table_styles( |
| 84 | + [{"selector": "th", "props": [("text-align", "center")]}] |
| 85 | + ) |
| 86 | + ) |
| 87 | + |
| 88 | + with tab2: |
| 89 | + if "composite" in fdf.columns: |
| 90 | + fig2 = px.line( |
| 91 | + fdf[["composite"]], |
| 92 | + title="Composite Factor Signal", |
| 93 | + labels={"composite": "Composite"}, |
| 94 | + ) |
| 95 | + fig2.update_layout( |
| 96 | + margin={"l": 30, "r": 30, "t": 40, "b": 30}, |
| 97 | + height=400, |
| 98 | + title_x=0.5, |
| 99 | + ) |
| 100 | + st.plotly_chart(fig2, use_container_width=True) |
| 101 | + else: |
| 102 | + st.info("No composite signal found.") |
46 | 103 | else: |
47 | | - st.info("Compute factors to view signals.") |
| 104 | + st.info("⚠️ Compute factors to view signals.") |
48 | 105 |
|
| 106 | +# --- Footer --- |
49 | 107 | st.markdown("---") |
50 | 108 | st.caption( |
51 | | - "Tip: Use qrs CLI to generate data, factors, and backtest results. Then refresh this page." |
| 109 | + "💡 Tip: Use `qrs` CLI to generate data, factors, and backtest results, then refresh this page." |
52 | 110 | ) |
0 commit comments