# Recipe Playground Dca

> Example driver: drive a time-based dollar-cost-averaging hypothesis into a recorded session.

- Skill: `jiayaoqijia/recipe-playground-dca` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add jiayaoqijia/recipe-playground-dca`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jiayaoqijia/recipe-playground-dca/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: jiayaoqijia (https://skillmd.com/u/jiayaoqijia)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jiayaoqijia/recipe-playground-dca

---


# Playground: DCA (Time-Gated)

> **PREREQUISITE:** Load `kraken-playground` and `kraken-dca-strategy` to run this recipe.
> **This recipe is an example, not a boundary.** Adapt the steps to your hypothesis, or write your own driver (see `kraken-playground` → Driving Your Own Hypothesis).

Test a dollar-cost-averaging hypothesis on live prices with no real money. Buy on a fixed cadence, optionally only when price dips below a short-term average, and record every buy, skip, and average cost into one session for later replay and P&L.

Use this skill for:
- running a time-based DCA hypothesis over a window
- gating each buy on a dip below a short-term average
- recording every decision with the numbers behind it

## Important

This recipe records a session inside a paper workspace. It never places a live order. Paper results may overstate live performance: fees and slippage are simulated and there are no partial fills (see `kraken-paper-strategy`).

Pacing is external. `/loop` owns the schedule and fires one round per interval (see `kraken-playground` → Pacing). One decision per interval, paced from outside, so the session is interventional over the window (see `kraken-playground` → Running over a Window).

## Params

Every number that gates a buy or skip goes in `--strategy-params`, not only in prose reasons. That is what makes two sessions comparable knob-for-knob:

- `dollars_per_buy`: quote currency deployed per qualifying round
- `rounds`: session length in decision rounds
- `interval_s`: spacing between rounds; set the `/loop` interval to this value
- `dip_threshold_pct` (dip-gated variant): only buy when price is at least this far below the short-term average; omit for unconditional time-based DCA

## Quick Start

Natural language:

```
Dollar-cost-average into Bitcoin over 10 hours. Buy $100 every hour if the
price dips more than 0.10% below the 1-hour SMA. Record all buys, skips, and
average cost. At the end, show P&L.
```

## Start the Session

Work inside a paper workspace (create one once: `kraken workspace create dca --capital 10000 --mode paper`), then:

```bash
export KRAKEN_WORKSPACE=dca

kraken session start \
  --symbols BTC/USD --channels ticker,trade --to duckdb,jsonl \
  --label dca-btc-$(date +%Y%m%d-%H%M%S) \
  --strategy recipe-playground-dca \
  --strategy-params '{"dollars_per_buy":100,"rounds":10,"interval_s":3600,"dip_threshold_pct":-0.10}' \
  -o json 2>/dev/null &

# The session_started stdout line carries the id: {"type":"session_started","session":"s<n>",...}
```

Print the session id to the user right after starting, and again in the final report — it is the handle for resuming the `/loop`, checking `kraken session show`, and locating the artifacts.

## Schedule the Rounds

`/loop` owns the pacing. Set its interval to `interval_s` and it fires each round on cadence (late under jitter, never early). Parse the interval from the user's request: "every hour" to `1h`, "every 30 minutes" to `30m`, "twice a day" to `12h`.

**The scheduler floor is 60 seconds** (`/loop`, cron, and `ScheduleWakeup` all clamp to a one-minute minimum). `interval_s` must be `>= 60`; a sub-minute cadence is impossible — clamp to `60` and tell the user if they ask for less.

```
/loop 1h "Run one DCA round for session s<n> in workspace dca per recipe-playground-dca"
```

Do not re-add an elapsed-time gate inside the round. `/loop` already enforces the spacing.

## Each Round

On each firing: stop if the session is complete, otherwise READ, THINK, ACT.

Check the last completed round against `rounds`. The round cursor lives in the session's typed state cell (`kraken session state get/set`) — not in the decision log, whose reasons stay free narrative (keep writing "round N" in them for the post-mortem story, but nothing parses it). Reading the cursor is O(1) however long the session runs, and a typo'd field refuses at `set` instead of silently steering the loop:

```bash
ROUNDS=10   # the `rounds` you set in --strategy-params
SESSION_ID=s1   # from the session_started line

# The typed cursor is the loop's memory: unset reads as round 0.
LAST_ROUND=$(kraken session state get --session "$SESSION_ID" -o json 2>/dev/null \
  | jq '.cursor.round // 0')
