Configuration reference¶
The engine reads config.json from the working directory. The file is JSONC:
// and /* */ comments, trailing commas, and a UTF-8 BOM are accepted.
Credentials and one-off overrides come from the environment; if a .env file
is present it is loaded dotenv-style (KEY=value per line). Start from
config.example.json and .env.example (both ship
inside the image at /usr/share/doc/dio/; see
DEPLOYMENT.md for extracting them).
The trading schema is strict: an unknown key under trading, an unknown
key in the oracle section, or a key that does not apply to the selected venue
causes the engine to exit at startup. This is deliberate, because a typo should not
silently trade the wrong thing. A transient read or JSON syntax failure after a
successful load keeps the last known good configuration; a schema violation
always exits.
Contents¶
- Top-level keys
- GC tunables
- Trading layout
- Grouped layout
- Legacy flat entries
- Trading entry keys
- Carry overlay params
- Oracle section
- Decision-flow telemetry
- Environment variables
Top-level keys¶
| Key | Default | Description |
|---|---|---|
logging_level |
INFO |
One of DEBUG, INFO, WARN, ERROR, CRITICAL. |
logging_sections |
unset | Comma-separated section filters; unset enables all sections. |
logging_width |
autodetect | Message column width; autodetected from the terminal or COLUMNS. |
cycle_mod |
10000 |
Legacy interval for periodic background work; unused by current strategies. |
latency_window_seconds |
5.0 |
Rolling window for network latency profiling statistics. |
latency_spike_threshold_us |
10.0 |
Per-stage ceiling; a window that breaches it emits one INFO line naming the offending stages, their worst spike, and breach count. |
latency_spike_report |
internal |
Which latency families emit spike logs: internal (per-domain pipeline), network (ws_ping/ws_feed/rest_request/signer), both, or none. |
latency_spike_report_seconds |
30.0 |
Minimum wall-clock seconds between per-domain internal spike log lines; 0 logs every window. |
latency_network_spike_threshold_us |
20000.0 |
Ceiling for the network spike family, in microseconds (20 ms); separate from the 10 us internal-operation target. |
gc |
see below | OCaml GC tunables applied before the engine starts. |
oracle |
see below | Capital-oracle knobs. Accepted only with the legacy flat trading array; the grouped layout uses trading.defaults.oracle and per-asset overrides. |
trading |
required | Grouped { "defaults", "assets" } object, or the legacy flat array of entries. |
fng_check_threshold |
1.5 |
Price movement (percent) from baseline that re-triggers a Fear & Greed check. |
theme |
unset | Default dashboard theme identifier; overridable with dio-dashboard --theme. |
alert_level |
warn |
Lowest severity that raises the dashboard alert popup: off, info, warn, error, or critical. |
flow_log |
disabled | Decision-flow telemetry section; see below. |
strategy_trace |
false |
Record per-cycle observable traces for the behavioral-equivalence harness. |
GC tunables¶
Applied at process start through Gc.set. Units for minor_heap_size and
major_heap_increment are OCaml words (8 bytes on 64-bit platforms).
| Key | Default | Example value |
|---|---|---|
minor_heap_size |
33554432 |
524288 |
space_overhead |
120 |
200 |
max_overhead |
1000000 |
1000000 |
window_size |
10 |
10 |
allocation_policy |
2 |
2 |
major_heap_increment |
100 |
1048576 |
The example values are tuned for tail latency rather than throughput: a small minor heap makes minor collections short and frequent instead of one long pause, and the smaller major-heap increment keeps heap-growth slices fine. Combined with the allocation reductions on the trading hot path, this targets a sub-10 us internal-pipeline p99.
Trading layout¶
trading accepts two shapes. The grouped layout is current; the flat array is
the legacy form. Both flatten to the same per-instrument entries.
Grouped layout¶
"trading": {
"defaults": {
"strategy": {
"hyperliquid": {
"name": "jacobs_ladder",
"sell_mult": "0.98",
"sell_levels": false
},
"kraken": {
"name": "jacobs_ladder",
"sell_mult": "0.99",
"accumulation_buffer": [0.1, 0.5]
}
},
"overlay": {
"name": "carry_overlay",
"exchange": "hyperliquid",
"min_entry_margin_ratio": 1.0
},
"oracle": {
"target_survival": 0.95,
"qty_cap_mult": 1.5,
"refresh_seconds": 300
}
},
"assets": [
{
"asset": "BTC",
"oracle": { "target_survival": 0.65, "qty_cap_mult": 3.0 },
"strategy": {
"exchange": "hyperliquid",
"symbol": "BTC/USDC",
"qty": "0.0003",
"grid_interval": [0.15, 0.18],
"cpu_priority": 10
},
"overlay": {
"leverage": 20,
"start_ratio_pct": 25,
"add_threshold_pct": 5,
"max_ratio_pct": 75,
"inventory": [{ "exchange": "kraken", "symbol": "BTC/USD" }]
}
}
]
}
| Key | Required | Description |
|---|---|---|
defaults.strategy |
no | Defaults keyed by venue (kraken, hyperliquid, lighter, ibkr, alpaca), merged into every group whose strategy.exchange matches. |
defaults.overlay |
no | Defaults merged into every group's overlay section. |
defaults.oracle |
no | Global oracle knobs, equivalent to the flat layout's top-level oracle. |
assets |
yes | Array of asset groups. |
Merge rules: objects merge key by key, recursively; lists and scalars replace.
After the merge a group's strategy section must resolve exchange, symbol,
and name, and its overlay section must resolve exchange.
Each asset group:
| Key | Required | Description |
|---|---|---|
asset |
yes | Label for the group (perpetual coin or logical name). Used in errors and logs, not to derive a symbol. |
strategy |
no | Primary strategy for this asset. |
overlay |
no | Overlay strategy fed by this group's strategy and inventory legs. |
oracle |
no | Per-symbol oracle overrides for this group's strategy.symbol. Requires strategy. |
A group must declare strategy, overlay, or both; a group with neither is a
startup error. An overlay with no primary strategy still needs at least one
inventory leg, otherwise it has nothing to hedge.
Strategy section¶
"strategy": {
"name": "jacobs_ladder",
"exchange": "hyperliquid",
"symbol": "BTC/USDC",
"qty": "0.0003",
"grid_interval": [0.15, 0.18]
}
name is the strategy file (strategies/<name>.strategy, then .json) and is
required here; the grouped layout reserves strategy for the section itself.
exchange and symbol are required. Every key in
Trading entry keys is accepted except strategy,
overlay, and oracle. The engine validates the file at startup: a missing
file, a parse error, or a declared name that differs from name stops startup.
Overlay section¶
The grouped overlay is a section on the asset group. Its carry parameters sit
directly under overlay, and it may name balance-only inventory legs:
"overlay": {
"name": "carry_overlay",
"exchange": "hyperliquid",
"leverage": 20,
"start_ratio_pct": 25,
"max_ratio_pct": 75,
"inventory": [
{ "exchange": "kraken", "symbol": "BTC/USD" }
]
}
| Key | Required | Description |
|---|---|---|
name |
no | Overlay strategy file. Default carry_overlay. |
exchange |
yes | Venue the overlay trades (the hedge/perp venue). It may differ from the strategy's exchange. |
symbol |
no | Overlay symbol. Defaults to the owning strategy symbol when the venue matches, or <COIN>/USDC for a Hyperliquid hedge. |
inventory |
no | Balance-only legs that feed the same overlay. Each leg takes { "exchange", "symbol", "testnet" } and runs no primary strategy. |
| carry keys | no | start_ratio_pct, add_threshold_pct, max_ratio_pct, recycle_profit_pct, recycle_base_share_pct, min_entry_margin_ratio, min_liq_mult, and leverage; see Carry overlay params. |
An asset group runs one overlay, which owns one perpetual position keyed by
hedge venue and perp coin. Every strategy and inventory leg in the group feeds
it. Legs of the same hedge must declare the same overlay strategy and params;
a conflicting strategy or params, or a repeated inventory leg (exchange +
symbol), is a startup error.
Legacy flat entries¶
The flat array remains accepted. Each element configures one symbol on one
exchange. strategy is the strategy file name, and an optional nested
overlay object binds a second strategy to the same entry:
{
"symbol": "BTC/USDC",
"exchange": "hyperliquid",
"qty": "0.01",
"grid_interval": [1.0, 5.0],
"strategy": "jacobs_ladder",
"maker_fee": null
}
The flat layout has no inventory legs. Use the grouped layout when one hedge
must combine several balance sources.
Flat overlay entries¶
An overlay is a second strategy file run in the same asset domain as the primary strategy. It is not a separate domain: it sees the same balances, state, book, and account. It is cycled by the asset's normal book/event cycle plus perp top-of-book, position, and funding updates; there is no timer-based overlay loop.
In the flat layout the overlay is nested inside the trading entry that owns it,
and it inherits that entry's exchange and symbol:
{
"symbol": "BTC/USDC",
"exchange": "hyperliquid",
"qty": "0.0001",
"strategy": "jacobs_ladder",
"overlay": {
"strategy": "<overlay-strategy>",
"params": { "start_ratio_pct": 25, "max_ratio_pct": 75 }
}
}
| Key | Description |
|---|---|
strategy |
Overlay strategy file. Resolved and validated like a primary strategy; the declared name must match. |
exchange |
Optional. Must match the owning entry's exchange when present. |
symbol |
Optional. Must match the owning entry's symbol when present; omit it. |
params |
Optional carry-overlay policy object. |
A trading entry has at most one overlay. An overlay with an empty or missing
strategy is ignored, so it does not bind. A missing file, a name mismatch, or
a mismatched exchange/symbol is a startup failure.
Trading entry keys¶
These keys apply to a flat entry, and to a grouped strategy section except for
strategy, overlay, and oracle: those are group-level sections, and name
replaces strategy as the file name.
| Key | Applies to | Description |
|---|---|---|
symbol |
all | Exchange symbol, e.g. BTC/USD (Kraken), BTC (Hyperliquid perpetual), BTC/USDC (Hyperliquid spot), AAPL (IBKR, Alpaca). |
exchange |
all | kraken, hyperliquid, lighter, ibkr, or alpaca. |
qty |
all | Base order size in base currency, encoded as a string (e.g. "0.01"). |
grid_interval |
jacobs_ladder | [gi_min, gi_max]: the hardened bounds (in percent) walked by the oracle's parameter search; the strategy reads only the oracle's resolved interval, never these bounds. |
min_usd_balance |
legacy | Accepted for backward compatibility; unused by the shipped strategy. |
max_exposure |
legacy | Accepted for backward compatibility; unused by the shipped strategy. |
strategy |
flat only | Name of the bound strategy file. In the grouped layout this is the strategy section, and the file name is name. |
maker_fee, taker_fee |
all | Explicit fee fraction (e.g. 0.0016). Both are required for a custom exchange; recognized venues resolve their own fees at startup. |
testnet |
HL, Lighter, IBKR, Alpaca | Route to sandbox/paper endpoints; rejected for Kraken. Accepted on inventory legs too. |
hedge |
Hyperliquid only | Legacy flag, parsed but unused. Perpetual overlays are configured through overlay; positions are left open across a graceful shutdown and reconciled on restart. |
accumulation_buffer |
all | [min, max] retained quote profit buffer required before base accumulation; resolved live from Fear & Greed on crypto venues. |
data_feed |
Alpaca | iex (free) or sip (paid, full-market); accepted on any recognized venue, consumed only by Alpaca. |
sell_mult |
jacobs_ladder | Fraction of each ladder rung's quantity sold per rung fill (1.0 sells the full rung; smaller values accrue base). |
base_accumulation |
all | Persist accumulated base and profit state for this entry (default true). |
sell_levels |
jacobs_ladder | Persist pending sell levels for this entry (default false). |
cpu_priority |
all | Integer priority for core assignment; assets with higher values claim a performance core first. Ties keep configuration order. Default 0. |
Venue-specific restrictions are enforced at startup:
hedgeis Hyperliquid-only.testnetis accepted for Hyperliquid, Lighter, IBKR, and Alpaca; it is rejected for Kraken.testnet,hedge,data_feed, andaccumulation_bufferare rejected for unrecognized (custom) exchanges.
Carry overlay params¶
The carry params apply to both layouts: directly under overlay in the grouped
layout, and under overlay.params in the flat layout. A custom overlay leaves
them out and uses its own tunable: defaults.
| key | default | meaning |
|---|---|---|
start_ratio_pct |
25 | Initial hedge ratio at the ladder anchor, as a whole-number percentage. |
add_threshold_pct |
10 | ROE loss step that triggers the next add, as a whole-number percentage. |
max_ratio_pct |
75 | Quantity cap, as a whole-number percentage of accumulated base. |
recycle_profit_pct |
1 | ROE profit target for the recycle take-profit, as a whole-number percentage. |
recycle_base_share_pct |
50 | Share of the realized recycle PnL that goes back into base. |
min_entry_margin_ratio |
2.0 | Minimum margin ratio for the first entry. Zero or unknown also passes. |
min_liq_mult |
2.5 | Multiple of the mark used for the liquidation-floor reserve. The reserve is sized to the configured max_ratio_pct short, not the live position, so the ladder's funding stays reserved as it adds. |
leverage |
0 | Venue leverage used to seed the take-profit before the first position snapshot. Set it to your actual leverage if you want the first take-profit to be immediate after a cold start. 0 means unknown. |
The add count and add size are derived from start_ratio_pct,
max_ratio_pct, and add_threshold_pct; they are not config keys. The engine
uses leverage first, then the last leverage persisted in carry_state.json,
then the first position snapshot.
The overlay file owns its policy. The engine supplies generic building blocks: live market and perp facts, generic spot/perp order placement and replacement with IOC/GTC/ALO and reduce-only, quote reservation, and base reservation. A quote reservation is subtracted from the primary grid's available quote; a base reservation reduces its sellable base. This is how an overlay can hold senior margin or inventory without a static capital split. A perp short is opened with a perp sell and closed with a reduce-only perp buy; see A short position, start to finish. Your first strategy is a working overlay example.
Set DIO_OVERLAY_DRY_RUN=1 to log intended overlay orders and reservations
without sending orders or mutating reservations. That is the shadow mode for
bringing up a new overlay.
Oracle section¶
The oracle is optional; every key falls back to its default. Unknown keys are rejected. Its placement depends on the trading layout:
| Layout | Global knobs | Per-symbol overrides |
|---|---|---|
| Grouped | trading.defaults.oracle |
oracle on the asset group (requires strategy) |
| Flat (legacy) | top-level oracle |
oracle on a trading entry, or the top-level oracle.assets map |
A top-level oracle is rejected when trading is grouped. Per-symbol
overrides are keyed by the strategy symbol, accept only
{ target_survival, min_active_dsurv, qty_cap_mult }, and may be declared
once: a duplicate symbol is a startup error.
| Key | Default | Description |
|---|---|---|
qty_cap_mult |
1.5 |
Buy-size upper-bound multiplier: the search sizes buy_qty within [qty, qty * qty_cap_mult]. |
target_survival |
0.95 |
Fraction of the historical maximum drawdown the runway covers: runway_pct = max_drawdown_pct * target_survival. Drives sizing only, never activity. |
min_active_dsurv |
0.0 |
Active gate: a strategy is active if and only if its replayed d_surv >= min_active_dsurv (or it has a resting buy to preserve), subject to affordability. |
refresh_seconds |
300.0 |
Background fallback poll for history and balances; decisions remain event-driven. |
assets |
unset | Flat layout only. Per-symbol overrides keyed by symbol, each accepting { target_survival, min_active_dsurv, qty_cap_mult }. |
Decision-flow telemetry¶
Optional. When enabled is true the engine emits structured decision-flow blocks to the
log stream, and the dashboard logs view shows them. There is one block per observed
top-of-book update (TICK) and per decision or order event (SIGNAL, GATE, SEND,
AMEND, CANCEL, FILL, PULSE). An unchanged decision digest collapses to a heartbeat,
so a steady market does not repeat identical lines. Disabled by default. Set
DIO_FLOW_LOG=1 to enable it without editing config.json. Unknown keys are rejected.
| Key | Default | Description |
|---|---|---|
enabled |
false |
Emit decision-flow blocks. |
min_interval_seconds |
0.2 |
Minimum seconds between decision digests for one symbol. |
heartbeat_seconds |
5.0 |
Emit one decision digest per symbol at least this often when nothing changed. |
pulse_seconds |
5.0 |
Cross-symbol pulse line cadence; 0 or less disables it. |
price_ticks |
true |
Emit one TICK block per top-of-book update. High volume; set false to keep decision and order blocks only. |
Environment variables¶
| Variable | Used by | Notes |
|---|---|---|
KRAKEN_API_KEY |
Kraken | |
KRAKEN_API_SECRET |
Kraken | |
HYPERLIQUID_WALLET_ADDRESS |
Hyperliquid | Account address used for balance and fee queries. |
HYPERLIQUID_PRIVATE_KEY |
Hyperliquid | Private key for the EIP-712 order signer. |
DIO_OVERLAY_DRY_RUN |
overlay | 1/true/yes logs overlay orders and reservations without sending or mutating them. Unset means live. |
IBKR_GATEWAY_HOST |
IBKR | Default 127.0.0.1. |
IBKR_GATEWAY_PORT |
IBKR | Default 4002; live mode forces 4001 when unset. |
IBKR_TRADING_MODE |
IBKR | paper or live; default paper. |
IBKR_CLIENT_ID |
IBKR | Default 0. |
IBKR_ACCOUNT_ID |
IBKR | Optional; auto-detected when unset. |
ALPACA_API_KEY |
Alpaca | |
ALPACA_API_SECRET |
Alpaca | |
LIGHTER_API_PRIVATE_KEY |
Lighter | |
LIGHTER_API_KEY_INDEX |
Lighter | |
LIGHTER_ACCOUNT_INDEX |
Lighter | |
LIGHTER_SIGNER_LIB_PATH |
Lighter | Path to the signer shared library (.dylib on macOS, .so on Linux). |
LIGHTER_PROXY_URL |
Lighter | Comma-separated list of relay proxy URLs. |
CMC_API_KEY |
Fear and Greed | CoinMarketCap API key; a missing key falls back to a neutral value. |
DISCORD_WEBHOOK_URL |
Discord notifier | Fill notifications; unset disables Discord. |
DIO_CONFIG |
Engine | Path to the config file; defaults to config.json in the working directory. |
DIO_BACKTRACE |
Engine | When set, pretty-prints OCaml backtraces on crashes. |
DIO_FLOW_LOG |
Engine | 1/true/on/yes enables decision-flow telemetry without editing config.json. |
DIO_DATA_DIR |
Persistence | State-directory override; defaults to /app/data when /app exists, otherwise data. |
DIO_WATCHDOG_OFF |
Engine | When set, disables the main-loop watchdog. |
DIO_CANARY |
Engine | 0/false/off/no disables the stop-the-world canary domain (which busy-spins one core while enabled). |
DIO_CANARY_THRESHOLD_US |
Engine | Canary spike threshold in microseconds (default 10). |
DIO_CANARY_WINDOW_S |
Engine | Canary report window in seconds (default 5). |
COLUMNS |
Engine | Fallback log width when stdout is not a TTY (default width 200). |
DIO_MOTION |
Dashboard | off/0/false/no disables animations. |
DIO_FPS |
Dashboard | Caps the animated frame rate (default 30.0). |
DIO_DAMAGE |
Dashboard | off/0/false/no forces full-frame redraws instead of incremental damage rendering. |
HOME |
Dashboard | Used for the persisted ~/.dio_theme theme path. |
Persistence¶
State lives in two JSON files under the state directory (data/, $DIO_DATA_DIR,
or /app/data in Docker), written atomically:
accumulation_state.json: accumulated base and realized profit, keyed by{strategy}:{symbol}:{venue}.sell_levels_state.json: pending sell levels for entries withsell_levels: true.
See SPEC.md ยง 4.8 for the full field list. In Docker,
/app/data must be mounted to a volume to survive container restarts.