Agent skill for building a draft
You work on the script of a draft the user has open in the pnl editor. Your saved edits appear in their editor as you make them, and every run you start is paid from their compute credits. Listing and trading stay with the user.
Save this page as ~/.claude/skills/pnl-draft-script/SKILL.md (the raw file is
https://docs.tradepnl.xyz/SKILLS.md). Endpoints are in the
Backtest workspace API reference: drafts, the
active script, the workspace, proposals, backtests, evaluations, markets and
candles, and credits. The builder agent inside the app calls the same routes.
The user creates an API key under Agent CLI in the editor’s agent chat
menu and gives it to you as PNL_API_KEY. Send it on every request:
API=https://mainnet.tradepnl.xyz # testnet: https://testnet.tradepnl.xyzcurl "$API/api/v1/drafts" -H "authorization: Bearer $PNL_API_KEY"The API has its own hostname per network; the app hostnames (app.,
app-test.) serve the browser app only.
A drafts-scoped key only reads and edits (403 forbidden_scope on runs).
Workflow
Section titled “Workflow”- Read.
GET /api/v1/drafts/{id}/scriptreturns the active script withrevision,marketsanddiagnostics. Use the draft id the user gives you, or find it withGET /api/v1/drafts. - Consult the reference when unsure.
GET /api/v1/reference/scriptlists the language sections;GET /api/v1/reference/script/{section}returns one as markdown with samples. The same sections are static files:https://docs.tradepnl.xyz/reference/script/index.md(id, title, summary) andhttps://docs.tradepnl.xyz/reference/script/{section}.md. Read the index, then only the sections you need, rather than guessing. - Edit with
baseRevision.PATCH /api/v1/drafts/{id}/scriptwith{"baseRevision": N, "edits": [{"find": "...", "replace": "..."}]}(eachfindmust match exactly once) or{"baseRevision": N, "script": "..."}for a rewrite. The program is compiled first. - Fix diagnostics. A program that does not compile is refused with
422 invalid_scriptand line-numbereddiagnostics; nothing was saved. Fix those lines and send again. - Backtest.
POST /api/v1/backtestswith{"draftId": "..."}queues the draft’s saved program on its venue and answers202 {id}; thenGET /api/v1/backtests/{id}?detail=summaryuntil it stops answering 202. It is not validation and says nothing about passing the gate. - Iterate in the workspace from what the result says about the idea: save
variants as named scripts (
PUT /api/v1/drafts/{id}/workspace/scripts/{name}with{"source": "...", "requireCompile": true}), backtest them by name ({"draftId": "...", "script": {"name": "..."}}), and keep notes (PUT …/workspace/notes/{name}). The user sees all of it on the Build page. - Evaluate by name.
POST /api/v1/evaluations/fastandPOST /api/v1/evaluations/fulltake{"draftId", "script": {"name"}}; each verdict attaches to the version it evaluated. Both are queued: collect a fast evaluation fromGET /api/v1/evaluations/fast/{id}as you collect a backtest.
Trading costs
Section titled “Trading costs”Backtests and evaluations are already net of all trading costs; see After it compiles and the fee policy.
Credits
Section titled “Credits”Check the balance with GET /api/v1/me/credits (displayCredits) and the
prices with GET /api/v1/prices. A run the balance cannot cover is refused
before it starts with 402 and
{"error":"insufficient_credits","required":N,"balance":M,"topUp":"/credits"}:
stop and tell the user how many credits the next step needs. A run that fails
is not charged.
Full results, searches and trials
Section titled “Full results, searches and trials”- Keep full results on disk, not in your context. Make one cache directory
per draft (for example
./.pnl-cache/{draftId}/). Run variants withPOST /api/v1/backtestsand an inline{"script": {"source", "name"}, "venue"}, collect each withGET /api/v1/backtests/{id}?detail=full(the net equity curve, the window, per-market daily attribution; gzipped when you sendaccept-encoding: gzip), write each response to a file named by the sha256 of the script you sent, and keep only a one-line summary per run in your context. Analyse the files with your own tools. - Preview the cost of a search first.
POST /api/v1/trials/previewwith{"script": {...}, "planned": N}returns the family’s observed trials, its trial floor, the effective count now and after N more runs, and in words how much smaller a p-value would then have to be. Show that to the user before a search, and ask them when it multiplies the count. - Search with ask/tell, locally. Use an optimiser with an ask/tell
interface (optuna’s
study.ask()/study.tell(), TPE or random) on your machine: ask for a parameter set, write it into the script’sparamlines, backtest it through the same endpoint, and tell the study the metric you chose. Every call is a trial counted exactly like the builder agent’s runs, and costs credits. - Discipline. Optimise only a stated hypothesis over its declared params and bounds, with a small budget (30 or fewer unless the user agrees), and report how the best trial’s neighbours did. The best of N trials is biased upward: never present it as validated. A sharp optimum is usually noise; fast evaluation’s parameter perturbations show it.
Research workflow
Section titled “Research workflow”How to research a strategy:
- Explore cheaply with backtests and studies.
- Check promising candidates periodically with a fast evaluation. It is advisory feedback, not the gate.
- Once a candidate is solid, iterate with full evaluations. The full evaluation is the real gate and part of the feedback loop, not a one-off final step: read its verdict, improve the candidate, evaluate again.
- Every run (backtest, fast evaluation or full evaluation) is a trial of the strategy’s family and costs credits at the live price book.
Fast evaluation is POST /api/v1/evaluations/fast, collected from
GET /api/v1/evaluations/fast/{id}; full evaluation is
POST /api/v1/evaluations/full (check …/availability first) and
GET /api/v1/evaluations/full/{jobId} for its verdict. A full evaluation
costs far more than a backtest: ask the user before you start one.
- 409 means re-read.
409 stale_revisionmeans the user or the builder agent edited the draft since your read. Read it again and redo your change on the new script; never resend the old one. - Every backtest is a trial. Each one is counted against the user’s wallet, and the evaluation gate gets stricter with every configuration tried. Search only as above, and think between runs. A strategy that needs one exact constant does not work.
- Listing and trading stay with the user, signed in to the app.
- On
429(or503 queue_overloaded), wait forretry-after/retryAfterMsbefore trying again. At most two of your backtests may be queued or running at once (429 too_many_backtests).
The network backtest queue holds 250 queued or running jobs. Fast backtests get
60 seconds to reach their first worker, then 30 seconds for loading and execution.
queue_timeout and execution_timeout fail without a charge or successful trial.
A pending collection (queued or running) means poll the same job ID again;
a collection wait ending is not a reason to submit another job.