Skip to main content
A CSV source is a snapshot: the platform downloads the file once and the data is frozen. A feed is the live counterpart. You run a small server that publishes records over WebSocket or SSE; the indicator subscribes to it and receives every new record while it runs: model outputs, alternative data, anything produced in real time. If the data already exists as history and you want reproducible runs, use a CSV. If the values are produced now and the script should react to them, use a feed.

Declaring a feed

A feed is declared with sources.DataFeed in place of sources.Csv:
  • The URL scheme selects the transport: wss:// is WebSocket, https:// is SSE. Insecure ws:// and http:// are rejected.
  • stale_after is optional: how long the feed may stay silent before the platform treats the stream as quiet. Whole seconds only. When omitted, the platform default applies (currently 90 seconds).
Feeds carry typed records, the same model as typed external data: you describe a record with a @dataclass and process records with a @data_context callback, or read them directly with request_series. The schema rules from that page apply; the fields of a record’s JSON payload are matched to dataclass fields by name.
Live model signal
Differences from a CSV source:
  • There is no time_frame. A feed has no cadence: records are point-in-time events. In loaded history a record is visible from the first chart bar that opens at or after its timestamp; a live record that lands inside the current bar is applied immediately and revises that bar. Passing time_frame= next to a DataFeed source is an error, and so is lookahead=, which requires a time frame.
  • There is no candle mode: a @sec_context callback does not accept a DataFeed source.
  • The server must be publicly reachable. There is no authentication in this version; if you need access control, put a signed token into the URL query string — it is passed through unchanged.

How much history a live run starts with

A live run does not have to start empty. What fills it depends on how the chart was launched. There is no separate setting for feeds and nothing to declare in code. Launched by history depth. The past comes from the platform’s own rolling buffer, which holds the recent records of every feed it is connected to — currently the last 1,000 records or 4 MiB per feed, whichever fills first. Your server is not asked for anything. Launched from a date. The platform loads the range before the run starts. It opens a short connection to your feed, asks for the records from that date up to the present, and subscribes for live records only after the answer is in. A feed that answers replay requests can start a run with far more past than the buffer holds.
A run launched from a date
Either way the seam is invisible to the script. It is one series, and no flag marks a record as replayed. Take an indicator that computes Sma.new(self._price, 50) over feed records. Without warm-up it stays empty until 50 live records arrive; at one record per minute that is most of an hour. With warm-up the past arrives first, and if it covers 50 records the average has a value right away. The code is the same in both cases. Warm-up is best effort. When it comes up short the series starts shallower and grows on live records, which is a normal outcome rather than an error:
  • A depth launch never reaches further back than the buffer.
  • A date launch on a feed that ignores replay requests gets only what the buffer holds.
  • A date launch whose collection is cut short — the feed goes quiet part-way through, or the range is larger than the platform loads at once — drops that collection whole and starts from the live stream.

Reconnects and gaps

Connections drop: your server restarts, the network blips, the platform redeploys. The platform reconnects on its own; you do not write any of this logic. What a reconnect recovers depends on where the break happened. A break inside the platform is healed from the buffer: if the buffered past reaches back to the last delivered record, the stream continues without a hole. A break between the platform and your server is different: the platform reconnects and resumes with live records, but it does not ask your server to resend, so records published during the outage are lost. Either way the stream continues, and a hole is not repaired later.

Runs over a date window

A strategy launch with both dates set does not open a live stream at all. The platform collects the records of the window, the script computes over them and finishes. Window records come from a single source, never merged: the feed’s replay answer when the feed gives one, the platform’s buffer otherwise. A feed that answers and finishes with a replay_done frame, even for an empty window, has said that this is all the history it has, and the script computes on it. A feed that starts answering and stops part-way is a different case: the platform cannot tell a whole window from a truncated one, so the run ends with external_feed_replay_interrupted rather than computing on a range it cannot vouch for. A window larger than the platform loads in one batch ends with external_feed_window_limit, and narrower dates fix it.

Errors and quiet streams

When a feed fails for good, the indicator stops and shows the error code followed by a short explanation, the same form CSV errors take — for example external_feed_gone: the data feed is no longer available. The codes are: The URL is validated when the indicator instance is created, before any connection: invalid_url, scheme_not_secure, userinfo_not_allowed, ipv6_literal_not_supported, non_ascii_host mirror the CSV declaration errors, and stale_invalid rejects a stale_after that is not a whole positive number of seconds. Passing time_frame= next to a DataFeed source fails with datafeed_time_frame_not_supported. Not everything stops the script:
  • Silence is not an error. A feed quiet for longer than stale_after is treated as stale; the script keeps its last values and the stream continues when records resume.
  • Reconnects and gaps are not errors: see above.
  • Rate limiting is not an error. When the server answers 429 or refuses connections because of load, the platform waits and retries with increasing delays.

What is not promised

The feed contract is best effort end to end:
  • Completeness is not guaranteed. The platform does not verify that the feed sent everything; a record the feed did not deliver does not exist.
  • Holes are not backfilled. A series already delivered to the script is not rewritten; the one exception is a record with the same timestamp as the latest one, which replaces it as a newer revision.
  • A record can arrive twice around reconnects. Duplicates at the seam are collapsed by timestamp, so the script sees each logical record once.
  • Feed data lives in platform memory. A platform restart can reset the loaded series; warm-up then refills what the buffer and the feed still hold.

Setting up the server

The server side of the contract is small: send records as JSON with a timestamp, and optionally answer replay requests. The wire format, frame by frame, is in Feed protocol and limits.