if [ "${LAST_ROUND:-0}" -ge "$ROUNDS" ]; then
  kraken session stop -o json 2>/dev/null
  exit 0
fi
ROUND=$((LAST_ROUND + 1))
# CLAIM the round BEFORE acting: a crash after the claim skips one buy
# (harmless); the reverse order would redo the round and buy twice. `set`
# replaces the whole cursor — carry every field your strategy tracks.
kraken session state set --round "$ROUND" -o json 2>/dev/null
```

READ: price and the short-term average. Mind the response shapes: the ticker
is keyed by Kraken's INTERNAL pair name (`XXBTZUSD`, not `BTC/USD`), so read
the price through the value (`.[].last_price`). The ohlc rows live under
`.candles`; read that array directly — the sibling `last` cursor would poison
naive iteration of the object.

```bash
PRICE=$(kraken ticker BTCUSD -o json 2>/dev/null | jq -r '.[].last_price')
# True 1h SMA: the last twelve 5-minute closes (NOT --interval 60, whose
# full-window mean is a multi-day average).
SMA=$(kraken ohlc BTCUSD --interval 5 -o json 2>/dev/null | jq '[.candles[-12:][].close] | add / length')
```

**Fail loud, never fabricate:** if either value comes back empty, the round
must not guess — note an alert and end the firing, or the decision log fills
with plausible-looking false reasons:

```bash
if [ -z "$PRICE" ] || [ -z "$SMA" ]; then
  kraken session note --kind alert --symbol BTC/USD \
    --reason "round $ROUND aborted: READ failed (price='$PRICE' sma='$SMA')" -o json 2>/dev/null
  exit 0
fi
```

Compute in `jq`/`bc`, do not eyeball it.

THINK:
- `vs_sma = (price - sma) / sma * 100`
- If the ticker spread is wider than 1%, skip and note it. A wide spread distorts the paper fill.
- If `vs_sma <= dip_threshold_pct`, buy `dollars_per_buy / price` units. Otherwise skip.

ACT (buy):

```bash
kraken order buy BTC/USD <volume> --type market \
  --reason "DCA round N: BTC/USD at <price> is <vs_sma>% below 1h SMA <sma> (threshold <dip>%); deploying \$<dollars_per_buy>" \
  -o json 2>/dev/null
```

Inside the paper workspace the order fills on the paper account, and the reason lands in the active session's decision log.

ACT (skip):

```bash
kraken session note --kind skip --symbol BTC/USD \
  --reason "skip round N: BTC/USD at <price> is <vs_sma>% from 1h SMA (threshold <dip>%)" \
  -o json 2>/dev/null
```

The round is already claimed, so nothing to write after acting: buy or skip,
log the reason, and exit the firing.

Always pass the numeric reason so the decision log carries the "why" behind every round.

## Stop and Review

```bash
kraken session stop -o json 2>/dev/null
kraken session show -o json 2>/dev/null | jq '.summary'
kraken explain pnl --session latest -o json 2>/dev/null | jq '{anchor, waterfall: [.components[] | {kind, amount}]}'

kraken session decisions --session "$SESSION_ID" -o json 2>/dev/null \
  | jq -c '.decisions[] | {kind, symbol, reason, timestamp}'
```

Report:
- total deployed vs expected (`dollars_per_buy × rounds`)
- buys vs skips, with dip measurements
- average fill cost vs window mean
- P&L, and whether the dip threshold fit the tape

## Hard Rules

- This recipe records a session inside a paper workspace. It never places a live order.
- `/loop` drives the pace and the agent runs each round. The CLI has no scheduler and runs no strategy.
- Do not add a pacing gate inside the round; `/loop` enforces spacing.
- A scheduled `/loop` fire only runs what the agent may invoke unprompted. Keep `kraken order buy`, `kraken session note`, and `kraken session show` permitted non-interactively, or a fire stalls waiting on an approval you never see.
- Keep every gating number in `--strategy-params` and the arithmetic in `jq`/`bc`, so a rerun on the same tape reproduces the decisions.
- The session directory (`session.json`, `decisions.jsonl`, and the tape sinks) is owned by the CLI recorder. The agent only **reads** it, and only for the stop-time summary. Never write, edit, `mkdir`, or append inside it — every buy and skip goes through `kraken order buy` / `kraken session note`, so the recorder stays the single writer.
- If you hit a mismatch between what you are trying to do and the CLI's interface or responses — including a mismatch between this skill and the installed CLI version's contract — feel free to submit feedback with `kraken feedback`.

