Your first strategy¶
A strategy is a text file. The engine reads it at startup and turns it into orders. You do not write OCaml for this, and you do not need to understand the order router.
This walkthrough adds a small overlay to an asset that is already trading. The overlay keeps one spot buy order below the market. It is deliberately boring: it will not fill unless the price drops, so you can watch the loop work without opening a position by accident. At the end we will cancel it.
Why an overlay¶
A primary strategy owns the asset's grid: the buy rungs, the sell levels, the accumulated balance, all of it. An overlay is a second file attached to the same asset. It can place orders and reserve quote or base, but it does not replace the grid.
That makes an overlay the smallest useful thing to write first. If the overlay has a bug, you can delete it and the grid keeps running.
1. Create the file¶
Create strategies/hello_overlay.strategy:
strategy hello_overlay
version 1
tunable:
qty: float = 0.0002
offset_pct: float = 0.10
when book.updates:
context:
market.facts
keep.bid:
if $platform.spot.bid > 0.0:
order.replace(market: "spot", side: "buy", qty: $params.qty,
price: $platform.spot.bid * (1.0 - $params.offset_pct),
tif: "ALO", dedup_key: "hello:buy")
Here is what each piece does. You can read the file top to bottom, which is the point of the language.
strategy hello_overlay is the name. It has to match the file name without the
.strategy suffix, or the engine refuses to start.
version 1 is the language version. Leave it alone for now.
tunable: declares two numbers you can edit without touching the logic. qty is in base
units, not dollars. offset_pct is a fraction, so 0.10 means 10% below the best bid.
when book.updates: says: run the steps below each time the asset gets a new top-of-book
snapshot.
context: is a step label. The engine does not care what you call it. market.facts
refreshes the market facts for this cycle, including $platform.spot.bid.
keep.bid: is another step. Labels are yours. Keep them short and put dots between words;
the engine turns keep.bid into keep_bid internally.
if $platform.spot.bid > 0.0: is a guard. A zero bid means the book is not ready, and we
do not want to price an order off a missing number.
order.replace(...) keeps one order for the key hello:buy. The first time it runs, it
places the order. On later cycles it finds that same order and amends the price if the bid
moved. tif: "ALO" means post-only: the order rests as a maker and will not cross the
spread.
One detail that trips people up: the log will say tif=GTC even though the file says
ALO. ALO is the engine's name for a post-only GTC order. The venue receives a post-only
limit.
Pick a quantity above your venue's minimum notional. On BTC/USDC, 0.0002 BTC is about
$17 at $85k, which clears Hyperliquid's $10 minimum. If you use a different asset or venue,
adjust it.
2. Validate¶
From the repo root:
dio strategy validate strategies/hello_overlay.strategy
You should see:
strategy "hello_overlay" (strategies/hello_overlay.strategy): ok
If it fails, the message gives the line and the name it did not like. Fix that before you put the file anywhere near a live config. The validator catches misspelled action names, wrong argument names, and bad references. It is much cheaper than finding the typo in a log at 2am.
3. Attach it¶
Pick an asset group in trading.assets that does not already have an overlay. An
asset group has at most one overlay. If your asset already has one, use another asset for
the experiment or remove the old binding first. If every asset you trade already has an
overlay, use a paper account or a spare symbol.
Add the overlay section to that group in config.json:
{
"asset": "BTC",
"strategy": {
"exchange": "hyperliquid",
"symbol": "BTC/USDC",
"name": "jacobs_ladder",
"qty": "0.0001"
},
"overlay": { "exchange": "hyperliquid", "name": "hello_overlay" }
}
overlay.exchange names the venue the overlay trades; here it is the same venue as the
strategy, but it may differ when the hedge lives elsewhere. overlay.symbol is optional
and defaults to the owning strategy symbol when the venues match (or <COIN>/USDC for a
Hyperliquid hedge). Leave the carry params out. A custom overlay reads its tunable values
from the file.
If your config still uses the legacy flat trading array, the same binding is
"overlay": { "strategy": "hello_overlay" } inside the trading entry; it inherits that
entry's exchange and symbol.
4. Restart and watch¶
Strategy files are read at startup. There is no hot reload.
docker compose down
docker compose up -d
docker compose logs -f engine
Look for the line that names your file:
strategy: BTC/USDC overlay strategies/hello_overlay.strategy
Then look for the order:
Overlay order: buy BTC/USDC 0.0002 @ 76500 tif=GTC reduce_only=false key=hello:buy
The price depends on the current bid and on the venue's tick rounding. Check the venue's open orders. You should see one spot buy, roughly 10% below the market.
If the order is not there, check three things in this order: the overlay line in the log, the minimum-notional check in the log, and the venue's open orders. The engine will skip an order below the venue minimum and keep going, so the strategy can look like it did nothing.
5. Change one thing¶
Edit offset_pct from 0.10 to 0.08. Validate, restart, and watch the resting order move
closer to the bid.
That is the whole loop:
- Edit the file.
- Validate.
- Restart.
- Read the log and check the venue.
There is no hidden state to reset for this example. The order key is stable, so the engine amends the same order instead of stacking a new one.
6. Stop it¶
Do not just delete the overlay from config.json. The order is already on the venue, and
removing the overlay leaves it there.
Cancel it first. Replace the step body with this:
when book.updates:
context:
market.facts
cancel:
if $platform.spot.bid > 0.0:
order.replace(market: "spot", side: "buy", qty: 0.0,
price: $platform.spot.bid, tif: "ALO",
dedup_key: "hello:buy")
Validate, restart, and confirm the open order is gone. order.replace with qty: 0.0
cancels the resting order for that key. Then remove the overlay entry from config.json
and restart once more.
You can also cancel the order in the venue's UI. The file is not the source of truth for orders that are already resting; it only manages them while it runs.
What you just used¶
One trigger, one fact, one action:
when book.updates:decides when the file runs.$platform.spot.bidis the number it reads.order.replaceis the order it keeps on the book.
Everything else in the language is another version of that shape. You add facts to the guard, actions to the body, and remembered variables when you need to carry a decision from one cycle to the next.
Where to go next¶
jacobs_ladder.strategy is the full grid. Read it after this page. The step labels are the
same kind of labels you just wrote, and the buy and sell phases map to the actions in the
glossary.
- Strategy script language is the reference for triggers, facts, expressions, and the file grammar.
- Action glossary lists the actions the engine actually handles, plus the older names that it does not.
- Authoring a strategy covers binding, validation, and restart details in more depth.
- Editor support if you want inline validation while you type.