Ypeng12 commited on
Commit
c9f1dfc
·
1 Parent(s): 93baf0e

feat: Integrate Quant.ai into InferRoute gateway under /quant and /api routes

Browse files
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 = None
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
- print(full_msg) # Print to server console
 
 
 
 
 
 
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
- self.adapter = AlpacaAdapter(
61
- api_key=ALPACA_API_KEY,
62
- api_secret=ALPACA_SECRET_KEY,
63
- base_url=ALPACA_BASE_URL
64
- )
65
- # Test connection
66
- self.adapter.get_account_summary()
 
 
 
 
67
  except Exception as e:
68
- self.add_log(f"【错误】无法启动机器人:连接 Alpaca 失败。详情: {str(e)}")
69
- return False
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
- self.loop_task = asyncio.create_task(self._run_loop())
 
 
 
 
 
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
- # backend/main_api.py
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('http://127.0.0.1:8000/api/ai_tune', {
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(`http://127.0.0.1:8000/api/backtest?${queryParams.toString()}`);
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(`http://127.0.0.1:8000/api/company_info?ticker=${activeTicker}`);
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(`http://127.0.0.1:8000/api/backtest?ticker=${ticker}&interval=1d`);
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(`http://127.0.0.1:8000/api/replay/available_dates?ticker=${activeTicker}`);
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(`http://127.0.0.1:8000/api/replay/data?${params.toString()}`);
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(`http://127.0.0.1:8000/api/intraday_data?ticker=${item.ticker}&date=${dateStr}`);
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 FastAPI is running on http://127.0.0.1:8000
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 React, { useState, useEffect } from 'react';
 
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('http://127.0.0.1:8000/api/broker/account');
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('http://127.0.0.1:8000/api/broker/positions');
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('http://127.0.0.1:8000/api/live/status');
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('http://127.0.0.1:8000/api/live/start', {
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('http://127.0.0.1:8000/api/live/stop', { method: 'POST' });
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('http://127.0.0.1:8000/api/broker/cancel_orders', { method: 'POST' });
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('http://127.0.0.1:8000/api/broker/close_positions', { method: 'POST' });
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
- justify: 'space-between',
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('http://127.0.0.1:8000/api/agent/research', {
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('http://127.0.0.1:8000/api/report/generate', {
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 http://127.0.0.1:8000`,
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('http://127.0.0.1:8000/api/experiments');
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('无法获取实验列表,请检查后端 API 是否已启动 127.0.0.1:8000');
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('http://127.0.0.1:8000/api/experiments/compare', {
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(`http://127.0.0.1:8000/api/experiments/${id}`, {
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('http://127.0.0.1:8000/api/research_report');
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 React, { useState, useEffect } from 'react';
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(`http://127.0.0.1:8000/api/replay/available_dates?ticker=${activeTicker}`);
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(`http://127.0.0.1:8000/api/replay/data?${params.toString()}`);
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(`http://127.0.0.1:8000/api/scan?tickers=${symbolsParam}`);
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('http://127.0.0.1:8000/api/walk_forward', {
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('无法连接到后端服务器,请检查 FastAPI 服务是否在 127.0.0.1:8000 启动');
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": true,
20
- "noUnusedParameters": true,
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}")