Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions benchmarks/reference/strategies/IndicatorProgramContract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

import numpy as np
import pandas as pd
import talib.abstract as ta
from freqtrade.strategy import IStrategy
from pandas import DataFrame


class IndicatorProgramContract(IStrategy):
"""Small source-only contract for the causal indicator-program-v1 DAG."""

timeframe = "5m"

@staticmethod
def rsi(values):
return ta.RSI(values, timeperiod=14)

def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
close = dataframe["close"]
dataframe["rsi"] = self.rsi(close)
dataframe["mean4"] = pd.Series(close).rolling(4).mean()
dataframe["previous"] = close.shift(1)
dataframe["ewm8"] = pd.Series(close).ewm(span=8, adjust=False).mean()
dataframe["selected"] = np.where(
dataframe["rsi"] > 50,
dataframe["mean4"],
dataframe["previous"],
)
return dataframe
19 changes: 19 additions & 0 deletions docs/indicator-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,22 @@ This inventory is not a Native-support claim. `native_status=inventory-only` mea
operation is required by the observed source but still needs a typed opcode, an exact
kernel, and independent Python/TA-Lib comparison in later M20 tasks. The upstream commit
is evidence identity only and is never used for runtime dispatch.

## Typed indicator program

`indicator-program-v1` is the causal execution contract built from a supported source
subset. It uses generic nodes for parameters, column reads and writes, arithmetic,
comparisons, selection, TA-Lib/qtpylib calls, rolling/shift/EWM, helper calls, and
informative merges.

```bash
nfi-bte strategy indicator-program NostalgiaForInfinityX7.py \
--class NostalgiaForInfinityX7 \
--output .nfi/indicator-program.json
```

