Skip to content

FastAPI backtest server

The server is a thin concurrent control plane layered on the published pineforge-release image. Market data remains a host-side provider concern; the harness sends normalized OHLCV, raw PineScript, syminfo, runtime options, and strategy inputs to POST /v1/backtests.

Endpoints

  • GET /healthz — process liveness.
  • GET /readyz — release tools, concurrency counters, and cache statistics.
  • POST /v1/backtests — synchronous backtest request; concurrent HTTP requests execute independently up to the configured process limit.
  • GET /docs — generated OpenAPI documentation.

Clients may supply X-Request-ID; otherwise the server creates one. When PINEFORGE_SERVER_API_KEY is set, the backtest endpoint requires Authorization: Bearer <token>. Health endpoints intentionally remain unauthenticated for container orchestration.

The request option trade_start_time_ms suppresses order execution before the given Unix-millisecond boundary while still processing every submitted bar. The CLI harness sets this automatically when --warmup-bars is nonzero; direct API clients must submit both the warmup bars and the boundary explicitly.

Concurrency and overload behavior

Run one Uvicorn worker per container. The service owns a process-wide semaphore; additional Uvicorn workers would multiply the configured compiler limit. Scale horizontally with multiple containers when more capacity is required.

Environment variable Default Meaning
PINEFORGE_SERVER_CONCURRENCY 2 in the image simultaneous compile/backtest processes
PINEFORGE_SERVER_MAX_QUEUE 8 in the image admitted requests waiting for a slot
PINEFORGE_SERVER_QUEUE_TIMEOUT 30 seconds before a queued request returns 503
PINEFORGE_SERVER_EXECUTION_TIMEOUT 300 total transpile, compile, and backtest deadline

When running and queued capacity is full, new requests receive HTTP 429. A transpile, compile, or engine failure returns HTTP 422 with a structured phase, code, message, and request ID. Execution timeouts return HTTP 504.

Compile cache

Pine source is transpiled for each request. The server hashes the generated C++ and combines that digest with the release image, engine/codegen versions, architecture, and parity-critical compiler flags. The resulting key identifies the compiled .so artifact.

This deliberately does not key compiled artifacts by Pine source: codegen changes can alter generated C++ for identical Pine, while different Pine inputs can theoretically produce the same translation unit. Writes are atomic and concurrent misses for one key are deduplicated.

Environment variable Default Meaning
PINEFORGE_SERVER_CACHE_DIR /cache compiled artifact directory
PINEFORGE_SERVER_CACHE_MAX_ENTRIES 1024 maximum retained .so files
PINEFORGE_SERVER_CACHE_MAX_BYTES 2147483648 maximum retained bytes

Mount /cache as a named volume to preserve compiled strategies across server restarts. The cache metadata returned with each result includes key, hit, and generated_cpp_sha256.

Runtime channel

The Dockerfile defaults to a semver-and-digest-pinned release image. Override it at build time for a rolling development server:

docker build -f docker/server.Dockerfile \
  --build-arg PINEFORGE_RELEASE_IMAGE=ghcr.io/pineforge-4pass/pineforge-release:latest \
  -t pineforge-data-server:latest .

Do not use the rolling channel for reproducibility-sensitive production runs. The cache key includes component/release identity, so a runtime upgrade cannot reuse a compiled artifact from a different engine version.

Deployment boundary

The server accepts CPU- and memory-intensive work. Bind it to a private network, set an API key, enforce request-size and rate limits at the ingress, and do not expose it directly to the public internet. The container runs as UID 10001; deploy it with a read-only root filesystem, an executable /tmp tmpfs, dropped capabilities, no-new-privileges, and a writable cache volume.