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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ jobs:
run: python -m pytest -q
- name: Validate example signal
run: python scripts/validate_latest_signal.py examples/latest_signal.example.json
- name: Run example overlay replay
run: python scripts/backtest_signal_overlay.py --prices examples/price_history.example.csv --signals examples/signal_history --symbol QQQ
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ Validate the promoted latest artifact when it exists:
python scripts/validate_latest_signal.py
```

Run the synthetic overlay replay:

```bash
python scripts/backtest_signal_overlay.py \
--prices examples/price_history.example.csv \
--signals examples/signal_history \
--symbol QQQ
```

The replay tests a deterministic risk-reducing overlay only. It does not call
AI models and does not treat the example as production evidence.

## Artifact Contract

The latest artifact path is:
Expand All @@ -87,3 +99,16 @@ data/output/signal_history/YYYY-MM-DD.json

All artifacts must remain shadow-only. They cannot encode broker orders, target
quantities, or live allocation overrides.

## Replay Contract

Historical validation should replay stored signal artifacts instead of asking a
model to re-create old judgments. The current example policy is intentionally
conservative:

- no active signal: keep baseline exposure
- `confidence < 0.55`: no-op
- `risk_off`: reduce exposure to `0.5`
- `mixed`: reduce exposure to `0.8`
- severe risk flags such as `liquidity_stress` cap exposure at `0.6`
- the overlay never increases exposure above the baseline
18 changes: 15 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ order routing.

## Validation Strategy

The current minimum check is schema validation for `latest_signal.json`. Future
promotion should add replay tests that consume stored artifacts without calling
model APIs.
The current minimum checks are schema validation and deterministic overlay
replay. Replay consumes stored signal artifacts from `signal_history` and maps
them through a fixed risk-reducing policy. It must never ask a model to recreate
old judgments.

The first overlay harness intentionally measures only:

- final equity
- total return
- maximum drawdown
- average exposure
- exposure turnover

This is enough to identify whether the stored AI context would have reduced
risk or created unacceptable opportunity cost before any runtime integration.

## Risk Notes

Expand Down
19 changes: 19 additions & 0 deletions examples/price_history.example.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
date,symbol,close
2026-01-02,QQQ,100.00
2026-01-09,QQQ,104.00
2026-01-16,QQQ,108.00
2026-01-23,QQQ,106.00
2026-01-30,QQQ,110.00
2026-02-06,QQQ,112.00
2026-02-13,QQQ,109.00
2026-02-20,QQQ,105.00
2026-02-27,QQQ,101.00
2026-03-06,QQQ,96.00
2026-03-13,QQQ,94.00
2026-03-20,QQQ,98.00
2026-03-27,QQQ,102.00
2026-04-03,QQQ,105.00
2026-04-10,QQQ,108.00
2026-04-17,QQQ,111.00
2026-04-24,QQQ,113.00
2026-05-01,QQQ,115.00
26 changes: 26 additions & 0 deletions examples/signal_history/2026-01-02.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"schema_version": "1",
"as_of": "2026-01-02",
"generated_at": "2026-01-02T22:00:00Z",
"mode": "shadow",
"horizon": "1-3 months",
"universe": ["QQQ", "BIL", "BOXX"],
"regime": "risk_on",
"risk_flags": [],
"candidate_bias": {
"QQQ": "positive",
"BIL": "neutral",
"BOXX": "neutral"
},
"confidence": 0.7,
"evidence": {
"sources": ["examples/context_bundle.example.json"],
"summary": "Synthetic risk-on example for overlay replay.",
"data_gaps": ["Synthetic example only."]
},
"expires_at": "2026-02-05",
"policy": {
"execution_allowed": false,
"downstream_use": "Shadow replay only; deterministic policy controls any exposure mapping."
}
}
26 changes: 26 additions & 0 deletions examples/signal_history/2026-02-06.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"schema_version": "1",
"as_of": "2026-02-06",
"generated_at": "2026-02-06T22:00:00Z",
"mode": "shadow",
"horizon": "1-3 months",
"universe": ["QQQ", "BIL", "BOXX"],
"regime": "risk_off",
"risk_flags": ["liquidity_stress"],
"candidate_bias": {
"QQQ": "avoid",
"BIL": "neutral",
"BOXX": "neutral"
},
"confidence": 0.8,
"evidence": {
"sources": ["examples/context_bundle.example.json"],
"summary": "Synthetic risk-off example for overlay replay.",
"data_gaps": ["Synthetic example only."]
},
"expires_at": "2026-03-19",
"policy": {
"execution_allowed": false,
"downstream_use": "Shadow replay only; deterministic policy controls any exposure mapping."
}
}
26 changes: 26 additions & 0 deletions examples/signal_history/2026-03-20.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"schema_version": "1",
"as_of": "2026-03-20",
"generated_at": "2026-03-20T22:00:00Z",
"mode": "shadow",
"horizon": "1-3 months",
"universe": ["QQQ", "BIL", "BOXX"],
"regime": "mixed",
"risk_flags": ["earnings_concentration"],
"candidate_bias": {
"QQQ": "watch",
"BIL": "neutral",
"BOXX": "neutral"
},
"confidence": 0.65,
"evidence": {
"sources": ["examples/context_bundle.example.json"],
"summary": "Synthetic mixed-regime example for overlay replay.",
"data_gaps": ["Synthetic example only."]
},
"expires_at": "2026-04-16",
"policy": {
"execution_allowed": false,
"downstream_use": "Shadow replay only; deterministic policy controls any exposure mapping."
}
}
26 changes: 26 additions & 0 deletions examples/signal_history/2026-04-17.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"schema_version": "1",
"as_of": "2026-04-17",
"generated_at": "2026-04-17T22:00:00Z",
"mode": "shadow",
"horizon": "1-3 months",
"universe": ["QQQ", "BIL", "BOXX"],
"regime": "risk_on",
"risk_flags": [],
"candidate_bias": {
"QQQ": "positive",
"BIL": "neutral",
"BOXX": "neutral"
},
"confidence": 0.72,
"evidence": {
"sources": ["examples/context_bundle.example.json"],
"summary": "Synthetic recovery example for overlay replay.",
"data_gaps": ["Synthetic example only."]
},
"expires_at": "2026-05-31",
"policy": {
"execution_allowed": false,
"downstream_use": "Shadow replay only; deterministic policy controls any exposure mapping."
}
}
51 changes: 51 additions & 0 deletions scripts/backtest_signal_overlay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))

from ai_long_horizon_signal_pipelines.overlay_backtest import ( # noqa: E402
OverlayPolicy,
backtest_overlay,
load_price_history,
load_signals,
)


def main() -> int:
parser = argparse.ArgumentParser(description="Replay shadow AI signals as a deterministic risk overlay.")
parser.add_argument("--prices", default="examples/price_history.example.csv", help="CSV with date,symbol,close")
parser.add_argument("--signals", default="examples/signal_history", help="Signal JSON file or directory")
parser.add_argument("--symbol", default="QQQ", help="Risk asset symbol to test")
parser.add_argument("--min-confidence", type=float, default=0.55)
parser.add_argument("--mixed-exposure", type=float, default=0.8)
parser.add_argument("--risk-off-exposure", type=float, default=0.5)
parser.add_argument("--severe-flag-exposure", type=float, default=0.6)
parser.add_argument("--output", default="", help="Optional JSON summary path")
args = parser.parse_args()

policy = OverlayPolicy(
min_confidence=args.min_confidence,
mixed_exposure=args.mixed_exposure,
risk_off_exposure=args.risk_off_exposure,
severe_flag_exposure=args.severe_flag_exposure,
)
prices = load_price_history(Path(args.prices), symbol=args.symbol)
signals = load_signals(Path(args.signals))
summary = backtest_overlay(prices, signals, policy=policy)
summary["symbol"] = args.symbol.upper()

text = json.dumps(summary, ensure_ascii=True, indent=2) + "\n"
if args.output:
Path(args.output).write_text(text, encoding="utf-8")
print(text, end="")
return 0


if __name__ == "__main__":
raise SystemExit(main())
3 changes: 2 additions & 1 deletion src/ai_long_horizon_signal_pipelines/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Shadow-only long-horizon AI signal artifact helpers."""

from .overlay_backtest import OverlayPolicy, backtest_overlay
from .schema import SignalValidationError, validate_signal

__all__ = ["SignalValidationError", "validate_signal"]
__all__ = ["OverlayPolicy", "SignalValidationError", "backtest_overlay", "validate_signal"]
Loading