diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-17 14:32:37 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-17 14:32:37 +0800 |
| commit | 5c0ba37eda80d39e6ceca59bb1d5f4942f858995 (patch) | |
| tree | 948723f9cedf7ccb0707fa6ee516bd30fe20fd10 /SPEC.md | |
| download | strategy-lab-5c0ba37eda80d39e6ceca59bb1d5f4942f858995.tar.gz | |
chore: establish Strategy Lab source baseline (development, not release)
Diffstat (limited to 'SPEC.md')
| -rw-r--r-- | SPEC.md | 53 |
1 files changed, 53 insertions, 0 deletions
@@ -0,0 +1,53 @@ +# 策研 Strategy Lab — internal proof of concept + +User approved implementation, not further brainstorming. Deliver working end-to-end web product, real data and model integrations, tests, local running service. No public deployment or paid resale in this phase. No fake market/AI/backtest output. Synthetic test fixtures must carry _synthetic=true and never be production fallback. + +## Chosen architecture +Rust/Axum server, SQLite WAL, immutable on-disk data objects, Svelte 5 + TypeScript + Vite static UI, Python AKShare data worker and Backtrader strategy worker. No Node/Python HTTP service at runtime; one active worker task initially. Docker execution isolation (internal trusted-user POC only, not claim public hostile-code safety). Server binds 127.0.0.1:8787. Server serves frontend/dist. All work under this project. No global Hermes/config changes. Do not print credentials/env or inspect home secrets. All model requests must use provided OPENCODE_GO_API_KEY environment only. Never commit secrets, create public GitHub repos, or upload user code elsewhere except explicitly requested provider call. + +## Product flow +User logs in -> creates project -> chooses instruments, range, fields/frequency/adjustment -> previews prepared data and provenance -> edits Backtrader Python Strategy -> saves immutable revisions -> runs experiment -> inspects equity/trades/logs -> compares previous runs -> optionally asks AI for a proposed full source and unified diff -> accepts using optimistic draft revision guard. Every historical result ties to immutable code, dataset manifest, engine/config versions. Recovery from old revision creates a new draft, never rewrites history. Data requests are user-visible, public underlying market data deduplicated, each user's project/code/private objects ownership checked. + +## Scope +Daily bars only with visible capabilities, stock/ETF/index (index explicitly nontradable proxy and rejected for direct orders or labeled index-proxy research). User can search actual instrument identity; manually enter exact symbol with explicit market/type if catalog unavailable, never invented identities. One or multiple selected instruments stored in dataset; strategy receives named feeds, prices never substituted across symbols. Max 5 symbols, max 15 years for internal POC; backend validates. OHLCV required for engine, optional raw fields preserved; frontend explains derived indicators (SMA/RSI etc) are calculated in code with warmup, not fetched. Unsupported frequencies/fields rejected. Date coverage differences surfaced before running. No portfolio rankings/live trading/payments. Model usage ledger and budget/quotas present but no payment processor. + +## HTTP contract (backend owns; all routes prefix /api) +JSON errors {error:{code,message,details?}}. IDs UUID strings; timestamps ISO UTC. Server uses cookie session HttpOnly SameSite=Strict, bounded lifetime; writes require same-origin check where Origin supplied, reject cross-site Sec-Fetch-Site, JSON content type. No open registration; bootstrap user via CLI/env, passwords Argon2, random session token hashed in DB. API response objects no secret fields. +- GET /health public => {status,version,worker_available,ai_configured} +- POST /auth/login {email,password} => {user:{id,email,name}} and cookie +- POST /auth/logout; GET /auth/me => {user} +- GET /capabilities => {frequencies:['daily'],asset_types,adjustments,fields,limits,internal_only:true} +- GET /instruments?q=... => {items:[{symbol,market,asset_type,name,currency}],source,status}; query cache via AKShare catalog optional; explicit symbol entry fallback UI. +- GET /projects => {items:[Project]}; POST /projects {name,description?} => Project +- GET /projects/:id => Project; PATCH /projects/:id {name?,description?} +Project {id,name,description,draft_code,draft_generation,created_at,updated_at} +- PUT /projects/:id/draft {code,expected_generation} => Project; stale generation 409 +- GET /projects/:id/versions => {items:[Version]}; POST same {message} => Version (snapshots draft) +Version {id,project_id,code,hash,message,created_at,source:'manual'|'run'|'ai'|'restore'} +- POST /projects/:id/restore {version_id,expected_generation} => Project +- GET /datasets => {items:[Dataset]}; POST /datasets {name,instruments:[{symbol,market,asset_type,name?}],start_date,end_date,frequency:'daily',adjustment:'none'|'qfq'|'hfq',fields:[...]} => Dataset, async pending +Dataset {id,name,request,status:'pending'|'running'|'ready'|'failed',manifest?,error?,created_at,cache_hit?} +- GET /datasets/:id => Dataset; GET /datasets/:id/preview => {columns,rows,coverage,warnings}; ownership enforced. +Manifest {id,hash,objects:[{instrument,object_hash,path internal-only,provider,endpoint,params,akshare_version,fetched_at,schema_version,normalization_version,adjustment,requested_start,requested_end,actual_start,actual_end,row_count,columns,raw_object_hash,warnings}], immutable:true}; don't expose host paths to clients. +- GET /runs[?project_id] => {items:[Run]}; POST /runs {project_id,dataset_id,capital,commission,slippage,benchmark_symbol?,parameters?:{},acknowledge_warnings?:bool} => Run. Snapshots draft, links version id and manifest hash. Fail if dataset not ready. Defaults commission .0003/slippage .001 but user sees. Validate bounded numeric settings and code lengths. +Run {id,project_id,version_id,dataset_id,status:'queued'|'running'|'succeeded'|'failed'|'cancelled',config,created_at,started_at?,finished_at?,error?,result?} +- GET /runs/:id => Run incl result; POST /runs/:id/cancel => Run; POST /runs/:id/rerun {use_original_data:true} => new Run pinned original code+data+config; no secret fetching latest. +Result {equity:[{date,equity,cash,benchmark?}],orders:[...],trades:[{date,symbol,side,quantity,price,commission,value}],metrics:{total_return,annual_return,max_drawdown,sharpe?,trade_count,final_equity},logs:[string],engine:{name:'backtrader',version},warnings,elapsed_ms,peak_rss_kb?,data_manifest_hash}. Return undefined metrics as null, never NaN/Inf. Trade_count semantics documented. Next-bar fills. Record source model limitations e.g no liquidity/price limits fully modeled; don't call production investment simulator. +- POST /ai/assist {project_id,instruction,expected_generation} => {id,model,explanation,proposed_code,diff,base_generation,usage:{...},status}. Server calls https://opencode.ai/zen/go/v1/chat/completions model glm-5.3-flash, configurable base/model. Send only scoped code, data schema and trusted framework docs prompt. No auto-exec/provider tool execution. Parse fenced full python or JSON output robustly, retain failure ledger. No hardcoded fake suggestion. +- POST /ai/:id/accept {expected_generation} => Project and new version source ai; check owner/project/base generation, reject stale. +- GET /ai/usage => {items:[...],totals:{...},internal_poc:true}; measure real usage, do not invent price/cost. Budget per user request/day cap and input/output caps. Key server-only environment, no config endpoint exposes it. + +## Worker filesystem protocol +Docker image strategy-lab-worker:local built from worker/Dockerfile. Root project requirements/scripts local venv for tests/data probes. Python executable entry worker/main.py subcommands: +`python -m worker.main fetch --request /input/request.json --output /output` network enabled for data adapter only. Output /output/result.json {status:'ready',manifest,preview,cache_key}; data CSV/Parquet plus raw JSON actual source response. Main source current AKShare APIs, configurable request details; no silent different provider/adjustment fallback. AKShare failures reported. Precision/source units kept. Shared cache outside fetch jobs: backend content hashes output directory artifacts and copies/moves into data/objects; exact-key cache and overlap reuse where supported. Concurrent same-key work serialized/rechecked. Immutable old snapshots not overwritten. Raw data stored before normalized transform. Cold storage retention documented. +`python -m worker.main backtest --request /input/request.json --output /output` input {code,config,dataset_manifest,data_root:'/data'}; only dataset files mounted read-only. Output result.json above. Validate Strategy class presence with syntax check; exec ONLY in isolated strategy worker, never API. Backtrader mature engine. Record open orders/fills and cash correctly, no same-bar lookahead. `Strategy` receives feeds ._name instrument canonical identity. Minimum lot/T+1/suspension capabilities explicitly stated and enforced for supported stock/ETF where applicable or reject unsupported; don't silently imply fidelity. Synthetic deterministic accounting fixtures for tests. Runtime record real process RSS and elapsed. No fabricated real market data. +Docker runner: no Docker socket in worker, no provider/auth env, non-root uid, cap-drop=ALL, no-new-privileges, read-only root, tmpfs /tmp, pids-limit, memory/cpu limit, backtest --network none; write output only and read input/data only. Kill task by specific container id on timeout/cancel, no global prune. Restart running tasks become failed/interrupted, queued resumable. Control host API may use Docker socket; local/internal security limitation explicit. + +## Visual design +Chinese primary UI, product name 策研 / Strategy Lab. Modern calm light research workspace, near-white, slate text, restrained teal highlights, good whitespace, no noisy gradients/glass, no marketing dashboard filler. Sidebar + clear project tabs 数据 / 策略 / 回测 / 结果, global 数据集 / 实验记录 / AI用量. CodeMirror editor, ECharts line charts lazy loaded, real tables, contextual empty/loading/error states. Inputs labeled, keyboard focus, responsive 375px viewing and desktop editing. SF Pro system font then Noto Sans CJK SC fallback, avoid copying restricted font files. AI diff accept/reject and version compare readable (can use diff package). Autosave debounce with visible status, no stale response overwrite. Selection multiple prior runs shows aligned comparison and explicit differing conditions. No dead buttons, placeholder fake metrics or fake model content. Login first run should work via supplied credentials; never embed real passwords. + +## Acceptance +Tests first RED then GREEN per component. Backend ownership/auth/CSRF/bounds/versions/restore/run lifecycle/shared-cache/AI stale acceptance tests. Worker synthetic (_synthetic true) accounting, dates, adjustment/unit/raw retention and error-path tests. Frontend checks/build and Playwright real browser happy path plus error/version/diff tests. Live AKShare at least one real instrument fetched and inspected; use exact identifier/name verify. Complete real backtest (not fixture) displayed in browser. AI real call yields proposed code accepted as new version without overwrite. At least two user accounts cannot read each others objects. Cache repeated dataset proves shared physical object reused. Restart persistence and failed runs retained. No secret values in logs/source/build. Measure idle process RSS and one run peak/time. Document limitations honestly, no public-readiness claim. All acceptance evidence in artifacts/qa with actual output. + +## Work boundaries +backend implement server/ plus docs/backend.md only; worker implement worker/ tests/worker/ requirements-worker.txt docs/worker.md only; frontend implement frontend/ docs/frontend.md only. Common interface is this file. Any cross-contract concern write docs/<area>-questions.md. Other agents concurrently working: never modify their files. Root README/deploy scripts/integration fixed later. Do not use git commits or modify global system services. Avoid broad recursive reading outside project. |
