refactor: wire web to the in-repo engine and shared data dir

- web/src/server/paths.ts resolves the repo root from its own location,
  loads the single repo-root .env, and defines data/web/ (watchlist,
  stock cache, backtest cache) and data/precomputed/.
- The API spawns bridge.py at the repo root instead of ../quant.
- precompute_backtest.py writes to data/precomputed/ instead of
  ../stock-agent/data/precomputed.
- Rename the project to quant-trading (web package quant-trading-web).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 11:42:35 +08:00
parent 0eb5b95efa
commit 96a845ebcd
12 changed files with 40 additions and 19 deletions

View File

@@ -1 +0,0 @@
IPADS_API_KEY=

4
web/package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "personal-stock-agent",
"name": "quant-trading-web",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "personal-stock-agent",
"name": "quant-trading-web",
"version": "0.1.0",
"dependencies": {
"@vitejs/plugin-react": "^5.0.0",

View File

@@ -1,5 +1,5 @@
{
"name": "personal-stock-agent",
"name": "quant-trading-web",
"version": "0.1.0",
"private": true,
"type": "module",

View File

@@ -1,4 +1,4 @@
import "dotenv/config";
import { PRECOMPUTED_DIR, WEB_DATA_DIR } from "./paths";
import cors from "cors";
import express from "express";
import { existsSync, readFileSync } from "node:fs";
@@ -15,11 +15,10 @@ import { listStrategies, runBacktest, getCachedResult, getLiveStrategies } from
const port = Number(process.env.PORT ?? 8787);
const host = process.env.HOST ?? "127.0.0.1";
const dataDir = process.env.STOCK_AGENT_DATA_DIR ?? join(process.cwd(), "data");
const watchlistStore = new WatchlistStore(join(dataDir, "watchlist.json"));
const watchlistStore = new WatchlistStore(join(WEB_DATA_DIR, "watchlist.json"));
const dataProvider = createProvider(loadIpadsConfig().apiKey);
const stockDataCache = new StockDataCache(join(dataDir, "cache"), dataProvider);
const stockDataCache = new StockDataCache(join(WEB_DATA_DIR, "cache"), dataProvider);
const app = express();
let cachedReportUpdatedAt = 0;
@@ -157,7 +156,7 @@ app.get("/api/strategies", async (_request, response, next) => {
});
function loadPrecomputedFile() {
const precomputedPath = join(dataDir, "precomputed", "backtest_results.json");
const precomputedPath = join(PRECOMPUTED_DIR, "backtest_results.json");
if (!existsSync(precomputedPath)) {
throw new Error("Pre-computed results not available. Run precompute_backtest.py first.");
}

14
web/src/server/paths.ts Normal file
View File

@@ -0,0 +1,14 @@
import { config } from "dotenv";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
// web/src/server/ → repository root, which also holds bridge.py and the Python engine.
export const REPO_ROOT = resolve(fileURLToPath(new URL("../../..", import.meta.url)));
// Secrets such as IPADS_API_KEY live in the single untracked repo-root .env.
config({ path: join(REPO_ROOT, ".env"), quiet: true });
// Web-only state: watchlist, per-stock cache and backtest result cache.
export const WEB_DATA_DIR = join(REPO_ROOT, "data", "web");
// Written by precompute_backtest.py, read by the strategy page.
export const PRECOMPUTED_DIR = join(REPO_ROOT, "data", "precomputed");

View File

@@ -1,11 +1,11 @@
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, unlinkSync } from "node:fs";
import { join, resolve } from "node:path";
import { join } from "node:path";
import { REPO_ROOT, WEB_DATA_DIR } from "./paths";
import type { BacktestConfig, BacktestResult, LiveStrategyState, StrategyInfo } from "../shared/strategyTypes.js";
const QUANT_DIR = resolve(process.env.QUANT_PROJECT_DIR || join(process.cwd(), "../quant"));
const CACHE_DIR = join(process.env.STOCK_AGENT_DATA_DIR || join(process.cwd(), "data"), "strategy_cache");
const CACHE_DIR = join(WEB_DATA_DIR, "strategy_cache");
const TIMEOUT_MS = Number(process.env.QUANT_BACKTEST_TIMEOUT_MS) || 180_000;
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const MAX_CACHE_FILES = 50;
@@ -37,7 +37,7 @@ function stableStringify(value: unknown): string {
function runBridge(args: string[]): Promise<unknown> {
return new Promise((resolve, reject) => {
const proc = spawn("uv", ["run", "python", "bridge.py", ...args], {
cwd: QUANT_DIR,
cwd: REPO_ROOT,
env: { ...process.env, PYTHONUNBUFFERED: "1" },
});