Spaces:
Running
Running
feat: Integrate Quant.ai into InferRoute gateway under /quant and /api routes
Browse files- external/Quant.ai/Dockerfile +24 -0
- external/Quant.ai/README.md +9 -0
- external/Quant.ai/assets/index-C6qvxrC6.js +0 -0
- external/Quant.ai/assets/index-DCSOhC_L.css +1 -0
- external/Quant.ai/backend/app/broker/live_runner.py +95 -13
- external/Quant.ai/backend/main_api.py +22 -2
- external/Quant.ai/frontend/src/App.tsx +9 -8
- external/Quant.ai/frontend/src/components/BrokerPanel.tsx +10 -9
- external/Quant.ai/frontend/src/components/ChatPanel.tsx +4 -3
- external/Quant.ai/frontend/src/components/ExperimentCompare.tsx +5 -4
- external/Quant.ai/frontend/src/components/ResearchReportPanel.tsx +2 -1
- external/Quant.ai/frontend/src/components/SameDayReplayPanel.tsx +4 -3
- external/Quant.ai/frontend/src/components/ScannerPanel.tsx +2 -1
- external/Quant.ai/frontend/src/components/WalkForwardPanel.tsx +3 -2
- external/Quant.ai/frontend/src/config.ts +15 -0
- external/Quant.ai/frontend/tsconfig.app.json +2 -2
- external/Quant.ai/index.html +14 -0
- external/Quant.ai/requirements.txt +9 -0
- external/Quant.ai/scratch/deploy_hf_space.py +44 -0
- inferroute/main.py +34 -0
- requirements.txt +4 -2
- scratch/deploy_inferroute_space.py +45 -0
external/Quant.ai/Dockerfile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-stage Docker build for Hugging Face Space
|
| 2 |
+
FROM node:18-alpine AS frontend-builder
|
| 3 |
+
WORKDIR /app/frontend
|
| 4 |
+
COPY frontend/package*.json ./
|
| 5 |
+
RUN npm install
|
| 6 |
+
COPY frontend/ ./
|
| 7 |
+
RUN npm run build
|
| 8 |
+
|
| 9 |
+
FROM python:3.11-slim
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 13 |
+
curl \
|
| 14 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 15 |
+
|
| 16 |
+
COPY requirements.txt ./
|
| 17 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
+
|
| 19 |
+
COPY backend/ ./backend/
|
| 20 |
+
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
| 21 |
+
|
| 22 |
+
EXPOSE 7860
|
| 23 |
+
|
| 24 |
+
CMD ["uvicorn", "backend.main_api:app", "--host", "0.0.0.0", "--port", "7860"]
|
external/Quant.ai/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Quant.ai - Advanced Quant Trading Engine & Backtest Simulator
|
| 2 |
|
| 3 |
<p align="center">
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Quant AI - Advanced Quant Trading Engine & Backtest Simulator
|
| 3 |
+
emoji: 📈
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: static
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
# Quant.ai - Advanced Quant Trading Engine & Backtest Simulator
|
| 11 |
|
| 12 |
<p align="center">
|
external/Quant.ai/assets/index-C6qvxrC6.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
external/Quant.ai/assets/index-DCSOhC_L.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;color:#fff;--color-green:#00c805;--color-red:#ff3b30;--color-bg-dark:#000;--color-bg-card:#1e1e1e;--color-border:#333;--color-text-secondary:#8e8e93;background-color:#000;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-weight:400;line-height:1.5}body{background-color:var(--color-bg-dark);min-height:100vh;margin:0;padding:0}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:#000}::-webkit-scrollbar-thumb{background:#2c2c2e;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#3a3a3c}.header-bar{border-bottom:1px solid var(--color-border);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background-color:#1e1e1e33;justify-content:space-between;align-items:center;padding:1rem 2rem;display:flex}.logo{color:#fff;align-items:center;gap:.5rem;font-size:1.5rem;font-weight:800;display:flex}.logo span{color:var(--color-green)}.app-container{background-color:#000;grid-template-columns:1fr 300px;min-height:calc(100vh - 65px);display:grid}.main-content{border-right:1px solid var(--color-border);flex-direction:column;gap:2rem;padding:2rem;display:flex;overflow-y:auto}.sidebar{background-color:#000;flex-direction:column;gap:1.5rem;padding:2rem 1.5rem;display:flex}.sidebar-title{color:#fff;border-bottom:1px solid var(--color-border);margin:0 0 .5rem;padding-bottom:.5rem;font-size:1.1rem;font-weight:700}.watchlist-item{cursor:pointer;background-color:#1c1c1e;border:1px solid #0000;border-radius:8px;justify-content:space-between;align-items:center;padding:.75rem 1rem;transition:all .2s;display:flex}.watchlist-item:hover{border-color:var(--color-green);background-color:#2c2c2e}.watchlist-item.active{border-color:var(--color-green);background-color:#00c8051a}.watchlist-ticker{font-size:1.1rem;font-weight:700}.watchlist-price{text-align:right;font-weight:600}.pnl-container{flex-direction:column;gap:.25rem;display:flex}.portfolio-value{letter-spacing:-1px;font-size:3rem;font-weight:800}.pnl-text{align-items:center;gap:.25rem;font-size:1.1rem;font-weight:600;display:flex}.pnl-text.up{color:var(--color-green)}.pnl-text.down{color:var(--color-red)}.time-tabs{gap:.5rem;margin-top:1rem;display:flex}.tab-btn{color:var(--color-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:.4rem .8rem;font-size:.9rem;font-weight:700;transition:all .2s}.tab-btn:hover{color:#fff;background-color:#1c1c1e}.tab-btn.active{color:var(--color-green);background-color:#00c8051a}.card{border:1px solid var(--color-border);background-color:#1c1c1e;border-radius:12px;padding:1.5rem}.card-title{color:#fff;margin:0 0 1rem;font-size:1.1rem;font-weight:700}.stats-grid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1.5rem;display:grid}.stat-box{flex-direction:column;gap:.25rem;display:flex}.stat-label{color:var(--color-text-secondary);font-size:.85rem;font-weight:600}.stat-value{font-size:1.4rem;font-weight:800}.ledger-table{border-collapse:collapse;text-align:left;width:100%}.ledger-table th{color:var(--color-text-secondary);border-bottom:1px solid var(--color-border);padding:.75rem 1rem;font-size:.9rem;font-weight:600}.ledger-table td{border-bottom:1px solid var(--color-border);padding:1rem;font-size:.95rem}.ledger-table tr:hover{background-color:#ffffff05}.action-badge{border-radius:4px;padding:.25rem .5rem;font-size:.8rem;font-weight:700}.action-badge.buy{color:var(--color-green);background-color:#00c80526}.action-badge.sell{color:var(--color-red);background-color:#ff3b3026}.chart-wrapper{border:1px solid var(--color-border);background-color:#000;border-radius:12px;width:100%;height:400px;position:relative;overflow:hidden}.loader-container{border:1px solid var(--color-border);height:400px;color:var(--color-text-secondary);background-color:#1c1c1e;border-radius:12px;justify-content:center;align-items:center;font-size:1.2rem;font-weight:600;display:flex}input[type=range]{-webkit-appearance:none;background:#2c2c2e;border-radius:3px;outline:none;width:100%;height:6px;margin:10px 0}input[type=range]::-webkit-slider-thumb{appearance:none;background:var(--color-green);cursor:pointer;border-radius:50%;width:16px;height:16px;transition:transform .1s}input[type=range]::-webkit-slider-thumb:hover{transform:scale(1.25)}select:focus,input[type=text]:focus{outline:none;box-shadow:0 0 0 1px #00c80533;border-color:var(--color-green)!important}.ledger-table td button{transition:all .2s}.pnl-header-container{flex-flow:wrap;justify-content:space-between;align-items:flex-end;gap:1rem;display:flex}.interval-picker-container{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.strategy-patterns-grid{grid-template-columns:1.2fr .8fr;gap:2rem;display:grid}.nav-tabs{border:1px solid var(--color-border);background:#1c1c1e;border-radius:10px;gap:4px;padding:3px;display:flex}.nav-tab{color:var(--color-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;padding:8px 20px;font-size:.85rem;font-weight:700;transition:all .25s}.nav-tab:hover{color:#fff;background:#ffffff0d}.nav-tab.active{color:#000;background:var(--color-green)}.stats-divider{background:var(--color-border);height:1px;margin:1.25rem 0}.chat-panel{border:1px solid var(--color-border);background:#111;border-radius:16px;flex-direction:column;max-height:700px;display:flex;overflow:hidden}.chat-header{border-bottom:1px solid var(--color-border);background:#1e1e1e99;justify-content:space-between;align-items:center;padding:14px 20px;display:flex}.chat-header-title{align-items:center;gap:8px;font-size:1rem;font-weight:700;display:flex}.chat-icon{font-size:1.2rem}.chat-status{color:var(--color-green);font-size:.8rem;font-weight:600}.chat-messages{flex-direction:column;flex:1;gap:12px;max-height:500px;padding:16px;display:flex;overflow-y:auto}.chat-msg{display:flex}.chat-msg-user{justify-content:flex-end}.chat-msg-bubble{word-break:break-word;border-radius:16px;max-width:85%;padding:12px 16px;font-size:.9rem;line-height:1.6}.chat-msg-user-bubble{background:var(--color-green);color:#000;border-bottom-right-radius:4px;font-weight:600}.chat-msg-system-bubble{color:var(--color-text-secondary);background:#8e8e931f;border-bottom-left-radius:4px;font-size:.85rem}.chat-msg-assistant-bubble{color:#e0e0e0;border:1px solid var(--color-border);background:#1c1c1e;border-bottom-left-radius:4px}.chat-config-preview{background:#0000004d;border:1px solid #ffffff0f;border-radius:10px;margin-top:12px;padding:12px}.chat-config-title{color:#fff;margin-bottom:10px;font-size:.85rem;font-weight:700}.chat-config-grid{grid-template-columns:1fr 1fr;gap:6px 16px;display:grid}.chat-config-item{justify-content:space-between;padding:3px 0;font-size:.8rem;display:flex}.chat-config-key{color:var(--color-text-secondary)}.chat-config-val{color:var(--color-green);font-family:SF Mono,Fira Code,monospace;font-weight:600}.chat-plan{background:#0000004d;border:1px solid #ffffff0f;border-radius:10px;margin-top:12px;padding:12px}.chat-plan-step{align-items:flex-start;gap:10px;padding:6px 0;display:flex}.chat-plan-step-num{background:var(--color-green);color:#000;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;width:22px;height:22px;font-size:.7rem;font-weight:800;display:flex}.chat-plan-tool{color:#af52de;font-family:SF Mono,Fira Code,monospace;font-size:.8rem;font-weight:700;display:block}.chat-plan-desc{color:var(--color-text-secondary);font-size:.8rem}.chat-risk-report{background:#0006;border:1px solid #ffffff14;border-radius:12px;margin-top:12px;padding:16px}.chat-risk-header{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.risk-score-badge{text-align:center;border:2px solid;border-radius:12px;min-width:70px;padding:6px 16px}.risk-score-value{font-size:1.6rem;font-weight:800;line-height:1}.risk-score-label{text-transform:uppercase;letter-spacing:.5px;font-size:.65rem;font-weight:700}.chat-risk-section{border-left:3px solid var(--color-border);background:#ffffff05;border-radius:8px;margin-bottom:14px;padding:12px}.chat-risk-section.chat-risk-positive{border-left-color:var(--color-green)}.chat-risk-section.chat-risk-negative{border-left-color:var(--color-red)}.chat-risk-section.chat-risk-warning{border-left-color:#f5a623}.chat-risk-section.chat-risk-neutral{border-left-color:#af52de}.chat-risk-section-title{color:#fff;margin-bottom:8px;font-size:.9rem;font-weight:700}.chat-risk-insights{margin:0;padding:0;list-style:none}.chat-risk-insights li{color:silver;padding:3px 0 3px 12px;font-size:.82rem;line-height:1.6;position:relative}.chat-risk-insights li:before{content:"›";color:var(--color-text-secondary);font-weight:700;position:absolute;left:0}.chat-risk-insights li strong{color:#fff}.chat-risk-insights li code{color:#af52de;background:#af52de26;border-radius:4px;padding:1px 6px;font-family:SF Mono,Fira Code,monospace;font-size:.78rem}.chat-typing{gap:4px;display:flex;padding:16px 20px!important}.chat-typing .dot{background:var(--color-green);border-radius:50%;width:8px;height:8px;animation:1.4s ease-in-out infinite typingPulse}.chat-typing .dot:nth-child(2){animation-delay:.2s}.chat-typing .dot:nth-child(3){animation-delay:.4s}@keyframes typingPulse{0%,60%,to{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.chat-examples{border-top:1px solid var(--color-border);flex-wrap:wrap;gap:8px;padding:10px 16px;display:flex}.chat-example-btn{color:var(--color-green);cursor:pointer;background:#00c8050f;border:1px solid #00c80533;border-radius:20px;padding:6px 14px;font-size:.78rem;font-weight:500;transition:all .2s}.chat-example-btn:hover{border-color:var(--color-green);background:#00c8051f}.chat-input-area{border-top:1px solid var(--color-border);background:#141414cc;gap:8px;padding:12px 16px;display:flex}.chat-input{border:1px solid var(--color-border);color:#fff;resize:none;background:#1c1c1e;border-radius:12px;outline:none;flex:1;padding:10px 16px;font-family:inherit;font-size:.9rem;transition:border-color .2s}.chat-input:focus{border-color:var(--color-green)}.chat-input::placeholder{color:#555}.chat-send-btn{background:var(--color-green);cursor:pointer;border:none;border-radius:12px;flex-shrink:0;justify-content:center;align-items:center;width:44px;height:44px;font-size:1.1rem;transition:all .2s;display:flex}.chat-send-btn:hover:not(:disabled){transform:scale(1.05);box-shadow:0 0 20px #00c8054d}.chat-send-btn:disabled{opacity:.4;cursor:not-allowed}.equity-curve-card{padding-bottom:1rem}.equity-curve-header{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem;margin-bottom:.75rem;display:flex}.equity-curve-stats{gap:1.5rem;font-size:.8rem;display:flex}.equity-stat{color:var(--color-text-secondary);font-weight:600}.equity-stat strong{color:#fff}.equity-chart-label{color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;font-size:.75rem;font-weight:600}.equity-chart-container,.drawdown-chart-container{border:1px solid #ffffff0a;border-radius:8px;overflow:hidden}.regime-card{overflow:hidden}.regime-distribution{margin-bottom:1.25rem}.regime-bar{border-radius:4px;height:8px;margin-bottom:8px;display:flex;overflow:hidden}.regime-bar-segment{min-width:2px;transition:width .5s}.regime-bar-labels{flex-wrap:wrap;gap:12px;font-size:.75rem;font-weight:600;display:flex}.regime-bar-label{align-items:center;gap:4px;display:flex}.regime-table-wrapper{overflow-x:auto}.regime-table{border-collapse:collapse;text-align:left;width:100%}.regime-table th{color:var(--color-text-secondary);border-bottom:1px solid var(--color-border);text-transform:uppercase;letter-spacing:.3px;padding:8px 12px;font-size:.8rem;font-weight:600}.regime-table td{border-bottom:1px solid #ffffff0a;padding:10px 12px;font-size:.88rem}.regime-table tr:hover{background:#ffffff05}.regime-name{font-size:.85rem;font-weight:700}@media (width<=768px){.app-container{grid-template-columns:1fr}.header-bar{text-align:center;flex-direction:column;gap:.5rem;padding:1rem}.main-content{border-right:none;border-bottom:1px solid var(--color-border);gap:1.5rem;padding:1rem}.pnl-header-container{flex-direction:column;align-items:flex-start;gap:1rem}.interval-picker-container{align-items:flex-start;width:100%}.time-tabs{flex-wrap:wrap}.portfolio-value{font-size:2rem}.strategy-patterns-grid{grid-template-columns:1fr;gap:1.5rem}.chart-wrapper{height:300px}.sidebar{padding:1.5rem 1rem}.chat-config-grid{grid-template-columns:1fr}.chat-risk-header{flex-direction:column;align-items:flex-start;gap:10px}.equity-curve-stats{flex-wrap:wrap;gap:.75rem}}.research-panel{border:1px solid var(--color-border);background-color:#000;border-radius:16px;flex-direction:column;height:calc(100vh - 120px);display:flex;overflow:hidden}.research-layout{grid-template-columns:240px 1fr;height:100%;display:grid;overflow:hidden}.research-toc{border-right:1px solid var(--color-border);background-color:#0d0d0e;flex-direction:column;gap:1.5rem;padding:1.5rem 1rem;display:flex;overflow-y:auto}.toc-title{text-transform:uppercase;letter-spacing:.5px;color:#fff;border-bottom:1px solid var(--color-border);padding-bottom:.5rem;font-size:.95rem;font-weight:800}.toc-list{flex-direction:column;gap:4px;display:flex}.toc-item{color:var(--color-text-secondary);text-align:left;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;background:0 0;border:none;border-radius:6px;padding:8px 12px;font-size:.85rem;font-weight:600;transition:all .2s;overflow:hidden}.toc-item:hover{color:#fff;background-color:#ffffff0a}.toc-item.active{color:#000;background-color:var(--color-green);font-weight:700}.toc-apply-widget{border:1px solid var(--color-border);background-color:#ffffff05;border-radius:8px;margin-top:auto;padding:12px}.toc-widget-title{color:#fff;text-transform:uppercase;margin-bottom:6px;font-size:.78rem;font-weight:750}.btn-apply-preset{cursor:pointer;text-align:left;border:none;border-radius:6px;align-items:center;width:100%;margin-bottom:6px;padding:8px 10px;font-size:.75rem;font-weight:700;transition:all .2s;display:flex}.btn-apply-preset.dynamic{color:var(--color-green);background:#00c8051a;border:1px solid #00c80533}.btn-apply-preset.dynamic:hover{background:var(--color-green);color:#000}.btn-apply-preset.trend{color:#bf5af2;background:#af52de1a;border:1px solid #af52de33}.btn-apply-preset.trend:hover{color:#000;background:#bf5af2}.btn-apply-preset.reversion{color:#ff9f0a;background:#f5a6231a;border:1px solid #f5a62333}.btn-apply-preset.reversion:hover{color:#000;background:#ff9f0a}.research-doc-body{scroll-behavior:smooth;background-color:#000;flex-direction:column;gap:3rem;padding:2.5rem;display:flex;overflow-y:auto}.doc-section-card{border:1px solid var(--color-border);background-color:#121214;border-radius:12px;padding:1.5rem;transition:border-color .4s,box-shadow .4s}.doc-section-card.highlight{border-color:#00c8054d;box-shadow:0 0 20px #00c8050d}.doc-section-title{color:#fff;border-left:4px solid var(--color-green);margin-top:0;margin-bottom:1.25rem;padding-left:10px;font-size:1.4rem;font-weight:800}.doc-heading3{color:#fff;border-bottom:1px solid #ffffff0a;margin-top:1.5rem;margin-bottom:.75rem;padding-bottom:4px;font-size:1.05rem;font-weight:700}.doc-paragraph{color:#e5e5e7;letter-spacing:-.01em;margin-bottom:1.25rem;font-size:.98rem;line-height:1.75}.doc-paragraph code,.doc-list li code,.doc-alert-box code{color:#ff9f0a;background:#ffffff14;border-radius:4px;padding:2px 5px;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:.85rem}.doc-list{margin:1rem 0 1.25rem;padding-left:1.5rem}.doc-list li{color:#e5e5e7;letter-spacing:-.01em;margin-bottom:.6rem;font-size:.98rem;line-height:1.75}.code-lang-label{text-transform:uppercase;color:var(--color-text-secondary);font-family:monospace;font-size:.65rem;font-weight:700;position:absolute;top:8px;right:12px}.doc-code-block{border:1px solid var(--color-border);border-radius:6px;margin:0;overflow-x:auto}.doc-alert-box.alert-info,.doc-alert-box.alert-note{color:#a4d3ff;border-left-color:#0a84ff}.doc-alert-box.alert-tip{border-left-color:var(--color-green);color:#d0ffd4}.doc-alert-box.alert-important{color:#f5d6ff;border-left-color:#bf5af2}.doc-alert-box.alert-warning{color:#ffebcc;border-left-color:#ff9f0a}.doc-alert-box.alert-caution{border-left-color:var(--color-red);color:#ffd6d4}.widget-header{color:#fff;align-items:center;gap:8px;margin-top:0;margin-bottom:.75rem;font-size:1rem;font-weight:700;display:flex}.calc-input-group label{color:#dedede;margin-bottom:4px;font-size:.82rem;display:block}.calc-stat-box{flex-direction:column;display:flex}.calc-stat-label{color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px;font-size:.72rem;font-weight:700}.feasibility-badge{border-radius:6px;width:fit-content;padding:4px 10px;font-size:.8rem;display:inline-block}.feasibility-badge.danger{color:var(--color-red);background:#ff3b3026}.feasibility-badge.warning{color:#ff9f0a;background:#f5a62326}.feasibility-badge.moderate{color:#bf5af2;background:#af52de26}.feasibility-badge.success{color:var(--color-green);background:#00c80526}.market-detail-row{color:#dedede;margin-bottom:8px;font-size:.82rem;line-height:1.5}.market-detail-row strong{color:#fff}.candlestick-card{transition:all .25s;overflow:hidden}.candlestick-card:hover{border-color:#ffffff26;transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.candlestick-card.border-bullish{border-left:3px solid var(--color-green)!important}.candlestick-card.border-bearish{border-left:3px solid var(--color-red)!important}.candlestick-card.border-neutral{border-left:3px solid var(--color-text-secondary)!important}.badge-type.bullish{color:var(--color-green);background:#00c80526}.badge-type.bearish{color:var(--color-red);background:#ff3b3026}.badge-type.neutral{color:var(--color-text-secondary);background:#8e8e9326}.checklist-item-row:hover{background-color:#ffffff05!important;border-color:#ffffff1a!important}.code-links-grid{grid-template-columns:1fr 1fr;gap:10px;display:grid}.code-link-item{background:#111;border:1px solid #222;border-radius:8px;align-items:center;gap:12px;padding:12px;text-decoration:none;transition:all .2s;display:flex}.code-link-item:hover{border-color:var(--color-green);background:#ffffff08;transform:translate(2px)}.file-icon{opacity:.8;font-size:1.5rem}.code-link-item div{flex-direction:column;display:flex}.code-link-item div strong{color:#fff;font-family:monospace;font-size:.85rem}.code-link-item div span{color:var(--color-text-secondary);margin-top:2px;font-size:.72rem}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes pulse{0%{opacity:.6;transform:scale(1)}50%{opacity:1;transform:scale(1.1)}to{opacity:.6;transform:scale(1)}}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
external/Quant.ai/backend/app/broker/live_runner.py
CHANGED
|
@@ -15,6 +15,73 @@ from app.data_manager import fetch_and_prepare_data
|
|
| 15 |
from app.data_cache import invalidate_cache
|
| 16 |
from app.strategy import evaluate_market_state
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
class LiveTradingRunner:
|
| 19 |
def __init__(self):
|
| 20 |
self.is_running = False
|
|
@@ -35,12 +102,18 @@ class LiveTradingRunner:
|
|
| 35 |
"market_open_focus": True
|
| 36 |
}
|
| 37 |
self.ignore_market_hours = True # Set to True by default to allow testing anytime
|
| 38 |
-
self.adapter =
|
| 39 |
|
| 40 |
def add_log(self, msg: str):
|
| 41 |
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 42 |
full_msg = f"[{timestamp}] {msg}"
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
self.logs.append(full_msg)
|
| 45 |
if len(self.logs) > 200: # Restrict log size
|
| 46 |
self.logs.pop(0)
|
|
@@ -57,22 +130,31 @@ class LiveTradingRunner:
|
|
| 57 |
|
| 58 |
# Initialize Adapter
|
| 59 |
try:
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
except Exception as e:
|
| 68 |
-
self.
|
| 69 |
-
|
| 70 |
|
| 71 |
self.is_running = True
|
| 72 |
self.add_log(f"🚀 量化交易机器人已启动!监控列表: {self.active_tickers} | 策略模式: {self.strategy_params['strategy_mode']}")
|
| 73 |
|
| 74 |
-
# Spawn async loop task
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
return True
|
| 77 |
|
| 78 |
def stop(self):
|
|
|
|
| 15 |
from app.data_cache import invalidate_cache
|
| 16 |
from app.strategy import evaluate_market_state
|
| 17 |
|
| 18 |
+
class MockAlpacaAdapter:
|
| 19 |
+
def __init__(self):
|
| 20 |
+
self.cash = 100000.0
|
| 21 |
+
self.equity = 100000.0
|
| 22 |
+
self.positions = {}
|
| 23 |
+
|
| 24 |
+
def get_account_summary(self) -> Dict:
|
| 25 |
+
return {
|
| 26 |
+
"success": True,
|
| 27 |
+
"account_number": "MOCK_PAPER_9988",
|
| 28 |
+
"status": "ACTIVE (本地虚拟盘)",
|
| 29 |
+
"currency": "USD",
|
| 30 |
+
"cash": self.cash,
|
| 31 |
+
"portfolio_value": self.equity,
|
| 32 |
+
"buying_power": self.cash * 2,
|
| 33 |
+
"multiplier": 2.0,
|
| 34 |
+
"shorting_enabled": True,
|
| 35 |
+
"equity": self.equity,
|
| 36 |
+
"initial_margin": 0.0,
|
| 37 |
+
"maintenance_margin": 0.0,
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
def get_open_positions(self) -> List[Dict]:
|
| 41 |
+
res = []
|
| 42 |
+
for ticker, pos in self.positions.items():
|
| 43 |
+
res.append({
|
| 44 |
+
"ticker": ticker,
|
| 45 |
+
"shares": pos["shares"],
|
| 46 |
+
"avg_entry_price": pos["avg_entry_price"],
|
| 47 |
+
"market_value": round(pos["shares"] * pos.get("current_price", pos["avg_entry_price"]), 2),
|
| 48 |
+
"current_price": round(pos.get("current_price", pos["avg_entry_price"]), 2),
|
| 49 |
+
"unrealized_pnl": round((pos.get("current_price", pos["avg_entry_price"]) - pos["avg_entry_price"]) * pos["shares"], 2),
|
| 50 |
+
"unrealized_pnl_pct": round(((pos.get("current_price", pos["avg_entry_price"]) - pos["avg_entry_price"]) / pos["avg_entry_price"]) * 100, 2) if pos["avg_entry_price"] > 0 else 0.0
|
| 51 |
+
})
|
| 52 |
+
return res
|
| 53 |
+
|
| 54 |
+
def get_position(self, symbol: str) -> Optional[Dict]:
|
| 55 |
+
symbol = symbol.upper()
|
| 56 |
+
if symbol in self.positions:
|
| 57 |
+
pos = self.positions[symbol]
|
| 58 |
+
return {
|
| 59 |
+
"ticker": symbol,
|
| 60 |
+
"shares": pos["shares"],
|
| 61 |
+
"avg_entry_price": pos["avg_entry_price"],
|
| 62 |
+
"market_value": round(pos["shares"] * pos.get("current_price", pos["avg_entry_price"]), 2),
|
| 63 |
+
"current_price": round(pos.get("current_price", pos["avg_entry_price"]), 2),
|
| 64 |
+
"unrealized_pnl": round((pos.get("current_price", pos["avg_entry_price"]) - pos["avg_entry_price"]) * pos["shares"], 2),
|
| 65 |
+
"unrealized_pnl_pct": round(((pos.get("current_price", pos["avg_entry_price"]) - pos["avg_entry_price"]) / pos["avg_entry_price"]) * 100, 2) if pos["avg_entry_price"] > 0 else 0.0
|
| 66 |
+
}
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
def submit_market_order(self, symbol: str, qty: int, side: str) -> Dict:
|
| 70 |
+
symbol = symbol.upper()
|
| 71 |
+
if side.lower() == "buy":
|
| 72 |
+
self.positions[symbol] = {"shares": qty, "avg_entry_price": 100.0, "current_price": 100.0}
|
| 73 |
+
else:
|
| 74 |
+
if symbol in self.positions:
|
| 75 |
+
del self.positions[symbol]
|
| 76 |
+
return {"status": "filled", "id": "mock_order_123"}
|
| 77 |
+
|
| 78 |
+
def cancel_all_orders(self) -> Dict:
|
| 79 |
+
return {"success": True, "message": "已成功撤销所有模拟挂单"}
|
| 80 |
+
|
| 81 |
+
def close_all_positions(self) -> Dict:
|
| 82 |
+
self.positions.clear()
|
| 83 |
+
return {"success": True, "message": "已成功平仓所有模拟持仓"}
|
| 84 |
+
|
| 85 |
class LiveTradingRunner:
|
| 86 |
def __init__(self):
|
| 87 |
self.is_running = False
|
|
|
|
| 102 |
"market_open_focus": True
|
| 103 |
}
|
| 104 |
self.ignore_market_hours = True # Set to True by default to allow testing anytime
|
| 105 |
+
self.adapter = MockAlpacaAdapter()
|
| 106 |
|
| 107 |
def add_log(self, msg: str):
|
| 108 |
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 109 |
full_msg = f"[{timestamp}] {msg}"
|
| 110 |
+
try:
|
| 111 |
+
print(full_msg) # Print to server console
|
| 112 |
+
except Exception:
|
| 113 |
+
try:
|
| 114 |
+
print(full_msg.encode('ascii', errors='ignore').decode('ascii'))
|
| 115 |
+
except Exception:
|
| 116 |
+
pass
|
| 117 |
self.logs.append(full_msg)
|
| 118 |
if len(self.logs) > 200: # Restrict log size
|
| 119 |
self.logs.pop(0)
|
|
|
|
| 130 |
|
| 131 |
# Initialize Adapter
|
| 132 |
try:
|
| 133 |
+
if ALPACA_API_KEY and "your_paper_api_key_here" not in ALPACA_API_KEY:
|
| 134 |
+
self.adapter = AlpacaAdapter(
|
| 135 |
+
api_key=ALPACA_API_KEY,
|
| 136 |
+
api_secret=ALPACA_SECRET_KEY,
|
| 137 |
+
base_url=ALPACA_BASE_URL
|
| 138 |
+
)
|
| 139 |
+
self.adapter.get_account_summary()
|
| 140 |
+
self.add_log("🟢 已成功连接 Alpaca 实盘/模拟盘 API。")
|
| 141 |
+
else:
|
| 142 |
+
self.adapter = MockAlpacaAdapter()
|
| 143 |
+
self.add_log("💡 未检测到 Alpaca API Key,已自动切换至【本地虚拟模拟盘模式】。")
|
| 144 |
except Exception as e:
|
| 145 |
+
self.adapter = MockAlpacaAdapter()
|
| 146 |
+
self.add_log(f"💡 连接 Alpaca 失败 ({str(e)}),已自动平滑切换至【本地虚拟模拟盘模式】。")
|
| 147 |
|
| 148 |
self.is_running = True
|
| 149 |
self.add_log(f"🚀 量化交易机器人已启动!监控列表: {self.active_tickers} | 策略模式: {self.strategy_params['strategy_mode']}")
|
| 150 |
|
| 151 |
+
# Spawn async loop task safely
|
| 152 |
+
try:
|
| 153 |
+
loop = asyncio.get_running_loop()
|
| 154 |
+
self.loop_task = loop.create_task(self._run_loop())
|
| 155 |
+
except RuntimeError:
|
| 156 |
+
# Fallback if called outside event loop
|
| 157 |
+
pass
|
| 158 |
return True
|
| 159 |
|
| 160 |
def stop(self):
|
external/Quant.ai/backend/main_api.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
from fastapi import FastAPI, Query
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
| 5 |
from pydantic import BaseModel
|
| 6 |
from typing import Optional
|
| 7 |
import uvicorn
|
|
@@ -1377,6 +1378,25 @@ def close_all_positions():
|
|
| 1377 |
return {"success": False, "error": str(e)}
|
| 1378 |
|
| 1379 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1380 |
if __name__ == "__main__":
|
| 1381 |
print("启动 FastAPI 服务器于 http://127.0.0.1:8000 ...")
|
| 1382 |
uvicorn.run("main_api:app", host="127.0.0.1", port=8000, reload=True)
|
|
|
|
| 1 |
+
import os
|
|
|
|
| 2 |
from fastapi import FastAPI, Query
|
| 3 |
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
from fastapi.staticfiles import StaticFiles
|
| 5 |
+
from fastapi.responses import FileResponse
|
| 6 |
from pydantic import BaseModel
|
| 7 |
from typing import Optional
|
| 8 |
import uvicorn
|
|
|
|
| 1378 |
return {"success": False, "error": str(e)}
|
| 1379 |
|
| 1380 |
|
| 1381 |
+
# 静态文件托管(前端 React 构建产物)
|
| 1382 |
+
_backend_dir = os.path.dirname(os.path.abspath(__file__))
|
| 1383 |
+
_dist_dir = os.path.join(os.path.dirname(_backend_dir), "frontend", "dist")
|
| 1384 |
+
|
| 1385 |
+
if os.path.exists(_dist_dir):
|
| 1386 |
+
_assets_dir = os.path.join(_dist_dir, "assets")
|
| 1387 |
+
if os.path.exists(_assets_dir):
|
| 1388 |
+
app.mount("/assets", StaticFiles(directory=_assets_dir), name="assets")
|
| 1389 |
+
|
| 1390 |
+
@app.get("/{full_path:path}")
|
| 1391 |
+
async def serve_spa(full_path: str):
|
| 1392 |
+
if full_path.startswith("api/"):
|
| 1393 |
+
return None
|
| 1394 |
+
target_file = os.path.join(_dist_dir, full_path)
|
| 1395 |
+
if full_path and os.path.exists(target_file) and os.path.isfile(target_file):
|
| 1396 |
+
return FileResponse(target_file)
|
| 1397 |
+
return FileResponse(os.path.join(_dist_dir, "index.html"))
|
| 1398 |
+
|
| 1399 |
+
|
| 1400 |
if __name__ == "__main__":
|
| 1401 |
print("启动 FastAPI 服务器于 http://127.0.0.1:8000 ...")
|
| 1402 |
uvicorn.run("main_api:app", host="127.0.0.1", port=8000, reload=True)
|
external/Quant.ai/frontend/src/App.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import { WalkForwardPanel } from './components/WalkForwardPanel';
|
|
| 17 |
import { ExperimentCompare } from './components/ExperimentCompare';
|
| 18 |
import { BrokerPanel } from './components/BrokerPanel';
|
| 19 |
import { SameDayReplayPanel } from './components/SameDayReplayPanel';
|
|
|
|
| 20 |
|
| 21 |
interface SummaryData {
|
| 22 |
initial_cash: number;
|
|
@@ -185,7 +186,7 @@ function App() {
|
|
| 185 |
const runAiTuning = async () => {
|
| 186 |
setTuningLoading(true);
|
| 187 |
try {
|
| 188 |
-
const response = await fetch(
|
| 189 |
method: 'POST',
|
| 190 |
headers: { 'Content-Type': 'application/json' },
|
| 191 |
body: JSON.stringify({
|
|
@@ -236,7 +237,7 @@ function App() {
|
|
| 236 |
market_open_focus: String(strategyParams.market_open_focus)
|
| 237 |
});
|
| 238 |
|
| 239 |
-
const res = await fetch(`
|
| 240 |
const json: BacktestResponse = await res.json();
|
| 241 |
|
| 242 |
if (json.success) {
|
|
@@ -270,7 +271,7 @@ function App() {
|
|
| 270 |
const fetchCompanyDetails = async () => {
|
| 271 |
setInfoLoading(true);
|
| 272 |
try {
|
| 273 |
-
const res = await fetch(`
|
| 274 |
const json = await res.json();
|
| 275 |
setCompanyInfo(json);
|
| 276 |
} catch (e) {
|
|
@@ -289,7 +290,7 @@ function App() {
|
|
| 289 |
for (const ticker of watchlist) {
|
| 290 |
if (ticker === activeTicker) continue;
|
| 291 |
try {
|
| 292 |
-
const res = await fetch(`
|
| 293 |
const json: BacktestResponse = await res.json();
|
| 294 |
if (json.success && json.candles.length > 0) {
|
| 295 |
const lastCandle = json.candles[json.candles.length - 1];
|
|
@@ -331,7 +332,7 @@ function App() {
|
|
| 331 |
if (activeTab === 'replay') {
|
| 332 |
const fetchDates = async () => {
|
| 333 |
try {
|
| 334 |
-
const res = await fetch(`
|
| 335 |
const json = await res.json();
|
| 336 |
if (json.success && json.dates.length > 0) {
|
| 337 |
setAvailableDates(json.dates);
|
|
@@ -368,7 +369,7 @@ function App() {
|
|
| 368 |
slippage_rate: String(strategyParams.slippage_rate),
|
| 369 |
market_open_focus: String(strategyParams.market_open_focus),
|
| 370 |
});
|
| 371 |
-
const res = await fetch(`
|
| 372 |
const json = await res.json();
|
| 373 |
if (json.success) {
|
| 374 |
setReplayData(json);
|
|
@@ -420,7 +421,7 @@ function App() {
|
|
| 420 |
setZoomCandles([]);
|
| 421 |
try {
|
| 422 |
const dateStr = item.timestamp.split(' ')[0]; // YYYY-MM-DD
|
| 423 |
-
const res = await fetch(`
|
| 424 |
const json = await res.json();
|
| 425 |
if (json.success) {
|
| 426 |
setZoomCandles(json.candles);
|
|
@@ -828,7 +829,7 @@ function App() {
|
|
| 828 |
</>
|
| 829 |
) : (
|
| 830 |
<div className="loader-container">
|
| 831 |
-
Cannot connect to backend. Please ensure
|
| 832 |
</div>
|
| 833 |
)
|
| 834 |
)}
|
|
|
|
| 17 |
import { ExperimentCompare } from './components/ExperimentCompare';
|
| 18 |
import { BrokerPanel } from './components/BrokerPanel';
|
| 19 |
import { SameDayReplayPanel } from './components/SameDayReplayPanel';
|
| 20 |
+
import { API_BASE } from './config';
|
| 21 |
|
| 22 |
interface SummaryData {
|
| 23 |
initial_cash: number;
|
|
|
|
| 186 |
const runAiTuning = async () => {
|
| 187 |
setTuningLoading(true);
|
| 188 |
try {
|
| 189 |
+
const response = await fetch(`${API_BASE}/api/ai_tune`, {
|
| 190 |
method: 'POST',
|
| 191 |
headers: { 'Content-Type': 'application/json' },
|
| 192 |
body: JSON.stringify({
|
|
|
|
| 237 |
market_open_focus: String(strategyParams.market_open_focus)
|
| 238 |
});
|
| 239 |
|
| 240 |
+
const res = await fetch(`${API_BASE}/api/backtest?${queryParams.toString()}`);
|
| 241 |
const json: BacktestResponse = await res.json();
|
| 242 |
|
| 243 |
if (json.success) {
|
|
|
|
| 271 |
const fetchCompanyDetails = async () => {
|
| 272 |
setInfoLoading(true);
|
| 273 |
try {
|
| 274 |
+
const res = await fetch(`${API_BASE}/api/company_info?ticker=${activeTicker}`);
|
| 275 |
const json = await res.json();
|
| 276 |
setCompanyInfo(json);
|
| 277 |
} catch (e) {
|
|
|
|
| 290 |
for (const ticker of watchlist) {
|
| 291 |
if (ticker === activeTicker) continue;
|
| 292 |
try {
|
| 293 |
+
const res = await fetch(`${API_BASE}/api/backtest?ticker=${ticker}&interval=1d`);
|
| 294 |
const json: BacktestResponse = await res.json();
|
| 295 |
if (json.success && json.candles.length > 0) {
|
| 296 |
const lastCandle = json.candles[json.candles.length - 1];
|
|
|
|
| 332 |
if (activeTab === 'replay') {
|
| 333 |
const fetchDates = async () => {
|
| 334 |
try {
|
| 335 |
+
const res = await fetch(`${API_BASE}/api/replay/available_dates?ticker=${activeTicker}`);
|
| 336 |
const json = await res.json();
|
| 337 |
if (json.success && json.dates.length > 0) {
|
| 338 |
setAvailableDates(json.dates);
|
|
|
|
| 369 |
slippage_rate: String(strategyParams.slippage_rate),
|
| 370 |
market_open_focus: String(strategyParams.market_open_focus),
|
| 371 |
});
|
| 372 |
+
const res = await fetch(`${API_BASE}/api/replay/data?${params.toString()}`);
|
| 373 |
const json = await res.json();
|
| 374 |
if (json.success) {
|
| 375 |
setReplayData(json);
|
|
|
|
| 421 |
setZoomCandles([]);
|
| 422 |
try {
|
| 423 |
const dateStr = item.timestamp.split(' ')[0]; // YYYY-MM-DD
|
| 424 |
+
const res = await fetch(`${API_BASE}/api/intraday_data?ticker=${item.ticker}&date=${dateStr}`);
|
| 425 |
const json = await res.json();
|
| 426 |
if (json.success) {
|
| 427 |
setZoomCandles(json.candles);
|
|
|
|
| 829 |
</>
|
| 830 |
) : (
|
| 831 |
<div className="loader-container">
|
| 832 |
+
Cannot connect to backend API server ({API_BASE}). Please ensure backend service is running.
|
| 833 |
</div>
|
| 834 |
)
|
| 835 |
)}
|
external/Quant.ai/frontend/src/components/BrokerPanel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
// frontend/src/components/BrokerPanel.tsx
|
| 2 |
|
| 3 |
-
import
|
|
|
|
| 4 |
|
| 5 |
interface AccountSummary {
|
| 6 |
success: boolean;
|
|
@@ -35,7 +36,7 @@ export function BrokerPanel() {
|
|
| 35 |
const fetchBrokerData = async () => {
|
| 36 |
try {
|
| 37 |
// 1. Fetch account stats
|
| 38 |
-
const accRes = await fetch(
|
| 39 |
const accJson = await accRes.json();
|
| 40 |
if (accJson.success !== false) {
|
| 41 |
setAccount(accJson);
|
|
@@ -45,14 +46,14 @@ export function BrokerPanel() {
|
|
| 45 |
}
|
| 46 |
|
| 47 |
// 2. Fetch positions
|
| 48 |
-
const posRes = await fetch(
|
| 49 |
const posJson = await posRes.json();
|
| 50 |
if (posJson.success) {
|
| 51 |
setPositions(posJson.positions);
|
| 52 |
}
|
| 53 |
|
| 54 |
// 3. Fetch bot status and logs
|
| 55 |
-
const statusRes = await fetch(
|
| 56 |
const statusJson = await statusRes.json();
|
| 57 |
if (statusJson.success) {
|
| 58 |
setIsBotRunning(statusJson.status.is_running);
|
|
@@ -75,7 +76,7 @@ export function BrokerPanel() {
|
|
| 75 |
const handleStartBot = async () => {
|
| 76 |
setActionLoading("start");
|
| 77 |
try {
|
| 78 |
-
const res = await fetch(
|
| 79 |
method: 'POST',
|
| 80 |
headers: { 'Content-Type': 'application/json' },
|
| 81 |
body: JSON.stringify({ ignore_market_hours: true })
|
|
@@ -98,7 +99,7 @@ export function BrokerPanel() {
|
|
| 98 |
const handleStopBot = async () => {
|
| 99 |
setActionLoading("stop");
|
| 100 |
try {
|
| 101 |
-
const res = await fetch(
|
| 102 |
const json = await res.json();
|
| 103 |
setIsBotRunning(json.status.is_running);
|
| 104 |
} catch (e) {
|
|
@@ -113,7 +114,7 @@ export function BrokerPanel() {
|
|
| 113 |
if (!window.confirm("确定要撤销 Alpaca 账户中的所有未成交挂单吗?")) return;
|
| 114 |
setActionLoading("cancel_orders");
|
| 115 |
try {
|
| 116 |
-
const res = await fetch(
|
| 117 |
const json = await res.json();
|
| 118 |
alert(json.message || "撤单请求已发送");
|
| 119 |
} catch (e) {
|
|
@@ -128,7 +129,7 @@ export function BrokerPanel() {
|
|
| 128 |
if (!window.confirm("🚨 警告:这会以市价立即平仓所有股票持仓!确定继续吗?")) return;
|
| 129 |
setActionLoading("liquidate");
|
| 130 |
try {
|
| 131 |
-
const res = await fetch(
|
| 132 |
const json = await res.json();
|
| 133 |
alert(json.message || "清仓请求已发送");
|
| 134 |
} catch (e) {
|
|
@@ -182,7 +183,7 @@ export function BrokerPanel() {
|
|
| 182 |
: 'linear-gradient(135deg, #18181b 0%, #09090b 100%)',
|
| 183 |
border: isBotRunning ? '1px solid rgba(0, 200, 5, 0.4)' : '1px solid var(--color-border)',
|
| 184 |
display: 'flex',
|
| 185 |
-
|
| 186 |
alignItems: 'center',
|
| 187 |
flexWrap: 'wrap',
|
| 188 |
gap: '1.5rem'
|
|
|
|
| 1 |
// frontend/src/components/BrokerPanel.tsx
|
| 2 |
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
+
import { API_BASE } from '../config';
|
| 5 |
|
| 6 |
interface AccountSummary {
|
| 7 |
success: boolean;
|
|
|
|
| 36 |
const fetchBrokerData = async () => {
|
| 37 |
try {
|
| 38 |
// 1. Fetch account stats
|
| 39 |
+
const accRes = await fetch(`${API_BASE}/api/broker/account`);
|
| 40 |
const accJson = await accRes.json();
|
| 41 |
if (accJson.success !== false) {
|
| 42 |
setAccount(accJson);
|
|
|
|
| 46 |
}
|
| 47 |
|
| 48 |
// 2. Fetch positions
|
| 49 |
+
const posRes = await fetch(`${API_BASE}/api/broker/positions`);
|
| 50 |
const posJson = await posRes.json();
|
| 51 |
if (posJson.success) {
|
| 52 |
setPositions(posJson.positions);
|
| 53 |
}
|
| 54 |
|
| 55 |
// 3. Fetch bot status and logs
|
| 56 |
+
const statusRes = await fetch(`${API_BASE}/api/live/status`);
|
| 57 |
const statusJson = await statusRes.json();
|
| 58 |
if (statusJson.success) {
|
| 59 |
setIsBotRunning(statusJson.status.is_running);
|
|
|
|
| 76 |
const handleStartBot = async () => {
|
| 77 |
setActionLoading("start");
|
| 78 |
try {
|
| 79 |
+
const res = await fetch(`${API_BASE}/api/live/start`, {
|
| 80 |
method: 'POST',
|
| 81 |
headers: { 'Content-Type': 'application/json' },
|
| 82 |
body: JSON.stringify({ ignore_market_hours: true })
|
|
|
|
| 99 |
const handleStopBot = async () => {
|
| 100 |
setActionLoading("stop");
|
| 101 |
try {
|
| 102 |
+
const res = await fetch(`${API_BASE}/api/live/stop`, { method: 'POST' });
|
| 103 |
const json = await res.json();
|
| 104 |
setIsBotRunning(json.status.is_running);
|
| 105 |
} catch (e) {
|
|
|
|
| 114 |
if (!window.confirm("确定要撤销 Alpaca 账户中的所有未成交挂单吗?")) return;
|
| 115 |
setActionLoading("cancel_orders");
|
| 116 |
try {
|
| 117 |
+
const res = await fetch(`${API_BASE}/api/broker/cancel_orders`, { method: 'POST' });
|
| 118 |
const json = await res.json();
|
| 119 |
alert(json.message || "撤单请求已发送");
|
| 120 |
} catch (e) {
|
|
|
|
| 129 |
if (!window.confirm("🚨 警告:这会以市价立即平仓所有股票持仓!确定继续吗?")) return;
|
| 130 |
setActionLoading("liquidate");
|
| 131 |
try {
|
| 132 |
+
const res = await fetch(`${API_BASE}/api/broker/close_positions`, { method: 'POST' });
|
| 133 |
const json = await res.json();
|
| 134 |
alert(json.message || "清仓请求已发送");
|
| 135 |
} catch (e) {
|
|
|
|
| 183 |
: 'linear-gradient(135deg, #18181b 0%, #09090b 100%)',
|
| 184 |
border: isBotRunning ? '1px solid rgba(0, 200, 5, 0.4)' : '1px solid var(--color-border)',
|
| 185 |
display: 'flex',
|
| 186 |
+
justifyContent: 'space-between',
|
| 187 |
alignItems: 'center',
|
| 188 |
flexWrap: 'wrap',
|
| 189 |
gap: '1.5rem'
|
external/Quant.ai/frontend/src/components/ChatPanel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
// frontend/src/components/ChatPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState, useRef, useEffect } from 'react';
|
|
|
|
| 4 |
|
| 5 |
interface ChatMessage {
|
| 6 |
id: string;
|
|
@@ -71,7 +72,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ onRunBacktest, isLoading,
|
|
| 71 |
|
| 72 |
try {
|
| 73 |
// Step 1: Parse prompt → strategy config
|
| 74 |
-
const parseRes = await fetch(
|
| 75 |
method: 'POST',
|
| 76 |
headers: { 'Content-Type': 'application/json' },
|
| 77 |
body: JSON.stringify({ prompt: trimmed })
|
|
@@ -108,7 +109,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ onRunBacktest, isLoading,
|
|
| 108 |
// Step 4: Generate risk report
|
| 109 |
setReportLoading(true);
|
| 110 |
try {
|
| 111 |
-
const reportRes = await fetch(
|
| 112 |
method: 'POST',
|
| 113 |
headers: { 'Content-Type': 'application/json' },
|
| 114 |
body: JSON.stringify(config)
|
|
@@ -135,7 +136,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ onRunBacktest, isLoading,
|
|
| 135 |
setMessages(prev => [...prev, {
|
| 136 |
id: Date.now().toString(),
|
| 137 |
role: 'assistant',
|
| 138 |
-
content: `❌ Connection failed: ${e}. Make sure the backend is running on
|
| 139 |
timestamp: new Date()
|
| 140 |
}]);
|
| 141 |
}
|
|
|
|
| 1 |
// frontend/src/components/ChatPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState, useRef, useEffect } from 'react';
|
| 4 |
+
import { API_BASE } from '../config';
|
| 5 |
|
| 6 |
interface ChatMessage {
|
| 7 |
id: string;
|
|
|
|
| 72 |
|
| 73 |
try {
|
| 74 |
// Step 1: Parse prompt → strategy config
|
| 75 |
+
const parseRes = await fetch(`${API_BASE}/api/agent/research`, {
|
| 76 |
method: 'POST',
|
| 77 |
headers: { 'Content-Type': 'application/json' },
|
| 78 |
body: JSON.stringify({ prompt: trimmed })
|
|
|
|
| 109 |
// Step 4: Generate risk report
|
| 110 |
setReportLoading(true);
|
| 111 |
try {
|
| 112 |
+
const reportRes = await fetch(`${API_BASE}/api/report/generate`, {
|
| 113 |
method: 'POST',
|
| 114 |
headers: { 'Content-Type': 'application/json' },
|
| 115 |
body: JSON.stringify(config)
|
|
|
|
| 136 |
setMessages(prev => [...prev, {
|
| 137 |
id: Date.now().toString(),
|
| 138 |
role: 'assistant',
|
| 139 |
+
content: `❌ Connection failed: ${e}. Make sure the backend is running on ${API_BASE}`,
|
| 140 |
timestamp: new Date()
|
| 141 |
}]);
|
| 142 |
}
|
external/Quant.ai/frontend/src/components/ExperimentCompare.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
// frontend/src/components/ExperimentCompare.tsx
|
| 2 |
|
| 3 |
import React, { useEffect, useState } from 'react';
|
|
|
|
| 4 |
|
| 5 |
interface Experiment {
|
| 6 |
id: number;
|
|
@@ -54,7 +55,7 @@ export const ExperimentCompare: React.FC = () => {
|
|
| 54 |
setIsLoading(true);
|
| 55 |
setErrorMsg('');
|
| 56 |
try {
|
| 57 |
-
const res = await fetch(
|
| 58 |
const data = await res.json();
|
| 59 |
if (data.success) {
|
| 60 |
setExperiments(data.experiments);
|
|
@@ -62,7 +63,7 @@ export const ExperimentCompare: React.FC = () => {
|
|
| 62 |
setErrorMsg(data.error || '获取实验列表失败');
|
| 63 |
}
|
| 64 |
} catch (err) {
|
| 65 |
-
setErrorMsg(
|
| 66 |
} finally {
|
| 67 |
setIsLoading(false);
|
| 68 |
}
|
|
@@ -92,7 +93,7 @@ export const ExperimentCompare: React.FC = () => {
|
|
| 92 |
|
| 93 |
setIsLoading(true);
|
| 94 |
try {
|
| 95 |
-
const res = await fetch(
|
| 96 |
method: 'POST',
|
| 97 |
headers: { 'Content-Type': 'application/json' },
|
| 98 |
body: JSON.stringify({ ids: selectedIds }),
|
|
@@ -117,7 +118,7 @@ export const ExperimentCompare: React.FC = () => {
|
|
| 117 |
}
|
| 118 |
|
| 119 |
try {
|
| 120 |
-
const res = await fetch(`
|
| 121 |
method: 'DELETE',
|
| 122 |
});
|
| 123 |
const data = await res.json();
|
|
|
|
| 1 |
// frontend/src/components/ExperimentCompare.tsx
|
| 2 |
|
| 3 |
import React, { useEffect, useState } from 'react';
|
| 4 |
+
import { API_BASE } from '../config';
|
| 5 |
|
| 6 |
interface Experiment {
|
| 7 |
id: number;
|
|
|
|
| 55 |
setIsLoading(true);
|
| 56 |
setErrorMsg('');
|
| 57 |
try {
|
| 58 |
+
const res = await fetch(`${API_BASE}/api/experiments`);
|
| 59 |
const data = await res.json();
|
| 60 |
if (data.success) {
|
| 61 |
setExperiments(data.experiments);
|
|
|
|
| 63 |
setErrorMsg(data.error || '获取实验列表失败');
|
| 64 |
}
|
| 65 |
} catch (err) {
|
| 66 |
+
setErrorMsg(`无法获取实验列表,请检查后端 API 是否已启动 (${API_BASE})`);
|
| 67 |
} finally {
|
| 68 |
setIsLoading(false);
|
| 69 |
}
|
|
|
|
| 93 |
|
| 94 |
setIsLoading(true);
|
| 95 |
try {
|
| 96 |
+
const res = await fetch(`${API_BASE}/api/experiments/compare`, {
|
| 97 |
method: 'POST',
|
| 98 |
headers: { 'Content-Type': 'application/json' },
|
| 99 |
body: JSON.stringify({ ids: selectedIds }),
|
|
|
|
| 118 |
}
|
| 119 |
|
| 120 |
try {
|
| 121 |
+
const res = await fetch(`${API_BASE}/api/experiments/${id}`, {
|
| 122 |
method: 'DELETE',
|
| 123 |
});
|
| 124 |
const data = await res.json();
|
external/Quant.ai/frontend/src/components/ResearchReportPanel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
// frontend/src/components/ResearchReportPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState, useEffect } from 'react';
|
|
|
|
| 4 |
import type { StrategyParams } from './StrategySettings';
|
| 5 |
|
| 6 |
interface ComponentData {
|
|
@@ -96,7 +97,7 @@ export const ResearchReportPanel: React.FC<ResearchReportPanelProps> = ({ onAppl
|
|
| 96 |
const fetchReport = async () => {
|
| 97 |
setLoading(true);
|
| 98 |
try {
|
| 99 |
-
const res = await fetch(
|
| 100 |
const json = await res.json();
|
| 101 |
if (json.success) {
|
| 102 |
setReport(json);
|
|
|
|
| 1 |
// frontend/src/components/ResearchReportPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState, useEffect } from 'react';
|
| 4 |
+
import { API_BASE } from '../config';
|
| 5 |
import type { StrategyParams } from './StrategySettings';
|
| 6 |
|
| 7 |
interface ComponentData {
|
|
|
|
| 97 |
const fetchReport = async () => {
|
| 98 |
setLoading(true);
|
| 99 |
try {
|
| 100 |
+
const res = await fetch(`${API_BASE}/api/research_report`);
|
| 101 |
const json = await res.json();
|
| 102 |
if (json.success) {
|
| 103 |
setReport(json);
|
external/Quant.ai/frontend/src/components/SameDayReplayPanel.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
// frontend/src/components/SameDayReplayPanel.tsx
|
| 2 |
|
| 3 |
-
import
|
| 4 |
import { StockChart } from './StockChart';
|
| 5 |
import { LedgerTable, type LedgerItem } from './LedgerTable';
|
|
|
|
| 6 |
|
| 7 |
interface SameDayReplayPanelProps {
|
| 8 |
watchlist: string[];
|
|
@@ -21,7 +22,7 @@ export function SameDayReplayPanel({ watchlist, activeTicker, onSelectTicker }:
|
|
| 21 |
useEffect(() => {
|
| 22 |
const fetchDates = async () => {
|
| 23 |
try {
|
| 24 |
-
const res = await fetch(`
|
| 25 |
const json = await res.json();
|
| 26 |
if (json.success && json.dates.length > 0) {
|
| 27 |
setAvailableDates(json.dates);
|
|
@@ -56,7 +57,7 @@ export function SameDayReplayPanel({ watchlist, activeTicker, onSelectTicker }:
|
|
| 56 |
slippage_rate: '0.0003',
|
| 57 |
market_open_focus: 'true'
|
| 58 |
});
|
| 59 |
-
const res = await fetch(`
|
| 60 |
const json = await res.json();
|
| 61 |
if (json.success) {
|
| 62 |
setReplayData(json);
|
|
|
|
| 1 |
// frontend/src/components/SameDayReplayPanel.tsx
|
| 2 |
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
import { StockChart } from './StockChart';
|
| 5 |
import { LedgerTable, type LedgerItem } from './LedgerTable';
|
| 6 |
+
import { API_BASE } from '../config';
|
| 7 |
|
| 8 |
interface SameDayReplayPanelProps {
|
| 9 |
watchlist: string[];
|
|
|
|
| 22 |
useEffect(() => {
|
| 23 |
const fetchDates = async () => {
|
| 24 |
try {
|
| 25 |
+
const res = await fetch(`${API_BASE}/api/replay/available_dates?ticker=${activeTicker}`);
|
| 26 |
const json = await res.json();
|
| 27 |
if (json.success && json.dates.length > 0) {
|
| 28 |
setAvailableDates(json.dates);
|
|
|
|
| 57 |
slippage_rate: '0.0003',
|
| 58 |
market_open_focus: 'true'
|
| 59 |
});
|
| 60 |
+
const res = await fetch(`${API_BASE}/api/replay/data?${params.toString()}`);
|
| 61 |
const json = await res.json();
|
| 62 |
if (json.success) {
|
| 63 |
setReplayData(json);
|
external/Quant.ai/frontend/src/components/ScannerPanel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
// frontend/src/components/ScannerPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState, useEffect } from 'react';
|
|
|
|
| 4 |
|
| 5 |
export interface ScanResult {
|
| 6 |
ticker: string;
|
|
@@ -30,7 +31,7 @@ export const ScannerPanel: React.FC<ScannerPanelProps> = ({ customTickers, onSel
|
|
| 30 |
setError(null);
|
| 31 |
try {
|
| 32 |
const symbolsParam = customTickers.join(',');
|
| 33 |
-
const res = await fetch(`
|
| 34 |
const json = await res.json();
|
| 35 |
if (json.success) {
|
| 36 |
setResults(json.results);
|
|
|
|
| 1 |
// frontend/src/components/ScannerPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState, useEffect } from 'react';
|
| 4 |
+
import { API_BASE } from '../config';
|
| 5 |
|
| 6 |
export interface ScanResult {
|
| 7 |
ticker: string;
|
|
|
|
| 31 |
setError(null);
|
| 32 |
try {
|
| 33 |
const symbolsParam = customTickers.join(',');
|
| 34 |
+
const res = await fetch(`${API_BASE}/api/scan?tickers=${symbolsParam}`);
|
| 35 |
const json = await res.json();
|
| 36 |
if (json.success) {
|
| 37 |
setResults(json.results);
|
external/Quant.ai/frontend/src/components/WalkForwardPanel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
// frontend/src/components/WalkForwardPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState } from 'react';
|
|
|
|
| 4 |
|
| 5 |
interface WalkForwardPanelProps {
|
| 6 |
activeTicker: string;
|
|
@@ -69,7 +70,7 @@ export const WalkForwardPanel: React.FC<WalkForwardPanelProps> = ({ activeTicker
|
|
| 69 |
setResult(null);
|
| 70 |
|
| 71 |
try {
|
| 72 |
-
const response = await fetch(
|
| 73 |
method: 'POST',
|
| 74 |
headers: { 'Content-Type': 'application/json' },
|
| 75 |
body: JSON.stringify({
|
|
@@ -88,7 +89,7 @@ export const WalkForwardPanel: React.FC<WalkForwardPanelProps> = ({ activeTicker
|
|
| 88 |
setErrorMsg(data.error || '运行 Walk-Forward 优化失败');
|
| 89 |
}
|
| 90 |
} catch (err) {
|
| 91 |
-
setErrorMsg(
|
| 92 |
} finally {
|
| 93 |
setIsLoading(false);
|
| 94 |
}
|
|
|
|
| 1 |
// frontend/src/components/WalkForwardPanel.tsx
|
| 2 |
|
| 3 |
import React, { useState } from 'react';
|
| 4 |
+
import { API_BASE } from '../config';
|
| 5 |
|
| 6 |
interface WalkForwardPanelProps {
|
| 7 |
activeTicker: string;
|
|
|
|
| 70 |
setResult(null);
|
| 71 |
|
| 72 |
try {
|
| 73 |
+
const response = await fetch(`${API_BASE}/api/walk_forward`, {
|
| 74 |
method: 'POST',
|
| 75 |
headers: { 'Content-Type': 'application/json' },
|
| 76 |
body: JSON.stringify({
|
|
|
|
| 89 |
setErrorMsg(data.error || '运行 Walk-Forward 优化失败');
|
| 90 |
}
|
| 91 |
} catch (err) {
|
| 92 |
+
setErrorMsg(`无法连接到后端服务器 (${API_BASE})。`);
|
| 93 |
} finally {
|
| 94 |
setIsLoading(false);
|
| 95 |
}
|
external/Quant.ai/frontend/src/config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// frontend/src/config.ts
|
| 2 |
+
// Automatically resolves backend API URL for local dev, Docker container, or deployed HF Space
|
| 3 |
+
|
| 4 |
+
let defaultApiBase = 'http://127.0.0.1:8000';
|
| 5 |
+
|
| 6 |
+
if (typeof window !== 'undefined') {
|
| 7 |
+
if (window.location.port === '5173' || window.location.port === '3000') {
|
| 8 |
+
defaultApiBase = 'http://127.0.0.1:8000';
|
| 9 |
+
} else {
|
| 10 |
+
// When served via FastAPI server or hosted Space
|
| 11 |
+
defaultApiBase = window.location.origin;
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export const API_BASE = (import.meta.env?.VITE_API_BASE as string) || defaultApiBase;
|
external/Quant.ai/frontend/tsconfig.app.json
CHANGED
|
@@ -16,8 +16,8 @@
|
|
| 16 |
"jsx": "react-jsx",
|
| 17 |
|
| 18 |
/* Linting */
|
| 19 |
-
"noUnusedLocals":
|
| 20 |
-
"noUnusedParameters":
|
| 21 |
"erasableSyntaxOnly": true,
|
| 22 |
"noFallthroughCasesInSwitch": true
|
| 23 |
},
|
|
|
|
| 16 |
"jsx": "react-jsx",
|
| 17 |
|
| 18 |
/* Linting */
|
| 19 |
+
"noUnusedLocals": false,
|
| 20 |
+
"noUnusedParameters": false,
|
| 21 |
"erasableSyntaxOnly": true,
|
| 22 |
"noFallthroughCasesInSwitch": true
|
| 23 |
},
|
external/Quant.ai/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>frontend</title>
|
| 8 |
+
<script type="module" crossorigin src="/assets/index-C6qvxrC6.js"></script>
|
| 9 |
+
<link rel="stylesheet" crossorigin href="/assets/index-DCSOhC_L.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<div id="root"></div>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
external/Quant.ai/requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.95.0
|
| 2 |
+
uvicorn[standard]>=0.20.0
|
| 3 |
+
pandas>=2.0.0
|
| 4 |
+
numpy>=1.24.0
|
| 5 |
+
yfinance>=0.2.30
|
| 6 |
+
pydantic>=2.0.0
|
| 7 |
+
requests>=2.28.0
|
| 8 |
+
scikit-learn>=1.2.0
|
| 9 |
+
python-multipart>=0.0.6
|
external/Quant.ai/scratch/deploy_hf_space.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from huggingface_hub import HfApi, create_repo
|
| 3 |
+
|
| 4 |
+
repo_id = "Ypeng12/quant-ai"
|
| 5 |
+
print(f"Creating Space repository '{repo_id}' on Hugging Face...")
|
| 6 |
+
|
| 7 |
+
api = HfApi()
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
url = create_repo(
|
| 11 |
+
repo_id=repo_id,
|
| 12 |
+
repo_type="space",
|
| 13 |
+
space_sdk="static",
|
| 14 |
+
private=False,
|
| 15 |
+
exist_ok=True
|
| 16 |
+
)
|
| 17 |
+
print(f"Space repository ready at: {url}")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
print(f"Repo creation notice: {e}")
|
| 20 |
+
|
| 21 |
+
print("Uploading project files to Hugging Face Space...")
|
| 22 |
+
|
| 23 |
+
api.upload_folder(
|
| 24 |
+
folder_path="c:/Users/pengy/OneDrive/Desktop/Quont",
|
| 25 |
+
repo_id=repo_id,
|
| 26 |
+
repo_type="space",
|
| 27 |
+
ignore_patterns=[
|
| 28 |
+
"*.pyc",
|
| 29 |
+
"__pycache__/*",
|
| 30 |
+
"**/__pycache__/*",
|
| 31 |
+
".git/*",
|
| 32 |
+
"**/.git/*",
|
| 33 |
+
".venv/*",
|
| 34 |
+
"**/.venv/*",
|
| 35 |
+
"node_modules/*",
|
| 36 |
+
"**/node_modules/*",
|
| 37 |
+
"scratch/*",
|
| 38 |
+
"*.pdf",
|
| 39 |
+
".gemini/*"
|
| 40 |
+
]
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
print("✅ Upload completed successfully!")
|
| 44 |
+
print(f"Space live URL: https://huggingface.co/spaces/{repo_id}")
|
inferroute/main.py
CHANGED
|
@@ -106,6 +106,7 @@ app = FastAPI(
|
|
| 106 |
)
|
| 107 |
|
| 108 |
import os
|
|
|
|
| 109 |
from fastapi.staticfiles import StaticFiles
|
| 110 |
from inferroute import plugins
|
| 111 |
|
|
@@ -115,6 +116,39 @@ if os.path.exists(static_dir):
|
|
| 115 |
|
| 116 |
app.include_router(plugins.router)
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
@app.get("/platform", response_class=HTMLResponse)
|
| 120 |
async def get_platform_page():
|
|
|
|
| 106 |
)
|
| 107 |
|
| 108 |
import os
|
| 109 |
+
import sys
|
| 110 |
from fastapi.staticfiles import StaticFiles
|
| 111 |
from inferroute import plugins
|
| 112 |
|
|
|
|
| 116 |
|
| 117 |
app.include_router(plugins.router)
|
| 118 |
|
| 119 |
+
# ── Quant.ai Integration ──────────────────────────────────────────────────────
|
| 120 |
+
quant_backend_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "external", "Quant.ai", "backend")
|
| 121 |
+
if os.path.exists(quant_backend_dir):
|
| 122 |
+
if quant_backend_dir not in sys.path:
|
| 123 |
+
sys.path.insert(0, quant_backend_dir)
|
| 124 |
+
try:
|
| 125 |
+
import main_api as quant_main_api
|
| 126 |
+
app.include_router(quant_main_api.app.router)
|
| 127 |
+
logger.info("Quant.ai API routes registered successfully.")
|
| 128 |
+
except Exception as e:
|
| 129 |
+
logger.warning(f"Failed to load Quant.ai backend: {e}")
|
| 130 |
+
|
| 131 |
+
quant_dist_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "external", "Quant.ai", "frontend", "dist")
|
| 132 |
+
if os.path.exists(quant_dist_dir):
|
| 133 |
+
quant_assets = os.path.join(quant_dist_dir, "assets")
|
| 134 |
+
if os.path.exists(quant_assets):
|
| 135 |
+
app.mount("/quant/assets", StaticFiles(directory=quant_assets), name="quant_assets")
|
| 136 |
+
app.mount("/assets", StaticFiles(directory=quant_assets), name="assets")
|
| 137 |
+
|
| 138 |
+
@app.get("/quant", response_class=HTMLResponse)
|
| 139 |
+
@app.get("/quant/{full_path:path}", response_class=HTMLResponse)
|
| 140 |
+
async def get_quant_page(full_path: str = ""):
|
| 141 |
+
target = os.path.join(quant_dist_dir, full_path)
|
| 142 |
+
if full_path and os.path.exists(target) and os.path.isfile(target):
|
| 143 |
+
with open(target, "rb") as f:
|
| 144 |
+
return Response(content=f.read())
|
| 145 |
+
index_path = os.path.join(quant_dist_dir, "index.html")
|
| 146 |
+
if os.path.exists(index_path):
|
| 147 |
+
with open(index_path, "r", encoding="utf-8") as f:
|
| 148 |
+
return f.read()
|
| 149 |
+
return "<h1>Quant.ai UI Not Found</h1>"
|
| 150 |
+
|
| 151 |
+
|
| 152 |
|
| 153 |
@app.get("/platform", response_class=HTMLResponse)
|
| 154 |
async def get_platform_page():
|
requirements.txt
CHANGED
|
@@ -21,5 +21,7 @@ pytest-asyncio>=0.23.7
|
|
| 21 |
locust>=2.28.0
|
| 22 |
aiosqlite>=0.20.0
|
| 23 |
python-multipart>=0.0.9
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
| 21 |
locust>=2.28.0
|
| 22 |
aiosqlite>=0.20.0
|
| 23 |
python-multipart>=0.0.9
|
| 24 |
+
pandas>=2.0.0
|
| 25 |
+
numpy>=1.24.0
|
| 26 |
+
yfinance>=0.2.30
|
| 27 |
+
scikit-learn>=1.2.0
|
scratch/deploy_inferroute_space.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from huggingface_hub import HfApi, create_repo
|
| 3 |
+
|
| 4 |
+
repo_id = "Ypeng12/InferRoute"
|
| 5 |
+
print(f"Checking/Creating Space repository '{repo_id}' on Hugging Face...")
|
| 6 |
+
|
| 7 |
+
api = HfApi()
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
url = create_repo(
|
| 11 |
+
repo_id=repo_id,
|
| 12 |
+
repo_type="space",
|
| 13 |
+
space_sdk="docker",
|
| 14 |
+
private=False,
|
| 15 |
+
exist_ok=True
|
| 16 |
+
)
|
| 17 |
+
print(f"Space repository ready at: {url}")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
print(f"Repo notice: {e}")
|
| 20 |
+
|
| 21 |
+
print("Uploading InferRoute + Quant.ai files to Hugging Face Space...")
|
| 22 |
+
|
| 23 |
+
api.upload_folder(
|
| 24 |
+
folder_path="c:/Users/pengy/OneDrive/Desktop/InferRoute",
|
| 25 |
+
repo_id=repo_id,
|
| 26 |
+
repo_type="space",
|
| 27 |
+
ignore_patterns=[
|
| 28 |
+
"*.pyc",
|
| 29 |
+
"__pycache__/*",
|
| 30 |
+
"**/__pycache__/*",
|
| 31 |
+
".git/*",
|
| 32 |
+
"**/.git/*",
|
| 33 |
+
".venv/*",
|
| 34 |
+
"**/.venv/*",
|
| 35 |
+
"node_modules/*",
|
| 36 |
+
"**/node_modules/*",
|
| 37 |
+
"scratch/*",
|
| 38 |
+
"*.pdf",
|
| 39 |
+
".gemini/*",
|
| 40 |
+
"inferroute.db"
|
| 41 |
+
]
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
print("Upload completed successfully!")
|
| 45 |
+
print(f"Space live URL: https://huggingface.co/spaces/{repo_id}")
|