> ## Documentation Index
> Fetch the complete documentation index at: https://takeprofit.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> Stream live data into Indie indicators with sources.DataFeed. How to declare a feed, how launch settings warm the series up, reconnects, date windows and feed errors.

# Live data from a feed

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`:

```py theme={null}
from datetime import timedelta
from indie.data import sources

sources.DataFeed('wss://feeds.example.com/signal', stale_after=timedelta(seconds=30))
```

* 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](/docs/indie/External-data/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.

```py Live model signal theme={null}
# indie:lang_version = 5
from dataclasses import dataclass
from datetime import timedelta
from indie import indicator, MainContext, data_context
from indie.data import sources

@dataclass
class ModelOutput:
    signal: float
    confidence: float

@data_context[ModelOutput]
def FeedSignal(self):
    return self.data[0].signal * self.data[0].confidence

@indicator('Live model signal')
class Main(MainContext):
    def __init__(self):
        self._sig = self.calc_on(
            FeedSignal,
            source=sources.DataFeed(
                'wss://feeds.example.com/signal',
                stale_after=timedelta(seconds=30)))

    def calc(self):
        return self._sig[0]
```

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.

```txt A run launched from a date theme={null}
1. short connection    ask:    from_time = the launch date, to_time = now
                       answer: the records in that range, then replay_done
2. live subscription   picks up at the last record of step 1
3. live records        continue on the same series
```

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](/docs/indie/External-data/CSV-format-and-limits#error-reference) take — for example
`external_feed_gone: the data feed is no longer available`. The codes are:

| Code                               | Meaning                                                                                                                                                                                   |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_feed_gone`               | The feed is permanently unavailable: the server sent an error frame, or answers 404 or 410.                                                                                               |
| `external_feed_auth_failed`        | The server rejected the connection as unauthorized (401 or 403).                                                                                                                          |
| `external_feed_bad_response`       | The feed sent data the platform accepted but cannot deliver: a record that fails conversion to the declared schema, a record too large to deliver, or a source the platform cannot serve. |
| `external_feed_schema_conflict`    | The same feed is already served with a different dataclass schema. One feed carries one schema.                                                                                           |
| `external_feed_disabled`           | External feeds are currently disabled on the platform.                                                                                                                                    |
| `external_feed_replay_interrupted` | A run over a date window asked the feed for the window and the feed stopped answering part-way through.                                                                                   |
| `external_feed_window_limit`       | A date window is larger than the platform loads in one batch. Narrow the dates.                                                                                                           |

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](/docs/indie/External-data/CSV-format-and-limits#errors-when-declaring-the-source), 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](/docs/indie/External-data/Feed-protocol-and-limits).
