!pasted image 20260628212010.png
技術棧選擇(全免費開源)
前端:React + Vite(或 Next.js)、Chart.js / Recharts(K線圖)、TailwindCSS
後端 API:FastAPI(Python)— 非同步、自動生成 API 檔案
即時通訊:WebSocket(FastAPI 內建)— 推送訊號、回測進度
任務佇列:Celery + Redis — 背景執行耗時的回測任務
資料庫:TimescaleDB(PostgreSQL 延伸,專為時序 K 棒設計)+ Redis 快取
佈署:Docker Compose 一鍵啟動所有服務
五個核心建構步驟
Step 1:資料庫設計(TimescaleDB)
sql
-- K棒時序表(自動分片優化)
CREATE TABLE ohlcv (
time TIMESTAMPTZ NOT NULL,
symbol TEXT NOT NULL,
market TEXT NOT NULL, -- 'TW' | 'US' | 'TWF'
open NUMERIC,
high NUMERIC,
low NUMERIC,
close NUMERIC,
volume BIGINT
);
SELECT create_hypertable('ohlcv', 'time');
CREATE INDEX ON ohlcv (symbol, time DESC);
-- HSP訊號記錄表
CREATE TABLE hsp_signals (
id SERIAL PRIMARY KEY,
packet_id TEXT UNIQUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
ticker TEXT,
grade TEXT, -- S/A/B/C
signal_type TEXT,
confidence NUMERIC,
payload JSONB
);
-- 回測結果表
CREATE TABLE backtest_results (
id SERIAL PRIMARY KEY,
strategy_name TEXT,
params JSONB,
run_at TIMESTAMPTZ DEFAULT NOW(),
sharpe_ratio NUMERIC,
max_drawdown NUMERIC,
total_return NUMERIC,
total_trades INT,
equity_curve JSONB -- 存逐日淨值
);Step 2:FastAPI 後端骨架
python
# main.py
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
from celery_app import run_backtest_task
import asyncio, json
app = FastAPI(title="Hermes Quant API")
app.add_middleware(CORSMiddleware, allow_origins=["*"])
# 觸發回測(非同步,丟給 Celery)
@app.post("/api/backtest/run")
async def trigger_backtest(payload: dict):
task = run_backtest_task.delay(payload)
return {"task_id": task.id, "status": "queued"}
# 查詢回測結果
@app.get("/api/backtest/{task_id}")
async def get_backtest(task_id: str):
from celery_app import app as celery
result = celery.AsyncResult(task_id)
return {"status": result.status, "result": result.result}
# WebSocket:Hermes 即時訊號推送
@app.websocket("/ws/signals")
async def signal_stream(ws: WebSocket):
await ws.accept()
# 連接 Redis pub/sub,有新 HSP 就推送給前端
import aioredis
redis = await aioredis.from_url("redis://localhost")
pubsub = redis.pubsub()
await pubsub.subscribe("hermes_signals")
async for message in pubsub.listen():
if message["type"] == "message":
await ws.send_text(message["data"])Step 3:Celery 回測工作
python
# celery_app.py
from celery import Celery
import backtrader as bt, json
app = Celery("hermes", broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1")
@app.task(bind=True)
def run_backtest_task(self, payload: dict):
"""背景執行回測,完成後存 DB + 推送 WebSocket"""
try:
self.update_state(state="RUNNING", meta={"progress": 10})
# 從 DB 撈資料
df = load_ohlcv_from_db(payload["symbol"], payload["start"], payload["end"])
self.update_state(state="RUNNING", meta={"progress": 40})
# 執行回測(Backtrader)
result = run_hermes_backtest(df, payload.get("params", {}))
self.update_state(state="RUNNING", meta={"progress": 80})
# 存入 DB
save_backtest_result(payload["strategy"], payload.get("params"), result)
# 推送通知到 WebSocket
import redis
r = redis.Redis()
r.publish("hermes_signals", json.dumps({
"type": "BACKTEST_DONE",
"result": result
}))
return result
except Exception as e:
self.update_state(state="FAILURE", meta={"error": str(e)})
raiseStep 4:React 前端核心
jsx
// components/BacktestPanel.jsx
import { useState, useEffect } from "react"
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts"
export default function BacktestPanel() {
const [params, setParams] = useState({
symbol: "2330", market: "TW",
start: "2022-01-01", end: "2024-12-31",
ema_fast: 20, ema_slow: 60
})
const [status, setStatus] = useState("idle")
const [result, setResult] = useState(null)
const runBacktest = async () => {
setStatus("running")
const { task_id } = await fetch("/api/backtest/run", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params)
}).then(r => r.json())
// Polling 等待結果
const poll = setInterval(async () => {
const data = await fetch(`/api/backtest/${task_id}`).then(r => r.json())
if (data.status === "SUCCESS") {
setResult(data.result)
setStatus("done")
clearInterval(poll)
}
}, 1500)
}
return (
<div style={{ padding: 24 }}>
{/* 參數設定 */}
<div style={{ display: "flex", gap: 12, marginBottom: 24 }}>
<input value={params.symbol} onChange={e => setParams({...params, symbol: e.target.value})}
placeholder="股票代碼 (e.g. 2330)" />
<input type="date" value={params.start}
onChange={e => setParams({...params, start: e.target.value})} />
<input type="date" value={params.end}
onChange={e => setParams({...params, end: e.target.value})} />
<button onClick={runBacktest} disabled={status === "running"}>
{status === "running" ? "回測中..." : "執行回測"}
</button>
</div>
{/* 績效摘要 */}
{result && (
<div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 12 }}>
{[
["總報酬", `${result.return_pct.toFixed(2)}%`],
["Sharpe", result.sharpe?.toFixed(2) ?? "—"],
["最大回落", `${result.max_drawdown?.toFixed(2)}%`],
["交易次數", result.total_trades]
].map(([label, val]) => (
<div key={label} style={{ background: "var(--surface-1)", padding: 16, borderRadius: 8 }}>
<div style={{ fontSize: 12, color: "var(--text-secondary)" }}>{label}</div>
<div style={{ fontSize: 22, fontWeight: 500 }}>{val}</div>
</div>
))}
</div>
)}
{/* 淨值曲線 */}
{result?.equity_curve && (
<ResponsiveContainer width="100%" height={260}>
<LineChart data={result.equity_curve}>
<XAxis dataKey="date" tick={{ fontSize: 11 }} />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#1D9E75" dot={false} strokeWidth={1.5} />
</LineChart>
</ResponsiveContainer>
)}
</div>
)
}Step 5:Docker Compose 一鍵啟動
yaml
# docker-compose.yml
services:
db:
image: timescale/timescaledb:latest-pg16
environment:
POSTGRES_DB: hermes_quant
POSTGRES_PASSWORD: password
ports: ["5432:5432"]
volumes: ["pgdata:/var/lib/postgresql/data"]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
api:
build: ./backend
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
depends_on: [db, redis]
ports: ["8000:8000"]
worker:
build: ./backend
command: celery -A celery_app worker --loglevel=info --concurrency=4
depends_on: [db, redis]
frontend:
build: ./frontend
ports: ["3000:3000"]
depends_on: [api]
volumes:
pgdata:請 Agent 建構的正確方式
若要讓 Claude Code 或其他 Coding Agent 幫你完整建構,使用以下 prompt 結構:
# 任務:Hermes 量化交易 Web 系統
## 目標
建立完整的量化交易動態網頁系統,包含:
1. React 前端(策略設定、回測儀表板、訊號監控、K線圖)
2. FastAPI 後端(REST API + WebSocket)
3. Celery 工作佇列(非同步回測)
4. TimescaleDB 時序資料庫
5. Docker Compose 部署
## 規格
- 台股資料來源:AKShare(akshare 套件)
- 美股資料來源:yfinance
- 回測框架:backtrader,台股手續費 0.001425
- 訊號格式:HSP(Hermes Standard Packet)含 S/A/B/C 等級
- 風控:ATR停損 + 固定風險2% + 每日最大虧損3%
## 請執行步驟
1. 建立 docker-compose.yml
2. 建立 backend/ 目錄,初始化 FastAPI 專案
3. 實作 /api/backtest/run 和 /ws/signals 端點
4. 建立 frontend/ 目錄,初始化 React + Vite 專案
5. 實作回測儀表板組件(含 Recharts 淨值圖)
6. 撰寫 README.md 含啟動指令
## 限制
- 全程使用免費開源工具
- Python 套件不得使用付費 API