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.

13 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

priceModetype: enumdefault: AUTONOMOUS
How the price relates to the outside world
externalSymboltype: stringdefault: null
The exchange symbol to track. Required for both non-autonomous modes
correlationStrengthtype: numberdefault: 50
How FAST the market converges on the reference, 0-100 — not how close it ends up
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

PUT/api/admin/ai/market-maker/market/{id}/price-modepermission: edit.ai.market_maker.market
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 The anchor for an AUTONOMOUS market. While a market is following a reference the anchor becomes the reference price instead, so the target stops steering the price — it is still the figure the deviation alert compares, which is why a tethered market whose target was never moved alerts while behaving correctly
Price range low / high A tracking tolerance, not fixed bounds. The band is re-centred on the reference at the width you configured and travels with it, so its width is how far price may stray from the reference. Narrower means tighter tracking, and more exposure — the price is then held close to a public figure informed traders can also see. Wider lets the market behave more like its own
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

Until v6.2.4 a price range that did not contain the reference produced a market that could never track: the containment leash is capped at six daily volatilities per day against the tether's three, so it out-pulled the tether two to one and the price parked at the band edge — at any correlation strength, so raising the slider changed nothing.

The band is now re-centred on the reference, so that failure cannot occur. The reference sits at the middle of the band by construction, which means the leash is not helping the tether so much as staying out of its way: the leash is dormant through the inner four-fifths of the band and ramps quadratically after that, so on a well-sized range its soft edge sits far enough from the reference that it never engages during normal tracking. Convergence is the tether alone.

What the Configuration tab reports is therefore no longer "can it reach the reference" but whether it is there yet: the live reference price, the current tracking error, and a warning when the price has come adrift from the band it should be tracking within.

Making the PAST match the exchange

Tracking a reference changes where the price goes next. It does nothing about the history already on the chart, which the engine wrote from its own trades and which therefore looks nothing like the same pair elsewhere — different candle shapes, forming at different times. For a trader comparing your chart against TradingView before deciding whether to trade, that history is most of what they are looking at.

It can be imported:

POST /api/admin/ai/market-maker/market/{id}/backfill
{ "intervals": ["1h", "4h", "1d"] }

With no body it imports every supported interval and reads from the market's configured externalSymbol.

Every imported bar lands strictly behind the history the market already has, because five separate things read the NEWEST candle of a series: binary entry and settlement, the market maker's seed price on restart, stop-order triggering, wallet P&L valuation, and copy-trading and P2P rate lookups. An imported bar that became the newest one would be a price that moves money.

So a market with no candles at all cannot be imported into — there is nothing to stay behind. Start the market maker so one live candle exists, reconcile its price level to the provider's first, then import. Skipping that reconciliation joins the two series with one enormous bridging candle and prices binaries off it.

Two further limits are structural rather than cautious. 3d and 1w cannot be imported — the ecosystem floors 3-day bars from the epoch and starts weeks on Sunday UTC, so provider bars for those intervals land beside your series instead of on it; import 1d. And depth is capped per interval, because every interval of one market shares a storage partition.

This is backfill only. It makes the past match and does nothing for the live series, which remains a synthetic price drifting toward a reference — see the note at the top of this page about what following is and is not.

What the tether costs you, and what pays it back

A market that tracks a public reference while the house is the counterparty is writing an option to everyone who can see that reference. Two settings decide how much you pay for it, and they pull against each other:

Setting Effect
Price range width How tightly the price is held to the reference. Narrower is closer tracking and more exposure
Real liquidity % How much of that exposure is real. At 0 the market is bot-to-bot and the cost is zero, because there is nothing for anyone to fill

Three things in the engine price and bound that cost, and they are worth knowing because two of them are visible in the log:

  • Quotes are widened while the reference moves. The premium is scaled to the quote's full resting life, not to the interval the move was observed over.
  • Quotes on a following market rest for five minutes, not an hour. A shorter life is a cheaper option, which is what lets the spread be competitive rather than merely honest. Autonomous markets keep the hour.
  • The real book is topped up to a floor of three quotes a side. This is the other half of the five-minute life: real quotes reach the book only as a side effect of the maker's own trades, so resting depth is trade rate times quote life — cutting the life without replacing the depth would leave a market that trades less often than every five minutes with an empty real book between trades, while the displayed ladder carried on looking full.

The top-up is deliberately harder to reach than ordinary trading. It applies only to following markets on the Ecosystem, it places nothing the market would not have been allowed to trade anyway — the pool, the daily volume cap, the volatility pause and the global risk gate all apply unchanged — and it stops entirely while the honest premium exceeds its 3% cap, because every quote written in that state is below its value. Two log lines are worth watching for:

not topping up real depth while the honest spread for a 5-minute quote
against BTC/USDT is 4.10%, above the cap

The reference is moving faster than a five-minute quote can be priced for. This is self-correcting and needs no action; depth returns when the reference settles. If it is constant, the market is tracking an asset too volatile for the real liquidity you have on it.

real depth is below its floor and the pool cannot back a quote on either side

The pool is empty, or holds only one of the two currencies. Top it up, or set real liquidity to 0 if the market is meant to be bot-to-bot. Without this line the market would simply have gone quiet, which looks the same as a broken maker.

What could not be determined

Whether a market is tracking well is answered by the Configuration tab's tracking error and by the price-sync deviation alert. What neither answers is whether the reference itself is the right one — a feed that is live, plausible and for the wrong asset reports as healthy tracking right up to the 10× mapping guard. Check the external symbol against the pair you meant.