ツール
Writing a risk script and a portfolio script
これらのページは英語です。仕様は一つの言語で保つことで、一つのことだけを語ります。
A component is a T0 program that TTT calls instead of running. Two roles matter for risk: a risk policy judges every order that would open exposure, and a portfolio controller says how much each
strategy should hold. Neither can place or cancel anything; the terminal does that, and the terminal's own limits always apply on top. The starting points are the examples
examples/67_risk_minimal.tzr (the smallest policy, to start from), 61_risk_firewall.tzr to 66_risk_discipline.tzr (policies) and 62_portfolio_allocation.tzp, 63_inverse_volatility.tzp (controllers).
A risk policy#
lang ">=0.2.0, <0.3.0";
host "ttt" ">=0.1.16, <0.2.0";
requires "risk" ">=0.1.0, <0.2.0";
mode "strict";
component risk "minimal" version "1.0.0";
struct Config {
max_order: Money,
}
fn refuse(code: String, message: String) -> risk.Decision {
return risk.Decision.Reject(risk.Refusal { reasons: [risk.Reason { code: code, message: message, limit: None, observed: None }] });
}
fn authorize(cfg: Config, intent: risk.Intent, ctx: risk.Context) -> risk.Decision {
if intent.notional.is_none() {
return refuse("unknown_value", "the order cannot be valued, so it is not judged fine");
}
let notional = intent.notional.expect("checked above");
if notional > cfg.max_order {
return refuse("max_order_notional", "the order is worth " + to_string(notional) + " and the limit per order is " + to_string(cfg.max_order));
}
return risk.Decision.Allow;
}Configis what the person writes when they activate it (the Risk tab of Account settings shows its fields). Anything not established isNone, never zero: read it as unknown.- The answer is
Allow,Reject(with reasons),Reduce(a smaller size, judged again from the start) orReevaluate(asked once more against a fresh context; twice is a refusal). A crash, a timeout (2 s at most) or an answer that is not a decision is a refusal. - A policy can only tighten:
Allowis never the end of the judgment. The terminal's own limits (per order, gross, drawdown, the day's loss, the stop beyond liquidation, the group's) still hold. Exits are never judged by a policy. - A policy has no memory. What it may need of the day is in
ctx.historyand inctx.origins[..].history(trades, the run of losses, the last loss, the result). fn size(cfg, request: risk.SizeRequest, ctx) -> risk.Sizingdecides the quantity of an entry a strategy placed without one (Sizing.ByRisk). It answersSizeorDecline(a declined signal is not a failure: the strategy goes on). The request carries the stop, the entry price andliquidation, where an isolated-margin entry would be liquidated;ctx.taker_feeandctx.slippageare what getting in costs.
A portfolio controller#
fn allocate(cfg, snapshot: portfolio.Snapshot, limits: portfolio.Constraints) -> portfolio.Decision answers Targets (how much gross each strategy should hold), Hold or Infeasible("why").
A sleeve is a strategy, named as the account knows it (alpha), or in a group of accounts alpha@BTCUSDT. sleeve.returns are the strategy's returns per minute as the terminal tracked them (it says
Infeasible when there are too few: never guess). The terminal holds the targets to the constraints, sets each strategy's ceiling and trims what is above its target; every order it makes passes the firewall.
Trying it#
t0 component check my-limits.tz --manifest contracts/ttt-host-0.1.jsoncompiles it as the role it declares and lists its fields (inside the terminal the host is the manifest).t0 component test my-limits.tz --manifest contracts/ttt-host-0.1.json --cases my-limits.cases.jsonruns the cases inmy-limits.cases.json(the shape of61_risk_firewall.cases.json:defaults, then each case sets what differs and says what it expects).- In the terminal: Risk tab of Account settings → Choose script…, write its configuration, Activate now. Then Try an order to see what it says to a real order of the account without placing anything.
- Run a backtest: the Backtests window runs under the account's risk (or under a risk script chosen there) (and a Together group under the group's limits), so the result is about the strategy as it will trade.
Before activating someone else's script#
A risk script of a third party is read before it is activated: it sees every order of the account. The window shows its name, version, hash and configuration; open the file and read it. Scripts are pinned by their SHA-256 in the bindings file, and any change to the file is refused until it is activated again.