Skip to content

Add Student-t distribution and comparison mode to Validation.py - #2

Open
XCODESSS wants to merge 1 commit into
mainfrom
validation-update-student-t-3993904861077718799
Open

Add Student-t distribution and comparison mode to Validation.py#2
XCODESSS wants to merge 1 commit into
mainfrom
validation-update-student-t-3993904861077718799

Conversation

@XCODESSS

@XCODESSS XCODESSS commented Feb 3, 2026

Copy link
Copy Markdown
Owner

Updated pages/Validation.py to include Student-t distribution validation and a new "Compare Both" mode. This allows users to compare the performance of Normal and Student-t distributions in backtesting. The changes integrate the user-provided logic for displaying comparison results and calculating interval widths.


PR created automatically by Jules for task 3993904861077718799 started by @XCODESSS

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Distribution Model selection (Normal, Student-t, or Compare Both) in the validation dashboard
    • Introduced configurable Degree of Freedom slider for Student-t distribution
    • Enhanced backtesting with distribution-specific Monte Carlo execution
    • Enabled comparative analysis mode with separate performance metrics for each distribution
    • Improved diagnostic reporting with model-comparison recommendations and recommendations

Co-authored-by: XCODESSS <164984183+XCODESSS@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Feb 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR introduces a Distribution Model UI to the validation dashboard enabling selection between Normal, Student-t, or Compare Both distributions. The backtesting flow is expanded to execute distribution-specific Monte Carlo simulations, compute appropriate bounds, and store results accordingly. Result processing and reporting are enhanced to support per-model metrics and comparison analysis when comparing both distributions.

Changes

