forge.indicator

Forge API 1

The indicator program type: ``fg.Indicator``, ``fg.Output`` and the manifest (contract C3).

Writing an indicator --------------------

An indicator file starts with ``FORGE_API = 1``, declares exactly one class that inherits ``fg.Indicator``, and defines exactly one of ``calculate(self, bars, start)`` (fill every output from bar ``start`` on, the whole-array form) or ``on_bar(self, i)`` (one bar at a time, with declared persistent ``state``). Both forms, and full and incremental runs, must give identical outputs::

import forge as fg from forge import param, ta

FORGE_API = 1

class RsiBands(fg.Indicator): name = "RSI bands" short_name = "RSIB" pane = "new" period = param.Int(14, min=2, max=200) source = param.Source("close") rsi = fg.Output.line("RSI") hot = fg.Output.state("Overbought")

def calculate(self, bars, start): r = ta.rsi(bars[self.source], self.period) self.out.rsi[start:] = r[start:] self.out.hot[start:] = r[start:] > 70

An indicator cannot trade: orders, the account and ``ctx`` are refused at load.

Naming ------

Functions are short and lower case (``ta.rsi``); indicator classes are CapWords (``RSI``); PEP 8 throughout; parameters run source first, then periods, then methods (``ta.ma(source, period, method)``), the order MetaTrader 5 uses. Outputs and settings are snake_case, at most 40 characters; classes are CapWords.

Execution order ---------------

1. Data arrives: a closed bar or, on a live chart only, the forming bar. 2. Bars are built (``forge-bars``, contract C1). 3. Indicators run in dependency order, upstream first. Each runs ``setup`` once, then ``calculate(bars, start)`` from the first new bar, or ``on_bar(i)`` for each new bar. A forming bar is recalculated on every price change only for a built-in indicator whose ``live = "every_tick"``, never for an uploaded indicator (it updates on closed bars, contract C6 section 1) and never in a backtest. 4. The strategy's hooks run in contract C4 section 2's event order and read indicator values cut at the current bar.

Outputs and styles ------------------

``fg.Output.line(label, style=..., color=..., width=..., dash=..., color_up=..., color_down=..., shift=..., show=..., pane=...)`` draws a value in one of 13 styles; ``fg.Output.histogram(label, base=0)`` draws bars; ``fg.Output.hidden(label)`` is a value a strategy can read that the chart does not draw; ``fg.Output.state(label)`` is a true or false value per bar. A shift moves the drawing only: a strategy reads the value at the bar it was computed on. Styling never changes a value, and a strategy reads outputs, never styles. Colours, plot styles and the up and down rule are in ``forge.draw``.

Panels and levels -----------------

``pane = "price"``, ``"new"`` or a named panel such as ``"momentum"``. Indicators that name the same panel share it and one vertical scale: the widest declared ``scale`` among them wins, and ``zero_base`` holds if any of them sets it. ``levels = [fg.Level(70, "Overbought")]`` draws lines in the indicator's own panel. ``fg.Band(a, b)`` shades between two value outputs or two levels and is drawn at its outputs' shift; ``fg.Output.histogram_between(a, b)`` draws that space as bars. A band adds no output. Drawings arrive in a later chunk of lane 6.

Other series ------------

``h4 = fg.Data(timeframe="H4")`` declares a second series; ``timeframe = "4h"`` on the class sets the timeframe this indicator is calculated on. Timeframes are C1's (``M7``, ``H4``, ``D1``, ``W1``, ``MN1``) or the free spelling ``7m``, ``90m``, ``4h``, ``3D``, ``1W``, ``1M``. A value of another series becomes visible on the first bar of this indicator whose close is at or after that value's own close; there is no switch that shows it earlier.

Repainting and look-ahead -------------------------

An indicator repaints when a value it already showed changes later, and it looks ahead when a value depends on bars that had not happened yet. There are seven ways an indicator's past can change, and each has a rule:

The forming bar: The engine never sees it. The chart recomputes it from the last confirmed state on every update, so it is never counted twice.

