A Gymnasium-compatible, microstructure-aware backtest environment with O(1)
steps for fast RL rollouts over 514 streaming indicators. The whole dataset is
precomputed once into a fixed feature tensor, so step() is a pure array index —
and the same seed produces a byte-identical trajectory in every language.
Part of the Wickra ecosystem. Built on the same deterministic engine and ten-language binding surface as wickra-backtest, wickra-benchmark and the rest.
wickra-gym turns a candle dataset plus an observation/action/reward
specification into a reinforcement-learning environment. It precomputes every
bar's features (indicators, and optionally orderbook / funding / open-interest
microstructure) in one O(1)-per-bar pass into a fixed FeatureTensor; from then
on each step() is a constant-time index into that tensor, so rollouts are fast.
The reward draws on the wickra-backtest
fill/PnL model, and the only source of randomness is an explicitly-seeded episode
RNG — so a (seed, policy) pair fully determines a trajectory.
The core (gym-core) is a JSON-over-C-ABI data API usable in ten languages, and
Python additionally ships a real gymnasium.Env subclass as its primary
consumer.
- O(1) steps — the dataset is precomputed once to a fixed feature tensor;
step()is a pure array index, never a recompute. - Seed-determined trajectories — the only RNG is the seeded episode RNG
(never
thread_rng); the same(seed, policy)yields a byte-identical trajectory in every language and between the parallel (rayon) and sequential (WASM) tensor precompute. - Fixed observation layout — an observation is an
f64vector of fixed length and canonical order (theObsSpecorder), so it is comparable across languages bit for bit.
Pre-release — functionally complete, CI-verified, not yet published. The core, the CLI, all ten language bindings, the golden trajectory corpus, the property + fuzz suites, the benchmarks and one runnable example per language are built and green across Linux, macOS and Windows. Packages are not yet on the registries. Track progress in ROADMAP.md and CHANGELOG.md.
ARCHITECTURE.md— the workspace, the feature tensor, and the environment lifecycle.docs/OBSERVATIONS.md— theObsSpec, the feature families, and the exact observation-vector order.docs/ACTIONS_REWARDS.md— discrete/continuous action spaces, the position model, and the reward kinds.docs/GYMNASIUM.md— thegymnasium.Envsubclass, how theBox/Discretespaces are derived, andmake/register.docs/MICROSTRUCTURE.md— orderbook levels, funding and open-interest observations.THREAT_MODEL.md— assets, actors, and what determinism buys.
Drive a fixed deterministic policy through an environment from the command line:
# A discrete "always long" rollout on the bundled example data, seed 42.
cargo run -p wickra-gym -- \
--spec examples/data/specs/momentum_discrete.json \
--data examples/data/series/BTCUSDT.csv \
--policy always-2 --seed 42
# Golden-format JSON ({reset, trajectory}) instead of the human-readable table.
cargo run -p wickra-gym -- \
--spec examples/data/specs/momentum_discrete.json \
--data examples/data/series/BTCUSDT.csv \
--policy always-2 --seed 42 --format jsonA spec names the observation features, the action space, the reward kind and the episode bounds; the CLI loads the candle CSV, resets with the seed and prints each step's reward and equity. The same spec, data and seed produce the same trajectory here as in every language binding.
The Python binding ships a real gymnasium.Env subclass — the environment plugs
straight into the RL ecosystem (Stable-Baselines3, CleanRL, RLlib, …):
import json
import gymnasium as gym
from wickra_gym import register
register() # registers "WickraGym-v0"
spec = json.dumps({
"dataset_ref": "demo",
"symbol": "BTCUSDT",
"observation": {"features": [
{"kind": "indicator", "name": "Rsi", "params": [14]},
{"kind": "price", "field": "close"},
]},
"action_space": {"type": "discrete", "n": 3},
"reward": "pnl",
"episode": {"max_steps": 256, "warmup": 15},
"seed": 42,
})
candles = [{"ts": 0, "open": 100, "high": 101, "low": 99, "close": 100, "volume": 1000}, ...]
env = gym.make("WickraGym-v0", spec_json=spec, candles=candles)
obs, info = env.reset(seed=42)
obs, reward, terminated, truncated, info = env.step(env.action_space.sample())observation_space is a Box derived from the spec (finite bounds where the
core knows them, ±inf otherwise); action_space is Discrete(n) or a 1-D
Box. Without Gymnasium installed, the lower-level RawEnv (the JSON command
surface) still works — see docs/GYMNASIUM.md and
examples/python/.
- Observation — an
ObsSpeclistsFeatures in declaration order, and the observation vector concatenates them in exactly that order: the features, then4 × include_book_levelsorderbook columns, then2funding/OI columns if requested. A feature is an indicator ({"kind":"indicator","name":"Rsi", "params":[14]}, optionally a sub-output"field"), a price field, or a microstructure field. Beforewarmupthe indicators are not ready, so the firstwarmupbars are skipped rather than emitted asNaN. - Action —
Discrete{n}mapsnbuckets onto a target position;Continuous{low,high}sets the target position directly (clamped). The position feeds thewickra-backtestfill/PnL model. - Reward —
Pnl,SharpeorLogReturn, computed from the realised position path.
Full semantics (with the exact ordering and formulas) are in
docs/OBSERVATIONS.md and
docs/ACTIONS_REWARDS.md.
Beyond price and indicators, the observation can include exchange
microstructure: set include_book_levels: N to append bid_px, bid_sz, ask_px, ask_sz for each of N orderbook levels, and include_funding_oi: true to
append funding rate and open interest. When a dataset lacks a requested column
the value is a defined 0.0 (never NaN), so the observation length stays
fixed. See docs/MICROSTRUCTURE.md.
The core drives a single command_json envelope (load, reset, step,
spec, version); every binding passes the same string through verbatim, so a
trajectory is byte-identical everywhere. The golden/ fixtures pin one
blessed response per case and the cross-language golden tests assert byte-for-byte
equality — the same observations, rewards and termination flags in all ten
bindings and between the parallel (rayon) and sequential (WASM) tensor
precompute. One runnable example per language lives under
examples/; per-binding quickstarts are in each
bindings/<lang>/README.md.
| Language | Binding | Package |
|---|---|---|
| Rust | gym-core (native) |
crates.io |
| Python | PyO3 (native) + gymnasium.Env |
PyPI |
| Node.js | napi (native) | npm |
| WASM | wasm-bindgen (native) | npm |
| C / C++ | C ABI | header + library |
| C# | C ABI (P/Invoke) | NuGet |
| Go | C ABI (cgo) | Go module |
| Java | C ABI (FFM/Panama) | Maven |
| R | C ABI (.Call) |
R-universe |
crates/gym-core the library: feature tensor + env + reward + command
crates/gym-cli reference CLI, binary `wickra-gym`
crates/gym-bench Criterion benchmarks
bindings/{c,python,node,wasm,go,csharp,java,r} ten-language surface
golden/ spec + policy + seed -> byte-exact trajectories
examples/ runnable per-language rollouts + shared data
fuzz/ cargo-fuzz targets (spec/command/tensor/step)
docs/ observations, actions/rewards, gymnasium, microstructure
cargo build --workspace
cargo test --workspace --all-featuresEach binding builds with its own toolchain; see bindings/<lang>/README.md. The
C-ABI consumers (C/C++, C#, Go, Java, R) need the C ABI library first:
cargo build --release -p wickra-gym-c.
Rust 1.86 (workspace) / 1.88 (Node binding). Per-binding toolchains: Python 3.9+ (Gymnasium needs 3.10+), Node.js 22+, .NET 8, JDK 22+, Go 1.23+, R release, and a C11/C++14 compiler with CMake for the C example.
Criterion benchmarks for reset + N × step throughput (at observation
dimensions 5/20/50) and build_tensor (at 1k/10k/100k bars, parallel vs
sequential) live in crates/gym-bench; numbers and methodology are in
BENCHMARKS.md.
See CONTRIBUTING.md and the Code of Conduct. Every change runs the full CI matrix (all ten languages × three OSes) plus CodeQL, Scorecard, zizmor and the metadata audit.
Report vulnerabilities per SECURITY.md. The threat model is in THREAT_MODEL.md.
Dual-licensed under either MIT or Apache-2.0, at your option.
wickra-gym is research and engineering tooling, not financial advice. A trained
agent's backtested performance says nothing about future returns; markets carry
risk and you are responsible for your own decisions. wickra-gym is free
software you run yourself: no hosted service, no data collection, no warranty.
