degenerator
one spec in, one degen bot out
# Momentum Degen venue: hyperliquid pairs: BTC, SOL, HYPE timeframe: 5m max_position: 5% stop_loss: 3% leverage: 3x cash: 1000 rules: - long when the 20 period average crosses above the 50 - close anything down 3% - flat before funding
├── README.md ├── .env.example ├── .gitignore ├── requirements.txt ├── Makefile ├── Dockerfile ├── compose.yml ├── .github/workflows/ci.yml ├── src/ │ ├── main.py │ ├── strategy.py │ ├── risk.py │ └── venue/ │ ├── base.py │ └── hyperliquid.py ├── tests/test_risk.py └── runs/
one spec inone degen bot out
degenerator reads a one-page spec file and writes a complete, working trading-bot repository: source, tests, a Dockerfile, GitHub Actions CI, an initialised git repo with the first commit made, and a JSON receipt of everything it wrote.
pip install -e .
github.com/0xbobaaa/degenerator
- python 3.9+
- no dependencies
- MIT
What it saves you
Starting a trading bot is half a day of scaffolding you have already written before: the same risk module, the same venue interface, the same test harness, the same Dockerfile, the same CI.
degenerator does that part in one command and leaves you the strategy, which was the only part that was ever yours.
Three promises the generator keeps
A rerun is safe
The spec is the source of truth and the repo is an artifact of it.
Every number in risk.py came from a line you wrote, and
the comment above it names that line.
CI exists on commit one
Tests and a docker build run on the first push, not in week three after something broke quietly. The generated repo arrives with a workflow already in it.
Nothing goes live on its own
Adapters refuse to construct with live=True. The order
path is left unwritten so that a human writes it and a human reviews
it before money is involved.
The spec language
A title, key: value lines, and a rules: list.
Unknown keys are kept and copied into the generated README, so nothing you
wrote is dropped. Rules are copied into the strategy as comments,
verbatim — turning English into trading logic is not something a generator
should pretend to do.
| key | default | notes |
|---|---|---|
| venue | paper | one of hyperliquid, dydx, robinhood, paper |
| pairs | BTC | comma separated |
| timeframe | 5m | free text, copied into the strategy as a comment |
| max_position | 0.05 | share of equity per position, % accepted |
| stop_loss | 0.03 | per position, % accepted |
| leverage | 1 | 3x and 3 both parse to 3; above 1 is refused on robinhood |
| cash | 1000 | starting paper equity |
Three ways a spec fails on purpose
Each of these is the real message, printed on one line, with a non-zero exit code.
-
A venue that does not exist.
degenerator: v.spec.md: unknown venue 'binance': choose one of hyperliquid, dydx, robinhood, paper -
A title with nothing in it, so there is no name to give the repo.
degenerator: t.spec.md:1: the title line is empty: write '# My Bot' -
No rules at all.
degenerator: r.spec.md: no rules: a bot with no rules is a random number generator
Venues
- hyperliquid — perps. Paper-mode adapter.
- dydx — perps. Paper-mode adapter.
-
robinhood — Robinhood Chain, an Ethereum layer-2 on Arbitrum
Orbit. Spot only: there is no leverage to ask for, so a
spec that pairs it with leverage above 1 is refused, pointing at the
line to delete. Its adapter marks fills
model=swaprather than implying an order book, and its.env.exampleasks for an RPC URL and nothing else. - paper — no venue at all, and the default.
The CLI
| command or flag | what it does |
|---|---|
| plan <spec> | print the file tree, write nothing |
| new <spec> | write it |
| -o, --out | output directory, default the slugified project name |
| --force | overwrite a non-empty directory |
| --no-git | skip git init and the first commit |
| --dry-run | report what would happen, touch nothing |
The tool that does it is five files:
degenerator/ ├── cli.py argparse front door ├── spec.py the spec language and its errors ├── scaffold.py writes the tree, runs git init ├── templates.py every generated file, as one flat table └── receipts.py runs/ receipts and the trace log tests/ ├── test_spec.py parsing, defaults, % and x suffixes, refusals └── test_scaffold.py scaffolds into a temp dir, runs the child repo's tests
Receipts
Every new run writes a JSON receipt next to where you ran it
and appends a line to runs/trace.log. If you cannot say which
run wrote a file and why, it is not a generator, it is a folder.
{
"at": "2026-09-08T19:25:57+00:00",
"spec": "examples/momentum.spec.md",
"project": "Momentum Degen",
"venue": "hyperliquid",
"risk": {
"max_position": 0.05,
"stop_loss": 0.03,
"leverage": 3
},
"files": [
"README.md",
".env.example",
"...",
"tests/test_risk.py"
],
"bytes": 13255,
"ms": 1044,
"git": "initialised · 1 commit"
}
The receipt stays where you invoked the tool rather than inside the
generated repo, so running new --force twice leaves that repo
byte-identical.
$DGEN
degenerator writes bots for Robinhood Chain, so the ticker lives on that chain rather than somewhere else with better fees.
The tool is MIT and stays MIT — the token is not a licence, a key or a gate. It is where the people building around this project stand.
TBA
- ticker
- $DGEN
- chain
- Robinhood Chain
- chain id
- 4663
- license
- MIT
The generated venue adapters are paper-mode stubs. There is no live order path in this repository, and nothing on this page is financial advice.
Safety
- Generated adapters are paper-mode stubs: placeholder prices, fake fills, no sockets. The loop runs end to end and touches no exchange.
-
An adapter refuses to construct with
live=True. The live order path is deliberately left to you to write and review. -
No signing, no wallets, no key handling. A generated
.env.examplenever names a private key, because a file that does teaches everyone who reads it the wrong habit. -
risk.pyenforces the caps from your spec and the generated tests assert those exact numbers. The caps are applied, not judged: nothing here decides your leverage is too high for you. - The generated bots place no trades. Every fill you will ever see from one is invented by the stub.