Protection and risk reduction
Updated: 2026-10-12 22:05 CEST
Proposals are advisory by default. The standard binary cannot place an order. In a trading build, manual submission requires the exact proposal and its fresh preview; optional pre-authorised buckets let the daemon schedule close/reduce actions under an owner-edited policy. Installing Canary does not enable that automation. The cash sweep and currency leveling are cash management, not protection: they have their own section of the policy and their own authority (Cash management).
A blocked proposal row is normally the system refusing to act on evidence it cannot trust, not a fault to work around. Canary exposes only constrained close/reduce actions here; use TWS for an unmodeled emergency exit.
What each command does
| Command | Reaches the broker | Use it for |
|---|---|---|
canary proposals list |
no | the current proposal set, blockers included |
canary proposals refresh |
no | rebuild the set against fresh positions and quotes |
canary proposals preview KEY REVISION |
no | mint a preview token and read the broker WhatIf verdict |
canary proposals submit KEY REVISION |
yes | place that one protective order |
canary proposals reduce SYMBOL --percent N |
only with --submit |
a discretionary partial close; the preview returns a token with the exact terms, --submit --preview-token places them |
canary proposals veto KEY |
no | hold the current automatic proposal revision before submission |
canary proposals request-stop SYMBOL |
no | stage a trailing-stop proposal for one uncovered stock/ETF holding now |
canary proposals with no subcommand runs list. In a standard build the submit
path fails closed anyway: the daemon's write handler is compiled out behind the
trading build tag and returns ErrTradingDisabled. See
Constrained orders and the trading build.
Proposals close or reduce only
The daemon owns generation. It rebuilds the set from current positions, the
protection policy, and market-event context, and every protection row it
emits closes or reduces, without exception. The cash sweep's bill buys and
currency leveling's conversions are not protection rows: the [cash] section
admits them by their own typed bounds (Cash management). The
submit path checks a protection row twice, once against the proposal's own
position effect and once against the preview's, and blocks on either. A
protection proposal cannot open, increase, or flip exposure.
authority.auto_submit must be false; the policy file fails validation
otherwise.
Six buckets generate protection rows, enabled through the protection policy:
- Trailing stop places a broker-side trail against a stock or ETF. Its
time-in-force is a policy decision, DAY by default, and a DAY
stop expires at the session close without covering the overnight gap. Set
tif = "GTC"under[buckets.trailing_stop]to persist it. The proposal spells out which lifetime you are getting. - Option loss exit appears only for an exact standalone long contract that
has a current exact
directional_intentsdeclaration or qualifies under the approveddefault_long_calls_directionalsetting. At the Rulebook-owned 60% loss of premium paid, measured on a fresh live bid against multiplier-adjusted cost, it proposes a full DAY patient-midpoint-limit close. It may remain unfilled while the loss worsens. It is event-driven: Canary does not install a resting loss stop or claim the loss is capped. - Option profit trail shares the trailing-stop bucket but is a separate
decision contract. It arms after a 50% premium gain and proposes a
full-quantity DAY
TRAIL LIMITwith a native percentage premium trail, normally 30%. Spread, minimum-premium-distance and tick floors can widen the effective percentage within the approved 20-50% range. The initial rounded stop must retain at least 5% over cost after spread and tick floors. Possible hedges, multi-leg strategies, stale/delayed quotes and wide spreads remain blocked. The profit trail needs at least 14 DTE; the loss exit keeps working to expiry, and an in-the-money long option is proposed for a DAY limit close from the Rulebook's expiry act level (7 DTE) asoption_expiry_close, so nothing is exercised by accident (owner decision, 2026-09-28). A hedge-listed index put needs both exact operator intent, a current directional Rulebook role, and exact-ConID Greeks evidence; symbol, option shape, and shared-cache Greeks never prove intent. Missing or invalid exact-ConID evidence keeps the role unclassified.
The option-exit policy uses the explicitly approved absolute 0.05
quote-currency TRAIL LIMIT offset. An inherited or omitted offset still fails
activation. Standing defaults apply only to standard ungrouped long contracts.
default_long_calls_directional classifies long calls and non-hedge-listed
long puts as directional unless they cover a holding: a call over a short stock
of its own underlying, a put over a long stock of its own underlying, or an
index option over such a stock anywhere in the book, which makes the option
protection. default_index_puts_protection excludes hedge-listed puts from
directional exits. Exact declarations, including expired declarations, take
precedence. A contract no standing default covers, such as a short option, a
non-standard multiplier or a strategy leg, stays an owner review; Canary
requests no quote for it and reports no quote blocker. Without a qualifying
standing default or exact intent, an exit stays blocked.
- Theta hygiene proposes closing an option whose remaining value is mostly
time value bleeding toward expiry. When the underlying spot or the option mark
is missing or stale, the row still appears, blocked with
extrinsic_uncomputable, because the daemon then cannot separate intrinsic from time value and cannot assert the close is non-destructive. - Risk reduction trims an issuer whose worst-case loss reaches the
Rulebook's act level (rule 1,
single_name_act_pct, 40% by default) back to its watch level (single_name_watch_pct, 30%; 20/30 for an illiquid issuer), on the same measure: the most the issuer can lose at any price, every leg netted. It reduces the leg that loses most at that price, capped bymax_order_notional, and becomes a full close only when the computed quantity equals the whole position. When that leg alone cannot reach the watch level the proposal says so; ranking rolls, collars and trims across legs by cost is a later step. The levels live inrulebook-policy.toml: the retiredsingle_name_target_pct_nlvkey is ignored with a note if a file still carries it. - Budget reduction brings long option premium back inside a declared share of the risk constitution's declared risk capital while the constitution's drawdown brake is engaged. Its caps are your numbers: the file Canary writes shows the table only as a commented placeholder, and the governor stays off until you write it; the section below says how.
Budget reduction
The bucket measures the market value of every long option leg Canary does not
hold as protection — a hedge-listed index put (SPY, SPX, SPXW, QQQ,
IWM), a put over a long stock of its own underlying, or a call over a short
one is never counted and never selected — against two caps you write as a
percent of capital.declared_risk_capital from risk-policy.toml:
[buckets.budget_reduction]
enabled = true
mode = "shadow" # shadow (default) or active
premium_at_risk_pct_of_risk_capital = 40 # total, (0, 100]
per_line_pct_of_risk_capital = 15 # one line, at most the total
max_order_notional = 10000 # one order, as risk_reduction
Bump policy_version when you add it. Neither cap has a default in code: a
value you did not write is a value the bucket does not have. An enabled table
missing a cap or max_order_notional stays valid, and the governor switches
itself off with the state needs_your_number, naming the keys it waits for;
every other bucket keeps working. The numbers above are the 2026-09-21 mandate
the product manager set for the desk owner to confirm by writing them; they
are not a recommendation from the software.
It generates rows only when all of the following hold, and the snapshot's
budget_reduction status names the first one that does not:
| State | Meaning |
|---|---|
needs_your_number |
the table is enabled but a cap or max_order_notional is not written; the reason names the keys |
constitution_unapproved |
no risk constitution is active, or a material key is unapproved |
enforcement_shadow |
drawdown.block_enforcement is shadow; the bucket acts only under advisory or stronger |
not_latched |
the drawdown block tier is neither latched nor breached |
currency_mismatch |
the constitution's base currency is not the account's |
unmeasurable |
no long option leg has a base market value |
within_budget / over_budget |
measured; the status carries the total, the share of risk capital, and the leg counts |
Selection runs per line first: a line above the per-line cap is cut to the
cap in whole contracts (keep = floor(cap ÷ value per contract)), and a cap
that leaves no whole contract is a full close. Then the total: while the
projected sum still exceeds the total cap, lines are cut in whole contracts,
ranked by what the sale fixes, until the sum is within the cap:
- the number of open Rulebook rules the line relieves: rows at watch or act among rules 1, 2, 4, 5, 13, 16 and 18 on which the line, or its issuer, is an offender, read from the Rulebook result Canary already holds;
- the share of the line's value that is time value, highest first (unknown last);
- the unrealised loss, largest first; then the line's value; then the contract id.
Without a current Rulebook result no line relieves anything, the ranking
starts at time value, and the status says ranking_without_rulebook. Every
row names the cap that selected it, the measured line and total, the excess,
its place in the order and the order used, under budget in JSON and on a
Budget: line in canary proposals list --details. max_order_notional
bounds one order exactly as risk_reduction.max_order_notional does; the next
cycle measures what is left. A stale mark blocks the row with
fresh_option_quote_required; a leg of a multi-leg unit is measured but
routes to the strategy workflow. Rows are close or reduce only, like every
proposal.
The whole fix in one place. The budget_reduction status lists up to
three candidates, the ranked lines with their contract, contracts held,
unit value, the rules they relieve (relief), time_value_pct, unrealised
P&L and a one-line why ("offends 2 open rules; 50% time value; unrealised
−500"), and the plan: every order the measurement needs, each with its
rank, contract, contracts, raises_base and cycle (1 for this
refresh, 2 and later for what max_order_notional holds back). canary proposals list --details prints both under the Budget header, and every
governor row there adds three detail lines: its place in the plan, the other
open rules the sale relieves, and the next two candidates with their why.
Ignoring a row takes its line out of the plan: the next refresh moves to the
next candidate.
Index-put classification is shared with the Rulebook for both budget and mandate reduction plans. A measured directional put can enter the plan even when its symbol is on the hedge list; measured protection stays excluded. Missing classification holds the line and is reported separately from protection. Planning may use the same closed-session observations as the warning. Before preview or prepared submission admits a reduction, a fresh, complete broker measurement must still confirm that the put is directional; the existing scope-bound evidence expires with the order token. The policy limits, order caps, veto window and approval requirements are unchanged.
Measured against the Rulebook instead. basis = "rulebook" replaces the
two caps with limits you already keep in the Rulebook policy, as shares of NLV:
a line is cut to option_line_act_pct (its premium at risk being the higher of
price paid and value), and when the book's premium at risk reaches the premium
budget's act level of the regime set in force (Rulebook rule 3,
premium_budget_act_pct), lines are sold in the same ranked order,
each contract counted at its premium at risk, until the total is back at the
budget's watch level (premium_budget_watch_pct). This basis needs the
account's NLV, not a risk constitution or available funds, and it waits for no
drawdown brake: the rows appear whenever those limits are breached. Leave out
the two percentages; the file fails validation with both a basis of rulebook
and declared-capital caps.
[buckets.budget_reduction]
enabled = true
mode = "shadow"
basis = "rulebook"
max_order_notional = 10000
The state is account_unavailable when NLV is missing; the status then
carries per_line_pct_of_nlv, premium_budget_watch_pct,
premium_budget_act_pct and premium_budget_set, the account values,
premium_pct_of_nlv and, once the act level is reached,
premium_excess_base (the premium at risk above the watch level), and each
row names the limit that selected it.
This basis sells against the Rulebook's numbers, which may still be Canary's
defaults. Your mode applies whatever state the Rulebook policy file is in:
you approve every order, and that approval is the gate. The status names the
state in rulebook_review, and while it is anything but reviewed every row
carries one detail line saying so:
rulebook_review |
Detail line on every row |
|---|---|
reviewed |
none: the file in force is yours, read cleanly |
unreviewed |
"Rulebook limits: Canary's defaults, not yet reviewed" (the file still opens with # Canary defaults, not yet reviewed.) |
no_file |
"Rulebook limits: no Rulebook policy file; compiled defaults" (canary policy ensure writes one) |
drift |
"Rulebook limits: the file on disk is not the one in force" (edited without a higher policy_version, or removed) |
error |
"Rulebook limits: the file could not be read; the last good file applies" (before any good file, Canary's compiled defaults apply) |
canary proposals list names the state on the Budget header. The
declared-risk-capital basis measures your own caps and reads no review state.
Shadow first. In mode = "shadow" the rows are generated, journaled in
the snapshot with shadow: true, marked shadow in canary proposals list and
listed by --details under a Shadow (budget reduction) heading of their
own, so you can read what the rule would have done beside what you did.
preview and submit refuse them with shadow_mode, and the
pre-authorisation scheduler skips them: a row is eligible for automatic
placement only when AutomaticEligible() holds, which a shadow row never
does. counts.budget_reduction and counts.budget_reduction_shadow report
the rows; shadow rows are never actionable.
Activating. Set mode = "active" and bump policy_version. The rows
become ordinary proposals under every existing gate (preview, WhatIf, the
double check of position effect, duplicate orders, account, mode, freeze,
origin and the explicit submit). One thing does not change with the mode:
every row carries never_skip_veto, so a scheduler that shortens or skips the
veto window under a latched brake must still wait the full window for these.
A reduction to budget is a discretionary-scale action, not a stop.
canary proposals reduce is separate: a discretionary partial close you size
yourself. It previews unless you pass --submit. Under --portfolio the
mode says what is cut. --mode net, the default, removes that share of the
book's net delta-adjusted risk, only on lines in the net direction. --mode longs sells the same share of every long-delta line and keeps every short
and long put. --mode all cuts that share of every line, buying back shorts.
--sector (repeatable, GICS names) and --min-beta narrow a longs or all
sweep. --plan sizes the legs without broker previews and reports what the
book would lose if the index fell 10% now and after, from the mandate's
index stress; a plan cannot be submitted.
A reduction reserves the quantities already committed by Canary's working
orders, including individual legs of combo orders. A known fill does not
make those units available for another sale while positions are catching up:
Canary checks a completed broker position snapshot against the position used
for the reduction and refuses while they disagree or cannot be confirmed.
An order-state change before transmission also requires another review.
These checks, and the reviewed worst price, apply when a reduction token is
submitted through order place as well as through proposals reduce.
No portfolio sweep, in any mode, selects a locked holding. canary locks lock SYMBOL --con-id ID --role hedge|core puts a line on the keep list:
a hedge is a hedge because you say so, not because of its delta sign that
day. Net mode reads the direction of the unlocked book. The mandate's
automatic reduction leaves locked lines out too. If the keep list cannot be
read, automatic reductions wait; an unreadable list never means no locks. A
lock save waits for any in-flight broker transaction before acknowledging the
new list. It does not cancel orders already sent. A reviewed basket refuses,
and sends nothing for, a leg locked after the review. A single-holding
proposals reduce SYMBOL still works on a locked line. --include-hedges is
a hard error under --portfolio instead of a silent no-op.
A net portfolio preview also discloses signed net dollar delta before and the
estimated delta after eligible orders fill, using current prices and Greeks;
blocked orders do not count toward that estimate. Its untouched_positions
list names every nonzero holding left unchanged, with exact contract identity,
held quantity and the reason: locked by you, opposite-direction delta,
unsupported position, whole-unit rounding, or a blocked preview. An empty list
means no untouched holdings; null means the review is unavailable, as for a
scoped sweep, which lists what it keeps under kept. These fields only explain
the existing sizing and never change which orders are selected.
A reduce preview returns, for each order it would place, a signed preview
token and the exact terms that token binds (terms): the contract, side,
quantity, order type, limit, time in force and algorithm the broker will
receive, the token's expiry, and the worst price the submit will hold the
market to. canary proposals reduce --submit --preview-token TOKEN places
exactly those terms, nothing re-sized and nothing re-priced; under
--portfolio pass one --preview-token per leg, and each leg is placed on
its own, so a refused leg never holds another. The daemon refuses, and sends
nothing, when the token is stale or expired, when the position no longer
holds the reviewed quantity on the reviewed side (a sale of at most the
confirmed long is admitted; a smaller position refuses as position_changed
rather than shrinking the order), when the market is already beyond the
reviewed worst price (beyond_worst_price: the bid below a sale's limit, or
the ask above a purchase's; a market that moved the other way is no reason
to refuse; stated in every preview as price_bound), when the token was already submitted (token_consumed), or
when any order gate refuses, Canary's own stop on the line included (a reduce
sale joins the stop's one-cancels-all group or waits for the stop to shrink,
like any other sale). --request-ref REF names a submission: a retry with
the same reference replays the recorded result and never sends twice.
Two reductions of one line never together sell more than is held (owner
instruction 2026-10-22 17:45 CEST): a reduction sizes its percentage from
what Canary's own working orders on the line leave unsold (stops aside,
which their one-cancels-all group and the stop guard keep within the line)
and states that quantity as committed_quantity; the send refuses
(reduce_already_working) when another of Canary's sales went out in
between. Hand orders in TWS do not count. A send that may have reached the
broker is submit_unclear, never submit_failed. Under --portfolio, a
stock the broker reports as having no market and values at zero counts as
no exposure and is listed as left out (defunct_holding) instead of
blocking the book.
A reduction is a limit priced as a worst price, DAY (owner decision
2026-10-22 15:26 CEST, which replaced the limit at the mid and its ±0.5%/2%
band): a sale's limit sits [order_limits].reduce_allowance_pct below the
bid (reduce_option_allowance_pct, and at least one tick, for an option), a
purchase's that far above the ask, rounded toward the touch. The order can
fill at once in a fast market, and the broker fills a marketable limit at
the best price available. On a US stock or ETF routed SMART it carries IBKR's
Adaptive algorithm at Normal priority unless --adaptive-priority asks
for Patient, Urgent or none (a plain limit). On an option, a non-USD
line or a directed route the order is a plain limit and the terms say
"Adaptive not supported for this contract"; the broker layer sends Adaptive
only for that one stock shape.
--submit without a token is the hand path: it previews the holding at the
live book and places that in one call, with the terms it sent in the result.
Anything that reviews terms with a person first, Desk and the paired app
included, submits the token.
canary proposals request-stop answers the coverage ledger directly: name an
uncovered stock/ETF holding (symbol, or --con-id when ambiguous) and the
daemon rebuilds the proposal set and returns that position's trailing-stop
proposal with the key and revision to preview. It generates only — placing the
stop still goes through preview and submit with every gate intact. An
earlier ignore for that stop is cleared by the explicit request, and the
result says so. The paired app offers the same action from the Protection
panel's uncovered-positions list.
Option exits do not enter the protection coverage ledger. That ledger remains stock/ETF stop coverage; calling a directional option exit portfolio protection would overstate what the broker is actually covering. Option proposals still use the same preview, WhatIf, full position-effect, duplicate-order, account, mode, freeze, origin and explicit submit gates as every other broker-adjacent proposal.
Option hedges are listed, not proposed
A held long option the engine holds as protection produces no exit row, but it
is not silent either. The snapshot lists it under option_hedges with what it
covers (book for a hedge-listed index option, otherwise the underlying whose
stock it covers), the Rulebook's role for a hedge-listed put (protection or
unclassified) and how that role was established: measured by Canary's check
of the whole book, structural because it covers a holding of its own
underlying, unmeasured when that check has not completed (the detail says
why), or closed_market when it waits for the options session. The check is
Canary's own work and asks nothing of the owner; the standing rule applies
until it completes, and the detail sentence says so in plain words. A
hedge record carries its days to expiry, cost basis per contract unit, mark and
market value, and no threshold, premium return or order terms. A hedge the
classifier measures as directional leaves the list and appears as an exit
review among the proposals. counts.option_hedges reports the list size
separately from the proposal counts.
One row per contract
Two buckets can ask to sell the same contract. A loss exit and theta hygiene
both close a near-expiry call deep in loss, and the budget governor and the
issuer trim can both cut one line. Canary shows one row for each exact
contract and side, and that row carries every reason under covers. Its size
is the largest single requirement, never the sum: each sale asks to sell at
least its own quantity now, so the largest meets them all. When sizes tie,
the Rulebook's exits come first (loss exit, expiry close, profit take), then
the issuer trim, the budget governor and theta hygiene; the orders are then
identical. The other rows stay listed, blocked with covered_by_proposal,
and name the covering row in covered_by.
Two rules settle the cases where the largest would be wrong (owner decisions, 2026-09-28):
- Owner, 2026-10-19 16:50 WEST: an unapproved stock sale must not suppress stock protection. Both proposals remain available until an actual order or confirmed intent invokes the existing duplicate-order checks.
- Otherwise, an immediate sale goes before a trailing stop, whatever the sizes, unless the stop is pre-authorised (below). The stop is conditional and would hide the sale. It is covered until the sale fills or is cancelled, then re-proposes for what is left.
- A pre-authorised row that Canary will still place is never covered by a row
that needs your approval. Unless it already meets the larger requirement,
Canary places it after its veto window, and the row you approve lists it
under
coverswithautomatic: true. Once its automatic submission has ended (vetoed, failed, superseded, or placed and no longer working), the row is left out of the merge, so the rows you can approve stay available.
Once an order works at the broker for the contract and side, the exits, trims, budget and theta rows for it block until that order fills or is cancelled, and they then recompute from the new position:
- Theta hygiene, issuer trims and budget reductions block with
existing_reduction_orderon any same-side order that the broker's complete open-order inventory shows working (not cancelled, inactive or rejected) for the exact contract in the account. It does not matter which client placed the order or what type it is. A trailing stop counts too: selling beside a stop would leave the stop larger than the position. - They block with
reduction_order_evidence_unavailablewhen that inventory is missing or stale, and withreduction_order_identity_unknownwhen a working order may match but carries no contract id. - Option exits keep
existing_option_exit_order. - A stock trailing stop also waits, with
existing_reduction_order, while a sale Canary proposed works for its position.
Preview and submit repeat the check against a fresh broker read. The brief counts covered rows apart from blocked ones.
A blocked row is the system working
Every blocker carries a code, a message, and an action line. Under stress the action is the part to read, because it names the next command.
An active regulatory or news halt blocks the row. So does an active LULD pause. That is deliberate: a protective order priced against a symbol that is not trading is a guess about the reopening print. Recent flags that are no longer active stay visible as context and do not block.
Other rows block because the evidence is not good enough. A stale option-exit
quote raises fresh_option_quote_required. A revision older than that row's
current one raises stale_revision; each row carries its own revision, so a
neighbour that moved (a cash sweep re-sized on net liquidation value, say)
leaves a reviewed stop current. A time-in-force that drifted between the
proposal and its preview raises tif_drift, and a quantity beyond the position
raises quantity_outside_position. The row stays visible with its reason
attached rather than quietly disappearing.
A directional option whose exact quote, cost, role, or session evidence is unavailable appears as a blocked Option exit review row. It is not silently dropped and it cannot be previewed as an order.
- Unit exit (
strategy_exit) covers every multi-leg unit: each current strategy, whatever its source, and every underlying whose legs have no single decomposition. Canary never asks which leg is which. It values the unit as one position: net premium paid per unit against the net close value at fresh leg quotes (long legs at bid, short legs at ask), applies the same Rulebook loss line to that net figure, and manages a profit trail itself from the unit's high-water close value, since a broker trail cannot follow a combo. Two long calls or two long puts of one underlying are not a unit; each is a standalone position under the standing defaults. A unit row names its route:canary strategies close ID REVISIONfor a recorded strategy, or a combo order at the broker when Canary has no strategy record.canary proposals previewandsubmitrefuse a unit withstrategy_workflow_required; nothing about a unit row places an order.
When a stop no longer matches its position
If a position shrinks or goes flat while a close-only protective order is still
working, the daemon marks that order position_mismatch and grades it critical.
Triggering it would open an opposite-direction position rather than close
anything.
| Kind | What it means | Fix |
|---|---|---|
short_entry_full |
no coverage left | cancel the order |
short_entry_excess |
partial coverage | reduce to reduce_to_quantity |
reduce_to_quantity is the position magnitude available in the order's closing
direction: the long share count for a SELL, the short magnitude for a BUY. It is
the exact quantity a reduce-modify has to target, and it appears with
short_risk_quantity in canary orders open --json. The same holdings show up in
canary positions as reconcile_required under protection coverage, where a
stale protective order is deliberately not counted as protection.
For a sell stop or trail Canary placed on a stock, ETF or option, the protective stop guard makes that fix itself (next section). An order placed by hand in TWS is yours: Canary reports it and leaves it alone.
Protective stop exemption and guard
A broker-side stop that protects a whole long stock position is usually larger
than the order cap in force (the risk constitution's [order_limits], scaled
with the book; Order limits), and every
apparent stock sell exit is otherwise read as opening a short, which needs
[order_limits].allow_stock_short. A protective stock
exit is exempt from both:
- a stock or ETF
SELLstop (TRAIL,TRAIL LIMIT,STP,STP LMT); - the position is long and the stop sells no more than it holds, so it closes or reduces and never opens or flips;
- the broker's complete, current open-order list, hand orders in TWS included, shows no other working sell for the same contract that together with this stop would sell more than is held.
If the open-order list cannot be read, the exemption does not apply and
today's refusal stands. A modify that only lowers the quantity of a working
stop is exempt on the same terms without the third condition, because it can
only shrink what is already working. Every other order keeps the short
re-read. The notional cap and [order_limits].max_option_contracts also pass
any close or reduction that lowers its underlying's absolute delta, a stock
sale, an option close or a strategy close alike (owner decision 2026-10-07
08:13 CEST; Order limits); an order
whose delta cannot be measured keeps both caps.
A trailing-stop row that the gates would refuse no longer reads ready. It
carries protective_exit_competing_sell when another working sell already
covers the shares, protective_exit_inventory_unavailable (waiting for the
broker) when the open orders cannot be read, or
protective_exit_exceeds_position when the stop is larger than the position.
A resting GTC stop outlives a later hand sale, so the exemption comes with a
guard. On the proposal cadence (30 seconds by default) the daemon compares each
working sell stop or trail it placed on a stock, ETF or option with the current
position, less any sale of Canary's own that is working beside it without being
linked to it (next paragraph): at zero or short it cancels the stop; below the
stop's remaining quantity it shrinks the stop to the whole-unit position,
keeping the trail amount or percent, the current stop price, the limit offset,
trigger method, time in force and outside-hours flag. It acts only on a
current, complete portfolio download for the connected account and a complete
open-order list, and only when the same correction is planned on two passes at
least 20 seconds apart, so a partial download or an out-of-order fill never
moves an order. Each change goes through the ordinary modify or cancel path
with the origin daemon-protective-guard, is journaled on the order, and is
written to the decision log (protective_stop_guard.shrink or .cancel). The
order's position_mismatch notice stays open until the change lands.
Two orders Canary placed never sell the same shares twice. Every stop or trail
Canary places is born in its own broker one-cancels-all group, named after its
order reference (oca_group in canary orders open --json). When you sell
the line through Canary while that stop works and the two together would sell
more than you hold, the sale joins the stop's group: the broker reduces the
other order by whatever fills first, a partial fill included, and routes one
of them at a time, so the stop cannot sell what the sale already sold, nor the
other way round, without waiting for the guard (decision log
protective_oca.link). A stop from before this release has no group, and a
working order cannot be moved into one, so Canary first shrinks that stop to
what the sale leaves, or cancels it when nothing is left, waits for the
broker's answer, and only then sends the sale (protective_oca.shrink,
.cancel). If that change is refused, unconfirmed within a few seconds, or
its send is uncertain, the sale is refused with the reason and the stop stays
as it is (protective_oca.refuse); a sale is never sent unlinked beside
Canary's own stop. A stop you placed by hand in TWS is never linked or
changed: the sale goes out as it is, and the guard reports the hand stop.
Every write gate still decides. With trading disabled, the broker link down or
daemon storage unhealthy the guard does nothing and logs the blocker once.
trading.freeze blocks every modify, so a shrink waits while frozen; a cancel
still goes through, because a freeze never strands an order that needs
cancelling.
The residual gap: while the daemon is down, a hand sale is not followed, so a stop can stay larger than the position until Canary is back and the guard has run; and a hand sale beside Canary's stop, or beside a Canary sale, can still sell the line twice until the guard's next passes.
Pre-authorised buckets
The protection policy's [authority].pre_authorised list is empty by default.
Only an explicit owner policy edit and version bump enable the named buckets:
trailing_stop, option_loss_exit, option_profit_trail,
budget_reduction or mandate_reduction (the reduction back to the
risk policy's [mandate] budget: a reduce-only batch, largest risk
contributor first, each row held to the order caps; see canary mandate).
Budget and mandate reductions always wait the full window, even with the
drawdown brake latched. The cash sweep is pre-authorised in its own section,
[cash] pre_authorised, under the same veto window
(Cash management); currency leveling never is.
auto_submit remains false; it is not the switch for this scoped scheduler.
Build capability, account/mode pins, freeze, fresh evidence, preview, journal,
and broker eligibility gates still apply to every submission.
veto_window defaults to "30m" and cannot be less than five minutes. The
window starts after the daemon records the Protection alert in its registry.
The paired app or another consuming application owns notification delivery;
registry acceptance does not prove that a phone received or displayed it, and
neither does push-service acceptance. A push is witnessed only when a
paired device reports it back: the app's service worker sends a displayed
receipt when it shows the notification and an opened receipt when you tap
it, and the app host relays the latest receipts to the daemon. canary status
shows the last push sent, the last receipt with its device, and how long alert
pushes have been silent; canary app push-test proves the channel with a
diagnostic push that never counts as an alert
(the paired app). Do not treat the
window as a guaranteed opportunity to receive a push, and do not list a bucket
until canary status shows a push witnessed on the phone.
After the open. An order that prices off the regular session (a patient
limit, or a trail without an initial stop) does not go out in the session's
first minutes: the pre-authorisation scheduler and a queued authorisation send
no earlier than the open plus 5 minutes for stocks and 15 for options. At a
stress open, while the latched regime stage reads confirmed stress (a stale
confirmed stage still counts; a stage never observed reads calm), options rows
that scale the book at your discretion (budget, theta and issuer-trim
reductions) wait 30 minutes; loss exits, expiry closes and trailing stops keep
15, because a stop is a stop. The cash sweep's bills are not options: they
keep the 5-minute offset after their own session opens (see
Cash sweep). A row's readiness dates
that time in default_send_at; at a stress open it adds stress_open: true to
a row that waits 30 minutes and stress_open_exempt: true to one that keeps
15, and its message says which applies, with the time.
A latched drawdown brake lets eligible non-budget protection records bypass
both the notice prerequisite and the waiting window. Budget reductions and
cash sweep rows always retain the notice prerequisite and full veto window;
shadow rows never schedule. A sweep row also waits for its bill's session: a
record created outside it is due at the session's open plus five minutes.
canary proposals status reports authorised buckets and pending counts. A human
can veto from canary proposals veto KEY or the app before submission. Agent
origins cannot veto. A veto applies to that proposal revision; changed evidence
can create a new revision and window.
A submission that trading.freeze refuses is deferred, not failed. The record
reads deferred with a resubmit time and is not tried again while the freeze
stays set. Once you lift the freeze, the daemon resubmits it once, provided the
proposal revision is still current; a changed revision supersedes it like a
pending record, and a veto still applies while it waits. Any other refusal
fails the record for that revision, as before.
A due submission also does not fire while a hand order that is new to its proposal revision is still working at the broker. A hand order is one entered by hand in TWS or by another API client, or placed from a terminal or the paired app; orders the daemon placed itself and agent-origin gated orders never count. It is new when it was placed, or last modified, after the automatic record for the revision was created. When the record is created, the daemon notes the hand orders already working, so a standing order that stays unmodified, such as a stop you placed last week, never holds it, even while its trailing trigger moves. A new order holds it, and so does a standing order you modify, in TWS or through Canary. Any instrument counts, not only the proposal's. The record stays pending, says why it is held, and fires as soon as that order is filled or cancelled, on its original window: waiting never restarts the veto window. The hold applies under a latched brake as well. If the broker's open-order list cannot be read when the record is created, every working hand order counts as new for that record; if it cannot be read when the submission is due, the submission waits. With no bucket pre-authorised, nothing is ever held.
While a record waits, deferred or held, its Protection notice stays open and says what it waits for: deferred because trading is frozen, held behind a hand order, or held because the broker's open orders cannot be read. It never reads as recovered while it waits; it recovers only when the record is submitted, vetoed, or superseded.
The daemon persists submission intent before the broker call and reconciles it against its journal after restart. Installing or updating the binary enables no bucket and clears no freeze; the daemon's next start migrates the policy file after a backup.
Cash management
The cash sweep and currency leveling buy bills and convert currencies. They
are not protection, so they live in their own [cash] section of the
protection policy file with their own authority; see
Cash management.