Every node has a value type, source-order index, source location, inputs, and causal
lookback. Function and node IDs are structural data; strategy and Signal names never
become opcodes. Negative shifts, centered rolling windows, backward fill, dynamic
window sizes, and prefilled informative merges fail closed. An unsupported source
construct must be generalized deliberately before it can enter the Native lane.
63 changes: 56 additions & 7 deletions planning/roadmap-state.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schema_version": "1.0.0",
"roadmap_id": "nfi-backtest-engine-post-v1.1.0",
"revision": 125,
"updated_at": "2026-08-10T17:47:42+09:00",
"revision": 127,
"updated_at": "2026-08-10T21:17:45+09:00",
"acceptance_commands": "planning/acceptance-commands.json",
"active_task_id": null,
"execution_policy": {
Expand Down Expand Up @@ -4656,7 +4656,7 @@
"order": 2002,
"milestone": "M20",
"title": "Define indicator-program-v1",
"status": "pending",
"status": "completed",
"depends_on": [
"M20-01"
],
Expand All @@ -4675,10 +4675,17 @@
"indicator schema tests",
"source mutation and identity tests"
],
"started_at": null,
"completed_at": null,
"commit_sha": null,
"evidence": [],
"started_at": "2026-08-10T20:46:10+09:00",
"completed_at": "2026-08-10T21:17:45+09:00",
"commit_sha": "1e07204306e3109ee4206ab885a26f3532ee9ef5",
"evidence": [
".nfi/roadmap-acceptance/M20-02/06025f7ac6216e8c6c4b65040e6b3d7145527fda/acceptance-report.json",
".nfi/roadmap-acceptance/M20-02/06025f7ac6216e8c6c4b65040e6b3d7145527fda/SHA256SUMS.txt",
".nfi/roadmap-acceptance/M20-02/06025f7ac6216e8c6c4b65040e6b3d7145527fda/indicator-program-contract.json",
".nfi/roadmap-acceptance/M20-02/06025f7ac6216e8c6c4b65040e6b3d7145527fda/latest-program-gap.json",
".nfi/roadmap-acceptance/M20-02/06025f7ac6216e8c6c4b65040e6b3d7145527fda/acceptance/x7-spot/run.json",
".nfi/roadmap-acceptance/M20-02/06025f7ac6216e8c6c4b65040e6b3d7145527fda/acceptance/x7-futures/run.json"
],
"blocker": null
},
{
Expand Down Expand Up @@ -8194,6 +8201,48 @@
"next_eligible_task": "M20-02",
"next_task_manual_gate": false
}
},
{
"sequence": 173,
"timestamp": "2026-08-10T20:46:10+09:00",
"task_id": "M20-02",
"event": "task_started",
"details": {
"objective": "define indicator-program-v1 as a typed causal DAG compiled from source",
"latest_upstream_head_at_start": "06025f7ac6216e8c6c4b65040e6b3d7145527fda",
"input_inventory_task": "M20-01",
"strategy_named_opcodes_allowed": false,
"lookahead_capable_defaults_allowed": false,
"runtime_hardcoding_allowed": false
}
},
{
"sequence": 174,
"timestamp": "2026-08-10T21:17:45+09:00",
"task_id": "M20-02",
"event": "task_completed",
"details": {
"implementation_commit": "1e07204306e3109ee4206ab885a26f3532ee9ef5",
"latest_upstream_commit": "06025f7ac6216e8c6c4b65040e6b3d7145527fda",
"latest_strategy_sha256": "851dcf36183490fa6a84d553e41f92f3613a592ee03d724115007faadc519d94",
"inventory_fingerprint": "292f3827a2be1d3fe780fc7fdf72fdc9240459c8e982b554798d9fd9fd01d454",
"program_fingerprint": "cb278c7294995c0a8c018d44485990612e8370ba4fff7e5d71df0c3c862fc8ca",
"deterministic_repeat_byte_exact": true,
"contract_function_count": 2,
"contract_node_count": 24,
"python_test_count": 729,
"rust_test_count": 145,
"spot_trade_surface_and_full_state_exact": true,
"futures_trade_surface_and_full_state_exact": true,
"strategy_named_opcodes_added": false,
"lookahead_capable_defaults_added": false,
"runtime_hardcoding_added": false,
"latest_x7_fully_compiled": false,
"latest_x7_fail_closed_source": "strategy.py:3168:21",
"native_indicator_kernel_claim": false,
"next_eligible_task": "M20-03",
"next_task_manual_gate": false
}
}
]
}
7 changes: 7 additions & 0 deletions python/nfi_backtest_engine/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,13 @@ def build_parser() -> argparse.ArgumentParser:
strategy_indicator_inventory.add_argument("--upstream-repository")
strategy_indicator_inventory.add_argument("--upstream-commit")
strategy_indicator_inventory.add_argument("--output", "-o", type=Path)
strategy_indicator_program = strategy_commands.add_parser(
"indicator-program",
help="compile the bounded causal indicator DAG into indicator-program-v1",
)
strategy_indicator_program.add_argument("source", type=Path)
strategy_indicator_program.add_argument("--class", dest="class_name")
strategy_indicator_program.add_argument("--output", "-o", type=Path, required=True)
strategy_callback_ir = strategy_commands.add_parser(
"callback-ir",
help="compile source-ordered callback routes, tags, and data dependencies",
Expand Down
17 changes: 17 additions & 0 deletions python/nfi_backtest_engine/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,23 @@ def _execute_strategy(args: argparse.Namespace) -> int:
if args.output:
print(f"indicator inventory report: {args.output}")
return 0
if args.strategy_command == "indicator-program":
from ..indicator_program import compile_indicator_program

program = compile_indicator_program(
args.source,
class_name=args.class_name,
)
write_json(args.output, program)
print(
"indicator program: "
f"class={program['selected_class']}, "
f"functions={len(program['functions'])}, "
f"nodes={len(program['nodes'])}, "
f"inputs={len(program['required_input_columns'])}"
)
print(f"indicator program report: {args.output}")
return 0
if args.strategy_command == "callback-ir":
from ..callback_source_ir import compile_callback_source_ir

Expand Down
Loading