Updated portfolio buidler and master pages for bulk file selection or select and scan a folder for MT5 html back test reports

This commit is contained in:
unknown
2026-05-12 13:51:56 +10:00
parent 902547d99f
commit e8ee01304a
2 changed files with 168 additions and 75 deletions
+75 -28
View File
@@ -617,48 +617,95 @@ def render():
unsafe_allow_html=True)
# ── Upload panel ─────────────────────────────────────────────────────────
with st.expander("📂 Upload Strategy Reports",
with st.expander("📂 Load Strategy Reports",
expanded=not bool(st.session_state.pb_uploaded_files)):
st.caption("Accepts: `.htm` · `.html` · `.csv` — one file per slot. "
"Increase the slot count if you need more.")
sc1, sc2 = st.columns([1, 5])
with sc1:
n_slots = st.selectbox(
"Slots",
list(range(1, 21)),
index=max(0, st.session_state.pb_n_slots - 1),
key="pb_n_slots_select",
load_mode = st.radio(
"Load method",
["📁 Upload files", "🗂 Scan folder"],
horizontal=True, key="pb_load_mode",
)
if n_slots != st.session_state.pb_n_slots:
st.session_state.pb_n_slots = n_slots
st.rerun()
slots_per_row = 5
for row_start in range(0, st.session_state.pb_n_slots, slots_per_row):
cols = st.columns(slots_per_row)
for j in range(slots_per_row):
idx = row_start + j
if idx >= st.session_state.pb_n_slots:
break
with cols[j]:
f = st.file_uploader(
f"File {idx + 1}",
if load_mode == "📁 Upload files":
st.caption("Select one or more `.htm` · `.html` · `.csv` files — hold Ctrl/Cmd to pick multiple.")
uploaded_files = st.file_uploader(
"Strategy reports",
type=["htm", "html", "csv"],
accept_multiple_files=False,
key=f"pb_upload_slot_{idx}",
accept_multiple_files=True,
key="pb_multi_uploader",
label_visibility="collapsed",
)
if f is not None:
if uploaded_files:
added = 0
for f in uploaded_files:
stem = os.path.splitext(f.name)[0]
if stem not in st.session_state.pb_uploaded_files:
df = _parse_uploaded(f)
if df is not None:
df = _ensure_columns(df, stem)
st.session_state.pb_uploaded_files[stem] = df
st.success(f"{stem}{len(df):,} trades")
added += 1
if added:
st.success(f"✅ Loaded {added} file(s)")
st.rerun()
else: # Scan folder
st.caption("Enter a folder path — all `.htm` / `.html` / `.csv` files inside will be loaded.")
fp_col, sub_col = st.columns([4, 1])
folder_path = fp_col.text_input(
"Folder path",
placeholder=r"e.g. C:\MT5\Reports\Backtest",
key="pb_folder_path",
label_visibility="collapsed",
)
include_sub = sub_col.checkbox("Subfolders", value=False, key="pb_folder_sub")
if st.button("📂 Scan & Load", key="pb_scan_btn", type="primary"):
if not folder_path or not os.path.isdir(folder_path):
st.error("Folder not found — check the path and try again.")
else:
import glob as _glob
exts = ("*.htm", "*.html", "*.csv")
found = []
for ext in exts:
pattern = os.path.join(folder_path, "**", ext) if include_sub \
else os.path.join(folder_path, ext)
found.extend(_glob.glob(pattern, recursive=include_sub))
found = sorted(set(found))
if not found:
st.warning("No `.htm` / `.html` / `.csv` files found in that folder.")
else:
added = skipped = errors = 0
for fpath in found:
stem = os.path.splitext(os.path.basename(fpath))[0]
if stem in st.session_state.pb_uploaded_files:
skipped += 1
continue
try:
with open(fpath, "rb") as fh:
raw = fh.read()
parser = _get_parser()
result = parser.detect_and_parse(raw, os.path.basename(fpath))
df = result[0] if isinstance(result, tuple) else result
if df is not None:
df = _ensure_columns(df, stem)
st.session_state.pb_uploaded_files[stem] = df
added += 1
except Exception as e:
st.warning(f"Could not load **{stem}**: {e}")
errors += 1
parts = [f"{added} loaded"]
if skipped: parts.append(f"{skipped} already present")
if errors: parts.append(f"{errors} failed")
st.success(" · ".join(parts))
if added:
st.rerun()
if st.session_state.pb_uploaded_files:
st.markdown("**Loaded strategies:**")
st.markdown("---")
st.markdown(f"**{len(st.session_state.pb_uploaded_files)} strategies loaded:**")
to_remove = []
for label in list(st.session_state.pb_uploaded_files):
c1, c2 = st.columns([6, 1])
+75 -29
View File
@@ -543,48 +543,94 @@ def render():
st.rerun()
# ── Upload ───────────────────────────────────────────────────────────────
with st.expander("📂 Upload Backtest Files",
with st.expander("📂 Load Backtest Files",
expanded=not bool(st.session_state.pm_files)):
st.caption("Accepts `.htm` · `.html` · `.csv` — one file per slot. "
"Increase the slot count if you need more.")
sc1, _ = st.columns([1, 5])
with sc1:
n_slots = st.selectbox(
"Slots",
list(range(1, 21)),
index=max(0, st.session_state.pm_n_slots - 1),
key="pm_n_slots_select",
load_mode = st.radio(
"Load method",
["📁 Upload files", "🗂 Scan folder"],
horizontal=True, key="pm_load_mode",
)
if n_slots != st.session_state.pm_n_slots:
st.session_state.pm_n_slots = n_slots
st.rerun()
slots_per_row = 5
for row_start in range(0, st.session_state.pm_n_slots, slots_per_row):
cols = st.columns(slots_per_row)
for j in range(slots_per_row):
idx = row_start + j
if idx >= st.session_state.pm_n_slots:
break
with cols[j]:
f = st.file_uploader(
f"File {idx + 1}",
if load_mode == "📁 Upload files":
st.caption("Select one or more `.htm` · `.html` · `.csv` files — hold Ctrl/Cmd to pick multiple.")
uploaded_files = st.file_uploader(
"Backtest reports",
type=["htm", "html", "csv"],
accept_multiple_files=False,
key=f"pm_upload_slot_{st.session_state.pm_uploader_key}_{idx}",
accept_multiple_files=True,
key=f"pm_multi_uploader_{st.session_state.pm_uploader_key}",
label_visibility="collapsed",
)
if f is not None:
if uploaded_files:
added = 0
for f in uploaded_files:
stem = os.path.splitext(f.name)[0]
if stem not in st.session_state.pm_files:
df = _parse_file(f)
if df is not None:
df = _normalise(df.copy(), stem)
st.session_state.pm_files[stem] = df
st.success(f"{stem}{len(df):,} trades")
added += 1
if added:
st.success(f"✅ Loaded {added} file(s)")
st.rerun()
else: # Scan folder
st.caption("Enter a folder path — all `.htm` / `.html` / `.csv` files inside will be loaded.")
fp_col, sub_col = st.columns([4, 1])
folder_path = fp_col.text_input(
"Folder path",
placeholder=r"e.g. C:\MT5\Reports\Backtest",
key="pm_folder_path",
label_visibility="collapsed",
)
include_sub = sub_col.checkbox("Subfolders", value=False, key="pm_folder_sub")
if st.button("📂 Scan & Load", key="pm_scan_btn", type="primary"):
if not folder_path or not os.path.isdir(folder_path):
st.error("Folder not found — check the path and try again.")
else:
import glob as _glob
found = []
for ext in ("*.htm", "*.html", "*.csv"):
pattern = os.path.join(folder_path, "**", ext) if include_sub \
else os.path.join(folder_path, ext)
found.extend(_glob.glob(pattern, recursive=include_sub))
found = sorted(set(found))
if not found:
st.warning("No `.htm` / `.html` / `.csv` files found in that folder.")
else:
added = skipped = errors = 0
for fpath in found:
stem = os.path.splitext(os.path.basename(fpath))[0]
if stem in st.session_state.pm_files:
skipped += 1
continue
try:
with open(fpath, "rb") as fh:
raw = fh.read()
parser = _get_parser()
result = parser.detect_and_parse(raw, os.path.basename(fpath))
df = result[0] if isinstance(result, tuple) else result
if df is not None:
df = _normalise(df.copy(), stem)
st.session_state.pm_files[stem] = df
added += 1
except Exception as e:
st.warning(f"Could not load **{stem}**: {e}")
errors += 1
parts = [f"{added} loaded"]
if skipped: parts.append(f"{skipped} already present")
if errors: parts.append(f"{errors} failed")
st.success(" · ".join(parts))
if added:
st.rerun()
if st.session_state.pm_files:
# Clear all button
st.markdown("---")
st.markdown(f"**{len(st.session_state.pm_files)} files loaded:**")
if st.button("🗑 Clear All Files", key="pm_clear_all"):
st.session_state.pm_files = {}
st.session_state.pm_custom_names = {}
@@ -604,7 +650,7 @@ def render():
strategy_dfs: dict = st.session_state.pm_files
if not strategy_dfs:
st.info("Upload backtest files above to get started.")
st.info("Load backtest files above to get started.")
return
# Explode each uploaded file into per-strategy DataFrames