Spaces:
Sleeping
Sleeping
| """ | |
| app.py β HuggingFace Spaces entry point. | |
| Architecture: | |
| Python : Gradio UI, Claude API calls, HF I/O, PDF processing | |
| Julia : Indicators, BacktestEngine, WalkForwardOptimizer, SignalCompiler | |
| Python NEVER does numerical computation. It only: | |
| 1. Calls Claude API (extraction + strategy code generation) | |
| 2. Calls Julia via juliacall for all math | |
| 3. Reads/writes HuggingFace datasets | |
| 4. Renders Gradio UI | |
| """ | |
| import io, json, zipfile, tempfile | |
| from pathlib import Path | |
| from datetime import datetime | |
| import gradio as gr | |
| from loguru import logger | |
| import utils.config as cfg | |
| import utils.hf_io as hf | |
| from pipeline.pdf_processor import PDFProcessor | |
| from pipeline.extractor import AIExtractor, Deduplicator | |
| from pipeline.julia_bridge import full_backtest_pipeline, julia_available | |
| from pipeline.exporter import ( | |
| slugify, strategy_md, formula_md, | |
| backtest_report_md, optimal_json, mt5_set, | |
| julia_config, index_md, | |
| ) | |
| # ββ Lazy KB βββββββββββββββββββββββββββββββββββββββββββ | |
| _kb = None | |
| def get_kb(): | |
| global _kb | |
| if _kb is None: _kb = hf.kb_load() | |
| return _kb | |
| def reset_kb(): | |
| global _kb; _kb = hf.kb_load() | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 1 β UPLOAD & EXTRACT | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _save_and_resolve_pdfs(pdf_files) -> list: | |
| """ | |
| Gradio 6 passes uploaded files as plain string paths into a temp dir | |
| that may be cleaned up before or during processing. | |
| This function: | |
| 1. Immediately copies every uploaded file to /tmp/quant/pdfs/ (persistent for session) | |
| 2. Uploads each to HuggingFace dataset pdfs/ folder (persistent across restarts) | |
| 3. Returns stable local Path objects ready for processing | |
| """ | |
| import shutil | |
| PDF_DIR = cfg.TMP / "pdfs" | |
| PDF_DIR.mkdir(parents=True, exist_ok=True) | |
| resolved = [] | |
| for f in (pdf_files or []): | |
| try: | |
| # Gradio 6: f is a str path; Gradio 5: f has .name attribute | |
| src = Path(f.name if hasattr(f, "name") else f) | |
| if not src.exists(): | |
| logger.warning(f"Uploaded path does not exist: {src}") | |
| continue | |
| dst = PDF_DIR / src.name | |
| if not dst.exists(): | |
| shutil.copy2(str(src), str(dst)) | |
| resolved.append(dst) | |
| # Persist to HuggingFace | |
| if cfg.HF_TOKEN and cfg.HF_DATASET_REPO: | |
| hf.pdf_upload(dst) | |
| except Exception as e: | |
| logger.error(f"Failed to resolve upload {f}: {e}") | |
| return resolved | |
| def load_pdfs_from_hf() -> list: | |
| """List PDFs previously uploaded to HuggingFace dataset.""" | |
| try: | |
| from huggingface_hub import list_repo_files | |
| files = list(list_repo_files( | |
| repo_id=cfg.HF_DATASET_REPO, | |
| repo_type="dataset", | |
| token=cfg.HF_TOKEN, | |
| )) | |
| return sorted([f for f in files if f.startswith("pdfs/") and f.endswith(".pdf")]) | |
| except Exception as e: | |
| logger.warning(f"Could not list HF PDFs: {e}") | |
| return [] | |
| def download_pdf_from_hf(remote_path: str) -> Path | None: | |
| """Download a PDF from HuggingFace to local cache.""" | |
| try: | |
| from huggingface_hub import hf_hub_download | |
| PDF_DIR = cfg.TMP / "pdfs" | |
| PDF_DIR.mkdir(parents=True, exist_ok=True) | |
| local = hf_hub_download( | |
| repo_id=cfg.HF_DATASET_REPO, | |
| filename=remote_path, | |
| repo_type="dataset", | |
| token=cfg.HF_TOKEN, | |
| local_dir=str(PDF_DIR), | |
| force_download=False, | |
| ) | |
| return Path(local) | |
| except Exception as e: | |
| logger.warning(f"Failed to download {remote_path}: {e}") | |
| return None | |
| def _extract_paths(paths: list, log: list, totals: dict, progress, kb: dict): | |
| """Core extraction loop β shared by new upload and re-process from HF.""" | |
| proc = PDFProcessor() | |
| ai = AIExtractor() | |
| dedup = Deduplicator() | |
| hf_files = [] | |
| for i, path in enumerate(paths): | |
| progress((i + 1) / max(len(paths), 1), desc=f"{path.name}") | |
| log.append(f"\nπ [{i+1}/{len(paths)}] {path.name}") | |
| try: | |
| chunks = list(proc.process(path)) | |
| log.append(f" β {len(chunks)} chunks extracted") | |
| except Exception as e: | |
| log.append(f" β PDF read error: {e}") | |
| continue | |
| for chunk in chunks: | |
| try: | |
| extracted = ai.extract(chunk) | |
| stats = dedup.process(extracted, kb) | |
| for kind in ("strategies", "formulas", "systems"): | |
| for act in ("added", "merged", "skipped"): | |
| totals[kind][act] += stats[kind][act] | |
| except Exception as e: | |
| log.append(f" β οΈ Chunk error: {e}") | |
| log.append(f" β New: {totals['strategies']['added']} strats, " | |
| f"{totals['formulas']['added']} formulas") | |
| for cid, rec in kb["strategies"].items(): | |
| hf_files.append((f"extracted/strategies/{slugify(rec.get('name',''))}.md", | |
| strategy_md(rec).encode())) | |
| for cid, rec in kb["formulas"].items(): | |
| hf_files.append((f"extracted/formulas/{slugify(rec.get('name',''))}.md", | |
| formula_md(rec).encode())) | |
| progress(0.95, desc="Saving to HuggingFaceβ¦") | |
| hf.kb_save(kb) | |
| if hf_files and cfg.HF_TOKEN: | |
| pushed = hf.push_batch(hf_files, "Update extracted knowledge") | |
| log.append(f"\nβοΈ Pushed {pushed} markdown files to HuggingFace") | |
| reset_kb() | |
| return ai.tokens_used | |
| def run_extraction(pdf_files, progress=gr.Progress()): | |
| if not cfg.ANTHROPIC_API_KEY: return "β ANTHROPIC_API_KEY secret not set.", "" | |
| if not cfg.HF_DATASET_REPO: return "β HF_DATASET_REPO secret not set.", "" | |
| # Step 1: resolve uploads β stable local paths + upload to HF | |
| progress(0.0, desc="Saving uploads to HuggingFaceβ¦") | |
| paths = _save_and_resolve_pdfs(pdf_files) | |
| if not paths: | |
| return ("β οΈ No valid PDFs found. Upload files above, " | |
| "or use 'Re-process from HF' to reprocess previously uploaded PDFs."), "" | |
| kb = get_kb() | |
| log = [] | |
| totals = {k: {"added":0,"merged":0,"skipped":0} | |
| for k in ("strategies","formulas","systems")} | |
| tokens = _extract_paths(paths, log, totals, progress, kb) | |
| counts = {k: len(kb[k]) for k in kb} | |
| summary = f"""β Extraction Complete | |
| PDFs processed : {len(paths)} | |
| Strategies β added: {totals['strategies']['added']} merged: {totals['strategies']['merged']} skipped: {totals['strategies']['skipped']} | |
| Formulas β added: {totals['formulas']['added']} merged: {totals['formulas']['merged']} skipped: {totals['formulas']['skipped']} | |
| Systems β added: {totals['systems']['added']} merged: {totals['systems']['merged']} skipped: {totals['systems']['skipped']} | |
| KB totals : {counts['strategies']} strategies Β· {counts['formulas']} formulas Β· {counts['systems']} systems | |
| Tokens used : {tokens:,} | |
| PDFs stored : HuggingFace β {cfg.HF_DATASET_REPO}/pdfs/""" | |
| return summary, "\n".join(log[-50:]) | |
| def reprocess_from_hf(selected_pdfs, progress=gr.Progress()): | |
| """Download selected PDFs from HF and re-extract.""" | |
| if not cfg.ANTHROPIC_API_KEY: return "β ANTHROPIC_API_KEY secret not set.", "" | |
| if not cfg.HF_DATASET_REPO: return "β HF_DATASET_REPO secret not set.", "" | |
| if not selected_pdfs: return "β οΈ No PDFs selected.", "" | |
| progress(0.0, desc="Downloading from HuggingFaceβ¦") | |
| paths = [] | |
| for remote in selected_pdfs: | |
| p = download_pdf_from_hf(remote) | |
| if p: paths.append(p) | |
| if not paths: | |
| return "β Could not download any PDFs from HuggingFace.", "" | |
| kb = get_kb() | |
| log = [f"Re-processing {len(paths)} PDF(s) from HuggingFace\n"] | |
| totals = {k: {"added":0,"merged":0,"skipped":0} | |
| for k in ("strategies","formulas","systems")} | |
| tokens = _extract_paths(paths, log, totals, progress, kb) | |
| counts = {k: len(kb[k]) for k in kb} | |
| return (f"β Re-extraction complete\n" | |
| f"PDFs: {len(paths)} Β· " | |
| f"Strategies: +{totals['strategies']['added']} Β· " | |
| f"Formulas: +{totals['formulas']['added']}\n" | |
| f"KB totals: {counts['strategies']} strategies Β· " | |
| f"{counts['formulas']} formulas\n" | |
| f"Tokens: {tokens:,}"), "\n".join(log[-50:]) | |
| def refresh_hf_pdf_list(): | |
| pdfs = load_pdfs_from_hf() | |
| return gr.update(choices=pdfs, value=[]) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 2 β BROWSE KB | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def search_strategies(query, category): | |
| kb = get_kb(); items = list(kb["strategies"].values()) | |
| if category and category != "All": | |
| items = [x for x in items if x.get("category") == category] | |
| if query: | |
| q = query.lower() | |
| items = [x for x in items if q in x.get("name","").lower() or q in x.get("description","").lower()] | |
| rows = [[x.get("name","")[:50], x.get("category",""), | |
| x.get("description","")[:100], | |
| ", ".join(x.get("sources",[]))[:40], len(x.get("layers",[]))] | |
| for x in items[:100]] | |
| return rows, f"{len(items)} strategies" | |
| def search_formulas(query): | |
| kb = get_kb(); items = list(kb["formulas"].values()) | |
| if query: | |
| q = query.lower() | |
| items = [x for x in items if q in x.get("name","").lower() or q in x.get("purpose","").lower()] | |
| return [[x.get("name","")[:50], x.get("category",""), | |
| x.get("purpose","")[:80], | |
| "β " if x.get("latex") else "β", | |
| ", ".join(x.get("sources",[]))[:40]] for x in items[:100]] | |
| def dl_strategy(name): | |
| kb = get_kb() | |
| for rec in kb["strategies"].values(): | |
| if rec.get("name","").lower() == name.strip().lower(): | |
| tmp = tempfile.mktemp(suffix=".md") | |
| Path(tmp).write_text(strategy_md(rec), encoding="utf-8") | |
| return tmp | |
| return None | |
| def dl_all_strategies_zip(category): | |
| kb = get_kb(); items = list(kb["strategies"].values()) | |
| if category and category != "All": | |
| items = [x for x in items if x.get("category") == category] | |
| tmp = tempfile.mktemp(suffix=".zip") | |
| with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zf: | |
| for rec in items: | |
| zf.writestr(f"{slugify(rec.get('name','unknown'))}.md", strategy_md(rec)) | |
| return tmp | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 3 β BACKTEST (Julia Engine) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_symbols(): | |
| syms = hf.tick_list_symbols() | |
| return gr.update(choices=syms, value=syms[:2] if len(syms)>=2 else syms) | |
| def run_backtests(selected_symbols, selected_timeframes, | |
| strategy_filter, max_strategies, viable_only, | |
| progress=gr.Progress()): | |
| if not cfg.HF_TICK_REPO: return "β HF_TICK_REPO not set.", "" | |
| if not cfg.ANTHROPIC_API_KEY: return "β ANTHROPIC_API_KEY not set.", "" | |
| if not julia_available(): return "β Julia runtime not available. Check build logs.", "" | |
| ai = AIExtractor() | |
| kb = get_kb() | |
| strats = list(kb["strategies"].values()) | |
| if strategy_filter: | |
| strats = [s for s in strats if strategy_filter.lower() in s.get("name","").lower()] | |
| if max_strategies > 0: | |
| strats = strats[:int(max_strategies)] | |
| if not strats: return "β οΈ No strategies. Run extraction first.", "" | |
| symbols = selected_symbols or hf.tick_list_symbols()[:2] | |
| timeframes = selected_timeframes or ["1h"] | |
| log, all_results, viable_count = [], [], 0 | |
| for si, rec in enumerate(strats): | |
| name = rec.get("name","?") | |
| progress(si/len(strats), desc=f"[{si+1}/{len(strats)}] {name[:35]}") | |
| # 1. Generate Julia signal code via Claude | |
| jl_code = ai.compile_strategy_code(rec) | |
| if not jl_code: | |
| log.append(f"β Code gen failed: {name[:40]}"); continue | |
| log.append(f"β Julia code generated: {name[:40]}") | |
| for sym in symbols: | |
| for tf in timeframes: | |
| df = hf.tick_load(sym, tf) | |
| if df is None or len(df) < 200: | |
| log.append(f" β οΈ {sym} {tf}: no data"); continue | |
| # 2. Full Julia pipeline (compile β optimize β backtest) | |
| result = full_backtest_pipeline( | |
| strategy_code = jl_code, | |
| strategy_name = name, | |
| open_p = df["open"].values, | |
| high = df["high"].values, | |
| low = df["low"].values, | |
| close = df["close"].values, | |
| volume = df["volume"].values, | |
| timeframe = tf, | |
| symbol = sym, | |
| n_windows = cfg.WF_WINDOWS, | |
| is_ratio = cfg.WF_IS_RATIO, | |
| min_trades = cfg.MIN_TRADES, | |
| min_sharpe = cfg.MIN_SHARPE, | |
| max_combos = cfg.MAX_PARAM_COMBOS, | |
| initial_equity = cfg.INITIAL_EQUITY, | |
| commission_pct = cfg.COMMISSION_PCT, | |
| risk_per_trade = cfg.RISK_PER_TRADE, | |
| ) | |
| all_results.append(result) | |
| # 3. Build + push output files | |
| if cfg.HF_TOKEN and cfg.HF_DATASET_REPO: | |
| if not viable_only or result.get("is_viable"): | |
| hf.push_result( | |
| name, sym, tf, | |
| backtest_report_md(result, rec), | |
| optimal_json(result, rec), | |
| mt5_set(result, rec), | |
| julia_config(result), | |
| ) | |
| status = "β " if result.get("is_viable") else "β" | |
| log.append( | |
| f" {status} {sym} {tf}: " | |
| f"Sharpe={result.get('oos_sharpe_mean',0):.2f} " | |
| f"DD={result.get('oos_max_dd',0):.1f}% " | |
| f"Score={result.get('robustness',0):.0f}") | |
| if result.get("is_viable"): viable_count += 1 | |
| # 4. Push master index | |
| if all_results and cfg.HF_TOKEN: | |
| hf.push_index(index_md(all_results), { | |
| "generated": datetime.now().isoformat(), | |
| "engine": "Julia 1.11", | |
| "total_strategies": len(all_results), | |
| "viable_count": viable_count, | |
| "strategies": all_results, | |
| }) | |
| summary = f"""π Julia Backtest Complete | |
| Engine: Julia 1.11 BacktestEngine.jl | |
| Strategies compiled: {len(strats)} | |
| Combinations tested: {len(all_results)} | |
| Viable strategies: {viable_count} | |
| Pass rate: {viable_count/max(len(all_results),1)*100:.1f}% | |
| Results on HuggingFace: | |
| {cfg.HF_DATASET_REPO}/optimal_sets/BACKTEST_INDEX.md""" | |
| return summary, "\n".join(log[-60:]) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 4 β RESULTS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_results(): | |
| data = hf.fetch_index() | |
| if not data: return [], "No results yet." | |
| strats = data.get("strategies",[]) | |
| viable = sorted([s for s in strats if s.get("is_viable")], | |
| key=lambda x: x.get("oos_sharpe_mean",0), reverse=True) | |
| rows = [[s.get("strategy","")[:45], s.get("symbol",""), s.get("timeframe",""), | |
| f'{s.get("oos_sharpe_mean",0):.2f}', f'{s.get("oos_max_dd",0):.1f}%', | |
| f'{s.get("oos_win_rate",0):.1f}%', f'{s.get("oos_pf_mean",0):.2f}', | |
| f'{s.get("robustness",0):.0f}'] for s in viable] | |
| count = (f"β {len(viable)} viable / {len(strats)} tested | " | |
| f"Engine: Julia | {data.get('generated','')[:16]}") | |
| return rows, count | |
| def dl_result_file(name, symbol, tf, ftype): | |
| sl = slugify(name); sym = symbol.upper().strip() | |
| pre = f"{sl}_{sym}_{tf}" | |
| ext_map = {"MT5 .set file": f"optimal_sets/{pre}.set", | |
| "Optimal JSON": f"optimal_sets/{pre}_optimal.json", | |
| "Julia config": f"optimal_sets/{pre}_config.jl", | |
| "Full report": f"backtests/{sl}/{pre}_report.md"} | |
| remote = ext_map.get(ftype,"") | |
| if not remote: return None | |
| data = hf.fetch_file(remote) | |
| if not data: return None | |
| tmp = tempfile.mktemp(suffix=Path(remote).suffix) | |
| Path(tmp).write_bytes(data) | |
| return tmp | |
| def dl_all_sets(): | |
| data = hf.fetch_index() | |
| if not data: return None | |
| tmp = tempfile.mktemp(suffix=".zip") | |
| with zipfile.ZipFile(tmp,"w",zipfile.ZIP_DEFLATED) as zf: | |
| for s in data.get("strategies",[]): | |
| if not s.get("is_viable"): continue | |
| sl = slugify(s["strategy"]); sym = s["symbol"]; tf = s["timeframe"] | |
| content = hf.fetch_file(f"optimal_sets/{sl}_{sym}_{tf}.set") | |
| if content: zf.writestr(f"{sl}_{sym}_{tf}.set", content) | |
| return tmp | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 5 β SETUP | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def check_config(): | |
| checks = [ | |
| ("ANTHROPIC_API_KEY", cfg.ANTHROPIC_API_KEY, "Claude API"), | |
| ("HF_TOKEN", cfg.HF_TOKEN, "HF write access"), | |
| ("HF_DATASET_REPO", cfg.HF_DATASET_REPO, "Results storage"), | |
| ("HF_TICK_REPO", cfg.HF_TICK_REPO, "Tick data source"), | |
| ] | |
| kb = get_kb() | |
| symbols = hf.tick_list_symbols() if cfg.HF_TICK_REPO else [] | |
| jl_ok = julia_available() | |
| lines = ["## Configuration Status", ""] | |
| for name, val, desc in checks: | |
| icon = "β " if val else "β" | |
| lines.append(f"{icon} `{name}` β {desc}") | |
| lines += ["", "## Julia Engine", "", | |
| f"{'β ' if jl_ok else 'β'} Julia runtime: {'available' if jl_ok else 'not available (check build logs)'}", | |
| "", "## Data Status", "", | |
| f"- Tick symbols: **{len(symbols)}** β {', '.join(symbols[:8])}", | |
| f"- Strategies in KB: **{len(kb['strategies'])}**", | |
| f"- Formulas in KB: **{len(kb['formulas'])}**", | |
| "", "## Backtest Settings", "", | |
| f"- WF Windows: `{cfg.WF_WINDOWS}` Β· IS Ratio: `{cfg.WF_IS_RATIO}`", | |
| f"- Min Trades: `{cfg.MIN_TRADES}` Β· Min Sharpe: `{cfg.MIN_SHARPE}`", | |
| f"- Commission: `{cfg.COMMISSION_PCT*100:.3f}%` Β· Risk/trade: `{cfg.RISK_PER_TRADE*100:.1f}%`", | |
| f"- Timeframes: `{', '.join(cfg.BACKTEST_TFS)}`"] | |
| return "\n".join(lines) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # BUILD APP | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CATS = ["All"] + cfg.CATEGORIES | |
| CSS = ".status-box{font-family:monospace;font-size:.82em}" | |
| with gr.Blocks(title="Quant Knowledge Extractor β Julia Engine") as demo: | |
| gr.HTML(""" | |
| <div style="text-align:center;padding:1.2em 0 .3em"> | |
| <h1 style="font-size:2em;color:#16a34a;margin:0">π Quant Knowledge Extractor</h1> | |
| <p style="color:#6b7280;margin:.4em 0 0"> | |
| Julia 1.11 Engine Β· BacktestEngine.jl Β· WalkForward Optimizer Β· MT5 .set Output | |
| </p> | |
| </div>""") | |
| with gr.Tabs(): | |
| # Tab 1 β Extract | |
| with gr.Tab("π€ Upload & Extract"): | |
| gr.Markdown("""### Upload algorithmic trading PDFs | |
| PDFs are **saved to HuggingFace** (`pdfs/` folder) so you can re-process them anytime without re-uploading. | |
| OCR is applied automatically to scanned pages.""") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| pdf_in = gr.File(label="Drop PDFs here", file_count="multiple", | |
| file_types=[".pdf"]) | |
| ext_btn = gr.Button("π Upload + Extract", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| ext_out = gr.Textbox(label="Result", lines=14, interactive=False, | |
| elem_classes=["status-box"]) | |
| ext_log = gr.Textbox(label="Log", lines=8, interactive=False, | |
| elem_classes=["status-box"]) | |
| gr.Markdown("---\n### Re-process PDFs already on HuggingFace") | |
| gr.Markdown("*Use this if the container restarted and lost your session, " | |
| "or to re-extract with updated prompts.*") | |
| with gr.Row(): | |
| hf_refresh = gr.Button("π Refresh HF PDF list") | |
| hf_pdf_list = gr.CheckboxGroup(label="PDFs stored on HuggingFace", | |
| choices=[], value=[]) | |
| rep_btn = gr.Button("β»οΈ Re-process selected PDFs from HuggingFace", | |
| variant="secondary") | |
| rep_out = gr.Textbox(label="Re-process result", lines=6, interactive=False, | |
| elem_classes=["status-box"]) | |
| rep_log = gr.Textbox(label="Re-process log", lines=6, interactive=False, | |
| elem_classes=["status-box"]) | |
| ext_btn.click(fn=run_extraction, inputs=[pdf_in], outputs=[ext_out, ext_log]) | |
| hf_refresh.click(fn=refresh_hf_pdf_list, outputs=[hf_pdf_list]) | |
| rep_btn.click(fn=reprocess_from_hf, inputs=[hf_pdf_list], | |
| outputs=[rep_out, rep_log]) | |
| demo.load(fn=refresh_hf_pdf_list, outputs=[hf_pdf_list]) | |
| # Tab 2 β Browse | |
| with gr.Tab("π Knowledge Base"): | |
| with gr.Tabs(): | |
| with gr.Tab("π Strategies"): | |
| with gr.Row(): | |
| sq = gr.Textbox(label="Search", placeholder="RSI, breakout, Kellyβ¦") | |
| sc = gr.Dropdown(choices=CATS, value="All", label="Category") | |
| sb = gr.Button("π Search", variant="primary") | |
| st = gr.Dataframe(headers=["Name","Category","Description","Sources","Variants"], | |
| datatype=["str"]*4+["number"], interactive=False) | |
| sn = gr.Markdown("") | |
| with gr.Row(): | |
| sni = gr.Textbox(label="Name to download") | |
| sdb = gr.Button("β¬οΈ Download MD"); sdf = gr.File(label="") | |
| szb = gr.Button("π¦ Category ZIP"); szf = gr.File(label="") | |
| sb.click(fn=search_strategies, inputs=[sq,sc], outputs=[st,sn]) | |
| sdb.click(fn=dl_strategy, inputs=[sni], outputs=[sdf]) | |
| szb.click(fn=dl_all_strategies_zip, inputs=[sc], outputs=[szf]) | |
| with gr.Tab("β Formulas"): | |
| with gr.Row(): | |
| fq = gr.Textbox(label="Search", placeholder="Sharpe, Kelly, ATRβ¦") | |
| fb = gr.Button("π Search", variant="primary") | |
| ft = gr.Dataframe(headers=["Name","Category","Purpose","LaTeX","Sources"], | |
| datatype=["str"]*5, interactive=False) | |
| fb.click(fn=search_formulas, inputs=[fq], outputs=[ft]) | |
| # Tab 3 β Backtest | |
| with gr.Tab("π¬ Julia Backtest"): | |
| gr.Markdown( | |
| "### Walk-Forward Backtest β Julia Engine\n" | |
| "Claude generates Julia signal code β Julia compiles + optimizes β " | |
| "MT5 `.set` files pushed to HuggingFace." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| bt_load = gr.Button("π Load Symbols from HF") | |
| bt_syms = gr.CheckboxGroup(label="Symbols", choices=[], value=[]) | |
| bt_tfs = gr.CheckboxGroup( | |
| label="Timeframes", value=["1h","4h"], | |
| choices=["1m","5m","15m","30m","1h","4h","1d"]) | |
| bt_filt = gr.Textbox(label="Strategy filter (optional)") | |
| bt_max = gr.Slider(0, 500, value=0, step=10, label="Max strategies (0=all)") | |
| bt_viable= gr.Checkbox(label="Push only VIABLE to HuggingFace", value=True) | |
| bt_run = gr.Button("π Run Julia Backtests", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| bt_out = gr.Textbox(label="Summary", lines=12, interactive=False, elem_classes=["status-box"]) | |
| bt_log = gr.Textbox(label="Log", lines=12, interactive=False, elem_classes=["status-box"]) | |
| bt_load.click(fn=load_symbols, outputs=[bt_syms]) | |
| bt_run.click(fn=run_backtests, | |
| inputs=[bt_syms, bt_tfs, bt_filt, bt_max, bt_viable], | |
| outputs=[bt_out, bt_log]) | |
| # Tab 4 β Results | |
| with gr.Tab("π Results"): | |
| gr.Markdown("### Viable Strategies β Download MT5 `.set` & Julia Configs") | |
| res_ref = gr.Button("π Refresh from HuggingFace", variant="primary") | |
| res_tbl = gr.Dataframe( | |
| headers=["Strategy","Symbol","TF","Sharpe","Max DD","Win%","PF","Score"], | |
| datatype=["str"]*8, interactive=False) | |
| res_cnt = gr.Markdown("") | |
| gr.Markdown("#### Download individual file") | |
| with gr.Row(): | |
| rn = gr.Textbox(label="Strategy name"); rs = gr.Textbox(label="Symbol") | |
| rt = gr.Textbox(label="Timeframe") | |
| rf = gr.Dropdown(choices=["MT5 .set file","Optimal JSON", | |
| "Julia config","Full report"], | |
| value="MT5 .set file", label="File type") | |
| rdb = gr.Button("β¬οΈ Download", variant="primary"); rdf = gr.File(label="") | |
| gr.Markdown("#### Batch download all viable strategies") | |
| with gr.Row(): | |
| rsb = gr.Button("π― All MT5 .set (ZIP)"); rsf = gr.File(label="") | |
| res_ref.click(fn=load_results, outputs=[res_tbl, res_cnt]) | |
| rdb.click(fn=dl_result_file, inputs=[rn,rs,rt,rf], outputs=[rdf]) | |
| rsb.click(fn=dl_all_sets, outputs=[rsf]) | |
| demo.load(fn=load_results, outputs=[res_tbl, res_cnt]) | |
| # Tab 5 β Setup | |
| with gr.Tab("βοΈ Setup & Status"): | |
| gr.Markdown("""### Required Secrets (Space Settings β Variables and Secrets) | |
| | Secret | Description | | |
| |--------|-------------| | |
| | `ANTHROPIC_API_KEY` | Claude API key | | |
| | `HF_TOKEN` | HuggingFace write token | | |
| | `HF_DATASET_REPO` | `your-username/quant-knowledge-base` | | |
| | `HF_TICK_REPO` | `your-username/tick-data` | | |
| ### Tick Data Format | |
| Upload to your `tick-data` dataset: | |
| ``` | |
| EURUSD/ticks.parquet (columns: timestamp, bid, ask OR open,high,low,close,volume) | |
| BTCUSDT/1h.parquet (pre-built OHLCV β faster) | |
| ``` | |
| """) | |
| cfg_ref = gr.Button("π Check Status") | |
| cfg_out = gr.Markdown(check_config()) | |
| cfg_ref.click(fn=check_config, outputs=[cfg_out]) | |
| gr.HTML("""<div style="text-align:center;padding:.8em;color:#9ca3af;font-size:.75em"> | |
| Quant Knowledge Extractor Β· Julia 1.11 Engine Β· HuggingFace Spaces | |
| </div>""") | |
| if __name__ == "__main__": | |
| demo.launch( | |
| theme=gr.themes.Base(primary_hue="green", neutral_hue="gray"), | |
| css=CSS, | |
| ) | |