구조
ADR-0014: Managers and playbooks: one script decides which strategies and risk scripts are on
이 페이지는 영어입니다. 하나의 언어로 유지된 사양은 하나의 내용만 말합니다.
Status: accepted (2026-10-21)
Context#
A strategy and a risk script are started and stopped by hand. A person wants to leave a calculation running on the chart -- a trend filter, a volatility switch, a session window -- and have it turn strategies on and off and change the risk script, in a backtest and live alike. The decision must be reproducible (a backtest of "the strategy under this filter" must give the same result twice, and the same decisions the live terminal would have given), it must not be able to trade or reach the account, and it must fail closed.
Decision#
- A new component role,
manager(.tzm). Pure like the others:fn decide(cfg, input) -> manager.Decision. It is not an indicator with signals: an indicator's signals are edges, and what a manager says is a state that lasts, with a memory (previous) so that it can have hysteresis without keeping state. - The decision is the complete set that is on. A strategy it does not name is suspended, not stopped: no new entries, and its stops and targets keep working. This makes a decision idempotent and makes "turn it off" the safe default.
- It reads the market only. The input is the closed candles of the playbook's timeframe, the price, the previous decision and the member names. No account, no profit. This is what lets a backtest -- in which every strategy is its own independent run with its own account -- evaluate the same manager and get the same answer as the live terminal. A manager that wants to react to a member's drawdown would need backtests of several strategies on one shared account; until then the risk firewall's limits do that job.
- Evaluated when a candle of the playbook's timeframe closes, never on a tick. In a backtest the decision rules from the event after the one that closed the candle (no look-ahead), and the script runs on fuel alone (no wall clock); a failure in a backtest fails the run with the reason. Live, a clock bounds a call, and a failure counts toward the playbook's fault policy.
- The playbook is a JSON file, not a header of the manager: it names the members (paths, sha256 pins, configuration), the timeframe, the fault policy, and
allow_real. A member that changed since it was pinned stops the playbook from starting live until it is pinned again.
Alternatives#
- Reuse the indicator and its
emits. Edge signals, no memory, capped at 30 a minute: the state would have to be rebuilt outside the script. - A table of rules (when state X, these members) instead of a script. The user wants any calculation; a table is what the script's answer is shown as, not how it is written.
- Let the manager call the host to suspend and swap. It would stop being pure and replayable, and a faulty manager could act every candle.
Consequences#
- A seventh kind of file (
ScriptKind::Manager) and a contract (manager0.1.0, API 0.1.25). - The host owns the actions -- suspending entries, swapping a risk script -- and journals every change.