Tracking: A-3254 (parent). Includes A-3257 (automatic recenter), A-3663 (band alerts to incident.io) and A-4540 (dated futures).
Replaces:
docs/rfc/auto-band-recenter.md. Uses:
POST /admin/trading-reference-price,
POST /admin/anchorage/deposits/{id}/credit, the Databento
and Pyth sources in underlying-publisher, and the
recon-engine check framework. Does not apply to: the
Bitnomial edition, where the venue sets the bands.
Objective. When a price band is close to rejecting
fairly priced orders, AX opens a task in a support queue and sends an
alert to #ax-support. An instrument runs in one of three
modes. The modes ship in this order:
alert: the task shows the detail. An operator moves the
band by hand.propose: the task also carries a proposed new anchor
price. An operator accepts or rejects it.auto: a rule accepts the proposal when the move passes
every safety rule. When a rule fails, the task waits for an
operator.The support queue is a new page in the admin GUI, below Dashboard. Two conditions that exist today, uncredited deposits and Bitnomial account sync errors, use it first (Validation producers). Band tasks follow.
underlying_prices.When the underlying price moves and the anchor does not, the market price reaches a band limit and EP3 rejects orders. No alert is sent. An operator learns of it from a customer, or from a red card on the admin dashboard. The operator then finds a price on a public website and types it into the settlement dialog.
Evidence, read on 2026-09-21:
PRICE_OUT_OF_BOUNDS rejects in
order_log over the two days before:
| Symbol | Rejects | Accounts | Hours affected |
|---|---|---|---|
ARM-PERP |
2,689,530 | 1 | 13 |
INTC-PERP |
280,878 | 1 | 4 |
NVDA-B300-2026-DEC |
9,018 | 2 | 3 |
NVDA-B300-2026-SEP |
8,078 | 3 | 4 |
WTIOIL-PERP |
450 | 1 | 8 |
AMD-PERP |
216 | 1 | 3 |
ARM-PERP rejected orders at the upper limit for 13 hours
in a row, from 00:00 UTC on 2026-09-21, at about 230,000 rejects an
hour. incident.io has no band alert since 2026-03-01.
Customer reports: Algocentric asked AX to move the B300 band (2026-09-16). Eigen reported B300 SEP and DEC at the lower limit (2026-09-20). Kappa reported rejects on INTC, MU and SPCX, said the rejects count against their rate limits, and asked if bands can update more often (2026-07-30). Wasabi reported MU and SNDK. Eigen also reported XCU, BRL and SNDK.
#ax-support shows a manual band update about once a
week since July (SNDK, AMD, MU, INTC, SPCX, WTIOIL). Several were made
between 01:00 and 06:00 CT.
instruments/ax/products.cue). Postgres stores
nothing about bands. api-gateway copies the percentages to Redis.
marketdata-publisher computes the displayed limits from the same anchor
that EP3 uses
(rs/marketdata-publisher/src/tasks/monitor_market_data.rs,
price_band_reference_price).POST /admin/trading-reference-price recenters a band
(rs/api-gateway/src/admin_routes.rs,
set_trading_reference_price). No GUI calls it. Operators
recenter with the settlement dialog, which writes a preliminary
settlement price. That price also feeds mark-to-market, funding
estimates and reports.useNearPriceBandMarkets
(gui/packages/admin/src/hooks/) marks a market when the
best bid or offer is within 2% of a limit, and the dashboard card turns
red. The rule reads resting orders. It does not read the underlying
price, and it shows nothing when one side of the book is empty.origin/alee/auto-band-recenter-impl
(rs/recon-engine/src/checks/underlying_price_band_drift.rs,
400 lines). It is not on main. It writes to EP3 from recon-engine. It
reads its settings from two instrument columns,
auto_set_reference_price_max_up_pct and
_max_down_pct.#ax-support. The route
ax_prod_recon_engine in
terraform/incident-io/prod.tf matches on the alert title.
recon-engine sends one alert per check
(rs/recon-engine/src/incident_io.rs), so a check that
covers many symbols sends one alert for all of them. recon-engine sends
no alerts when its two incident.io environment variables are unset. The
repository does not set them (see Ops items).underlying_prices over the last 14 days has
DATABENTO rows for 22 equity symbols, from 04:00 ET each
day, and ORNN rows for one compute index. It has no Pyth
rows. Equity perps have no underlying series overnight. WTIOIL, XCU, BRL
and the FX and metal perps have none.gui/apps/admin/src/pages/btnl-sync/WorkQueue.tsx) is the
closest in layout, and it has the only badge in the sidebar.producer ──opens──> support task ──notifier──> incident.io ──> #ax-support
│
├── proposal (optional) ──accept──> executor ──> EP3
└── events (append-only)
| Record | Meaning | Table |
|---|---|---|
| Support task | One condition on one subject that needs an operator. | support_tasks (new) |
| Proposal | The action that the producer recommends, with the inputs it used,
its revision, its expiry and the reasons it waits for a person. It is
part of the task row. A task in alert mode has none. |
support_tasks.proposal |
| Event | One thing that happened to a task, and who did it. | support_task_events (new) |
A producer is a service that detects a condition. It
runs as one instance. On every run it reports each condition that holds.
It opens a task when a condition starts. It updates
last_seen_at while the condition holds. It resolves the
task as self_resolved when the condition ends. When a
producer stops, its tasks stay open and last_seen_at shows
their age. A producer writes tasks through ax_db.
recon-engine already writes to Postgres this way (the leaderboard
upsert), so it needs no new role. The band producer is a recon-engine
check, because recon-engine runs as one instance and already has
scheduling, timeouts, and Postgres and ClickHouse pools.
The notifier is a loop in recon-engine. It sends one
incident.io alert for each open urgent or
action task. It resolves the alert when the task resolves
or is deferred. It uses the fire, re-send and resolve logic that
recon-engine checks use today (reconcile_alert in
rs/recon-engine/src/lib.rs). The deduplication key is
support-task/{env}/{subject}, so each symbol has its own
alert. The alert title is support-queue: {subject}, and the
description is detail rendered as text, as
format_details does for a check today. A new producer needs
no alert code.
The executor is the code in api-gateway that carries out a proposal. It is the only code that writes a band change to EP3 for a task. api-gateway already has the EP3 admin client and the trading-reference-price write. recon-engine needs only read access to EP3.
{kind}:{key}, for example
price_band_drift:AAPL-PERP. The kind is the text before the
first colon, so a kind name cannot contain one. The key is stable for
the life of the condition. A partial unique index enforces this, as it
does for btnl_ib_account_sync_errors. A condition that
continues updates its task.proposal.rev has changed, when the
proposal is more than 5 minutes old, or when the EP3 anchor differs from
the anchor in the proposal.require_human_admin_actor
(rs/api-gateway/src/admin_routes.rs), which refuses service
tokens and API keys. The rule uses a second route,
rule-accept, which takes only the service token.rule-accept the executor reads the mode and the caps and
applies every rule in Auto mode. A caller with the service
token cannot move a band further than the instrument's settings
allow.InvariantCheck, so it appears on the Invariants page and in
invariants_log. Its paging flag is off (the
flag is from the continuous-recon-checks
RFC). Its tasks send the alerts.gui/packages/admin/src/editionParity.test.ts fails when a
web section has no mobile screen.info task, which appears in the queue and sends no
alert.alert mode. An operator
turns on propose or auto for each
instrument.The tables go in db/postgres/1.sql. Atlas applies
them.
CREATE TABLE support_tasks (
id UUID PRIMARY KEY,
subject TEXT NOT NULL, -- '{kind}:{key}', see decision 2
severity TEXT NOT NULL
CHECK (severity IN ('info', 'action', 'urgent')),
detail JSONB NOT NULL, -- rewritten on every producer run
proposal JSONB, -- NULL when there is none
producer TEXT NOT NULL,
opened_at TIMESTAMPTZ NOT NULL,
last_seen_at TIMESTAMPTZ NOT NULL,
deferred_until TIMESTAMPTZ,
resolved_at TIMESTAMPTZ,
resolution TEXT CHECK (resolution IN
('accepted', 'auto_applied', 'self_resolved', 'dismissed')),
resolved_by TEXT,
CHECK ((resolved_at IS NULL) = (resolution IS NULL))
);
CREATE UNIQUE INDEX support_tasks_one_open
ON support_tasks (subject) WHERE resolved_at IS NULL;
CREATE TABLE support_task_events (
id BIGSERIAL PRIMARY KEY,
task_id UUID NOT NULL REFERENCES support_tasks (id),
at TIMESTAMPTZ NOT NULL DEFAULT now(),
actor_kind TEXT NOT NULL CHECK (actor_kind IN ('admin', 'rule', 'producer')),
actor TEXT NOT NULL,
event TEXT NOT NULL CHECK (event IN (
'opened', -- producer
'severity_changed', -- producer; the alert re-sends at the new priority
'proposed', -- producer; payload holds the proposal
'deferred', -- admin; note required
'rejected', -- admin; note required, sets a deferral
'accepted', -- admin; payload holds rev and both prices
'executed', -- admin or rule
'execution_failed', -- admin or rule; payload holds the error
'rule_declined', -- rule; payload holds the failed rule names
'self_resolved', -- producer
'dismissed')), -- admin
note TEXT,
payload JSONB
);A trigger makes support_task_events append-only.
application_decisions has the same kind of trigger.
actor_kind has the same meaning as
application_decisions.decided_by_kind. Stage A emits only
opened, severity_changed,
deferred, self_resolved and
dismissed; the CHECK lists every event now to save a later
schema change. A detail update is not an event, or the table would gain
one row per subject per run.
The three JSON fields answer three questions from the operator.
detail is what the producer saw. proposal is
what it suggests, and it carries rev,
expires_at and needs_human_reasons.
needs_human_reasons is why the proposal waits for a person
instead of the rule; it is empty when the proposal is empty and resets
when the proposal is replaced. The queue has no claim: the team is
small, and concurrent accepts are handled by the row lock and
rev.
Severity sets the alert. An urgent or
action task sends an alert and counts in the badge. An
info task does neither.
Each instrument has its settings in one new column:
instruments.band_recenter_config JSONB NULL
BandRecenterConfig {
mode: Off | Alert | Propose | Auto, // column NULL means Alert
max_up_pct: Decimal, // default 10, ceiling 15
max_down_pct: Decimal, // default 10, ceiling 15
max_cumulative_pct: Decimal, // default 20
alert_fraction: Decimal, // default 0.5
sources: [Source], // default [Databento, Pyth, Ornn]
width: Option<{ base_pct, carry_pct_per_year }>, // dated futures only
}
The band producer runs every 60 seconds for each EP3 instrument:
series = the first source in `sources` with a fresh newest row
(fresh = newer than 3 × the source's write interval)
check = the next fresh source in `sources`, if there is one
twap = mean of `series` over the last 5 minutes
anchor = trading reference price, else settlement price [EP3 book stats]
drift = (twap − anchor) / anchor
consumed = |drift| / band percentage on that side
underlying trigger : consumed ≥ alert_fraction (default 0.5)
reject trigger : ≥ 50 PRICE_OUT_OF_BOUNDS rejects on the symbol in the
last 5 minutes, from ≥ 2 accounts [order_log]
urgent when the reject
trigger holds or consumed ≥ 0.9. Otherwise
action. info when the rejects come from one
account and the underlying trigger does not hold (decision 14).check exists, the
detail has both means and the difference between them. A difference over
1% adds sources_disagree to the proposal's
needs_human_reasons. The card shows it as a warning. In
auto mode the executor does not apply the move.consumed. The AX best
bid and offer. The number of rejects and of distinct accounts over 5 and
60 minutes. The number of resting orders outside the proposed
limits.order_log does not store the
price of a rejected order, so the producer cannot compare rejected
prices with the underlying.A dated future uses the same anchor as a perp: the TWAP of the
underlying. The anchor has no basis term. The band allows for the basis
instead. It is wider when expiry is far, and it equals
base_pct at expiry:
band pct = base_pct + carry_pct_per_year × years to `instruments.expiration`
carry_pct_per_year is the largest annual basis (cost of
carry) that the band allows on each side. With base_pct 10
and carry_pct_per_year 12, a contract three months from
expiry has a ±13% band. One week from expiry it has ±10.2%.
The producer computes the width once a day, after settlement. When
the width differs from the EP3 percentages by 0.25 points or more, the
producer opens a price_band_width task with a
set_price_bands proposal and calls
rule-accept. The executor writes through the existing
set_price_bands code. This runs in every mode except
Off, because the width depends only on the date and on two
settings that an operator chose. The task appears in the History tab and
sends no alert.
Stages A, C and D deliver this mode.
A task sends an alert to #ax-support with a link to the
task. Each symbol has its own alert. The task has no proposal. The
operator recenters with the trading-reference dialog from stage 0. The
producer resolves the task on its next run.
support-queue: {subject}, for
example support-queue: price_band_drift:ARM-PERP. The
notifier adds the prefix, so a producer cannot leave it out.detail
rendered as text, so the Slack message carries the numbers, such as
consumed and the reject counts. A last line says to defer
or dismiss the task in the queue, because an incident.io snooze only
pauses the reminders.terraform/incident-io/prod.tf sends titles that contain
support-queue to #ax-support. The general prod
route excludes them, as it excludes recon-engine
titles.metadata.priority comes from
the severity. source_url is
/support-queue?task={id}.incident.io and the queue each own one part of the work. incident.io owns the page: notify, re-notify, acknowledge and snooze. These say only that a person has seen the alert. They do not change the task, and the queue does not read them back. incident.io has no webhook and no API for an acknowledgement or a snooze. The queue owns the task: defer, dismiss, accept and reject. These resolve the alert, and the route cancels the escalation with it.
| An operator | Result |
|---|---|
| Acknowledges or snoozes in incident.io | The re-notifications stop or pause. The task stays open and counts on the badge. |
| Defers in the queue | The alert resolves. It fires again when the deferral ends, if the condition holds. |
| Dismisses in the queue, or the producer self-resolves | The alert resolves. |
| Resolves the alert in incident.io | The notifier fires it again within a minute, because the task is open. |
An incident.io snooze ends on a timer and does not know the condition. A deferral ends the same way but fires again only if the condition holds, and it is in the task's history. So an operator who wants quiet for longer than the re-notification cadence defers the task. The alert's resolve text names the deferral, so incident.io shows why the alert closed.
Stage E delivers this mode.
When the mode is Propose and an allowed source is fresh,
the task carries a proposal:
{ "action": "set_trading_reference_price",
"rev": 3, "expires_at": "…", "needs_human_reasons": [],
"symbol": "SNDK-PERP", "price": "1738.90",
"basis": { "anchor": "1655.20", "anchor_kind": "settlement",
"anchor_set_time": "…", "twap": "1738.94",
"source": "DATABENTO", "window_secs": 300, "points": 5,
"new_lower": "1565.05", "new_upper": "1912.75" } }The producer replaces the proposal and increments rev in
two cases: the TWAP differs from the proposed price by more than one
tenth of the band width, or the proposal has expired. The proposed price
therefore stays the same while the operator reads it.
The routes are in a new file,
rs/api-gateway/src/admin_support_routes.rs, under
/admin/support-tasks:
| Route | Who can call it | Effect |
|---|---|---|
GET /, GET /{id} |
admin | Lists tasks with the open count; returns one task with its events |
POST /{id}/defer |
admin | Sets deferred_until; needs a note |
POST /{id}/reject |
a person | Decision 3 |
POST /{id}/accept |
a person | Decisions 4 to 6; body {rev, price_override?, note?};
price_override applies to band proposals only |
POST /{id}/dismiss |
a person | Closes a task of a kind that does not resolve itself |
POST /{id}/rule-accept |
service token | Auto mode |
Accept does these steps in order. The executor dispatches on
proposal.action; stage U1 delivers the route with the
credit_deposit action, and stage E adds
set_trading_reference_price.
set_trading_reference_price code.executed and resolve the task as
accepted.When the EP3 write fails, the executor records
execution_failed and the task stays open.
Stage F delivers this mode.
When the mode is Auto, the producer calls
rule-accept on a proposal in the run that creates it. The
executor applies the move when every rule in the table passes. When a
rule fails, the executor records rule_declined and adds the
reason to the proposal's needs_human_reasons. The task then
stays open, sends an alert, and waits for an operator, as in
propose mode.
| Rule | Default | Reason when it fails |
|---|---|---|
| The move from the current anchor is within the cap for its direction | 10%, ceiling 15% | extreme_move |
| The move from the trusted anchor is within the cumulative cap | 20% | cumulative_limit |
| The window has at least 4 of the 5 expected points | thin_series |
|
| The points in the window differ by less than 3% | unstable_series |
|
| The AX mid price, when both sides have quotes, is within half a band of the TWAP | market_disagrees |
|
| A second fresh source, when there is one, is within 1% | sources_disagree |
|
| The time is not within 15 minutes of the instrument's settlement time | near_settlement |
|
| The anchor was last set at least 30 seconds ago | none; the producer tries again on its next run |
The trusted anchor is the later of two prices: the last settlement price and the last price that an operator accepted. The directional cap limits one move. The cumulative cap limits the total of the automatic moves since the trusted anchor. A faulty series can drift in steps that each pass the directional cap, and the cumulative cap stops the total.
The executor applies a move in full or not at all. It does not apply part of a move that exceeds a cap.
NAV_ITEMS, below Dashboard
(gui/apps/admin/src/components/features/SideBar.tsx). The
badge shows the number of open urgent and
action tasks that are not deferred. It follows
useBtnlSyncOpenCount. On mobile it is a drawer link below
DASHBOARD_LINK in AppNavigator.tsx.urgent first, then oldest first. History lists
resolved tasks, with filters for subject and resolution. A filter on the
text before the colon selects a kind. The layout follows
pages/btnl-sync/.1655.20 → 1738.90 (+5.1%). The card dispatches on
the kind, the text before the colon; the row does not need it.needs_human_reasons and the events. The
actions are Accept, Accept at another price, Reject and Defer. Reject
and Defer use the note dialog from
ApplicationDecisionButtons.tsx.MarketsTable.tsx.@architect/admin, so web and mobile use the same ones.Stages U1 and U2 put two conditions that exist today into the queue before the band producer. They exercise the tables, the routes, the notifier and the page on real rows, on both editions, with no EP3 write in the loop. The two were chosen from a survey of the admin pages and the service loops on 2026-09-22 (Other task kinds has the rest).
Every Anchorage deposit stays at INGESTED until a person
calls POST /admin/anchorage/deposits/{id}/credit. No GUI
calls that route, and the Deposits page is read-only. No alert exists
for a deposit that waits.
anchorage-deposits-uncredited, every minute. It replaces
the balance check in
rs/recon-engine/src/checks/anchorage_deposits_processed.rs,
whose comment already asks for an ops work queue. The condition is a row
in treasury_engine.anchorage_deposits with
status = 'SUCCESS' and
credited_by_admin IS NULL; the partial index
anchorage_deposits_uncredited_idx serves it. A deposit that
Anchorage has not settled is not a condition, because the credit route
refuses it.deposit_uncredited:anchorage/{transaction_id}.info for the first 30
minutes, then action.ManualReview and Flagged included), the chain
verdict, first_seen_at and the age.{ "action": "credit_deposit", "deposit_id": …, "account_id": …, "amount": … }.
The executor calls the existing settle_deposit code behind
the credit route. That code is already behind
require_human_admin_actor, requires a reason, and refuses a
second credit through the deposit_credits primary key, so a
double accept returns 409 with no second write. The task's
note is the reason.accepted when the credit
comes from the task. self_resolved when the producer sees a
credit from the route, or a credit from the task that has no
executed event because api-gateway restarted. A deposit
task cannot be dismissed; an operator defers it.onboarding_gateway.btnl_ib_account_sync_errors is
already the shape of a task: one open row per
(email, kind), first_seen_at,
last_seen_at, and resolved_by = 'sweep' for
self-resolution. Its page has the only sidebar badge. The hourly sweep
in rs/bitnomial-reconciler/src/ib_accounts/task.rs is the
producer.
self_resolved, and a
row resolved by a person is dismissed. Dismiss in the queue
calls the existing resolve_by_id code, so both pages agree.
When the Work Queue page is removed, the producer writes tasks directly
and the old table goes.btnl_ib_sync_{kind}:{email},
for example
btnl_ib_sync_conflicting_identity:alice@example.com. The
sub-kind is in the kind because each one has a different remedy.info while in shadow mode,
so nothing alerts that does not alert today.
action_required_connections becomes action
when the old page is removed.detail,
external_account_id, user_id and the remedy
text from btnlSyncMeta.ts.dismissed for a condition that never
ends, and the queue page against a page that operators already use.The survey on 2026-09-22 found these further conditions. None is in scope.
| Condition today | Fit |
|---|---|
| Failing invariants | Each check is a task keyed by check_id. The Invariants
snooze is keyed by check, and a per-task deferral would lose it when a
check flaps. Convert after the notifier has run on U1 for a month. |
| Monthly loss-limit breaches | Latched per account in monthly_loss_limits; the cure is
one PATCH; no alert today. A good producer, but breaches are rare, so it
validates little. |
TRM screening in OUTCOME_UNKNOWN |
No route and no GUI reopen it. Needs the reopen route before it can be a task. |
| Parked liquidations | liquidation_engine.account_state has the task shape,
but no service runs the engine yet. |
| EP3 surveillance alerts | Stays as it is. EP3 owns the status. |
| Onboarding review, MMLP accruals, special settlements | Stay as they are (decision 1). |
| Stage | Scope | Reviewers |
|---|---|---|
| 0 | Trading-reference dialog on web and mobile; anchor kind and set time
in MarketsTable; settlement dialog renamed "Set EP3
Settlement Price (EOD)"; runbook for a band alert |
frontend |
| A | support_tasks, support_task_events,
ax_db entities; the read, defer and dismiss routes |
backend |
| B | Support Queue page, badge, mobile screen, dashboard card | frontend |
| C | Notifier loop in recon-engine; terraform route and escalation path | backend, infra |
| U1 | anchorage-deposits-uncredited producer; the accept
route and the executor with the credit_deposit action; the
deposit card |
backend, frontend |
| U2 | Bitnomial sync shadow producer in bitnomial-reconciler; dismiss
wired to resolve_by_id; the sync-error card |
backend, frontend |
| D | Band producer in alert mode, starting from the check on
origin/alee/auto-band-recenter-impl: both triggers, the
cross-check, band_recenter_config, the dated-future width,
an EP3 read client in recon-engine, the paging flag |
backend |
| E | Band proposals; the reject route; the
set_trading_reference_price action in the executor; the
band card actions |
backend, frontend, high-risk |
| F | rule-accept; the rules in Auto mode; the daily
digest |
backend, high-risk |
| G | Pyth rows in prod underlying_prices for overnight
equities, futures and crypto; pyth_config on every
instrument that has a Pyth feed |
backend |
0
A ── B ─────────────────────────────┐
└─── C ── U1 ── U2 ── D ── [alert] ── E ── [propose] ── F ── [auto]
G
alert mode is live when A, C and D are in prod. Until B
is in prod, the alert links to the Markets page.Conditions to move a symbol from propose to
auto. The symbol has been in propose
for two weeks or more. Operators have decided ten or more proposals.
They accepted 90% or more with no price change. They rejected none as a
wrong price. The numbers come from support_task_events. Use
demo first, with one symbol for a week. Then use prod, one symbol at a
time.
Tests. These cases need a test, because the feature
is a risk control: EP3 disconnects during accept; api-gateway restarts
between accepted and executed; recon-engine
restarts with a task open; two operators accept at the same time; a
proposal expires while the confirm dialog is open; an incident.io alert
differs from its task. ep3-mock already stores the trading
reference price. For U1: two operators accept the same deposit; a
deposit is credited through the route while its task is open;
api-gateway restarts between the credit and executed.
The producer keeps no state in memory. EP3 holds the anchor and the time it was set. Postgres holds the settings and the tasks. ClickHouse holds the series. After a restart, the next run reads all of them again. The 30-second rule reads the set time from EP3, so a producer that restarts in a loop cannot recenter more often than every 30 seconds.
| Failure | Result |
|---|---|
| recon-engine is down | Bands do not move. Open tasks stay open and
last_seen_at stops. The service heartbeat sends an
alert. |
| ClickHouse is down | No source is fresh, so the underlying trigger does not run. |
| api-gateway is down | Accept fails. The task stays open. |
| The EP3 write fails | The executor records execution_failed. The task stays
open. |
POST /admin/trading-reference-price. Do
not use the settlement dialog. The route has no dialog until stage
0.order_log,
grouped by symbol.is_admin.
The routes check is_admin. When the admin-interface-hardening
RFC ships, band tasks need ManageMarkets.rs/sdk rule on internal detail
applies to it.RECON_ENGINE_INCIDENT_IO_ALERT_SOURCE_URL and
RECON_ENGINE_INCIDENT_IO_ALERT_SOURCE_TOKEN in demo and
prod. Send one test alert and confirm that it reaches
#ax-support.docs/public/guides/perpetual-futures/contract-specs.mdx
before any symbol uses auto. It says that bands reset at
each settlement.underlying_prices has no Pyth rows. Stage
G can be a settings change only.carry_pct_per_year for the B300, H100 and H200
contracts.docs/internal/operations/ep3/sandbox-reset.mdx), before
stage E is in prod.postgres user today, so producers write tasks through
ax_db (The model).