Files
zhutoutoutousan 605faf5310 Prepare source-only public release for develop.
Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 15:03:43 +02:00

96 lines
3.4 KiB
Python

from __future__ import annotations
import pandas as pd
import streamlit as st
from factor_tester.data import load_prices_csv
from factor_tester.engine import EngineConfig, run_factor_engine, summary_metrics
from factor_tester.optimize import optimize_grid
st.set_page_config(page_title="Factor Strategy Tester", layout="wide")
st.title("Factor Strategy Tester")
st.caption("MT5-style workflow for factor investing with custom expressions")
with st.sidebar:
st.header("Inputs")
uploaded = st.file_uploader("Upload prices/factors CSV", type=["csv"])
expr = st.text_area(
"Factor expression",
value="(z(momentum_12m) + z(quality) - z(volatility_20d)) / 3",
height=110,
)
freq = st.selectbox("Rebalance", ["D", "W", "M", "Q"], index=2)
long_q = st.slider("Long quantile", 0.05, 0.50, 0.20, 0.05)
long_short = st.checkbox("Long-short", value=True)
short_q = st.slider("Short quantile", 0.05, 0.50, 0.20, 0.05, disabled=not long_short)
run_btn = st.button("Run Single Test", type="primary", use_container_width=True)
st.divider()
st.subheader("Optimization")
freq_grid = st.multiselect("Freq grid", ["D", "W", "M", "Q"], default=["W", "M"])
long_grid = st.text_input("Long quantiles", value="0.1,0.2,0.3")
short_grid = st.text_input("Short quantiles", value="0.1,0.2")
ls_grid = st.multiselect("Long-short options", [True, False], default=[True])
opt_btn = st.button("Run Optimization", use_container_width=True)
if uploaded is None:
st.info("Upload a CSV to start. Required columns: `date`, `asset`, `close` + factor columns.")
st.stop()
try:
df = load_prices_csv(uploaded)
except Exception as e:
st.error(f"Failed to load CSV: {e}")
st.stop()
st.write("### Data Preview")
st.dataframe(df.head(20), use_container_width=True)
col1, col2, col3, col4 = st.columns(4)
if run_btn:
try:
cfg = EngineConfig(
factor_expression=expr,
rebalance_frequency=freq,
long_quantile=float(long_q),
short_quantile=float(short_q if long_short else 0.0),
long_short=bool(long_short),
)
curve, detail = run_factor_engine(df, cfg)
m = summary_metrics(curve)
col1.metric("CAGR", f"{m['cagr']:.2%}")
col2.metric("Sharpe", f"{m['sharpe']:.2f}")
col3.metric("Max Drawdown", f"{m['max_dd']:.2%}")
col4.metric("Total Return", f"{m['total_return']:.2%}")
st.write("### Equity Curve")
st.line_chart(curve.set_index("date")["equity"])
st.write("### Daily Returns")
st.line_chart(curve.set_index("date")["portfolio_ret"])
st.write("### Engine Detail (tail)")
st.dataframe(detail.tail(50), use_container_width=True)
except Exception as e:
st.error(f"Backtest failed: {e}")
if opt_btn:
try:
lq = [float(x.strip()) for x in long_grid.split(",") if x.strip()]
sq = [float(x.strip()) for x in short_grid.split(",") if x.strip()]
results = optimize_grid(
df=df,
factor_expression=expr,
freqs=freq_grid or ["M"],
long_qs=lq or [0.2],
short_qs=sq or [0.2],
long_short_options=ls_grid or [True],
)
st.write("### Optimization Results")
st.dataframe(results, use_container_width=True)
except Exception as e:
st.error(f"Optimization failed: {e}")