# Use strategy outputs

> Consume evaluated metrics, text and action intents in charts, application logs and your own data consumers, with delivery and storage owned by the host.

[Canonical documentation](https://flowscript.ftw.trade/docs/outputs)

A program's outputs can become data in your own application: chart series, status labels, event rows, alerts or inputs to another consumer. The core returns structured values. Your host chooses where they go and which consumers may read them.

## What comes out

| Output | Core API | Example consumer |
|---|---|---|
| Numeric or boolean metrics | `step.metrics` | A chart, table, monitor or derived dataset |
| Text metrics | `step.texts` | A status label, annotation or application log message |
| Action decisions | `step.decisions` | An event stream, notification adapter or execution adapter |
| Application logs | Records your application constructs | A log viewer or observability service |
| Stored rows | Records your application persists | A database, API, dashboard or another application |

`metric("average", average)` publishes a number. `metric("state", "ready")` publishes text, returned through `texts`. In a replay those fields contain one column per metric; `run.step` returns the values for the bar just evaluated.

An action decision means the program requested a declared action. It is not a receipt, fill or confirmation that an external effect happened. An application can inspect or store decisions without attaching an execution adapter.

## Turn evaluated values into application data

The following complete example computes an average, publishes a text state and requests a host-defined `signals.publish` action when the condition changes. Its six hourly prices are an illustrated input sequence, not recorded market data. The example collects outputs in memory; it does not send notifications, place orders or write a database.

Use an authorized source build of the package as described in [Embedding Flowscript](https://flowscript.ftw.trade/docs/embedding). Save this as `outputs.mjs` in an application with that built package installed and run `node outputs.mjs`.

```js
import { loadFlowscript } from '@flowscript/core';

const flowscript = await loadFlowscript();
const host = {
  sources: [
    { name: 'bars.close', type: 'number', keyed: true, intervals: ['1h'] },
  ],
  capabilities: [JSON.stringify({
    name: 'signals',
    actions: {
      publish: {
        args: { message: 'Text', close: 'Number' },
        required: ['message', 'close'],
        evaluated: ['message', 'close'],
        authority: { kind: 'host', operation: 'signals.publish' },
      },
    },
  })],
};
const source = `flowscript "0.3";
uses { bars: "^1", signals: "^1" }
const close = bars.close("DEMO-USD", "1h");
const average = isNa(close[2]) ? na : sma(close, 3);
const above = close > average;
metric("close", close);
metric("average", average);
metric("state", isNa(average) ? "warming up" : above ? "above average" : "at or below average");
when (above) { signals.publish({ message: "above average", close: close }); }
when (!above) { signals.publish({ message: "at or below average", close: close }); }
`;
const compiled = flowscript.compile(source, host);
if (!compiled.ok) throw new Error(JSON.stringify(compiled.problems));

try {
  if (compiled.program.warnings.length) {
    throw new Error(JSON.stringify(compiled.program.warnings));
  }
  const started = flowscript.run(compiled.program);
  if (!started.ok) throw new Error(started.error);
  try {
    const run = started.run;
    if (run.unmodelled.length) throw new Error(run.unmodelled.join('; '));
    const closes = [100, 102, 104, 101, 99, 105];
    const firstClose = Date.UTC(2026, 0, 1, 1) / 1000;
    const runId = 'illustrated-signal-run';
    const chartRows = [];
    const actionIntents = [];
    const applicationLogs = [];
    // Preserve unknown values as explicit chart gaps when encoding JSON.
    const chartNumber = (value) => {
      if (Number.isNaN(value)) return null;
      if (!Number.isFinite(value)) throw new Error('Invalid output metric');
      return value;
    };

    for (let i = 0; i < closes.length; i++) {
      const time = firstClose + i * 3600;
      const step = run.step({ 'bars.close': closes[i] }, time);
      if (!step.ok) throw new Error(step.error);
      if (step.orders.length || step.refused.length) {
        throw new Error('Unexpected broker order or invariant refusal');
      }

      chartRows.push({
        time,
        close: chartNumber(step.metrics.close),
        average: chartNumber(step.metrics.average),
        state: step.texts.state,
      });
      for (const decision of step.decisions) {
        actionIntents.push({ runId, bar: step.bar, time, decision });
      }
      // This log record belongs to the application, not a core stdout stream.
      applicationLogs.push({
        level: 'info',
        event: 'flowscript.step',
        runId,
        bar: step.bar,
        time,
        message: step.texts.state,
        actionCount: step.decisions.length,
      });
    }

    console.log(JSON.stringify({ chartRows, actionIntents, applicationLogs }, null, 2));
  } finally {
    started.run.drop();
  }
} finally {
  flowscript.drop(compiled.program);
}
```

The program explicitly waits for three observations with `isNa(close[2])`, so its average is unknown for the first two bars, then `102`, `102.333…`, `101.333…` and `101.666…`. A chart can draw `close` and `average` against `time`, leave the initial unknown values as gaps, and use `state` for labels. `time` is the input bar's close in Unix seconds; convert it explicitly if your visualization expects milliseconds.

The three actual decisions occur at bars `2`, `3` and `5`. Each retains the core's `site`, `capability`, `action`, symbolic `args` and evaluated `values`. Here, `values.message` and `values.close` contain the text and price at the moment the action fired. A consumer can place markers on those bars or store them as events. These records do not claim that anything was delivered or executed.

## Where logs come from

The core returns metrics, text, decisions and result errors; this example does not call a Flowscript `log` function or receive a core stdout stream. The JavaScript application creates `applicationLogs` from each successful step, then prints its collected data with its own `console.log`.

A host can instead send those structured records to its own logging service, store text metrics as annotations, or declare a separate logging capability. In each case the host implements delivery and chooses retention. Compiler diagnostics and runtime errors should remain visible as failures, not successful evaluation log entries.

## Connect a durable consumer

Choose the output contract your consumers need: program identity and version, run identity, source identity and units, input cursor, evaluation timestamp, metric names, and action site. Persist that identity with the output so a replay can be recognized rather than inserted as a new event.

Commit output rows, the input cursor and the next [checkpoint](https://flowscript.ftw.trade/docs/embedding#replay-and-checkpoint) together where your storage supports a transaction. For delivery to another system, an application-owned durable outbox can carry committed events; the consumer needs an idempotency key that identifies the same run, input and action site. An in-memory array like this example has no durability or delivery guarantee.

An adapter that performs an external action must retain and reconcile its real outcome. `run.report` can feed supported outcomes back to the run for handlers; an emitted decision alone must never be marked as a receipt. The language does not provide your database, charting library, event bus, signing service or consumer access policy.

These boundaries also let an application publish strategy outputs as its own data product or use them as inputs to another process. The host owns the schema, permissions, delivery and storage. For ftw.trade's particular strategy and visualization workflow, read [its platform documentation](https://docs.ftw.trade/strategies/overview); for the underlying API, read the [language reference](https://flowscript.ftw.trade/docs/reference).