A higher timeframe bar that has not closed: Its value is visible only after the bar closes. There is no look-ahead setting.

Look-ahead in the code: The lint flags it when the file is checked, and the causality check catches it on save and on publish.

A lower timeframe: Only closed bars are aligned, so a smaller bar inside the current bar is visible once it closes, never before.

State inside the forming bar: Not available: state rolls back on every update of the forming bar.

Drawing into the past: Drawings are for the eyes only, and the badge says the indicator repaints.

Corrected history: The indicator recomputes from the first changed bar, and a corrected upload is a new data version.

When a file is saved, and again when it is published, Forge runs the same check on its own sample prices: it cuts the history at many bars, recomputes, and compares every output with what the full run showed. A value that changed is proof: the verdict is "repaints", and strategies are fed the value the indicator showed at each bar's close. "Does not repaint" is only said when every output was exercised enough to mean something; with too little data the verdict is "not checked", with the reason, and never "clean". Four things are set aside, and the report lists which were used:

Training label: An output marked as a label for machine learning. It is reported, never offered to strategies.

Declared as repainting: An indicator that sets repaints to True. Strategies read its values as they were at each bar's close.

Rounding differences: Differences no larger than one part in a billion. They come from the order of floating-point sums, not from later bars.

Drawings: Labels, lines and boxes an indicator draws. Drawings are for the eyes only; no strategy reads them.

Alerts ------

An indicator declares what it can alert on, ``alerts = [fg.AlertCondition("RSI above 70", when=hot)]``, where ``when`` is one of its ``state`` or ``event`` outputs, and may raise one in its code with ``self.alert("message")``. ``forge.alerts`` says how often an alert may fire, what a message may contain and what the limits are. Nothing an indicator does can send anything.

NameKindSummary
BandclassShade (or draw as bars) the space between two sides of one indicator.
DataclassAnother series an indicator reads: a timeframe, a symbol, a bar type or a price.
IndicatorclassThe one base class of an indicator (IND-001: declared by subclassing, never inferred).
LevelclassA horizontal line in the indicator's pane: ``fg.Level(70, "Overbought")``.
OutputclassOutput declarations.
BAR_TYPESconstant
DECLARABLE_EXCEPTIONSconstant
FORMATSconstant
LIVE_CADENCESconstant
MANIFEST_SCHEMAconstant
check_causality_exceptionsfunctionThe declared causality exceptions: output names to ``DECLARABLE_EXCEPTIONS``.
check_data_declarationsfunctionThe checks that need the whole class.
check_output_declarationsfunctionThe checks that need the whole class: shift names and the price pane.
check_repaintsfunction``repaints`` is ``True`` or ``False``; anything else is ``metadata_invalid``.
check_timeframe_attributefunctionThe class ``timeframe`` in C1's spelling, or a setting's name.
describe_bandsfunctionOne manifest entry per band, in declaration order.
describe_outputsfunctionOne manifest entry per output, in declaration order (all keys always present).
normalize_timeframefunctionA timeframe in C1's spelling: ``M7``, ``H4``, ``D1``, ``W1``, ``MN1``.
output_stylesfunctionEvery output's style with its default colour filled in.
timeframe_nsfunctionThe length of a C1 timeframe in nanoseconds, or ``None`` for a month (no fixed length).
use_call_targetsfunctionThe first argument of every ``self.use(...)`` in the class's methods, in source order.
use_target_keyfunctionThe registry key a ``self.use`` first argument names; ``use_invalid`` when it names none.
uses_from_sourcefunctionThe built-in indicators this class names in ``self.use(...)``, sorted, each once.
warmup_expression_from_nodefunctionOne warm-up expression from a literal, a ``lambda self: ...`` or a ``def warmup(self)``.
warmup_manifest_from_nodefunctionThe warm-up manifest from a ``warmup`` node: what the class and the load check both say.
warmup_nodefunctionThe class's ``warmup`` assignment value or ``def warmup``, or ``None``.