Following an external price
The two price modes that tether a market to an outside exchange — what they require, how the gravity pull is sized, the caches behind it, the 30-second sync job and its deviation alert, and what happens when the feed goes away.
A market maker's price comes from one of three places, and two of them are somebody else's exchange. Choosing one of those two adds a dependency the create wizard never mentions, a cache you cannot see, a restoring force with its own timescale, and a scheduled job that will start writing alerts into the market's history.
This page is that whole surface. The controls themselves live on the market's Configuration tab — see Creating and configuring a market for the rest of that screen.
The three modes
| Mode | Label on screen | What it does |
|---|---|---|
AUTONOMOUS |
Autonomous | The engine's own price process, alone. No external reference is fetched at all |
FOLLOW_EXTERNAL |
Follow external | Tethered to externalSymbol at correlationStrength |
HYBRID |
Hybrid | The same tether at half that strength |
There is one tether. HYBRID applies it at correlationStrength / 2 and
nothing else differs — same fetch, same cache, same restoring drift. It is a
blend of engine and reference, not a peg with extra rules, and a HYBRID market
at 100% correlation behaves exactly like a FOLLOW_EXTERNAL market at 50%.
externalSymbol is required for both non-autonomous modes. Without it the
request is refused:
External symbol is required for FOLLOW_EXTERNAL and HYBRID price modes
correlationStrength is validated to 0–100; anything outside is refused with
"Correlation strength must be between 0 and 100". The column is
DECIMAL(5,2), defaulting to 50, and externalSymbol is a VARCHAR(20) — a
long pair name will not fit.
The dependency nobody states: you need an exchange provider
External prices are not fetched from a URL you configure on this addon. They come
through the platform's shared ExchangeManager — the same connection that serves
spot markets, spot currencies and spot charts.
ExchangeManager.startExchange() returns the single exchange row whose status
is enabled. If no provider is enabled, if its API credentials are missing from
.env, or if the provider has banned the box, the call returns null — and
the tether simply does not engage. The market keeps quoting, the price mode still
reads Follow external on screen, and nothing on the admin side reports the
gap.
Set the provider up first: Connecting a spot exchange provider.
Two consequences follow from that being a shared provider:
- The symbol must exist on that provider, in CCXT form.
externalSymbolis passed straight tofetchTicker, so it isBASE/QUOTEin upper case —BTC/USDT, notBTCUSDTand notbtc/usdt. A symbol the provider does not list resolves to no price, which is indistinguishable from no provider from where you are standing. - Your ecosystem tokens are usually not listed anywhere. A platform-only token has no external reference by definition. Following one is a configuration that can never engage.
The gravity effect
The tether is a restoring drift, not an assignment. Nothing ever writes an external price onto the market.
Each refresh produces exactly two figures, stored on the market instance as a single pair:
| Figure | Meaning |
|---|---|
price |
The reference's last traded price, as the exchange's ticker returned it |
strength |
correlationStrength / 100, halved on HYBRID, clamped to 0–1 |
Nothing else is computed when the reference is fetched. There is no stored divergence figure and no blended target price: the divergence is re-derived inside the price step itself, from the market's own price against that stored reference, every time it steps.
Inside the price process the pull is sized by convergence time, not by divergence alone. The gap closes exponentially with a timescale of roughly one day at full strength, divided by the configured strength — so 100% tracks within about a day, 20% within about five. The whole restoring drift is then clamped to at most three times the market's own daily volatility per day, so a mis-mapped symbol — a reference feed handing back the price of an entirely different asset — cannot rip the market off its anchor in one step.
That clamp is the reason this is always a smooth transition and never an instant jump. There is no code path that sets the price to the external price.
correlationStrength, in practice
The slider on the Configuration tab is 0–100 and reads as a percentage. What it changes is how fast the market converges on the reference, not how close it ends up:
- Low (10–30) — a long, loose tether. The market keeps its own character and drifts toward the reference over days. Divergence of several percent is normal and expected.
- Mid (40–60) — the default region. Convergence in roughly two days.
- High (80–100) — the market tracks within about a day and its own price process is largely overridden while a gap exists.
On HYBRID, halve all of that.
This is inherent, not a defect. A client watching both your market and the reference can see the divergence and bet on convergence. The first time a tether engages, the engine states the size of that edge in the server log:
SYMBOL: tracking BTC/USDT at 60% strength. A client watching both feeds has an estimated x.x% directional edge on 1h bets while the prices diverge. Use AUTONOMOUS for markets that settle binary options.
Use AUTONOMOUS on any market that also settles binary options. The edge cap bounds the exposure; it cannot remove it.
Two caches, and why a slow exchange cannot stall a tick
The engine's price step is synchronous by design. It never waits on the network.
The tether is refreshed beside the tick, not inside it:
- The market instance refreshes its cached gravity at most every 15 seconds.
- That refresh asks
ExternalPriceSync.getExternalPrice(), which reads Redis keyexternal_price:<symbol>with a 5-second TTL before touching the exchange. - Whatever it gets is stored as a plain
{ price, strength }pair. The price step reads that pair and returns immediately.
A slow, rate-limited or unreachable exchange therefore costs a stale tether, never a stalled market. Note that only successful reads are cached: a symbol the provider does not list is not remembered anywhere on this path, so the engine re-attempts the failing lookup every 15 seconds for as long as the market runs.
processAiPriceSync does not share the engine's cache. It keeps an in-process
map with a 5-minute TTL, remembers symbols the exchange did not have for
one hour, and bounds its whole network pass to 20 seconds so one slow
exchange cannot carry a tick into the next scheduled run. If it runs out of
budget it says so:
Price sync budget exhausted after N of M markets
The price-sync job and its deviation alert
processAiPriceSync runs every 30 seconds and is visible under
Admin → System → Cron in the ai_market_maker category as AI Price Sync.
What it actually does is narrower than its name suggests, and worth knowing precisely:
- It runs for every ACTIVE market maker, not only the tethered ones. The
symbol it looks up is the market's own pair (
currency/pair), notexternalSymbol. - It compares that reference price against the market's
targetPrice— not against its live price. - It does not move any price. It is an alerting job.
- It skips entirely when
aiMarketMakerEnabledis off.
When the gap exceeds 10% it broadcasts a warning on the cron log:
BTC/USDT: Target price $x deviates 14.20% from external $y
and writes one aiMarketMakerHistory row per market at most once per hour —
action: CONFIG_CHANGE with details.field = "PRICE_DEVIATION_ALERT". The
throttle exists because a market deviates for as long as you leave it deviating,
and an unthrottled alert would add two rows a minute forever. Those rows are
pruned by the retention job; see
Where the tape lives.
An AUTONOMOUS market whose pair happens to be listed on your exchange provider
will raise these alerts too, and that is correct — it is telling you your target
price disagrees with the outside world. It says nothing about whether any tether
is working.
When the feed is unavailable
There is no external-price indicator anywhere in the admin UI. Be clear about what you can and cannot see.
What the market does. The refresh returns without changing anything, so the
market keeps pulling toward the last external price it successfully read — for
as long as the outage lasts. If it has never read one, externalGravity is
absent and the market runs on its own price process alone, exactly like an
autonomous market. In neither case does the market stop, pause or slow down.
How to tell. Four places, in order of usefulness:
| Where | What you are looking for |
|---|---|
Server log, filtered to AI_MM |
Error fetching external price for SYMBOL, Symbol SYMBOL not found on exchange, No exchange provider available for external price, or External price refresh failed for SYMBOL |
| Server log, once per market start | The tracking … at N% strength line. Its absence is the signal — it is only written when a tether first engages |
| Admin → System → Cron → AI Price Sync | Deviation warnings, or their complete absence across every market |
| Finance → Trading Infrastructure → Exchange Providers | Whether a provider is licensed, credentialled and enabled at all |
The market detail Overview tab reports the mode and the intent — "Following BTC/USDT at 60% correlation", or "No external symbol set" — but it reads those from the database row. It is telling you what was configured, not what the feed did.
The masthead badge on /admin/ai/market-maker describes the dashboard's own refresh, not the exchange feed. A green Live dot beside a tethered market says nothing about whether that market has an external price.
Changing the mode
priceMode is the only required field in the body; externalSymbol and
correlationStrength are optional and each is left unchanged when omitted.
The handler validates the pairing, writes the row, applies the change to the
running market immediately, and records a CONFIG_CHANGE history entry
carrying both the previous and the new triple.
On the Configuration tab all three fields belong to one group behind the single Save button, and only groups whose values actually changed are posted. The external symbol and correlation controls are hidden entirely while the mode is Autonomous.
-
Confirm the provider first. An exchange provider that is licensed, credentialled in
.env, and enabled — and a restart since you pasted the credentials, because.envis read at boot. -
Confirm the symbol is listed there in
BASE/QUOTEform. If your reference is not on the provider you have enabled, choose a different reference or a different provider. -
Set the mode on the market's Configuration tab. Start at a low correlation strength — 20–30 — and let it run for a day.
-
Check the server log for the
tracking …line. No line means no tether. -
Raise the strength in steps, watching the market's price band on the dashboard as you do.
The controls that interact with it
Four other settings on the same tab pull on the same price, and the tether does not override any of them.
| Control | Interaction |
|---|---|
| Target price | Independent of the tether. It is the anchor the price band is validated against, and the figure the deviation alert compares — so a tethered market whose target was never moved will alert forever while behaving correctly |
| Price range low / high | The containment leash still applies, and it is sized to out-pull the diffusion. A tether pointing outside the configured band loses: the price is held at the band edge, permanently diverged |
| Market bias + strength | Still steers phase transitions. A bias fighting the tether shows up as a market that never converges |
| Base volatility | Sets the units the tether's own cap is measured in — the clamp is three daily volatilities per day, so a low-volatility market converges more slowly for the same strength |
The single most common way to get a tethered market that never tracks is a price range that does not contain the reference price. Nothing refuses this combination: the target price validation only checks the target against the range, and neither the price-mode endpoint nor the engine knows where the reference will be tomorrow. The symptom is a price pinned at the range edge, which the dashboard reports as at range edge or outside range.
What could not be determined
The addon exposes no endpoint, column or screen that reports the currently
observed external price, the live divergence, or whether a tether is engaged
right now. phase.get returns priceMode and externalSymbol — the
configuration — and nothing more. Diagnosis is the server log.