feat: named live books; start selected_2026_09 (26 strategies)

A live book is a cohort of strategies, each with its own account, started
on one date with one capital and a flat fee per order (trader.LIVE_BOOKS).
The original book keeps its state in data/; a named book in
data/books/<book>/. The monitor runs every book; auto/status/log/compare/
replay take --book. A fresh account opens on the book's first session.

selected_2026_09: US $10,000 and $1 per order from 2026-09-25, CN ¥50,000
and ¥5 per order from 2026-09-28 (the first CN session after the choice).
Strategies beat their benchmark in the Apr-Sep 2026 point-in-time replay
and passed a 10-year PIT backtest (US Sharpe >= 0.6, MaxDD > -50%; CN
Sharpe >= 0.2, CAGR > 0), no daily variants, at most two frequencies per
factor signal, plus the three 2026-09 research strategies.

bridge.py live --book and bridge.py books feed a book selector on the
live page (/api/strategies/live?book=, /api/strategies/live/books).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 20:46:09 +08:00
parent 905ca4fe05
commit 5dbabc3e08
8 changed files with 309 additions and 47 deletions

View File

@@ -11,7 +11,7 @@ import { WatchlistStore } from "../agent/watchlistStore";
import { loadIpadsConfig } from "./ipadsConfig";
import type { ChartPeriod, WatchStock } from "../shared/types";
import type { BacktestConfig } from "../shared/strategyTypes";
import { listStrategies, runBacktest, getCachedResult, getLiveStrategies } from "./strategyService";
import { listStrategies, runBacktest, getCachedResult, getLiveStrategies, getLiveBooks } from "./strategyService";
const port = Number(process.env.PORT ?? 8787);
const host = process.env.HOST ?? "127.0.0.1";
@@ -284,7 +284,16 @@ app.get("/api/strategies/live", async (request, response, next) => {
try {
const market = String(request.query.market ?? "us");
const strategy = request.query.strategy ? String(request.query.strategy) : undefined;
response.json(await getLiveStrategies(market, strategy));
const book = request.query.book ? String(request.query.book) : undefined;
response.json(await getLiveStrategies(market, strategy, book));
} catch (error) {
next(error);
}
});
app.get("/api/strategies/live/books", async (_request, response, next) => {
try {
response.json(await getLiveBooks());
} catch (error) {
next(error);
}

View File

@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { REPO_ROOT, WEB_DATA_DIR } from "./paths";
import type { BacktestConfig, BacktestResult, LiveStrategyState, StrategyInfo } from "../shared/strategyTypes.js";
import type { BacktestConfig, BacktestResult, LiveBook, LiveStrategyState, StrategyInfo } from "../shared/strategyTypes.js";
const CACHE_DIR = join(WEB_DATA_DIR, "strategy_cache");
const TIMEOUT_MS = Number(process.env.QUANT_BACKTEST_TIMEOUT_MS) || 180_000;
@@ -157,12 +157,17 @@ export async function getCachedResult(id: string): Promise<BacktestResult | null
return normalizeBacktestResult(JSON.parse(readFileSync(cachePath, "utf-8")) as BacktestResult);
}
export async function getLiveStrategies(market: string, strategy?: string): Promise<LiveStrategyState[]> {
export async function getLiveStrategies(market: string, strategy?: string, book?: string): Promise<LiveStrategyState[]> {
const args = ["live", "--market", market];
if (strategy) args.push("--strategy", strategy);
if (book) args.push("--book", book);
return (await runBridge(args)) as LiveStrategyState[];
}
export async function getLiveBooks(): Promise<LiveBook[]> {
return (await runBridge(["books"])) as LiveBook[];
}
function normalizeBacktestResult(result: BacktestResult): BacktestResult {
return {
...result,