Two places where risk can live
Every robot trades against a constraint, and the first architectural decision you make about a strategy is where that constraint is evaluated: inside the engine, against your own view of the portfolio — or at the venue, inside a protective order that has already left you. The engine supports both, they can be mixed freely on one account, and they are not two spellings of the same thing.
The choice, stated
Risk computed inside the engine. The account holds the constraint and the engine is the authority. A risk policy is an account-attached limit checked before an order is allowed out: the engine tests it against the portfolio the order would produce and refuses the order if the policy does not hold. A risk rule watches a contract's exposure and emits a closing order when the position's unrealized-P&L ratio crosses a threshold. Both are reproduced bit for bit in a backtest, and both are alive only as long as your process is.
Risk delegated to the venue. A bracket and a one-cancels-other pair do not keep the decision. They emit protective orders with their trigger levels already priced and hand them to the execution point — the Simulator in a backtest, the exchange in production. A resting stop survives a crash and a restart. What you give up is what you gained on the other side: the venue sees one contract and one price, never the portfolio.
| Property | Inside the engine | Left at the venue |
|---|---|---|
| What arms it | attachment to the account | a pattern firing |
| What it watches | the unrealized-P&L ratio | a price level |
| Scope | one contract or the portfolio | one contract |
| Can refuse an order | yes | no |
| Survives a process loss | no | yes |
A threshold on one side is not a threshold on the other
The threshold ratio of a risk rule is a ratio of the position's unrealized P&L: the contract's unrealized P&L divided by its acquisition value. The ratio of a venue risk leg is measured in price space around the entry fill. They measure different quantities, and a value tuned on one side has to be re-derived for the other, never ported.
The second difference is what arms them: a bracket and an OCO emit nothing until their pattern fires, while a risk rule takes no pattern at all.
The pre-trade gate
A risk policy is a named constraint attached to the account and evaluated at submit time. Three kinds exist. A value policy limits the market value of a position or of the portfolio. A quantity policy limits a position's quantity and is per-contract only, because quantities of different instruments are not summable. A time-period policy is a trading window: a repeating cycle with an offset and an active duration, resolved against an IANA time-zone name.
The enforcement rule is asymmetric, and this is the property to build on. While an attached policy does not hold, submissions that would increase exposure are refused: the transaction is rolled back and never reaches the execution. Transactions that do not increase exposure — reductions and closes — are never blocked, so a breached limit can never trap you inside the position it was meant to bound.
account.add_risk_policy("MaxValue", tse.RiskPolicy.Value, 1_000_000.0, tse.Cmp.Le)
account.add_risk_policy_time_period("Session", "WTI", day_ns, open_offset_ns, active_ns,
"America/New_York")
The position watchers
The risk rule builds four position watchers. Each is bound to a contract's exposure, takes no pattern, and compares a positive threshold ratio against the unrealized-P&L ratio.
- Fixed stop-loss fires when the loss reaches the threshold.
- Fixed take-profit fires when the gain reaches the threshold.
- Trailing stop-loss tracks the running extreme of the position's mark and fires when the retreat from it reaches the threshold.
- Trailing take-profit first arms — the extreme must itself have travelled the threshold in the position's favour — then fires on a retreat from it.
account.add_rule_risk("Stop", tse.RuleType.StopLoss, params, 0.02, "WTI")
Risk left at the venue
A bracket is an entry order that carries its own protection; an OCO is that protection placed alone, on a position that already exists. Both are built from one specification: a pair of optional risk legs and a shared time-in-force. At least one leg must be present, and every ratio must be strictly positive.
The protection is materialized from the position, not from the rule. Nothing rests at the venue when the rule is built, or when the pattern merely fires. The engine waits for a fill, reads the position's side, open quantity and acquisition price, prices the legs off that acquisition price and sends them out on the opposite side. On a partial fill the pair is re-materialized. An OCO fired while the contract is flat is refused.
One-cancels-other is the sibling law. When one leg executes, the engine cancels the other in the same step. In a backtest the Simulator stands in for the venue: while the trigger has not fired the order is not executable at all.
risk = tse.make_venue_risk_spec(tse.make_venue_risk_leg(True, 0, 0.02),
tse.make_venue_risk_leg(True, 1, 0.05), tse.Tif.Gtc)
account.add_rule_bracket("Bracket", entry_leg, risk, "PatternToLong")
account.add_rule_oco("Oco", "WTI", risk, "PatternAtNoon")
Version 1.0