Cohort / File(s) Summary
Distribution Model Selection & Backtesting Logic
pages/Validation.py
Adds UI for selecting distribution models (Normal, Student-t with Degree of Freedom slider, or Compare Both). Implements distribution-specific Monte Carlo execution: Normal runs standard Monte Carlo; Student-t uses student-t distribution with configurable degrees of freedom; Compare Both runs both models and captures separate bounds and within-bounds flags for each. Result storage adjusted to handle single-model vs. comparison modes with appropriate labeling.
Result Processing & Reporting
pages/Validation.py
Enhances result processing to compute and display per-model hit rates and interval width analyses when comparing both distributions. Adds integrated model-comparison narrative with recommendations based on hit rate performance. Retains traditional reporting for single-model modes. Includes error handling with user-facing messages and traceback on exceptions.
Plotting & Formatting
pages/Validation.py
Extends dark-theme plotting to support per-model or combined results visualization. Ensures chronological ordering of test dates and includes guards for insufficient or missing forecast data. Minor structural consistency improvements throughout.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Validation Dashboard
    participant Engine as Backtesting Engine
    participant MC as Monte Carlo Simulator
    participant Processor as Result Processor
    participant Reporter as Report Generator
    participant Plotter as Plot Engine

    UI->>Engine: Select "Compare Both" + Historical Data
    activate Engine
    Engine->>MC: Run Normal Distribution MC
    activate MC
    MC-->>Engine: Normal Bounds & Prices vs Bounds
    deactivate MC
    Engine->>MC: Run Student-t Distribution MC (df param)
    activate MC
    MC-->>Engine: Student-t Bounds & Prices vs Bounds
    deactivate MC
    Engine->>Processor: Store Separate Normal/Student-t Results
    deactivate Engine
    activate Processor
    Processor->>Processor: Compute Per-Model Hit Rates
    Processor->>Processor: Analyze Interval Widths
    Processor-->>Reporter: Processed Metrics for Both Models
    deactivate Processor
    activate Reporter
    Reporter->>Reporter: Generate Model Comparison Narrative
    Reporter->>Reporter: Determine Better Model Indicator
    Reporter-->>UI: Display Comparative Results & Recommendations
    deactivate Reporter
    UI->>Plotter: Request Visualization
    activate Plotter
    Plotter->>Plotter: Plot Bounds & Prices for Both Models
    Plotter-->>UI: Render Comparison Plots
    deactivate Plotter
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A dash of Normal, a Student-t delight,
Compare them both beneath the dashboard light,
Monte Carlo bounds now dance in pairs,
With fat tails caught in statistical snares,
The validation hops forward, refined and bright!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding Student-t distribution support and a comparison mode to Validation.py.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch validation-update-student-t-3993904861077718799

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@pages/Validation.py`:
- Around line 399-411: The interval-width comparison can raise ZeroDivisionError
when avg_width_normal is 0; in the block that computes avg_width_normal,
avg_width_student_t and width_difference (variables and DataFrame columns:
df['Interval Width Normal'], df['Interval Width Student-t'], avg_width_normal,
avg_width_student_t, width_difference), add a guard: if avg_width_normal == 0
(or nearly zero via a small epsilon) set width_difference to None or a sentinel
and render "N/A" instead of computing the percentage; update the Streamlit
writes that output width_difference so they print "N/A" when width_difference is
None (or use conditional formatting) to avoid division by zero.
- Around line 617-633: The plotting block assumes df_valid contains 'Lower
Bound', 'Upper Bound', and 'Within Bounds', which crashes when distribution ==
"Compare Both"; update the code that builds the plot (the block using dates =
pd.to_datetime(df_valid['Test Date']) and subsequent ax.fill_between/ax.plot and
hits/misses) to first detect distribution == "Compare Both" (or test for missing
columns in df_valid) and either select one model's result (e.g., choose a column
prefix or a selected_model variable) to create/alias 'Lower Bound'/'Upper
Bound'/'Within Bounds' for plotting, or skip the confidence-interval and bounds
rendering when those columns are absent; ensure you reference df_valid and the
plotting variables so later ax.fill_between, ax.plot and the hits/misses filters
always operate on existing columns.
- Line 420: The st.info call uses an unnecessary f-string prefix with no
interpolations; update the call in Validation.py (the st.info("ℹ️
**Recommendation:** Normal distribution is adequate for this stock/timeframe.")
invocation currently written as st.info(f"...")) by removing the leading f so it
becomes a plain string, and then run the linter to confirm Ruff F541 is
resolved.

Comment thread pages/Validation.py
Comment on lines +399 to +411
# Calculate interval widths
df['Interval Width Normal'] = (df['Upper Bound Normal'] - df['Lower Bound Normal']) / df['Starting Price'] * 100
df['Interval Width Student-t'] = (df['Upper Bound Student-t'] - df['Lower Bound Student-t']) / df['Starting Price'] * 100

avg_width_normal = df['Interval Width Normal'].mean()
avg_width_student_t = df['Interval Width Student-t'].mean()
width_difference = ((avg_width_student_t - avg_width_normal) / avg_width_normal) * 100

st.write("---")
st.subheader("📏 Interval Width Analysis")
st.write(f"**Normal Distribution:** Average interval width: {avg_width_normal:.1f}% of stock price")
st.write(f"**Student-t Distribution:** Average interval width: {avg_width_student_t:.1f}% of stock price")
st.write(f"**Difference:** Student-t intervals are {width_difference:.1f}% wider")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard interval-width ratio against zero-width normals.

If avg_width_normal is 0 (flat-volatility or deterministic runs), width_difference raises a ZeroDivisionError. Add a guard and render an explicit “N/A” when the normal width is zero.

🔧 Proposed fix
-            width_difference = ((avg_width_student_t - avg_width_normal) / avg_width_normal) * 100
+            if avg_width_normal > 0:
+                width_difference = ((avg_width_student_t - avg_width_normal) / avg_width_normal) * 100
+                width_diff_text = f"{width_difference:.1f}% wider"
+            else:
+                width_difference = 0.0
+                width_diff_text = "N/A (normal width is 0)"
@@
-            st.write(f"**Difference:** Student-t intervals are {width_difference:.1f}% wider")
+            st.write(f"**Difference:** Student-t intervals are {width_diff_text}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Calculate interval widths
df['Interval Width Normal'] = (df['Upper Bound Normal'] - df['Lower Bound Normal']) / df['Starting Price'] * 100
df['Interval Width Student-t'] = (df['Upper Bound Student-t'] - df['Lower Bound Student-t']) / df['Starting Price'] * 100
avg_width_normal = df['Interval Width Normal'].mean()
avg_width_student_t = df['Interval Width Student-t'].mean()
width_difference = ((avg_width_student_t - avg_width_normal) / avg_width_normal) * 100
st.write("---")
st.subheader("📏 Interval Width Analysis")
st.write(f"**Normal Distribution:** Average interval width: {avg_width_normal:.1f}% of stock price")
st.write(f"**Student-t Distribution:** Average interval width: {avg_width_student_t:.1f}% of stock price")
st.write(f"**Difference:** Student-t intervals are {width_difference:.1f}% wider")
# Calculate interval widths
df['Interval Width Normal'] = (df['Upper Bound Normal'] - df['Lower Bound Normal']) / df['Starting Price'] * 100
df['Interval Width Student-t'] = (df['Upper Bound Student-t'] - df['Lower Bound Student-t']) / df['Starting Price'] * 100
avg_width_normal = df['Interval Width Normal'].mean()
avg_width_student_t = df['Interval Width Student-t'].mean()
if avg_width_normal > 0:
width_difference = ((avg_width_student_t - avg_width_normal) / avg_width_normal) * 100
width_diff_text = f"{width_difference:.1f}% wider"
else:
width_difference = 0.0
width_diff_text = "N/A (normal width is 0)"
st.write("---")
st.subheader("📏 Interval Width Analysis")
st.write(f"**Normal Distribution:** Average interval width: {avg_width_normal:.1f}% of stock price")
st.write(f"**Student-t Distribution:** Average interval width: {avg_width_student_t:.1f}% of stock price")
st.write(f"**Difference:** Student-t intervals are {width_diff_text}")
🤖 Prompt for AI Agents
In `@pages/Validation.py` around lines 399 - 411, The interval-width comparison
can raise ZeroDivisionError when avg_width_normal is 0; in the block that
computes avg_width_normal, avg_width_student_t and width_difference (variables
and DataFrame columns: df['Interval Width Normal'], df['Interval Width
Student-t'], avg_width_normal, avg_width_student_t, width_difference), add a
guard: if avg_width_normal == 0 (or nearly zero via a small epsilon) set
width_difference to None or a sentinel and render "N/A" instead of computing the
percentage; update the Streamlit writes that output width_difference so they
print "N/A" when width_difference is None (or use conditional formatting) to
avoid division by zero.

Comment thread pages/Validation.py
1. **Confidence intervals may be too wide** - The model is being overly conservative
2. **Possible date indexing issue** - Verify that actual prices are being compared at the correct forecast dates
3. **Model may be well-calibrated** - But intervals are wider than necessary for risk management
st.info(f"ℹ️ **Recommendation:** Normal distribution is adequate for this stock/timeframe.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the file
find . -name "Validation.py" -type f

Repository: XCODESSS/Monte-Carlo-Simulation

Length of output: 96


🏁 Script executed:

# Check if the file exists and view the relevant lines around line 420
if [ -f "pages/Validation.py" ]; then
  wc -l pages/Validation.py
  sed -n '415,425p' pages/Validation.py | cat -n
fi

Repository: XCODESSS/Monte-Carlo-Simulation

Length of output: 934


🏁 Script executed:

# Run ruff on the file to check for F541 violations
python -m ruff check pages/Validation.py --select F541 2>&1 || true

Repository: XCODESSS/Monte-Carlo-Simulation

Length of output: 880


Remove unused f-string prefix.

Ruff F541 correctly flags the f-string at line 420 as having no placeholders. The string contains no variable interpolations, making the f prefix unnecessary. Removing it resolves the linting violation.

Proposed fix
-                st.info(f"ℹ️ **Recommendation:** Normal distribution is adequate for this stock/timeframe.")
+                st.info("ℹ️ **Recommendation:** Normal distribution is adequate for this stock/timeframe.")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
st.info(f"ℹ️ **Recommendation:** Normal distribution is adequate for this stock/timeframe.")
st.info("ℹ️ **Recommendation:** Normal distribution is adequate for this stock/timeframe.")
🧰 Tools
🪛 Ruff (0.14.14)

[error] 420-420: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 420-420: String contains ambiguous (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?

(RUF001)

🤖 Prompt for AI Agents
In `@pages/Validation.py` at line 420, The st.info call uses an unnecessary
f-string prefix with no interpolations; update the call in Validation.py (the
st.info("ℹ️ **Recommendation:** Normal distribution is adequate for this
stock/timeframe.") invocation currently written as st.info(f"...")) by removing
the leading f so it becomes a plain string, and then run the linter to confirm
Ruff F541 is resolved.

Comment thread pages/Validation.py
Comment on lines +617 to +633
# Plot bounds and actual prices (use valid results only)
dates = pd.to_datetime(df_valid['Test Date'])

# Confidence interval
ax.fill_between(dates, df_valid['Lower Bound'], df_valid['Upper Bound'],
alpha=0.3, color='#4A90E2', label='90% Confidence Interval', zorder=1)

# Plot bounds
ax.plot(dates, df_valid['Lower Bound'], color='#E74C3C', linestyle='--',
linewidth=2, alpha=0.8, label='Lower Bound (5%)', zorder=2)
ax.plot(dates, df_valid['Upper Bound'], color='#E74C3C', linestyle='--',
linewidth=2, alpha=0.8, label='Upper Bound (95%)', zorder=2)

# Plot actual prices
hits = df_valid[df_valid['Within Bounds'] == True]
misses = df_valid[df_valid['Within Bounds'] == False]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Compare Both mode will crash in the plot due to missing columns.

When distribution == "Compare Both", df_valid does not have Lower Bound, Upper Bound, or Within Bounds, so the plot block raises KeyError. Add a column selector (or plot both intervals).

🧩 Proposed fix (select a plot model; easy to extend for both)
-            # Plot bounds and actual prices (use valid results only)
-            dates = pd.to_datetime(df_valid['Test Date'])
-
-            # Confidence interval
-            ax.fill_between(dates, df_valid['Lower Bound'], df_valid['Upper Bound'],
-                            alpha=0.3, color='#4A90E2', label='90% Confidence Interval', zorder=1)
-
-            # Plot bounds
-            ax.plot(dates, df_valid['Lower Bound'], color='#E74C3C', linestyle='--',
-                linewidth=2, alpha=0.8, label='Lower Bound (5%)', zorder=2)
-            ax.plot(dates, df_valid['Upper Bound'], color='#E74C3C', linestyle='--',
-                linewidth=2, alpha=0.8, label='Upper Bound (95%)', zorder=2)
-
-            # Plot actual prices
-            hits = df_valid[df_valid['Within Bounds'] == True]
-            misses = df_valid[df_valid['Within Bounds'] == False]
+            # Plot bounds and actual prices (use valid results only)
+            dates = pd.to_datetime(df_valid['Test Date'])
+
+            if distribution == "Compare Both":
+                lower_col = 'Lower Bound Normal'
+                upper_col = 'Upper Bound Normal'
+                within_col = 'Within Bounds Normal'
+                interval_label = 'Normal 90% CI'
+            else:
+                lower_col = 'Lower Bound'
+                upper_col = 'Upper Bound'
+                within_col = 'Within Bounds'
+                interval_label = '90% Confidence Interval'
+
+            ax.fill_between(dates, df_valid[lower_col], df_valid[upper_col],
+                            alpha=0.3, color='#4A90E2', label=interval_label, zorder=1)
+
+            ax.plot(dates, df_valid[lower_col], color='#E74C3C', linestyle='--',
+                linewidth=2, alpha=0.8, label='Lower Bound (5%)', zorder=2)
+            ax.plot(dates, df_valid[upper_col], color='#E74C3C', linestyle='--',
+                linewidth=2, alpha=0.8, label='Upper Bound (95%)', zorder=2)
+
+            # Plot actual prices
+            hits = df_valid[df_valid[within_col]]
+            misses = df_valid[~df_valid[within_col]]
🧰 Tools
🪛 Ruff (0.14.14)

[error] 631-631: Avoid equality comparisons to True; use df_valid['Within Bounds']: for truth checks

Replace with df_valid['Within Bounds']

(E712)


[error] 632-632: Avoid equality comparisons to False; use not df_valid['Within Bounds']: for false checks

Replace with not df_valid['Within Bounds']

(E712)

🤖 Prompt for AI Agents
In `@pages/Validation.py` around lines 617 - 633, The plotting block assumes
df_valid contains 'Lower Bound', 'Upper Bound', and 'Within Bounds', which
crashes when distribution == "Compare Both"; update the code that builds the
plot (the block using dates = pd.to_datetime(df_valid['Test Date']) and
subsequent ax.fill_between/ax.plot and hits/misses) to first detect distribution
== "Compare Both" (or test for missing columns in df_valid) and either select
one model's result (e.g., choose a column prefix or a selected_model variable)
to create/alias 'Lower Bound'/'Upper Bound'/'Within Bounds' for plotting, or
skip the confidence-interval and bounds rendering when those columns are absent;
ensure you reference df_valid and the plotting variables so later
ax.fill_between, ax.plot and the hits/misses filters always operate on existing
columns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant