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:
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copy to .env (untracked) at the repository root.
|
||||
# IPADS Responses API key for the web watchlist agent; blank uses demo data.
|
||||
IPADS_API_KEY=
|
||||
# Proxy URL for the live monitor's market-data fetches (deploy/start-monitor.sh).
|
||||
QUANT_MARKET_PROXY=
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -55,3 +55,7 @@ data/*_unavailable.json
|
||||
logs/
|
||||
memory/
|
||||
.tmp_finance/
|
||||
|
||||
# Web UI state and precomputed backtests (web/src/server/paths.ts)
|
||||
data/web/
|
||||
data/precomputed/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Bridge script for stock-agent integration.
|
||||
Bridge between the Python engine and the web API (web/src/server/strategyService.ts).
|
||||
|
||||
Outputs JSON to stdout; all logs go to stderr.
|
||||
|
||||
@@ -1186,7 +1186,7 @@ def cmd_live(args: argparse.Namespace) -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Quant bridge for stock-agent")
|
||||
parser = argparse.ArgumentParser(description="Quant bridge for the web API")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
# list
|
||||
|
||||
@@ -326,7 +326,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(description="Pre-compute backtest results")
|
||||
parser.add_argument(
|
||||
"--output", default=None,
|
||||
help="Output directory (default: ../stock-agent/data/precomputed)",
|
||||
help="Output directory (default: data/precomputed, read by the web UI)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--years", type=int, default=10,
|
||||
@@ -335,7 +335,7 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
output_dir = args.output or os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "..", "stock-agent", "data", "precomputed"
|
||||
os.path.dirname(os.path.abspath(__file__)), "data", "precomputed"
|
||||
)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[project]
|
||||
name = "quant"
|
||||
name = "quant-trading"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
|
||||
2
uv.lock
generated
2
uv.lock
generated
@@ -761,7 +761,7 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quant"
|
||||
name = "quant-trading"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
IPADS_API_KEY=
|
||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "personal-stock-agent",
|
||||
"name": "quant-trading-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -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
14
web/src/server/paths.ts
Normal 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");
|
||||
@@ -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" },
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user