Spaces:
Sleeping
Sleeping
CLAUDE.md
Project Overview
Stocker β multi-agent council RL environment for stock trading on
OpenEnv. Seven specialist LLM analysts vote each step; a moderator LLM
merges votes into a (side, quantity) trade. The moderator is GRPO-trained
via TRL on top of google/gemma-4-E4B-it.
Stack
- Language: Python 3.10+ (Docker image runs 3.13)
- Framework: FastAPI + Uvicorn
- Models: Pydantic v2 + pydantic-settings
- OpenEnv:
openenv-core>=0.2.0 - Package manager:
uv(deps grouped into optional extras:data,eval,serve,train) - Tests: pytest + FastAPI TestClient (
MockLLMClientfor council tests β no GPU/API needed) - Serving: vLLM (OpenAI-compatible at :8000) β see scripts/serve_vllm.sh
- Training: TRL
GRPOTrainer+ PEFT LoRA β see training/train_grpo.py
Layout
.
βββ app/
β βββ api/ # HTTP routers (health, meta, env, state, frontend)
β βββ council/ # 7 specialists + moderator + asyncio runner
β β βββ llm.py # OpenAILLMClient, MockLLMClient, parse_json_object
β β βββ specialists.py # ChartPattern / Seasonal / Indicator / News / Forum / Peer / Geo
β β βββ moderator.py # merges votes β TradeAction (extra_body for LoRA)
β β βββ runner.py # Council.run / run_async + .cache/council/* on-disk cache
β βββ core/ # environment.py, graders.py, tasks.py
β βββ data/loader.py # parquet lookups + chart_path
β βββ config.py
β βββ main.py
β βββ models.py # Pydantic schemas (the public OpenEnv contract)
βββ data/ # bundled by scripts/build_dataset.py
β βββ *.parquet
β βββ charts/ # 768x768 candlestick PNGs
β βββ sources/ # curated news / forums / macro JSON
βββ server/app.py # OpenEnv entry point (`server.app:main`)
βββ inference.py # council-driven OpenEnv inference loop (root)
βββ client.py
βββ scripts/ # build_dataset, render_charts, serve_vllm, validate_tasks
βββ training/ # eval_rollout, train_grpo, runs/
βββ tests/
βββ Dockerfile
βββ openenv.yaml
βββ pyproject.toml
Conventions
- All Pydantic schemas live in app/models.py. Don't move
them. Names:
MarketObservation,TradeAction,SpecialistVote,CouncilDecision,RewardResult,StepResult,ResetResult,EnvironmentState. StockerEnvexposesreset() / step() / state() / load_snapshot().- The OpenEnv contract is single-agent. Multi-agent council lives in
app/council/andinference.pyβ never insidestep(). inference.pyMUST keep the[START] / [STEP] / [END]log format and also emit[COUNCIL]per step β graders parse all four.- Reward is clipped to
[-1.0, 1.0]at the env boundary (app/core/graders.py). - Specialists return JSON
{"signal": float, "confidence": float, "rationale": str}β the parser in app/council/llm.py tolerates fenced code blocks and trailing prose. - The moderator response goes with
extra_body={"lora_request": {"name": ...}}when a LoRA is configured, otherwise plain base. - Cache layout:
.cache/council/<role>/base/<ticker>__<date>.jsonfor specialists,.cache/council/moderator/<lora>/<ticker>__<date>__<hash>.jsonfor the moderator (votes hash differentiates). - Tests use
MockLLMClientβ neverHF_TOKENin CI. The mock routes by system-prompt keyword, so changing role keywords requires updatingMockLLMClient.SIGNAL_BIAS.
Don't
- Don't introduce a frontend build step β the HTML lives inline in app/api/frontend.py.
- Don't bake live API calls into specialists. Council inputs come from the
bundled parquet dataset (
app/data/loader.py). Live sources are the data builder's job. - Don't pull large model weights into the Docker image; serve via vLLM externally.
- Don't break the per-step
[COUNCIL]log line format β the writeup + trainer's reward replay both depend on it. - Don't make specialists rely on the moderator's LoRA. Specialists must remain frozen so their cached votes are reusable across GRPO steps.