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.

9 min readUpdated 6 August 2026price-mode, external, exchange, correlation, price-sync

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

How the price relates to the outside world
The exchange symbol to track. Required for both non-autonomous modes
How closely to follow, 0-100
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. externalSymbol is passed straight to fetchTicker, so it is BASE/QUOTE in upper case — BTC/USDT, not BTCUSDT and not btc/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:

  1. The market instance refreshes its cached gravity at most every 15 seconds.
  2. That refresh asks ExternalPriceSync.getExternalPrice(), which reads Redis key external_price:<symbol> with a 5-second TTL before touching the exchange.
  3. 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), not externalSymbol.
  • 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 aiMarketMakerEnabled is 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 houraction: 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 → CronAI 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

Set the price mode, external symbol and correlation strength

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.

  1. Confirm the provider first. An exchange provider that is licensed, credentialled in .env, and enabled — and a restart since you pasted the credentials, because .env is read at boot.

  2. Confirm the symbol is listed there in BASE/QUOTE form. If your reference is not on the provider you have enabled, choose a different reference or a different provider.

  3. Set the mode on the market's Configuration tab. Start at a low correlation strength — 20–30 — and let it run for a day.

  4. Check the server log for the tracking … line. No line means no tether.

  5. 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.