Ypeng12 commited on
Commit
f307930
ยท
1 Parent(s): a5ae89a

feat: optimize Hugging Face Space Docker build and zero-config deployment

Browse files
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ **/node_modules
3
+ __pycache__
4
+ **/__pycache__
5
+ *.pyc
6
+ *.pyo
7
+ .venv
8
+ venv
9
+ .git
10
+ .pytest_cache
11
+ *.db
12
+ *.log
13
+ .DS_Store
14
+ Thumbs.db
Dockerfile CHANGED
@@ -1,4 +1,12 @@
1
- # Build stage using official python 3.12 slim image
 
 
 
 
 
 
 
 
2
  FROM python:3.12-slim
3
 
4
  # Set environment variables
@@ -14,27 +22,34 @@ ENV PYTHONUNBUFFERED=1 \
14
  # Set working directory
15
  WORKDIR /app
16
 
17
- # Install system dependencies (optional, for potential sqlite compilation or git)
18
  RUN apt-get update && apt-get install -y --no-install-recommends \
19
  build-essential \
 
20
  && rm -rf /var/lib/apt/lists/*
21
 
22
  # Copy requirements file first for caching
23
  COPY requirements.txt .
24
 
25
- # Install dependencies
26
  RUN pip install --no-cache-dir -r requirements.txt
27
 
28
- # Copy all project source code
29
  COPY inferroute/ ./inferroute/
30
  COPY docs/ ./docs/
31
  COPY benchmarks/ ./benchmarks/
32
  COPY external/ ./external/
 
 
33
  COPY *.html ./
34
  COPY assets/ ./assets/
 
 
 
 
35
 
36
  # Expose default port (7860 for Hugging Face Spaces)
37
  EXPOSE 7860
38
 
39
- # Command to run uvicorn dynamically binding to PORT environment variable (for Render/Hugging Face compatibility)
40
  CMD ["sh", "-c", "python -m uvicorn inferroute.main:app --host 0.0.0.0 --port ${PORT:-7860}"]
 
1
+ # Stage 1: Build React frontend for Quant.ai
2
+ FROM node:18-alpine AS frontend-builder
3
+ WORKDIR /app/frontend
4
+ COPY external/Quant.ai/frontend/package*.json ./
5
+ RUN npm install --legacy-peer-deps
6
+ COPY external/Quant.ai/frontend/ ./
7
+ RUN npm run build
8
+
9
+ # Stage 2: Python 3.12 high-performance inference gateway
10
  FROM python:3.12-slim
11
 
12
  # Set environment variables
 
22
  # Set working directory
23
  WORKDIR /app
24
 
25
+ # Install system dependencies
26
  RUN apt-get update && apt-get install -y --no-install-recommends \
27
  build-essential \
28
+ curl \
29
  && rm -rf /var/lib/apt/lists/*
30
 
31
  # Copy requirements file first for caching
32
  COPY requirements.txt .
33
 
34
+ # Install Python dependencies
35
  RUN pip install --no-cache-dir -r requirements.txt
36
 
37
+ # Copy project source code
38
  COPY inferroute/ ./inferroute/
39
  COPY docs/ ./docs/
40
  COPY benchmarks/ ./benchmarks/
41
  COPY external/ ./external/
42
+ COPY platform/ ./platform/
43
+ COPY quant/ ./quant/
44
  COPY *.html ./
45
  COPY assets/ ./assets/
46
+ COPY *.svg ./
47
+
48
+ # Copy compiled React frontend distribution bundle from Stage 1
49
+ COPY --from=frontend-builder /app/frontend/dist ./external/Quant.ai/frontend/dist
50
 
51
  # Expose default port (7860 for Hugging Face Spaces)
52
  EXPOSE 7860
53
 
54
+ # Launch Uvicorn gateway
55
  CMD ["sh", "-c", "python -m uvicorn inferroute.main:app --host 0.0.0.0 --port ${PORT:-7860}"]
external/Quant.ai/backend/app/config.py CHANGED
@@ -43,5 +43,42 @@ MARKET_OPEN_FOCUS_START = "09:30" # ๅผ€็›˜ๅผ€ๅง‹ๆ—ถ้—ด
43
  MARKET_OPEN_FOCUS_END = "10:15" # ๅผ€็›˜็ป“ๆŸๆ—ถ้—ด (ๅ‰ 45 ๅˆ†้’Ÿ)
44
  FORCE_LIQUIDATION_OPEN_FOCUS = "10:30" # ๅผ€็›˜็ชๅ‡ปๆจกๅผไธ‹๏ผŒ10:30 ๅผบๅˆถๆธ…ไป“ๅ‡บๅœบ๏ผŒ้˜ฒๅˆ็›˜ๆจช็›˜้œ‡่กๆŸ่€—
45
 
46
- # ้ป˜่ฎค็›‘ๆŽง็š„้ซ˜ๆตๅŠจๆ€ง่‚ก็ฅจๆฑ 
47
- WATCHLIST = ["TSLA", "NVDA", "AAPL", "MSFT", "AMD"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  MARKET_OPEN_FOCUS_END = "10:15" # ๅผ€็›˜็ป“ๆŸๆ—ถ้—ด (ๅ‰ 45 ๅˆ†้’Ÿ)
44
  FORCE_LIQUIDATION_OPEN_FOCUS = "10:30" # ๅผ€็›˜็ชๅ‡ปๆจกๅผไธ‹๏ผŒ10:30 ๅผบๅˆถๆธ…ไป“ๅ‡บๅœบ๏ผŒ้˜ฒๅˆ็›˜ๆจช็›˜้œ‡่กๆŸ่€—
45
 
46
+ # ็ƒญ้—จๆฟๅ—ๅˆ†็ฑปๅบ“ (Hot Sectors Universe)
47
+ HOT_SECTORS = {
48
+ "AI_CHIPS": {
49
+ "name": "๐Ÿ”ฅ AI & ๅŠๅฏผไฝ“็ฎ—ๅŠ›",
50
+ "description": "ไบบๅทฅๆ™บ่ƒฝ่Šฏ็‰‡ใ€็ฎ—ๅŠ›ๆœๅŠกๅ™จไธŽๆ™ถๅœ†ไปฃๅทฅ้พ™ๅคด",
51
+ "tickers": ["NVDA", "AMD", "AVGO", "TSM", "SMCI"]
52
+ },
53
+ "MEMORY_STORAGE": {
54
+ "name": "๐Ÿ’พ ๅญ˜ๅ‚จ่Šฏ็‰‡ & AI ๅญ˜ๅ‚จ",
55
+ "description": "DRAM/NAND/HBM ๅญ˜ๅ‚จๅทจๅคดไธŽไผไธš็บง AI ๅญ˜ๅ‚จๅŸบ็ก€่ฎพๆ–ฝ",
56
+ "tickers": ["MU", "WDC", "STX", "PSTG", "NTAP"]
57
+ },
58
+ "AI_POWER_NUCLEAR": {
59
+ "name": "โš›๏ธ AI ๆ ธ่ƒฝ & ็”ตๅŠ›ๅŸบ็ก€่ฎพๆ–ฝ",
60
+ "description": "AIๆ•ฐๆฎไธญๅฟƒๆ ธ็”ตใ€ๆธ…ๆด่ƒฝๆบไธŽ็”ตๅŠ›ๅŸบ็ก€่ฎพๆ–ฝ้พ™ๅคด",
61
+ "tickers": ["OKLO", "SMR", "VST", "CEG", "TLN"]
62
+ },
63
+ "QUANTUM_FRONTIER": {
64
+ "name": "๐Ÿ”ฎ ้‡ๅญ่ฎก็ฎ— & AI ่ฝฏไปถ",
65
+ "description": "้‡ๅญ่ฎก็ฎ—ใ€ๅคงๆ•ฐๆฎๅˆ†ๆžไธŽ้ซ˜่ดๅก”ๅ‰ๆฒฟ็ง‘ๆŠ€",
66
+ "tickers": ["PLTR", "IONQ", "RGTI"]
67
+ },
68
+ "BIG_TECH": {
69
+ "name": "๐Ÿ›๏ธ ็ง‘ๆŠ€ๅทจๅคด / ๆ ธๅฟƒๆŒไป“",
70
+ "description": "้ซ˜ๆตๅŠจๆ€งๅคง็›˜็ง‘ๆŠ€่‚กไธŽๆ ธๅฟƒ่ต„ไบง",
71
+ "tickers": ["TSLA", "AAPL", "MSFT", "META", "GOOGL"]
72
+ },
73
+ "MOMENTUM_CRYPTO": {
74
+ "name": "๐Ÿš€ ้ซ˜ๅŠจ่ƒฝ & ๅŠ ๅฏ†ๆฆ‚ๅฟต",
75
+ "description": "ๆž้ซ˜่ดๅก”็ณปๆ•ฐใ€ๅผบๆˆไบค้‡ไธŽๅŠ ๅฏ†่ต„ไบงๆฆ‚ๅฟต่‚ก",
76
+ "tickers": ["MSTR", "COIN", "HOOD", "MARA", "TQQQ"]
77
+ }
78
+ }
79
+
80
+ # ้ป˜่ฎค็›‘ๆŽง็š„็ฒพ็ฎ€ๆ ธๅฟƒ่‚ก็ฅจๆฑ ๏ผˆๆ–นไพฟ็”จๆˆท่‡ชไธปๆทปๅŠ ไธŽ่‡ชๅฎšไน‰๏ผ‰
81
+ WATCHLIST = ["NVDA", "TSLA", "AAPL", "AMD", "MU", "PLTR", "MSTR"]
82
+
83
+
84
+
external/Quant.ai/backend/main_api.py CHANGED
@@ -32,7 +32,7 @@ def clean_float(val, default=0.0):
32
  return default
33
 
34
 
35
- from app.config import INITIAL_CASH, WATCHLIST, FORCE_LIQUIDATION_TIME
36
  from app.data_manager import fetch_and_prepare_data, get_company_info, calculate_atr, INTERVAL_TO_PERIOD
37
  from app.patterns import analyze_patterns
38
  from app.simulator import run_backtest_sim
@@ -87,7 +87,7 @@ def load_persistent_watchlist() -> list:
87
  return [str(t).upper() for t in data]
88
  except Exception:
89
  pass
90
- return ["TSLA", "NVDA", "AAPL", "MSFT", "AMD"]
91
 
92
  def save_persistent_watchlist(watchlist: list):
93
  try:
@@ -106,9 +106,72 @@ class WatchlistModifyRequest(BaseModel):
106
  @app.get("/api/watchlist")
107
  def get_watchlist_data():
108
  """
109
- ่Žทๅ–่‡ช้€‰่‚กๆฑ ็š„ๅˆ—่กจ๏ผˆๆ”ฏๆŒๆŒไน…ๅŒ–๏ผ‰
110
  """
111
- return {"watchlist": WATCHLIST}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  @app.post("/api/watchlist/add")
114
  def add_watchlist_ticker(req: WatchlistModifyRequest):
 
32
  return default
33
 
34
 
35
+ from app.config import INITIAL_CASH, WATCHLIST, FORCE_LIQUIDATION_TIME, HOT_SECTORS
36
  from app.data_manager import fetch_and_prepare_data, get_company_info, calculate_atr, INTERVAL_TO_PERIOD
37
  from app.patterns import analyze_patterns
38
  from app.simulator import run_backtest_sim
 
87
  return [str(t).upper() for t in data]
88
  except Exception:
89
  pass
90
+ return ["NVDA", "TSLA", "AAPL", "AMD", "MU", "PLTR", "MSTR"]
91
 
92
  def save_persistent_watchlist(watchlist: list):
93
  try:
 
106
  @app.get("/api/watchlist")
107
  def get_watchlist_data():
108
  """
109
+ ่Žทๅ–่‡ช้€‰่‚กๆฑ ็š„ๅˆ—่กจ๏ผˆๆ”ฏๆŒๆŒไน…ๅŒ– + ่‡ชๅŠจๆ ‡่ฎฐๆŒไป“็Šถๆ€ไธŽ็ƒญ้—จๆฟๅ—ๅ…ƒๆ•ฐๆฎ๏ผ‰
110
  """
111
+ open_positions = []
112
+ try:
113
+ positions_info = live_runner.alpaca.get_open_positions()
114
+ open_positions = [p["ticker"].upper() for p in positions_info]
115
+ except Exception:
116
+ pass
117
+
118
+ enriched_watchlist = []
119
+ for ticker in WATCHLIST:
120
+ # ๅˆคๆ–ญๆ‰€ๅฑž็ƒญ้—จๆฟๅ—
121
+ category = "ๅ…ถไป–"
122
+ for sec_key, sec_data in HOT_SECTORS.items():
123
+ if ticker in sec_data["tickers"]:
124
+ category = sec_data["name"]
125
+ break
126
+
127
+ enriched_watchlist.append({
128
+ "ticker": ticker,
129
+ "is_holding": ticker in open_positions,
130
+ "category": category
131
+ })
132
+
133
+ return {
134
+ "watchlist": WATCHLIST,
135
+ "details": enriched_watchlist,
136
+ "total": len(WATCHLIST),
137
+ "holdings_count": len([item for item in enriched_watchlist if item["is_holding"]])
138
+ }
139
+
140
+ @app.post("/api/watchlist/sync_holdings")
141
+ def sync_holdings_to_watchlist():
142
+ """
143
+ ไธ€้”ฎๅŒๆญฅๅทฒๆ‹ฅๆœ‰็š„่‚ก็ฅจ๏ผˆๆŒไป“๏ผ‰ๅˆฐ Watchlist
144
+ """
145
+ try:
146
+ positions_info = live_runner.alpaca.get_open_positions()
147
+ added_count = 0
148
+ for p in positions_info:
149
+ t = p["ticker"].upper()
150
+ if t not in WATCHLIST:
151
+ WATCHLIST.append(t)
152
+ added_count += 1
153
+ if added_count > 0:
154
+ save_persistent_watchlist(WATCHLIST)
155
+ live_runner.active_tickers = WATCHLIST.copy()
156
+ return {
157
+ "success": True,
158
+ "added_count": added_count,
159
+ "watchlist": WATCHLIST,
160
+ "message": f"ๆˆๅŠŸไปŽ็Žฐๆœ‰ๆŒไป“ๅŒๆญฅ {added_count} ๅช่‚ก็ฅจๅˆฐ Watchlist ็›‘ๆŽงๆฑ ใ€‚"
161
+ }
162
+ except Exception as e:
163
+ return {"success": False, "error": f"ๅŒๆญฅๆŒไป“ๅคฑ่ดฅ: {str(e)}"}
164
+
165
+ @app.get("/api/watchlist/hot_sectors")
166
+ def get_hot_sectors_universe():
167
+ """
168
+ ่Žทๅ–ๆŽจ่็ƒญ้—จๆฟๅ—ๅบ“ (AI็ฎ—ๅŠ›ใ€็ง‘ๆŠ€ๅทจๅคดใ€ๅŠ ๅฏ†ๅŠจ่ƒฝ็ญ‰)
169
+ """
170
+ return {
171
+ "success": True,
172
+ "hot_sectors": HOT_SECTORS,
173
+ "recommended_tickers": ["NVDA", "TSLA", "PLTR", "AMD", "AVGO", "MSTR", "MSFT", "AAPL", "COIN", "TQQQ"]
174
+ }
175
 
176
  @app.post("/api/watchlist/add")
177
  def add_watchlist_ticker(req: WatchlistModifyRequest):
external/Quant.ai/backend/watchlist.json CHANGED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "NVDA",
3
+ "TSLA",
4
+ "AAPL",
5
+ "AMD",
6
+ "MU",
7
+ "PLTR",
8
+ "CRCL",
9
+ "MSTR"
10
+ ]
inferroute/config.py CHANGED
@@ -8,7 +8,7 @@ class Settings(BaseSettings):
8
 
9
  # โ”€โ”€ Databases โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
10
  DATABASE_URL: str = Field(
11
- default="postgresql+asyncpg://postgres:postgres@localhost:5432/inferroute",
12
  validation_alias="DATABASE_URL"
13
  )
14
  REDIS_URL: str = Field(
 
8
 
9
  # โ”€โ”€ Databases โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
10
  DATABASE_URL: str = Field(
11
+ default="sqlite+aiosqlite:///./inferroute.db",
12
  validation_alias="DATABASE_URL"
13
  )
14
  REDIS_URL: str = Field(
inferroute/main.py CHANGED
@@ -76,21 +76,29 @@ concurrency_limiter = AdaptiveConcurrencyLimiter()
76
  @asynccontextmanager
77
  async def lifespan(app: FastAPI):
78
  logger.info("Starting InferRoute gatewayโ€ฆ")
79
- await init_db()
80
-
81
- import inferroute.auth as auth
82
- redis_client = aioredis.from_url(settings.REDIS_URL, decode_responses=True)
83
- auth.redis_client = redis_client
84
 
85
- # Inject Redis into circuit breakers
86
- circuit_breaker.initialize_circuit_breakers(redis_client, list(ADAPTERS.keys()))
 
 
 
 
 
87
 
88
  logger.info("InferRoute gateway ready.")
89
  yield
90
 
91
- if auth.redis_client:
92
- await auth.redis_client.aclose()
93
- logger.info("Redis connection closed.")
 
 
 
 
94
 
95
 
96
  # โ”€โ”€ App โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
76
  @asynccontextmanager
77
  async def lifespan(app: FastAPI):
78
  logger.info("Starting InferRoute gatewayโ€ฆ")
79
+ try:
80
+ await init_db()
81
+ except Exception as e:
82
+ logger.warning(f"Database init warning (running in degraded/mock mode): {e}")
 
83
 
84
+ try:
85
+ import inferroute.auth as auth
86
+ redis_client = aioredis.from_url(settings.REDIS_URL, decode_responses=True)
87
+ auth.redis_client = redis_client
88
+ circuit_breaker.initialize_circuit_breakers(redis_client, list(ADAPTERS.keys()))
89
+ except Exception as e:
90
+ logger.warning(f"Redis initialization skipped ({e}). Gateway running in standalone mode.")
91
 
92
  logger.info("InferRoute gateway ready.")
93
  yield
94
 
95
+ try:
96
+ import inferroute.auth as auth
97
+ if auth.redis_client:
98
+ await auth.redis_client.aclose()
99
+ logger.info("Redis connection closed.")
100
+ except Exception:
101
+ pass
102
 
103
 
104
  # โ”€โ”€ App โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€